From 57124f889afb98e0c7d070c5f4addd1d92adc45c Mon Sep 17 00:00:00 2001
From: huangenjun <1021217094@qq.com>
Date: Tue, 12 May 2026 11:58:59 +0800
Subject: [PATCH 01/11] refactor sdk plugin contract
---
Makefile | 4 +-
README.md | 667 +--
billing.go | 263 -
billing_test.go | 453 --
capability_test.go | 114 -
{devserver => devkit/devserver}/accounts.go | 0
{devserver => devkit/devserver}/context.go | 2 +-
devkit/devserver/doc.go | 5 +
{devserver => devkit/devserver}/logger.go | 0
{devserver => devkit/devserver}/proxy.go | 15 +-
{devserver => devkit/devserver}/scheduler.go | 2 +-
{devserver => devkit/devserver}/server.go | 2 +-
.../devserver}/server_test.go | 2 +-
.../devserver}/static/index.html | 47 -
.../devserver}/static/js/accounts.js | 67 +-
.../devserver}/static/js/app.js | 0
.../devserver}/static/js/form.js | 0
.../devserver}/static/js/scheduler.js | 0
.../devserver}/static/js/utils.js | 9 +
.../devserver}/static/js/widget.js | 26 +-
.../devserver}/static/theme.css | 0
...1-plugin-capability-and-isolation-model.md | 423 --
docs/plugin-style-guide.md | 6 +-
docs/sdk-package-boundaries.md | 111 +
frontend/package-lock.json | 4 +-
frontend/package.json | 2 +-
frontend/src/helpers.ts | 2 +-
frontend/src/index.ts | 44 +
frontend/src/plugin.tsx | 38 +-
frontend/src/tokens.ts | 2 +-
frontend/src/types.ts | 2 +-
grpc/host_client.go | 361 --
host.go | 199 -
models.go | 119 -
protocol/proto/doc.go | 4 +
{proto => protocol/proto}/plugin.pb.go | 5180 ++++++++---------
{proto => protocol/proto}/plugin.proto | 519 +-
{proto => protocol/proto}/plugin_grpc.pb.go | 811 +--
{grpc => runtimego/grpc}/common.go | 57 +-
{grpc => runtimego/grpc}/context.go | 24 +-
{grpc => runtimego/grpc}/context_test.go | 0
{grpc => runtimego/grpc}/convert_test.go | 94 +-
runtimego/grpc/doc.go | 5 +
runtimego/grpc/event_schema_test.go | 87 +
runtimego/grpc/event_server.go | 136 +
{grpc => runtimego/grpc}/extension_client.go | 20 +-
{grpc => runtimego/grpc}/extension_router.go | 2 +-
{grpc => runtimego/grpc}/extension_server.go | 39 +-
{grpc => runtimego/grpc}/gateway_client.go | 20 +-
{grpc => runtimego/grpc}/gateway_server.go | 253 +-
.../grpc}/gateway_stream_test.go | 7 +-
{grpc => runtimego/grpc}/go_plugin.go | 85 +-
{grpc => runtimego/grpc}/handshake.go | 4 +-
runtimego/grpc/host_client.go | 122 +
runtimego/grpc/host_client_test.go | 146 +
runtimego/grpc/json_payload.go | 22 +
.../grpc}/logging_interceptor.go | 2 +-
{grpc => runtimego/grpc}/middleware_client.go | 64 +-
{grpc => runtimego/grpc}/middleware_server.go | 66 +-
{grpc => runtimego/grpc}/plugin_server.go | 22 +-
runtimego/grpc/schema.go | 141 +
{grpc => runtimego/grpc}/ws_server.go | 4 +-
capability.go => sdkgo/capability.go | 84 +-
sdkgo/capability_test.go | 110 +
sdkgo/doc.go | 5 +
errors.go => sdkgo/errors.go | 2 +-
errors_test.go => sdkgo/errors_test.go | 2 +-
sdkgo/event.go | 38 +
extension.go => sdkgo/extension.go | 0
gateway.go => sdkgo/gateway.go | 8 +-
sdkgo/host.go | 91 +
log.go => sdkgo/log.go | 0
log_pretty.go => sdkgo/log_pretty.go | 0
middleware.go => sdkgo/middleware.go | 53 +-
sdkgo/models.go | 117 +
models_test.go => sdkgo/models_test.go | 116 +-
outcome.go => sdkgo/outcome.go | 46 +-
outcome_test.go => sdkgo/outcome_test.go | 2 +-
plugin.go => sdkgo/plugin.go | 14 +-
plugin_test.go => sdkgo/plugin_test.go | 12 +-
sdkgo/schema.go | 62 +
sdkgo/task.go | 74 +
sdkgo/usage.go | 42 +
sdkgo/usage_test.go | 61 +
84 files changed, 5287 insertions(+), 6579 deletions(-)
delete mode 100644 billing.go
delete mode 100644 billing_test.go
delete mode 100644 capability_test.go
rename {devserver => devkit/devserver}/accounts.go (100%)
rename {devserver => devkit/devserver}/context.go (96%)
create mode 100644 devkit/devserver/doc.go
rename {devserver => devkit/devserver}/logger.go (100%)
rename {devserver => devkit/devserver}/proxy.go (91%)
rename {devserver => devkit/devserver}/scheduler.go (99%)
rename {devserver => devkit/devserver}/server.go (99%)
rename {devserver => devkit/devserver}/server_test.go (91%)
rename {devserver => devkit/devserver}/static/index.html (94%)
rename {devserver => devkit/devserver}/static/js/accounts.js (54%)
rename {devserver => devkit/devserver}/static/js/app.js (100%)
rename {devserver => devkit/devserver}/static/js/form.js (100%)
rename {devserver => devkit/devserver}/static/js/scheduler.js (100%)
rename {devserver => devkit/devserver}/static/js/utils.js (90%)
rename {devserver => devkit/devserver}/static/js/widget.js (84%)
rename {devserver => devkit/devserver}/static/theme.css (100%)
delete mode 100644 docs/adr/0001-plugin-capability-and-isolation-model.md
create mode 100644 docs/sdk-package-boundaries.md
delete mode 100644 grpc/host_client.go
delete mode 100644 host.go
delete mode 100644 models.go
create mode 100644 protocol/proto/doc.go
rename {proto => protocol/proto}/plugin.pb.go (53%)
rename {proto => protocol/proto}/plugin.proto (50%)
rename {proto => protocol/proto}/plugin_grpc.pb.go (68%)
rename {grpc => runtimego/grpc}/common.go (88%)
rename {grpc => runtimego/grpc}/context.go (78%)
rename {grpc => runtimego/grpc}/context_test.go (100%)
rename {grpc => runtimego/grpc}/convert_test.go (84%)
create mode 100644 runtimego/grpc/doc.go
create mode 100644 runtimego/grpc/event_schema_test.go
create mode 100644 runtimego/grpc/event_server.go
rename {grpc => runtimego/grpc}/extension_client.go (78%)
rename {grpc => runtimego/grpc}/extension_router.go (97%)
rename {grpc => runtimego/grpc}/extension_server.go (86%)
rename {grpc => runtimego/grpc}/gateway_client.go (93%)
rename {grpc => runtimego/grpc}/gateway_server.go (68%)
rename {grpc => runtimego/grpc}/gateway_stream_test.go (96%)
rename {grpc => runtimego/grpc}/go_plugin.go (60%)
rename {grpc => runtimego/grpc}/handshake.go (87%)
create mode 100644 runtimego/grpc/host_client.go
create mode 100644 runtimego/grpc/host_client_test.go
create mode 100644 runtimego/grpc/json_payload.go
rename {grpc => runtimego/grpc}/logging_interceptor.go (99%)
rename {grpc => runtimego/grpc}/middleware_client.go (66%)
rename {grpc => runtimego/grpc}/middleware_server.go (70%)
rename {grpc => runtimego/grpc}/plugin_server.go (91%)
create mode 100644 runtimego/grpc/schema.go
rename {grpc => runtimego/grpc}/ws_server.go (96%)
rename capability.go => sdkgo/capability.go (57%)
create mode 100644 sdkgo/capability_test.go
create mode 100644 sdkgo/doc.go
rename errors.go => sdkgo/errors.go (88%)
rename errors_test.go => sdkgo/errors_test.go (97%)
create mode 100644 sdkgo/event.go
rename extension.go => sdkgo/extension.go (100%)
rename gateway.go => sdkgo/gateway.go (86%)
create mode 100644 sdkgo/host.go
rename log.go => sdkgo/log.go (100%)
rename log_pretty.go => sdkgo/log_pretty.go (100%)
rename middleware.go => sdkgo/middleware.go (75%)
create mode 100644 sdkgo/models.go
rename models_test.go => sdkgo/models_test.go (58%)
rename outcome.go => sdkgo/outcome.go (79%)
rename outcome_test.go => sdkgo/outcome_test.go (98%)
rename plugin.go => sdkgo/plugin.go (90%)
rename plugin_test.go => sdkgo/plugin_test.go (90%)
create mode 100644 sdkgo/schema.go
create mode 100644 sdkgo/task.go
create mode 100644 sdkgo/usage.go
create mode 100644 sdkgo/usage_test.go
diff --git a/Makefile b/Makefile
index d4e1455..c387f04 100644
--- a/Makefile
+++ b/Makefile
@@ -53,7 +53,7 @@ build: ## 编译检查
theme: ## 构建前端主题包并生成 DevServer 用 theme.css
cd frontend && npm run build
- node --input-type=module -e "import{generateThemeCSS}from'./frontend/dist/css.js';process.stdout.write(generateThemeCSS())" > devserver/static/theme.css
+ node --input-type=module -e "import{generateThemeCSS}from'./frontend/dist/css.js';process.stdout.write(generateThemeCSS())" > devkit/devserver/static/theme.css
@echo "theme.css 已生成"
# ===================== 代码生成 =====================
@@ -79,7 +79,7 @@ proto-tools: ## 安装指定版本的 protoc 和 Go 插件
@echo "protoc-gen-go / protoc-gen-go-grpc 已就绪"
proto: proto-tools ## 重新生成 protobuf 代码
- @cd proto && PATH=$(TOOLS_DIR)/bin:$$PATH $(PROTOC_BIN) \
+ @cd protocol/proto && PATH=$(TOOLS_DIR)/bin:$$PATH $(PROTOC_BIN) \
--go_out=. --go_opt=paths=source_relative \
--go-grpc_out=. --go-grpc_opt=paths=source_relative \
plugin.proto
diff --git a/README.md b/README.md
index d6d9c2e..4047d14 100644
--- a/README.md
+++ b/README.md
@@ -1,208 +1,112 @@
---
-AirGate SDK 是 [airgate-core](https://github.com/DouDOU-start/airgate-core) 插件生态的**协议层**,定义了插件与 core 之间的全部边界:接口契约、共享类型、gRPC 桥接、本地开发服务器和统一前端主题。
+AirGate SDK 是 [AirGate Core](https://github.com/DouDOU-start/airgate-core) 和插件之间的公共契约。它定义插件要实现什么接口、Core 如何启动插件进程、双方如何通过 gRPC 通信,以及插件前端如何复用统一主题和公共组件。
-- **Core** = 用户、账号、调度、计费、限流、订阅、管理后台 —— 平台无关的通用能力
-- **SDK**(本仓库)= 插件如何被装载、被调度、被回调的全部规则
-- **Plugin** = 依赖 SDK 实现接口的独立 Go 进程,提供具体平台的能力
+简单理解:
-同一份契约在 core 和插件两端使用,保证升级不会偏离。底层走 [hashicorp/go-plugin](https://github.com/hashicorp/go-plugin) 的 gRPC 模式,每个插件运行在自己的子进程里,崩溃不影响 core 与其他插件。
+- **Core** 负责用户、账号、调度、限流、插件管理、记录存储和管理后台。
+- **SDK** 定义接口、协议、运行时适配、本地开发工具和前端基础包。
+- **Plugin** 是独立 Go 进程,依赖 SDK 实现具体平台或扩展能力。
+
+## 安装
```bash
go get github.com/DouDOU-start/airgate-sdk@latest
```
-## ✨ 核心特性
-
-- **🔌 三类插件模型** — `GatewayPlugin`(upstream 适配)/ `ExtensionPlugin`(路由 + 后台任务)/ `MiddlewarePlugin`(forward 路径拦截层,旁路观察 / 审计 / 脱敏),详见 [ADR-0001](docs/adr/0001-plugin-capability-and-isolation-model.md)
-- **🛂 能力模型** — 插件显式声明所需的 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](https://github.com/DouDOU-start/airgate-openai) |
-| **扩展插件** | `ExtensionPlugin` | 一切非网关场景。提供路由注册、数据库迁移、后台任务三大基础能力 | [airgate-epay](https://github.com/DouDOU-start/airgate-epay) · [airgate-health](https://github.com/DouDOU-start/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](docs/adr/0001-plugin-capability-and-isolation-model.md)):
-
-- **失败即跳过**:`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`。
+Go 插件通常只需要两个包:
```go
-func (p *MyExtension) Info() sdk.PluginInfo {
- return sdk.PluginInfo{
- ID: "ext-monitor",
- Type: sdk.PluginTypeExtension,
- Capabilities: []string{
- sdk.CapabilityHostListGroups,
- sdk.CapabilityHostProbeForward,
- sdk.CapabilityHostReportAccountResult,
- },
- // ...
- }
-}
+import (
+ sdk "github.com/DouDOU-start/airgate-sdk/sdkgo"
+ runtime "github.com/DouDOU-start/airgate-sdk/runtimego/grpc"
+)
```
-当前 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` 起强制校验。
-
-**命名规范**:`.`。新增 capability 必须在 ADR 里说明语义 / owner / 允许的插件类型。
-
-## 🔁 反向通道 HostService
+前端插件使用:
-过去插件要回调 core(列分组、选号、探测)只能走 admin HTTP API + admin key —— 管理员要手工生成 key、插件要拼 URL 签 Bearer、同机两个进程也被迫走完整 HTTP+JSON 栈。`HostService` 通过 hashicorp/go-plugin 的 `GRPCBroker` 为每个插件子进程架起一条**反向 gRPC stream**,子进程隧道天然互信。
-
-```go
-type MyExtension struct {
- host sdk.Host
+```json
+{
+ "dependencies": {
+ "@airgate/theme": "file:../../airgate-sdk/frontend"
+ }
}
+```
-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 }
+| 目录 | 用途 | 谁会用 |
+|---|---|---|
+| `sdkgo/` | Go 插件接口、共享类型、Capability、Host 调用类型 | 插件作者 |
+| `protocol/proto/` | `airgate.plugin.v2` protobuf 协议和生成代码 | Core / runtime |
+| `runtimego/grpc/` | hashicorp/go-plugin、gRPC bridge、proto 转换、Core 反向调用通道 | 插件入口 / Core 加载器 |
+| `devkit/devserver/` | 本地开发服务器,无需启动完整 Core 即可调试插件 | 插件作者 |
+| `frontend/` | `@airgate/theme`:主题 token、样式隔离、Tailwind helper、公共组件 | 插件前端 |
+| `docs/` | 设计边界和前端样式规范 | 维护者 |
- groups, err := p.host.ListGroups(ctx)
- if err != nil { /* ... */ }
+普通插件业务代码不直接依赖 `protocol/proto`。
- 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(克制暴露面,等真实需求再加):
+| 类型 | 接口 | 用途 |
+|---|---|---|
+| `gateway` | `sdk.GatewayPlugin` | 接入 AI 平台,声明模型、路由、账号字段,并转发请求 |
+| `extension` | `sdk.ExtensionPlugin` | 后台任务、自定义 API、支付、健康监控等非网关能力 |
+| `middleware` | `sdk.MiddlewarePlugin` | forward 前后的旁路拦截,例如审计、脱敏、采样、合规标签 |
-| RPC | 语义 |
-|---|---|
-| `SelectAccount` | 走和真实用户请求完全相同的调度路径选号 |
-| `ProbeForward` | 黑盒探测 chat completion:跳过 `usage_log` / 余额扣款,但仍触发 `ReportResult` |
-| `ListGroups` | 列出所有分组(id / name / platform / 是否独占 / 倍率) |
-| `ReportAccountResult` | 把账号调用结果反馈给 scheduler 的失败计数器 / 状态机 |
+## 如何写一个 Gateway 插件
-**设计原则**(详见 [ADR-0001 §2](docs/adr/0001-plugin-capability-and-isolation-model.md)):
-- **只加字段不删字段**(protobuf 天然向前兼容)
-- **加新 RPC 用新 rpc name**,不 hijack 旧的
-- **新能力必须伴随新 capability flag**,旧插件不声明就不启用
-- **Core 是 trust root**:HostService 所有输入做参数校验,credentials / password_hash / admin key 等敏感字段永远不通过 RPC 流向插件
+Gateway 插件的核心工作只有三件事:
-## 🛠 技术栈
+1. 在 `Info` 中声明插件信息和账号字段。
+2. 在 `Models` / `Routes` 中声明模型和 API 路由。
+3. 在 `Forward` 中把请求转发到真实上游,并返回 `ForwardOutcome`。
-| 层 | 技术 |
-|---|---|
-| 语言 | Go 1.25 |
-| 插件协议 | hashicorp/go-plugin (gRPC + protobuf) |
-| 序列化 | protobuf v3 |
-| 前端主题 | TypeScript · CSS Variables · Tailwind 桥接 |
-| 开发服务器 | net/http + 内嵌 HTML 管理 UI |
+账号管理、添加账号、编辑账号和使用记录页面由插件前端承接。插件通过 `FrontendPages`
+/ `FrontendWidgets` 声明入口,通过 `WebAssetsProvider` 提供静态资源;页面需要的数据走
+插件自己的 API,不进入 `GatewayService`。
-## 🚀 快速开始
-
-### 1. 编写一个最小网关插件
+入口代码:
```go
package main
import (
- "context"
- sdk "github.com/DouDOU-start/airgate-sdk"
- "github.com/DouDOU-start/airgate-sdk/grpc"
+ sdk "github.com/DouDOU-start/airgate-sdk/sdkgo"
+ runtime "github.com/DouDOU-start/airgate-sdk/runtimego/grpc"
)
-type MyGateway struct{}
+type Gateway struct{}
+
+func main() {
+ runtime.Serve(&Gateway{})
+}
+```
+
+关键方法。下面只展示接口形状,不是完整可编译文件:
-func (g *MyGateway) Info() sdk.PluginInfo {
+```go
+func (g *Gateway) Info() sdk.PluginInfo {
return sdk.PluginInfo{
- ID: "gateway-myplatform",
- Name: "My Platform 网关",
- Version: "1.0.0",
- Type: sdk.PluginTypeGateway,
+ ID: "gateway-demo",
+ Name: "Demo Gateway",
+ Version: "0.1.0",
+ SDKVersion: sdk.SDKVersion,
+ Type: sdk.PluginTypeGateway,
AccountTypes: []sdk.AccountType{{
Key: "apikey",
Label: "API Key",
@@ -213,336 +117,235 @@ func (g *MyGateway) Info() sdk.PluginInfo {
}
}
-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 *Gateway) Platform() string { return "demo" }
-func (g *MyGateway) Platform() string { return "myplatform" }
-
-func (g *MyGateway) Models() []sdk.ModelInfo {
+func (g *Gateway) Models() []sdk.ModelInfo {
return []sdk.ModelInfo{{
- ID: "my-model-v1", Name: "My Model V1",
- ContextWindow: 128000, MaxOutputTokens: 16384,
- InputPrice: 1.0, OutputPrice: 3.0,
+ ID: "demo-model",
+ Name: "Demo Model",
+ ContextWindow: 128000,
+ MaxOutputTokens: 8192,
+ Capabilities: []string{sdk.ModelCapChat},
}}
}
-func (g *MyGateway) Routes() []sdk.RouteDefinition {
- return []sdk.RouteDefinition{
- {Method: "POST", Path: "/v1/chat/completions"},
- }
+func (g *Gateway) 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",
+func (g *Gateway) Forward(ctx context.Context, req *sdk.ForwardRequest) (sdk.ForwardOutcome, error) {
+ // 这里请求真实上游,并把响应写入 req.Writer。
+ return sdk.ForwardOutcome{
+ Kind: sdk.OutcomeSuccess,
+ Usage: &sdk.Usage{
+ Model: "demo-model",
+ 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"},
+ },
+ },
}, 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
-}
+完整 `GatewayPlugin` 还需要实现:
-func main() { grpc.Serve(&MyGateway{}) }
-```
+| 方法 | 用途 |
+|---|---|
+| `Init` / `Start` / `Stop` | 插件生命周期 |
+| `ValidateAccount` | 添加账号时验证凭证 |
+| `HandleWebSocket` | 处理 WebSocket;不支持时返回 `sdk.ErrNotSupported` |
-### 2. 本地开发验证(无需部署 core)
+本地调试使用 devserver:
```go
package main
-import (
- "log"
- "github.com/DouDOU-start/airgate-sdk/devserver"
-)
+import "github.com/DouDOU-start/airgate-sdk/devkit/devserver"
func main() {
- if err := devserver.Run(devserver.Config{Plugin: &MyGateway{}}); err != nil {
- log.Fatal(err)
+ if err := devserver.Run(devserver.Config{Plugin: &Gateway{}}); err != nil {
+ panic(err)
}
}
```
-启动后访问 `http://localhost:18080`,即可看到管理 UI,支持账号 CRUD、HTTP/SSE 代理转发、WebSocket 升级、插件前端资源服务。命令行参数 `-addr` / `-data` / `-log` 可覆盖默认配置。
-
-### 3. 构建与发布
-
-```bash
-go build -o my-plugin .
-# 打包:my-plugin.tar.gz 包含二进制 + plugin.yaml
-```
-
-完整范例(含 Makefile / release workflow / 前端嵌入)见 [airgate-openai](https://github.com/DouDOU-start/airgate-openai)。
+## 运行原理
-## 🏗 架构
+AirGate 的插件运行链路如下:
```text
-┌─────────────────────── 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, ...] │
- └─────────────────────────────────────────────────┘
+Core 启动插件子进程
+ -> runtimego/grpc 完成 go-plugin handshake
+ -> Core 调用 Info / Init / Start 获取插件信息
+ -> Core 挂载插件页面、静态资源、schema、健康检查和 API 代理
+ -> Core 按插件类型注册网关、middleware、扩展路由、迁移、后台任务或事件订阅
+ -> 用户请求进入 Core
+ -> Core 完成鉴权、限流和账号调度
+ -> Core 调用 Gateway.Forward
+ -> 插件请求上游并返回 ForwardOutcome
+ -> Core 存储插件返回的 usage/account_cost,按倍率计算用户扣费,更新账号状态,返回用户响应
```
-**请求生命周期(含 middleware chain)**:
+关键边界:
-```text
-用户请求
- │
- ▼
-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 默认只管理插件生命周期、页面入口、静态资源、schema、健康检查和 API 代理。
+- Gateway 插件是主请求链路,Core 会主动调用 `Forward`、账号验证和 WebSocket 处理。
+- SDK 不内置平台计费规则;网关插件计算标准账号成本并填入 `Usage.AccountCost`、`Usage.Attributes`、`Usage.Metrics`、`Usage.CostDetails`。
+- Core 统一入库后,根据用户、分组、模型等倍率写入 `UserCost` / `BillingMultiplier`;倍率规则不进入 SDK。
+- 账号管理和使用记录 UI 由插件提供静态资源,Core 只加载页面、插槽和插件 API 代理,不解释平台字段。
+- Middleware、扩展路由、后台任务、事件订阅、异步任务处理都属于插件显式暴露的能力;没有暴露就不会被 Core 调度。
+- Extension 插件做独立业务扩展,业务入口来自页面、插件 API、后台任务或事件,不应绕过 Core 直接访问核心业务库;需要 Core 能力时通过 `Host.Invoke` 或 `Host.InvokeStream` 回调。
-**反向调用(插件 → Core)**:
+## 插件回调 Core
-```text
-Plugin.probe()
- └─ ctx.(HostAware).Host().ListGroups(ctx)
- │
- │ gRPC stream (GRPCBroker 子进程隧道,无需 admin key)
- ▼
- Core: HostService interceptor
- │
- │ 检查 plugin capability set
- │ 未声明 → PermissionDenied
- ▼
- Core: groupRepo.List()
-```
+插件要回调 Core 能力时,通过 `Host.Invoke` 或 `Host.InvokeStream` 调用。SDK 不为 Core 方法定义强类型接口;Core 自己维护方法注册表,并在加载和调用时校验插件权限、插件类型、请求 schema、是否允许流式和幂等规则。
-**账号模型**:Core 用一张 `accounts` 表存所有平台账号,靠 `platform` + `type` 区分。SDK `Account` 是 core 传给插件的**最小视图**,只包含 `ID / Name / Platform / Type / Credentials / ProxyURL` —— 调度和计费参数全部留在 core。
+这意味着后续扩展 Core 能力通常不需要改 SDK:
-## 🎨 前端集成
-
-插件的前端能力分两种,通过同一套 `WebAssetsProvider` 资源机制提供:
-
-| 模式 | 说明 | 谁控制布局 |
-|---|---|---|
-| **独立页面** `FrontendPages` | 插件拥有完整页面,core 分配路由和导航入口 | 插件 |
-| **组件嵌入** `FrontendWidgets` | 插件提供组件片段,嵌入 core 已有页面的指定 Slot | Core |
+- Core 新增 method,例如 `scheduler.select_account`、`tasks.update`、`notifications.publish`。
+- 插件声明 `host.invoke`,必要时再声明 `host.invoke.` 做细粒度授权。
+- 普通调用使用 `Invoke`,通过 `Payload` 传 JSON 对象语义的参数,通过 `Response.Payload` 读取结果。
+- 流式调用使用 `InvokeStream`,通过 `HostStreamFrame` 双向传递 chunk、progress、result 等帧。
```go
-// 独立页面
-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"},
-},
+Capabilities: []sdk.Capability{
+ sdk.CapabilityHostInvoke,
+ sdk.CapabilityForHostMethod("tasks.update"),
+}
```
-**宿主边界**:Core 拥有路由、导航、弹窗骨架、Slot 位置和生命周期;Widget 只渲染 slot 内部内容,不假设控制整个页面。详见 [docs/plugin-style-guide.md](docs/plugin-style-guide.md)。
+插件在 `Init` 中获取 Host:
-## 🎭 主题系统 `@airgate/theme`
+```go
+func (p *Plugin) Init(ctx sdk.PluginContext) error {
+ if h, ok := ctx.(sdk.HostAware); ok {
+ p.host = h.Host()
+ }
+ return nil
+}
+```
-SDK 在 `frontend/` 目录提供统一的前端主题包,作为 core 和所有插件的颜色/样式**唯一来源**,支持亮暗切换。
+调用 Core 方法:
-```json
-// 插件 package.json
-{ "dependencies": { "@airgate/theme": "file:../../airgate-sdk/frontend" } }
+```go
+resp, err := p.host.Invoke(ctx, sdk.HostInvokeRequest{
+ Method: "tasks.update",
+ Payload: map[string]interface{}{
+ "task_id": taskID,
+ "status": sdk.TaskStatusCompleted.String(),
+ "progress": 100,
+ },
+})
```
-```typescript
-import { cssVar, themeStyle } from '@airgate/theme';
+调用 Core 流式方法:
+
+```go
+stream, err := p.host.InvokeStream(ctx, sdk.HostStreamRequest{
+ Method: "chat.stream",
+ Payload: map[string]interface{}{"prompt": "hello"},
+})
+if err != nil {
+ return err
+}
+defer stream.CloseSend()
-color: cssVar('text') // → 'var(--ag-text, #e8ecf4)'
-backgroundColor: cssVar('bgSurface') // → 'var(--ag-bg-surface, #1c2237)'
+for {
+ frame, err := stream.Recv()
+ if err != nil {
+ return err
+ }
+ if frame.Done {
+ break
+ }
+ // 使用 frame.Event / frame.Payload 处理流式数据。
+}
```
-`@airgate/theme/plugin` 子包额外提供:`ensurePluginStyleFoundation()` 主题注入、`useScopedPluginTheme()` 亮暗跟随、`createPluginTailwindConfig()` Tailwind 桥接,以及 `Field` / `TextInput` / `Button` 等基础 primitives。
+当前内置 capability:
-| 规范 | 说明 |
+| Capability | 用途 |
|---|---|
-| 唯一 token 源 | 颜色/阴影/圆角/字体统一来自 `@airgate/theme` |
-| 作用域隔离 | 插件根节点必须用自己的 scope selector,Tailwind 配 `important` |
-| 不覆盖宿主骨架 | 插件不得重写 core Modal / Page / Sidebar 全局样式 |
-| 亮暗天然可用 | 不写死颜色,所有前景/背景/边框走 token |
+| `host.invoke` | 允许插件调用 Core 开放的方法 |
+| `host.invoke.` | Core method 级细粒度授权,例如 `host.invoke.tasks.update` |
+| `middleware.read_body` | middleware 接收请求/响应 body |
-## 📁 项目结构
+完整规则见 [SDK 包边界](docs/sdk-package-boundaries.md)。
-```text
-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
-```
+## 插件私有数据
-**推荐的插件项目结构**:
+插件私有数据库使用 `plugin_dsn`。Core 注入的 DSN 指向插件独立 schema,插件不得读取 Core 业务库 DSN。
-```text
-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 # 由代码生成的分发文件
+```go
+func (p *Plugin) Init(ctx sdk.PluginContext) error {
+ dsn := sdk.GetPluginDSN(ctx)
+ if dsn == "" {
+ return nil
+ }
+ // 使用插件私有 schema 建表和读写数据。
+ return nil
+}
```
-## 📦 打包与发布
-
-`plugin.yaml` 是由插件代码生成的**分发文件**,仅用于安装和市场展示。**运行时真相始终在插件代码里**,core 不依赖 `plugin.yaml` 做运行时决策。
-
-```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 }
-```
+## 前端插件 SDK
-**打包格式**:
+`frontend/` 发布为 `@airgate/theme`,用于插件前端复用 AirGate 的主题和公共组件。
-```text
-my-plugin.tar.gz
-├── my-plugin # 插件二进制(前端资源已 go:embed 打入)
-└── plugin.yaml # 分发元信息
+| 入口 | 用途 |
+|---|---|
+| `@airgate/theme` | token、CSS 工具、Tailwind bridge、插件前端类型和公共组件统一出口 |
+| `@airgate/theme/plugin` | 插件样式隔离、主题同步、Tailwind helper、公共 UI 组件 |
+| `@airgate/theme/css` | CSS 变量生成和运行时主题注入 |
+| `@airgate/theme/tailwind` | Tailwind 主题桥接 |
+
+推荐插件前端使用:
+
+```tsx
+import {
+ Button,
+ Field,
+ SecretInput,
+ createPluginTailwindConfig,
+ ensurePluginStyleFoundation,
+ useScopedPluginTheme,
+} from "@airgate/theme/plugin";
```
-**发布检查清单**:
-
-- [ ] `go test ./...` / `go vet ./...` 通过
-- [ ] 重新生成最新 `plugin.yaml`
-- [ ] 构建多架构二进制(amd64 / arm64)
-- [ ] 如有前端,构建并嵌入 `dist/`
-- [ ] 打包并验证完整性
+完整样式规则见 [插件前端样式规范](docs/plugin-style-guide.md)。
-## 🔧 SDK 开发工具
-
-```bash
-make lint # 代码检查
-make fmt # 代码格式化
-make test # 运行测试
-make proto # 重新生成 protobuf 代码
-```
+网关插件账号相关 UI 建议使用这些稳定插槽。Core 仍提供通用账号列表和详情框架,
+插件只补平台差异片段;需要完整独立页面时使用 `FrontendPages`。
-## 👀 给 Core 开发者
+| Slot | 用途 |
+|---|---|
+| `account-identity` | 账号标识、套餐、状态等平台差异信息 |
+| `account-create` | 添加账号 |
+| `account-edit` | 编辑账号 |
+| `account-usage-window` | 账号用量窗口、额度、重置时间等平台差异信息 |
+| `usage-metric-detail` | 使用记录里的计量明细,例如 token、模型、思考层级、分辨率、图片张数 |
+| `usage-cost-detail` | 使用记录里的费用明细,例如单价、账号成本、Core 倍率、用户扣费 |
-Core 启动插件后的消费流程:
+## 开发命令
-```text
-启动插件进程(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 业务层处理
+```bash
+make proto # 重新生成 protocol/proto
+GOTOOLCHAIN=local go test ./... # 运行 Go 测试
+GOTOOLCHAIN=local go build ./... # 验证 Go 包可构建
+cd frontend && npm run build # 构建 @airgate/theme
```
-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](docs/adr/0001-plugin-capability-and-isolation-model.md))
-
-## 🤝 贡献 / 反馈
-
-- Bug / Feature: [Issues](https://github.com/DouDOU-start/airgate-sdk/issues)
-- 主仓库: [airgate-core](https://github.com/DouDOU-start/airgate-core)
-- 插件参考实现: [airgate-openai](https://github.com/DouDOU-start/airgate-openai) · [airgate-epay](https://github.com/DouDOU-start/airgate-epay) · [airgate-health](https://github.com/DouDOU-start/airgate-health)
-
-## 📜 License
+## License
MIT
diff --git a/billing.go b/billing.go
deleted file mode 100644
index fca41f8..0000000
--- a/billing.go
+++ /dev/null
@@ -1,263 +0,0 @@
-package sdk
-
-import (
- "strings"
- "time"
-)
-
-// 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" 使用优先级价格
-}
-
-// CostResult 费用计算结果(美元)
-type CostResult struct {
- InputCost float64
- OutputCost float64
- CachedInputCost float64 // cache read 费用
- CacheCreationCost float64 // cache write 费用
-}
-
-// TotalCost 返回费用总和
-func (r CostResult) TotalCost() float64 {
- return r.InputCost + r.OutputCost + r.CachedInputCost + r.CacheCreationCost
-}
-
-// 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 CalculateCost(input CostInput, model ModelInfo) CostResult {
- inputPrice := model.InputPrice / 1_000_000
- outputPrice := model.OutputPrice / 1_000_000
- cachedInputPrice := model.CachedInputPrice / 1_000_000
-
- // 缓存写入价格(Anthropic 两档 TTL,均基于 input 价格):
- // - 5 分钟 TTL:input × 1.25(默认档)
- // - 1 小时 TTL:input × 2.00(长效档)
- // 未显式配置时按官方倍率兜底。
- cacheCreation5mPrice := model.CacheCreationPrice / 1_000_000
- if model.CacheCreationPrice == 0 && model.InputPrice > 0 {
- cacheCreation5mPrice = model.InputPrice * 1.25 / 1_000_000
- }
- cacheCreation1hPrice := model.CacheCreation1hPrice / 1_000_000
- if model.CacheCreation1hPrice == 0 && model.InputPrice > 0 {
- cacheCreation1hPrice = model.InputPrice * 2.0 / 1_000_000
- }
-
- tier := strings.ToLower(strings.TrimSpace(input.ServiceTier))
-
- switch tier {
- case "priority":
- // Priority 档:价格约为标准 × 2。优先使用配置单价,未配置兜底 × 2。
- if model.InputPricePriority > 0 {
- inputPrice = model.InputPricePriority / 1_000_000
- } else {
- inputPrice *= 2.0
- }
- if model.OutputPricePriority > 0 {
- outputPrice = model.OutputPricePriority / 1_000_000
- } else {
- outputPrice *= 2.0
- }
- if model.CachedInputPricePriority > 0 {
- cachedInputPrice = model.CachedInputPricePriority / 1_000_000
- } else {
- cachedInputPrice *= 2.0
- }
- cacheCreationMultiplier := 2.0
- if model.InputPrice > 0 && model.InputPricePriority > 0 {
- cacheCreationMultiplier = model.InputPricePriority / model.InputPrice
- }
- cacheCreation5mPrice *= cacheCreationMultiplier
- cacheCreation1hPrice *= cacheCreationMultiplier
-
- case "fast":
- // Fast 档:OpenAI Codex 官方独立 tier,价格约为标准 × 2.5。
- if model.InputPriceFast > 0 {
- inputPrice = model.InputPriceFast / 1_000_000
- } else {
- inputPrice *= 2.5
- }
- if model.OutputPriceFast > 0 {
- outputPrice = model.OutputPriceFast / 1_000_000
- } else {
- outputPrice *= 2.5
- }
- if model.CachedInputPriceFast > 0 {
- cachedInputPrice = model.CachedInputPriceFast / 1_000_000
- } else {
- cachedInputPrice *= 2.5
- }
- cacheCreation5mPrice *= 2.5
- cacheCreation1hPrice *= 2.5
-
- case "flex", "batch":
- // Flex / Batch 档:价格约为标准 × 0.5。优先使用配置单价,未配置兜底 × 0.5。
- if model.InputPriceFlex > 0 {
- inputPrice = model.InputPriceFlex / 1_000_000
- } else {
- inputPrice *= 0.5
- }
- if model.OutputPriceFlex > 0 {
- outputPrice = model.OutputPriceFlex / 1_000_000
- } else {
- outputPrice *= 0.5
- }
- if model.CachedInputPriceFlex > 0 {
- cachedInputPrice = model.CachedInputPriceFlex / 1_000_000
- } else {
- cachedInputPrice *= 0.5
- }
- cacheCreation5mPrice *= 0.5
- cacheCreation1hPrice *= 0.5
- }
-
- // 长上下文阶梯(仅配置了 LongContextThreshold 的模型启用)
- if model.LongContextThreshold > 0 {
- fullInput := input.InputTokens + input.CachedInputTokens + input.CacheCreationTokens
- if fullInput > model.LongContextThreshold {
- if model.LongContextInputMultiplier > 1 {
- inputPrice *= model.LongContextInputMultiplier
- cacheCreation5mPrice *= model.LongContextInputMultiplier
- cacheCreation1hPrice *= model.LongContextInputMultiplier
- }
- if model.LongContextOutputMultiplier > 1 {
- outputPrice *= model.LongContextOutputMultiplier
- }
- if model.LongContextCachedMultiplier > 1 {
- cachedInputPrice *= model.LongContextCachedMultiplier
- }
- }
- }
-
- // Cache creation 分档计费:
- // - 插件透传了 5m/1h 明细 → 按各自单价分别计算
- // - 插件只透传了总数(breakdown 缺失)→ 全部按 5m 默认档计价(向后兼容)
- var cacheCreationCost float64
- if input.CacheCreation5mTokens > 0 || input.CacheCreation1hTokens > 0 {
- cacheCreationCost = float64(input.CacheCreation5mTokens)*cacheCreation5mPrice +
- float64(input.CacheCreation1hTokens)*cacheCreation1hPrice
- } else {
- cacheCreationCost = float64(input.CacheCreationTokens) * cacheCreation5mPrice
- }
-
- return CostResult{
- InputCost: float64(input.InputTokens) * inputPrice,
- OutputCost: float64(input.OutputTokens) * outputPrice,
- CachedInputCost: float64(input.CachedInputTokens) * cachedInputPrice,
- CacheCreationCost: cacheCreationCost,
- }
-}
-
-// AccountUsageWindow 描述账号的单个用量窗口。
-// 插件负责把平台专属窗口语义归一化到这个结构,Core 只做通用展示。
-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"`
-}
-
-// 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 AccountTodayStats struct {
- Requests int64 `json:"requests"`
- Tokens int64 `json:"tokens"`
- AccountCost float64 `json:"account_cost"`
- UserCost float64 `json:"user_cost"`
-}
-
-// AccountUsageCredits 描述账号的额度信息。
-type AccountUsageCredits struct {
- Balance float64 `json:"balance"`
- Unlimited bool `json:"unlimited"`
-}
-
-// AccountUsageInfo 描述单个账号的通用用量视图。
-//
-// TodayStats 是 Core 本地聚合的当天统计(从 usage_logs 按自然日计算),
-// 和 Windows 是两码事:Windows 反映上游 quota 百分比,TodayStats 反映
-// 本地 gateway 视角的账号当天真实消耗。
-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"`
-}
-
-// AccountUsageError 描述插件在探测账号用量时发现的单账号错误。
-type AccountUsageError struct {
- ID int64 `json:"id"`
- Message string `json:"message"`
-}
-
-// AccountUsageAccountsResponse 是 usage/accounts 之类账号批量用量接口的通用响应。
-type AccountUsageAccountsResponse struct {
- Accounts map[string]AccountUsageInfo `json:"accounts"`
- Errors []AccountUsageError `json:"errors,omitempty"`
-}
-
-// ResetAtFromBase 根据基准时间和 reset_after_seconds 计算绝对重置时间。
-// 负数会被钳制为 0。
-func ResetAtFromBase(base time.Time, resetAfterSeconds int) *time.Time {
- if base.IsZero() {
- base = time.Now()
- }
- sec := resetAfterSeconds
- if sec < 0 {
- sec = 0
- }
- resetAt := base.UTC().Add(time.Duration(sec) * time.Second)
- return &resetAt
-}
-
-// RemainingSecondsUntil 返回从 now 到 resetAt 的剩余秒数。
-// 过期或 nil 一律返回 0。
-func RemainingSecondsUntil(resetAt *time.Time, now time.Time) int {
- if resetAt == nil {
- return 0
- }
- if now.IsZero() {
- now = time.Now()
- }
- diff := int(resetAt.UTC().Sub(now.UTC()).Seconds())
- if diff < 0 {
- return 0
- }
- return diff
-}
-
-// NewAccountUsageWindow 构建通用用量窗口,并同时填充 reset_at / reset_seconds。
-func NewAccountUsageWindow(key, label string, usedPercent float64, resetAt *time.Time, now time.Time) AccountUsageWindow {
- window := AccountUsageWindow{
- Key: key,
- Label: label,
- UsedPercent: usedPercent,
- }
- if resetAt != nil {
- window.ResetAt = resetAt.UTC().Format(time.RFC3339)
- window.ResetSeconds = RemainingSecondsUntil(resetAt, now)
- }
- return window
-}
diff --git a/billing_test.go b/billing_test.go
deleted file mode 100644
index 19d260b..0000000
--- a/billing_test.go
+++ /dev/null
@@ -1,453 +0,0 @@
-package sdk
-
-import (
- "math"
- "testing"
- "time"
-)
-
-// ==================== 费用计算测试 ====================
-
-func almostEqual(a, b float64) bool {
- return math.Abs(a-b) < 1e-12
-}
-
-func TestCalculateCost_Standard(t *testing.T) {
- model := ModelInfo{
- InputPrice: 3.0, // $3/1M tokens
- OutputPrice: 15.0, // $15/1M tokens
- CachedInputPrice: 0.3, // $0.3/1M tokens
- CacheCreationPrice: 3.75, // $3.75/1M tokens (1.25x input)
- }
- result := CalculateCost(CostInput{
- InputTokens: 1000,
- OutputTokens: 500,
- CachedInputTokens: 2000,
- CacheCreationTokens: 1000,
- }, model)
-
- if !almostEqual(result.InputCost, 0.003) {
- t.Errorf("InputCost = %v, want 0.003", result.InputCost)
- }
- if !almostEqual(result.OutputCost, 0.0075) {
- t.Errorf("OutputCost = %v, want 0.0075", result.OutputCost)
- }
- if !almostEqual(result.CachedInputCost, 0.0006) {
- t.Errorf("CachedInputCost = %v, want 0.0006", result.CachedInputCost)
- }
- // cache creation: 1000 × 3.75/1e6 = 0.00375
- if !almostEqual(result.CacheCreationCost, 0.00375) {
- t.Errorf("CacheCreationCost = %v, want 0.00375", result.CacheCreationCost)
- }
- // total: 0.003 + 0.0075 + 0.0006 + 0.00375 = 0.01485
- if !almostEqual(result.TotalCost(), 0.01485) {
- t.Errorf("TotalCost = %v, want 0.01485", result.TotalCost())
- }
-}
-
-func TestCalculateCost_CacheCreationFallback(t *testing.T) {
- // CacheCreationPrice 未配置时,按 input × 1.25 兜底
- model := ModelInfo{
- InputPrice: 3.0,
- OutputPrice: 15.0,
- CachedInputPrice: 0.3,
- // CacheCreationPrice 未设置
- }
- result := CalculateCost(CostInput{
- CacheCreationTokens: 1000,
- }, model)
-
- // fallback: 1000 × (3.0 × 1.25 / 1e6) = 1000 × 3.75e-6 = 0.00375
- if !almostEqual(result.CacheCreationCost, 0.00375) {
- t.Errorf("CacheCreationCost fallback = %v, want 0.00375", result.CacheCreationCost)
- }
-}
-
-func TestCalculateCost_CacheCreation5m1hBreakdown(t *testing.T) {
- // 同时有 5m 和 1h breakdown 时分别计价
- // Claude Sonnet: input $3, cache_5m $3.75, cache_1h $6
- model := ModelInfo{
- InputPrice: 3.0,
- OutputPrice: 15.0,
- CachedInputPrice: 0.3,
- CacheCreationPrice: 3.75, // 5m
- CacheCreation1hPrice: 6.0, // 1h
- }
- result := CalculateCost(CostInput{
- CacheCreationTokens: 3000, // 总数
- CacheCreation5mTokens: 2000,
- CacheCreation1hTokens: 1000,
- }, model)
-
- // 5m: 2000 × 3.75/1e6 = 0.0075
- // 1h: 1000 × 6.0/1e6 = 0.006
- // 合计: 0.0135
- if !almostEqual(result.CacheCreationCost, 0.0135) {
- t.Errorf("CacheCreationCost 5m/1h breakdown = %v, want 0.0135", result.CacheCreationCost)
- }
-}
-
-func TestCalculateCost_CacheCreation1hFallback(t *testing.T) {
- // 只配了 InputPrice,1h 价格按 input × 2 兜底
- model := ModelInfo{
- InputPrice: 3.0,
- }
- result := CalculateCost(CostInput{
- CacheCreation1hTokens: 1000,
- }, model)
-
- // fallback: 1000 × (3.0 × 2.0 / 1e6) = 0.006
- if !almostEqual(result.CacheCreationCost, 0.006) {
- t.Errorf("CacheCreationCost 1h fallback = %v, want 0.006", result.CacheCreationCost)
- }
-}
-
-func TestCalculateCost_CacheCreationBreakdownMissingFallsBackTo5m(t *testing.T) {
- // 没有 5m/1h breakdown 时,所有 CacheCreationTokens 按 5m 价格计(向后兼容)
- model := ModelInfo{
- InputPrice: 3.0,
- CacheCreationPrice: 3.75,
- CacheCreation1hPrice: 6.0,
- }
- result := CalculateCost(CostInput{
- CacheCreationTokens: 1000,
- // 5m/1h 未设置
- }, model)
-
- // 全部按 5m: 1000 × 3.75/1e6 = 0.00375
- if !almostEqual(result.CacheCreationCost, 0.00375) {
- t.Errorf("CacheCreationCost aggregate fallback = %v, want 0.00375", result.CacheCreationCost)
- }
-}
-
-func TestCalculateCost_Priority(t *testing.T) {
- model := ModelInfo{
- InputPrice: 3.0,
- OutputPrice: 15.0,
- CachedInputPrice: 0.3,
- InputPricePriority: 6.0, // 2x
- OutputPricePriority: 30.0,
- CachedInputPricePriority: 0.6,
- }
- result := CalculateCost(CostInput{
- InputTokens: 1000,
- OutputTokens: 500,
- ServiceTier: "priority",
- }, model)
-
- if !almostEqual(result.InputCost, 0.006) {
- t.Errorf("InputCost = %v, want 0.006", result.InputCost)
- }
- if !almostEqual(result.OutputCost, 0.015) {
- t.Errorf("OutputCost = %v, want 0.015", result.OutputCost)
- }
-}
-
-func TestCalculateCost_PriorityFallbackDoublesStandard(t *testing.T) {
- // priority 价格未配置时按标准 × 2 兜底(对齐 OpenAI 官方 priority 档规则)
- model := ModelInfo{
- InputPrice: 3.0,
- OutputPrice: 15.0,
- CachedInputPrice: 0.3,
- }
- result := CalculateCost(CostInput{
- InputTokens: 1000,
- OutputTokens: 500,
- CachedInputTokens: 2000,
- ServiceTier: "priority",
- }, model)
-
- if !almostEqual(result.InputCost, 0.006) {
- t.Errorf("InputCost = %v, want 0.006 (2× fallback)", result.InputCost)
- }
- if !almostEqual(result.OutputCost, 0.015) {
- t.Errorf("OutputCost = %v, want 0.015 (2× fallback)", result.OutputCost)
- }
- if !almostEqual(result.CachedInputCost, 0.0012) {
- t.Errorf("CachedInputCost = %v, want 0.0012 (2× fallback)", result.CachedInputCost)
- }
-}
-
-func TestCalculateCost_Fast(t *testing.T) {
- model := ModelInfo{
- InputPrice: 3.0,
- OutputPrice: 15.0,
- CachedInputPrice: 0.3,
- InputPriceFast: 7.5,
- OutputPriceFast: 37.5,
- CachedInputPriceFast: 0.75,
- }
- result := CalculateCost(CostInput{
- InputTokens: 1000,
- OutputTokens: 500,
- CachedInputTokens: 2000,
- ServiceTier: "fast",
- }, model)
-
- if !almostEqual(result.InputCost, 0.0075) {
- t.Errorf("InputCost = %v, want 0.0075", result.InputCost)
- }
- if !almostEqual(result.OutputCost, 0.01875) {
- t.Errorf("OutputCost = %v, want 0.01875", result.OutputCost)
- }
- if !almostEqual(result.CachedInputCost, 0.0015) {
- t.Errorf("CachedInputCost = %v, want 0.0015", result.CachedInputCost)
- }
-}
-
-func TestCalculateCost_FastFallbackUsesTwoPointFiveX(t *testing.T) {
- model := ModelInfo{
- InputPrice: 3.0,
- OutputPrice: 15.0,
- CachedInputPrice: 0.3,
- }
- result := CalculateCost(CostInput{
- InputTokens: 1000,
- OutputTokens: 500,
- CachedInputTokens: 2000,
- ServiceTier: "fast",
- }, model)
-
- if !almostEqual(result.InputCost, 0.0075) {
- t.Errorf("InputCost = %v, want 0.0075 (2.5× fallback)", result.InputCost)
- }
- if !almostEqual(result.OutputCost, 0.01875) {
- t.Errorf("OutputCost = %v, want 0.01875 (2.5× fallback)", result.OutputCost)
- }
- if !almostEqual(result.CachedInputCost, 0.0015) {
- t.Errorf("CachedInputCost = %v, want 0.0015 (2.5× fallback)", result.CachedInputCost)
- }
-}
-
-func TestCalculateCost_Flex(t *testing.T) {
- // 显式配置的 flex 单价优先
- model := ModelInfo{
- InputPrice: 2.5,
- OutputPrice: 15.0,
- CachedInputPrice: 0.25,
- InputPriceFlex: 1.25,
- OutputPriceFlex: 7.5,
- CachedInputPriceFlex: 0.125,
- }
- result := CalculateCost(CostInput{
- InputTokens: 1000,
- OutputTokens: 500,
- CachedInputTokens: 2000,
- ServiceTier: "flex",
- }, model)
-
- if !almostEqual(result.InputCost, 0.00125) {
- t.Errorf("InputCost = %v, want 0.00125", result.InputCost)
- }
- if !almostEqual(result.OutputCost, 0.00375) {
- t.Errorf("OutputCost = %v, want 0.00375", result.OutputCost)
- }
- if !almostEqual(result.CachedInputCost, 0.00025) {
- t.Errorf("CachedInputCost = %v, want 0.00025", result.CachedInputCost)
- }
-}
-
-func TestCalculateCost_FlexFallbackHalvesStandard(t *testing.T) {
- // flex 价格未配置时按标准 × 0.5 兜底
- model := ModelInfo{
- InputPrice: 2.5,
- OutputPrice: 15.0,
- CachedInputPrice: 0.25,
- }
- result := CalculateCost(CostInput{
- InputTokens: 1000,
- OutputTokens: 500,
- CachedInputTokens: 2000,
- ServiceTier: "batch", // batch 与 flex 同价
- }, model)
-
- if !almostEqual(result.InputCost, 0.00125) {
- t.Errorf("InputCost = %v, want 0.00125 (0.5× fallback)", result.InputCost)
- }
- if !almostEqual(result.OutputCost, 0.00375) {
- t.Errorf("OutputCost = %v, want 0.00375 (0.5× fallback)", result.OutputCost)
- }
- if !almostEqual(result.CachedInputCost, 0.00025) {
- t.Errorf("CachedInputCost = %v, want 0.00025 (0.5× fallback)", result.CachedInputCost)
- }
-}
-
-func TestCalculateCost_LongContext_Standard(t *testing.T) {
- // gpt-5.4 完整 input_tokens = 200k + 80k = 280k > 272k → 长上下文档
- // 标准档 input ×2 / cached ×2 / output ×1.5
- model := ModelInfo{
- InputPrice: 2.5,
- OutputPrice: 15.0,
- CachedInputPrice: 0.25,
- LongContextThreshold: 272_000,
- LongContextInputMultiplier: 2.0,
- LongContextOutputMultiplier: 1.5,
- LongContextCachedMultiplier: 2.0,
- }
- result := CalculateCost(CostInput{
- InputTokens: 200_000,
- CachedInputTokens: 80_000,
- OutputTokens: 10_000,
- }, model)
-
- // input: 200000 × (2.5 × 2 / 1e6) = 1.0
- if !almostEqual(result.InputCost, 1.0) {
- t.Errorf("InputCost = %v, want 1.0", result.InputCost)
- }
- // output: 10000 × (15 × 1.5 / 1e6) = 0.225
- if !almostEqual(result.OutputCost, 0.225) {
- t.Errorf("OutputCost = %v, want 0.225", result.OutputCost)
- }
- // cached: 80000 × (0.25 × 2 / 1e6) = 0.04
- if !almostEqual(result.CachedInputCost, 0.04) {
- t.Errorf("CachedInputCost = %v, want 0.04", result.CachedInputCost)
- }
-}
-
-func TestCalculateCost_LongContext_BelowThreshold(t *testing.T) {
- // 完整 input_tokens = 100k + 80k = 180k < 272k → 不触发长上下文
- model := ModelInfo{
- InputPrice: 2.5,
- OutputPrice: 15.0,
- CachedInputPrice: 0.25,
- LongContextThreshold: 272_000,
- LongContextInputMultiplier: 2.0,
- LongContextOutputMultiplier: 1.5,
- LongContextCachedMultiplier: 2.0,
- }
- result := CalculateCost(CostInput{
- InputTokens: 100_000,
- CachedInputTokens: 80_000,
- OutputTokens: 10_000,
- }, model)
-
- // 按标准价:100000 × 2.5/1e6 = 0.25
- if !almostEqual(result.InputCost, 0.25) {
- t.Errorf("InputCost = %v, want 0.25 (standard)", result.InputCost)
- }
- // 10000 × 15/1e6 = 0.15
- if !almostEqual(result.OutputCost, 0.15) {
- t.Errorf("OutputCost = %v, want 0.15 (standard)", result.OutputCost)
- }
-}
-
-func TestCalculateCost_LongContext_PriorityStacks(t *testing.T) {
- // priority 档先选 priority 单价,再叠加长上下文倍率
- model := ModelInfo{
- InputPrice: 2.5,
- OutputPrice: 15.0,
- CachedInputPrice: 0.25,
- InputPricePriority: 5.0,
- OutputPricePriority: 30.0,
- CachedInputPricePriority: 0.5,
- LongContextThreshold: 272_000,
- LongContextInputMultiplier: 2.0,
- LongContextOutputMultiplier: 1.5,
- LongContextCachedMultiplier: 2.0,
- }
- result := CalculateCost(CostInput{
- InputTokens: 200_000,
- CachedInputTokens: 80_000, // full = 280k > 272k
- OutputTokens: 10_000,
- ServiceTier: "priority",
- }, model)
-
- // input: 200000 × (5 × 2 / 1e6) = 2.0
- if !almostEqual(result.InputCost, 2.0) {
- t.Errorf("InputCost = %v, want 2.0 (priority + longctx)", result.InputCost)
- }
- // output: 10000 × (30 × 1.5 / 1e6) = 0.45
- if !almostEqual(result.OutputCost, 0.45) {
- t.Errorf("OutputCost = %v, want 0.45 (priority + longctx)", result.OutputCost)
- }
- // cached: 80000 × (0.5 × 2 / 1e6) = 0.08
- if !almostEqual(result.CachedInputCost, 0.08) {
- t.Errorf("CachedInputCost = %v, want 0.08 (priority + longctx)", result.CachedInputCost)
- }
-}
-
-func TestCalculateCost_LongContext_FlexStacks(t *testing.T) {
- // flex 档:先半价,再叠加长上下文倍率
- model := ModelInfo{
- InputPrice: 2.5,
- OutputPrice: 15.0,
- CachedInputPrice: 0.25,
- LongContextThreshold: 272_000,
- LongContextInputMultiplier: 2.0,
- LongContextOutputMultiplier: 1.5,
- LongContextCachedMultiplier: 2.0,
- }
- result := CalculateCost(CostInput{
- InputTokens: 200_000,
- CachedInputTokens: 80_000,
- OutputTokens: 10_000,
- ServiceTier: "flex",
- }, model)
-
- // input: 200000 × (2.5 × 0.5 × 2 / 1e6) = 200000 × 2.5e-6 = 0.5
- if !almostEqual(result.InputCost, 0.5) {
- t.Errorf("InputCost = %v, want 0.5 (flex + longctx)", result.InputCost)
- }
- // output: 10000 × (15 × 0.5 × 1.5 / 1e6) = 10000 × 11.25e-6 = 0.1125
- if !almostEqual(result.OutputCost, 0.1125) {
- t.Errorf("OutputCost = %v, want 0.1125 (flex + longctx)", result.OutputCost)
- }
- // cached: 80000 × (0.25 × 0.5 × 2 / 1e6) = 80000 × 0.25e-6 = 0.02
- if !almostEqual(result.CachedInputCost, 0.02) {
- t.Errorf("CachedInputCost = %v, want 0.02 (flex + longctx)", result.CachedInputCost)
- }
-}
-
-func TestCalculateCost_ZeroTokens(t *testing.T) {
- model := ModelInfo{InputPrice: 3.0, OutputPrice: 15.0}
- result := CalculateCost(CostInput{}, model)
-
- if result.TotalCost() != 0 {
- t.Errorf("TotalCost = %v, want 0", result.TotalCost())
- }
-}
-
-// ==================== 账号用量测试 ====================
-
-func TestResetAtFromBaseClampsNegativeSeconds(t *testing.T) {
- base := time.Date(2026, 3, 27, 10, 0, 0, 0, time.UTC)
- resetAt := ResetAtFromBase(base, -30)
- if resetAt == nil {
- t.Fatal("expected resetAt")
- return
- }
- if !resetAt.Equal(base) {
- t.Fatalf("expected resetAt=%s, got %s", base, *resetAt)
- }
-}
-
-func TestRemainingSecondsUntil(t *testing.T) {
- now := time.Date(2026, 3, 27, 10, 0, 0, 0, time.UTC)
- resetAt := time.Date(2026, 3, 27, 10, 5, 0, 0, time.UTC)
- if got := RemainingSecondsUntil(&resetAt, now); got != 300 {
- t.Fatalf("expected 300, got %d", got)
- }
-}
-
-func TestRemainingSecondsUntilExpired(t *testing.T) {
- now := time.Date(2026, 3, 27, 10, 5, 0, 0, time.UTC)
- resetAt := time.Date(2026, 3, 27, 10, 0, 0, 0, time.UTC)
- if got := RemainingSecondsUntil(&resetAt, now); got != 0 {
- t.Fatalf("expected 0, got %d", got)
- }
-}
-
-func TestNewAccountUsageWindow(t *testing.T) {
- now := time.Date(2026, 3, 27, 10, 0, 0, 0, time.UTC)
- resetAt := time.Date(2026, 3, 27, 10, 5, 0, 0, time.UTC)
- window := NewAccountUsageWindow("5h", "5h", 25, &resetAt, now)
- if window.Key != "5h" {
- t.Fatalf("expected key=5h, got %q", window.Key)
- }
- if window.ResetAt != "2026-03-27T10:05:00Z" {
- t.Fatalf("expected reset_at to be serialized, got %q", window.ResetAt)
- }
- if window.ResetSeconds != 300 {
- t.Fatalf("expected reset_seconds=300, got %d", window.ResetSeconds)
- }
-}
diff --git a/capability_test.go b/capability_test.go
deleted file mode 100644
index 9344007..0000000
--- a/capability_test.go
+++ /dev/null
@@ -1,114 +0,0 @@
-package sdk_test
-
-import (
- "reflect"
- "testing"
-
- sdk "github.com/DouDOU-start/airgate-sdk"
-)
-
-func TestIsKnownCapability(t *testing.T) {
- known := []sdk.Capability{
- sdk.CapabilityHostListGroups,
- sdk.CapabilityHostSelectAccount,
- sdk.CapabilityHostProbeForward,
- sdk.CapabilityHostReportAccountResult,
- sdk.CapabilityMiddlewareReadBody,
- }
- for _, c := range known {
- if !sdk.IsKnownCapability(c) {
- t.Errorf("IsKnownCapability(%q) = false, want true", c)
- }
- }
-
- if sdk.IsKnownCapability("host.totally_made_up") {
- t.Error("IsKnownCapability returned true for an unknown capability")
- }
-}
-
-func TestKnownCapabilitiesSortedAndComplete(t *testing.T) {
- caps := sdk.KnownCapabilities()
- if len(caps) < 5 {
- t.Fatalf("KnownCapabilities returned %d entries, expected at least 5", len(caps))
- }
- for i := 1; i < len(caps); i++ {
- if caps[i-1] >= caps[i] {
- t.Errorf("KnownCapabilities not sorted: %q >= %q at index %d", caps[i-1], caps[i], i)
- }
- }
-}
-
-func TestValidateCapabilities_HappyPath(t *testing.T) {
- report := sdk.ValidateCapabilities(sdk.PluginTypeExtension, []sdk.Capability{
- sdk.CapabilityHostListGroups,
- sdk.CapabilityHostProbeForward,
- sdk.CapabilityHostReportAccountResult,
- })
- if report.HasIssues() {
- t.Errorf("HasIssues() = true, want false; report=%+v", report)
- }
- want := []sdk.Capability{
- sdk.CapabilityHostListGroups,
- sdk.CapabilityHostProbeForward,
- sdk.CapabilityHostReportAccountResult,
- }
- // Effective is sorted, so compare against sorted want
- wantSorted := []sdk.Capability{
- sdk.CapabilityHostListGroups, // host.list_groups
- sdk.CapabilityHostProbeForward, // host.probe_forward
- sdk.CapabilityHostReportAccountResult, // host.report_account_result
- }
- _ = want
- if !reflect.DeepEqual(report.Effective, wantSorted) {
- t.Errorf("Effective = %v, want %v", report.Effective, wantSorted)
- }
-}
-
-func TestValidateCapabilities_Unknown(t *testing.T) {
- report := sdk.ValidateCapabilities(sdk.PluginTypeExtension, []sdk.Capability{
- sdk.CapabilityHostListGroups,
- "host.probe_forawrd", // typo of probe_forward
- })
- if !report.HasIssues() {
- t.Fatal("HasIssues() = false, expected true for unknown capability")
- }
- if len(report.Unknown) != 1 || report.Unknown[0] != "host.probe_forawrd" {
- t.Errorf("Unknown = %v, want [host.probe_forawrd]", report.Unknown)
- }
- if len(report.Effective) != 1 || report.Effective[0] != sdk.CapabilityHostListGroups {
- t.Errorf("Effective = %v, want [%v]", report.Effective, sdk.CapabilityHostListGroups)
- }
-}
-
-func TestValidateCapabilities_Denied(t *testing.T) {
- // gateway 插件声明了 extension 专属的 capability
- report := sdk.ValidateCapabilities(sdk.PluginTypeGateway, []sdk.Capability{
- sdk.CapabilityHostProbeForward,
- })
- if !report.HasIssues() {
- t.Fatal("HasIssues() = false, expected true for type-denied capability")
- }
- if len(report.Denied) != 1 || report.Denied[0] != sdk.CapabilityHostProbeForward {
- t.Errorf("Denied = %v, want [%v]", report.Denied, sdk.CapabilityHostProbeForward)
- }
- if len(report.Effective) != 0 {
- t.Errorf("Effective = %v, want empty", report.Effective)
- }
-}
-
-func TestValidateCapabilities_Dedup(t *testing.T) {
- report := sdk.ValidateCapabilities(sdk.PluginTypeExtension, []sdk.Capability{
- sdk.CapabilityHostListGroups,
- sdk.CapabilityHostListGroups, // duplicate
- sdk.CapabilityHostListGroups,
- })
- if len(report.Effective) != 1 {
- t.Errorf("Effective = %v, want exactly 1 entry after dedup", report.Effective)
- }
-}
-
-func TestGetPluginDSN_NilCtx(t *testing.T) {
- if got := sdk.GetPluginDSN(nil); got != "" {
- t.Errorf("GetPluginDSN(nil) = %q, want empty", got)
- }
-}
diff --git a/devserver/accounts.go b/devkit/devserver/accounts.go
similarity index 100%
rename from devserver/accounts.go
rename to devkit/devserver/accounts.go
diff --git a/devserver/context.go b/devkit/devserver/context.go
similarity index 96%
rename from devserver/context.go
rename to devkit/devserver/context.go
index 7f022c8..1f7f016 100644
--- a/devserver/context.go
+++ b/devkit/devserver/context.go
@@ -5,7 +5,7 @@ import (
"os"
"time"
- sdk "github.com/DouDOU-start/airgate-sdk"
+ sdk "github.com/DouDOU-start/airgate-sdk/sdkgo"
)
// devPluginContext 开发模式的 PluginContext 实现
diff --git a/devkit/devserver/doc.go b/devkit/devserver/doc.go
new file mode 100644
index 0000000..d99fd21
--- /dev/null
+++ b/devkit/devserver/doc.go
@@ -0,0 +1,5 @@
+// Package devserver 提供本地插件开发服务器。
+//
+// 本包用于在不启动完整 airgate-core 的情况下调试网关插件。
+// 生产插件二进制不应依赖 devserver。
+package devserver
diff --git a/devserver/logger.go b/devkit/devserver/logger.go
similarity index 100%
rename from devserver/logger.go
rename to devkit/devserver/logger.go
diff --git a/devserver/proxy.go b/devkit/devserver/proxy.go
similarity index 91%
rename from devserver/proxy.go
rename to devkit/devserver/proxy.go
index 16fa7d4..1516a78 100644
--- a/devserver/proxy.go
+++ b/devkit/devserver/proxy.go
@@ -10,7 +10,7 @@ import (
"github.com/gorilla/websocket"
- sdk "github.com/DouDOU-start/airgate-sdk"
+ sdk "github.com/DouDOU-start/airgate-sdk/sdkgo"
)
// ProxyHandler 将请求代理给插件
@@ -106,15 +106,16 @@ func (p *ProxyHandler) handleHTTP(w http.ResponseWriter, r *http.Request) {
return
}
- var tokensIn, tokensOut int
- var model string
+ var model, usageSummary, currency string
+ var accountCost float64
if outcome.Usage != nil {
- tokensIn = outcome.Usage.InputTokens
- tokensOut = outcome.Usage.OutputTokens
model = outcome.Usage.Model
+ usageSummary = outcome.Usage.Summary
+ accountCost = outcome.Usage.AccountCost
+ currency = outcome.Usage.Currency
}
- log.Printf("Forward 完成: kind=%s status=%d model=%s input=%d output=%d duration=%s account=%d(%s)",
- outcome.Kind, outcome.Upstream.StatusCode, model, tokensIn, tokensOut, outcome.Duration,
+ log.Printf("Forward 完成: kind=%s status=%d model=%s account_cost=%.6f%s usage=%s duration=%s account=%d(%s)",
+ outcome.Kind, outcome.Upstream.StatusCode, model, accountCost, currency, usageSummary, outcome.Duration,
account.ID, account.Name)
return
}
diff --git a/devserver/scheduler.go b/devkit/devserver/scheduler.go
similarity index 99%
rename from devserver/scheduler.go
rename to devkit/devserver/scheduler.go
index a3d1ddd..916df7a 100644
--- a/devserver/scheduler.go
+++ b/devkit/devserver/scheduler.go
@@ -9,7 +9,7 @@ import (
"sync"
"time"
- sdk "github.com/DouDOU-start/airgate-sdk"
+ sdk "github.com/DouDOU-start/airgate-sdk/sdkgo"
)
// SchedulePolicy 调度策略
diff --git a/devserver/server.go b/devkit/devserver/server.go
similarity index 99%
rename from devserver/server.go
rename to devkit/devserver/server.go
index 705cbce..570e046 100644
--- a/devserver/server.go
+++ b/devkit/devserver/server.go
@@ -15,7 +15,7 @@ import (
"strings"
"time"
- sdk "github.com/DouDOU-start/airgate-sdk"
+ sdk "github.com/DouDOU-start/airgate-sdk/sdkgo"
)
//go:embed static
diff --git a/devserver/server_test.go b/devkit/devserver/server_test.go
similarity index 91%
rename from devserver/server_test.go
rename to devkit/devserver/server_test.go
index aea71c4..0c352a9 100644
--- a/devserver/server_test.go
+++ b/devkit/devserver/server_test.go
@@ -3,7 +3,7 @@ package devserver
import (
"testing"
- sdk "github.com/DouDOU-start/airgate-sdk"
+ sdk "github.com/DouDOU-start/airgate-sdk/sdkgo"
)
func TestRoutePrefixesDeduplicates(t *testing.T) {
diff --git a/devserver/static/index.html b/devkit/devserver/static/index.html
similarity index 94%
rename from devserver/static/index.html
rename to devkit/devserver/static/index.html
index c4caf2d..74526f4 100644
--- a/devserver/static/index.html
+++ b/devkit/devserver/static/index.html
@@ -372,53 +372,6 @@
text-transform: uppercase;
letter-spacing: 0.04em;
}
- .acct-plan-badge {
- font-size: 0.55rem;
- font-weight: 600;
- padding: 0.08rem 0.35rem;
- border-radius: 9999px;
- text-transform: capitalize;
- margin-left: 0.35rem;
- vertical-align: middle;
- }
- .acct-plan-free { background: #f3f4f6; color: #6b7280; }
- .acct-plan-plus { background: #d1fae5; color: #059669; }
- .acct-plan-pro { background: #ede9fe; color: #7c3aed; }
- .acct-plan-team { background: #dbeafe; color: #2563eb; }
- /* 用量条 */
- .acct-usage {
- display: flex; align-items: center; gap: 0.35rem;
- margin-top: 0.25rem; flex-wrap: wrap;
- }
- .usage-label {
- font-size: 0.6rem; font-weight: 600;
- color: var(--ag-text-secondary);
- min-width: 1.2rem;
- }
- .usage-bar {
- width: 60px; height: 5px;
- background: var(--ag-border);
- border-radius: 3px;
- overflow: hidden;
- }
- .usage-fill {
- display: block; height: 100%;
- border-radius: 3px;
- transition: width 0.3s;
- }
- .usage-ok { background: #22c55e; }
- .usage-warn { background: #f59e0b; }
- .usage-danger { background: #ef4444; }
- .usage-pct {
- font-size: 0.6rem; font-family: var(--ag-font-mono);
- color: var(--ag-text-secondary);
- min-width: 2.5rem;
- }
- .usage-meta {
- font-size: 0.55rem; color: var(--ag-text-tertiary);
- background: var(--ag-bg-surface); padding: 0.1rem 0.35rem;
- border-radius: 3px; margin-right: 4px; white-space: nowrap;
- }
.acct-weight-tag {
font-family: var(--ag-font-mono);
font-size: 0.65rem;
diff --git a/devserver/static/js/accounts.js b/devkit/devserver/static/js/accounts.js
similarity index 54%
rename from devserver/static/js/accounts.js
rename to devkit/devserver/static/js/accounts.js
index 41c7bdb..5a5d21f 100644
--- a/devserver/static/js/accounts.js
+++ b/devkit/devserver/static/js/accounts.js
@@ -1,18 +1,8 @@
// 账号列表、CRUD、连通测试
-import { API, getBadgeStyle, getTypeLabel, maskKey, iconLetter } from './utils.js';
+import { API, getBadgeStyle, getTypeLabel, maskKey, iconLetter, escapeHtml } from './utils.js';
import { loadScheduler } from './scheduler.js';
-/** 从 JWT 中解析 plan_type(不验签) */
-function parsePlanFromJWT(token) {
- try {
- const parts = token.split('.');
- if (parts.length !== 3) return '';
- const payload = JSON.parse(atob(parts[1].replace(/-/g, '+').replace(/_/g, '/')));
- return payload?.['https://api.openai.com/auth']?.chatgpt_plan_type || '';
- } catch { return ''; }
-}
-
export async function loadAccounts() {
const res = await fetch(API + '/api/accounts');
const accounts = await res.json();
@@ -27,17 +17,14 @@ export async function loadAccounts() {
const credKeys = Object.keys(a.credentials || {});
const credHint = credKeys.length > 0 ? maskKey(a.credentials[credKeys[0]]) : '';
const weight = a.weight || 1;
- const planType = a.credentials?.plan_type || parsePlanFromJWT(a.credentials?.access_token || '');
- const planBadge = planType ? `${planType}` : '';
return `
${iconLetter(a.name, a.account_type)}
-
${a.name || '未命名'} ${planBadge}
+
${escapeHtml(a.name || '未命名')}
- ${typeLabel}
- ${credHint ? `${credHint}` : ''}
+ ${escapeHtml(typeLabel)}
+ ${credHint ? `${escapeHtml(credHint)}` : ''}
-
W:${weight}
@@ -48,8 +35,6 @@ export async function loadAccounts() {
`;
}).join('');
- // 异步加载每个账号的用量
- accounts.forEach(a => loadAccountUsage(a.id));
}
export function editWeight(event, id, current) {
@@ -109,47 +94,3 @@ export async function deleteAccount(id) {
loadAccounts();
loadScheduler();
}
-
-/** 查询并展示账号用量(Codex 速率限制) */
-export async function loadAccountUsage(id) {
- const el = document.getElementById('usage-' + id);
- if (!el) return;
- try {
- const res = await fetch(API + '/api/accounts/usage/' + id);
- const data = await res.json();
- if (!data.available || !data.usage) {
- el.style.display = 'none';
- return;
- }
- const u = data.usage;
- const parts = [];
- const wl = (min) => min >= 1440 ? Math.round(min / 1440) + 'd' : min >= 60 ? Math.round(min / 60) + 'h' : min + 'm';
- const bar = (label, pct) => `${label}${pct.toFixed(1)}%`;
- // 总限制(primary_window_minutes > 0 表示头存在)
- if (u.primary_window_minutes > 0) parts.push(bar(wl(u.primary_window_minutes), u.primary_used_percent));
- if (u.secondary_window_minutes > 0) parts.push(bar(wl(u.secondary_window_minutes), u.secondary_used_percent));
- // 模型子限制(bengalfox)
- // 从 limit_name 提取短名,如 "GPT-5.3-Codex-Spark" → "Spark"
- const shortLimit = (u.limit_name || '').split('-').pop() || 'model';
- if (u.bengalfox_primary_window_minutes > 0) {
- parts.push(bar(shortLimit + ' ' + wl(u.bengalfox_primary_window_minutes), u.bengalfox_primary_used_percent));
- }
- if (u.bengalfox_secondary_window_minutes > 0) {
- parts.push(bar(shortLimit + ' ' + wl(u.bengalfox_secondary_window_minutes), u.bengalfox_secondary_used_percent));
- }
- if (parts.length === 0) {
- el.style.display = 'none';
- return;
- }
- el.innerHTML = parts.join('');
- el.style.display = 'flex';
- } catch {
- el.style.display = 'none';
- }
-}
-
-function usageColor(pct) {
- if (pct >= 90) return 'usage-danger';
- if (pct >= 70) return 'usage-warn';
- return 'usage-ok';
-}
diff --git a/devserver/static/js/app.js b/devkit/devserver/static/js/app.js
similarity index 100%
rename from devserver/static/js/app.js
rename to devkit/devserver/static/js/app.js
diff --git a/devserver/static/js/form.js b/devkit/devserver/static/js/form.js
similarity index 100%
rename from devserver/static/js/form.js
rename to devkit/devserver/static/js/form.js
diff --git a/devserver/static/js/scheduler.js b/devkit/devserver/static/js/scheduler.js
similarity index 100%
rename from devserver/static/js/scheduler.js
rename to devkit/devserver/static/js/scheduler.js
diff --git a/devserver/static/js/utils.js b/devkit/devserver/static/js/utils.js
similarity index 90%
rename from devserver/static/js/utils.js
rename to devkit/devserver/static/js/utils.js
index 49c4bd9..42d2699 100644
--- a/devserver/static/js/utils.js
+++ b/devkit/devserver/static/js/utils.js
@@ -39,6 +39,15 @@ export function iconLetter(name, typeKey) {
return (typeKey || '?')[0].toUpperCase();
}
+export function escapeHtml(value) {
+ return String(value ?? '')
+ .replace(/&/g, '&')
+ .replace(//g, '>')
+ .replace(/"/g, '"')
+ .replace(/'/g, ''');
+}
+
export function applyTheme(theme) {
const nextTheme = theme === 'light' ? 'light' : 'dark';
document.documentElement.setAttribute('data-theme', nextTheme);
diff --git a/devserver/static/js/widget.js b/devkit/devserver/static/js/widget.js
similarity index 84%
rename from devserver/static/js/widget.js
rename to devkit/devserver/static/js/widget.js
index a13cc94..d3a8b19 100644
--- a/devserver/static/js/widget.js
+++ b/devkit/devserver/static/js/widget.js
@@ -3,24 +3,26 @@
import { pluginInfo, API } from './utils.js';
-let pluginModule = null;
+const pluginModules = new Map();
let widgetRoot = null;
/** 加载插件前端模块(仅加载一次) */
-async function loadPluginModule() {
- if (pluginModule !== null) return pluginModule;
- const widget = pluginInfo?.frontend_widgets?.find(w => w.slot === 'account-form');
+async function loadPluginModule(slotName) {
+ if (pluginModules.has(slotName)) return pluginModules.get(slotName);
+ const widget = pluginInfo?.frontend_widgets?.find(w => w.slot === slotName);
if (!widget) {
- pluginModule = false;
+ pluginModules.set(slotName, false);
return false;
}
try {
- pluginModule = await import('/plugin-assets/' + widget.entry_file);
+ const mod = await import('/plugin-assets/' + widget.entry_file);
+ pluginModules.set(slotName, mod);
+ return mod;
} catch (e) {
console.warn('加载插件前端模块失败:', e);
- pluginModule = false;
+ pluginModules.set(slotName, false);
+ return false;
}
- return pluginModule;
}
/**
@@ -35,8 +37,10 @@ export async function tryLoadPluginWidget(typeKey, formState) {
const typeCards = document.getElementById('type-cards');
const dialog = document.getElementById('form-dialog');
- const mod = await loadPluginModule();
- if (!mod || !mod.default?.accountForm) {
+ const slotName = formState.mode === 'edit' ? 'account-edit' : 'account-create';
+ const componentName = formState.mode === 'edit' ? 'accountEdit' : 'accountCreate';
+ const mod = await loadPluginModule(slotName);
+ if (!mod || !mod.default?.[componentName]) {
slot.style.display = 'none';
slot.innerHTML = '';
credFields.style.display = '';
@@ -50,7 +54,7 @@ export async function tryLoadPluginWidget(typeKey, formState) {
typeCards.style.display = 'none';
dialog?.setAttribute('data-form-mode', 'plugin');
- const FormComponent = mod.default.accountForm;
+ const FormComponent = mod.default[componentName];
// 初始化 React root(仅一次)
if (!widgetRoot) {
diff --git a/devserver/static/theme.css b/devkit/devserver/static/theme.css
similarity index 100%
rename from devserver/static/theme.css
rename to devkit/devserver/static/theme.css
diff --git a/docs/adr/0001-plugin-capability-and-isolation-model.md b/docs/adr/0001-plugin-capability-and-isolation-model.md
deleted file mode 100644
index b172938..0000000
--- a/docs/adr/0001-plugin-capability-and-isolation-model.md
+++ /dev/null
@@ -1,423 +0,0 @@
-# ADR-0001:插件能力模型与隔离边界
-
-- **状态**:Accepted
-- **日期**:2026-04-10
-- **作者**:AirGate 核心团队
-- **影响范围**:airgate-sdk(proto / pluginsdk / grpc bridge)、airgate-core(plugin manager / host service / forwarder)、所有现有插件(airgate-openai、airgate-health、airgate-epay)
-
----
-
-## 1. Context(为什么要写这份 ADR)
-
-### 1.1 插件系统的演进历史
-
-AirGate 从一开始就把"可插拔"作为架构目标。插件以 hashicorp/go-plugin 子进程形式运行,通过 gRPC 与 core 通信。已经上线的插件有:
-
-- **airgate-openai**(gateway 类型):OpenAI/Anthropic upstream adapter
-- **airgate-epay**(extension 类型):第三方支付接入 + 订单表
-- **airgate-health**(extension 类型):分组级健康监控探测 + 时序表
-
-最初的协议(`plugin.proto` v1)只定义了 **Core → Plugin** 方向的三个服务:`PluginService`(生命周期)、`GatewayService`(upstream 转发)、`ExtensionService`(后台任务 + HTTP 代理)。没有 **Plugin → Core** 的反向通道。
-
-### 1.2 Step 1 补上了反向通道,但留下了四个结构性问题
-
-2026-04-09 的 Step 1 重构里引入了 `HostService`:Core 通过 hashicorp/go-plugin 的 `GRPCBroker` 向每个插件子进程暴露一条反向 gRPC stream,让插件可以回调 core 能力(`SelectAccount` / `ProbeForward` / `ListGroups` / `ReportAccountResult`)。Step 2 用这套机制把 airgate-health 从账号级探测重写为分组级黑盒探测。
-
-Step 2 落地后,我们识别出 4 个结构性问题需要在 Step 3 之前解决:
-
-**问题 1:插件直连 core 业务数据库**
-
-现状:`airgate-health` 和 `airgate-epay` 都通过 `db_dsn` 注入直接拿 core 的 PostgreSQL 连接字符串,然后用 `database/sql` 自己跑 SQL。后果:
-
-- 插件能读写 core 任何表(`accounts` / `users` / `api_keys` / `usage_logs`),没有权限边界
-- 插件与 core schema 强耦合。core 重命名字段或改表结构,插件就挂。`airgate-health/aggregator.go` 里 `SELECT ... FROM groups g JOIN account_groups ag ON ...` 就是这类反模式的典型
-- 没有审计、没有事务边界、没有最小权限原则
-
-**问题 2:HostService 权限完全无差别**
-
-HostService 的 5 个 RPC 对任何插件一视同仁。这导致:
-
-- 一个监控用途的扩展插件可以调 `ReportAccountResult` 污染账号状态机
-- 一个 upstream gateway 插件可以调 `ListGroups` 读到所有分组元信息
-- 无法在审计时回答"这个插件到底用了哪些 core 能力"
-
-**问题 3:Forward 路径没有插件 hook 点**
-
-我们想给 AirGate 加"请求/响应中间层"——例如:
-
-- 内容审计(记录完整 request/response 到独立日志库)
-- 敏感数据脱敏
-- 跨 request 合规标签注入
-- 旁路回放 / 流量镜像 / 异常样本采样
-
-这些需求天然就是 middleware 模式。但当前 `forwarder.go:143 Forward(c *gin.Context)` 是一个闭合流程,内部直接 `buildForwardState → ensureAllowed → selectAccount → prepare → execute → finish`,没有任何第三方插件插桩的地方。
-
-此外,当前的 `GatewayService.Forward` 是为"插件作为 upstream adapter"设计的——一个 gateway 插件**替代**了 upstream。我们想要的 middleware 插件是另一种角色:**旁路观察 + 允许修改 request/response**,不替代 upstream。SDK 里现在根本没有这种角色。
-
-**问题 4:插件类型只有两种**
-
-`sdk.PluginType` 当前只有 `gateway` 和 `extension`。问题 3 里的"请求中间层"既不是 gateway 也不是 extension。强行塞进 extension 会让 extension 的语义越来越模糊。
-
-### 1.3 现在做还是以后做
-
-这些问题不是 Step 2 引入的,是 Step 1 为了"最快打通反向通道"一并带过的历史债。**现在做最便宜**,理由:
-
-- 插件生态还小(3 个官方插件),改动面可控
-- 还没有第三方插件作者,没有向前兼容压力
-- 正准备写"请求中间层"这种新类型的插件,这是最后的窗口
-
-一旦第三方作者开始写插件、或者中间层插件写完再回头改,代价会翻倍。
-
----
-
-## 2. Design Principles(未来所有 SDK 演进的宪法)
-
-### 原则 1:插件永远不直连 core 业务表
-
-core 的业务数据(`accounts` / `groups` / `users` / `usage_logs` / `api_keys` 等)一律通过 `HostService` RPC 访问。**插件不拿 core schema 的 DSN**。
-
-例外:插件仍然可以把**自己的数据**存在 PostgreSQL 里,但必须在独立 schema 下,通过受限 postgres 角色访问(参见原则 5)。
-
-### 原则 2:能力按插件类型分级授权(Capability-based)
-
-不同的 PluginType 拿到不同的 HostService 接口。具体机制:
-
-1. 插件在 `PluginInfo.Capabilities` 里声明它**将要使用**的所有 HostService 能力
-2. Core 启动插件时校验:
- - SDK 版本是否支持这些 capability
- - 插件类型是否有权使用这些 capability(用"插件类型 → 允许的 capability 集合"映射表)
-3. Core 用一个 gRPC interceptor,在每次 HostService RPC 调用时检查"这个插件声明了这个 capability 吗?",未声明直接返回 `PermissionDenied`
-
-这是**最小权限原则**的落地:一个插件拿到的能力是它"明确索取的 ∩ 插件类型允许的"。
-
-### 原则 3:引入 middleware 插件类型
-
-为"请求/响应记录 / 审计 / 脱敏 / 流量采样"这类场景新建一种插件角色:
-
-- 不替代 upstream(那是 gateway 的工作)
-- 不跑后台任务(那是 extension 的工作)
-- **核心职责**:在每次 forward 的前后被 core 回调,可观察、可修改请求/响应
-
-这把"中间件"从"代码里的 interface"抬到了**协议层的 RPC**。任何语言写的插件都能做 middleware,不局限于 Go。
-
-### 原则 4:SDK 协议向前兼容
-
-HostService / MiddlewareService 一旦暴露就按"契约"对待:
-
-- **只加字段不删字段**(protobuf 天然支持)
-- **加新 RPC 用新 rpc name**,不 hijack 旧的
-- **新能力必须伴随新 capability flag**,旧插件不声明就不启用
-- deprecated 字段保留至少 2 个大版本
-- SDK 版本号(`sdk.SDKVersion`)与 proto 的向后兼容性同步
-
-### 原则 5:Core 是 trust root,插件是 untrusted
-
-即便是自己写的插件,也按"不可信"对待:
-
-- 所有 HostService 输入做参数校验(边界 / 类型 / 长度)
-- 插件声明的 capability 不能超过 SDK 版本允许的集合
-- credentials / password_hash / admin_api_key 等敏感字段**永远不通过 RPC 流向插件**(即使 `HostService.GetAccount` 也会脱敏)
-- 插件的 frontend 资源在独立 iframe 或加 sandbox 属性
-- 插件写自己的表用独立 postgres 角色,只授 `USAGE + ALL` 在自己的 schema 上,`public` schema **全部 REVOKE**
-
-### 原则 6:所有决策记录在 ADR 里
-
-未来的 SDK / plugin 架构改动都必须有一份对应的 ADR,遵循本文档的结构(Context → Design → Decision → Migration → Risks)。ADR 一旦 accepted 就不改历史,通过新 ADR `supersedes` 旧 ADR。
-
----
-
-## 3. Decisions(5 个拍板的决策)
-
-### Decision 1(Q1):插件禁止直连 core 业务数据库
-
-**选择**:插件不能读写 `public` schema 下的 core 表。core 业务数据一律通过 `HostService` RPC 访问。插件自己的数据存在独立 schema(见 Decision 5)。
-
-**rejected 方案**:
-- "允许只读" → 仍然有 schema 耦合问题(core 改表名插件就挂)
-- "文档约定" → 靠自觉永远不可靠
-
-**影响**:
-- `airgate-health` 的 `aggregator.go` 中所有 `SELECT ... FROM groups` / `SELECT ... FROM accounts` 要迁移到 HostService RPC。预计改 ~10 个 SQL 语句
-- `airgate-epay` 继续用独立 schema 存 `payment_orders`,不受影响
-- core 不再向插件注入 `db_dsn` 字段。改为注入 `plugin_dsn`(见 Decision 5)
-
-### Decision 2(Q2):middleware 插件起步只做 2 个 hook 点
-
-**选择**:`OnForwardBegin` + `OnForwardEnd`
-
-**签名**:
-
-```go
-type MiddlewarePlugin interface {
- Plugin
-
- // OnForwardBegin 在 core 选完账号、但还没调 upstream 插件之前触发。
- // 允许返回 decision 修改请求(加 header、改 body),或拒绝放行(返回 Deny)。
- OnForwardBegin(ctx context.Context, req *MiddlewareRequest) (*MiddlewareDecision, error)
-
- // OnForwardEnd 在 upstream 插件返回之后、core 写 usage_log 之前触发。
- // 拿到完整的请求 + 响应元数据(但不一定包含 body,见 Decision 3)。
- // 返回 error 不影响主流程(只 log warn),保证 middleware 永远不 block 生产流量。
- OnForwardEnd(ctx context.Context, evt *MiddlewareEvent) error
-}
-```
-
-**不做**(留给未来 ADR):`OnSelectAccount` / `OnFailover` / `OnStreamChunk`
-
-**理由**:
-- Begin + End 覆盖 90% 的用例(日志 / 审计 / 合规标签 / 脱敏)
-- 流式 chunk 级 hook 性能代价大,只在少数场景必要,延后引入
-- 每加一个 hook 都是永久协议承诺,宁缺毋滥
-
-**顺序**:
-- 多个 middleware 按 `Priority`(PluginInfo 新字段)从小到大排序依次调用
-- `OnForwardBegin` 按 priority 升序
-- `OnForwardEnd` 按 priority **降序**(LIFO,像 middleware stack 展开)
-
-**失败语义**:
-- `OnForwardBegin` 返回 error → 该 middleware 被跳过,流程继续(不 block 生产)
-- `OnForwardBegin` 返回 `MiddlewareDecision{Action: Deny}` → 整个请求被拒绝,返回给用户的错误信息来自 decision
-- `OnForwardEnd` 返回 error → log warn,其余 middleware 仍然照常跑
-
-### Decision 3(Q3):middleware payload 采用两段式
-
-**选择**:默认只传元数据;需要 body 的 middleware 显式声明 capability `middleware.read_body`
-
-**MiddlewareRequest / MiddlewareEvent 字段设计**:
-
-```proto
-message MiddlewareRequest {
- // 元数据(默认传)
- string request_id = 1;
- int64 user_id = 2;
- int64 group_id = 3;
- int64 account_id = 4;
- string platform = 5;
- string model = 6;
- bool stream = 7;
- int64 input_tokens_est = 8; // core 侧的粗略估算
-
- // 按需传(声明了 middleware.read_body 的插件才会收到)
- bytes request_body = 100;
- map request_headers = 101;
-}
-
-message MiddlewareEvent {
- string request_id = 1;
- // ... 同上的元数据
-
- int64 status_code = 20;
- int64 duration_ms = 21;
- int64 input_tokens = 22; // 实际值(插件计算后)
- int64 output_tokens = 23;
- int64 first_token_ms = 24;
- string error_kind = 25; // 失败时填
- string error_msg = 26;
-
- // 按需传
- bytes response_body = 100;
- map response_headers = 101;
-}
-```
-
-**理由**:
-- 大部分 middleware(日志 / 计费 / 审计标签)只需要元数据。0 额外开销
-- 内容审计 / 脱敏 / replay 这类真的需要 body 的场景,显式 opt-in
-- 管理员在"插件管理"页能清楚看到"这个 middleware 插件声明了读取 body 的权限",有知情同意
-
-**取舍**:
-- 声明了 `middleware.read_body` 的插件会让该次请求在 core 侧多一次 body 序列化 + gRPC 传输。对开发者透明但成本真实
-- 流式 body 的处理:Begin 阶段能拿到完整 request body;End 阶段流式响应的 response_body 只传**聚合后的首次非空 chunk 拼装**的文本摘要(完整流式内容 hook 留给未来的 `OnStreamChunk`)
-
-### Decision 4(Q4):capability 系统现在就做
-
-**选择**:Step 3 一并落地 capability 模型
-
-**proto 改动**:
-
-```proto
-message PluginInfoResponse {
- // ... 现有字段
- repeated string capabilities = 14; // 新增:插件声明的能力列表
-}
-```
-
-**capability 命名规范**:`.`
-
-当前(Step 1 + Step 2)已经隐式使用的能力,现在要求 **显式声明**:
-
-| Capability | Owner RPC | 允许的插件类型 |
-|---|---|---|
-| `host.list_groups` | `HostService.ListGroups` | extension, middleware |
-| `host.probe_forward` | `HostService.ProbeForward` | extension(只给 probe 子类,详见未来的 PluginSubtype) |
-| `host.select_account` | `HostService.SelectAccount` | extension |
-| `host.report_account_result` | `HostService.ReportAccountResult` | extension(只给 probe 子类) |
-| `middleware.read_body` | 改变 MiddlewareRequest/Event 的 body 字段填充 | middleware |
-
-**未来可能新增**(不在 Step 3 范围内,仅作预留):
-- `host.list_accounts` / `host.get_account` / `host.list_users`(只读业务数据)
-- `host.write_usage_log`(某些计费中间件)
-- `middleware.rewrite_request` / `middleware.rewrite_response`(有副作用的 middleware)
-
-**core 侧实现**:
-1. 插件启动时 `PluginInfo.Capabilities` 和"插件类型 → 允许集合"做交集,产出**有效 capability set**
-2. `HostService` 注册一个 gRPC unary interceptor,每次 RPC 调用时从 `context` 里取出本插件的 capability set,检查当前 method 是否允许
-3. 未允许 → 返回 `status.Errorf(codes.PermissionDenied, "plugin %s lacks capability %s", pluginID, cap)`
-4. 管理员页能看到每个插件的 capability 列表 + 一个"capability 校验失败次数"计数
-
-**迁移**:
-- `airgate-health` 的 `metadata.go` 加 `Capabilities: []string{"host.list_groups", "host.probe_forward", "host.report_account_result"}`
-- `airgate-openai` / `airgate-epay` 当前没用 HostService,`Capabilities` 留空即可
-- 旧版本插件(未声明 Capabilities):**SDK 版本 <= 0.2.x 的插件豁免**(向后兼容),SDK 版本 >= 0.3.x 的插件必须声明。Core 侧按 sdk_version 字段区分
-
-### Decision 5(Q5):插件数据库使用独立 schema
-
-**选择**:同一个 PostgreSQL 实例下为每个有 DB 需求的插件创建独立 schema + 独立受限角色
-
-**机制**:
-
-1. Core 启动时,对每个已加载的插件:
- - 检查 DB 里是否存在 `plugin_` schema,不存在则创建(`CREATE SCHEMA IF NOT EXISTS`)
- - 检查是否存在 `plugin__role` 角色,不存在则创建,密码随机生成存 settings 表
- - `GRANT USAGE, CREATE ON SCHEMA plugin_ TO plugin__role`
- - `REVOKE ALL ON SCHEMA public FROM plugin__role`
-2. Core 向插件注入 `plugin_dsn`(不再叫 `db_dsn`),DSN 里:
- - 用户名 = `plugin__role`
- - 密码 = 上面生成的随机值
- - `search_path=plugin_`(所有 SQL 默认查这个 schema)
-3. 插件 Init 时拿到 `plugin_dsn`,用 `database/sql` 正常连接。插件代码里写 `CREATE TABLE group_health_probes (...)` 会被自动建在 `plugin_airgate_health.group_health_probes`,而 `SELECT * FROM groups` 会因为 `public` schema 的 REVOKE 而直接被 PostgreSQL 拒绝
-
-**rejected 方案**:
-- **独立 DB 实例**:需要额外运维成本(备份 / 连接池 / 监控),对单机部署不友好
-- **KV-only RPC**:airgate-health 的日桶聚合 SQL 没法做
-
-**好处**:
-- 权限在 PostgreSQL 层面强制执行,core 代码层不需要任何审查
-- 插件开发体验几乎不变:原来 `CREATE TABLE health_probes` 现在建在 `plugin_airgate_health.health_probes`,对 SQL 透明
-- 未来想迁移到独立 DB 实例也容易:只要改 DSN,插件代码不变
-
-**迁移**:
-- **airgate-health**:旧表 `public.health_probes` 已在 Step 2 被 DROP;新表 `public.group_health_probes` 在 Step 2 建在了 `public` schema。Step 3 需要把它迁到 `plugin_airgate_health.group_health_probes`。迁移 SQL:`ALTER TABLE public.group_health_probes SET SCHEMA plugin_airgate_health;`
-- **airgate-epay**:类似,`payment_*` 表 SET SCHEMA 到 `plugin_airgate_epay`
-- **airgate-openai**:有 `plugin_openai_session_states` / `plugin_anthropic_digest_sessions`,同样迁移
-- **core 自己的 `plugins` / `plugin_sources` / `plugin_account_usage_snapshots` 表保留在 `public`**(这是 core 自己的表,不是插件的表)
-
----
-
-## 4. Implementation Plan(分步落地)
-
-### Step 3:Capability + Middleware + DB 隔离(这份 ADR 的实现)
-
-**核心产物**:
-- proto 加 `capabilities` 字段、`MiddlewareService` 服务、Middleware 相关 messages
-- sdk/pluginsdk 加 `MiddlewarePlugin` interface、`PluginTypeMiddleware` 常量、capability 常量表
-- airgate-core 加 HostService interceptor + middleware chain + DB isolation 逻辑
-- 文档和样例
-
-**范围边界**:
-- **做**:capability 模型、middleware 接口、DB schema 隔离、已有 3 个插件的迁移
-- **不做**:`OnSelectAccount` / `OnFailover` / `OnStreamChunk`(留给未来 ADR)
-- **不做**:`host.list_accounts` / `host.get_account` 等新业务查询 RPC(等真的有插件要用再加)
-
-### Step 4+:未来 ADR 的候选议题
-
-- ADR-0002:Middleware 流式 hook 点(`OnStreamChunk`)
-- ADR-0003:插件级资源配额与故障隔离(一个失控的 middleware 不能拖垮 core)
-- ADR-0004:插件热更新与 capability 变更(管理员开关某个 capability 后的生效路径)
-- ADR-0005:跨插件事件总线(如果出现 plugin A 需要订阅 plugin B 产生的事件)
-
----
-
-## 5. Migration Plan(三个现有插件怎么升级)
-
-### 5.1 airgate-openai(gateway 插件)
-
-- 不使用 HostService,不使用插件 DB
-- **唯一改动**:`PluginInfo.Capabilities = []string{}`(可空)
-- 风险:低
-
-### 5.2 airgate-epay(extension 插件,payment)
-
-- 不使用 HostService,但使用 `db_dsn`
-- **改动**:
- 1. 配置键从 `db_dsn` 改为 `plugin_dsn`(或者 SDK 新增 helper `ctx.PluginDB()` 返回已配好 search_path 的 `*sql.DB`)
- 2. `PluginInfo.Capabilities = []string{}`
- 3. 启动时 core 自动迁移 `payment_*` 表到 `plugin_airgate_epay` schema
-- 风险:中(DB 迁移需要停机或小心做)
-
-### 5.3 airgate-health(extension 插件,monitoring)
-
-- 使用 HostService + 插件 DB
-- **改动**:
- 1. `PluginInfo.Capabilities = []string{"host.list_groups", "host.probe_forward", "host.report_account_result"}`
- 2. 配置键从 `db_dsn` 改为 `plugin_dsn`
- 3. `aggregator.go` 里 `SELECT ... FROM groups` 的 SQL 迁移到 `host.ListGroups()` RPC 调用
- 4. `group_health_probes` 表 `SET SCHEMA plugin_airgate_health`
- 5. `fillMissingPlatforms` 里 `SELECT platform, COUNT(*) FROM groups GROUP BY platform` 迁到 HostService 新 RPC `ListPlatforms()` —— 或者干脆从 `ListGroups()` 的结果里在 Go 侧聚合
-- 风险:中高(SQL 改动面广)
-
-### 5.4 迁移顺序
-
-1. **Step 3a**:先做 proto + sdk + core 的基础设施改动(capability、middleware 接口、DB 隔离的 core 侧)。向后兼容:旧插件不声明 capability 仍然跑(sdk_version 豁免)
-2. **Step 3b**:迁移 airgate-openai(最简单,验证 capability 声明路径)
-3. **Step 3c**:迁移 airgate-epay(验证 DB schema 隔离的自动迁移路径)
-4. **Step 3d**:迁移 airgate-health(验证"SQL 迁到 RPC"路径,这是最复杂的)
-5. **Step 3e**:写第一个 middleware 插件(建议写 `airgate-audit`,最小 MVP 就是 `OnForwardEnd` 写一行到独立 schema 的 `audit_events` 表)来端到端验证 middleware 接口
-
----
-
-## 6. Risks and Open Questions
-
-### 6.1 风险
-
-**R1:DB 迁移失败导致插件起不来**
-- 缓解:迁移 SQL 写成幂等(`SET SCHEMA` 本身幂等)、遇到已存在的目标表报错时 log + skip
-- 兜底:保留一个"恢复模式"——管理员可以强制插件以旧 DSN 启动一次,手动干预数据
-
-**R2:middleware 插件拖慢关键路径**
-- 缓解:每次 OnForwardBegin/End 调用设 deadline(默认 200ms),超时即跳过该 middleware(log warn)
-- 缓解:middleware chain 总超时预算(默认 500ms),超预算剩下的 middleware 全部跳过
-- 监控:core 暴露 `middleware_latency_ms` / `middleware_timeout_total` 指标
-
-**R3:capability 声明漂移**
-- 问题:插件代码里调 `host.list_groups`,但忘了声明 capability → 每次调用都被 interceptor 拒绝
-- 缓解:插件首次 Dispense 后做一次 self-check——把声明的 capabilities 发给 core,core 返回"你声明的里有 X 个是我不认识的" + "你没声明但未来可能用到的 Y 个是这些"(只 hint)
-
-**R4:向后兼容打破存量插件**
-- 缓解:用 `sdk_version` 字段区分新旧行为。SDK 0.2.x 的插件豁免 capability 校验,SDK 0.3.x 起强制
-- 存量插件给一个 grace period(一个大版本),期间只 log warn 不 block
-
-### 6.2 Open Questions
-
-**Q-open-1:middleware 的链式修改如何 merge?**
-
-如果 middleware A 在 Begin 阶段改了 request header,middleware B 也改了同一个 header,谁赢?
-- **倾向**:按 priority 顺序后来者覆盖,不做自动 merge。但这需要在 `MiddlewareDecision` 里明确语义
-- **延后**:Step 3 MVP 只支持 header 的"追加不覆盖",修改 body 留到有真实需求时再设计
-
-**Q-open-2:capability 需要 versioning 吗?**
-
-如果未来 `host.list_groups` 的返回结构加字段,算不算 breaking?
-- **倾向**:proto 本身的字段增加是向前兼容的,不需要新 capability。但如果语义变化(比如"以后 ListGroups 默认只返回启用的分组"),就必须引入 `host.list_groups.v2`
-- **当前不解决**:等真的遇到了再说
-
-**Q-open-3:middleware 之间能互相通信吗?**
-
-比如 A 在 Begin 里给 request 打了个 tag,B 在 End 里要读这个 tag。
-- **倾向**:在 MiddlewareRequest/Event 里加一个 `map metadata` 字段,所有 middleware 共享一个 KV bag
-- **Step 3**:加这个字段,但不规定命名空间规则。让它自由生长,三个月后复盘是否需要规则
-
----
-
-## 7. References
-
-- Step 1(HostService 引入):提交 `TBD`(本 ADR approve 后在这里回填)
-- Step 2(airgate-health 分组级重写):提交 `TBD`
-- hashicorp/go-plugin GRPCBroker 文档:https://github.com/hashicorp/go-plugin/blob/master/docs/grpc-broker.md
-- Capability-based security(Wikipedia):https://en.wikipedia.org/wiki/Capability-based_security
-
----
-
-## 8. Changelog
-
-- **2026-04-10**:v1 draft,AirGate 核心团队
diff --git a/docs/plugin-style-guide.md b/docs/plugin-style-guide.md
index bd5245e..b935af5 100644
--- a/docs/plugin-style-guide.md
+++ b/docs/plugin-style-guide.md
@@ -27,6 +27,8 @@ your-plugin/
## 2. 依赖配置
+插件前端 SDK 的正式包名是 `@airgate/theme`。插件业务代码优先使用 `@airgate/theme/plugin`,该入口提供主题初始化、样式作用域、Tailwind bridge、插件前端类型和公共 UI 组件。
+
### package.json
```json
@@ -151,7 +153,7 @@ import { ensurePluginStyles } from './theme/runtime';
ensurePluginStyles();
export default {
- accountForm: YourComponent, // 或 routes / menuItems
+ accountCreate: YourComponent, // 或 accountEdit / accountIdentity / accountUsageWindow / usageMetricDetail / usageCostDetail / routes / menuItems
};
```
@@ -245,7 +247,7 @@ export function YourComponent(props) {
## 6. SDK 提供的 UI 组件
-SDK 提供了一套预制组件(`@airgate/theme/plugin`),样式与 Core 保持一致,**优先使用这些组件**:
+SDK 提供了一套预制组件(`@airgate/theme/plugin`),样式与 Core 保持一致,属于插件前端稳定公共契约。插件业务 UI **优先使用这些组件**:
```tsx
import {
diff --git a/docs/sdk-package-boundaries.md b/docs/sdk-package-boundaries.md
new file mode 100644
index 0000000..351d09f
--- /dev/null
+++ b/docs/sdk-package-boundaries.md
@@ -0,0 +1,111 @@
+# SDK 包边界
+
+本文定义 `airgate-sdk` 单仓库内的包职责边界。新增能力必须先判断归属,不能默认修改 `sdkgo` 根接口。
+
+## 分层
+
+| 包 | 职责 | 可依赖 | 不应包含 |
+| --- | --- | --- | --- |
+| `sdkgo` | 插件作者 API、共享类型、capability helper、日志 helper | Go 标准库、少量稳定依赖 | protobuf、gRPC、go-plugin、devserver、Core 产品逻辑 |
+| `protocol/proto` | protobuf schema 与生成代码 | protobuf runtime | 插件业务 helper、Core 实现细节 |
+| `runtimego/grpc` | go-plugin/gRPC 适配、stream bridge、proto 转换、Core 反向调用 broker | `sdkgo`、`protocol/proto` | 插件业务逻辑、devserver UI |
+| `devkit/devserver` | 本地开发服务器和 fake core 能力 | `sdkgo` | 生产运行时依赖、Core 数据库访问 |
+| `frontend` | 前端插件 API、主题 token、样式注入、Tailwind bridge、公共 UI 组件 | TypeScript 生态、React peer dependency | Go runtime、Core 后端逻辑、具体插件业务页面 |
+
+## 新需求判断
+
+只有以下变化可以修改 `sdkgo` 稳定接口:
+
+- 新增稳定插件角色或生命周期钩子。
+- 新增跨插件通用领域原语。
+- 新增需要 SDK 表达的跨插件通用 capability。
+- 修正已有公开契约的错误或缺口。
+
+只有以下变化可以修改 `frontend` 稳定接口:
+
+- 新增跨插件通用的主题 token、CSS 变量或 Tailwind bridge 能力。
+- 新增多个插件都会复用的基础 UI 组件。
+- 新增插件前端模块加载、账号表单、路由、菜单、OAuth 桥接等公共类型。
+- 修正已有公共组件、样式 helper 或主题契约的错误。
+
+以下变化不应直接修改 `sdkgo` 根接口:
+
+- 某个 provider 新增参数、route、错误格式或模型字段。
+- 某个 UI 页面需要额外产品数据。
+- 某个插件需要私有后台任务状态。
+- 某个平台新增计费规则、套餐、价格档位、重置窗口或用量展示字段。
+- 某个实验性功能只服务单个官方插件。
+- Core 内部实现为了方便调用而需要的 helper。
+
+这些变化应优先放到 manifest、插件私有 metadata、Core 方法注册表、插件私有数据库或明确 schema 的插件 API 中。
+
+前端页面需求应优先进入具体插件前端代码。只有当样式、组件或类型能被多个插件稳定复用时,才进入 `frontend`。
+
+## 弱契约扩展点
+
+SDK 提供少量弱契约扩展点,用来承接展示、分类和通用计量等变化,避免为每个插件需求新增强类型字段:
+
+- `PluginInfo.Metadata`:插件市场分类、标签、展示提示等。
+- `ModelInfo.Metadata`:模型家族、展示分组、供应商标签等。
+- `RouteDefinition.Metadata`:路由文档链接、展示分组、调试提示等。
+- `Usage.Attributes`:模型、思考层级、分辨率、质量档、服务档位等非数值审计维度。
+- `Usage.Metrics`:图片张数、视频秒数、音频分钟数、工具调用次数、token 等插件计算后的通用计量结果。
+- `Usage.CostDetails`:费用明细;插件填账号成本,Core 填用户扣费和倍率。
+- `Usage.Metadata`:单次调用的展示或审计辅助信息。
+- `EventHandler`:Core 向插件推送标准事件。
+- `Host.Invoke` / `Host.InvokeStream`:插件用 `method + payload` 调用 Core 开放的方法,必须由 `host.invoke` 或 `host.invoke.` capability 门控。
+- `SchemaProvider`:插件声明 routes、tasks、events、invokes 的 payload schema。
+
+这些字段不能用于权限、调度、账号状态机或敏感数据传递。平台计费规则不得进入 SDK;网关插件负责计算标准账号成本 `Usage.AccountCost` / `Currency` 和审计明细。Core 统一入库、索引、汇总,并写入 `UserCost` / `BillingMultiplier`。
+
+## 用量与计费边界
+
+SDK 不提供 `CalculateCost`、价格档位、token 拆分公式或平台套餐模型。
+
+- 模型声明只包含 `ID`、`Name`、上下文窗口、最大输出和能力标签。
+- 单次调用的标准账号成本由网关插件写入 `Usage.AccountCost` / `Usage.Currency`。
+- Core 根据用户、分组、模型等倍率计算用户侧扣费,写入 `Usage.UserCost` / `Usage.BillingMultiplier`。
+- 模型、思考层级、分辨率、质量档等非数值维度统一放入 `Usage.Attributes`。
+- token、图片、音频、视频、请求数等数值明细统一放入 `Usage.Metrics`。
+- 标准账号成本和用户扣费拆分统一放入 `Usage.CostDetails`:插件填 `AccountCost`,Core 填 `UserCost` / `BillingMultiplier`。
+- 使用记录和账号管理页面由插件前端与插件私有 API 实现,SDK 不定义账号用量查询 RPC。
+- Core 不应把平台规则写入 SDK 或 Core 公共逻辑;Core 统一入库 `Usage`,需要页面时加载插件的静态资源和 API 代理。
+
+## 网关前端边界
+
+网关插件可以通过 `FrontendPages` / `FrontendWidgets` 声明账号相关入口,通过 `WebAssetsProvider` 提供静态资源。Core 负责加载资源、传递登录上下文和代理插件 API,不理解页面内部数据结构。
+
+账号管理不作为整页 slot。Core 保留通用账号列表和详情框架,插件只补平台差异片段:
+
+- `account-identity`:账号标识、套餐、状态等平台差异信息。
+- `account-create`:添加账号。
+- `account-edit`:编辑账号。
+- `account-usage-window`:账号用量窗口、额度、重置时间等平台差异信息。
+- `usage-metric-detail`:使用记录里的计量明细,例如 token、模型、思考层级、分辨率、图片张数。
+- `usage-cost-detail`:使用记录里的费用明细,例如单价、账号成本、Core 倍率、用户扣费。
+
+新增平台页面优先放在插件自己的 `FrontendPages`;只有多个插件都需要同一宿主位置时,才新增通用 slot。
+
+## Host 调用约束
+
+SDK 的 `Host` 接口只保留通用调用通道:`Invoke(ctx, req)` 和 `InvokeStream(ctx, req)`。新增 Core 宿主能力不得向 `Host` 追加业务方法;应在 Core 注册一个 method,并声明 method 的权限、schema、传输模式和实现。
+
+Core method 必须至少定义:
+
+- method 名称,例如 `scheduler.select_account`、`tasks.update`。
+- 允许的插件类型和可调用插件范围。
+- 所需 capability,至少 `host.invoke`,敏感方法应使用 `host.invoke.`。
+- 请求、响应和流式 frame payload schema。
+- 传输模式:普通 request/response、server stream、client stream 或 bidirectional stream。
+- 幂等策略、敏感字段暴露规则和审计日志。
+
+SDK 只提供传输契约和自检 helper,不承载 Core 方法枚举。这样 Core 后续扩展 method 时,不需要为每个能力修改 SDK。
+
+## 当前导入路径
+
+- 插件业务代码使用 `github.com/DouDOU-start/airgate-sdk/sdkgo`。
+- 插件运行入口使用 `github.com/DouDOU-start/airgate-sdk/runtimego/grpc`。
+- 本地开发工具使用 `github.com/DouDOU-start/airgate-sdk/devkit/devserver`。
+- 普通插件业务代码不直接导入 `protocol/proto`。
+- 插件前端使用 `@airgate/theme/plugin` 引用样式隔离、主题同步、Tailwind helper 和公共 UI 组件。
+- 宿主前端或工具代码可使用 `@airgate/theme`、`@airgate/theme/css`、`@airgate/theme/tailwind` 引用 token 与主题桥接能力。
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 73475b6..02c227f 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@airgate/theme",
- "version": "0.1.0",
+ "version": "2.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@airgate/theme",
- "version": "0.1.0",
+ "version": "2.0.0",
"devDependencies": {
"@types/react": "^19.0.0",
"react": "^19.0.0",
diff --git a/frontend/package.json b/frontend/package.json
index fcb3ae1..6c721e9 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -1,6 +1,6 @@
{
"name": "@airgate/theme",
- "version": "0.1.0",
+ "version": "2.0.0",
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
diff --git a/frontend/src/helpers.ts b/frontend/src/helpers.ts
index 29f44fa..667773e 100644
--- a/frontend/src/helpers.ts
+++ b/frontend/src/helpers.ts
@@ -9,7 +9,7 @@ const defaultThemeCssVarMap = createThemeCssVarMap();
const defaultStaticCssVarMap = createStaticCssVarMap();
/**
- * 获取带 fallback 的 CSS var() 引用。
+ * 获取带默认值的 CSS var() 引用。
* 同时支持主题 token 和静态 token。
*
* @example
diff --git a/frontend/src/index.ts b/frontend/src/index.ts
index ab17108..79a5f71 100644
--- a/frontend/src/index.ts
+++ b/frontend/src/index.ts
@@ -48,7 +48,14 @@ export type { TokenName } from './helpers.js';
export { cssVar, themeStyle } from './helpers.js';
export type {
+ AccountSurfaceProps,
AccountFormProps,
+ BadgeProps,
+ ButtonProps,
+ ButtonVariant,
+ CardProps,
+ FieldProps,
+ FormActionsProps,
PluginBatchAccountInput,
PluginBatchImportResult,
PluginFrontendModule,
@@ -57,6 +64,43 @@ export type {
PluginOAuthBridge,
PluginOAuthExchangeResult,
PluginOAuthStartResult,
+ PanelHeaderProps,
PluginPlatformIconProps,
+ PluginStatusKind,
+ PluginStyleFoundationOptions,
+ PluginTailwindConfigOptions,
PluginRouteDefinition,
+ ResolvePluginThemeOptions,
+ ScopedPluginThemeOptions,
+ SectionProps,
+ SelectableCardProps,
+ StatusTextProps,
+ UsageRecordSurfaceProps,
+} from './plugin.js';
+
+// 插件前端 SDK:样式注入、主题同步、Tailwind bridge 和公共组件
+export {
+ DEFAULT_PLUGIN_CLASS_PREFIX,
+ DEFAULT_PLUGIN_FOUNDATION_STYLE_ID,
+ DEFAULT_PLUGIN_THEME_ATTRIBUTE,
+ DEFAULT_PLUGIN_THEME_STYLE_ID,
+ Badge,
+ Button,
+ Card,
+ Field,
+ FormActions,
+ PanelHeader,
+ Section,
+ SecretInput,
+ SelectableCard,
+ StatusText,
+ TextInput,
+ TextArea,
+ cn,
+ createPluginTailwindConfig,
+ ensurePluginStyleFoundation,
+ injectStyle,
+ pluginFoundationCssText,
+ resolvePluginTheme,
+ useScopedPluginTheme,
} from './plugin.js';
diff --git a/frontend/src/plugin.tsx b/frontend/src/plugin.tsx
index 57812a6..aea01ff 100644
--- a/frontend/src/plugin.tsx
+++ b/frontend/src/plugin.tsx
@@ -113,10 +113,26 @@ export interface PluginPlatformIconProps {
style?: CSSProperties;
}
+export interface AccountSurfaceProps {
+ accountId?: string | number;
+ accountType?: string;
+ context?: Record;
+}
+
+export interface UsageRecordSurfaceProps {
+ recordId?: string | number;
+ context?: Record;
+}
+
export interface PluginFrontendModule {
routes?: PluginRouteDefinition[];
menuItems?: PluginMenuItemDefinition[];
- accountForm?: ComponentType;
+ accountIdentity?: ComponentType;
+ accountCreate?: ComponentType;
+ accountEdit?: ComponentType;
+ accountUsageWindow?: ComponentType;
+ usageMetricDetail?: ComponentType;
+ usageCostDetail?: ComponentType;
platformIcon?: ComponentType;
}
@@ -562,7 +578,7 @@ export function cn(...values: Array): string
return values.filter(Boolean).join(' ');
}
-interface FieldProps {
+export interface FieldProps {
label: ReactNode;
required?: boolean;
hint?: ReactNode;
@@ -595,7 +611,7 @@ export function TextArea({ className, ...props }: TextareaHTMLAttributes;
}
-interface PanelHeaderProps {
+export interface PanelHeaderProps {
title: ReactNode;
description?: ReactNode;
eyebrow?: ReactNode;
@@ -612,7 +628,7 @@ export function PanelHeader({ title, description, eyebrow, className }: PanelHea
);
}
-interface SectionProps extends PanelHeaderProps {
+export interface SectionProps extends PanelHeaderProps {
children: ReactNode;
panel?: boolean;
contentClassName?: string;
@@ -635,7 +651,7 @@ export function Section({
);
}
-interface CardProps {
+export interface CardProps {
children: ReactNode;
className?: string;
}
@@ -644,7 +660,7 @@ export function Card({ children, className }: CardProps) {
return {children}
;
}
-interface SelectableCardProps extends ButtonHTMLAttributes {
+export interface SelectableCardProps extends ButtonHTMLAttributes {
active?: boolean;
}
@@ -660,7 +676,7 @@ export function SelectableCard({ active = false, className, children, ...props }
);
}
-type ButtonVariant = 'primary' | 'secondary' | 'outline';
+export type ButtonVariant = 'primary' | 'secondary' | 'outline';
const buttonClassMap: Record = {
primary: 'agw-button-primary',
@@ -668,7 +684,7 @@ const buttonClassMap: Record = {
outline: 'agw-button-outline',
};
-interface ButtonProps extends ButtonHTMLAttributes {
+export interface ButtonProps extends ButtonHTMLAttributes {
variant?: ButtonVariant;
}
@@ -684,7 +700,7 @@ export function Button({ variant = 'secondary', className, children, ...props }:
);
}
-interface FormActionsProps {
+export interface FormActionsProps {
children: ReactNode;
className?: string;
}
@@ -693,7 +709,7 @@ export function FormActions({ children, className }: FormActionsProps) {
return {children}
;
}
-interface BadgeProps {
+export interface BadgeProps {
children: ReactNode;
tone?: 'neutral' | 'success' | 'violet' | 'info';
className?: string;
@@ -710,7 +726,7 @@ export function Badge({ children, tone = 'neutral', className }: BadgeProps) {
return {children};
}
-interface StatusTextProps {
+export interface StatusTextProps {
type: PluginStatusKind;
text: string;
}
diff --git a/frontend/src/tokens.ts b/frontend/src/tokens.ts
index 240d998..5fcba6e 100644
--- a/frontend/src/tokens.ts
+++ b/frontend/src/tokens.ts
@@ -160,7 +160,7 @@ export const staticTokenGroups: StaticTokenGroups = {
appShell: appShellTokens,
};
-/** 不随主题变化的静态 token(向后兼容的扁平导出) */
+/** 不随主题变化的静态 token */
export const staticTokens: StaticTokens = {
...foundationTokens,
...appShellTokens,
diff --git a/frontend/src/types.ts b/frontend/src/types.ts
index 47e8c1c..41a9ffe 100644
--- a/frontend/src/types.ts
+++ b/frontend/src/types.ts
@@ -89,7 +89,7 @@ export interface AppShellTokens {
topbarHeight: string;
}
-/** 不随主题变化的 token(保持向后兼容的扁平结构) */
+/** 不随主题变化的 token */
export interface StaticTokens extends FoundationTokens, AppShellTokens {}
export interface StaticTokenGroups {
diff --git a/grpc/host_client.go b/grpc/host_client.go
deleted file mode 100644
index 7e17dc0..0000000
--- a/grpc/host_client.go
+++ /dev/null
@@ -1,361 +0,0 @@
-package grpc
-
-import (
- "context"
- "io"
- "log/slog"
- "time"
-
- sdk "github.com/DouDOU-start/airgate-sdk"
- pb "github.com/DouDOU-start/airgate-sdk/proto"
-)
-
-// hostClient 把 pb.HostServiceClient 包装成 sdk.Host 接口。
-// 插件代码看到的是 sdk.Host(plain Go),不直接接触 protobuf 类型,
-// 这样未来 proto 演进时插件源码不需要跟着改。
-//
-// 日志策略:core→plugin 的 conn 由 hashicorp/go-plugin 内部建立,我们没法装拦截器,
-// 所以这里手写 RPC 进入 / 失败 / 完成日志。Error 强制打点;正常路径只 Debug。
-// 跨进程链路靠 LoggingUnaryClientInterceptor 写入 outgoing metadata 的 request_id 串起来。
-type hostClient struct {
- c pb.HostServiceClient
-}
-
-// NewHostClient 用一个 grpc client 构造 sdk.Host。
-// 一般由 grpcPluginContext.Host() lazy 调用,不建议插件直接构造。
-func NewHostClient(c pb.HostServiceClient) sdk.Host {
- return &hostClient{c: c}
-}
-
-// hostRPCLogger 派生 host 调用专用 logger,并返回起始时间。
-func hostRPCLogger(ctx context.Context, method string) (*slog.Logger, time.Time) {
- return sdk.LoggerFromContext(ctx).With("host_rpc", method), time.Now()
-}
-
-// ── 调度 ──
-
-func (h *hostClient) SelectAccount(ctx context.Context, req sdk.HostSelectAccountRequest) (*sdk.HostSelectAccountResult, error) {
- logger, start := hostRPCLogger(ctx, "SelectAccount")
- resp, err := h.c.SelectAccount(ctx, &pb.HostSelectAccountRequest{
- GroupId: req.GroupID,
- Model: req.Model,
- SessionId: req.SessionID,
- ExcludeAccountIds: req.ExcludeAccountIDs,
- })
- if err != nil {
- logger.Error("host_call_select_account_failed",
- sdk.LogFieldGroupID, req.GroupID,
- sdk.LogFieldModel, req.Model,
- sdk.LogFieldDurationMs, time.Since(start).Milliseconds(),
- sdk.LogFieldError, err,
- )
- return nil, err
- }
- logger.Debug("host_call_select_account_completed",
- sdk.LogFieldDurationMs, time.Since(start).Milliseconds(),
- )
- return &sdk.HostSelectAccountResult{
- AccountID: resp.AccountId,
- AccountName: resp.AccountName,
- Platform: resp.Platform,
- }, nil
-}
-
-func (h *hostClient) ReportAccountResult(ctx context.Context, accountID int64, success bool, errMsg string) error {
- logger, start := hostRPCLogger(ctx, "ReportAccountResult")
- _, err := h.c.ReportAccountResult(ctx, &pb.HostReportAccountResultRequest{
- AccountId: accountID,
- Success: success,
- ErrorMsg: errMsg,
- })
- if err != nil {
- logger.Error("host_call_report_account_result_failed",
- sdk.LogFieldAccountID, accountID,
- sdk.LogFieldDurationMs, time.Since(start).Milliseconds(),
- sdk.LogFieldError, err,
- )
- return err
- }
- return nil
-}
-
-// ── 探测 ──
-
-func (h *hostClient) ProbeForward(ctx context.Context, req sdk.HostProbeForwardRequest) (*sdk.HostProbeForwardResult, error) {
- logger, start := hostRPCLogger(ctx, "ProbeForward")
- resp, err := h.c.ProbeForward(ctx, &pb.HostProbeForwardRequest{
- GroupId: req.GroupID,
- Model: req.Model,
- })
- if err != nil {
- logger.Error("host_call_probe_forward_failed",
- sdk.LogFieldGroupID, req.GroupID,
- sdk.LogFieldModel, req.Model,
- sdk.LogFieldDurationMs, time.Since(start).Milliseconds(),
- sdk.LogFieldError, err,
- )
- return nil, err
- }
- return &sdk.HostProbeForwardResult{
- Success: resp.Success,
- AccountID: resp.AccountId,
- Platform: resp.Platform,
- Model: resp.Model,
- StatusCode: resp.StatusCode,
- LatencyMs: resp.LatencyMs,
- ErrorKind: resp.ErrorKind,
- ErrorMsg: resp.ErrorMsg,
- }, nil
-}
-
-// ── Forward 管线 ──
-
-func (h *hostClient) Forward(ctx context.Context, req sdk.HostForwardRequest) (*sdk.HostForwardResponse, error) {
- logger, start := hostRPCLogger(ctx, "Forward")
- resp, err := h.c.Forward(ctx, &pb.HostForwardRequest{
- UserId: req.UserID,
- GroupId: req.GroupID,
- Model: req.Model,
- Method: req.Method,
- Path: req.Path,
- Headers: httpHeadersToProto(req.Headers),
- Body: req.Body,
- Stream: req.Stream,
- })
- if err != nil {
- logger.Error("host_call_forward_failed",
- sdk.LogFieldUserID, req.UserID,
- sdk.LogFieldGroupID, req.GroupID,
- sdk.LogFieldModel, req.Model,
- sdk.LogFieldDurationMs, time.Since(start).Milliseconds(),
- sdk.LogFieldError, err,
- )
- return nil, err
- }
- result := &sdk.HostForwardResponse{
- StatusCode: int(resp.StatusCode),
- Headers: protoHeadersToHTTP(resp.Headers),
- Body: resp.Body,
- }
- if resp.Usage != nil {
- result.Usage = sdk.HostForwardUsage{
- InputTokens: resp.Usage.InputTokens,
- OutputTokens: resp.Usage.OutputTokens,
- Cost: resp.Usage.Cost,
- Model: resp.Usage.Model,
- }
- }
- return result, nil
-}
-
-func (h *hostClient) ForwardStream(ctx context.Context, req sdk.HostForwardRequest, callback func(chunk sdk.HostForwardChunk) error) error {
- logger, start := hostRPCLogger(ctx, "ForwardStream")
- stream, err := h.c.ForwardStream(ctx, &pb.HostForwardRequest{
- UserId: req.UserID,
- GroupId: req.GroupID,
- Model: req.Model,
- Method: req.Method,
- Path: req.Path,
- Headers: httpHeadersToProto(req.Headers),
- Body: req.Body,
- Stream: req.Stream,
- })
- if err != nil {
- logger.Error("host_call_forward_stream_open_failed",
- sdk.LogFieldUserID, req.UserID,
- sdk.LogFieldGroupID, req.GroupID,
- sdk.LogFieldModel, req.Model,
- sdk.LogFieldDurationMs, time.Since(start).Milliseconds(),
- sdk.LogFieldError, err,
- )
- return err
- }
- for {
- chunk, err := stream.Recv()
- if err == io.EOF {
- return nil
- }
- if err != nil {
- logger.Error("host_call_forward_stream_recv_failed",
- sdk.LogFieldUserID, req.UserID,
- sdk.LogFieldGroupID, req.GroupID,
- sdk.LogFieldModel, req.Model,
- sdk.LogFieldDurationMs, time.Since(start).Milliseconds(),
- sdk.LogFieldError, err,
- )
- return err
- }
- c := sdk.HostForwardChunk{
- Data: chunk.Data,
- Done: chunk.Done,
- StatusCode: int(chunk.StatusCode),
- Headers: protoHeadersToHTTP(chunk.Headers),
- }
- if chunk.Usage != nil {
- c.Usage = sdk.HostForwardUsage{
- InputTokens: chunk.Usage.InputTokens,
- OutputTokens: chunk.Usage.OutputTokens,
- Cost: chunk.Usage.Cost,
- Model: chunk.Usage.Model,
- }
- }
- if err := callback(c); err != nil {
- logger.Error("host_call_forward_stream_callback_failed",
- sdk.LogFieldUserID, req.UserID,
- sdk.LogFieldGroupID, req.GroupID,
- sdk.LogFieldModel, req.Model,
- sdk.LogFieldDurationMs, time.Since(start).Milliseconds(),
- sdk.LogFieldError, err,
- )
- return err
- }
- }
-}
-
-// ── 数据查询 ──
-
-func (h *hostClient) ListGroups(ctx context.Context) ([]sdk.HostGroup, error) {
- logger, start := hostRPCLogger(ctx, "ListGroups")
- resp, err := h.c.ListGroups(ctx, &pb.HostListGroupsRequest{})
- if err != nil {
- logger.Error("host_call_list_groups_failed",
- sdk.LogFieldDurationMs, time.Since(start).Milliseconds(),
- sdk.LogFieldError, err,
- )
- return nil, err
- }
- groups := make([]sdk.HostGroup, 0, len(resp.Groups))
- for _, g := range resp.Groups {
- groups = append(groups, sdk.HostGroup{
- ID: g.Id,
- Name: g.Name,
- Platform: g.Platform,
- IsExclusive: g.IsExclusive,
- RateMultiplier: g.RateMultiplier,
- })
- }
- return groups, nil
-}
-
-func (h *hostClient) ListPlatforms(ctx context.Context) ([]sdk.HostPlatform, error) {
- logger, start := hostRPCLogger(ctx, "ListPlatforms")
- resp, err := h.c.ListPlatforms(ctx, &pb.HostListPlatformsRequest{})
- if err != nil {
- logger.Error("host_call_list_platforms_failed",
- sdk.LogFieldDurationMs, time.Since(start).Milliseconds(),
- sdk.LogFieldError, err,
- )
- return nil, err
- }
- platforms := make([]sdk.HostPlatform, 0, len(resp.Platforms))
- for _, p := range resp.Platforms {
- platforms = append(platforms, sdk.HostPlatform{
- Name: p.Name,
- DisplayName: p.DisplayName,
- })
- }
- return platforms, nil
-}
-
-func (h *hostClient) ListModels(ctx context.Context, platform string) ([]sdk.ModelInfo, error) {
- logger, start := hostRPCLogger(ctx, "ListModels")
- resp, err := h.c.ListModels(ctx, &pb.HostListModelsRequest{Platform: platform})
- if err != nil {
- logger.Error("host_call_list_models_failed",
- sdk.LogFieldPlatform, platform,
- sdk.LogFieldDurationMs, time.Since(start).Milliseconds(),
- sdk.LogFieldError, err,
- )
- return nil, err
- }
- models := make([]sdk.ModelInfo, 0, len(resp.Models))
- for _, m := range resp.Models {
- models = append(models, sdk.ModelInfo{
- ID: m.Id,
- Name: m.Name,
- InputPrice: m.InputPrice,
- OutputPrice: m.OutputPrice,
- CachedInputPrice: m.CachedInputPrice,
- CacheCreationPrice: m.CacheCreationPrice,
- CacheCreation1hPrice: m.CacheCreation_1HPrice,
- ContextWindow: int(m.ContextWindow),
- MaxOutputTokens: int(m.MaxOutputTokens),
- InputPricePriority: m.InputPricePriority,
- OutputPricePriority: m.OutputPricePriority,
- })
- }
- return models, nil
-}
-
-func (h *hostClient) GetUserInfo(ctx context.Context, userID int64) (*sdk.HostUserInfo, error) {
- logger, start := hostRPCLogger(ctx, "GetUserInfo")
- resp, err := h.c.GetUserInfo(ctx, &pb.HostGetUserInfoRequest{UserId: userID})
- if err != nil {
- logger.Error("host_call_get_user_info_failed",
- sdk.LogFieldUserID, userID,
- sdk.LogFieldDurationMs, time.Since(start).Milliseconds(),
- sdk.LogFieldError, err,
- )
- return nil, err
- }
- return &sdk.HostUserInfo{
- UserID: resp.UserId,
- Username: resp.Username,
- Email: resp.Email,
- Role: resp.Role,
- Balance: resp.Balance,
- Status: resp.Status,
- }, nil
-}
-
-func (h *hostClient) StoreAsset(ctx context.Context, req sdk.HostStoreAssetRequest) (*sdk.HostStoredAsset, error) {
- logger, start := hostRPCLogger(ctx, "StoreAsset")
- resp, err := h.c.StoreAsset(ctx, &pb.HostStoreAssetRequest{
- UserId: req.UserID,
- Scope: req.Scope,
- ContentType: req.ContentType,
- Data: req.Data,
- FileExtension: req.FileExtension,
- })
- if err != nil {
- logger.Error("host_call_store_asset_failed",
- sdk.LogFieldUserID, req.UserID,
- sdk.LogFieldDurationMs, time.Since(start).Milliseconds(),
- sdk.LogFieldError, err,
- )
- return nil, err
- }
- return &sdk.HostStoredAsset{
- AssetID: resp.AssetId,
- ObjectKey: resp.ObjectKey,
- PublicURL: resp.PublicUrl,
- SizeBytes: resp.SizeBytes,
- ContentType: resp.ContentType,
- }, nil
-}
-
-func (h *hostClient) GetAssetURL(ctx context.Context, objectKey string) (string, error) {
- logger, start := hostRPCLogger(ctx, "GetAssetURL")
- resp, err := h.c.GetAssetURL(ctx, &pb.HostGetAssetURLRequest{ObjectKey: objectKey})
- if err != nil {
- logger.Error("host_call_get_asset_url_failed",
- sdk.LogFieldDurationMs, time.Since(start).Milliseconds(),
- sdk.LogFieldError, err,
- )
- return "", err
- }
- return resp.PublicUrl, nil
-}
-
-func (h *hostClient) GetAssetBytes(ctx context.Context, objectKey string) (*sdk.HostAssetBytes, error) {
- logger, start := hostRPCLogger(ctx, "GetAssetBytes")
- resp, err := h.c.GetAssetBytes(ctx, &pb.HostGetAssetBytesRequest{ObjectKey: objectKey})
- if err != nil {
- logger.Error("host_call_get_asset_bytes_failed",
- sdk.LogFieldDurationMs, time.Since(start).Milliseconds(),
- sdk.LogFieldError, err,
- )
- return nil, err
- }
- return &sdk.HostAssetBytes{Data: resp.Data, ContentType: resp.ContentType}, nil
-}
diff --git a/host.go b/host.go
deleted file mode 100644
index e8348ee..0000000
--- a/host.go
+++ /dev/null
@@ -1,199 +0,0 @@
-package sdk
-
-import (
- "context"
- "net/http"
-)
-
-// 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 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 管线(完整计费) ──
-
- // 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)
-}
-
-// HostAware 可选接口:PluginContext 实现它就能暴露 Host。老 dev server / 测试 mock 可忽略。
-type HostAware interface {
- // Host 返回 Host 客户端;可能为 nil(Core 版本不支持 / 未启用)。
- Host() Host
-}
-
-// ── 调度 ──
-
-// HostSelectAccountRequest 调度选号入参。
-type HostSelectAccountRequest struct {
- GroupID int64
- Model string
- SessionID string
- ExcludeAccountIDs []int64
-}
-
-// HostSelectAccountResult 调度选号结果。
-type HostSelectAccountResult struct {
- AccountID int64
- AccountName string
- Platform string
-}
-
-// ── 探测 ──
-
-// HostProbeForwardRequest 黑盒探测入参。
-type HostProbeForwardRequest struct {
- GroupID int64
- Model string
-}
-
-// HostProbeForwardResult 黑盒探测结果。
-type HostProbeForwardResult struct {
- Success bool
- AccountID int64
- Platform string
- Model string
- StatusCode int64
- LatencyMs int64
- ErrorKind string
- ErrorMsg string
-}
-
-// ── Forward 管线 ──
-
-// HostForwardRequest 业务转发入参。
-type HostForwardRequest struct {
- UserID int64
- GroupID int64
- Model string
- Method string
- Path string
- Headers http.Header
- Body []byte
- Stream bool
-}
-
-// HostForwardResponse 非流式转发结果。
-type HostForwardResponse struct {
- StatusCode int
- Headers http.Header
- Body []byte
- Usage HostForwardUsage
-}
-
-// HostForwardChunk 流式转发的单块数据。
-type HostForwardChunk struct {
- Data []byte
- Done bool
- StatusCode int
- Headers http.Header
- Usage HostForwardUsage
-}
-
-// HostForwardUsage 转发的 token / 费用摘要。
-type HostForwardUsage struct {
- InputTokens int64
- OutputTokens int64
- Cost float64
- Model string
-}
-
-// ── 数据查询 ──
-
-// HostGroup Host.ListGroups 返回的分组摘要。
-type HostGroup struct {
- ID int64
- Name string
- Platform string
- IsExclusive bool
- RateMultiplier float64
-}
-
-// HostPlatform 已加载的网关平台。
-type HostPlatform struct {
- Name string
- DisplayName string
-}
-
-// HostUserInfo 用户基本信息。
-type HostUserInfo struct {
- UserID int64
- Username string
- Email string
- Role string
- Balance float64
- Status string
-}
-
-// HostStoreAssetRequest 资产存储入参。
-type HostStoreAssetRequest struct {
- UserID int64
- Scope string
- ContentType string
- Data []byte
- FileExtension string
-}
-
-// HostStoredAsset 资产存储结果。
-type HostStoredAsset struct {
- AssetID string
- ObjectKey string
- PublicURL string
- SizeBytes int64
- ContentType string
-}
-
-// HostAssetBytes 资产读取结果。
-type HostAssetBytes struct {
- Data []byte
- ContentType string
-}
diff --git a/models.go b/models.go
deleted file mode 100644
index ad6155c..0000000
--- a/models.go
+++ /dev/null
@@ -1,119 +0,0 @@
-package sdk
-
-import "net/http"
-
-// Account 上游账户(Core 调度后传给插件的最小视图)。
-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"`
-}
-
-// 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 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"`
-}
-
-// RouteDefinition 网关插件声明的 API 端点。
-type RouteDefinition struct {
- Method string `json:"method"`
- Path string `json:"path"`
- Description string `json:"description"`
-}
-
-// RouteRegistrar 扩展插件使用的路由注册器。
-type RouteRegistrar interface {
- Handle(method, path string, handler http.HandlerFunc)
- Group(prefix string) RouteRegistrar
-}
-
-// 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"`
-}
-
-// AccountType 账号类型声明。
-type AccountType struct {
- Key string `json:"key"`
- Label string `json:"label"`
- Description string `json:"description"`
- Fields []CredentialField `json:"fields"`
-}
-
-// 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"`
-}
-
-// 前端组件插槽。
-const (
- SlotAccountForm = "account-form"
- SlotAccountDetail = "account-detail"
-)
-
-// FrontendWidget 前端组件嵌入声明。
-type FrontendWidget struct {
- Slot string `json:"slot"`
- EntryFile string `json:"entry_file"`
- Title string `json:"title"`
-}
-
-// 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"`
-}
diff --git a/protocol/proto/doc.go b/protocol/proto/doc.go
new file mode 100644
index 0000000..ed21cdd
--- /dev/null
+++ b/protocol/proto/doc.go
@@ -0,0 +1,4 @@
+// Package proto 是 AirGate 插件协议的 protobuf 生成代码。
+//
+// 普通插件业务代码应优先使用 sdkgo 包,不应直接依赖本包。
+package proto
diff --git a/proto/plugin.pb.go b/protocol/proto/plugin.pb.go
similarity index 53%
rename from proto/plugin.pb.go
rename to protocol/proto/plugin.pb.go
index ee56c6b..f9a5c66 100644
--- a/proto/plugin.pb.go
+++ b/protocol/proto/plugin.pb.go
@@ -91,7 +91,7 @@ const (
WebSocketFrame_TEXT WebSocketFrame_FrameType = 1 // 文本消息
WebSocketFrame_BINARY WebSocketFrame_FrameType = 2 // 二进制消息
WebSocketFrame_CLOSE WebSocketFrame_FrameType = 3 // 关闭连接
- WebSocketFrame_RESULT WebSocketFrame_FrameType = 4 // 连接结束,携带 ForwardResult
+ WebSocketFrame_RESULT WebSocketFrame_FrameType = 4 // 连接结束,携带 ForwardOutcome
)
// Enum value maps for WebSocketFrame_FrameType.
@@ -136,7 +136,7 @@ func (x WebSocketFrame_FrameType) Number() protoreflect.EnumNumber {
// Deprecated: Use WebSocketFrame_FrameType.Descriptor instead.
func (WebSocketFrame_FrameType) EnumDescriptor() ([]byte, []int) {
- return file_plugin_proto_rawDescGZIP(), []int{53, 0}
+ return file_plugin_proto_rawDescGZIP(), []int{30, 0}
}
type MiddlewareDecision_Action int32
@@ -185,33 +185,29 @@ func (x MiddlewareDecision_Action) Number() protoreflect.EnumNumber {
// Deprecated: Use MiddlewareDecision_Action.Descriptor instead.
func (MiddlewareDecision_Action) EnumDescriptor() ([]byte, []int) {
- return file_plugin_proto_rawDescGZIP(), []int{59, 0}
+ return file_plugin_proto_rawDescGZIP(), []int{42, 0}
}
-type HostSelectAccountRequest struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- GroupId int64 `protobuf:"varint,1,opt,name=group_id,json=groupId,proto3" json:"group_id,omitempty"`
- Model string `protobuf:"bytes,2,opt,name=model,proto3" json:"model,omitempty"` // 可空:空时 Core 用 platform 的第一个 model
- SessionId string `protobuf:"bytes,3,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` // 可空
- ExcludeAccountIds []int64 `protobuf:"varint,4,rep,packed,name=exclude_account_ids,json=excludeAccountIds,proto3" json:"exclude_account_ids,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
+type Empty struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
}
-func (x *HostSelectAccountRequest) Reset() {
- *x = HostSelectAccountRequest{}
+func (x *Empty) Reset() {
+ *x = Empty{}
mi := &file_plugin_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
-func (x *HostSelectAccountRequest) String() string {
+func (x *Empty) String() string {
return protoimpl.X.MessageStringOf(x)
}
-func (*HostSelectAccountRequest) ProtoMessage() {}
+func (*Empty) ProtoMessage() {}
-func (x *HostSelectAccountRequest) ProtoReflect() protoreflect.Message {
+func (x *Empty) ProtoReflect() protoreflect.Message {
mi := &file_plugin_proto_msgTypes[0]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
@@ -223,62 +219,32 @@ func (x *HostSelectAccountRequest) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
-// Deprecated: Use HostSelectAccountRequest.ProtoReflect.Descriptor instead.
-func (*HostSelectAccountRequest) Descriptor() ([]byte, []int) {
+// Deprecated: Use Empty.ProtoReflect.Descriptor instead.
+func (*Empty) Descriptor() ([]byte, []int) {
return file_plugin_proto_rawDescGZIP(), []int{0}
}
-func (x *HostSelectAccountRequest) GetGroupId() int64 {
- if x != nil {
- return x.GroupId
- }
- return 0
-}
-
-func (x *HostSelectAccountRequest) GetModel() string {
- if x != nil {
- return x.Model
- }
- return ""
-}
-
-func (x *HostSelectAccountRequest) GetSessionId() string {
- if x != nil {
- return x.SessionId
- }
- return ""
-}
-
-func (x *HostSelectAccountRequest) GetExcludeAccountIds() []int64 {
- if x != nil {
- return x.ExcludeAccountIds
- }
- return nil
-}
-
-type HostSelectAccountResponse struct {
+type StringResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
- AccountId int64 `protobuf:"varint,1,opt,name=account_id,json=accountId,proto3" json:"account_id,omitempty"`
- AccountName string `protobuf:"bytes,2,opt,name=account_name,json=accountName,proto3" json:"account_name,omitempty"`
- Platform string `protobuf:"bytes,3,opt,name=platform,proto3" json:"platform,omitempty"`
+ Value string `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
-func (x *HostSelectAccountResponse) Reset() {
- *x = HostSelectAccountResponse{}
+func (x *StringResponse) Reset() {
+ *x = StringResponse{}
mi := &file_plugin_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
-func (x *HostSelectAccountResponse) String() string {
+func (x *StringResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
-func (*HostSelectAccountResponse) ProtoMessage() {}
+func (*StringResponse) ProtoMessage() {}
-func (x *HostSelectAccountResponse) ProtoReflect() protoreflect.Message {
+func (x *StringResponse) ProtoReflect() protoreflect.Message {
mi := &file_plugin_proto_msgTypes[1]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
@@ -290,54 +256,40 @@ func (x *HostSelectAccountResponse) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
-// Deprecated: Use HostSelectAccountResponse.ProtoReflect.Descriptor instead.
-func (*HostSelectAccountResponse) Descriptor() ([]byte, []int) {
+// Deprecated: Use StringResponse.ProtoReflect.Descriptor instead.
+func (*StringResponse) Descriptor() ([]byte, []int) {
return file_plugin_proto_rawDescGZIP(), []int{1}
}
-func (x *HostSelectAccountResponse) GetAccountId() int64 {
- if x != nil {
- return x.AccountId
- }
- return 0
-}
-
-func (x *HostSelectAccountResponse) GetAccountName() string {
- if x != nil {
- return x.AccountName
- }
- return ""
-}
-
-func (x *HostSelectAccountResponse) GetPlatform() string {
+func (x *StringResponse) GetValue() string {
if x != nil {
- return x.Platform
+ return x.Value
}
return ""
}
-type HostProbeForwardRequest struct {
+// HeaderValues 支持同一 Header 有多个值(如 Set-Cookie)
+type HeaderValues struct {
state protoimpl.MessageState `protogen:"open.v1"`
- GroupId int64 `protobuf:"varint,1,opt,name=group_id,json=groupId,proto3" json:"group_id,omitempty"`
- Model string `protobuf:"bytes,2,opt,name=model,proto3" json:"model,omitempty"` // 可空:自动取 platform 第一个 model
+ Values []string `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
-func (x *HostProbeForwardRequest) Reset() {
- *x = HostProbeForwardRequest{}
+func (x *HeaderValues) Reset() {
+ *x = HeaderValues{}
mi := &file_plugin_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
-func (x *HostProbeForwardRequest) String() string {
+func (x *HeaderValues) String() string {
return protoimpl.X.MessageStringOf(x)
}
-func (*HostProbeForwardRequest) ProtoMessage() {}
+func (*HeaderValues) ProtoMessage() {}
-func (x *HostProbeForwardRequest) ProtoReflect() protoreflect.Message {
+func (x *HeaderValues) ProtoReflect() protoreflect.Message {
mi := &file_plugin_proto_msgTypes[2]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
@@ -349,53 +301,60 @@ func (x *HostProbeForwardRequest) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
-// Deprecated: Use HostProbeForwardRequest.ProtoReflect.Descriptor instead.
-func (*HostProbeForwardRequest) Descriptor() ([]byte, []int) {
+// Deprecated: Use HeaderValues.ProtoReflect.Descriptor instead.
+func (*HeaderValues) Descriptor() ([]byte, []int) {
return file_plugin_proto_rawDescGZIP(), []int{2}
}
-func (x *HostProbeForwardRequest) GetGroupId() int64 {
- if x != nil {
- return x.GroupId
- }
- return 0
-}
-
-func (x *HostProbeForwardRequest) GetModel() string {
+func (x *HeaderValues) GetValues() []string {
if x != nil {
- return x.Model
+ return x.Values
}
- return ""
+ return nil
}
-type HostProbeForwardResponse struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"`
- AccountId int64 `protobuf:"varint,2,opt,name=account_id,json=accountId,proto3" json:"account_id,omitempty"` // 本次实际命中的账号 ID(用于运维诊断)
- Platform string `protobuf:"bytes,3,opt,name=platform,proto3" json:"platform,omitempty"`
- Model string `protobuf:"bytes,4,opt,name=model,proto3" json:"model,omitempty"` // 实际探测用的 model
- StatusCode int64 `protobuf:"varint,5,opt,name=status_code,json=statusCode,proto3" json:"status_code,omitempty"`
- LatencyMs int64 `protobuf:"varint,6,opt,name=latency_ms,json=latencyMs,proto3" json:"latency_ms,omitempty"`
- ErrorKind string `protobuf:"bytes,7,opt,name=error_kind,json=errorKind,proto3" json:"error_kind,omitempty"` // "" / "no_account" / "scheduler" / "upstream_5xx" / "timeout" / ...
- ErrorMsg string `protobuf:"bytes,8,opt,name=error_msg,json=errorMsg,proto3" json:"error_msg,omitempty"`
+type PluginInfoResponse struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
+ Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
+ Version string `protobuf:"bytes,3,opt,name=version,proto3" json:"version,omitempty"`
+ Description string `protobuf:"bytes,4,opt,name=description,proto3" json:"description,omitempty"`
+ Author string `protobuf:"bytes,5,opt,name=author,proto3" json:"author,omitempty"`
+ Type string `protobuf:"bytes,6,opt,name=type,proto3" json:"type,omitempty"`
+ AccountTypes []*AccountTypeProto `protobuf:"bytes,7,rep,name=account_types,json=accountTypes,proto3" json:"account_types,omitempty"`
+ FrontendPages []*FrontendPageProto `protobuf:"bytes,8,rep,name=frontend_pages,json=frontendPages,proto3" json:"frontend_pages,omitempty"`
+ FrontendWidgets []*FrontendWidgetProto `protobuf:"bytes,9,rep,name=frontend_widgets,json=frontendWidgets,proto3" json:"frontend_widgets,omitempty"`
+ SdkVersion string `protobuf:"bytes,10,opt,name=sdk_version,json=sdkVersion,proto3" json:"sdk_version,omitempty"` // 插件编译时的 SDK 版本
+ Dependencies []string `protobuf:"bytes,11,rep,name=dependencies,proto3" json:"dependencies,omitempty"` // 依赖的其他插件 ID
+ ConfigSchema []*ConfigFieldProto `protobuf:"bytes,12,rep,name=config_schema,json=configSchema,proto3" json:"config_schema,omitempty"` // 配置项声明
+ InstructionPresets []string `protobuf:"bytes,13,rep,name=instruction_presets,json=instructionPresets,proto3" json:"instruction_presets,omitempty"` // 可用的 instructions 预设名称列表
+ // capabilities 声明 Host.Invoke / Host.InvokeStream、method 级授权和 Middleware 能力。
+ // Core 启动时按插件类型、方法注册表和 RPC 调用做准入校验。
+ Capabilities []string `protobuf:"bytes,14,rep,name=capabilities,proto3" json:"capabilities,omitempty"`
+ // priority 中间件链中的排序权重(数值越小越早进 Begin、越晚出 End)。
+ // 仅 type="middleware" 插件使用;其他类型忽略。默认 100。
+ Priority int32 `protobuf:"varint,15,opt,name=priority,proto3" json:"priority,omitempty"`
+ // metadata 保存插件声明层面的弱契约扩展信息,例如分类、市场标签、展示提示。
+ // 需要 Core 授权或参与调度的数据必须进入显式字段或 capability。
+ Metadata map[string]string `protobuf:"bytes,16,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
-func (x *HostProbeForwardResponse) Reset() {
- *x = HostProbeForwardResponse{}
+func (x *PluginInfoResponse) Reset() {
+ *x = PluginInfoResponse{}
mi := &file_plugin_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
-func (x *HostProbeForwardResponse) String() string {
+func (x *PluginInfoResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
-func (*HostProbeForwardResponse) ProtoMessage() {}
+func (*PluginInfoResponse) ProtoMessage() {}
-func (x *HostProbeForwardResponse) ProtoReflect() protoreflect.Message {
+func (x *PluginInfoResponse) ProtoReflect() protoreflect.Message {
mi := &file_plugin_proto_msgTypes[3]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
@@ -407,201 +366,151 @@ func (x *HostProbeForwardResponse) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
-// Deprecated: Use HostProbeForwardResponse.ProtoReflect.Descriptor instead.
-func (*HostProbeForwardResponse) Descriptor() ([]byte, []int) {
+// Deprecated: Use PluginInfoResponse.ProtoReflect.Descriptor instead.
+func (*PluginInfoResponse) Descriptor() ([]byte, []int) {
return file_plugin_proto_rawDescGZIP(), []int{3}
}
-func (x *HostProbeForwardResponse) GetSuccess() bool {
+func (x *PluginInfoResponse) GetId() string {
if x != nil {
- return x.Success
+ return x.Id
}
- return false
+ return ""
}
-func (x *HostProbeForwardResponse) GetAccountId() int64 {
+func (x *PluginInfoResponse) GetName() string {
if x != nil {
- return x.AccountId
+ return x.Name
}
- return 0
+ return ""
}
-func (x *HostProbeForwardResponse) GetPlatform() string {
+func (x *PluginInfoResponse) GetVersion() string {
if x != nil {
- return x.Platform
+ return x.Version
}
return ""
}
-func (x *HostProbeForwardResponse) GetModel() string {
+func (x *PluginInfoResponse) GetDescription() string {
if x != nil {
- return x.Model
+ return x.Description
}
return ""
}
-func (x *HostProbeForwardResponse) GetStatusCode() int64 {
+func (x *PluginInfoResponse) GetAuthor() string {
if x != nil {
- return x.StatusCode
+ return x.Author
}
- return 0
+ return ""
}
-func (x *HostProbeForwardResponse) GetLatencyMs() int64 {
+func (x *PluginInfoResponse) GetType() string {
if x != nil {
- return x.LatencyMs
+ return x.Type
}
- return 0
+ return ""
}
-func (x *HostProbeForwardResponse) GetErrorKind() string {
+func (x *PluginInfoResponse) GetAccountTypes() []*AccountTypeProto {
if x != nil {
- return x.ErrorKind
+ return x.AccountTypes
}
- return ""
+ return nil
}
-func (x *HostProbeForwardResponse) GetErrorMsg() string {
+func (x *PluginInfoResponse) GetFrontendPages() []*FrontendPageProto {
if x != nil {
- return x.ErrorMsg
+ return x.FrontendPages
}
- return ""
-}
-
-type HostListGroupsRequest struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *HostListGroupsRequest) Reset() {
- *x = HostListGroupsRequest{}
- mi := &file_plugin_proto_msgTypes[4]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *HostListGroupsRequest) String() string {
- return protoimpl.X.MessageStringOf(x)
+ return nil
}
-func (*HostListGroupsRequest) ProtoMessage() {}
-
-func (x *HostListGroupsRequest) ProtoReflect() protoreflect.Message {
- mi := &file_plugin_proto_msgTypes[4]
+func (x *PluginInfoResponse) GetFrontendWidgets() []*FrontendWidgetProto {
if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
+ return x.FrontendWidgets
}
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use HostListGroupsRequest.ProtoReflect.Descriptor instead.
-func (*HostListGroupsRequest) Descriptor() ([]byte, []int) {
- return file_plugin_proto_rawDescGZIP(), []int{4}
-}
-
-type HostGroup struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
- Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
- Platform string `protobuf:"bytes,3,opt,name=platform,proto3" json:"platform,omitempty"`
- IsExclusive bool `protobuf:"varint,4,opt,name=is_exclusive,json=isExclusive,proto3" json:"is_exclusive,omitempty"`
- RateMultiplier float64 `protobuf:"fixed64,5,opt,name=rate_multiplier,json=rateMultiplier,proto3" json:"rate_multiplier,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *HostGroup) Reset() {
- *x = HostGroup{}
- mi := &file_plugin_proto_msgTypes[5]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *HostGroup) String() string {
- return protoimpl.X.MessageStringOf(x)
+ return nil
}
-func (*HostGroup) ProtoMessage() {}
-
-func (x *HostGroup) ProtoReflect() protoreflect.Message {
- mi := &file_plugin_proto_msgTypes[5]
+func (x *PluginInfoResponse) GetSdkVersion() string {
if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
+ return x.SdkVersion
}
- return mi.MessageOf(x)
+ return ""
}
-// Deprecated: Use HostGroup.ProtoReflect.Descriptor instead.
-func (*HostGroup) Descriptor() ([]byte, []int) {
- return file_plugin_proto_rawDescGZIP(), []int{5}
+func (x *PluginInfoResponse) GetDependencies() []string {
+ if x != nil {
+ return x.Dependencies
+ }
+ return nil
}
-func (x *HostGroup) GetId() int64 {
+func (x *PluginInfoResponse) GetConfigSchema() []*ConfigFieldProto {
if x != nil {
- return x.Id
+ return x.ConfigSchema
}
- return 0
+ return nil
}
-func (x *HostGroup) GetName() string {
+func (x *PluginInfoResponse) GetInstructionPresets() []string {
if x != nil {
- return x.Name
+ return x.InstructionPresets
}
- return ""
+ return nil
}
-func (x *HostGroup) GetPlatform() string {
+func (x *PluginInfoResponse) GetCapabilities() []string {
if x != nil {
- return x.Platform
+ return x.Capabilities
}
- return ""
+ return nil
}
-func (x *HostGroup) GetIsExclusive() bool {
+func (x *PluginInfoResponse) GetPriority() int32 {
if x != nil {
- return x.IsExclusive
+ return x.Priority
}
- return false
+ return 0
}
-func (x *HostGroup) GetRateMultiplier() float64 {
+func (x *PluginInfoResponse) GetMetadata() map[string]string {
if x != nil {
- return x.RateMultiplier
+ return x.Metadata
}
- return 0
+ return nil
}
-type HostListGroupsResponse struct {
+type ConfigFieldProto struct {
state protoimpl.MessageState `protogen:"open.v1"`
- Groups []*HostGroup `protobuf:"bytes,1,rep,name=groups,proto3" json:"groups,omitempty"`
+ Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
+ Label string `protobuf:"bytes,2,opt,name=label,proto3" json:"label,omitempty"`
+ Type string `protobuf:"bytes,3,opt,name=type,proto3" json:"type,omitempty"`
+ Required bool `protobuf:"varint,4,opt,name=required,proto3" json:"required,omitempty"`
+ DefaultValue string `protobuf:"bytes,5,opt,name=default_value,json=defaultValue,proto3" json:"default_value,omitempty"`
+ Description string `protobuf:"bytes,6,opt,name=description,proto3" json:"description,omitempty"`
+ Placeholder string `protobuf:"bytes,7,opt,name=placeholder,proto3" json:"placeholder,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
-func (x *HostListGroupsResponse) Reset() {
- *x = HostListGroupsResponse{}
- mi := &file_plugin_proto_msgTypes[6]
+func (x *ConfigFieldProto) Reset() {
+ *x = ConfigFieldProto{}
+ mi := &file_plugin_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
-func (x *HostListGroupsResponse) String() string {
+func (x *ConfigFieldProto) String() string {
return protoimpl.X.MessageStringOf(x)
}
-func (*HostListGroupsResponse) ProtoMessage() {}
+func (*ConfigFieldProto) ProtoMessage() {}
-func (x *HostListGroupsResponse) ProtoReflect() protoreflect.Message {
- mi := &file_plugin_proto_msgTypes[6]
+func (x *ConfigFieldProto) ProtoReflect() protoreflect.Message {
+ mi := &file_plugin_proto_msgTypes[4]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -612,109 +521,85 @@ func (x *HostListGroupsResponse) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
-// Deprecated: Use HostListGroupsResponse.ProtoReflect.Descriptor instead.
-func (*HostListGroupsResponse) Descriptor() ([]byte, []int) {
- return file_plugin_proto_rawDescGZIP(), []int{6}
+// Deprecated: Use ConfigFieldProto.ProtoReflect.Descriptor instead.
+func (*ConfigFieldProto) Descriptor() ([]byte, []int) {
+ return file_plugin_proto_rawDescGZIP(), []int{4}
}
-func (x *HostListGroupsResponse) GetGroups() []*HostGroup {
+func (x *ConfigFieldProto) GetKey() string {
if x != nil {
- return x.Groups
+ return x.Key
}
- return nil
+ return ""
}
-type HostReportAccountResultRequest struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- AccountId int64 `protobuf:"varint,1,opt,name=account_id,json=accountId,proto3" json:"account_id,omitempty"`
- Success bool `protobuf:"varint,2,opt,name=success,proto3" json:"success,omitempty"`
- ErrorMsg string `protobuf:"bytes,3,opt,name=error_msg,json=errorMsg,proto3" json:"error_msg,omitempty"` // 失败时上报,便于调试
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
+func (x *ConfigFieldProto) GetLabel() string {
+ if x != nil {
+ return x.Label
+ }
+ return ""
}
-func (x *HostReportAccountResultRequest) Reset() {
- *x = HostReportAccountResultRequest{}
- mi := &file_plugin_proto_msgTypes[7]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *HostReportAccountResultRequest) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*HostReportAccountResultRequest) ProtoMessage() {}
-
-func (x *HostReportAccountResultRequest) ProtoReflect() protoreflect.Message {
- mi := &file_plugin_proto_msgTypes[7]
+func (x *ConfigFieldProto) GetType() string {
if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
+ return x.Type
}
- return mi.MessageOf(x)
+ return ""
}
-// Deprecated: Use HostReportAccountResultRequest.ProtoReflect.Descriptor instead.
-func (*HostReportAccountResultRequest) Descriptor() ([]byte, []int) {
- return file_plugin_proto_rawDescGZIP(), []int{7}
+func (x *ConfigFieldProto) GetRequired() bool {
+ if x != nil {
+ return x.Required
+ }
+ return false
}
-func (x *HostReportAccountResultRequest) GetAccountId() int64 {
+func (x *ConfigFieldProto) GetDefaultValue() string {
if x != nil {
- return x.AccountId
+ return x.DefaultValue
}
- return 0
+ return ""
}
-func (x *HostReportAccountResultRequest) GetSuccess() bool {
+func (x *ConfigFieldProto) GetDescription() string {
if x != nil {
- return x.Success
+ return x.Description
}
- return false
+ return ""
}
-func (x *HostReportAccountResultRequest) GetErrorMsg() string {
+func (x *ConfigFieldProto) GetPlaceholder() string {
if x != nil {
- return x.ErrorMsg
+ return x.Placeholder
}
return ""
}
-// HostForwardRequest 业务转发入参。
-// user_id + group_id 共同确定计费主体和调度路径。
-type HostForwardRequest struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- UserId int64 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` // 计费主体(扣余额的用户)
- GroupId int64 `protobuf:"varint,2,opt,name=group_id,json=groupId,proto3" json:"group_id,omitempty"` // 调度分组
- Model string `protobuf:"bytes,3,opt,name=model,proto3" json:"model,omitempty"` // 可空:空时 Core 取该 platform 的第一个 model
- Method string `protobuf:"bytes,4,opt,name=method,proto3" json:"method,omitempty"` // HTTP method(POST / GET / ...)
- Path string `protobuf:"bytes,5,opt,name=path,proto3" json:"path,omitempty"` // 请求路径(如 /v1/chat/completions)
- Headers map[string]*HeaderValues `protobuf:"bytes,6,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
- Body []byte `protobuf:"bytes,7,opt,name=body,proto3" json:"body,omitempty"`
- Stream bool `protobuf:"varint,8,opt,name=stream,proto3" json:"stream,omitempty"` // 是否流式
+type AccountTypeProto struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
+ Label string `protobuf:"bytes,2,opt,name=label,proto3" json:"label,omitempty"`
+ Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"`
+ Fields []*CredentialFieldProto `protobuf:"bytes,4,rep,name=fields,proto3" json:"fields,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
-func (x *HostForwardRequest) Reset() {
- *x = HostForwardRequest{}
- mi := &file_plugin_proto_msgTypes[8]
+func (x *AccountTypeProto) Reset() {
+ *x = AccountTypeProto{}
+ mi := &file_plugin_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
-func (x *HostForwardRequest) String() string {
+func (x *AccountTypeProto) String() string {
return protoimpl.X.MessageStringOf(x)
}
-func (*HostForwardRequest) ProtoMessage() {}
+func (*AccountTypeProto) ProtoMessage() {}
-func (x *HostForwardRequest) ProtoReflect() protoreflect.Message {
- mi := &file_plugin_proto_msgTypes[8]
+func (x *AccountTypeProto) ProtoReflect() protoreflect.Message {
+ mi := &file_plugin_proto_msgTypes[5]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -725,93 +610,66 @@ func (x *HostForwardRequest) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
-// Deprecated: Use HostForwardRequest.ProtoReflect.Descriptor instead.
-func (*HostForwardRequest) Descriptor() ([]byte, []int) {
- return file_plugin_proto_rawDescGZIP(), []int{8}
-}
-
-func (x *HostForwardRequest) GetUserId() int64 {
- if x != nil {
- return x.UserId
- }
- return 0
-}
-
-func (x *HostForwardRequest) GetGroupId() int64 {
- if x != nil {
- return x.GroupId
- }
- return 0
+// Deprecated: Use AccountTypeProto.ProtoReflect.Descriptor instead.
+func (*AccountTypeProto) Descriptor() ([]byte, []int) {
+ return file_plugin_proto_rawDescGZIP(), []int{5}
}
-func (x *HostForwardRequest) GetModel() string {
+func (x *AccountTypeProto) GetKey() string {
if x != nil {
- return x.Model
+ return x.Key
}
return ""
}
-func (x *HostForwardRequest) GetMethod() string {
+func (x *AccountTypeProto) GetLabel() string {
if x != nil {
- return x.Method
+ return x.Label
}
return ""
}
-func (x *HostForwardRequest) GetPath() string {
+func (x *AccountTypeProto) GetDescription() string {
if x != nil {
- return x.Path
+ return x.Description
}
return ""
}
-func (x *HostForwardRequest) GetHeaders() map[string]*HeaderValues {
- if x != nil {
- return x.Headers
- }
- return nil
-}
-
-func (x *HostForwardRequest) GetBody() []byte {
+func (x *AccountTypeProto) GetFields() []*CredentialFieldProto {
if x != nil {
- return x.Body
+ return x.Fields
}
return nil
}
-func (x *HostForwardRequest) GetStream() bool {
- if x != nil {
- return x.Stream
- }
- return false
-}
-
-// HostForwardResponse 非流式转发结果。
-type HostForwardResponse struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- StatusCode int32 `protobuf:"varint,1,opt,name=status_code,json=statusCode,proto3" json:"status_code,omitempty"`
- Headers map[string]*HeaderValues `protobuf:"bytes,2,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
- Body []byte `protobuf:"bytes,3,opt,name=body,proto3" json:"body,omitempty"`
- Usage *HostForwardUsage `protobuf:"bytes,4,opt,name=usage,proto3" json:"usage,omitempty"`
+type CredentialFieldProto struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
+ Label string `protobuf:"bytes,2,opt,name=label,proto3" json:"label,omitempty"`
+ Type string `protobuf:"bytes,3,opt,name=type,proto3" json:"type,omitempty"`
+ Required bool `protobuf:"varint,4,opt,name=required,proto3" json:"required,omitempty"`
+ Placeholder string `protobuf:"bytes,5,opt,name=placeholder,proto3" json:"placeholder,omitempty"`
+ EditDisabled bool `protobuf:"varint,6,opt,name=edit_disabled,json=editDisabled,proto3" json:"edit_disabled,omitempty"` // 编辑模式下隐藏该字段
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
-func (x *HostForwardResponse) Reset() {
- *x = HostForwardResponse{}
- mi := &file_plugin_proto_msgTypes[9]
+func (x *CredentialFieldProto) Reset() {
+ *x = CredentialFieldProto{}
+ mi := &file_plugin_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
-func (x *HostForwardResponse) String() string {
+func (x *CredentialFieldProto) String() string {
return protoimpl.X.MessageStringOf(x)
}
-func (*HostForwardResponse) ProtoMessage() {}
+func (*CredentialFieldProto) ProtoMessage() {}
-func (x *HostForwardResponse) ProtoReflect() protoreflect.Message {
- mi := &file_plugin_proto_msgTypes[9]
+func (x *CredentialFieldProto) ProtoReflect() protoreflect.Message {
+ mi := &file_plugin_proto_msgTypes[6]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -822,67 +680,79 @@ func (x *HostForwardResponse) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
-// Deprecated: Use HostForwardResponse.ProtoReflect.Descriptor instead.
-func (*HostForwardResponse) Descriptor() ([]byte, []int) {
- return file_plugin_proto_rawDescGZIP(), []int{9}
+// Deprecated: Use CredentialFieldProto.ProtoReflect.Descriptor instead.
+func (*CredentialFieldProto) Descriptor() ([]byte, []int) {
+ return file_plugin_proto_rawDescGZIP(), []int{6}
}
-func (x *HostForwardResponse) GetStatusCode() int32 {
+func (x *CredentialFieldProto) GetKey() string {
if x != nil {
- return x.StatusCode
+ return x.Key
}
- return 0
+ return ""
}
-func (x *HostForwardResponse) GetHeaders() map[string]*HeaderValues {
+func (x *CredentialFieldProto) GetLabel() string {
if x != nil {
- return x.Headers
+ return x.Label
}
- return nil
+ return ""
}
-func (x *HostForwardResponse) GetBody() []byte {
+func (x *CredentialFieldProto) GetType() string {
if x != nil {
- return x.Body
+ return x.Type
}
- return nil
+ return ""
}
-func (x *HostForwardResponse) GetUsage() *HostForwardUsage {
+func (x *CredentialFieldProto) GetRequired() bool {
if x != nil {
- return x.Usage
+ return x.Required
}
- return nil
+ return false
}
-// HostForwardChunk 流式转发的单块数据。
-// 第一块携带 status_code + headers;最后一块 done=true 携带 usage。
-type HostForwardChunk struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"`
- Done bool `protobuf:"varint,2,opt,name=done,proto3" json:"done,omitempty"`
- StatusCode int32 `protobuf:"varint,3,opt,name=status_code,json=statusCode,proto3" json:"status_code,omitempty"` // 仅首块
- Headers map[string]*HeaderValues `protobuf:"bytes,4,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` // 仅首块
- Usage *HostForwardUsage `protobuf:"bytes,5,opt,name=usage,proto3" json:"usage,omitempty"` // 仅末块
+func (x *CredentialFieldProto) GetPlaceholder() string {
+ if x != nil {
+ return x.Placeholder
+ }
+ return ""
+}
+
+func (x *CredentialFieldProto) GetEditDisabled() bool {
+ if x != nil {
+ return x.EditDisabled
+ }
+ return false
+}
+
+type FrontendPageProto struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Path string `protobuf:"bytes,1,opt,name=path,proto3" json:"path,omitempty"`
+ Title string `protobuf:"bytes,2,opt,name=title,proto3" json:"title,omitempty"`
+ Icon string `protobuf:"bytes,3,opt,name=icon,proto3" json:"icon,omitempty"`
+ Description string `protobuf:"bytes,4,opt,name=description,proto3" json:"description,omitempty"`
+ Audience string `protobuf:"bytes,5,opt,name=audience,proto3" json:"audience,omitempty"` // "admin" | "user" | "all",空 = "admin"
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
-func (x *HostForwardChunk) Reset() {
- *x = HostForwardChunk{}
- mi := &file_plugin_proto_msgTypes[10]
+func (x *FrontendPageProto) Reset() {
+ *x = FrontendPageProto{}
+ mi := &file_plugin_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
-func (x *HostForwardChunk) String() string {
+func (x *FrontendPageProto) String() string {
return protoimpl.X.MessageStringOf(x)
}
-func (*HostForwardChunk) ProtoMessage() {}
+func (*FrontendPageProto) ProtoMessage() {}
-func (x *HostForwardChunk) ProtoReflect() protoreflect.Message {
- mi := &file_plugin_proto_msgTypes[10]
+func (x *FrontendPageProto) ProtoReflect() protoreflect.Message {
+ mi := &file_plugin_proto_msgTypes[7]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -893,72 +763,70 @@ func (x *HostForwardChunk) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
-// Deprecated: Use HostForwardChunk.ProtoReflect.Descriptor instead.
-func (*HostForwardChunk) Descriptor() ([]byte, []int) {
- return file_plugin_proto_rawDescGZIP(), []int{10}
+// Deprecated: Use FrontendPageProto.ProtoReflect.Descriptor instead.
+func (*FrontendPageProto) Descriptor() ([]byte, []int) {
+ return file_plugin_proto_rawDescGZIP(), []int{7}
}
-func (x *HostForwardChunk) GetData() []byte {
+func (x *FrontendPageProto) GetPath() string {
if x != nil {
- return x.Data
+ return x.Path
}
- return nil
+ return ""
}
-func (x *HostForwardChunk) GetDone() bool {
+func (x *FrontendPageProto) GetTitle() string {
if x != nil {
- return x.Done
+ return x.Title
}
- return false
+ return ""
}
-func (x *HostForwardChunk) GetStatusCode() int32 {
+func (x *FrontendPageProto) GetIcon() string {
if x != nil {
- return x.StatusCode
+ return x.Icon
}
- return 0
+ return ""
}
-func (x *HostForwardChunk) GetHeaders() map[string]*HeaderValues {
+func (x *FrontendPageProto) GetDescription() string {
if x != nil {
- return x.Headers
+ return x.Description
}
- return nil
+ return ""
}
-func (x *HostForwardChunk) GetUsage() *HostForwardUsage {
+func (x *FrontendPageProto) GetAudience() string {
if x != nil {
- return x.Usage
+ return x.Audience
}
- return nil
+ return ""
}
-// HostForwardUsage 转发的 token / 费用摘要(Core 侧计算后回传给调用方插件)。
-type HostForwardUsage struct {
+type FrontendWidgetProto struct {
state protoimpl.MessageState `protogen:"open.v1"`
- InputTokens int64 `protobuf:"varint,1,opt,name=input_tokens,json=inputTokens,proto3" json:"input_tokens,omitempty"`
- OutputTokens int64 `protobuf:"varint,2,opt,name=output_tokens,json=outputTokens,proto3" json:"output_tokens,omitempty"`
- Cost float64 `protobuf:"fixed64,3,opt,name=cost,proto3" json:"cost,omitempty"` // 总费用(已计入倍率)
- Model string `protobuf:"bytes,4,opt,name=model,proto3" json:"model,omitempty"` // 实际使用的 model(可能被 Core 改写)
+ Slot string `protobuf:"bytes,1,opt,name=slot,proto3" json:"slot,omitempty"`
+ EntryFile string `protobuf:"bytes,2,opt,name=entry_file,json=entryFile,proto3" json:"entry_file,omitempty"`
+ Title string `protobuf:"bytes,3,opt,name=title,proto3" json:"title,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
-func (x *HostForwardUsage) Reset() {
- *x = HostForwardUsage{}
- mi := &file_plugin_proto_msgTypes[11]
+func (x *FrontendWidgetProto) Reset() {
+ *x = FrontendWidgetProto{}
+ mi := &file_plugin_proto_msgTypes[8]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
-func (x *HostForwardUsage) String() string {
+func (x *FrontendWidgetProto) String() string {
return protoimpl.X.MessageStringOf(x)
}
-func (*HostForwardUsage) ProtoMessage() {}
+func (*FrontendWidgetProto) ProtoMessage() {}
-func (x *HostForwardUsage) ProtoReflect() protoreflect.Message {
- mi := &file_plugin_proto_msgTypes[11]
+func (x *FrontendWidgetProto) ProtoReflect() protoreflect.Message {
+ mi := &file_plugin_proto_msgTypes[8]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -969,60 +837,60 @@ func (x *HostForwardUsage) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
-// Deprecated: Use HostForwardUsage.ProtoReflect.Descriptor instead.
-func (*HostForwardUsage) Descriptor() ([]byte, []int) {
- return file_plugin_proto_rawDescGZIP(), []int{11}
-}
-
-func (x *HostForwardUsage) GetInputTokens() int64 {
- if x != nil {
- return x.InputTokens
- }
- return 0
+// Deprecated: Use FrontendWidgetProto.ProtoReflect.Descriptor instead.
+func (*FrontendWidgetProto) Descriptor() ([]byte, []int) {
+ return file_plugin_proto_rawDescGZIP(), []int{8}
}
-func (x *HostForwardUsage) GetOutputTokens() int64 {
+func (x *FrontendWidgetProto) GetSlot() string {
if x != nil {
- return x.OutputTokens
+ return x.Slot
}
- return 0
+ return ""
}
-func (x *HostForwardUsage) GetCost() float64 {
+func (x *FrontendWidgetProto) GetEntryFile() string {
if x != nil {
- return x.Cost
+ return x.EntryFile
}
- return 0
+ return ""
}
-func (x *HostForwardUsage) GetModel() string {
+func (x *FrontendWidgetProto) GetTitle() string {
if x != nil {
- return x.Model
+ return x.Title
}
return ""
}
-type HostListPlatformsRequest struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
+type InitRequest struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Config map[string]string `protobuf:"bytes,1,rep,name=config,proto3" json:"config,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
+ LogLevel string `protobuf:"bytes,2,opt,name=log_level,json=logLevel,proto3" json:"log_level,omitempty"`
+ // core_invoke_broker_id 是 Core 通过 hashicorp/go-plugin GRPCBroker 启动的
+ // 反向调用 stream ID。插件 Init 时拿到 ID 后,可以通过 broker.Dial(id)
+ // 拿到 CoreInvokeService grpc client,通过 Invoke / InvokeStream 回调 Core 开放的方法。
+ // 0 表示 Core 没启用反向调用。
+ CoreInvokeBrokerId uint32 `protobuf:"varint,3,opt,name=core_invoke_broker_id,json=coreInvokeBrokerId,proto3" json:"core_invoke_broker_id,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
}
-func (x *HostListPlatformsRequest) Reset() {
- *x = HostListPlatformsRequest{}
- mi := &file_plugin_proto_msgTypes[12]
+func (x *InitRequest) Reset() {
+ *x = InitRequest{}
+ mi := &file_plugin_proto_msgTypes[9]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
-func (x *HostListPlatformsRequest) String() string {
+func (x *InitRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
-func (*HostListPlatformsRequest) ProtoMessage() {}
+func (*InitRequest) ProtoMessage() {}
-func (x *HostListPlatformsRequest) ProtoReflect() protoreflect.Message {
- mi := &file_plugin_proto_msgTypes[12]
+func (x *InitRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_plugin_proto_msgTypes[9]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1033,34 +901,59 @@ func (x *HostListPlatformsRequest) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
-// Deprecated: Use HostListPlatformsRequest.ProtoReflect.Descriptor instead.
-func (*HostListPlatformsRequest) Descriptor() ([]byte, []int) {
- return file_plugin_proto_rawDescGZIP(), []int{12}
+// Deprecated: Use InitRequest.ProtoReflect.Descriptor instead.
+func (*InitRequest) Descriptor() ([]byte, []int) {
+ return file_plugin_proto_rawDescGZIP(), []int{9}
}
-type HostPlatform struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` // 平台标识(如 "openai")
- DisplayName string `protobuf:"bytes,2,opt,name=display_name,json=displayName,proto3" json:"display_name,omitempty"` // 展示名称(如 "OpenAI 网关")
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
+func (x *InitRequest) GetConfig() map[string]string {
+ if x != nil {
+ return x.Config
+ }
+ return nil
}
-func (x *HostPlatform) Reset() {
- *x = HostPlatform{}
- mi := &file_plugin_proto_msgTypes[13]
+func (x *InitRequest) GetLogLevel() string {
+ if x != nil {
+ return x.LogLevel
+ }
+ return ""
+}
+
+func (x *InitRequest) GetCoreInvokeBrokerId() uint32 {
+ if x != nil {
+ return x.CoreInvokeBrokerId
+ }
+ return 0
+}
+
+type ModelInfoProto struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
+ Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
+ ContextWindow int64 `protobuf:"varint,3,opt,name=context_window,json=contextWindow,proto3" json:"context_window,omitempty"`
+ MaxOutputTokens int64 `protobuf:"varint,4,opt,name=max_output_tokens,json=maxOutputTokens,proto3" json:"max_output_tokens,omitempty"`
+ Capabilities []string `protobuf:"bytes,5,rep,name=capabilities,proto3" json:"capabilities,omitempty"`
+ Metadata map[string]string `protobuf:"bytes,6,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *ModelInfoProto) Reset() {
+ *x = ModelInfoProto{}
+ mi := &file_plugin_proto_msgTypes[10]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
-func (x *HostPlatform) String() string {
+func (x *ModelInfoProto) String() string {
return protoimpl.X.MessageStringOf(x)
}
-func (*HostPlatform) ProtoMessage() {}
+func (*ModelInfoProto) ProtoMessage() {}
-func (x *HostPlatform) ProtoReflect() protoreflect.Message {
- mi := &file_plugin_proto_msgTypes[13]
+func (x *ModelInfoProto) ProtoReflect() protoreflect.Message {
+ mi := &file_plugin_proto_msgTypes[10]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1071,91 +964,75 @@ func (x *HostPlatform) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
-// Deprecated: Use HostPlatform.ProtoReflect.Descriptor instead.
-func (*HostPlatform) Descriptor() ([]byte, []int) {
- return file_plugin_proto_rawDescGZIP(), []int{13}
+// Deprecated: Use ModelInfoProto.ProtoReflect.Descriptor instead.
+func (*ModelInfoProto) Descriptor() ([]byte, []int) {
+ return file_plugin_proto_rawDescGZIP(), []int{10}
}
-func (x *HostPlatform) GetName() string {
+func (x *ModelInfoProto) GetId() string {
if x != nil {
- return x.Name
+ return x.Id
}
return ""
}
-func (x *HostPlatform) GetDisplayName() string {
+func (x *ModelInfoProto) GetName() string {
if x != nil {
- return x.DisplayName
+ return x.Name
}
return ""
}
-type HostListPlatformsResponse struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Platforms []*HostPlatform `protobuf:"bytes,1,rep,name=platforms,proto3" json:"platforms,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *HostListPlatformsResponse) Reset() {
- *x = HostListPlatformsResponse{}
- mi := &file_plugin_proto_msgTypes[14]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *HostListPlatformsResponse) String() string {
- return protoimpl.X.MessageStringOf(x)
+func (x *ModelInfoProto) GetContextWindow() int64 {
+ if x != nil {
+ return x.ContextWindow
+ }
+ return 0
}
-func (*HostListPlatformsResponse) ProtoMessage() {}
-
-func (x *HostListPlatformsResponse) ProtoReflect() protoreflect.Message {
- mi := &file_plugin_proto_msgTypes[14]
+func (x *ModelInfoProto) GetMaxOutputTokens() int64 {
if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
+ return x.MaxOutputTokens
}
- return mi.MessageOf(x)
+ return 0
}
-// Deprecated: Use HostListPlatformsResponse.ProtoReflect.Descriptor instead.
-func (*HostListPlatformsResponse) Descriptor() ([]byte, []int) {
- return file_plugin_proto_rawDescGZIP(), []int{14}
+func (x *ModelInfoProto) GetCapabilities() []string {
+ if x != nil {
+ return x.Capabilities
+ }
+ return nil
}
-func (x *HostListPlatformsResponse) GetPlatforms() []*HostPlatform {
+func (x *ModelInfoProto) GetMetadata() map[string]string {
if x != nil {
- return x.Platforms
+ return x.Metadata
}
return nil
}
-type HostListModelsRequest struct {
+type ModelsResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
- Platform string `protobuf:"bytes,1,opt,name=platform,proto3" json:"platform,omitempty"` // 必填:平台标识
+ Models []*ModelInfoProto `protobuf:"bytes,1,rep,name=models,proto3" json:"models,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
-func (x *HostListModelsRequest) Reset() {
- *x = HostListModelsRequest{}
- mi := &file_plugin_proto_msgTypes[15]
+func (x *ModelsResponse) Reset() {
+ *x = ModelsResponse{}
+ mi := &file_plugin_proto_msgTypes[11]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
-func (x *HostListModelsRequest) String() string {
+func (x *ModelsResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
-func (*HostListModelsRequest) ProtoMessage() {}
+func (*ModelsResponse) ProtoMessage() {}
-func (x *HostListModelsRequest) ProtoReflect() protoreflect.Message {
- mi := &file_plugin_proto_msgTypes[15]
+func (x *ModelsResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_plugin_proto_msgTypes[11]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1166,40 +1043,43 @@ func (x *HostListModelsRequest) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
-// Deprecated: Use HostListModelsRequest.ProtoReflect.Descriptor instead.
-func (*HostListModelsRequest) Descriptor() ([]byte, []int) {
- return file_plugin_proto_rawDescGZIP(), []int{15}
+// Deprecated: Use ModelsResponse.ProtoReflect.Descriptor instead.
+func (*ModelsResponse) Descriptor() ([]byte, []int) {
+ return file_plugin_proto_rawDescGZIP(), []int{11}
}
-func (x *HostListModelsRequest) GetPlatform() string {
+func (x *ModelsResponse) GetModels() []*ModelInfoProto {
if x != nil {
- return x.Platform
+ return x.Models
}
- return ""
+ return nil
}
-type HostListModelsResponse struct {
+type RouteDefinitionProto struct {
state protoimpl.MessageState `protogen:"open.v1"`
- Models []*ModelInfoProto `protobuf:"bytes,1,rep,name=models,proto3" json:"models,omitempty"` // 复用已有 ModelInfoProto,避免平行数据结构漂移
+ Method string `protobuf:"bytes,1,opt,name=method,proto3" json:"method,omitempty"`
+ Path string `protobuf:"bytes,2,opt,name=path,proto3" json:"path,omitempty"`
+ Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"`
+ Metadata map[string]string `protobuf:"bytes,4,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
-func (x *HostListModelsResponse) Reset() {
- *x = HostListModelsResponse{}
- mi := &file_plugin_proto_msgTypes[16]
+func (x *RouteDefinitionProto) Reset() {
+ *x = RouteDefinitionProto{}
+ mi := &file_plugin_proto_msgTypes[12]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
-func (x *HostListModelsResponse) String() string {
+func (x *RouteDefinitionProto) String() string {
return protoimpl.X.MessageStringOf(x)
}
-func (*HostListModelsResponse) ProtoMessage() {}
+func (*RouteDefinitionProto) ProtoMessage() {}
-func (x *HostListModelsResponse) ProtoReflect() protoreflect.Message {
- mi := &file_plugin_proto_msgTypes[16]
+func (x *RouteDefinitionProto) ProtoReflect() protoreflect.Message {
+ mi := &file_plugin_proto_msgTypes[12]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1210,40 +1090,61 @@ func (x *HostListModelsResponse) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
-// Deprecated: Use HostListModelsResponse.ProtoReflect.Descriptor instead.
-func (*HostListModelsResponse) Descriptor() ([]byte, []int) {
- return file_plugin_proto_rawDescGZIP(), []int{16}
+// Deprecated: Use RouteDefinitionProto.ProtoReflect.Descriptor instead.
+func (*RouteDefinitionProto) Descriptor() ([]byte, []int) {
+ return file_plugin_proto_rawDescGZIP(), []int{12}
}
-func (x *HostListModelsResponse) GetModels() []*ModelInfoProto {
+func (x *RouteDefinitionProto) GetMethod() string {
if x != nil {
- return x.Models
+ return x.Method
+ }
+ return ""
+}
+
+func (x *RouteDefinitionProto) GetPath() string {
+ if x != nil {
+ return x.Path
+ }
+ return ""
+}
+
+func (x *RouteDefinitionProto) GetDescription() string {
+ if x != nil {
+ return x.Description
+ }
+ return ""
+}
+
+func (x *RouteDefinitionProto) GetMetadata() map[string]string {
+ if x != nil {
+ return x.Metadata
}
return nil
}
-type HostGetUserInfoRequest struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- UserId int64 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
+type RoutesResponse struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Routes []*RouteDefinitionProto `protobuf:"bytes,1,rep,name=routes,proto3" json:"routes,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
-func (x *HostGetUserInfoRequest) Reset() {
- *x = HostGetUserInfoRequest{}
- mi := &file_plugin_proto_msgTypes[17]
+func (x *RoutesResponse) Reset() {
+ *x = RoutesResponse{}
+ mi := &file_plugin_proto_msgTypes[13]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
-func (x *HostGetUserInfoRequest) String() string {
+func (x *RoutesResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
-func (*HostGetUserInfoRequest) ProtoMessage() {}
+func (*RoutesResponse) ProtoMessage() {}
-func (x *HostGetUserInfoRequest) ProtoReflect() protoreflect.Message {
- mi := &file_plugin_proto_msgTypes[17]
+func (x *RoutesResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_plugin_proto_msgTypes[13]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1254,45 +1155,45 @@ func (x *HostGetUserInfoRequest) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
-// Deprecated: Use HostGetUserInfoRequest.ProtoReflect.Descriptor instead.
-func (*HostGetUserInfoRequest) Descriptor() ([]byte, []int) {
- return file_plugin_proto_rawDescGZIP(), []int{17}
+// Deprecated: Use RoutesResponse.ProtoReflect.Descriptor instead.
+func (*RoutesResponse) Descriptor() ([]byte, []int) {
+ return file_plugin_proto_rawDescGZIP(), []int{13}
}
-func (x *HostGetUserInfoRequest) GetUserId() int64 {
+func (x *RoutesResponse) GetRoutes() []*RouteDefinitionProto {
if x != nil {
- return x.UserId
+ return x.Routes
}
- return 0
+ return nil
}
-type HostGetUserInfoResponse struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- UserId int64 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
- Username string `protobuf:"bytes,2,opt,name=username,proto3" json:"username,omitempty"`
- Email string `protobuf:"bytes,3,opt,name=email,proto3" json:"email,omitempty"`
- Role string `protobuf:"bytes,4,opt,name=role,proto3" json:"role,omitempty"` // "admin" / "user"
- Balance float64 `protobuf:"fixed64,5,opt,name=balance,proto3" json:"balance,omitempty"`
- Status string `protobuf:"bytes,6,opt,name=status,proto3" json:"status,omitempty"` // "active" / "disabled"
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
+type AccountProto struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
+ Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
+ Platform string `protobuf:"bytes,3,opt,name=platform,proto3" json:"platform,omitempty"`
+ Type string `protobuf:"bytes,4,opt,name=type,proto3" json:"type,omitempty"`
+ CredentialsJson []byte `protobuf:"bytes,5,opt,name=credentials_json,json=credentialsJson,proto3" json:"credentials_json,omitempty"`
+ ProxyUrl string `protobuf:"bytes,6,opt,name=proxy_url,json=proxyUrl,proto3" json:"proxy_url,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
}
-func (x *HostGetUserInfoResponse) Reset() {
- *x = HostGetUserInfoResponse{}
- mi := &file_plugin_proto_msgTypes[18]
+func (x *AccountProto) Reset() {
+ *x = AccountProto{}
+ mi := &file_plugin_proto_msgTypes[14]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
-func (x *HostGetUserInfoResponse) String() string {
+func (x *AccountProto) String() string {
return protoimpl.X.MessageStringOf(x)
}
-func (*HostGetUserInfoResponse) ProtoMessage() {}
+func (*AccountProto) ProtoMessage() {}
-func (x *HostGetUserInfoResponse) ProtoReflect() protoreflect.Message {
- mi := &file_plugin_proto_msgTypes[18]
+func (x *AccountProto) ProtoReflect() protoreflect.Message {
+ mi := &file_plugin_proto_msgTypes[14]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1303,79 +1204,79 @@ func (x *HostGetUserInfoResponse) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
-// Deprecated: Use HostGetUserInfoResponse.ProtoReflect.Descriptor instead.
-func (*HostGetUserInfoResponse) Descriptor() ([]byte, []int) {
- return file_plugin_proto_rawDescGZIP(), []int{18}
+// Deprecated: Use AccountProto.ProtoReflect.Descriptor instead.
+func (*AccountProto) Descriptor() ([]byte, []int) {
+ return file_plugin_proto_rawDescGZIP(), []int{14}
}
-func (x *HostGetUserInfoResponse) GetUserId() int64 {
+func (x *AccountProto) GetId() int64 {
if x != nil {
- return x.UserId
+ return x.Id
}
return 0
}
-func (x *HostGetUserInfoResponse) GetUsername() string {
+func (x *AccountProto) GetName() string {
if x != nil {
- return x.Username
+ return x.Name
}
return ""
}
-func (x *HostGetUserInfoResponse) GetEmail() string {
+func (x *AccountProto) GetPlatform() string {
if x != nil {
- return x.Email
+ return x.Platform
}
return ""
}
-func (x *HostGetUserInfoResponse) GetRole() string {
+func (x *AccountProto) GetType() string {
if x != nil {
- return x.Role
+ return x.Type
}
return ""
}
-func (x *HostGetUserInfoResponse) GetBalance() float64 {
+func (x *AccountProto) GetCredentialsJson() []byte {
if x != nil {
- return x.Balance
+ return x.CredentialsJson
}
- return 0
+ return nil
}
-func (x *HostGetUserInfoResponse) GetStatus() string {
+func (x *AccountProto) GetProxyUrl() string {
if x != nil {
- return x.Status
+ return x.ProxyUrl
}
return ""
}
-type HostStoreAssetRequest struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- UserId int64 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
- Scope string `protobuf:"bytes,2,opt,name=scope,proto3" json:"scope,omitempty"`
- ContentType string `protobuf:"bytes,3,opt,name=content_type,json=contentType,proto3" json:"content_type,omitempty"`
- Data []byte `protobuf:"bytes,4,opt,name=data,proto3" json:"data,omitempty"`
- FileExtension string `protobuf:"bytes,5,opt,name=file_extension,json=fileExtension,proto3" json:"file_extension,omitempty"`
+type ForwardRequest struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Body []byte `protobuf:"bytes,7,opt,name=body,proto3" json:"body,omitempty"`
+ Headers map[string]*HeaderValues `protobuf:"bytes,8,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
+ Model string `protobuf:"bytes,9,opt,name=model,proto3" json:"model,omitempty"`
+ Stream bool `protobuf:"varint,10,opt,name=stream,proto3" json:"stream,omitempty"`
+ Account *AccountProto `protobuf:"bytes,11,opt,name=account,proto3" json:"account,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
-func (x *HostStoreAssetRequest) Reset() {
- *x = HostStoreAssetRequest{}
- mi := &file_plugin_proto_msgTypes[19]
+func (x *ForwardRequest) Reset() {
+ *x = ForwardRequest{}
+ mi := &file_plugin_proto_msgTypes[15]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
-func (x *HostStoreAssetRequest) String() string {
+func (x *ForwardRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
-func (*HostStoreAssetRequest) ProtoMessage() {}
+func (*ForwardRequest) ProtoMessage() {}
-func (x *HostStoreAssetRequest) ProtoReflect() protoreflect.Message {
- mi := &file_plugin_proto_msgTypes[19]
+func (x *ForwardRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_plugin_proto_msgTypes[15]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1386,72 +1287,71 @@ func (x *HostStoreAssetRequest) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
-// Deprecated: Use HostStoreAssetRequest.ProtoReflect.Descriptor instead.
-func (*HostStoreAssetRequest) Descriptor() ([]byte, []int) {
- return file_plugin_proto_rawDescGZIP(), []int{19}
+// Deprecated: Use ForwardRequest.ProtoReflect.Descriptor instead.
+func (*ForwardRequest) Descriptor() ([]byte, []int) {
+ return file_plugin_proto_rawDescGZIP(), []int{15}
}
-func (x *HostStoreAssetRequest) GetUserId() int64 {
+func (x *ForwardRequest) GetBody() []byte {
if x != nil {
- return x.UserId
+ return x.Body
}
- return 0
+ return nil
}
-func (x *HostStoreAssetRequest) GetScope() string {
+func (x *ForwardRequest) GetHeaders() map[string]*HeaderValues {
if x != nil {
- return x.Scope
+ return x.Headers
}
- return ""
+ return nil
}
-func (x *HostStoreAssetRequest) GetContentType() string {
+func (x *ForwardRequest) GetModel() string {
if x != nil {
- return x.ContentType
+ return x.Model
}
return ""
}
-func (x *HostStoreAssetRequest) GetData() []byte {
+func (x *ForwardRequest) GetStream() bool {
if x != nil {
- return x.Data
+ return x.Stream
}
- return nil
+ return false
}
-func (x *HostStoreAssetRequest) GetFileExtension() string {
+func (x *ForwardRequest) GetAccount() *AccountProto {
if x != nil {
- return x.FileExtension
+ return x.Account
}
- return ""
+ return nil
}
-type HostStoreAssetResponse struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- AssetId string `protobuf:"bytes,1,opt,name=asset_id,json=assetId,proto3" json:"asset_id,omitempty"`
- ObjectKey string `protobuf:"bytes,2,opt,name=object_key,json=objectKey,proto3" json:"object_key,omitempty"`
- PublicUrl string `protobuf:"bytes,3,opt,name=public_url,json=publicUrl,proto3" json:"public_url,omitempty"`
- SizeBytes int64 `protobuf:"varint,4,opt,name=size_bytes,json=sizeBytes,proto3" json:"size_bytes,omitempty"`
- ContentType string `protobuf:"bytes,5,opt,name=content_type,json=contentType,proto3" json:"content_type,omitempty"`
+// UpstreamResponse 上游返回的原始 HTTP 快照。
+type UpstreamResponse struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ StatusCode int32 `protobuf:"varint,1,opt,name=status_code,json=statusCode,proto3" json:"status_code,omitempty"`
+ Headers map[string]*HeaderValues `protobuf:"bytes,2,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
+ Body []byte `protobuf:"bytes,3,opt,name=body,proto3" json:"body,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
-func (x *HostStoreAssetResponse) Reset() {
- *x = HostStoreAssetResponse{}
- mi := &file_plugin_proto_msgTypes[20]
+func (x *UpstreamResponse) Reset() {
+ *x = UpstreamResponse{}
+ mi := &file_plugin_proto_msgTypes[16]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
-func (x *HostStoreAssetResponse) String() string {
+func (x *UpstreamResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
-func (*HostStoreAssetResponse) ProtoMessage() {}
+func (*UpstreamResponse) ProtoMessage() {}
-func (x *HostStoreAssetResponse) ProtoReflect() protoreflect.Message {
- mi := &file_plugin_proto_msgTypes[20]
+func (x *UpstreamResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_plugin_proto_msgTypes[16]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1462,68 +1362,59 @@ func (x *HostStoreAssetResponse) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
-// Deprecated: Use HostStoreAssetResponse.ProtoReflect.Descriptor instead.
-func (*HostStoreAssetResponse) Descriptor() ([]byte, []int) {
- return file_plugin_proto_rawDescGZIP(), []int{20}
-}
-
-func (x *HostStoreAssetResponse) GetAssetId() string {
- if x != nil {
- return x.AssetId
- }
- return ""
-}
-
-func (x *HostStoreAssetResponse) GetObjectKey() string {
- if x != nil {
- return x.ObjectKey
- }
- return ""
+// Deprecated: Use UpstreamResponse.ProtoReflect.Descriptor instead.
+func (*UpstreamResponse) Descriptor() ([]byte, []int) {
+ return file_plugin_proto_rawDescGZIP(), []int{16}
}
-func (x *HostStoreAssetResponse) GetPublicUrl() string {
+func (x *UpstreamResponse) GetStatusCode() int32 {
if x != nil {
- return x.PublicUrl
+ return x.StatusCode
}
- return ""
+ return 0
}
-func (x *HostStoreAssetResponse) GetSizeBytes() int64 {
+func (x *UpstreamResponse) GetHeaders() map[string]*HeaderValues {
if x != nil {
- return x.SizeBytes
+ return x.Headers
}
- return 0
+ return nil
}
-func (x *HostStoreAssetResponse) GetContentType() string {
+func (x *UpstreamResponse) GetBody() []byte {
if x != nil {
- return x.ContentType
+ return x.Body
}
- return ""
+ return nil
}
-type HostGetAssetURLRequest struct {
+// UsageAttribute 是插件计算后的通用审计维度。
+type UsageAttribute struct {
state protoimpl.MessageState `protogen:"open.v1"`
- ObjectKey string `protobuf:"bytes,1,opt,name=object_key,json=objectKey,proto3" json:"object_key,omitempty"`
+ Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
+ Label string `protobuf:"bytes,2,opt,name=label,proto3" json:"label,omitempty"`
+ Kind string `protobuf:"bytes,3,opt,name=kind,proto3" json:"kind,omitempty"`
+ Value string `protobuf:"bytes,4,opt,name=value,proto3" json:"value,omitempty"`
+ Metadata map[string]string `protobuf:"bytes,5,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
-func (x *HostGetAssetURLRequest) Reset() {
- *x = HostGetAssetURLRequest{}
- mi := &file_plugin_proto_msgTypes[21]
+func (x *UsageAttribute) Reset() {
+ *x = UsageAttribute{}
+ mi := &file_plugin_proto_msgTypes[17]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
-func (x *HostGetAssetURLRequest) String() string {
+func (x *UsageAttribute) String() string {
return protoimpl.X.MessageStringOf(x)
}
-func (*HostGetAssetURLRequest) ProtoMessage() {}
+func (*UsageAttribute) ProtoMessage() {}
-func (x *HostGetAssetURLRequest) ProtoReflect() protoreflect.Message {
- mi := &file_plugin_proto_msgTypes[21]
+func (x *UsageAttribute) ProtoReflect() protoreflect.Message {
+ mi := &file_plugin_proto_msgTypes[17]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1534,40 +1425,76 @@ func (x *HostGetAssetURLRequest) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
-// Deprecated: Use HostGetAssetURLRequest.ProtoReflect.Descriptor instead.
-func (*HostGetAssetURLRequest) Descriptor() ([]byte, []int) {
- return file_plugin_proto_rawDescGZIP(), []int{21}
+// Deprecated: Use UsageAttribute.ProtoReflect.Descriptor instead.
+func (*UsageAttribute) Descriptor() ([]byte, []int) {
+ return file_plugin_proto_rawDescGZIP(), []int{17}
+}
+
+func (x *UsageAttribute) GetKey() string {
+ if x != nil {
+ return x.Key
+ }
+ return ""
+}
+
+func (x *UsageAttribute) GetLabel() string {
+ if x != nil {
+ return x.Label
+ }
+ return ""
+}
+
+func (x *UsageAttribute) GetKind() string {
+ if x != nil {
+ return x.Kind
+ }
+ return ""
}
-func (x *HostGetAssetURLRequest) GetObjectKey() string {
+func (x *UsageAttribute) GetValue() string {
if x != nil {
- return x.ObjectKey
+ return x.Value
}
return ""
}
-type HostGetAssetURLResponse struct {
+func (x *UsageAttribute) GetMetadata() map[string]string {
+ if x != nil {
+ return x.Metadata
+ }
+ return nil
+}
+
+// UsageMetric 是插件计算后的通用计量结果。
+type UsageMetric struct {
state protoimpl.MessageState `protogen:"open.v1"`
- PublicUrl string `protobuf:"bytes,1,opt,name=public_url,json=publicUrl,proto3" json:"public_url,omitempty"`
+ Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
+ Label string `protobuf:"bytes,2,opt,name=label,proto3" json:"label,omitempty"`
+ Kind string `protobuf:"bytes,3,opt,name=kind,proto3" json:"kind,omitempty"`
+ Unit string `protobuf:"bytes,4,opt,name=unit,proto3" json:"unit,omitempty"`
+ Value float64 `protobuf:"fixed64,5,opt,name=value,proto3" json:"value,omitempty"`
+ AccountCost float64 `protobuf:"fixed64,6,opt,name=account_cost,json=accountCost,proto3" json:"account_cost,omitempty"`
+ Currency string `protobuf:"bytes,7,opt,name=currency,proto3" json:"currency,omitempty"`
+ Metadata map[string]string `protobuf:"bytes,8,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
-func (x *HostGetAssetURLResponse) Reset() {
- *x = HostGetAssetURLResponse{}
- mi := &file_plugin_proto_msgTypes[22]
+func (x *UsageMetric) Reset() {
+ *x = UsageMetric{}
+ mi := &file_plugin_proto_msgTypes[18]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
-func (x *HostGetAssetURLResponse) String() string {
+func (x *UsageMetric) String() string {
return protoimpl.X.MessageStringOf(x)
}
-func (*HostGetAssetURLResponse) ProtoMessage() {}
+func (*UsageMetric) ProtoMessage() {}
-func (x *HostGetAssetURLResponse) ProtoReflect() protoreflect.Message {
- mi := &file_plugin_proto_msgTypes[22]
+func (x *UsageMetric) ProtoReflect() protoreflect.Message {
+ mi := &file_plugin_proto_msgTypes[18]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1578,85 +1505,96 @@ func (x *HostGetAssetURLResponse) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
-// Deprecated: Use HostGetAssetURLResponse.ProtoReflect.Descriptor instead.
-func (*HostGetAssetURLResponse) Descriptor() ([]byte, []int) {
- return file_plugin_proto_rawDescGZIP(), []int{22}
+// Deprecated: Use UsageMetric.ProtoReflect.Descriptor instead.
+func (*UsageMetric) Descriptor() ([]byte, []int) {
+ return file_plugin_proto_rawDescGZIP(), []int{18}
}
-func (x *HostGetAssetURLResponse) GetPublicUrl() string {
+func (x *UsageMetric) GetKey() string {
if x != nil {
- return x.PublicUrl
+ return x.Key
}
return ""
}
-type HostGetAssetBytesRequest struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- ObjectKey string `protobuf:"bytes,1,opt,name=object_key,json=objectKey,proto3" json:"object_key,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
+func (x *UsageMetric) GetLabel() string {
+ if x != nil {
+ return x.Label
+ }
+ return ""
}
-func (x *HostGetAssetBytesRequest) Reset() {
- *x = HostGetAssetBytesRequest{}
- mi := &file_plugin_proto_msgTypes[23]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
+func (x *UsageMetric) GetKind() string {
+ if x != nil {
+ return x.Kind
+ }
+ return ""
}
-func (x *HostGetAssetBytesRequest) String() string {
- return protoimpl.X.MessageStringOf(x)
+func (x *UsageMetric) GetUnit() string {
+ if x != nil {
+ return x.Unit
+ }
+ return ""
}
-func (*HostGetAssetBytesRequest) ProtoMessage() {}
-
-func (x *HostGetAssetBytesRequest) ProtoReflect() protoreflect.Message {
- mi := &file_plugin_proto_msgTypes[23]
+func (x *UsageMetric) GetValue() float64 {
if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
+ return x.Value
}
- return mi.MessageOf(x)
+ return 0
}
-// Deprecated: Use HostGetAssetBytesRequest.ProtoReflect.Descriptor instead.
-func (*HostGetAssetBytesRequest) Descriptor() ([]byte, []int) {
- return file_plugin_proto_rawDescGZIP(), []int{23}
+func (x *UsageMetric) GetAccountCost() float64 {
+ if x != nil {
+ return x.AccountCost
+ }
+ return 0
}
-func (x *HostGetAssetBytesRequest) GetObjectKey() string {
+func (x *UsageMetric) GetCurrency() string {
if x != nil {
- return x.ObjectKey
+ return x.Currency
}
return ""
}
-type HostGetAssetBytesResponse struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"`
- ContentType string `protobuf:"bytes,2,opt,name=content_type,json=contentType,proto3" json:"content_type,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
+func (x *UsageMetric) GetMetadata() map[string]string {
+ if x != nil {
+ return x.Metadata
+ }
+ return nil
}
-func (x *HostGetAssetBytesResponse) Reset() {
- *x = HostGetAssetBytesResponse{}
- mi := &file_plugin_proto_msgTypes[24]
+// UsageCostDetail 是通用费用明细。
+type UsageCostDetail struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
+ Label string `protobuf:"bytes,2,opt,name=label,proto3" json:"label,omitempty"`
+ AccountCost float64 `protobuf:"fixed64,3,opt,name=account_cost,json=accountCost,proto3" json:"account_cost,omitempty"`
+ UserCost float64 `protobuf:"fixed64,4,opt,name=user_cost,json=userCost,proto3" json:"user_cost,omitempty"`
+ BillingMultiplier float64 `protobuf:"fixed64,5,opt,name=billing_multiplier,json=billingMultiplier,proto3" json:"billing_multiplier,omitempty"`
+ Currency string `protobuf:"bytes,6,opt,name=currency,proto3" json:"currency,omitempty"`
+ Metadata map[string]string `protobuf:"bytes,7,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *UsageCostDetail) Reset() {
+ *x = UsageCostDetail{}
+ mi := &file_plugin_proto_msgTypes[19]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
-func (x *HostGetAssetBytesResponse) String() string {
+func (x *UsageCostDetail) String() string {
return protoimpl.X.MessageStringOf(x)
}
-func (*HostGetAssetBytesResponse) ProtoMessage() {}
+func (*UsageCostDetail) ProtoMessage() {}
-func (x *HostGetAssetBytesResponse) ProtoReflect() protoreflect.Message {
- mi := &file_plugin_proto_msgTypes[24]
+func (x *UsageCostDetail) ProtoReflect() protoreflect.Message {
+ mi := &file_plugin_proto_msgTypes[19]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1667,83 +1605,93 @@ func (x *HostGetAssetBytesResponse) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
-// Deprecated: Use HostGetAssetBytesResponse.ProtoReflect.Descriptor instead.
-func (*HostGetAssetBytesResponse) Descriptor() ([]byte, []int) {
- return file_plugin_proto_rawDescGZIP(), []int{24}
+// Deprecated: Use UsageCostDetail.ProtoReflect.Descriptor instead.
+func (*UsageCostDetail) Descriptor() ([]byte, []int) {
+ return file_plugin_proto_rawDescGZIP(), []int{19}
}
-func (x *HostGetAssetBytesResponse) GetData() []byte {
+func (x *UsageCostDetail) GetKey() string {
if x != nil {
- return x.Data
+ return x.Key
}
- return nil
+ return ""
}
-func (x *HostGetAssetBytesResponse) GetContentType() string {
+func (x *UsageCostDetail) GetLabel() string {
if x != nil {
- return x.ContentType
+ return x.Label
}
return ""
}
-type Empty struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
+func (x *UsageCostDetail) GetAccountCost() float64 {
+ if x != nil {
+ return x.AccountCost
+ }
+ return 0
}
-func (x *Empty) Reset() {
- *x = Empty{}
- mi := &file_plugin_proto_msgTypes[25]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
+func (x *UsageCostDetail) GetUserCost() float64 {
+ if x != nil {
+ return x.UserCost
+ }
+ return 0
}
-func (x *Empty) String() string {
- return protoimpl.X.MessageStringOf(x)
+func (x *UsageCostDetail) GetBillingMultiplier() float64 {
+ if x != nil {
+ return x.BillingMultiplier
+ }
+ return 0
}
-func (*Empty) ProtoMessage() {}
-
-func (x *Empty) ProtoReflect() protoreflect.Message {
- mi := &file_plugin_proto_msgTypes[25]
+func (x *UsageCostDetail) GetCurrency() string {
if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
+ return x.Currency
}
- return mi.MessageOf(x)
+ return ""
}
-// Deprecated: Use Empty.ProtoReflect.Descriptor instead.
-func (*Empty) Descriptor() ([]byte, []int) {
- return file_plugin_proto_rawDescGZIP(), []int{25}
+func (x *UsageCostDetail) GetMetadata() map[string]string {
+ if x != nil {
+ return x.Metadata
+ }
+ return nil
}
-type StringResponse struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Value string `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
+// Usage 单次调用的用量与费用结果。非 Success 判决下应为空。
+type Usage struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Model string `protobuf:"bytes,1,opt,name=model,proto3" json:"model,omitempty"`
+ AccountCost float64 `protobuf:"fixed64,2,opt,name=account_cost,json=accountCost,proto3" json:"account_cost,omitempty"`
+ UserCost float64 `protobuf:"fixed64,3,opt,name=user_cost,json=userCost,proto3" json:"user_cost,omitempty"`
+ BillingMultiplier float64 `protobuf:"fixed64,4,opt,name=billing_multiplier,json=billingMultiplier,proto3" json:"billing_multiplier,omitempty"`
+ Currency string `protobuf:"bytes,5,opt,name=currency,proto3" json:"currency,omitempty"`
+ Summary string `protobuf:"bytes,6,opt,name=summary,proto3" json:"summary,omitempty"`
+ FirstTokenMs int64 `protobuf:"varint,7,opt,name=first_token_ms,json=firstTokenMs,proto3" json:"first_token_ms,omitempty"`
+ Metrics []*UsageMetric `protobuf:"bytes,8,rep,name=metrics,proto3" json:"metrics,omitempty"`
+ Attributes []*UsageAttribute `protobuf:"bytes,9,rep,name=attributes,proto3" json:"attributes,omitempty"`
+ CostDetails []*UsageCostDetail `protobuf:"bytes,10,rep,name=cost_details,json=costDetails,proto3" json:"cost_details,omitempty"`
+ Metadata map[string]string `protobuf:"bytes,11,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
}
-func (x *StringResponse) Reset() {
- *x = StringResponse{}
- mi := &file_plugin_proto_msgTypes[26]
+func (x *Usage) Reset() {
+ *x = Usage{}
+ mi := &file_plugin_proto_msgTypes[20]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
-func (x *StringResponse) String() string {
+func (x *Usage) String() string {
return protoimpl.X.MessageStringOf(x)
}
-func (*StringResponse) ProtoMessage() {}
+func (*Usage) ProtoMessage() {}
-func (x *StringResponse) ProtoReflect() protoreflect.Message {
- mi := &file_plugin_proto_msgTypes[26]
+func (x *Usage) ProtoReflect() protoreflect.Message {
+ mi := &file_plugin_proto_msgTypes[20]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1754,104 +1702,117 @@ func (x *StringResponse) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
-// Deprecated: Use StringResponse.ProtoReflect.Descriptor instead.
-func (*StringResponse) Descriptor() ([]byte, []int) {
- return file_plugin_proto_rawDescGZIP(), []int{26}
+// Deprecated: Use Usage.ProtoReflect.Descriptor instead.
+func (*Usage) Descriptor() ([]byte, []int) {
+ return file_plugin_proto_rawDescGZIP(), []int{20}
}
-func (x *StringResponse) GetValue() string {
+func (x *Usage) GetModel() string {
if x != nil {
- return x.Value
+ return x.Model
}
return ""
}
-// HeaderValues 支持同一 Header 有多个值(如 Set-Cookie)
-type HeaderValues struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Values []string `protobuf:"bytes,1,rep,name=values,proto3" json:"values,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
+func (x *Usage) GetAccountCost() float64 {
+ if x != nil {
+ return x.AccountCost
+ }
+ return 0
}
-func (x *HeaderValues) Reset() {
- *x = HeaderValues{}
- mi := &file_plugin_proto_msgTypes[27]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
+func (x *Usage) GetUserCost() float64 {
+ if x != nil {
+ return x.UserCost
+ }
+ return 0
}
-func (x *HeaderValues) String() string {
- return protoimpl.X.MessageStringOf(x)
+func (x *Usage) GetBillingMultiplier() float64 {
+ if x != nil {
+ return x.BillingMultiplier
+ }
+ return 0
}
-func (*HeaderValues) ProtoMessage() {}
+func (x *Usage) GetCurrency() string {
+ if x != nil {
+ return x.Currency
+ }
+ return ""
+}
-func (x *HeaderValues) ProtoReflect() protoreflect.Message {
- mi := &file_plugin_proto_msgTypes[27]
+func (x *Usage) GetSummary() string {
if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
+ return x.Summary
}
- return mi.MessageOf(x)
+ return ""
}
-// Deprecated: Use HeaderValues.ProtoReflect.Descriptor instead.
-func (*HeaderValues) Descriptor() ([]byte, []int) {
- return file_plugin_proto_rawDescGZIP(), []int{27}
+func (x *Usage) GetFirstTokenMs() int64 {
+ if x != nil {
+ return x.FirstTokenMs
+ }
+ return 0
}
-func (x *HeaderValues) GetValues() []string {
+func (x *Usage) GetMetrics() []*UsageMetric {
if x != nil {
- return x.Values
+ return x.Metrics
}
return nil
}
-type PluginInfoResponse struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
- Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
- Version string `protobuf:"bytes,3,opt,name=version,proto3" json:"version,omitempty"`
- Description string `protobuf:"bytes,4,opt,name=description,proto3" json:"description,omitempty"`
- Author string `protobuf:"bytes,5,opt,name=author,proto3" json:"author,omitempty"`
- Type string `protobuf:"bytes,6,opt,name=type,proto3" json:"type,omitempty"`
- AccountTypes []*AccountTypeProto `protobuf:"bytes,7,rep,name=account_types,json=accountTypes,proto3" json:"account_types,omitempty"`
- FrontendPages []*FrontendPageProto `protobuf:"bytes,8,rep,name=frontend_pages,json=frontendPages,proto3" json:"frontend_pages,omitempty"`
- FrontendWidgets []*FrontendWidgetProto `protobuf:"bytes,9,rep,name=frontend_widgets,json=frontendWidgets,proto3" json:"frontend_widgets,omitempty"`
- SdkVersion string `protobuf:"bytes,10,opt,name=sdk_version,json=sdkVersion,proto3" json:"sdk_version,omitempty"` // 插件编译时的 SDK 版本
- Dependencies []string `protobuf:"bytes,11,rep,name=dependencies,proto3" json:"dependencies,omitempty"` // 依赖的其他插件 ID
- ConfigSchema []*ConfigFieldProto `protobuf:"bytes,12,rep,name=config_schema,json=configSchema,proto3" json:"config_schema,omitempty"` // 配置项声明
- InstructionPresets []string `protobuf:"bytes,13,rep,name=instruction_presets,json=instructionPresets,proto3" json:"instruction_presets,omitempty"` // 可用的 instructions 预设名称列表
- // capabilities 声明的 HostService / Middleware 能力。Core 启动时按
- // "插件类型 → 允许集合" 做交集,gRPC interceptor 按此 set 做准入校验。
- // SDK 版本 <= 0.2.x 的插件豁免(兼容存量),0.3.x 起强制。详见 ADR-0001 Decision 4。
- Capabilities []string `protobuf:"bytes,14,rep,name=capabilities,proto3" json:"capabilities,omitempty"`
- // priority 中间件链中的排序权重(数值越小越早进 Begin、越晚出 End)。
- // 仅 type="middleware" 插件使用;其他类型忽略。默认 100。
- Priority int32 `protobuf:"varint,15,opt,name=priority,proto3" json:"priority,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
+func (x *Usage) GetAttributes() []*UsageAttribute {
+ if x != nil {
+ return x.Attributes
+ }
+ return nil
}
-func (x *PluginInfoResponse) Reset() {
- *x = PluginInfoResponse{}
- mi := &file_plugin_proto_msgTypes[28]
+func (x *Usage) GetCostDetails() []*UsageCostDetail {
+ if x != nil {
+ return x.CostDetails
+ }
+ return nil
+}
+
+func (x *Usage) GetMetadata() map[string]string {
+ if x != nil {
+ return x.Metadata
+ }
+ return nil
+}
+
+// ForwardOutcome 插件对一次 Forward 的完整判决结果。
+type ForwardOutcome struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Kind OutcomeKind `protobuf:"varint,1,opt,name=kind,proto3,enum=airgate.plugin.v2.OutcomeKind" json:"kind,omitempty"`
+ Upstream *UpstreamResponse `protobuf:"bytes,2,opt,name=upstream,proto3" json:"upstream,omitempty"`
+ Usage *Usage `protobuf:"bytes,3,opt,name=usage,proto3" json:"usage,omitempty"`
+ DurationMs int64 `protobuf:"varint,4,opt,name=duration_ms,json=durationMs,proto3" json:"duration_ms,omitempty"`
+ RetryAfterMs int64 `protobuf:"varint,5,opt,name=retry_after_ms,json=retryAfterMs,proto3" json:"retry_after_ms,omitempty"`
+ Reason string `protobuf:"bytes,6,opt,name=reason,proto3" json:"reason,omitempty"`
+ UpdatedCredentials map[string]string `protobuf:"bytes,7,rep,name=updated_credentials,json=updatedCredentials,proto3" json:"updated_credentials,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *ForwardOutcome) Reset() {
+ *x = ForwardOutcome{}
+ mi := &file_plugin_proto_msgTypes[21]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
-func (x *PluginInfoResponse) String() string {
+func (x *ForwardOutcome) String() string {
return protoimpl.X.MessageStringOf(x)
}
-func (*PluginInfoResponse) ProtoMessage() {}
+func (*ForwardOutcome) ProtoMessage() {}
-func (x *PluginInfoResponse) ProtoReflect() protoreflect.Message {
- mi := &file_plugin_proto_msgTypes[28]
+func (x *ForwardOutcome) ProtoReflect() protoreflect.Message {
+ mi := &file_plugin_proto_msgTypes[21]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -1862,144 +1823,207 @@ func (x *PluginInfoResponse) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
-// Deprecated: Use PluginInfoResponse.ProtoReflect.Descriptor instead.
-func (*PluginInfoResponse) Descriptor() ([]byte, []int) {
- return file_plugin_proto_rawDescGZIP(), []int{28}
+// Deprecated: Use ForwardOutcome.ProtoReflect.Descriptor instead.
+func (*ForwardOutcome) Descriptor() ([]byte, []int) {
+ return file_plugin_proto_rawDescGZIP(), []int{21}
}
-func (x *PluginInfoResponse) GetId() string {
+func (x *ForwardOutcome) GetKind() OutcomeKind {
if x != nil {
- return x.Id
+ return x.Kind
}
- return ""
+ return OutcomeKind_OUTCOME_UNKNOWN
}
-func (x *PluginInfoResponse) GetName() string {
+func (x *ForwardOutcome) GetUpstream() *UpstreamResponse {
if x != nil {
- return x.Name
+ return x.Upstream
}
- return ""
+ return nil
}
-func (x *PluginInfoResponse) GetVersion() string {
+func (x *ForwardOutcome) GetUsage() *Usage {
if x != nil {
- return x.Version
+ return x.Usage
}
- return ""
+ return nil
}
-func (x *PluginInfoResponse) GetDescription() string {
+func (x *ForwardOutcome) GetDurationMs() int64 {
if x != nil {
- return x.Description
+ return x.DurationMs
}
- return ""
+ return 0
}
-func (x *PluginInfoResponse) GetAuthor() string {
+func (x *ForwardOutcome) GetRetryAfterMs() int64 {
if x != nil {
- return x.Author
+ return x.RetryAfterMs
}
- return ""
+ return 0
}
-func (x *PluginInfoResponse) GetType() string {
+func (x *ForwardOutcome) GetReason() string {
if x != nil {
- return x.Type
+ return x.Reason
}
return ""
}
-func (x *PluginInfoResponse) GetAccountTypes() []*AccountTypeProto {
+func (x *ForwardOutcome) GetUpdatedCredentials() map[string]string {
if x != nil {
- return x.AccountTypes
+ return x.UpdatedCredentials
}
return nil
}
-func (x *PluginInfoResponse) GetFrontendPages() []*FrontendPageProto {
+type ForwardChunk struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"`
+ Done bool `protobuf:"varint,2,opt,name=done,proto3" json:"done,omitempty"`
+ FinalOutcome *ForwardOutcome `protobuf:"bytes,3,opt,name=final_outcome,json=finalOutcome,proto3" json:"final_outcome,omitempty"`
+ StatusCode int32 `protobuf:"varint,4,opt,name=status_code,json=statusCode,proto3" json:"status_code,omitempty"`
+ Headers map[string]*HeaderValues `protobuf:"bytes,5,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *ForwardChunk) Reset() {
+ *x = ForwardChunk{}
+ mi := &file_plugin_proto_msgTypes[22]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *ForwardChunk) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ForwardChunk) ProtoMessage() {}
+
+func (x *ForwardChunk) ProtoReflect() protoreflect.Message {
+ mi := &file_plugin_proto_msgTypes[22]
if x != nil {
- return x.FrontendPages
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
}
- return nil
+ return mi.MessageOf(x)
}
-func (x *PluginInfoResponse) GetFrontendWidgets() []*FrontendWidgetProto {
+// Deprecated: Use ForwardChunk.ProtoReflect.Descriptor instead.
+func (*ForwardChunk) Descriptor() ([]byte, []int) {
+ return file_plugin_proto_rawDescGZIP(), []int{22}
+}
+
+func (x *ForwardChunk) GetData() []byte {
if x != nil {
- return x.FrontendWidgets
+ return x.Data
}
return nil
}
-func (x *PluginInfoResponse) GetSdkVersion() string {
+func (x *ForwardChunk) GetDone() bool {
if x != nil {
- return x.SdkVersion
+ return x.Done
}
- return ""
+ return false
}
-func (x *PluginInfoResponse) GetDependencies() []string {
+func (x *ForwardChunk) GetFinalOutcome() *ForwardOutcome {
if x != nil {
- return x.Dependencies
+ return x.FinalOutcome
}
return nil
}
-func (x *PluginInfoResponse) GetConfigSchema() []*ConfigFieldProto {
+func (x *ForwardChunk) GetStatusCode() int32 {
if x != nil {
- return x.ConfigSchema
+ return x.StatusCode
}
- return nil
+ return 0
}
-func (x *PluginInfoResponse) GetInstructionPresets() []string {
+func (x *ForwardChunk) GetHeaders() map[string]*HeaderValues {
if x != nil {
- return x.InstructionPresets
+ return x.Headers
}
return nil
}
-func (x *PluginInfoResponse) GetCapabilities() []string {
+type CredentialsRequest struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Credentials map[string]string `protobuf:"bytes,1,rep,name=credentials,proto3" json:"credentials,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *CredentialsRequest) Reset() {
+ *x = CredentialsRequest{}
+ mi := &file_plugin_proto_msgTypes[23]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *CredentialsRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*CredentialsRequest) ProtoMessage() {}
+
+func (x *CredentialsRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_plugin_proto_msgTypes[23]
if x != nil {
- return x.Capabilities
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
}
- return nil
+ return mi.MessageOf(x)
}
-func (x *PluginInfoResponse) GetPriority() int32 {
+// Deprecated: Use CredentialsRequest.ProtoReflect.Descriptor instead.
+func (*CredentialsRequest) Descriptor() ([]byte, []int) {
+ return file_plugin_proto_rawDescGZIP(), []int{23}
+}
+
+func (x *CredentialsRequest) GetCredentials() map[string]string {
if x != nil {
- return x.Priority
+ return x.Credentials
}
- return 0
+ return nil
}
-type ConfigFieldProto struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
- Label string `protobuf:"bytes,2,opt,name=label,proto3" json:"label,omitempty"`
- Type string `protobuf:"bytes,3,opt,name=type,proto3" json:"type,omitempty"`
- Required bool `protobuf:"varint,4,opt,name=required,proto3" json:"required,omitempty"`
- DefaultValue string `protobuf:"bytes,5,opt,name=default_value,json=defaultValue,proto3" json:"default_value,omitempty"`
- Description string `protobuf:"bytes,6,opt,name=description,proto3" json:"description,omitempty"`
- Placeholder string `protobuf:"bytes,7,opt,name=placeholder,proto3" json:"placeholder,omitempty"`
+type HttpRequest struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Method string `protobuf:"bytes,1,opt,name=method,proto3" json:"method,omitempty"`
+ Path string `protobuf:"bytes,2,opt,name=path,proto3" json:"path,omitempty"`
+ Query string `protobuf:"bytes,3,opt,name=query,proto3" json:"query,omitempty"`
+ Headers map[string]*HeaderValues `protobuf:"bytes,4,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
+ Body []byte `protobuf:"bytes,5,opt,name=body,proto3" json:"body,omitempty"`
+ RemoteAddr string `protobuf:"bytes,6,opt,name=remote_addr,json=remoteAddr,proto3" json:"remote_addr,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
-func (x *ConfigFieldProto) Reset() {
- *x = ConfigFieldProto{}
- mi := &file_plugin_proto_msgTypes[29]
+func (x *HttpRequest) Reset() {
+ *x = HttpRequest{}
+ mi := &file_plugin_proto_msgTypes[24]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
-func (x *ConfigFieldProto) String() string {
+func (x *HttpRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
-func (*ConfigFieldProto) ProtoMessage() {}
+func (*HttpRequest) ProtoMessage() {}
-func (x *ConfigFieldProto) ProtoReflect() protoreflect.Message {
- mi := &file_plugin_proto_msgTypes[29]
+func (x *HttpRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_plugin_proto_msgTypes[24]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2010,85 +2034,77 @@ func (x *ConfigFieldProto) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
-// Deprecated: Use ConfigFieldProto.ProtoReflect.Descriptor instead.
-func (*ConfigFieldProto) Descriptor() ([]byte, []int) {
- return file_plugin_proto_rawDescGZIP(), []int{29}
+// Deprecated: Use HttpRequest.ProtoReflect.Descriptor instead.
+func (*HttpRequest) Descriptor() ([]byte, []int) {
+ return file_plugin_proto_rawDescGZIP(), []int{24}
}
-func (x *ConfigFieldProto) GetKey() string {
+func (x *HttpRequest) GetMethod() string {
if x != nil {
- return x.Key
+ return x.Method
}
return ""
}
-func (x *ConfigFieldProto) GetLabel() string {
+func (x *HttpRequest) GetPath() string {
if x != nil {
- return x.Label
+ return x.Path
}
return ""
}
-func (x *ConfigFieldProto) GetType() string {
+func (x *HttpRequest) GetQuery() string {
if x != nil {
- return x.Type
+ return x.Query
}
return ""
}
-func (x *ConfigFieldProto) GetRequired() bool {
+func (x *HttpRequest) GetHeaders() map[string]*HeaderValues {
if x != nil {
- return x.Required
+ return x.Headers
}
- return false
-}
-
-func (x *ConfigFieldProto) GetDefaultValue() string {
- if x != nil {
- return x.DefaultValue
- }
- return ""
+ return nil
}
-func (x *ConfigFieldProto) GetDescription() string {
+func (x *HttpRequest) GetBody() []byte {
if x != nil {
- return x.Description
+ return x.Body
}
- return ""
+ return nil
}
-func (x *ConfigFieldProto) GetPlaceholder() string {
+func (x *HttpRequest) GetRemoteAddr() string {
if x != nil {
- return x.Placeholder
+ return x.RemoteAddr
}
return ""
}
-type AccountTypeProto struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
- Label string `protobuf:"bytes,2,opt,name=label,proto3" json:"label,omitempty"`
- Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"`
- Fields []*CredentialFieldProto `protobuf:"bytes,4,rep,name=fields,proto3" json:"fields,omitempty"`
+type HttpResponse struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ StatusCode int32 `protobuf:"varint,1,opt,name=status_code,json=statusCode,proto3" json:"status_code,omitempty"`
+ Headers map[string]*HeaderValues `protobuf:"bytes,2,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
+ Body []byte `protobuf:"bytes,3,opt,name=body,proto3" json:"body,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
-func (x *AccountTypeProto) Reset() {
- *x = AccountTypeProto{}
- mi := &file_plugin_proto_msgTypes[30]
+func (x *HttpResponse) Reset() {
+ *x = HttpResponse{}
+ mi := &file_plugin_proto_msgTypes[25]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
-func (x *AccountTypeProto) String() string {
+func (x *HttpResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
-func (*AccountTypeProto) ProtoMessage() {}
+func (*HttpResponse) ProtoMessage() {}
-func (x *AccountTypeProto) ProtoReflect() protoreflect.Message {
- mi := &file_plugin_proto_msgTypes[30]
+func (x *HttpResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_plugin_proto_msgTypes[25]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2099,66 +2115,57 @@ func (x *AccountTypeProto) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
-// Deprecated: Use AccountTypeProto.ProtoReflect.Descriptor instead.
-func (*AccountTypeProto) Descriptor() ([]byte, []int) {
- return file_plugin_proto_rawDescGZIP(), []int{30}
-}
-
-func (x *AccountTypeProto) GetKey() string {
- if x != nil {
- return x.Key
- }
- return ""
+// Deprecated: Use HttpResponse.ProtoReflect.Descriptor instead.
+func (*HttpResponse) Descriptor() ([]byte, []int) {
+ return file_plugin_proto_rawDescGZIP(), []int{25}
}
-func (x *AccountTypeProto) GetLabel() string {
+func (x *HttpResponse) GetStatusCode() int32 {
if x != nil {
- return x.Label
+ return x.StatusCode
}
- return ""
+ return 0
}
-func (x *AccountTypeProto) GetDescription() string {
+func (x *HttpResponse) GetHeaders() map[string]*HeaderValues {
if x != nil {
- return x.Description
+ return x.Headers
}
- return ""
+ return nil
}
-func (x *AccountTypeProto) GetFields() []*CredentialFieldProto {
+func (x *HttpResponse) GetBody() []byte {
if x != nil {
- return x.Fields
+ return x.Body
}
return nil
}
-type CredentialFieldProto struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
- Label string `protobuf:"bytes,2,opt,name=label,proto3" json:"label,omitempty"`
- Type string `protobuf:"bytes,3,opt,name=type,proto3" json:"type,omitempty"`
- Required bool `protobuf:"varint,4,opt,name=required,proto3" json:"required,omitempty"`
- Placeholder string `protobuf:"bytes,5,opt,name=placeholder,proto3" json:"placeholder,omitempty"`
- EditDisabled bool `protobuf:"varint,6,opt,name=edit_disabled,json=editDisabled,proto3" json:"edit_disabled,omitempty"` // 编辑模式下隐藏该字段
+type HttpResponseChunk struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"`
+ Done bool `protobuf:"varint,2,opt,name=done,proto3" json:"done,omitempty"`
+ StatusCode int32 `protobuf:"varint,3,opt,name=status_code,json=statusCode,proto3" json:"status_code,omitempty"`
+ Headers map[string]*HeaderValues `protobuf:"bytes,4,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
-func (x *CredentialFieldProto) Reset() {
- *x = CredentialFieldProto{}
- mi := &file_plugin_proto_msgTypes[31]
+func (x *HttpResponseChunk) Reset() {
+ *x = HttpResponseChunk{}
+ mi := &file_plugin_proto_msgTypes[26]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
-func (x *CredentialFieldProto) String() string {
+func (x *HttpResponseChunk) String() string {
return protoimpl.X.MessageStringOf(x)
}
-func (*CredentialFieldProto) ProtoMessage() {}
+func (*HttpResponseChunk) ProtoMessage() {}
-func (x *CredentialFieldProto) ProtoReflect() protoreflect.Message {
- mi := &file_plugin_proto_msgTypes[31]
+func (x *HttpResponseChunk) ProtoReflect() protoreflect.Message {
+ mi := &file_plugin_proto_msgTypes[26]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2169,79 +2176,62 @@ func (x *CredentialFieldProto) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
-// Deprecated: Use CredentialFieldProto.ProtoReflect.Descriptor instead.
-func (*CredentialFieldProto) Descriptor() ([]byte, []int) {
- return file_plugin_proto_rawDescGZIP(), []int{31}
-}
-
-func (x *CredentialFieldProto) GetKey() string {
- if x != nil {
- return x.Key
- }
- return ""
-}
-
-func (x *CredentialFieldProto) GetLabel() string {
- if x != nil {
- return x.Label
- }
- return ""
+// Deprecated: Use HttpResponseChunk.ProtoReflect.Descriptor instead.
+func (*HttpResponseChunk) Descriptor() ([]byte, []int) {
+ return file_plugin_proto_rawDescGZIP(), []int{26}
}
-func (x *CredentialFieldProto) GetType() string {
+func (x *HttpResponseChunk) GetData() []byte {
if x != nil {
- return x.Type
+ return x.Data
}
- return ""
+ return nil
}
-func (x *CredentialFieldProto) GetRequired() bool {
+func (x *HttpResponseChunk) GetDone() bool {
if x != nil {
- return x.Required
+ return x.Done
}
return false
}
-func (x *CredentialFieldProto) GetPlaceholder() string {
+func (x *HttpResponseChunk) GetStatusCode() int32 {
if x != nil {
- return x.Placeholder
+ return x.StatusCode
}
- return ""
+ return 0
}
-func (x *CredentialFieldProto) GetEditDisabled() bool {
+func (x *HttpResponseChunk) GetHeaders() map[string]*HeaderValues {
if x != nil {
- return x.EditDisabled
+ return x.Headers
}
- return false
+ return nil
}
-type FrontendPageProto struct {
+type BackgroundTaskProto struct {
state protoimpl.MessageState `protogen:"open.v1"`
- Path string `protobuf:"bytes,1,opt,name=path,proto3" json:"path,omitempty"`
- Title string `protobuf:"bytes,2,opt,name=title,proto3" json:"title,omitempty"`
- Icon string `protobuf:"bytes,3,opt,name=icon,proto3" json:"icon,omitempty"`
- Description string `protobuf:"bytes,4,opt,name=description,proto3" json:"description,omitempty"`
- Audience string `protobuf:"bytes,5,opt,name=audience,proto3" json:"audience,omitempty"` // "admin" | "user" | "all",空 = "admin"
+ Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
+ IntervalMs int64 `protobuf:"varint,2,opt,name=interval_ms,json=intervalMs,proto3" json:"interval_ms,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
-func (x *FrontendPageProto) Reset() {
- *x = FrontendPageProto{}
- mi := &file_plugin_proto_msgTypes[32]
+func (x *BackgroundTaskProto) Reset() {
+ *x = BackgroundTaskProto{}
+ mi := &file_plugin_proto_msgTypes[27]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
-func (x *FrontendPageProto) String() string {
+func (x *BackgroundTaskProto) String() string {
return protoimpl.X.MessageStringOf(x)
}
-func (*FrontendPageProto) ProtoMessage() {}
+func (*BackgroundTaskProto) ProtoMessage() {}
-func (x *FrontendPageProto) ProtoReflect() protoreflect.Message {
- mi := &file_plugin_proto_msgTypes[32]
+func (x *BackgroundTaskProto) ProtoReflect() protoreflect.Message {
+ mi := &file_plugin_proto_msgTypes[27]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2252,70 +2242,91 @@ func (x *FrontendPageProto) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
-// Deprecated: Use FrontendPageProto.ProtoReflect.Descriptor instead.
-func (*FrontendPageProto) Descriptor() ([]byte, []int) {
- return file_plugin_proto_rawDescGZIP(), []int{32}
+// Deprecated: Use BackgroundTaskProto.ProtoReflect.Descriptor instead.
+func (*BackgroundTaskProto) Descriptor() ([]byte, []int) {
+ return file_plugin_proto_rawDescGZIP(), []int{27}
}
-func (x *FrontendPageProto) GetPath() string {
+func (x *BackgroundTaskProto) GetName() string {
if x != nil {
- return x.Path
+ return x.Name
}
return ""
}
-func (x *FrontendPageProto) GetTitle() string {
+func (x *BackgroundTaskProto) GetIntervalMs() int64 {
if x != nil {
- return x.Title
+ return x.IntervalMs
}
- return ""
+ return 0
}
-func (x *FrontendPageProto) GetIcon() string {
- if x != nil {
- return x.Icon
- }
- return ""
+type BackgroundTasksResponse struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Tasks []*BackgroundTaskProto `protobuf:"bytes,1,rep,name=tasks,proto3" json:"tasks,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
}
-func (x *FrontendPageProto) GetDescription() string {
+func (x *BackgroundTasksResponse) Reset() {
+ *x = BackgroundTasksResponse{}
+ mi := &file_plugin_proto_msgTypes[28]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *BackgroundTasksResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*BackgroundTasksResponse) ProtoMessage() {}
+
+func (x *BackgroundTasksResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_plugin_proto_msgTypes[28]
if x != nil {
- return x.Description
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
}
- return ""
+ return mi.MessageOf(x)
}
-func (x *FrontendPageProto) GetAudience() string {
+// Deprecated: Use BackgroundTasksResponse.ProtoReflect.Descriptor instead.
+func (*BackgroundTasksResponse) Descriptor() ([]byte, []int) {
+ return file_plugin_proto_rawDescGZIP(), []int{28}
+}
+
+func (x *BackgroundTasksResponse) GetTasks() []*BackgroundTaskProto {
if x != nil {
- return x.Audience
+ return x.Tasks
}
- return ""
+ return nil
}
-type FrontendWidgetProto struct {
+type RunBackgroundTaskRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
- Slot string `protobuf:"bytes,1,opt,name=slot,proto3" json:"slot,omitempty"`
- EntryFile string `protobuf:"bytes,2,opt,name=entry_file,json=entryFile,proto3" json:"entry_file,omitempty"`
- Title string `protobuf:"bytes,3,opt,name=title,proto3" json:"title,omitempty"`
+ Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
-func (x *FrontendWidgetProto) Reset() {
- *x = FrontendWidgetProto{}
- mi := &file_plugin_proto_msgTypes[33]
+func (x *RunBackgroundTaskRequest) Reset() {
+ *x = RunBackgroundTaskRequest{}
+ mi := &file_plugin_proto_msgTypes[29]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
-func (x *FrontendWidgetProto) String() string {
+func (x *RunBackgroundTaskRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
-func (*FrontendWidgetProto) ProtoMessage() {}
+func (*RunBackgroundTaskRequest) ProtoMessage() {}
-func (x *FrontendWidgetProto) ProtoReflect() protoreflect.Message {
- mi := &file_plugin_proto_msgTypes[33]
+func (x *RunBackgroundTaskRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_plugin_proto_msgTypes[29]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2326,60 +2337,48 @@ func (x *FrontendWidgetProto) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
-// Deprecated: Use FrontendWidgetProto.ProtoReflect.Descriptor instead.
-func (*FrontendWidgetProto) Descriptor() ([]byte, []int) {
- return file_plugin_proto_rawDescGZIP(), []int{33}
+// Deprecated: Use RunBackgroundTaskRequest.ProtoReflect.Descriptor instead.
+func (*RunBackgroundTaskRequest) Descriptor() ([]byte, []int) {
+ return file_plugin_proto_rawDescGZIP(), []int{29}
}
-func (x *FrontendWidgetProto) GetSlot() string {
+func (x *RunBackgroundTaskRequest) GetName() string {
if x != nil {
- return x.Slot
- }
- return ""
-}
-
-func (x *FrontendWidgetProto) GetEntryFile() string {
- if x != nil {
- return x.EntryFile
- }
- return ""
-}
-
-func (x *FrontendWidgetProto) GetTitle() string {
- if x != nil {
- return x.Title
+ return x.Name
}
return ""
}
-type InitRequest struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Config map[string]string `protobuf:"bytes,1,rep,name=config,proto3" json:"config,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
- LogLevel string `protobuf:"bytes,2,opt,name=log_level,json=logLevel,proto3" json:"log_level,omitempty"`
- // host_broker_id 是 Core 通过 hashicorp/go-plugin GRPCBroker 启动的
- // HostService stream 的 ID。插件 Init 时拿到 ID 后,可以通过 broker.Dial(id)
- // 拿到 HostService 的 grpc client,回调 Core 提供的能力(SelectAccount /
- // ProbeForward / ListGroups 等)。0 表示 Core 没启用 HostService。
- HostBrokerId uint32 `protobuf:"varint,3,opt,name=host_broker_id,json=hostBrokerId,proto3" json:"host_broker_id,omitempty"`
+type WebSocketFrame struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Type WebSocketFrame_FrameType `protobuf:"varint,1,opt,name=type,proto3,enum=airgate.plugin.v2.WebSocketFrame_FrameType" json:"type,omitempty"`
+ Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"`
+ // 仅 CONNECT 帧使用
+ ConnectInfo *WebSocketConnectInfo `protobuf:"bytes,3,opt,name=connect_info,json=connectInfo,proto3" json:"connect_info,omitempty"`
+ // 仅 CLOSE 帧使用
+ CloseCode int32 `protobuf:"varint,4,opt,name=close_code,json=closeCode,proto3" json:"close_code,omitempty"`
+ CloseReason string `protobuf:"bytes,5,opt,name=close_reason,json=closeReason,proto3" json:"close_reason,omitempty"`
+ // 仅 RESULT 帧使用
+ Outcome *ForwardOutcome `protobuf:"bytes,6,opt,name=outcome,proto3" json:"outcome,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
-func (x *InitRequest) Reset() {
- *x = InitRequest{}
- mi := &file_plugin_proto_msgTypes[34]
+func (x *WebSocketFrame) Reset() {
+ *x = WebSocketFrame{}
+ mi := &file_plugin_proto_msgTypes[30]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
-func (x *InitRequest) String() string {
+func (x *WebSocketFrame) String() string {
return protoimpl.X.MessageStringOf(x)
}
-func (*InitRequest) ProtoMessage() {}
+func (*WebSocketFrame) ProtoMessage() {}
-func (x *InitRequest) ProtoReflect() protoreflect.Message {
- mi := &file_plugin_proto_msgTypes[34]
+func (x *WebSocketFrame) ProtoReflect() protoreflect.Message {
+ mi := &file_plugin_proto_msgTypes[30]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2390,65 +2389,80 @@ func (x *InitRequest) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
-// Deprecated: Use InitRequest.ProtoReflect.Descriptor instead.
-func (*InitRequest) Descriptor() ([]byte, []int) {
- return file_plugin_proto_rawDescGZIP(), []int{34}
+// Deprecated: Use WebSocketFrame.ProtoReflect.Descriptor instead.
+func (*WebSocketFrame) Descriptor() ([]byte, []int) {
+ return file_plugin_proto_rawDescGZIP(), []int{30}
}
-func (x *InitRequest) GetConfig() map[string]string {
+func (x *WebSocketFrame) GetType() WebSocketFrame_FrameType {
if x != nil {
- return x.Config
+ return x.Type
+ }
+ return WebSocketFrame_CONNECT
+}
+
+func (x *WebSocketFrame) GetData() []byte {
+ if x != nil {
+ return x.Data
}
return nil
}
-func (x *InitRequest) GetLogLevel() string {
+func (x *WebSocketFrame) GetConnectInfo() *WebSocketConnectInfo {
if x != nil {
- return x.LogLevel
+ return x.ConnectInfo
}
- return ""
+ return nil
}
-func (x *InitRequest) GetHostBrokerId() uint32 {
+func (x *WebSocketFrame) GetCloseCode() int32 {
if x != nil {
- return x.HostBrokerId
+ return x.CloseCode
}
return 0
}
-type ModelInfoProto struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
- Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
- InputPrice float64 `protobuf:"fixed64,4,opt,name=input_price,json=inputPrice,proto3" json:"input_price,omitempty"`
- OutputPrice float64 `protobuf:"fixed64,5,opt,name=output_price,json=outputPrice,proto3" json:"output_price,omitempty"`
- CachedInputPrice float64 `protobuf:"fixed64,6,opt,name=cached_input_price,json=cachedInputPrice,proto3" json:"cached_input_price,omitempty"`
- InputPricePriority float64 `protobuf:"fixed64,7,opt,name=input_price_priority,json=inputPricePriority,proto3" json:"input_price_priority,omitempty"`
- OutputPricePriority float64 `protobuf:"fixed64,8,opt,name=output_price_priority,json=outputPricePriority,proto3" json:"output_price_priority,omitempty"`
- CachedInputPricePriority float64 `protobuf:"fixed64,9,opt,name=cached_input_price_priority,json=cachedInputPricePriority,proto3" json:"cached_input_price_priority,omitempty"`
- ContextWindow int64 `protobuf:"varint,10,opt,name=context_window,json=contextWindow,proto3" json:"context_window,omitempty"`
- MaxOutputTokens int64 `protobuf:"varint,11,opt,name=max_output_tokens,json=maxOutputTokens,proto3" json:"max_output_tokens,omitempty"`
- CacheCreationPrice float64 `protobuf:"fixed64,12,opt,name=cache_creation_price,json=cacheCreationPrice,proto3" json:"cache_creation_price,omitempty"` // 缓存写入 5m TTL 单价($/1M token,1.25x input)
- CacheCreation_1HPrice float64 `protobuf:"fixed64,13,opt,name=cache_creation_1h_price,json=cacheCreation1hPrice,proto3" json:"cache_creation_1h_price,omitempty"` // 缓存写入 1h TTL 单价($/1M token,2.00x input)
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
+func (x *WebSocketFrame) GetCloseReason() string {
+ if x != nil {
+ return x.CloseReason
+ }
+ return ""
}
-func (x *ModelInfoProto) Reset() {
- *x = ModelInfoProto{}
- mi := &file_plugin_proto_msgTypes[35]
+func (x *WebSocketFrame) GetOutcome() *ForwardOutcome {
+ if x != nil {
+ return x.Outcome
+ }
+ return nil
+}
+
+type WebSocketConnectInfo struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Path string `protobuf:"bytes,1,opt,name=path,proto3" json:"path,omitempty"`
+ Query string `protobuf:"bytes,2,opt,name=query,proto3" json:"query,omitempty"`
+ Headers map[string]*HeaderValues `protobuf:"bytes,3,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
+ RemoteAddr string `protobuf:"bytes,4,opt,name=remote_addr,json=remoteAddr,proto3" json:"remote_addr,omitempty"`
+ ConnectionId string `protobuf:"bytes,5,opt,name=connection_id,json=connectionId,proto3" json:"connection_id,omitempty"`
+ Account *AccountProto `protobuf:"bytes,12,opt,name=account,proto3" json:"account,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *WebSocketConnectInfo) Reset() {
+ *x = WebSocketConnectInfo{}
+ mi := &file_plugin_proto_msgTypes[31]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
-func (x *ModelInfoProto) String() string {
+func (x *WebSocketConnectInfo) String() string {
return protoimpl.X.MessageStringOf(x)
}
-func (*ModelInfoProto) ProtoMessage() {}
+func (*WebSocketConnectInfo) ProtoMessage() {}
-func (x *ModelInfoProto) ProtoReflect() protoreflect.Message {
- mi := &file_plugin_proto_msgTypes[35]
+func (x *WebSocketConnectInfo) ProtoReflect() protoreflect.Message {
+ mi := &file_plugin_proto_msgTypes[31]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2459,117 +2473,128 @@ func (x *ModelInfoProto) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
-// Deprecated: Use ModelInfoProto.ProtoReflect.Descriptor instead.
-func (*ModelInfoProto) Descriptor() ([]byte, []int) {
- return file_plugin_proto_rawDescGZIP(), []int{35}
+// Deprecated: Use WebSocketConnectInfo.ProtoReflect.Descriptor instead.
+func (*WebSocketConnectInfo) Descriptor() ([]byte, []int) {
+ return file_plugin_proto_rawDescGZIP(), []int{31}
}
-func (x *ModelInfoProto) GetId() string {
+func (x *WebSocketConnectInfo) GetPath() string {
if x != nil {
- return x.Id
+ return x.Path
}
return ""
}
-func (x *ModelInfoProto) GetName() string {
+func (x *WebSocketConnectInfo) GetQuery() string {
if x != nil {
- return x.Name
+ return x.Query
}
return ""
}
-func (x *ModelInfoProto) GetInputPrice() float64 {
+func (x *WebSocketConnectInfo) GetHeaders() map[string]*HeaderValues {
if x != nil {
- return x.InputPrice
+ return x.Headers
}
- return 0
+ return nil
}
-func (x *ModelInfoProto) GetOutputPrice() float64 {
+func (x *WebSocketConnectInfo) GetRemoteAddr() string {
if x != nil {
- return x.OutputPrice
+ return x.RemoteAddr
}
- return 0
+ return ""
}
-func (x *ModelInfoProto) GetCachedInputPrice() float64 {
+func (x *WebSocketConnectInfo) GetConnectionId() string {
if x != nil {
- return x.CachedInputPrice
+ return x.ConnectionId
}
- return 0
+ return ""
}
-func (x *ModelInfoProto) GetInputPricePriority() float64 {
+func (x *WebSocketConnectInfo) GetAccount() *AccountProto {
if x != nil {
- return x.InputPricePriority
+ return x.Account
}
- return 0
+ return nil
}
-func (x *ModelInfoProto) GetOutputPricePriority() float64 {
- if x != nil {
- return x.OutputPricePriority
- }
- return 0
+type WebAssetFile struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Path string `protobuf:"bytes,1,opt,name=path,proto3" json:"path,omitempty"`
+ Content []byte `protobuf:"bytes,2,opt,name=content,proto3" json:"content,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
}
-func (x *ModelInfoProto) GetCachedInputPricePriority() float64 {
- if x != nil {
- return x.CachedInputPricePriority
- }
- return 0
+func (x *WebAssetFile) Reset() {
+ *x = WebAssetFile{}
+ mi := &file_plugin_proto_msgTypes[32]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
}
-func (x *ModelInfoProto) GetContextWindow() int64 {
- if x != nil {
- return x.ContextWindow
- }
- return 0
+func (x *WebAssetFile) String() string {
+ return protoimpl.X.MessageStringOf(x)
}
-func (x *ModelInfoProto) GetMaxOutputTokens() int64 {
+func (*WebAssetFile) ProtoMessage() {}
+
+func (x *WebAssetFile) ProtoReflect() protoreflect.Message {
+ mi := &file_plugin_proto_msgTypes[32]
if x != nil {
- return x.MaxOutputTokens
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
}
- return 0
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use WebAssetFile.ProtoReflect.Descriptor instead.
+func (*WebAssetFile) Descriptor() ([]byte, []int) {
+ return file_plugin_proto_rawDescGZIP(), []int{32}
}
-func (x *ModelInfoProto) GetCacheCreationPrice() float64 {
+func (x *WebAssetFile) GetPath() string {
if x != nil {
- return x.CacheCreationPrice
+ return x.Path
}
- return 0
+ return ""
}
-func (x *ModelInfoProto) GetCacheCreation_1HPrice() float64 {
+func (x *WebAssetFile) GetContent() []byte {
if x != nil {
- return x.CacheCreation_1HPrice
+ return x.Content
}
- return 0
+ return nil
}
-type ModelsResponse struct {
+type WebAssetsResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
- Models []*ModelInfoProto `protobuf:"bytes,1,rep,name=models,proto3" json:"models,omitempty"`
+ Files []*WebAssetFile `protobuf:"bytes,1,rep,name=files,proto3" json:"files,omitempty"`
+ HasAssets bool `protobuf:"varint,2,opt,name=has_assets,json=hasAssets,proto3" json:"has_assets,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
-func (x *ModelsResponse) Reset() {
- *x = ModelsResponse{}
- mi := &file_plugin_proto_msgTypes[36]
+func (x *WebAssetsResponse) Reset() {
+ *x = WebAssetsResponse{}
+ mi := &file_plugin_proto_msgTypes[33]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
-func (x *ModelsResponse) String() string {
+func (x *WebAssetsResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
-func (*ModelsResponse) ProtoMessage() {}
+func (*WebAssetsResponse) ProtoMessage() {}
-func (x *ModelsResponse) ProtoReflect() protoreflect.Message {
- mi := &file_plugin_proto_msgTypes[36]
+func (x *WebAssetsResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_plugin_proto_msgTypes[33]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2580,42 +2605,50 @@ func (x *ModelsResponse) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
-// Deprecated: Use ModelsResponse.ProtoReflect.Descriptor instead.
-func (*ModelsResponse) Descriptor() ([]byte, []int) {
- return file_plugin_proto_rawDescGZIP(), []int{36}
+// Deprecated: Use WebAssetsResponse.ProtoReflect.Descriptor instead.
+func (*WebAssetsResponse) Descriptor() ([]byte, []int) {
+ return file_plugin_proto_rawDescGZIP(), []int{33}
}
-func (x *ModelsResponse) GetModels() []*ModelInfoProto {
+func (x *WebAssetsResponse) GetFiles() []*WebAssetFile {
if x != nil {
- return x.Models
+ return x.Files
}
return nil
}
-type RouteDefinitionProto struct {
+func (x *WebAssetsResponse) GetHasAssets() bool {
+ if x != nil {
+ return x.HasAssets
+ }
+ return false
+}
+
+type PayloadSchemaProto struct {
state protoimpl.MessageState `protogen:"open.v1"`
- Method string `protobuf:"bytes,1,opt,name=method,proto3" json:"method,omitempty"`
- Path string `protobuf:"bytes,2,opt,name=path,proto3" json:"path,omitempty"`
- Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"`
+ ContentType string `protobuf:"bytes,1,opt,name=content_type,json=contentType,proto3" json:"content_type,omitempty"`
+ Schema string `protobuf:"bytes,2,opt,name=schema,proto3" json:"schema,omitempty"` // JSON Schema 字符串
+ Example string `protobuf:"bytes,3,opt,name=example,proto3" json:"example,omitempty"` // JSON 示例字符串
+ Metadata map[string]string `protobuf:"bytes,4,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
-func (x *RouteDefinitionProto) Reset() {
- *x = RouteDefinitionProto{}
- mi := &file_plugin_proto_msgTypes[37]
+func (x *PayloadSchemaProto) Reset() {
+ *x = PayloadSchemaProto{}
+ mi := &file_plugin_proto_msgTypes[34]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
-func (x *RouteDefinitionProto) String() string {
+func (x *PayloadSchemaProto) String() string {
return protoimpl.X.MessageStringOf(x)
}
-func (*RouteDefinitionProto) ProtoMessage() {}
+func (*PayloadSchemaProto) ProtoMessage() {}
-func (x *RouteDefinitionProto) ProtoReflect() protoreflect.Message {
- mi := &file_plugin_proto_msgTypes[37]
+func (x *PayloadSchemaProto) ProtoReflect() protoreflect.Message {
+ mi := &file_plugin_proto_msgTypes[34]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2626,54 +2659,66 @@ func (x *RouteDefinitionProto) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
-// Deprecated: Use RouteDefinitionProto.ProtoReflect.Descriptor instead.
-func (*RouteDefinitionProto) Descriptor() ([]byte, []int) {
- return file_plugin_proto_rawDescGZIP(), []int{37}
+// Deprecated: Use PayloadSchemaProto.ProtoReflect.Descriptor instead.
+func (*PayloadSchemaProto) Descriptor() ([]byte, []int) {
+ return file_plugin_proto_rawDescGZIP(), []int{34}
}
-func (x *RouteDefinitionProto) GetMethod() string {
+func (x *PayloadSchemaProto) GetContentType() string {
if x != nil {
- return x.Method
+ return x.ContentType
}
return ""
}
-func (x *RouteDefinitionProto) GetPath() string {
+func (x *PayloadSchemaProto) GetSchema() string {
if x != nil {
- return x.Path
+ return x.Schema
}
return ""
}
-func (x *RouteDefinitionProto) GetDescription() string {
+func (x *PayloadSchemaProto) GetExample() string {
if x != nil {
- return x.Description
+ return x.Example
}
return ""
}
-type RoutesResponse struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Routes []*RouteDefinitionProto `protobuf:"bytes,1,rep,name=routes,proto3" json:"routes,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
+func (x *PayloadSchemaProto) GetMetadata() map[string]string {
+ if x != nil {
+ return x.Metadata
+ }
+ return nil
}
-func (x *RoutesResponse) Reset() {
- *x = RoutesResponse{}
- mi := &file_plugin_proto_msgTypes[38]
+type RouteSchemaProto struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Method string `protobuf:"bytes,1,opt,name=method,proto3" json:"method,omitempty"`
+ Path string `protobuf:"bytes,2,opt,name=path,proto3" json:"path,omitempty"`
+ Summary string `protobuf:"bytes,3,opt,name=summary,proto3" json:"summary,omitempty"`
+ Request *PayloadSchemaProto `protobuf:"bytes,4,opt,name=request,proto3" json:"request,omitempty"`
+ Response *PayloadSchemaProto `protobuf:"bytes,5,opt,name=response,proto3" json:"response,omitempty"`
+ Metadata map[string]string `protobuf:"bytes,6,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *RouteSchemaProto) Reset() {
+ *x = RouteSchemaProto{}
+ mi := &file_plugin_proto_msgTypes[35]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
-func (x *RoutesResponse) String() string {
+func (x *RouteSchemaProto) String() string {
return protoimpl.X.MessageStringOf(x)
}
-func (*RoutesResponse) ProtoMessage() {}
+func (*RouteSchemaProto) ProtoMessage() {}
-func (x *RoutesResponse) ProtoReflect() protoreflect.Message {
- mi := &file_plugin_proto_msgTypes[38]
+func (x *RouteSchemaProto) ProtoReflect() protoreflect.Message {
+ mi := &file_plugin_proto_msgTypes[35]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2684,45 +2729,79 @@ func (x *RoutesResponse) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
-// Deprecated: Use RoutesResponse.ProtoReflect.Descriptor instead.
-func (*RoutesResponse) Descriptor() ([]byte, []int) {
- return file_plugin_proto_rawDescGZIP(), []int{38}
+// Deprecated: Use RouteSchemaProto.ProtoReflect.Descriptor instead.
+func (*RouteSchemaProto) Descriptor() ([]byte, []int) {
+ return file_plugin_proto_rawDescGZIP(), []int{35}
}
-func (x *RoutesResponse) GetRoutes() []*RouteDefinitionProto {
+func (x *RouteSchemaProto) GetMethod() string {
if x != nil {
- return x.Routes
+ return x.Method
+ }
+ return ""
+}
+
+func (x *RouteSchemaProto) GetPath() string {
+ if x != nil {
+ return x.Path
+ }
+ return ""
+}
+
+func (x *RouteSchemaProto) GetSummary() string {
+ if x != nil {
+ return x.Summary
+ }
+ return ""
+}
+
+func (x *RouteSchemaProto) GetRequest() *PayloadSchemaProto {
+ if x != nil {
+ return x.Request
}
return nil
}
-type AccountProto struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
- Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
- Platform string `protobuf:"bytes,3,opt,name=platform,proto3" json:"platform,omitempty"`
- Type string `protobuf:"bytes,4,opt,name=type,proto3" json:"type,omitempty"`
- CredentialsJson []byte `protobuf:"bytes,5,opt,name=credentials_json,json=credentialsJson,proto3" json:"credentials_json,omitempty"`
- ProxyUrl string `protobuf:"bytes,6,opt,name=proxy_url,json=proxyUrl,proto3" json:"proxy_url,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
+func (x *RouteSchemaProto) GetResponse() *PayloadSchemaProto {
+ if x != nil {
+ return x.Response
+ }
+ return nil
}
-func (x *AccountProto) Reset() {
- *x = AccountProto{}
- mi := &file_plugin_proto_msgTypes[39]
+func (x *RouteSchemaProto) GetMetadata() map[string]string {
+ if x != nil {
+ return x.Metadata
+ }
+ return nil
+}
+
+type TaskSchemaProto struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"`
+ Summary string `protobuf:"bytes,2,opt,name=summary,proto3" json:"summary,omitempty"`
+ Input *PayloadSchemaProto `protobuf:"bytes,3,opt,name=input,proto3" json:"input,omitempty"`
+ Output *PayloadSchemaProto `protobuf:"bytes,4,opt,name=output,proto3" json:"output,omitempty"`
+ Metadata map[string]string `protobuf:"bytes,5,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *TaskSchemaProto) Reset() {
+ *x = TaskSchemaProto{}
+ mi := &file_plugin_proto_msgTypes[36]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
-func (x *AccountProto) String() string {
+func (x *TaskSchemaProto) String() string {
return protoimpl.X.MessageStringOf(x)
}
-func (*AccountProto) ProtoMessage() {}
+func (*TaskSchemaProto) ProtoMessage() {}
-func (x *AccountProto) ProtoReflect() protoreflect.Message {
- mi := &file_plugin_proto_msgTypes[39]
+func (x *TaskSchemaProto) ProtoReflect() protoreflect.Message {
+ mi := &file_plugin_proto_msgTypes[36]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2733,79 +2812,72 @@ func (x *AccountProto) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
-// Deprecated: Use AccountProto.ProtoReflect.Descriptor instead.
-func (*AccountProto) Descriptor() ([]byte, []int) {
- return file_plugin_proto_rawDescGZIP(), []int{39}
-}
-
-func (x *AccountProto) GetId() int64 {
- if x != nil {
- return x.Id
- }
- return 0
+// Deprecated: Use TaskSchemaProto.ProtoReflect.Descriptor instead.
+func (*TaskSchemaProto) Descriptor() ([]byte, []int) {
+ return file_plugin_proto_rawDescGZIP(), []int{36}
}
-func (x *AccountProto) GetName() string {
+func (x *TaskSchemaProto) GetType() string {
if x != nil {
- return x.Name
+ return x.Type
}
return ""
}
-func (x *AccountProto) GetPlatform() string {
+func (x *TaskSchemaProto) GetSummary() string {
if x != nil {
- return x.Platform
+ return x.Summary
}
return ""
}
-func (x *AccountProto) GetType() string {
+func (x *TaskSchemaProto) GetInput() *PayloadSchemaProto {
if x != nil {
- return x.Type
+ return x.Input
}
- return ""
+ return nil
}
-func (x *AccountProto) GetCredentialsJson() []byte {
+func (x *TaskSchemaProto) GetOutput() *PayloadSchemaProto {
if x != nil {
- return x.CredentialsJson
+ return x.Output
}
return nil
}
-func (x *AccountProto) GetProxyUrl() string {
+func (x *TaskSchemaProto) GetMetadata() map[string]string {
if x != nil {
- return x.ProxyUrl
+ return x.Metadata
}
- return ""
+ return nil
}
-type ForwardRequest struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Body []byte `protobuf:"bytes,7,opt,name=body,proto3" json:"body,omitempty"`
- Headers map[string]*HeaderValues `protobuf:"bytes,8,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
- Model string `protobuf:"bytes,9,opt,name=model,proto3" json:"model,omitempty"`
- Stream bool `protobuf:"varint,10,opt,name=stream,proto3" json:"stream,omitempty"`
- Account *AccountProto `protobuf:"bytes,11,opt,name=account,proto3" json:"account,omitempty"`
+type EventSchemaProto struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"`
+ Source string `protobuf:"bytes,2,opt,name=source,proto3" json:"source,omitempty"`
+ Summary string `protobuf:"bytes,3,opt,name=summary,proto3" json:"summary,omitempty"`
+ Payload *PayloadSchemaProto `protobuf:"bytes,4,opt,name=payload,proto3" json:"payload,omitempty"`
+ Metadata map[string]string `protobuf:"bytes,5,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
-func (x *ForwardRequest) Reset() {
- *x = ForwardRequest{}
- mi := &file_plugin_proto_msgTypes[40]
+func (x *EventSchemaProto) Reset() {
+ *x = EventSchemaProto{}
+ mi := &file_plugin_proto_msgTypes[37]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
-func (x *ForwardRequest) String() string {
+func (x *EventSchemaProto) String() string {
return protoimpl.X.MessageStringOf(x)
}
-func (*ForwardRequest) ProtoMessage() {}
+func (*EventSchemaProto) ProtoMessage() {}
-func (x *ForwardRequest) ProtoReflect() protoreflect.Message {
- mi := &file_plugin_proto_msgTypes[40]
+func (x *EventSchemaProto) ProtoReflect() protoreflect.Message {
+ mi := &file_plugin_proto_msgTypes[37]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2816,71 +2888,72 @@ func (x *ForwardRequest) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
-// Deprecated: Use ForwardRequest.ProtoReflect.Descriptor instead.
-func (*ForwardRequest) Descriptor() ([]byte, []int) {
- return file_plugin_proto_rawDescGZIP(), []int{40}
+// Deprecated: Use EventSchemaProto.ProtoReflect.Descriptor instead.
+func (*EventSchemaProto) Descriptor() ([]byte, []int) {
+ return file_plugin_proto_rawDescGZIP(), []int{37}
}
-func (x *ForwardRequest) GetBody() []byte {
+func (x *EventSchemaProto) GetType() string {
if x != nil {
- return x.Body
+ return x.Type
}
- return nil
+ return ""
}
-func (x *ForwardRequest) GetHeaders() map[string]*HeaderValues {
+func (x *EventSchemaProto) GetSource() string {
if x != nil {
- return x.Headers
+ return x.Source
}
- return nil
+ return ""
}
-func (x *ForwardRequest) GetModel() string {
+func (x *EventSchemaProto) GetSummary() string {
if x != nil {
- return x.Model
+ return x.Summary
}
return ""
}
-func (x *ForwardRequest) GetStream() bool {
+func (x *EventSchemaProto) GetPayload() *PayloadSchemaProto {
if x != nil {
- return x.Stream
+ return x.Payload
}
- return false
+ return nil
}
-func (x *ForwardRequest) GetAccount() *AccountProto {
+func (x *EventSchemaProto) GetMetadata() map[string]string {
if x != nil {
- return x.Account
+ return x.Metadata
}
return nil
}
-// UpstreamResponse 上游返回的原始 HTTP 快照。
-type UpstreamResponse struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- StatusCode int32 `protobuf:"varint,1,opt,name=status_code,json=statusCode,proto3" json:"status_code,omitempty"`
- Headers map[string]*HeaderValues `protobuf:"bytes,2,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
- Body []byte `protobuf:"bytes,3,opt,name=body,proto3" json:"body,omitempty"`
+type InvokeSchemaProto struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Method string `protobuf:"bytes,1,opt,name=method,proto3" json:"method,omitempty"`
+ Summary string `protobuf:"bytes,2,opt,name=summary,proto3" json:"summary,omitempty"`
+ Request *PayloadSchemaProto `protobuf:"bytes,3,opt,name=request,proto3" json:"request,omitempty"`
+ Response *PayloadSchemaProto `protobuf:"bytes,4,opt,name=response,proto3" json:"response,omitempty"`
+ Metadata map[string]string `protobuf:"bytes,5,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
-func (x *UpstreamResponse) Reset() {
- *x = UpstreamResponse{}
- mi := &file_plugin_proto_msgTypes[41]
+func (x *InvokeSchemaProto) Reset() {
+ *x = InvokeSchemaProto{}
+ mi := &file_plugin_proto_msgTypes[38]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
-func (x *UpstreamResponse) String() string {
+func (x *InvokeSchemaProto) String() string {
return protoimpl.X.MessageStringOf(x)
}
-func (*UpstreamResponse) ProtoMessage() {}
+func (*InvokeSchemaProto) ProtoMessage() {}
-func (x *UpstreamResponse) ProtoReflect() protoreflect.Message {
- mi := &file_plugin_proto_msgTypes[41]
+func (x *InvokeSchemaProto) ProtoReflect() protoreflect.Message {
+ mi := &file_plugin_proto_msgTypes[38]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2891,78 +2964,72 @@ func (x *UpstreamResponse) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
-// Deprecated: Use UpstreamResponse.ProtoReflect.Descriptor instead.
-func (*UpstreamResponse) Descriptor() ([]byte, []int) {
- return file_plugin_proto_rawDescGZIP(), []int{41}
+// Deprecated: Use InvokeSchemaProto.ProtoReflect.Descriptor instead.
+func (*InvokeSchemaProto) Descriptor() ([]byte, []int) {
+ return file_plugin_proto_rawDescGZIP(), []int{38}
}
-func (x *UpstreamResponse) GetStatusCode() int32 {
+func (x *InvokeSchemaProto) GetMethod() string {
if x != nil {
- return x.StatusCode
+ return x.Method
}
- return 0
+ return ""
}
-func (x *UpstreamResponse) GetHeaders() map[string]*HeaderValues {
+func (x *InvokeSchemaProto) GetSummary() string {
if x != nil {
- return x.Headers
+ return x.Summary
+ }
+ return ""
+}
+
+func (x *InvokeSchemaProto) GetRequest() *PayloadSchemaProto {
+ if x != nil {
+ return x.Request
}
return nil
}
-func (x *UpstreamResponse) GetBody() []byte {
+func (x *InvokeSchemaProto) GetResponse() *PayloadSchemaProto {
if x != nil {
- return x.Body
+ return x.Response
}
return nil
}
-// Usage 单次调用的 token / 费用统计。非 Success 判决下应为空。
-type Usage struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- InputTokens int64 `protobuf:"varint,1,opt,name=input_tokens,json=inputTokens,proto3" json:"input_tokens,omitempty"`
- OutputTokens int64 `protobuf:"varint,2,opt,name=output_tokens,json=outputTokens,proto3" json:"output_tokens,omitempty"`
- CachedInputTokens int64 `protobuf:"varint,3,opt,name=cached_input_tokens,json=cachedInputTokens,proto3" json:"cached_input_tokens,omitempty"`
- CacheCreationTokens int64 `protobuf:"varint,4,opt,name=cache_creation_tokens,json=cacheCreationTokens,proto3" json:"cache_creation_tokens,omitempty"`
- CacheCreation_5MTokens int64 `protobuf:"varint,5,opt,name=cache_creation_5m_tokens,json=cacheCreation5mTokens,proto3" json:"cache_creation_5m_tokens,omitempty"`
- CacheCreation_1HTokens int64 `protobuf:"varint,6,opt,name=cache_creation_1h_tokens,json=cacheCreation1hTokens,proto3" json:"cache_creation_1h_tokens,omitempty"`
- ReasoningOutputTokens int64 `protobuf:"varint,7,opt,name=reasoning_output_tokens,json=reasoningOutputTokens,proto3" json:"reasoning_output_tokens,omitempty"`
- InputCost float64 `protobuf:"fixed64,10,opt,name=input_cost,json=inputCost,proto3" json:"input_cost,omitempty"`
- OutputCost float64 `protobuf:"fixed64,11,opt,name=output_cost,json=outputCost,proto3" json:"output_cost,omitempty"`
- CachedInputCost float64 `protobuf:"fixed64,12,opt,name=cached_input_cost,json=cachedInputCost,proto3" json:"cached_input_cost,omitempty"`
- CacheCreationCost float64 `protobuf:"fixed64,13,opt,name=cache_creation_cost,json=cacheCreationCost,proto3" json:"cache_creation_cost,omitempty"`
- InputPrice float64 `protobuf:"fixed64,20,opt,name=input_price,json=inputPrice,proto3" json:"input_price,omitempty"`
- OutputPrice float64 `protobuf:"fixed64,21,opt,name=output_price,json=outputPrice,proto3" json:"output_price,omitempty"`
- CachedInputPrice float64 `protobuf:"fixed64,22,opt,name=cached_input_price,json=cachedInputPrice,proto3" json:"cached_input_price,omitempty"`
- CacheCreationPrice float64 `protobuf:"fixed64,23,opt,name=cache_creation_price,json=cacheCreationPrice,proto3" json:"cache_creation_price,omitempty"`
- CacheCreation_1HPrice float64 `protobuf:"fixed64,24,opt,name=cache_creation_1h_price,json=cacheCreation1hPrice,proto3" json:"cache_creation_1h_price,omitempty"`
- Model string `protobuf:"bytes,30,opt,name=model,proto3" json:"model,omitempty"`
- ServiceTier string `protobuf:"bytes,31,opt,name=service_tier,json=serviceTier,proto3" json:"service_tier,omitempty"`
- FirstTokenMs int64 `protobuf:"varint,32,opt,name=first_token_ms,json=firstTokenMs,proto3" json:"first_token_ms,omitempty"`
- // image_size 是图像生成请求实际出图的尺寸("WxH",例如 "1024x1024"、"3840x2160")。
- // 网关侧按 1K/2K/4K 三档计费,把分档来源(实际尺寸)记下来,admin 后台 usage_log
- // 显示费用时旁边带上 size,用户能直观看出"为什么这次扣了 0.40"。
- // 非图像请求留空。
- ImageSize string `protobuf:"bytes,33,opt,name=image_size,json=imageSize,proto3" json:"image_size,omitempty"`
+func (x *InvokeSchemaProto) GetMetadata() map[string]string {
+ if x != nil {
+ return x.Metadata
+ }
+ return nil
+}
+
+type PluginSchemaResponse struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Routes []*RouteSchemaProto `protobuf:"bytes,1,rep,name=routes,proto3" json:"routes,omitempty"`
+ Tasks []*TaskSchemaProto `protobuf:"bytes,2,rep,name=tasks,proto3" json:"tasks,omitempty"`
+ Events []*EventSchemaProto `protobuf:"bytes,3,rep,name=events,proto3" json:"events,omitempty"`
+ Invokes []*InvokeSchemaProto `protobuf:"bytes,4,rep,name=invokes,proto3" json:"invokes,omitempty"`
+ Metadata map[string]string `protobuf:"bytes,5,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
-func (x *Usage) Reset() {
- *x = Usage{}
- mi := &file_plugin_proto_msgTypes[42]
+func (x *PluginSchemaResponse) Reset() {
+ *x = PluginSchemaResponse{}
+ mi := &file_plugin_proto_msgTypes[39]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
-func (x *Usage) String() string {
+func (x *PluginSchemaResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
-func (*Usage) ProtoMessage() {}
+func (*PluginSchemaResponse) ProtoMessage() {}
-func (x *Usage) ProtoReflect() protoreflect.Message {
- mi := &file_plugin_proto_msgTypes[42]
+func (x *PluginSchemaResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_plugin_proto_msgTypes[39]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -2973,180 +3040,218 @@ func (x *Usage) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
-// Deprecated: Use Usage.ProtoReflect.Descriptor instead.
-func (*Usage) Descriptor() ([]byte, []int) {
- return file_plugin_proto_rawDescGZIP(), []int{42}
+// Deprecated: Use PluginSchemaResponse.ProtoReflect.Descriptor instead.
+func (*PluginSchemaResponse) Descriptor() ([]byte, []int) {
+ return file_plugin_proto_rawDescGZIP(), []int{39}
}
-func (x *Usage) GetInputTokens() int64 {
+func (x *PluginSchemaResponse) GetRoutes() []*RouteSchemaProto {
if x != nil {
- return x.InputTokens
+ return x.Routes
}
- return 0
+ return nil
}
-func (x *Usage) GetOutputTokens() int64 {
+func (x *PluginSchemaResponse) GetTasks() []*TaskSchemaProto {
if x != nil {
- return x.OutputTokens
+ return x.Tasks
}
- return 0
+ return nil
}
-func (x *Usage) GetCachedInputTokens() int64 {
+func (x *PluginSchemaResponse) GetEvents() []*EventSchemaProto {
if x != nil {
- return x.CachedInputTokens
+ return x.Events
}
- return 0
+ return nil
}
-func (x *Usage) GetCacheCreationTokens() int64 {
+func (x *PluginSchemaResponse) GetInvokes() []*InvokeSchemaProto {
if x != nil {
- return x.CacheCreationTokens
+ return x.Invokes
}
- return 0
+ return nil
}
-func (x *Usage) GetCacheCreation_5MTokens() int64 {
+func (x *PluginSchemaResponse) GetMetadata() map[string]string {
if x != nil {
- return x.CacheCreation_5MTokens
+ return x.Metadata
}
- return 0
+ return nil
+}
+
+// MiddlewareRequest OnForwardBegin 的输入:请求元数据 + 可选的 request body。
+type MiddlewareRequest struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ // === 核心元数据(默认总是填充)===
+ RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` // core 为本次请求分配的唯一 ID(便于跨 Begin/End 关联)
+ UserId int64 `protobuf:"varint,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
+ GroupId int64 `protobuf:"varint,3,opt,name=group_id,json=groupId,proto3" json:"group_id,omitempty"`
+ AccountId int64 `protobuf:"varint,4,opt,name=account_id,json=accountId,proto3" json:"account_id,omitempty"` // 已由 scheduler 选出
+ Platform string `protobuf:"bytes,5,opt,name=platform,proto3" json:"platform,omitempty"`
+ Model string `protobuf:"bytes,6,opt,name=model,proto3" json:"model,omitempty"`
+ Stream bool `protobuf:"varint,7,opt,name=stream,proto3" json:"stream,omitempty"`
+ Estimates []*UsageMetric `protobuf:"bytes,8,rep,name=estimates,proto3" json:"estimates,omitempty"` // Core 或插件提供的通用预估值,仅用于早期决策
+ // metadata KV bag:供多个 middleware 之间传递上下文(Open Question Q-open-3)。
+ // 命名空间规则暂不强制,未来可能收紧。
+ Metadata map[string]string `protobuf:"bytes,9,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
+ // === 按需字段(声明了 middleware.read_body capability 的插件才会收到)===
+ RequestBody []byte `protobuf:"bytes,100,opt,name=request_body,json=requestBody,proto3" json:"request_body,omitempty"`
+ RequestHeaders map[string]*HeaderValues `protobuf:"bytes,101,rep,name=request_headers,json=requestHeaders,proto3" json:"request_headers,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *MiddlewareRequest) Reset() {
+ *x = MiddlewareRequest{}
+ mi := &file_plugin_proto_msgTypes[40]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *MiddlewareRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
}
-func (x *Usage) GetCacheCreation_1HTokens() int64 {
+func (*MiddlewareRequest) ProtoMessage() {}
+
+func (x *MiddlewareRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_plugin_proto_msgTypes[40]
if x != nil {
- return x.CacheCreation_1HTokens
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
}
- return 0
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use MiddlewareRequest.ProtoReflect.Descriptor instead.
+func (*MiddlewareRequest) Descriptor() ([]byte, []int) {
+ return file_plugin_proto_rawDescGZIP(), []int{40}
}
-func (x *Usage) GetReasoningOutputTokens() int64 {
+func (x *MiddlewareRequest) GetRequestId() string {
if x != nil {
- return x.ReasoningOutputTokens
+ return x.RequestId
}
- return 0
+ return ""
}
-func (x *Usage) GetInputCost() float64 {
+func (x *MiddlewareRequest) GetUserId() int64 {
if x != nil {
- return x.InputCost
+ return x.UserId
}
return 0
}
-func (x *Usage) GetOutputCost() float64 {
+func (x *MiddlewareRequest) GetGroupId() int64 {
if x != nil {
- return x.OutputCost
+ return x.GroupId
}
return 0
}
-func (x *Usage) GetCachedInputCost() float64 {
- if x != nil {
- return x.CachedInputCost
- }
- return 0
-}
-
-func (x *Usage) GetCacheCreationCost() float64 {
- if x != nil {
- return x.CacheCreationCost
- }
- return 0
-}
-
-func (x *Usage) GetInputPrice() float64 {
- if x != nil {
- return x.InputPrice
- }
- return 0
-}
-
-func (x *Usage) GetOutputPrice() float64 {
+func (x *MiddlewareRequest) GetAccountId() int64 {
if x != nil {
- return x.OutputPrice
+ return x.AccountId
}
return 0
}
-func (x *Usage) GetCachedInputPrice() float64 {
+func (x *MiddlewareRequest) GetPlatform() string {
if x != nil {
- return x.CachedInputPrice
+ return x.Platform
}
- return 0
+ return ""
}
-func (x *Usage) GetCacheCreationPrice() float64 {
+func (x *MiddlewareRequest) GetModel() string {
if x != nil {
- return x.CacheCreationPrice
+ return x.Model
}
- return 0
+ return ""
}
-func (x *Usage) GetCacheCreation_1HPrice() float64 {
+func (x *MiddlewareRequest) GetStream() bool {
if x != nil {
- return x.CacheCreation_1HPrice
+ return x.Stream
}
- return 0
+ return false
}
-func (x *Usage) GetModel() string {
+func (x *MiddlewareRequest) GetEstimates() []*UsageMetric {
if x != nil {
- return x.Model
+ return x.Estimates
}
- return ""
+ return nil
}
-func (x *Usage) GetServiceTier() string {
+func (x *MiddlewareRequest) GetMetadata() map[string]string {
if x != nil {
- return x.ServiceTier
+ return x.Metadata
}
- return ""
+ return nil
}
-func (x *Usage) GetFirstTokenMs() int64 {
+func (x *MiddlewareRequest) GetRequestBody() []byte {
if x != nil {
- return x.FirstTokenMs
+ return x.RequestBody
}
- return 0
+ return nil
}
-func (x *Usage) GetImageSize() string {
+func (x *MiddlewareRequest) GetRequestHeaders() map[string]*HeaderValues {
if x != nil {
- return x.ImageSize
+ return x.RequestHeaders
}
- return ""
+ return nil
}
-// ForwardOutcome 插件对一次 Forward 的完整判决结果。
-type ForwardOutcome struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Kind OutcomeKind `protobuf:"varint,1,opt,name=kind,proto3,enum=airgate.plugin.v1.OutcomeKind" json:"kind,omitempty"`
- Upstream *UpstreamResponse `protobuf:"bytes,2,opt,name=upstream,proto3" json:"upstream,omitempty"`
- Usage *Usage `protobuf:"bytes,3,opt,name=usage,proto3" json:"usage,omitempty"`
- DurationMs int64 `protobuf:"varint,4,opt,name=duration_ms,json=durationMs,proto3" json:"duration_ms,omitempty"`
- RetryAfterMs int64 `protobuf:"varint,5,opt,name=retry_after_ms,json=retryAfterMs,proto3" json:"retry_after_ms,omitempty"`
- Reason string `protobuf:"bytes,6,opt,name=reason,proto3" json:"reason,omitempty"`
- UpdatedCredentials map[string]string `protobuf:"bytes,7,rep,name=updated_credentials,json=updatedCredentials,proto3" json:"updated_credentials,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
+// MiddlewareEvent OnForwardEnd 的输入:完整的请求 + 响应元数据。
+type MiddlewareEvent struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ // === 核心元数据(与 MiddlewareRequest 对齐)===
+ RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"`
+ UserId int64 `protobuf:"varint,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
+ GroupId int64 `protobuf:"varint,3,opt,name=group_id,json=groupId,proto3" json:"group_id,omitempty"`
+ AccountId int64 `protobuf:"varint,4,opt,name=account_id,json=accountId,proto3" json:"account_id,omitempty"`
+ Platform string `protobuf:"bytes,5,opt,name=platform,proto3" json:"platform,omitempty"`
+ Model string `protobuf:"bytes,6,opt,name=model,proto3" json:"model,omitempty"`
+ Stream bool `protobuf:"varint,7,opt,name=stream,proto3" json:"stream,omitempty"`
+ Estimates []*UsageMetric `protobuf:"bytes,8,rep,name=estimates,proto3" json:"estimates,omitempty"` // 与 MiddlewareRequest 字段对齐,便于 estimate vs actual 比对。
+ // === 响应结果 ===
+ StatusCode int64 `protobuf:"varint,20,opt,name=status_code,json=statusCode,proto3" json:"status_code,omitempty"`
+ DurationMs int64 `protobuf:"varint,21,opt,name=duration_ms,json=durationMs,proto3" json:"duration_ms,omitempty"`
+ Usage *Usage `protobuf:"bytes,22,opt,name=usage,proto3" json:"usage,omitempty"`
+ ErrorKind string `protobuf:"bytes,23,opt,name=error_kind,json=errorKind,proto3" json:"error_kind,omitempty"` // "" / "upstream_5xx" / "timeout" / "no_account" / ...
+ ErrorMsg string `protobuf:"bytes,24,opt,name=error_msg,json=errorMsg,proto3" json:"error_msg,omitempty"` // 限长 512,见 core 实现
+ // metadata 延续自 OnForwardBegin 的 bag
+ Metadata map[string]string `protobuf:"bytes,40,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
+ // === 按需字段(声明了 middleware.read_body capability 的插件才会收到)===
+ // 流式响应时 response_body 只给摘要(首次非空 chunk 拼装)。
+ ResponseBody []byte `protobuf:"bytes,100,opt,name=response_body,json=responseBody,proto3" json:"response_body,omitempty"`
+ ResponseHeaders map[string]*HeaderValues `protobuf:"bytes,101,rep,name=response_headers,json=responseHeaders,proto3" json:"response_headers,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
}
-func (x *ForwardOutcome) Reset() {
- *x = ForwardOutcome{}
- mi := &file_plugin_proto_msgTypes[43]
+func (x *MiddlewareEvent) Reset() {
+ *x = MiddlewareEvent{}
+ mi := &file_plugin_proto_msgTypes[41]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
-func (x *ForwardOutcome) String() string {
+func (x *MiddlewareEvent) String() string {
return protoimpl.X.MessageStringOf(x)
}
-func (*ForwardOutcome) ProtoMessage() {}
+func (*MiddlewareEvent) ProtoMessage() {}
-func (x *ForwardOutcome) ProtoReflect() protoreflect.Message {
- mi := &file_plugin_proto_msgTypes[43]
+func (x *MiddlewareEvent) ProtoReflect() protoreflect.Message {
+ mi := &file_plugin_proto_msgTypes[41]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -3157,158 +3262,153 @@ func (x *ForwardOutcome) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
-// Deprecated: Use ForwardOutcome.ProtoReflect.Descriptor instead.
-func (*ForwardOutcome) Descriptor() ([]byte, []int) {
- return file_plugin_proto_rawDescGZIP(), []int{43}
+// Deprecated: Use MiddlewareEvent.ProtoReflect.Descriptor instead.
+func (*MiddlewareEvent) Descriptor() ([]byte, []int) {
+ return file_plugin_proto_rawDescGZIP(), []int{41}
}
-func (x *ForwardOutcome) GetKind() OutcomeKind {
+func (x *MiddlewareEvent) GetRequestId() string {
if x != nil {
- return x.Kind
+ return x.RequestId
}
- return OutcomeKind_OUTCOME_UNKNOWN
+ return ""
}
-func (x *ForwardOutcome) GetUpstream() *UpstreamResponse {
+func (x *MiddlewareEvent) GetUserId() int64 {
if x != nil {
- return x.Upstream
+ return x.UserId
}
- return nil
+ return 0
}
-func (x *ForwardOutcome) GetUsage() *Usage {
+func (x *MiddlewareEvent) GetGroupId() int64 {
if x != nil {
- return x.Usage
+ return x.GroupId
}
- return nil
+ return 0
}
-func (x *ForwardOutcome) GetDurationMs() int64 {
+func (x *MiddlewareEvent) GetAccountId() int64 {
if x != nil {
- return x.DurationMs
+ return x.AccountId
}
return 0
}
-func (x *ForwardOutcome) GetRetryAfterMs() int64 {
+func (x *MiddlewareEvent) GetPlatform() string {
if x != nil {
- return x.RetryAfterMs
+ return x.Platform
}
- return 0
+ return ""
}
-func (x *ForwardOutcome) GetReason() string {
+func (x *MiddlewareEvent) GetModel() string {
if x != nil {
- return x.Reason
+ return x.Model
}
return ""
}
-func (x *ForwardOutcome) GetUpdatedCredentials() map[string]string {
+func (x *MiddlewareEvent) GetStream() bool {
if x != nil {
- return x.UpdatedCredentials
+ return x.Stream
}
- return nil
-}
-
-type ForwardChunk struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"`
- Done bool `protobuf:"varint,2,opt,name=done,proto3" json:"done,omitempty"`
- FinalOutcome *ForwardOutcome `protobuf:"bytes,3,opt,name=final_outcome,json=finalOutcome,proto3" json:"final_outcome,omitempty"`
- StatusCode int32 `protobuf:"varint,4,opt,name=status_code,json=statusCode,proto3" json:"status_code,omitempty"`
- Headers map[string]*HeaderValues `protobuf:"bytes,5,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
+ return false
}
-func (x *ForwardChunk) Reset() {
- *x = ForwardChunk{}
- mi := &file_plugin_proto_msgTypes[44]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
+func (x *MiddlewareEvent) GetEstimates() []*UsageMetric {
+ if x != nil {
+ return x.Estimates
+ }
+ return nil
}
-func (x *ForwardChunk) String() string {
- return protoimpl.X.MessageStringOf(x)
+func (x *MiddlewareEvent) GetStatusCode() int64 {
+ if x != nil {
+ return x.StatusCode
+ }
+ return 0
}
-func (*ForwardChunk) ProtoMessage() {}
-
-func (x *ForwardChunk) ProtoReflect() protoreflect.Message {
- mi := &file_plugin_proto_msgTypes[44]
+func (x *MiddlewareEvent) GetDurationMs() int64 {
if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
+ return x.DurationMs
}
- return mi.MessageOf(x)
+ return 0
}
-// Deprecated: Use ForwardChunk.ProtoReflect.Descriptor instead.
-func (*ForwardChunk) Descriptor() ([]byte, []int) {
- return file_plugin_proto_rawDescGZIP(), []int{44}
+func (x *MiddlewareEvent) GetUsage() *Usage {
+ if x != nil {
+ return x.Usage
+ }
+ return nil
}
-func (x *ForwardChunk) GetData() []byte {
+func (x *MiddlewareEvent) GetErrorKind() string {
if x != nil {
- return x.Data
+ return x.ErrorKind
}
- return nil
+ return ""
}
-func (x *ForwardChunk) GetDone() bool {
+func (x *MiddlewareEvent) GetErrorMsg() string {
if x != nil {
- return x.Done
+ return x.ErrorMsg
}
- return false
+ return ""
}
-func (x *ForwardChunk) GetFinalOutcome() *ForwardOutcome {
+func (x *MiddlewareEvent) GetMetadata() map[string]string {
if x != nil {
- return x.FinalOutcome
+ return x.Metadata
}
return nil
}
-func (x *ForwardChunk) GetStatusCode() int32 {
+func (x *MiddlewareEvent) GetResponseBody() []byte {
if x != nil {
- return x.StatusCode
+ return x.ResponseBody
}
- return 0
+ return nil
}
-func (x *ForwardChunk) GetHeaders() map[string]*HeaderValues {
+func (x *MiddlewareEvent) GetResponseHeaders() map[string]*HeaderValues {
if x != nil {
- return x.Headers
+ return x.ResponseHeaders
}
return nil
}
-type CredentialsRequest struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Credentials map[string]string `protobuf:"bytes,1,rep,name=credentials,proto3" json:"credentials,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
+// MiddlewareDecision OnForwardBegin 的输出:放行 / 拒绝 / 改请求。
+type MiddlewareDecision struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Action MiddlewareDecision_Action `protobuf:"varint,1,opt,name=action,proto3,enum=airgate.plugin.v2.MiddlewareDecision_Action" json:"action,omitempty"`
+ // action=DENY 时的错误码 / 文案(对用户可见)
+ DenyStatusCode int32 `protobuf:"varint,10,opt,name=deny_status_code,json=denyStatusCode,proto3" json:"deny_status_code,omitempty"` // 默认 403 if Action=DENY and 未指定
+ DenyMessage string `protobuf:"bytes,11,opt,name=deny_message,json=denyMessage,proto3" json:"deny_message,omitempty"`
+ // action=MUTATE 时要追加/覆盖的请求头
+ SetHeaders map[string]*HeaderValues `protobuf:"bytes,20,rep,name=set_headers,json=setHeaders,proto3" json:"set_headers,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
+ // 贯穿式 metadata:无论 allow/deny/mutate,都能往 bag 里写东西供后续 middleware / End 使用
+ Metadata map[string]string `protobuf:"bytes,30,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
-func (x *CredentialsRequest) Reset() {
- *x = CredentialsRequest{}
- mi := &file_plugin_proto_msgTypes[45]
+func (x *MiddlewareDecision) Reset() {
+ *x = MiddlewareDecision{}
+ mi := &file_plugin_proto_msgTypes[42]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
-func (x *CredentialsRequest) String() string {
+func (x *MiddlewareDecision) String() string {
return protoimpl.X.MessageStringOf(x)
}
-func (*CredentialsRequest) ProtoMessage() {}
+func (*MiddlewareDecision) ProtoMessage() {}
-func (x *CredentialsRequest) ProtoReflect() protoreflect.Message {
- mi := &file_plugin_proto_msgTypes[45]
+func (x *MiddlewareDecision) ProtoReflect() protoreflect.Message {
+ mi := &file_plugin_proto_msgTypes[42]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -3319,484 +3419,71 @@ func (x *CredentialsRequest) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
-// Deprecated: Use CredentialsRequest.ProtoReflect.Descriptor instead.
-func (*CredentialsRequest) Descriptor() ([]byte, []int) {
- return file_plugin_proto_rawDescGZIP(), []int{45}
+// Deprecated: Use MiddlewareDecision.ProtoReflect.Descriptor instead.
+func (*MiddlewareDecision) Descriptor() ([]byte, []int) {
+ return file_plugin_proto_rawDescGZIP(), []int{42}
}
-func (x *CredentialsRequest) GetCredentials() map[string]string {
+func (x *MiddlewareDecision) GetAction() MiddlewareDecision_Action {
if x != nil {
- return x.Credentials
+ return x.Action
}
- return nil
+ return MiddlewareDecision_ALLOW
}
-type QuotaInfoResponse struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Total float64 `protobuf:"fixed64,1,opt,name=total,proto3" json:"total,omitempty"`
- Used float64 `protobuf:"fixed64,2,opt,name=used,proto3" json:"used,omitempty"`
- Remaining float64 `protobuf:"fixed64,3,opt,name=remaining,proto3" json:"remaining,omitempty"`
- Currency string `protobuf:"bytes,4,opt,name=currency,proto3" json:"currency,omitempty"`
- ExpiresAt string `protobuf:"bytes,5,opt,name=expires_at,json=expiresAt,proto3" json:"expires_at,omitempty"`
- Extra map[string]string `protobuf:"bytes,6,rep,name=extra,proto3" json:"extra,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
+func (x *MiddlewareDecision) GetDenyStatusCode() int32 {
+ if x != nil {
+ return x.DenyStatusCode
+ }
+ return 0
}
-func (x *QuotaInfoResponse) Reset() {
- *x = QuotaInfoResponse{}
- mi := &file_plugin_proto_msgTypes[46]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
+func (x *MiddlewareDecision) GetDenyMessage() string {
+ if x != nil {
+ return x.DenyMessage
+ }
+ return ""
}
-func (x *QuotaInfoResponse) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*QuotaInfoResponse) ProtoMessage() {}
-
-func (x *QuotaInfoResponse) ProtoReflect() protoreflect.Message {
- mi := &file_plugin_proto_msgTypes[46]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use QuotaInfoResponse.ProtoReflect.Descriptor instead.
-func (*QuotaInfoResponse) Descriptor() ([]byte, []int) {
- return file_plugin_proto_rawDescGZIP(), []int{46}
-}
-
-func (x *QuotaInfoResponse) GetTotal() float64 {
- if x != nil {
- return x.Total
- }
- return 0
-}
-
-func (x *QuotaInfoResponse) GetUsed() float64 {
- if x != nil {
- return x.Used
- }
- return 0
-}
-
-func (x *QuotaInfoResponse) GetRemaining() float64 {
- if x != nil {
- return x.Remaining
- }
- return 0
-}
-
-func (x *QuotaInfoResponse) GetCurrency() string {
- if x != nil {
- return x.Currency
- }
- return ""
-}
-
-func (x *QuotaInfoResponse) GetExpiresAt() string {
- if x != nil {
- return x.ExpiresAt
- }
- return ""
-}
-
-func (x *QuotaInfoResponse) GetExtra() map[string]string {
- if x != nil {
- return x.Extra
- }
- return nil
-}
-
-type HttpRequest struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Method string `protobuf:"bytes,1,opt,name=method,proto3" json:"method,omitempty"`
- Path string `protobuf:"bytes,2,opt,name=path,proto3" json:"path,omitempty"`
- Query string `protobuf:"bytes,3,opt,name=query,proto3" json:"query,omitempty"`
- Headers map[string]*HeaderValues `protobuf:"bytes,4,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
- Body []byte `protobuf:"bytes,5,opt,name=body,proto3" json:"body,omitempty"`
- RemoteAddr string `protobuf:"bytes,6,opt,name=remote_addr,json=remoteAddr,proto3" json:"remote_addr,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *HttpRequest) Reset() {
- *x = HttpRequest{}
- mi := &file_plugin_proto_msgTypes[47]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *HttpRequest) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*HttpRequest) ProtoMessage() {}
-
-func (x *HttpRequest) ProtoReflect() protoreflect.Message {
- mi := &file_plugin_proto_msgTypes[47]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use HttpRequest.ProtoReflect.Descriptor instead.
-func (*HttpRequest) Descriptor() ([]byte, []int) {
- return file_plugin_proto_rawDescGZIP(), []int{47}
-}
-
-func (x *HttpRequest) GetMethod() string {
- if x != nil {
- return x.Method
- }
- return ""
-}
-
-func (x *HttpRequest) GetPath() string {
- if x != nil {
- return x.Path
- }
- return ""
-}
-
-func (x *HttpRequest) GetQuery() string {
- if x != nil {
- return x.Query
- }
- return ""
-}
-
-func (x *HttpRequest) GetHeaders() map[string]*HeaderValues {
- if x != nil {
- return x.Headers
- }
- return nil
-}
-
-func (x *HttpRequest) GetBody() []byte {
- if x != nil {
- return x.Body
- }
- return nil
-}
-
-func (x *HttpRequest) GetRemoteAddr() string {
- if x != nil {
- return x.RemoteAddr
- }
- return ""
-}
-
-type HttpResponse struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- StatusCode int32 `protobuf:"varint,1,opt,name=status_code,json=statusCode,proto3" json:"status_code,omitempty"`
- Headers map[string]*HeaderValues `protobuf:"bytes,2,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
- Body []byte `protobuf:"bytes,3,opt,name=body,proto3" json:"body,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *HttpResponse) Reset() {
- *x = HttpResponse{}
- mi := &file_plugin_proto_msgTypes[48]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *HttpResponse) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*HttpResponse) ProtoMessage() {}
-
-func (x *HttpResponse) ProtoReflect() protoreflect.Message {
- mi := &file_plugin_proto_msgTypes[48]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use HttpResponse.ProtoReflect.Descriptor instead.
-func (*HttpResponse) Descriptor() ([]byte, []int) {
- return file_plugin_proto_rawDescGZIP(), []int{48}
-}
-
-func (x *HttpResponse) GetStatusCode() int32 {
- if x != nil {
- return x.StatusCode
- }
- return 0
-}
-
-func (x *HttpResponse) GetHeaders() map[string]*HeaderValues {
- if x != nil {
- return x.Headers
- }
- return nil
-}
-
-func (x *HttpResponse) GetBody() []byte {
- if x != nil {
- return x.Body
- }
- return nil
-}
-
-type HttpResponseChunk struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"`
- Done bool `protobuf:"varint,2,opt,name=done,proto3" json:"done,omitempty"`
- StatusCode int32 `protobuf:"varint,3,opt,name=status_code,json=statusCode,proto3" json:"status_code,omitempty"`
- Headers map[string]*HeaderValues `protobuf:"bytes,4,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *HttpResponseChunk) Reset() {
- *x = HttpResponseChunk{}
- mi := &file_plugin_proto_msgTypes[49]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *HttpResponseChunk) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*HttpResponseChunk) ProtoMessage() {}
-
-func (x *HttpResponseChunk) ProtoReflect() protoreflect.Message {
- mi := &file_plugin_proto_msgTypes[49]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use HttpResponseChunk.ProtoReflect.Descriptor instead.
-func (*HttpResponseChunk) Descriptor() ([]byte, []int) {
- return file_plugin_proto_rawDescGZIP(), []int{49}
-}
-
-func (x *HttpResponseChunk) GetData() []byte {
- if x != nil {
- return x.Data
- }
- return nil
-}
-
-func (x *HttpResponseChunk) GetDone() bool {
- if x != nil {
- return x.Done
- }
- return false
-}
-
-func (x *HttpResponseChunk) GetStatusCode() int32 {
- if x != nil {
- return x.StatusCode
- }
- return 0
-}
-
-func (x *HttpResponseChunk) GetHeaders() map[string]*HeaderValues {
+func (x *MiddlewareDecision) GetSetHeaders() map[string]*HeaderValues {
if x != nil {
- return x.Headers
+ return x.SetHeaders
}
return nil
}
-type BackgroundTaskProto struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
- IntervalMs int64 `protobuf:"varint,2,opt,name=interval_ms,json=intervalMs,proto3" json:"interval_ms,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *BackgroundTaskProto) Reset() {
- *x = BackgroundTaskProto{}
- mi := &file_plugin_proto_msgTypes[50]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *BackgroundTaskProto) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*BackgroundTaskProto) ProtoMessage() {}
-
-func (x *BackgroundTaskProto) ProtoReflect() protoreflect.Message {
- mi := &file_plugin_proto_msgTypes[50]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use BackgroundTaskProto.ProtoReflect.Descriptor instead.
-func (*BackgroundTaskProto) Descriptor() ([]byte, []int) {
- return file_plugin_proto_rawDescGZIP(), []int{50}
-}
-
-func (x *BackgroundTaskProto) GetName() string {
- if x != nil {
- return x.Name
- }
- return ""
-}
-
-func (x *BackgroundTaskProto) GetIntervalMs() int64 {
- if x != nil {
- return x.IntervalMs
- }
- return 0
-}
-
-type BackgroundTasksResponse struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Tasks []*BackgroundTaskProto `protobuf:"bytes,1,rep,name=tasks,proto3" json:"tasks,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *BackgroundTasksResponse) Reset() {
- *x = BackgroundTasksResponse{}
- mi := &file_plugin_proto_msgTypes[51]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *BackgroundTasksResponse) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*BackgroundTasksResponse) ProtoMessage() {}
-
-func (x *BackgroundTasksResponse) ProtoReflect() protoreflect.Message {
- mi := &file_plugin_proto_msgTypes[51]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use BackgroundTasksResponse.ProtoReflect.Descriptor instead.
-func (*BackgroundTasksResponse) Descriptor() ([]byte, []int) {
- return file_plugin_proto_rawDescGZIP(), []int{51}
-}
-
-func (x *BackgroundTasksResponse) GetTasks() []*BackgroundTaskProto {
+func (x *MiddlewareDecision) GetMetadata() map[string]string {
if x != nil {
- return x.Tasks
+ return x.Metadata
}
return nil
}
-type RunBackgroundTaskRequest struct {
+type EventSubscriptionProto struct {
state protoimpl.MessageState `protogen:"open.v1"`
- Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
+ Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"`
+ Source string `protobuf:"bytes,2,opt,name=source,proto3" json:"source,omitempty"`
+ Filter map[string]string `protobuf:"bytes,3,rep,name=filter,proto3" json:"filter,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
+ Metadata map[string]string `protobuf:"bytes,4,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
-
-func (x *RunBackgroundTaskRequest) Reset() {
- *x = RunBackgroundTaskRequest{}
- mi := &file_plugin_proto_msgTypes[52]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *RunBackgroundTaskRequest) String() string {
- return protoimpl.X.MessageStringOf(x)
-}
-
-func (*RunBackgroundTaskRequest) ProtoMessage() {}
-
-func (x *RunBackgroundTaskRequest) ProtoReflect() protoreflect.Message {
- mi := &file_plugin_proto_msgTypes[52]
- if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
- }
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use RunBackgroundTaskRequest.ProtoReflect.Descriptor instead.
-func (*RunBackgroundTaskRequest) Descriptor() ([]byte, []int) {
- return file_plugin_proto_rawDescGZIP(), []int{52}
-}
-
-func (x *RunBackgroundTaskRequest) GetName() string {
- if x != nil {
- return x.Name
- }
- return ""
-}
-
-type WebSocketFrame struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Type WebSocketFrame_FrameType `protobuf:"varint,1,opt,name=type,proto3,enum=airgate.plugin.v1.WebSocketFrame_FrameType" json:"type,omitempty"`
- Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"`
- // 仅 CONNECT 帧使用
- ConnectInfo *WebSocketConnectInfo `protobuf:"bytes,3,opt,name=connect_info,json=connectInfo,proto3" json:"connect_info,omitempty"`
- // 仅 CLOSE 帧使用
- CloseCode int32 `protobuf:"varint,4,opt,name=close_code,json=closeCode,proto3" json:"close_code,omitempty"`
- CloseReason string `protobuf:"bytes,5,opt,name=close_reason,json=closeReason,proto3" json:"close_reason,omitempty"`
- // 仅 RESULT 帧使用
- Outcome *ForwardOutcome `protobuf:"bytes,6,opt,name=outcome,proto3" json:"outcome,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *WebSocketFrame) Reset() {
- *x = WebSocketFrame{}
- mi := &file_plugin_proto_msgTypes[53]
+
+func (x *EventSubscriptionProto) Reset() {
+ *x = EventSubscriptionProto{}
+ mi := &file_plugin_proto_msgTypes[43]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
-func (x *WebSocketFrame) String() string {
+func (x *EventSubscriptionProto) String() string {
return protoimpl.X.MessageStringOf(x)
}
-func (*WebSocketFrame) ProtoMessage() {}
+func (*EventSubscriptionProto) ProtoMessage() {}
-func (x *WebSocketFrame) ProtoReflect() protoreflect.Message {
- mi := &file_plugin_proto_msgTypes[53]
+func (x *EventSubscriptionProto) ProtoReflect() protoreflect.Message {
+ mi := &file_plugin_proto_msgTypes[43]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -3807,80 +3494,113 @@ func (x *WebSocketFrame) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
-// Deprecated: Use WebSocketFrame.ProtoReflect.Descriptor instead.
-func (*WebSocketFrame) Descriptor() ([]byte, []int) {
- return file_plugin_proto_rawDescGZIP(), []int{53}
+// Deprecated: Use EventSubscriptionProto.ProtoReflect.Descriptor instead.
+func (*EventSubscriptionProto) Descriptor() ([]byte, []int) {
+ return file_plugin_proto_rawDescGZIP(), []int{43}
}
-func (x *WebSocketFrame) GetType() WebSocketFrame_FrameType {
+func (x *EventSubscriptionProto) GetType() string {
if x != nil {
return x.Type
}
- return WebSocketFrame_CONNECT
+ return ""
}
-func (x *WebSocketFrame) GetData() []byte {
+func (x *EventSubscriptionProto) GetSource() string {
if x != nil {
- return x.Data
+ return x.Source
}
- return nil
+ return ""
}
-func (x *WebSocketFrame) GetConnectInfo() *WebSocketConnectInfo {
+func (x *EventSubscriptionProto) GetFilter() map[string]string {
if x != nil {
- return x.ConnectInfo
+ return x.Filter
}
return nil
}
-func (x *WebSocketFrame) GetCloseCode() int32 {
+func (x *EventSubscriptionProto) GetMetadata() map[string]string {
if x != nil {
- return x.CloseCode
+ return x.Metadata
}
- return 0
+ return nil
}
-func (x *WebSocketFrame) GetCloseReason() string {
+type EventSubscriptionsResponse struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Subscriptions []*EventSubscriptionProto `protobuf:"bytes,1,rep,name=subscriptions,proto3" json:"subscriptions,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *EventSubscriptionsResponse) Reset() {
+ *x = EventSubscriptionsResponse{}
+ mi := &file_plugin_proto_msgTypes[44]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *EventSubscriptionsResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*EventSubscriptionsResponse) ProtoMessage() {}
+
+func (x *EventSubscriptionsResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_plugin_proto_msgTypes[44]
if x != nil {
- return x.CloseReason
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
}
- return ""
+ return mi.MessageOf(x)
}
-func (x *WebSocketFrame) GetOutcome() *ForwardOutcome {
+// Deprecated: Use EventSubscriptionsResponse.ProtoReflect.Descriptor instead.
+func (*EventSubscriptionsResponse) Descriptor() ([]byte, []int) {
+ return file_plugin_proto_rawDescGZIP(), []int{44}
+}
+
+func (x *EventSubscriptionsResponse) GetSubscriptions() []*EventSubscriptionProto {
if x != nil {
- return x.Outcome
+ return x.Subscriptions
}
return nil
}
-type WebSocketConnectInfo struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Path string `protobuf:"bytes,1,opt,name=path,proto3" json:"path,omitempty"`
- Query string `protobuf:"bytes,2,opt,name=query,proto3" json:"query,omitempty"`
- Headers map[string]*HeaderValues `protobuf:"bytes,3,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
- RemoteAddr string `protobuf:"bytes,4,opt,name=remote_addr,json=remoteAddr,proto3" json:"remote_addr,omitempty"`
- ConnectionId string `protobuf:"bytes,5,opt,name=connection_id,json=connectionId,proto3" json:"connection_id,omitempty"`
- Account *AccountProto `protobuf:"bytes,12,opt,name=account,proto3" json:"account,omitempty"`
+type PluginEvent struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
+ Type string `protobuf:"bytes,2,opt,name=type,proto3" json:"type,omitempty"`
+ Source string `protobuf:"bytes,3,opt,name=source,proto3" json:"source,omitempty"`
+ Subject string `protobuf:"bytes,4,opt,name=subject,proto3" json:"subject,omitempty"`
+ UserId int64 `protobuf:"varint,5,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
+ GroupId int64 `protobuf:"varint,6,opt,name=group_id,json=groupId,proto3" json:"group_id,omitempty"`
+ Payload []byte `protobuf:"bytes,7,opt,name=payload,proto3" json:"payload,omitempty"` // JSON 编码的对象
+ Metadata map[string]string `protobuf:"bytes,8,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
+ OccurredAt int64 `protobuf:"varint,9,opt,name=occurred_at,json=occurredAt,proto3" json:"occurred_at,omitempty"` // unix millis, 0 = unset
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
-func (x *WebSocketConnectInfo) Reset() {
- *x = WebSocketConnectInfo{}
- mi := &file_plugin_proto_msgTypes[54]
+func (x *PluginEvent) Reset() {
+ *x = PluginEvent{}
+ mi := &file_plugin_proto_msgTypes[45]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
-func (x *WebSocketConnectInfo) String() string {
+func (x *PluginEvent) String() string {
return protoimpl.X.MessageStringOf(x)
}
-func (*WebSocketConnectInfo) ProtoMessage() {}
+func (*PluginEvent) ProtoMessage() {}
-func (x *WebSocketConnectInfo) ProtoReflect() protoreflect.Message {
- mi := &file_plugin_proto_msgTypes[54]
+func (x *PluginEvent) ProtoReflect() protoreflect.Message {
+ mi := &file_plugin_proto_msgTypes[45]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -3891,128 +3611,97 @@ func (x *WebSocketConnectInfo) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
-// Deprecated: Use WebSocketConnectInfo.ProtoReflect.Descriptor instead.
-func (*WebSocketConnectInfo) Descriptor() ([]byte, []int) {
- return file_plugin_proto_rawDescGZIP(), []int{54}
+// Deprecated: Use PluginEvent.ProtoReflect.Descriptor instead.
+func (*PluginEvent) Descriptor() ([]byte, []int) {
+ return file_plugin_proto_rawDescGZIP(), []int{45}
}
-func (x *WebSocketConnectInfo) GetPath() string {
+func (x *PluginEvent) GetId() string {
if x != nil {
- return x.Path
+ return x.Id
}
return ""
}
-func (x *WebSocketConnectInfo) GetQuery() string {
+func (x *PluginEvent) GetType() string {
if x != nil {
- return x.Query
+ return x.Type
}
return ""
}
-func (x *WebSocketConnectInfo) GetHeaders() map[string]*HeaderValues {
+func (x *PluginEvent) GetSource() string {
if x != nil {
- return x.Headers
+ return x.Source
}
- return nil
+ return ""
}
-func (x *WebSocketConnectInfo) GetRemoteAddr() string {
+func (x *PluginEvent) GetSubject() string {
if x != nil {
- return x.RemoteAddr
+ return x.Subject
}
return ""
}
-func (x *WebSocketConnectInfo) GetConnectionId() string {
+func (x *PluginEvent) GetUserId() int64 {
if x != nil {
- return x.ConnectionId
+ return x.UserId
}
- return ""
+ return 0
}
-func (x *WebSocketConnectInfo) GetAccount() *AccountProto {
+func (x *PluginEvent) GetGroupId() int64 {
if x != nil {
- return x.Account
+ return x.GroupId
}
- return nil
-}
-
-type WebAssetFile struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Path string `protobuf:"bytes,1,opt,name=path,proto3" json:"path,omitempty"`
- Content []byte `protobuf:"bytes,2,opt,name=content,proto3" json:"content,omitempty"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
-}
-
-func (x *WebAssetFile) Reset() {
- *x = WebAssetFile{}
- mi := &file_plugin_proto_msgTypes[55]
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- ms.StoreMessageInfo(mi)
-}
-
-func (x *WebAssetFile) String() string {
- return protoimpl.X.MessageStringOf(x)
+ return 0
}
-func (*WebAssetFile) ProtoMessage() {}
-
-func (x *WebAssetFile) ProtoReflect() protoreflect.Message {
- mi := &file_plugin_proto_msgTypes[55]
+func (x *PluginEvent) GetPayload() []byte {
if x != nil {
- ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
- if ms.LoadMessageInfo() == nil {
- ms.StoreMessageInfo(mi)
- }
- return ms
+ return x.Payload
}
- return mi.MessageOf(x)
-}
-
-// Deprecated: Use WebAssetFile.ProtoReflect.Descriptor instead.
-func (*WebAssetFile) Descriptor() ([]byte, []int) {
- return file_plugin_proto_rawDescGZIP(), []int{55}
+ return nil
}
-func (x *WebAssetFile) GetPath() string {
+func (x *PluginEvent) GetMetadata() map[string]string {
if x != nil {
- return x.Path
+ return x.Metadata
}
- return ""
+ return nil
}
-func (x *WebAssetFile) GetContent() []byte {
+func (x *PluginEvent) GetOccurredAt() int64 {
if x != nil {
- return x.Content
+ return x.OccurredAt
}
- return nil
+ return 0
}
-type WebAssetsResponse struct {
+type EventHandleResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
- Files []*WebAssetFile `protobuf:"bytes,1,rep,name=files,proto3" json:"files,omitempty"`
- HasAssets bool `protobuf:"varint,2,opt,name=has_assets,json=hasAssets,proto3" json:"has_assets,omitempty"`
+ Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"`
+ ErrorMessage string `protobuf:"bytes,2,opt,name=error_message,json=errorMessage,proto3" json:"error_message,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
-func (x *WebAssetsResponse) Reset() {
- *x = WebAssetsResponse{}
- mi := &file_plugin_proto_msgTypes[56]
+func (x *EventHandleResponse) Reset() {
+ *x = EventHandleResponse{}
+ mi := &file_plugin_proto_msgTypes[46]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
-func (x *WebAssetsResponse) String() string {
+func (x *EventHandleResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
-func (*WebAssetsResponse) ProtoMessage() {}
+func (*EventHandleResponse) ProtoMessage() {}
-func (x *WebAssetsResponse) ProtoReflect() protoreflect.Message {
- mi := &file_plugin_proto_msgTypes[56]
+func (x *EventHandleResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_plugin_proto_msgTypes[46]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -4023,62 +3712,50 @@ func (x *WebAssetsResponse) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
-// Deprecated: Use WebAssetsResponse.ProtoReflect.Descriptor instead.
-func (*WebAssetsResponse) Descriptor() ([]byte, []int) {
- return file_plugin_proto_rawDescGZIP(), []int{56}
+// Deprecated: Use EventHandleResponse.ProtoReflect.Descriptor instead.
+func (*EventHandleResponse) Descriptor() ([]byte, []int) {
+ return file_plugin_proto_rawDescGZIP(), []int{46}
}
-func (x *WebAssetsResponse) GetFiles() []*WebAssetFile {
+func (x *EventHandleResponse) GetSuccess() bool {
if x != nil {
- return x.Files
+ return x.Success
}
- return nil
+ return false
}
-func (x *WebAssetsResponse) GetHasAssets() bool {
+func (x *EventHandleResponse) GetErrorMessage() string {
if x != nil {
- return x.HasAssets
+ return x.ErrorMessage
}
- return false
+ return ""
}
-// MiddlewareRequest OnForwardBegin 的输入:请求元数据 + 可选的 request body。
-type MiddlewareRequest struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- // === 核心元数据(默认总是填充)===
- RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` // core 为本次请求分配的唯一 ID(便于跨 Begin/End 关联)
- UserId int64 `protobuf:"varint,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
- GroupId int64 `protobuf:"varint,3,opt,name=group_id,json=groupId,proto3" json:"group_id,omitempty"`
- AccountId int64 `protobuf:"varint,4,opt,name=account_id,json=accountId,proto3" json:"account_id,omitempty"` // 已由 scheduler 选出
- Platform string `protobuf:"bytes,5,opt,name=platform,proto3" json:"platform,omitempty"`
- Model string `protobuf:"bytes,6,opt,name=model,proto3" json:"model,omitempty"`
- Stream bool `protobuf:"varint,7,opt,name=stream,proto3" json:"stream,omitempty"`
- InputTokensEst int64 `protobuf:"varint,8,opt,name=input_tokens_est,json=inputTokensEst,proto3" json:"input_tokens_est,omitempty"` // core 侧粗略估算,仅用于早期决策
- // metadata KV bag:供多个 middleware 之间传递上下文(Open Question Q-open-3)。
- // 命名空间规则暂不强制,未来可能收紧。
- Metadata map[string]string `protobuf:"bytes,9,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
- // === 按需字段(声明了 middleware.read_body capability 的插件才会收到)===
- RequestBody []byte `protobuf:"bytes,100,opt,name=request_body,json=requestBody,proto3" json:"request_body,omitempty"`
- RequestHeaders map[string]*HeaderValues `protobuf:"bytes,101,rep,name=request_headers,json=requestHeaders,proto3" json:"request_headers,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
+type HostInvokeRequest struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Method string `protobuf:"bytes,1,opt,name=method,proto3" json:"method,omitempty"`
+ Payload []byte `protobuf:"bytes,2,opt,name=payload,proto3" json:"payload,omitempty"` // JSON 编码的对象,由 Core method 自己校验 schema
+ IdempotencyKey string `protobuf:"bytes,3,opt,name=idempotency_key,json=idempotencyKey,proto3" json:"idempotency_key,omitempty"` // 副作用方法的幂等键;只读方法可留空
+ Metadata map[string]string `protobuf:"bytes,4,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` // 调用级辅助信息,不用于替代权限、调度或核心业务字段
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
-func (x *MiddlewareRequest) Reset() {
- *x = MiddlewareRequest{}
- mi := &file_plugin_proto_msgTypes[57]
+func (x *HostInvokeRequest) Reset() {
+ *x = HostInvokeRequest{}
+ mi := &file_plugin_proto_msgTypes[47]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
-func (x *MiddlewareRequest) String() string {
+func (x *HostInvokeRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
-func (*MiddlewareRequest) ProtoMessage() {}
+func (*HostInvokeRequest) ProtoMessage() {}
-func (x *MiddlewareRequest) ProtoReflect() protoreflect.Message {
- mi := &file_plugin_proto_msgTypes[57]
+func (x *HostInvokeRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_plugin_proto_msgTypes[47]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -4089,139 +3766,63 @@ func (x *MiddlewareRequest) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
-// Deprecated: Use MiddlewareRequest.ProtoReflect.Descriptor instead.
-func (*MiddlewareRequest) Descriptor() ([]byte, []int) {
- return file_plugin_proto_rawDescGZIP(), []int{57}
+// Deprecated: Use HostInvokeRequest.ProtoReflect.Descriptor instead.
+func (*HostInvokeRequest) Descriptor() ([]byte, []int) {
+ return file_plugin_proto_rawDescGZIP(), []int{47}
}
-func (x *MiddlewareRequest) GetRequestId() string {
+func (x *HostInvokeRequest) GetMethod() string {
if x != nil {
- return x.RequestId
+ return x.Method
}
return ""
}
-func (x *MiddlewareRequest) GetUserId() int64 {
- if x != nil {
- return x.UserId
- }
- return 0
-}
-
-func (x *MiddlewareRequest) GetGroupId() int64 {
- if x != nil {
- return x.GroupId
- }
- return 0
-}
-
-func (x *MiddlewareRequest) GetAccountId() int64 {
- if x != nil {
- return x.AccountId
- }
- return 0
-}
-
-func (x *MiddlewareRequest) GetPlatform() string {
+func (x *HostInvokeRequest) GetPayload() []byte {
if x != nil {
- return x.Platform
+ return x.Payload
}
- return ""
+ return nil
}
-func (x *MiddlewareRequest) GetModel() string {
+func (x *HostInvokeRequest) GetIdempotencyKey() string {
if x != nil {
- return x.Model
+ return x.IdempotencyKey
}
return ""
}
-func (x *MiddlewareRequest) GetStream() bool {
- if x != nil {
- return x.Stream
- }
- return false
-}
-
-func (x *MiddlewareRequest) GetInputTokensEst() int64 {
- if x != nil {
- return x.InputTokensEst
- }
- return 0
-}
-
-func (x *MiddlewareRequest) GetMetadata() map[string]string {
+func (x *HostInvokeRequest) GetMetadata() map[string]string {
if x != nil {
return x.Metadata
}
return nil
}
-func (x *MiddlewareRequest) GetRequestBody() []byte {
- if x != nil {
- return x.RequestBody
- }
- return nil
-}
-
-func (x *MiddlewareRequest) GetRequestHeaders() map[string]*HeaderValues {
- if x != nil {
- return x.RequestHeaders
- }
- return nil
-}
-
-// MiddlewareEvent OnForwardEnd 的输入:完整的请求 + 响应元数据。
-type MiddlewareEvent struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- // === 核心元数据(与 MiddlewareRequest 对齐)===
- RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"`
- UserId int64 `protobuf:"varint,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
- GroupId int64 `protobuf:"varint,3,opt,name=group_id,json=groupId,proto3" json:"group_id,omitempty"`
- AccountId int64 `protobuf:"varint,4,opt,name=account_id,json=accountId,proto3" json:"account_id,omitempty"`
- Platform string `protobuf:"bytes,5,opt,name=platform,proto3" json:"platform,omitempty"`
- Model string `protobuf:"bytes,6,opt,name=model,proto3" json:"model,omitempty"`
- Stream bool `protobuf:"varint,7,opt,name=stream,proto3" json:"stream,omitempty"`
- InputTokensEst int64 `protobuf:"varint,8,opt,name=input_tokens_est,json=inputTokensEst,proto3" json:"input_tokens_est,omitempty"` // 与 MiddlewareRequest 字段对齐:core 侧粗略估算,
- // === 响应结果 ===
- StatusCode int64 `protobuf:"varint,20,opt,name=status_code,json=statusCode,proto3" json:"status_code,omitempty"`
- DurationMs int64 `protobuf:"varint,21,opt,name=duration_ms,json=durationMs,proto3" json:"duration_ms,omitempty"`
- InputTokens int64 `protobuf:"varint,22,opt,name=input_tokens,json=inputTokens,proto3" json:"input_tokens,omitempty"`
- OutputTokens int64 `protobuf:"varint,23,opt,name=output_tokens,json=outputTokens,proto3" json:"output_tokens,omitempty"`
- CachedInputTokens int64 `protobuf:"varint,24,opt,name=cached_input_tokens,json=cachedInputTokens,proto3" json:"cached_input_tokens,omitempty"`
- FirstTokenMs int64 `protobuf:"varint,25,opt,name=first_token_ms,json=firstTokenMs,proto3" json:"first_token_ms,omitempty"`
- ErrorKind string `protobuf:"bytes,26,opt,name=error_kind,json=errorKind,proto3" json:"error_kind,omitempty"` // "" / "upstream_5xx" / "timeout" / "no_account" / ...
- ErrorMsg string `protobuf:"bytes,27,opt,name=error_msg,json=errorMsg,proto3" json:"error_msg,omitempty"` // 限长 512,见 core 实现
- // 费用快照(core 已计算好)
- InputCost float64 `protobuf:"fixed64,30,opt,name=input_cost,json=inputCost,proto3" json:"input_cost,omitempty"`
- OutputCost float64 `protobuf:"fixed64,31,opt,name=output_cost,json=outputCost,proto3" json:"output_cost,omitempty"`
- CachedInputCost float64 `protobuf:"fixed64,32,opt,name=cached_input_cost,json=cachedInputCost,proto3" json:"cached_input_cost,omitempty"`
- // metadata 延续自 OnForwardBegin 的 bag
- Metadata map[string]string `protobuf:"bytes,40,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
- // === 按需字段(声明了 middleware.read_body capability 的插件才会收到)===
- // 流式响应时 response_body 只给摘要(首次非空 chunk 拼装),完整流式内容
- // 留给未来的 OnStreamChunk(ADR-0002)。
- ResponseBody []byte `protobuf:"bytes,100,opt,name=response_body,json=responseBody,proto3" json:"response_body,omitempty"`
- ResponseHeaders map[string]*HeaderValues `protobuf:"bytes,101,rep,name=response_headers,json=responseHeaders,proto3" json:"response_headers,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
- unknownFields protoimpl.UnknownFields
- sizeCache protoimpl.SizeCache
+type HostInvokeResponse struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` // method 业务状态;传输/鉴权/schema 错误走 gRPC error
+ Payload []byte `protobuf:"bytes,2,opt,name=payload,proto3" json:"payload,omitempty"` // JSON 编码的对象
+ Metadata map[string]string `protobuf:"bytes,3,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` // 调用级辅助信息
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
}
-func (x *MiddlewareEvent) Reset() {
- *x = MiddlewareEvent{}
- mi := &file_plugin_proto_msgTypes[58]
+func (x *HostInvokeResponse) Reset() {
+ *x = HostInvokeResponse{}
+ mi := &file_plugin_proto_msgTypes[48]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
-func (x *MiddlewareEvent) String() string {
+func (x *HostInvokeResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
-func (*MiddlewareEvent) ProtoMessage() {}
+func (*HostInvokeResponse) ProtoMessage() {}
-func (x *MiddlewareEvent) ProtoReflect() protoreflect.Message {
- mi := &file_plugin_proto_msgTypes[58]
+func (x *HostInvokeResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_plugin_proto_msgTypes[48]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -4232,195 +3833,215 @@ func (x *MiddlewareEvent) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
-// Deprecated: Use MiddlewareEvent.ProtoReflect.Descriptor instead.
-func (*MiddlewareEvent) Descriptor() ([]byte, []int) {
- return file_plugin_proto_rawDescGZIP(), []int{58}
+// Deprecated: Use HostInvokeResponse.ProtoReflect.Descriptor instead.
+func (*HostInvokeResponse) Descriptor() ([]byte, []int) {
+ return file_plugin_proto_rawDescGZIP(), []int{48}
}
-func (x *MiddlewareEvent) GetRequestId() string {
+func (x *HostInvokeResponse) GetStatus() string {
if x != nil {
- return x.RequestId
+ return x.Status
}
return ""
}
-func (x *MiddlewareEvent) GetUserId() int64 {
+func (x *HostInvokeResponse) GetPayload() []byte {
if x != nil {
- return x.UserId
+ return x.Payload
}
- return 0
+ return nil
}
-func (x *MiddlewareEvent) GetGroupId() int64 {
+func (x *HostInvokeResponse) GetMetadata() map[string]string {
if x != nil {
- return x.GroupId
+ return x.Metadata
}
- return 0
+ return nil
}
-func (x *MiddlewareEvent) GetAccountId() int64 {
- if x != nil {
- return x.AccountId
- }
- return 0
+type HostStreamFrame struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Method string `protobuf:"bytes,1,opt,name=method,proto3" json:"method,omitempty"` // 首个 client frame 必填;后续 frame 可留空
+ Event string `protobuf:"bytes,2,opt,name=event,proto3" json:"event,omitempty"` // method 内部约定的帧类型
+ Payload []byte `protobuf:"bytes,3,opt,name=payload,proto3" json:"payload,omitempty"` // JSON 编码的对象
+ IdempotencyKey string `protobuf:"bytes,4,opt,name=idempotency_key,json=idempotencyKey,proto3" json:"idempotency_key,omitempty"` // 首个 client frame 使用
+ Metadata map[string]string `protobuf:"bytes,5,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` // 调用级或帧级辅助信息
+ Done bool `protobuf:"varint,6,opt,name=done,proto3" json:"done,omitempty"` // method 业务最终帧;传输结束仍以 stream EOF 为准
+ Status string `protobuf:"bytes,7,opt,name=status,proto3" json:"status,omitempty"` // method 业务状态,通常只在最终帧使用
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
}
-func (x *MiddlewareEvent) GetPlatform() string {
- if x != nil {
- return x.Platform
- }
- return ""
+func (x *HostStreamFrame) Reset() {
+ *x = HostStreamFrame{}
+ mi := &file_plugin_proto_msgTypes[49]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
}
-func (x *MiddlewareEvent) GetModel() string {
- if x != nil {
- return x.Model
- }
- return ""
+func (x *HostStreamFrame) String() string {
+ return protoimpl.X.MessageStringOf(x)
}
-func (x *MiddlewareEvent) GetStream() bool {
+func (*HostStreamFrame) ProtoMessage() {}
+
+func (x *HostStreamFrame) ProtoReflect() protoreflect.Message {
+ mi := &file_plugin_proto_msgTypes[49]
if x != nil {
- return x.Stream
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
}
- return false
+ return mi.MessageOf(x)
}
-func (x *MiddlewareEvent) GetInputTokensEst() int64 {
- if x != nil {
- return x.InputTokensEst
- }
- return 0
+// Deprecated: Use HostStreamFrame.ProtoReflect.Descriptor instead.
+func (*HostStreamFrame) Descriptor() ([]byte, []int) {
+ return file_plugin_proto_rawDescGZIP(), []int{49}
}
-func (x *MiddlewareEvent) GetStatusCode() int64 {
+func (x *HostStreamFrame) GetMethod() string {
if x != nil {
- return x.StatusCode
+ return x.Method
}
- return 0
+ return ""
}
-func (x *MiddlewareEvent) GetDurationMs() int64 {
+func (x *HostStreamFrame) GetEvent() string {
if x != nil {
- return x.DurationMs
+ return x.Event
}
- return 0
+ return ""
}
-func (x *MiddlewareEvent) GetInputTokens() int64 {
+func (x *HostStreamFrame) GetPayload() []byte {
if x != nil {
- return x.InputTokens
+ return x.Payload
}
- return 0
+ return nil
}
-func (x *MiddlewareEvent) GetOutputTokens() int64 {
+func (x *HostStreamFrame) GetIdempotencyKey() string {
if x != nil {
- return x.OutputTokens
+ return x.IdempotencyKey
}
- return 0
+ return ""
}
-func (x *MiddlewareEvent) GetCachedInputTokens() int64 {
+func (x *HostStreamFrame) GetMetadata() map[string]string {
if x != nil {
- return x.CachedInputTokens
+ return x.Metadata
}
- return 0
+ return nil
}
-func (x *MiddlewareEvent) GetFirstTokenMs() int64 {
+func (x *HostStreamFrame) GetDone() bool {
if x != nil {
- return x.FirstTokenMs
+ return x.Done
}
- return 0
+ return false
}
-func (x *MiddlewareEvent) GetErrorKind() string {
+func (x *HostStreamFrame) GetStatus() string {
if x != nil {
- return x.ErrorKind
+ return x.Status
}
return ""
}
-func (x *MiddlewareEvent) GetErrorMsg() string {
- if x != nil {
- return x.ErrorMsg
- }
- return ""
+type ProcessTaskRequest struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ TaskId int64 `protobuf:"varint,1,opt,name=task_id,json=taskId,proto3" json:"task_id,omitempty"`
+ TaskType string `protobuf:"bytes,2,opt,name=task_type,json=taskType,proto3" json:"task_type,omitempty"`
+ Input []byte `protobuf:"bytes,3,opt,name=input,proto3" json:"input,omitempty"` // JSON
+ UserId int64 `protobuf:"varint,4,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
}
-func (x *MiddlewareEvent) GetInputCost() float64 {
- if x != nil {
- return x.InputCost
- }
- return 0
+func (x *ProcessTaskRequest) Reset() {
+ *x = ProcessTaskRequest{}
+ mi := &file_plugin_proto_msgTypes[50]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *ProcessTaskRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
}
-func (x *MiddlewareEvent) GetOutputCost() float64 {
+func (*ProcessTaskRequest) ProtoMessage() {}
+
+func (x *ProcessTaskRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_plugin_proto_msgTypes[50]
if x != nil {
- return x.OutputCost
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
}
- return 0
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use ProcessTaskRequest.ProtoReflect.Descriptor instead.
+func (*ProcessTaskRequest) Descriptor() ([]byte, []int) {
+ return file_plugin_proto_rawDescGZIP(), []int{50}
}
-func (x *MiddlewareEvent) GetCachedInputCost() float64 {
+func (x *ProcessTaskRequest) GetTaskId() int64 {
if x != nil {
- return x.CachedInputCost
+ return x.TaskId
}
return 0
}
-func (x *MiddlewareEvent) GetMetadata() map[string]string {
+func (x *ProcessTaskRequest) GetTaskType() string {
if x != nil {
- return x.Metadata
+ return x.TaskType
}
- return nil
+ return ""
}
-func (x *MiddlewareEvent) GetResponseBody() []byte {
+func (x *ProcessTaskRequest) GetInput() []byte {
if x != nil {
- return x.ResponseBody
+ return x.Input
}
return nil
}
-func (x *MiddlewareEvent) GetResponseHeaders() map[string]*HeaderValues {
+func (x *ProcessTaskRequest) GetUserId() int64 {
if x != nil {
- return x.ResponseHeaders
+ return x.UserId
}
- return nil
+ return 0
}
-// MiddlewareDecision OnForwardBegin 的输出:放行 / 拒绝 / 改请求。
-type MiddlewareDecision struct {
- state protoimpl.MessageState `protogen:"open.v1"`
- Action MiddlewareDecision_Action `protobuf:"varint,1,opt,name=action,proto3,enum=airgate.plugin.v1.MiddlewareDecision_Action" json:"action,omitempty"`
- // action=DENY 时的错误码 / 文案(对用户可见)
- DenyStatusCode int32 `protobuf:"varint,10,opt,name=deny_status_code,json=denyStatusCode,proto3" json:"deny_status_code,omitempty"` // 默认 403 if Action=DENY and 未指定
- DenyMessage string `protobuf:"bytes,11,opt,name=deny_message,json=denyMessage,proto3" json:"deny_message,omitempty"`
- // action=MUTATE 时要追加/覆盖的请求头
- SetHeaders map[string]*HeaderValues `protobuf:"bytes,20,rep,name=set_headers,json=setHeaders,proto3" json:"set_headers,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
- // 贯穿式 metadata:无论 allow/deny/mutate,都能往 bag 里写东西供后续 middleware / End 使用
- Metadata map[string]string `protobuf:"bytes,30,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
+type ProcessTaskResponse struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"`
+ ErrorMessage string `protobuf:"bytes,2,opt,name=error_message,json=errorMessage,proto3" json:"error_message,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
-func (x *MiddlewareDecision) Reset() {
- *x = MiddlewareDecision{}
- mi := &file_plugin_proto_msgTypes[59]
+func (x *ProcessTaskResponse) Reset() {
+ *x = ProcessTaskResponse{}
+ mi := &file_plugin_proto_msgTypes[51]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
-func (x *MiddlewareDecision) String() string {
+func (x *ProcessTaskResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
-func (*MiddlewareDecision) ProtoMessage() {}
+func (*ProcessTaskResponse) ProtoMessage() {}
-func (x *MiddlewareDecision) ProtoReflect() protoreflect.Message {
- mi := &file_plugin_proto_msgTypes[59]
+func (x *ProcessTaskResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_plugin_proto_msgTypes[51]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
@@ -4431,42 +4052,65 @@ func (x *MiddlewareDecision) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
-// Deprecated: Use MiddlewareDecision.ProtoReflect.Descriptor instead.
-func (*MiddlewareDecision) Descriptor() ([]byte, []int) {
- return file_plugin_proto_rawDescGZIP(), []int{59}
+// Deprecated: Use ProcessTaskResponse.ProtoReflect.Descriptor instead.
+func (*ProcessTaskResponse) Descriptor() ([]byte, []int) {
+ return file_plugin_proto_rawDescGZIP(), []int{51}
}
-func (x *MiddlewareDecision) GetAction() MiddlewareDecision_Action {
+func (x *ProcessTaskResponse) GetSuccess() bool {
if x != nil {
- return x.Action
+ return x.Success
}
- return MiddlewareDecision_ALLOW
+ return false
}
-func (x *MiddlewareDecision) GetDenyStatusCode() int32 {
+func (x *ProcessTaskResponse) GetErrorMessage() string {
if x != nil {
- return x.DenyStatusCode
+ return x.ErrorMessage
}
- return 0
+ return ""
}
-func (x *MiddlewareDecision) GetDenyMessage() string {
- if x != nil {
- return x.DenyMessage
- }
- return ""
+type TaskTypesResponse struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Types []string `protobuf:"bytes,1,rep,name=types,proto3" json:"types,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
}
-func (x *MiddlewareDecision) GetSetHeaders() map[string]*HeaderValues {
+func (x *TaskTypesResponse) Reset() {
+ *x = TaskTypesResponse{}
+ mi := &file_plugin_proto_msgTypes[52]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *TaskTypesResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*TaskTypesResponse) ProtoMessage() {}
+
+func (x *TaskTypesResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_plugin_proto_msgTypes[52]
if x != nil {
- return x.SetHeaders
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
}
- return nil
+ return mi.MessageOf(x)
}
-func (x *MiddlewareDecision) GetMetadata() map[string]string {
+// Deprecated: Use TaskTypesResponse.ProtoReflect.Descriptor instead.
+func (*TaskTypesResponse) Descriptor() ([]byte, []int) {
+ return file_plugin_proto_rawDescGZIP(), []int{52}
+}
+
+func (x *TaskTypesResponse) GetTypes() []string {
if x != nil {
- return x.Metadata
+ return x.Types
}
return nil
}
@@ -4475,135 +4119,12 @@ var File_plugin_proto protoreflect.FileDescriptor
const file_plugin_proto_rawDesc = "" +
"\n" +
- "\fplugin.proto\x12\x11airgate.plugin.v1\"\x9a\x01\n" +
- "\x18HostSelectAccountRequest\x12\x19\n" +
- "\bgroup_id\x18\x01 \x01(\x03R\agroupId\x12\x14\n" +
- "\x05model\x18\x02 \x01(\tR\x05model\x12\x1d\n" +
- "\n" +
- "session_id\x18\x03 \x01(\tR\tsessionId\x12.\n" +
- "\x13exclude_account_ids\x18\x04 \x03(\x03R\x11excludeAccountIds\"y\n" +
- "\x19HostSelectAccountResponse\x12\x1d\n" +
- "\n" +
- "account_id\x18\x01 \x01(\x03R\taccountId\x12!\n" +
- "\faccount_name\x18\x02 \x01(\tR\vaccountName\x12\x1a\n" +
- "\bplatform\x18\x03 \x01(\tR\bplatform\"J\n" +
- "\x17HostProbeForwardRequest\x12\x19\n" +
- "\bgroup_id\x18\x01 \x01(\x03R\agroupId\x12\x14\n" +
- "\x05model\x18\x02 \x01(\tR\x05model\"\x81\x02\n" +
- "\x18HostProbeForwardResponse\x12\x18\n" +
- "\asuccess\x18\x01 \x01(\bR\asuccess\x12\x1d\n" +
- "\n" +
- "account_id\x18\x02 \x01(\x03R\taccountId\x12\x1a\n" +
- "\bplatform\x18\x03 \x01(\tR\bplatform\x12\x14\n" +
- "\x05model\x18\x04 \x01(\tR\x05model\x12\x1f\n" +
- "\vstatus_code\x18\x05 \x01(\x03R\n" +
- "statusCode\x12\x1d\n" +
- "\n" +
- "latency_ms\x18\x06 \x01(\x03R\tlatencyMs\x12\x1d\n" +
- "\n" +
- "error_kind\x18\a \x01(\tR\terrorKind\x12\x1b\n" +
- "\terror_msg\x18\b \x01(\tR\berrorMsg\"\x17\n" +
- "\x15HostListGroupsRequest\"\x97\x01\n" +
- "\tHostGroup\x12\x0e\n" +
- "\x02id\x18\x01 \x01(\x03R\x02id\x12\x12\n" +
- "\x04name\x18\x02 \x01(\tR\x04name\x12\x1a\n" +
- "\bplatform\x18\x03 \x01(\tR\bplatform\x12!\n" +
- "\fis_exclusive\x18\x04 \x01(\bR\visExclusive\x12'\n" +
- "\x0frate_multiplier\x18\x05 \x01(\x01R\x0erateMultiplier\"N\n" +
- "\x16HostListGroupsResponse\x124\n" +
- "\x06groups\x18\x01 \x03(\v2\x1c.airgate.plugin.v1.HostGroupR\x06groups\"v\n" +
- "\x1eHostReportAccountResultRequest\x12\x1d\n" +
- "\n" +
- "account_id\x18\x01 \x01(\x03R\taccountId\x12\x18\n" +
- "\asuccess\x18\x02 \x01(\bR\asuccess\x12\x1b\n" +
- "\terror_msg\x18\x03 \x01(\tR\berrorMsg\"\xe1\x02\n" +
- "\x12HostForwardRequest\x12\x17\n" +
- "\auser_id\x18\x01 \x01(\x03R\x06userId\x12\x19\n" +
- "\bgroup_id\x18\x02 \x01(\x03R\agroupId\x12\x14\n" +
- "\x05model\x18\x03 \x01(\tR\x05model\x12\x16\n" +
- "\x06method\x18\x04 \x01(\tR\x06method\x12\x12\n" +
- "\x04path\x18\x05 \x01(\tR\x04path\x12L\n" +
- "\aheaders\x18\x06 \x03(\v22.airgate.plugin.v1.HostForwardRequest.HeadersEntryR\aheaders\x12\x12\n" +
- "\x04body\x18\a \x01(\fR\x04body\x12\x16\n" +
- "\x06stream\x18\b \x01(\bR\x06stream\x1a[\n" +
- "\fHeadersEntry\x12\x10\n" +
- "\x03key\x18\x01 \x01(\tR\x03key\x125\n" +
- "\x05value\x18\x02 \x01(\v2\x1f.airgate.plugin.v1.HeaderValuesR\x05value:\x028\x01\"\xb1\x02\n" +
- "\x13HostForwardResponse\x12\x1f\n" +
- "\vstatus_code\x18\x01 \x01(\x05R\n" +
- "statusCode\x12M\n" +
- "\aheaders\x18\x02 \x03(\v23.airgate.plugin.v1.HostForwardResponse.HeadersEntryR\aheaders\x12\x12\n" +
- "\x04body\x18\x03 \x01(\fR\x04body\x129\n" +
- "\x05usage\x18\x04 \x01(\v2#.airgate.plugin.v1.HostForwardUsageR\x05usage\x1a[\n" +
- "\fHeadersEntry\x12\x10\n" +
- "\x03key\x18\x01 \x01(\tR\x03key\x125\n" +
- "\x05value\x18\x02 \x01(\v2\x1f.airgate.plugin.v1.HeaderValuesR\x05value:\x028\x01\"\xbf\x02\n" +
- "\x10HostForwardChunk\x12\x12\n" +
- "\x04data\x18\x01 \x01(\fR\x04data\x12\x12\n" +
- "\x04done\x18\x02 \x01(\bR\x04done\x12\x1f\n" +
- "\vstatus_code\x18\x03 \x01(\x05R\n" +
- "statusCode\x12J\n" +
- "\aheaders\x18\x04 \x03(\v20.airgate.plugin.v1.HostForwardChunk.HeadersEntryR\aheaders\x129\n" +
- "\x05usage\x18\x05 \x01(\v2#.airgate.plugin.v1.HostForwardUsageR\x05usage\x1a[\n" +
- "\fHeadersEntry\x12\x10\n" +
- "\x03key\x18\x01 \x01(\tR\x03key\x125\n" +
- "\x05value\x18\x02 \x01(\v2\x1f.airgate.plugin.v1.HeaderValuesR\x05value:\x028\x01\"\x84\x01\n" +
- "\x10HostForwardUsage\x12!\n" +
- "\finput_tokens\x18\x01 \x01(\x03R\vinputTokens\x12#\n" +
- "\routput_tokens\x18\x02 \x01(\x03R\foutputTokens\x12\x12\n" +
- "\x04cost\x18\x03 \x01(\x01R\x04cost\x12\x14\n" +
- "\x05model\x18\x04 \x01(\tR\x05model\"\x1a\n" +
- "\x18HostListPlatformsRequest\"E\n" +
- "\fHostPlatform\x12\x12\n" +
- "\x04name\x18\x01 \x01(\tR\x04name\x12!\n" +
- "\fdisplay_name\x18\x02 \x01(\tR\vdisplayName\"Z\n" +
- "\x19HostListPlatformsResponse\x12=\n" +
- "\tplatforms\x18\x01 \x03(\v2\x1f.airgate.plugin.v1.HostPlatformR\tplatforms\"3\n" +
- "\x15HostListModelsRequest\x12\x1a\n" +
- "\bplatform\x18\x01 \x01(\tR\bplatform\"S\n" +
- "\x16HostListModelsResponse\x129\n" +
- "\x06models\x18\x01 \x03(\v2!.airgate.plugin.v1.ModelInfoProtoR\x06models\"1\n" +
- "\x16HostGetUserInfoRequest\x12\x17\n" +
- "\auser_id\x18\x01 \x01(\x03R\x06userId\"\xaa\x01\n" +
- "\x17HostGetUserInfoResponse\x12\x17\n" +
- "\auser_id\x18\x01 \x01(\x03R\x06userId\x12\x1a\n" +
- "\busername\x18\x02 \x01(\tR\busername\x12\x14\n" +
- "\x05email\x18\x03 \x01(\tR\x05email\x12\x12\n" +
- "\x04role\x18\x04 \x01(\tR\x04role\x12\x18\n" +
- "\abalance\x18\x05 \x01(\x01R\abalance\x12\x16\n" +
- "\x06status\x18\x06 \x01(\tR\x06status\"\xa4\x01\n" +
- "\x15HostStoreAssetRequest\x12\x17\n" +
- "\auser_id\x18\x01 \x01(\x03R\x06userId\x12\x14\n" +
- "\x05scope\x18\x02 \x01(\tR\x05scope\x12!\n" +
- "\fcontent_type\x18\x03 \x01(\tR\vcontentType\x12\x12\n" +
- "\x04data\x18\x04 \x01(\fR\x04data\x12%\n" +
- "\x0efile_extension\x18\x05 \x01(\tR\rfileExtension\"\xb3\x01\n" +
- "\x16HostStoreAssetResponse\x12\x19\n" +
- "\basset_id\x18\x01 \x01(\tR\aassetId\x12\x1d\n" +
- "\n" +
- "object_key\x18\x02 \x01(\tR\tobjectKey\x12\x1d\n" +
- "\n" +
- "public_url\x18\x03 \x01(\tR\tpublicUrl\x12\x1d\n" +
- "\n" +
- "size_bytes\x18\x04 \x01(\x03R\tsizeBytes\x12!\n" +
- "\fcontent_type\x18\x05 \x01(\tR\vcontentType\"7\n" +
- "\x16HostGetAssetURLRequest\x12\x1d\n" +
- "\n" +
- "object_key\x18\x01 \x01(\tR\tobjectKey\"8\n" +
- "\x17HostGetAssetURLResponse\x12\x1d\n" +
- "\n" +
- "public_url\x18\x01 \x01(\tR\tpublicUrl\"9\n" +
- "\x18HostGetAssetBytesRequest\x12\x1d\n" +
- "\n" +
- "object_key\x18\x01 \x01(\tR\tobjectKey\"R\n" +
- "\x19HostGetAssetBytesResponse\x12\x12\n" +
- "\x04data\x18\x01 \x01(\fR\x04data\x12!\n" +
- "\fcontent_type\x18\x02 \x01(\tR\vcontentType\"\a\n" +
+ "\fplugin.proto\x12\x11airgate.plugin.v2\"\a\n" +
"\x05Empty\"&\n" +
"\x0eStringResponse\x12\x14\n" +
"\x05value\x18\x01 \x01(\tR\x05value\"&\n" +
"\fHeaderValues\x12\x16\n" +
- "\x06values\x18\x01 \x03(\tR\x06values\"\x8a\x05\n" +
+ "\x06values\x18\x01 \x03(\tR\x06values\"\x98\x06\n" +
"\x12PluginInfoResponse\x12\x0e\n" +
"\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n" +
"\x04name\x18\x02 \x01(\tR\x04name\x12\x18\n" +
@@ -4611,17 +4132,21 @@ const file_plugin_proto_rawDesc = "" +
"\vdescription\x18\x04 \x01(\tR\vdescription\x12\x16\n" +
"\x06author\x18\x05 \x01(\tR\x06author\x12\x12\n" +
"\x04type\x18\x06 \x01(\tR\x04type\x12H\n" +
- "\raccount_types\x18\a \x03(\v2#.airgate.plugin.v1.AccountTypeProtoR\faccountTypes\x12K\n" +
- "\x0efrontend_pages\x18\b \x03(\v2$.airgate.plugin.v1.FrontendPageProtoR\rfrontendPages\x12Q\n" +
- "\x10frontend_widgets\x18\t \x03(\v2&.airgate.plugin.v1.FrontendWidgetProtoR\x0ffrontendWidgets\x12\x1f\n" +
+ "\raccount_types\x18\a \x03(\v2#.airgate.plugin.v2.AccountTypeProtoR\faccountTypes\x12K\n" +
+ "\x0efrontend_pages\x18\b \x03(\v2$.airgate.plugin.v2.FrontendPageProtoR\rfrontendPages\x12Q\n" +
+ "\x10frontend_widgets\x18\t \x03(\v2&.airgate.plugin.v2.FrontendWidgetProtoR\x0ffrontendWidgets\x12\x1f\n" +
"\vsdk_version\x18\n" +
" \x01(\tR\n" +
"sdkVersion\x12\"\n" +
"\fdependencies\x18\v \x03(\tR\fdependencies\x12H\n" +
- "\rconfig_schema\x18\f \x03(\v2#.airgate.plugin.v1.ConfigFieldProtoR\fconfigSchema\x12/\n" +
+ "\rconfig_schema\x18\f \x03(\v2#.airgate.plugin.v2.ConfigFieldProtoR\fconfigSchema\x12/\n" +
"\x13instruction_presets\x18\r \x03(\tR\x12instructionPresets\x12\"\n" +
"\fcapabilities\x18\x0e \x03(\tR\fcapabilities\x12\x1a\n" +
- "\bpriority\x18\x0f \x01(\x05R\bpriority\"\xd3\x01\n" +
+ "\bpriority\x18\x0f \x01(\x05R\bpriority\x12O\n" +
+ "\bmetadata\x18\x10 \x03(\v23.airgate.plugin.v2.PluginInfoResponse.MetadataEntryR\bmetadata\x1a;\n" +
+ "\rMetadataEntry\x12\x10\n" +
+ "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
+ "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xd3\x01\n" +
"\x10ConfigFieldProto\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
"\x05label\x18\x02 \x01(\tR\x05label\x12\x12\n" +
@@ -4634,7 +4159,7 @@ const file_plugin_proto_rawDesc = "" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
"\x05label\x18\x02 \x01(\tR\x05label\x12 \n" +
"\vdescription\x18\x03 \x01(\tR\vdescription\x12?\n" +
- "\x06fields\x18\x04 \x03(\v2'.airgate.plugin.v1.CredentialFieldProtoR\x06fields\"\xb5\x01\n" +
+ "\x06fields\x18\x04 \x03(\v2'.airgate.plugin.v2.CredentialFieldProtoR\x06fields\"\xb5\x01\n" +
"\x14CredentialFieldProto\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
"\x05label\x18\x02 \x01(\tR\x05label\x12\x12\n" +
@@ -4652,37 +4177,36 @@ const file_plugin_proto_rawDesc = "" +
"\x04slot\x18\x01 \x01(\tR\x04slot\x12\x1d\n" +
"\n" +
"entry_file\x18\x02 \x01(\tR\tentryFile\x12\x14\n" +
- "\x05title\x18\x03 \x01(\tR\x05title\"\xcf\x01\n" +
+ "\x05title\x18\x03 \x01(\tR\x05title\"\xdc\x01\n" +
"\vInitRequest\x12B\n" +
- "\x06config\x18\x01 \x03(\v2*.airgate.plugin.v1.InitRequest.ConfigEntryR\x06config\x12\x1b\n" +
- "\tlog_level\x18\x02 \x01(\tR\blogLevel\x12$\n" +
- "\x0ehost_broker_id\x18\x03 \x01(\rR\fhostBrokerId\x1a9\n" +
+ "\x06config\x18\x01 \x03(\v2*.airgate.plugin.v2.InitRequest.ConfigEntryR\x06config\x12\x1b\n" +
+ "\tlog_level\x18\x02 \x01(\tR\blogLevel\x121\n" +
+ "\x15core_invoke_broker_id\x18\x03 \x01(\rR\x12coreInvokeBrokerId\x1a9\n" +
"\vConfigEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
- "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\x8d\x04\n" +
+ "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xb5\x02\n" +
"\x0eModelInfoProto\x12\x0e\n" +
"\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n" +
- "\x04name\x18\x02 \x01(\tR\x04name\x12\x1f\n" +
- "\vinput_price\x18\x04 \x01(\x01R\n" +
- "inputPrice\x12!\n" +
- "\foutput_price\x18\x05 \x01(\x01R\voutputPrice\x12,\n" +
- "\x12cached_input_price\x18\x06 \x01(\x01R\x10cachedInputPrice\x120\n" +
- "\x14input_price_priority\x18\a \x01(\x01R\x12inputPricePriority\x122\n" +
- "\x15output_price_priority\x18\b \x01(\x01R\x13outputPricePriority\x12=\n" +
- "\x1bcached_input_price_priority\x18\t \x01(\x01R\x18cachedInputPricePriority\x12%\n" +
- "\x0econtext_window\x18\n" +
- " \x01(\x03R\rcontextWindow\x12*\n" +
- "\x11max_output_tokens\x18\v \x01(\x03R\x0fmaxOutputTokens\x120\n" +
- "\x14cache_creation_price\x18\f \x01(\x01R\x12cacheCreationPrice\x125\n" +
- "\x17cache_creation_1h_price\x18\r \x01(\x01R\x14cacheCreation1hPriceJ\x04\b\x03\x10\x04\"K\n" +
+ "\x04name\x18\x02 \x01(\tR\x04name\x12%\n" +
+ "\x0econtext_window\x18\x03 \x01(\x03R\rcontextWindow\x12*\n" +
+ "\x11max_output_tokens\x18\x04 \x01(\x03R\x0fmaxOutputTokens\x12\"\n" +
+ "\fcapabilities\x18\x05 \x03(\tR\fcapabilities\x12K\n" +
+ "\bmetadata\x18\x06 \x03(\v2/.airgate.plugin.v2.ModelInfoProto.MetadataEntryR\bmetadata\x1a;\n" +
+ "\rMetadataEntry\x12\x10\n" +
+ "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
+ "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"K\n" +
"\x0eModelsResponse\x129\n" +
- "\x06models\x18\x01 \x03(\v2!.airgate.plugin.v1.ModelInfoProtoR\x06models\"d\n" +
+ "\x06models\x18\x01 \x03(\v2!.airgate.plugin.v2.ModelInfoProtoR\x06models\"\xf4\x01\n" +
"\x14RouteDefinitionProto\x12\x16\n" +
"\x06method\x18\x01 \x01(\tR\x06method\x12\x12\n" +
"\x04path\x18\x02 \x01(\tR\x04path\x12 \n" +
- "\vdescription\x18\x03 \x01(\tR\vdescription\"Q\n" +
+ "\vdescription\x18\x03 \x01(\tR\vdescription\x12Q\n" +
+ "\bmetadata\x18\x04 \x03(\v25.airgate.plugin.v2.RouteDefinitionProto.MetadataEntryR\bmetadata\x1a;\n" +
+ "\rMetadataEntry\x12\x10\n" +
+ "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
+ "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"Q\n" +
"\x0eRoutesResponse\x12?\n" +
- "\x06routes\x18\x01 \x03(\v2'.airgate.plugin.v1.RouteDefinitionProtoR\x06routes\"\xaa\x01\n" +
+ "\x06routes\x18\x01 \x03(\v2'.airgate.plugin.v2.RouteDefinitionProtoR\x06routes\"\xaa\x01\n" +
"\fAccountProto\x12\x0e\n" +
"\x02id\x18\x01 \x01(\x03R\x02id\x12\x12\n" +
"\x04name\x18\x02 \x01(\tR\x04name\x12\x1a\n" +
@@ -4692,131 +4216,143 @@ const file_plugin_proto_rawDesc = "" +
"\tproxy_url\x18\x06 \x01(\tR\bproxyUrl\"\xd8\x02\n" +
"\x0eForwardRequest\x12\x12\n" +
"\x04body\x18\a \x01(\fR\x04body\x12H\n" +
- "\aheaders\x18\b \x03(\v2..airgate.plugin.v1.ForwardRequest.HeadersEntryR\aheaders\x12\x14\n" +
+ "\aheaders\x18\b \x03(\v2..airgate.plugin.v2.ForwardRequest.HeadersEntryR\aheaders\x12\x14\n" +
"\x05model\x18\t \x01(\tR\x05model\x12\x16\n" +
"\x06stream\x18\n" +
" \x01(\bR\x06stream\x129\n" +
- "\aaccount\x18\v \x01(\v2\x1f.airgate.plugin.v1.AccountProtoR\aaccount\x1a[\n" +
+ "\aaccount\x18\v \x01(\v2\x1f.airgate.plugin.v2.AccountProtoR\aaccount\x1a[\n" +
"\fHeadersEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x125\n" +
- "\x05value\x18\x02 \x01(\v2\x1f.airgate.plugin.v1.HeaderValuesR\x05value:\x028\x01J\x04\b\x01\x10\x02J\x04\b\x02\x10\x03J\x04\b\x03\x10\x04J\x04\b\x04\x10\x05J\x04\b\x05\x10\x06J\x04\b\x06\x10\a\"\xf0\x01\n" +
+ "\x05value\x18\x02 \x01(\v2\x1f.airgate.plugin.v2.HeaderValuesR\x05value:\x028\x01J\x04\b\x01\x10\x02J\x04\b\x02\x10\x03J\x04\b\x03\x10\x04J\x04\b\x04\x10\x05J\x04\b\x05\x10\x06J\x04\b\x06\x10\a\"\xf0\x01\n" +
"\x10UpstreamResponse\x12\x1f\n" +
"\vstatus_code\x18\x01 \x01(\x05R\n" +
"statusCode\x12J\n" +
- "\aheaders\x18\x02 \x03(\v20.airgate.plugin.v1.UpstreamResponse.HeadersEntryR\aheaders\x12\x12\n" +
+ "\aheaders\x18\x02 \x03(\v20.airgate.plugin.v2.UpstreamResponse.HeadersEntryR\aheaders\x12\x12\n" +
"\x04body\x18\x03 \x01(\fR\x04body\x1a[\n" +
"\fHeadersEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x125\n" +
- "\x05value\x18\x02 \x01(\v2\x1f.airgate.plugin.v1.HeaderValuesR\x05value:\x028\x01\"\xd2\x06\n" +
- "\x05Usage\x12!\n" +
- "\finput_tokens\x18\x01 \x01(\x03R\vinputTokens\x12#\n" +
- "\routput_tokens\x18\x02 \x01(\x03R\foutputTokens\x12.\n" +
- "\x13cached_input_tokens\x18\x03 \x01(\x03R\x11cachedInputTokens\x122\n" +
- "\x15cache_creation_tokens\x18\x04 \x01(\x03R\x13cacheCreationTokens\x127\n" +
- "\x18cache_creation_5m_tokens\x18\x05 \x01(\x03R\x15cacheCreation5mTokens\x127\n" +
- "\x18cache_creation_1h_tokens\x18\x06 \x01(\x03R\x15cacheCreation1hTokens\x126\n" +
- "\x17reasoning_output_tokens\x18\a \x01(\x03R\x15reasoningOutputTokens\x12\x1d\n" +
- "\n" +
- "input_cost\x18\n" +
- " \x01(\x01R\tinputCost\x12\x1f\n" +
- "\voutput_cost\x18\v \x01(\x01R\n" +
- "outputCost\x12*\n" +
- "\x11cached_input_cost\x18\f \x01(\x01R\x0fcachedInputCost\x12.\n" +
- "\x13cache_creation_cost\x18\r \x01(\x01R\x11cacheCreationCost\x12\x1f\n" +
- "\vinput_price\x18\x14 \x01(\x01R\n" +
- "inputPrice\x12!\n" +
- "\foutput_price\x18\x15 \x01(\x01R\voutputPrice\x12,\n" +
- "\x12cached_input_price\x18\x16 \x01(\x01R\x10cachedInputPrice\x120\n" +
- "\x14cache_creation_price\x18\x17 \x01(\x01R\x12cacheCreationPrice\x125\n" +
- "\x17cache_creation_1h_price\x18\x18 \x01(\x01R\x14cacheCreation1hPrice\x12\x14\n" +
- "\x05model\x18\x1e \x01(\tR\x05model\x12!\n" +
- "\fservice_tier\x18\x1f \x01(\tR\vserviceTier\x12$\n" +
- "\x0efirst_token_ms\x18 \x01(\x03R\ffirstTokenMs\x12\x1d\n" +
+ "\x05value\x18\x02 \x01(\v2\x1f.airgate.plugin.v2.HeaderValuesR\x05value:\x028\x01\"\xec\x01\n" +
+ "\x0eUsageAttribute\x12\x10\n" +
+ "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
+ "\x05label\x18\x02 \x01(\tR\x05label\x12\x12\n" +
+ "\x04kind\x18\x03 \x01(\tR\x04kind\x12\x14\n" +
+ "\x05value\x18\x04 \x01(\tR\x05value\x12K\n" +
+ "\bmetadata\x18\x05 \x03(\v2/.airgate.plugin.v2.UsageAttribute.MetadataEntryR\bmetadata\x1a;\n" +
+ "\rMetadataEntry\x12\x10\n" +
+ "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
+ "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xb9\x02\n" +
+ "\vUsageMetric\x12\x10\n" +
+ "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
+ "\x05label\x18\x02 \x01(\tR\x05label\x12\x12\n" +
+ "\x04kind\x18\x03 \x01(\tR\x04kind\x12\x12\n" +
+ "\x04unit\x18\x04 \x01(\tR\x04unit\x12\x14\n" +
+ "\x05value\x18\x05 \x01(\x01R\x05value\x12!\n" +
+ "\faccount_cost\x18\x06 \x01(\x01R\vaccountCost\x12\x1a\n" +
+ "\bcurrency\x18\a \x01(\tR\bcurrency\x12H\n" +
+ "\bmetadata\x18\b \x03(\v2,.airgate.plugin.v2.UsageMetric.MetadataEntryR\bmetadata\x1a;\n" +
+ "\rMetadataEntry\x12\x10\n" +
+ "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
+ "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xcf\x02\n" +
+ "\x0fUsageCostDetail\x12\x10\n" +
+ "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
+ "\x05label\x18\x02 \x01(\tR\x05label\x12!\n" +
+ "\faccount_cost\x18\x03 \x01(\x01R\vaccountCost\x12\x1b\n" +
+ "\tuser_cost\x18\x04 \x01(\x01R\buserCost\x12-\n" +
+ "\x12billing_multiplier\x18\x05 \x01(\x01R\x11billingMultiplier\x12\x1a\n" +
+ "\bcurrency\x18\x06 \x01(\tR\bcurrency\x12L\n" +
+ "\bmetadata\x18\a \x03(\v20.airgate.plugin.v2.UsageCostDetail.MetadataEntryR\bmetadata\x1a;\n" +
+ "\rMetadataEntry\x12\x10\n" +
+ "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
+ "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xad\x04\n" +
+ "\x05Usage\x12\x14\n" +
+ "\x05model\x18\x01 \x01(\tR\x05model\x12!\n" +
+ "\faccount_cost\x18\x02 \x01(\x01R\vaccountCost\x12\x1b\n" +
+ "\tuser_cost\x18\x03 \x01(\x01R\buserCost\x12-\n" +
+ "\x12billing_multiplier\x18\x04 \x01(\x01R\x11billingMultiplier\x12\x1a\n" +
+ "\bcurrency\x18\x05 \x01(\tR\bcurrency\x12\x18\n" +
+ "\asummary\x18\x06 \x01(\tR\asummary\x12$\n" +
+ "\x0efirst_token_ms\x18\a \x01(\x03R\ffirstTokenMs\x128\n" +
+ "\ametrics\x18\b \x03(\v2\x1e.airgate.plugin.v2.UsageMetricR\ametrics\x12A\n" +
"\n" +
- "image_size\x18! \x01(\tR\timageSize\"\xc7\x03\n" +
+ "attributes\x18\t \x03(\v2!.airgate.plugin.v2.UsageAttributeR\n" +
+ "attributes\x12E\n" +
+ "\fcost_details\x18\n" +
+ " \x03(\v2\".airgate.plugin.v2.UsageCostDetailR\vcostDetails\x12B\n" +
+ "\bmetadata\x18\v \x03(\v2&.airgate.plugin.v2.Usage.MetadataEntryR\bmetadata\x1a;\n" +
+ "\rMetadataEntry\x12\x10\n" +
+ "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
+ "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xc7\x03\n" +
"\x0eForwardOutcome\x122\n" +
- "\x04kind\x18\x01 \x01(\x0e2\x1e.airgate.plugin.v1.OutcomeKindR\x04kind\x12?\n" +
- "\bupstream\x18\x02 \x01(\v2#.airgate.plugin.v1.UpstreamResponseR\bupstream\x12.\n" +
- "\x05usage\x18\x03 \x01(\v2\x18.airgate.plugin.v1.UsageR\x05usage\x12\x1f\n" +
+ "\x04kind\x18\x01 \x01(\x0e2\x1e.airgate.plugin.v2.OutcomeKindR\x04kind\x12?\n" +
+ "\bupstream\x18\x02 \x01(\v2#.airgate.plugin.v2.UpstreamResponseR\bupstream\x12.\n" +
+ "\x05usage\x18\x03 \x01(\v2\x18.airgate.plugin.v2.UsageR\x05usage\x12\x1f\n" +
"\vduration_ms\x18\x04 \x01(\x03R\n" +
"durationMs\x12$\n" +
"\x0eretry_after_ms\x18\x05 \x01(\x03R\fretryAfterMs\x12\x16\n" +
"\x06reason\x18\x06 \x01(\tR\x06reason\x12j\n" +
- "\x13updated_credentials\x18\a \x03(\v29.airgate.plugin.v1.ForwardOutcome.UpdatedCredentialsEntryR\x12updatedCredentials\x1aE\n" +
+ "\x13updated_credentials\x18\a \x03(\v29.airgate.plugin.v2.ForwardOutcome.UpdatedCredentialsEntryR\x12updatedCredentials\x1aE\n" +
"\x17UpdatedCredentialsEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xc4\x02\n" +
"\fForwardChunk\x12\x12\n" +
"\x04data\x18\x01 \x01(\fR\x04data\x12\x12\n" +
"\x04done\x18\x02 \x01(\bR\x04done\x12F\n" +
- "\rfinal_outcome\x18\x03 \x01(\v2!.airgate.plugin.v1.ForwardOutcomeR\ffinalOutcome\x12\x1f\n" +
+ "\rfinal_outcome\x18\x03 \x01(\v2!.airgate.plugin.v2.ForwardOutcomeR\ffinalOutcome\x12\x1f\n" +
"\vstatus_code\x18\x04 \x01(\x05R\n" +
"statusCode\x12F\n" +
- "\aheaders\x18\x05 \x03(\v2,.airgate.plugin.v1.ForwardChunk.HeadersEntryR\aheaders\x1a[\n" +
+ "\aheaders\x18\x05 \x03(\v2,.airgate.plugin.v2.ForwardChunk.HeadersEntryR\aheaders\x1a[\n" +
"\fHeadersEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x125\n" +
- "\x05value\x18\x02 \x01(\v2\x1f.airgate.plugin.v1.HeaderValuesR\x05value:\x028\x01\"\xae\x01\n" +
+ "\x05value\x18\x02 \x01(\v2\x1f.airgate.plugin.v2.HeaderValuesR\x05value:\x028\x01\"\xae\x01\n" +
"\x12CredentialsRequest\x12X\n" +
- "\vcredentials\x18\x01 \x03(\v26.airgate.plugin.v1.CredentialsRequest.CredentialsEntryR\vcredentials\x1a>\n" +
+ "\vcredentials\x18\x01 \x03(\v26.airgate.plugin.v2.CredentialsRequest.CredentialsEntryR\vcredentials\x1a>\n" +
"\x10CredentialsEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
- "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\x97\x02\n" +
- "\x11QuotaInfoResponse\x12\x14\n" +
- "\x05total\x18\x01 \x01(\x01R\x05total\x12\x12\n" +
- "\x04used\x18\x02 \x01(\x01R\x04used\x12\x1c\n" +
- "\tremaining\x18\x03 \x01(\x01R\tremaining\x12\x1a\n" +
- "\bcurrency\x18\x04 \x01(\tR\bcurrency\x12\x1d\n" +
- "\n" +
- "expires_at\x18\x05 \x01(\tR\texpiresAt\x12E\n" +
- "\x05extra\x18\x06 \x03(\v2/.airgate.plugin.v1.QuotaInfoResponse.ExtraEntryR\x05extra\x1a8\n" +
- "\n" +
- "ExtraEntry\x12\x10\n" +
- "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xa8\x02\n" +
"\vHttpRequest\x12\x16\n" +
"\x06method\x18\x01 \x01(\tR\x06method\x12\x12\n" +
"\x04path\x18\x02 \x01(\tR\x04path\x12\x14\n" +
"\x05query\x18\x03 \x01(\tR\x05query\x12E\n" +
- "\aheaders\x18\x04 \x03(\v2+.airgate.plugin.v1.HttpRequest.HeadersEntryR\aheaders\x12\x12\n" +
+ "\aheaders\x18\x04 \x03(\v2+.airgate.plugin.v2.HttpRequest.HeadersEntryR\aheaders\x12\x12\n" +
"\x04body\x18\x05 \x01(\fR\x04body\x12\x1f\n" +
"\vremote_addr\x18\x06 \x01(\tR\n" +
"remoteAddr\x1a[\n" +
"\fHeadersEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x125\n" +
- "\x05value\x18\x02 \x01(\v2\x1f.airgate.plugin.v1.HeaderValuesR\x05value:\x028\x01\"\xe8\x01\n" +
+ "\x05value\x18\x02 \x01(\v2\x1f.airgate.plugin.v2.HeaderValuesR\x05value:\x028\x01\"\xe8\x01\n" +
"\fHttpResponse\x12\x1f\n" +
"\vstatus_code\x18\x01 \x01(\x05R\n" +
"statusCode\x12F\n" +
- "\aheaders\x18\x02 \x03(\v2,.airgate.plugin.v1.HttpResponse.HeadersEntryR\aheaders\x12\x12\n" +
+ "\aheaders\x18\x02 \x03(\v2,.airgate.plugin.v2.HttpResponse.HeadersEntryR\aheaders\x12\x12\n" +
"\x04body\x18\x03 \x01(\fR\x04body\x1a[\n" +
"\fHeadersEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x125\n" +
- "\x05value\x18\x02 \x01(\v2\x1f.airgate.plugin.v1.HeaderValuesR\x05value:\x028\x01\"\x86\x02\n" +
+ "\x05value\x18\x02 \x01(\v2\x1f.airgate.plugin.v2.HeaderValuesR\x05value:\x028\x01\"\x86\x02\n" +
"\x11HttpResponseChunk\x12\x12\n" +
"\x04data\x18\x01 \x01(\fR\x04data\x12\x12\n" +
"\x04done\x18\x02 \x01(\bR\x04done\x12\x1f\n" +
"\vstatus_code\x18\x03 \x01(\x05R\n" +
"statusCode\x12K\n" +
- "\aheaders\x18\x04 \x03(\v21.airgate.plugin.v1.HttpResponseChunk.HeadersEntryR\aheaders\x1a[\n" +
+ "\aheaders\x18\x04 \x03(\v21.airgate.plugin.v2.HttpResponseChunk.HeadersEntryR\aheaders\x1a[\n" +
"\fHeadersEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x125\n" +
- "\x05value\x18\x02 \x01(\v2\x1f.airgate.plugin.v1.HeaderValuesR\x05value:\x028\x01\"J\n" +
+ "\x05value\x18\x02 \x01(\v2\x1f.airgate.plugin.v2.HeaderValuesR\x05value:\x028\x01\"J\n" +
"\x13BackgroundTaskProto\x12\x12\n" +
"\x04name\x18\x01 \x01(\tR\x04name\x12\x1f\n" +
"\vinterval_ms\x18\x02 \x01(\x03R\n" +
"intervalMs\"W\n" +
"\x17BackgroundTasksResponse\x12<\n" +
- "\x05tasks\x18\x01 \x03(\v2&.airgate.plugin.v1.BackgroundTaskProtoR\x05tasks\".\n" +
+ "\x05tasks\x18\x01 \x03(\v2&.airgate.plugin.v2.BackgroundTaskProtoR\x05tasks\".\n" +
"\x18RunBackgroundTaskRequest\x12\x12\n" +
"\x04name\x18\x01 \x01(\tR\x04name\"\xf7\x02\n" +
"\x0eWebSocketFrame\x12?\n" +
- "\x04type\x18\x01 \x01(\x0e2+.airgate.plugin.v1.WebSocketFrame.FrameTypeR\x04type\x12\x12\n" +
+ "\x04type\x18\x01 \x01(\x0e2+.airgate.plugin.v2.WebSocketFrame.FrameTypeR\x04type\x12\x12\n" +
"\x04data\x18\x02 \x01(\fR\x04data\x12J\n" +
- "\fconnect_info\x18\x03 \x01(\v2'.airgate.plugin.v1.WebSocketConnectInfoR\vconnectInfo\x12\x1d\n" +
+ "\fconnect_info\x18\x03 \x01(\v2'.airgate.plugin.v2.WebSocketConnectInfoR\vconnectInfo\x12\x1d\n" +
"\n" +
"close_code\x18\x04 \x01(\x05R\tcloseCode\x12!\n" +
"\fclose_reason\x18\x05 \x01(\tR\vcloseReason\x12;\n" +
- "\aoutcome\x18\x06 \x01(\v2!.airgate.plugin.v1.ForwardOutcomeR\aoutcome\"E\n" +
+ "\aoutcome\x18\x06 \x01(\v2!.airgate.plugin.v2.ForwardOutcomeR\aoutcome\"E\n" +
"\tFrameType\x12\v\n" +
"\aCONNECT\x10\x00\x12\b\n" +
"\x04TEXT\x10\x01\x12\n" +
@@ -4828,23 +4364,77 @@ const file_plugin_proto_rawDesc = "" +
"\x14WebSocketConnectInfo\x12\x12\n" +
"\x04path\x18\x01 \x01(\tR\x04path\x12\x14\n" +
"\x05query\x18\x02 \x01(\tR\x05query\x12N\n" +
- "\aheaders\x18\x03 \x03(\v24.airgate.plugin.v1.WebSocketConnectInfo.HeadersEntryR\aheaders\x12\x1f\n" +
+ "\aheaders\x18\x03 \x03(\v24.airgate.plugin.v2.WebSocketConnectInfo.HeadersEntryR\aheaders\x12\x1f\n" +
"\vremote_addr\x18\x04 \x01(\tR\n" +
"remoteAddr\x12#\n" +
"\rconnection_id\x18\x05 \x01(\tR\fconnectionId\x129\n" +
- "\aaccount\x18\f \x01(\v2\x1f.airgate.plugin.v1.AccountProtoR\aaccount\x1a[\n" +
+ "\aaccount\x18\f \x01(\v2\x1f.airgate.plugin.v2.AccountProtoR\aaccount\x1a[\n" +
"\fHeadersEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x125\n" +
- "\x05value\x18\x02 \x01(\v2\x1f.airgate.plugin.v1.HeaderValuesR\x05value:\x028\x01J\x04\b\x06\x10\aJ\x04\b\a\x10\bJ\x04\b\b\x10\tJ\x04\b\t\x10\n" +
+ "\x05value\x18\x02 \x01(\v2\x1f.airgate.plugin.v2.HeaderValuesR\x05value:\x028\x01J\x04\b\x06\x10\aJ\x04\b\a\x10\bJ\x04\b\b\x10\tJ\x04\b\t\x10\n" +
"J\x04\b\n" +
"\x10\vJ\x04\b\v\x10\f\"<\n" +
"\fWebAssetFile\x12\x12\n" +
"\x04path\x18\x01 \x01(\tR\x04path\x12\x18\n" +
"\acontent\x18\x02 \x01(\fR\acontent\"i\n" +
"\x11WebAssetsResponse\x125\n" +
- "\x05files\x18\x01 \x03(\v2\x1f.airgate.plugin.v1.WebAssetFileR\x05files\x12\x1d\n" +
+ "\x05files\x18\x01 \x03(\v2\x1f.airgate.plugin.v2.WebAssetFileR\x05files\x12\x1d\n" +
"\n" +
- "has_assets\x18\x02 \x01(\bR\thasAssets\"\xf0\x04\n" +
+ "has_assets\x18\x02 \x01(\bR\thasAssets\"\xf7\x01\n" +
+ "\x12PayloadSchemaProto\x12!\n" +
+ "\fcontent_type\x18\x01 \x01(\tR\vcontentType\x12\x16\n" +
+ "\x06schema\x18\x02 \x01(\tR\x06schema\x12\x18\n" +
+ "\aexample\x18\x03 \x01(\tR\aexample\x12O\n" +
+ "\bmetadata\x18\x04 \x03(\v23.airgate.plugin.v2.PayloadSchemaProto.MetadataEntryR\bmetadata\x1a;\n" +
+ "\rMetadataEntry\x12\x10\n" +
+ "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
+ "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xe8\x02\n" +
+ "\x10RouteSchemaProto\x12\x16\n" +
+ "\x06method\x18\x01 \x01(\tR\x06method\x12\x12\n" +
+ "\x04path\x18\x02 \x01(\tR\x04path\x12\x18\n" +
+ "\asummary\x18\x03 \x01(\tR\asummary\x12?\n" +
+ "\arequest\x18\x04 \x01(\v2%.airgate.plugin.v2.PayloadSchemaProtoR\arequest\x12A\n" +
+ "\bresponse\x18\x05 \x01(\v2%.airgate.plugin.v2.PayloadSchemaProtoR\bresponse\x12M\n" +
+ "\bmetadata\x18\x06 \x03(\v21.airgate.plugin.v2.RouteSchemaProto.MetadataEntryR\bmetadata\x1a;\n" +
+ "\rMetadataEntry\x12\x10\n" +
+ "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
+ "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xc6\x02\n" +
+ "\x0fTaskSchemaProto\x12\x12\n" +
+ "\x04type\x18\x01 \x01(\tR\x04type\x12\x18\n" +
+ "\asummary\x18\x02 \x01(\tR\asummary\x12;\n" +
+ "\x05input\x18\x03 \x01(\v2%.airgate.plugin.v2.PayloadSchemaProtoR\x05input\x12=\n" +
+ "\x06output\x18\x04 \x01(\v2%.airgate.plugin.v2.PayloadSchemaProtoR\x06output\x12L\n" +
+ "\bmetadata\x18\x05 \x03(\v20.airgate.plugin.v2.TaskSchemaProto.MetadataEntryR\bmetadata\x1a;\n" +
+ "\rMetadataEntry\x12\x10\n" +
+ "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
+ "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xa5\x02\n" +
+ "\x10EventSchemaProto\x12\x12\n" +
+ "\x04type\x18\x01 \x01(\tR\x04type\x12\x16\n" +
+ "\x06source\x18\x02 \x01(\tR\x06source\x12\x18\n" +
+ "\asummary\x18\x03 \x01(\tR\asummary\x12?\n" +
+ "\apayload\x18\x04 \x01(\v2%.airgate.plugin.v2.PayloadSchemaProtoR\apayload\x12M\n" +
+ "\bmetadata\x18\x05 \x03(\v21.airgate.plugin.v2.EventSchemaProto.MetadataEntryR\bmetadata\x1a;\n" +
+ "\rMetadataEntry\x12\x10\n" +
+ "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
+ "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xd6\x02\n" +
+ "\x11InvokeSchemaProto\x12\x16\n" +
+ "\x06method\x18\x01 \x01(\tR\x06method\x12\x18\n" +
+ "\asummary\x18\x02 \x01(\tR\asummary\x12?\n" +
+ "\arequest\x18\x03 \x01(\v2%.airgate.plugin.v2.PayloadSchemaProtoR\arequest\x12A\n" +
+ "\bresponse\x18\x04 \x01(\v2%.airgate.plugin.v2.PayloadSchemaProtoR\bresponse\x12N\n" +
+ "\bmetadata\x18\x05 \x03(\v22.airgate.plugin.v2.InvokeSchemaProto.MetadataEntryR\bmetadata\x1a;\n" +
+ "\rMetadataEntry\x12\x10\n" +
+ "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
+ "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\x9a\x03\n" +
+ "\x14PluginSchemaResponse\x12;\n" +
+ "\x06routes\x18\x01 \x03(\v2#.airgate.plugin.v2.RouteSchemaProtoR\x06routes\x128\n" +
+ "\x05tasks\x18\x02 \x03(\v2\".airgate.plugin.v2.TaskSchemaProtoR\x05tasks\x12;\n" +
+ "\x06events\x18\x03 \x03(\v2#.airgate.plugin.v2.EventSchemaProtoR\x06events\x12>\n" +
+ "\ainvokes\x18\x04 \x03(\v2$.airgate.plugin.v2.InvokeSchemaProtoR\ainvokes\x12Q\n" +
+ "\bmetadata\x18\x05 \x03(\v25.airgate.plugin.v2.PluginSchemaResponse.MetadataEntryR\bmetadata\x1a;\n" +
+ "\rMetadataEntry\x12\x10\n" +
+ "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
+ "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\x84\x05\n" +
"\x11MiddlewareRequest\x12\x1d\n" +
"\n" +
"request_id\x18\x01 \x01(\tR\trequestId\x12\x17\n" +
@@ -4854,17 +4444,17 @@ const file_plugin_proto_rawDesc = "" +
"account_id\x18\x04 \x01(\x03R\taccountId\x12\x1a\n" +
"\bplatform\x18\x05 \x01(\tR\bplatform\x12\x14\n" +
"\x05model\x18\x06 \x01(\tR\x05model\x12\x16\n" +
- "\x06stream\x18\a \x01(\bR\x06stream\x12(\n" +
- "\x10input_tokens_est\x18\b \x01(\x03R\x0einputTokensEst\x12N\n" +
- "\bmetadata\x18\t \x03(\v22.airgate.plugin.v1.MiddlewareRequest.MetadataEntryR\bmetadata\x12!\n" +
+ "\x06stream\x18\a \x01(\bR\x06stream\x12<\n" +
+ "\testimates\x18\b \x03(\v2\x1e.airgate.plugin.v2.UsageMetricR\testimates\x12N\n" +
+ "\bmetadata\x18\t \x03(\v22.airgate.plugin.v2.MiddlewareRequest.MetadataEntryR\bmetadata\x12!\n" +
"\frequest_body\x18d \x01(\fR\vrequestBody\x12a\n" +
- "\x0frequest_headers\x18e \x03(\v28.airgate.plugin.v1.MiddlewareRequest.RequestHeadersEntryR\x0erequestHeaders\x1a;\n" +
+ "\x0frequest_headers\x18e \x03(\v28.airgate.plugin.v2.MiddlewareRequest.RequestHeadersEntryR\x0erequestHeaders\x1a;\n" +
"\rMetadataEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\x1ab\n" +
"\x13RequestHeadersEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x125\n" +
- "\x05value\x18\x02 \x01(\v2\x1f.airgate.plugin.v1.HeaderValuesR\x05value:\x028\x01\"\xf8\a\n" +
+ "\x05value\x18\x02 \x01(\v2\x1f.airgate.plugin.v2.HeaderValuesR\x05value:\x028\x01\"\xb2\x06\n" +
"\x0fMiddlewareEvent\x12\x1d\n" +
"\n" +
"request_id\x18\x01 \x01(\tR\trequestId\x12\x17\n" +
@@ -4874,44 +4464,36 @@ const file_plugin_proto_rawDesc = "" +
"account_id\x18\x04 \x01(\x03R\taccountId\x12\x1a\n" +
"\bplatform\x18\x05 \x01(\tR\bplatform\x12\x14\n" +
"\x05model\x18\x06 \x01(\tR\x05model\x12\x16\n" +
- "\x06stream\x18\a \x01(\bR\x06stream\x12(\n" +
- "\x10input_tokens_est\x18\b \x01(\x03R\x0einputTokensEst\x12\x1f\n" +
+ "\x06stream\x18\a \x01(\bR\x06stream\x12<\n" +
+ "\testimates\x18\b \x03(\v2\x1e.airgate.plugin.v2.UsageMetricR\testimates\x12\x1f\n" +
"\vstatus_code\x18\x14 \x01(\x03R\n" +
"statusCode\x12\x1f\n" +
"\vduration_ms\x18\x15 \x01(\x03R\n" +
- "durationMs\x12!\n" +
- "\finput_tokens\x18\x16 \x01(\x03R\vinputTokens\x12#\n" +
- "\routput_tokens\x18\x17 \x01(\x03R\foutputTokens\x12.\n" +
- "\x13cached_input_tokens\x18\x18 \x01(\x03R\x11cachedInputTokens\x12$\n" +
- "\x0efirst_token_ms\x18\x19 \x01(\x03R\ffirstTokenMs\x12\x1d\n" +
- "\n" +
- "error_kind\x18\x1a \x01(\tR\terrorKind\x12\x1b\n" +
- "\terror_msg\x18\x1b \x01(\tR\berrorMsg\x12\x1d\n" +
+ "durationMs\x12.\n" +
+ "\x05usage\x18\x16 \x01(\v2\x18.airgate.plugin.v2.UsageR\x05usage\x12\x1d\n" +
"\n" +
- "input_cost\x18\x1e \x01(\x01R\tinputCost\x12\x1f\n" +
- "\voutput_cost\x18\x1f \x01(\x01R\n" +
- "outputCost\x12*\n" +
- "\x11cached_input_cost\x18 \x01(\x01R\x0fcachedInputCost\x12L\n" +
- "\bmetadata\x18( \x03(\v20.airgate.plugin.v1.MiddlewareEvent.MetadataEntryR\bmetadata\x12#\n" +
+ "error_kind\x18\x17 \x01(\tR\terrorKind\x12\x1b\n" +
+ "\terror_msg\x18\x18 \x01(\tR\berrorMsg\x12L\n" +
+ "\bmetadata\x18( \x03(\v20.airgate.plugin.v2.MiddlewareEvent.MetadataEntryR\bmetadata\x12#\n" +
"\rresponse_body\x18d \x01(\fR\fresponseBody\x12b\n" +
- "\x10response_headers\x18e \x03(\v27.airgate.plugin.v1.MiddlewareEvent.ResponseHeadersEntryR\x0fresponseHeaders\x1a;\n" +
+ "\x10response_headers\x18e \x03(\v27.airgate.plugin.v2.MiddlewareEvent.ResponseHeadersEntryR\x0fresponseHeaders\x1a;\n" +
"\rMetadataEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\x1ac\n" +
"\x14ResponseHeadersEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x125\n" +
- "\x05value\x18\x02 \x01(\v2\x1f.airgate.plugin.v1.HeaderValuesR\x05value:\x028\x01\"\x98\x04\n" +
+ "\x05value\x18\x02 \x01(\v2\x1f.airgate.plugin.v2.HeaderValuesR\x05value:\x028\x01\"\x98\x04\n" +
"\x12MiddlewareDecision\x12D\n" +
- "\x06action\x18\x01 \x01(\x0e2,.airgate.plugin.v1.MiddlewareDecision.ActionR\x06action\x12(\n" +
+ "\x06action\x18\x01 \x01(\x0e2,.airgate.plugin.v2.MiddlewareDecision.ActionR\x06action\x12(\n" +
"\x10deny_status_code\x18\n" +
" \x01(\x05R\x0edenyStatusCode\x12!\n" +
"\fdeny_message\x18\v \x01(\tR\vdenyMessage\x12V\n" +
- "\vset_headers\x18\x14 \x03(\v25.airgate.plugin.v1.MiddlewareDecision.SetHeadersEntryR\n" +
+ "\vset_headers\x18\x14 \x03(\v25.airgate.plugin.v2.MiddlewareDecision.SetHeadersEntryR\n" +
"setHeaders\x12O\n" +
- "\bmetadata\x18\x1e \x03(\v23.airgate.plugin.v1.MiddlewareDecision.MetadataEntryR\bmetadata\x1a^\n" +
+ "\bmetadata\x18\x1e \x03(\v23.airgate.plugin.v2.MiddlewareDecision.MetadataEntryR\bmetadata\x1a^\n" +
"\x0fSetHeadersEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x125\n" +
- "\x05value\x18\x02 \x01(\v2\x1f.airgate.plugin.v1.HeaderValuesR\x05value:\x028\x01\x1a;\n" +
+ "\x05value\x18\x02 \x01(\v2\x1f.airgate.plugin.v2.HeaderValuesR\x05value:\x028\x01\x1a;\n" +
"\rMetadataEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\")\n" +
@@ -4919,7 +4501,73 @@ const file_plugin_proto_rawDesc = "" +
"\x05ALLOW\x10\x00\x12\b\n" +
"\x04DENY\x10\x01\x12\n" +
"\n" +
- "\x06MUTATE\x10\x02*\xc9\x01\n" +
+ "\x06MUTATE\x10\x02\"\xe0\x02\n" +
+ "\x16EventSubscriptionProto\x12\x12\n" +
+ "\x04type\x18\x01 \x01(\tR\x04type\x12\x16\n" +
+ "\x06source\x18\x02 \x01(\tR\x06source\x12M\n" +
+ "\x06filter\x18\x03 \x03(\v25.airgate.plugin.v2.EventSubscriptionProto.FilterEntryR\x06filter\x12S\n" +
+ "\bmetadata\x18\x04 \x03(\v27.airgate.plugin.v2.EventSubscriptionProto.MetadataEntryR\bmetadata\x1a9\n" +
+ "\vFilterEntry\x12\x10\n" +
+ "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
+ "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\x1a;\n" +
+ "\rMetadataEntry\x12\x10\n" +
+ "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
+ "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"m\n" +
+ "\x1aEventSubscriptionsResponse\x12O\n" +
+ "\rsubscriptions\x18\x01 \x03(\v2).airgate.plugin.v2.EventSubscriptionProtoR\rsubscriptions\"\xd9\x02\n" +
+ "\vPluginEvent\x12\x0e\n" +
+ "\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n" +
+ "\x04type\x18\x02 \x01(\tR\x04type\x12\x16\n" +
+ "\x06source\x18\x03 \x01(\tR\x06source\x12\x18\n" +
+ "\asubject\x18\x04 \x01(\tR\asubject\x12\x17\n" +
+ "\auser_id\x18\x05 \x01(\x03R\x06userId\x12\x19\n" +
+ "\bgroup_id\x18\x06 \x01(\x03R\agroupId\x12\x18\n" +
+ "\apayload\x18\a \x01(\fR\apayload\x12H\n" +
+ "\bmetadata\x18\b \x03(\v2,.airgate.plugin.v2.PluginEvent.MetadataEntryR\bmetadata\x12\x1f\n" +
+ "\voccurred_at\x18\t \x01(\x03R\n" +
+ "occurredAt\x1a;\n" +
+ "\rMetadataEntry\x12\x10\n" +
+ "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
+ "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"T\n" +
+ "\x13EventHandleResponse\x12\x18\n" +
+ "\asuccess\x18\x01 \x01(\bR\asuccess\x12#\n" +
+ "\rerror_message\x18\x02 \x01(\tR\ferrorMessage\"\xfb\x01\n" +
+ "\x11HostInvokeRequest\x12\x16\n" +
+ "\x06method\x18\x01 \x01(\tR\x06method\x12\x18\n" +
+ "\apayload\x18\x02 \x01(\fR\apayload\x12'\n" +
+ "\x0fidempotency_key\x18\x03 \x01(\tR\x0eidempotencyKey\x12N\n" +
+ "\bmetadata\x18\x04 \x03(\v22.airgate.plugin.v2.HostInvokeRequest.MetadataEntryR\bmetadata\x1a;\n" +
+ "\rMetadataEntry\x12\x10\n" +
+ "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
+ "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xd4\x01\n" +
+ "\x12HostInvokeResponse\x12\x16\n" +
+ "\x06status\x18\x01 \x01(\tR\x06status\x12\x18\n" +
+ "\apayload\x18\x02 \x01(\fR\apayload\x12O\n" +
+ "\bmetadata\x18\x03 \x03(\v23.airgate.plugin.v2.HostInvokeResponse.MetadataEntryR\bmetadata\x1a;\n" +
+ "\rMetadataEntry\x12\x10\n" +
+ "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
+ "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xb9\x02\n" +
+ "\x0fHostStreamFrame\x12\x16\n" +
+ "\x06method\x18\x01 \x01(\tR\x06method\x12\x14\n" +
+ "\x05event\x18\x02 \x01(\tR\x05event\x12\x18\n" +
+ "\apayload\x18\x03 \x01(\fR\apayload\x12'\n" +
+ "\x0fidempotency_key\x18\x04 \x01(\tR\x0eidempotencyKey\x12L\n" +
+ "\bmetadata\x18\x05 \x03(\v20.airgate.plugin.v2.HostStreamFrame.MetadataEntryR\bmetadata\x12\x12\n" +
+ "\x04done\x18\x06 \x01(\bR\x04done\x12\x16\n" +
+ "\x06status\x18\a \x01(\tR\x06status\x1a;\n" +
+ "\rMetadataEntry\x12\x10\n" +
+ "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
+ "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"y\n" +
+ "\x12ProcessTaskRequest\x12\x17\n" +
+ "\atask_id\x18\x01 \x01(\x03R\x06taskId\x12\x1b\n" +
+ "\ttask_type\x18\x02 \x01(\tR\btaskType\x12\x14\n" +
+ "\x05input\x18\x03 \x01(\fR\x05input\x12\x17\n" +
+ "\auser_id\x18\x04 \x01(\x03R\x06userId\"T\n" +
+ "\x13ProcessTaskResponse\x12\x18\n" +
+ "\asuccess\x18\x01 \x01(\bR\asuccess\x12#\n" +
+ "\rerror_message\x18\x02 \x01(\tR\ferrorMessage\")\n" +
+ "\x11TaskTypesResponse\x12\x14\n" +
+ "\x05types\x18\x01 \x03(\tR\x05types*\xc9\x01\n" +
"\vOutcomeKind\x12\x13\n" +
"\x0fOUTCOME_UNKNOWN\x10\x00\x12\x13\n" +
"\x0fOUTCOME_SUCCESS\x10\x01\x12\x18\n" +
@@ -4927,50 +4575,41 @@ const file_plugin_proto_rawDesc = "" +
"\x1cOUTCOME_ACCOUNT_RATE_LIMITED\x10\x03\x12\x18\n" +
"\x14OUTCOME_ACCOUNT_DEAD\x10\x04\x12\x1e\n" +
"\x1aOUTCOME_UPSTREAM_TRANSIENT\x10\x05\x12\x1a\n" +
- "\x16OUTCOME_STREAM_ABORTED\x10\x062\xfb\x03\n" +
+ "\x16OUTCOME_STREAM_ABORTED\x10\x062\xcb\x04\n" +
"\rPluginService\x12J\n" +
- "\aGetInfo\x12\x18.airgate.plugin.v1.Empty\x1a%.airgate.plugin.v1.PluginInfoResponse\x12@\n" +
- "\x04Init\x12\x1e.airgate.plugin.v1.InitRequest\x1a\x18.airgate.plugin.v1.Empty\x12;\n" +
- "\x05Start\x12\x18.airgate.plugin.v1.Empty\x1a\x18.airgate.plugin.v1.Empty\x12:\n" +
- "\x04Stop\x12\x18.airgate.plugin.v1.Empty\x1a\x18.airgate.plugin.v1.Empty\x12N\n" +
- "\fGetWebAssets\x12\x18.airgate.plugin.v1.Empty\x1a$.airgate.plugin.v1.WebAssetsResponse\x12A\n" +
- "\vHealthCheck\x12\x18.airgate.plugin.v1.Empty\x1a\x18.airgate.plugin.v1.Empty\x12P\n" +
- "\rHandleRequest\x12\x1e.airgate.plugin.v1.HttpRequest\x1a\x1f.airgate.plugin.v1.HttpResponse2\xa4\x05\n" +
+ "\aGetInfo\x12\x18.airgate.plugin.v2.Empty\x1a%.airgate.plugin.v2.PluginInfoResponse\x12@\n" +
+ "\x04Init\x12\x1e.airgate.plugin.v2.InitRequest\x1a\x18.airgate.plugin.v2.Empty\x12;\n" +
+ "\x05Start\x12\x18.airgate.plugin.v2.Empty\x1a\x18.airgate.plugin.v2.Empty\x12:\n" +
+ "\x04Stop\x12\x18.airgate.plugin.v2.Empty\x1a\x18.airgate.plugin.v2.Empty\x12N\n" +
+ "\fGetWebAssets\x12\x18.airgate.plugin.v2.Empty\x1a$.airgate.plugin.v2.WebAssetsResponse\x12N\n" +
+ "\tGetSchema\x12\x18.airgate.plugin.v2.Empty\x1a'.airgate.plugin.v2.PluginSchemaResponse\x12A\n" +
+ "\vHealthCheck\x12\x18.airgate.plugin.v2.Empty\x1a\x18.airgate.plugin.v2.Empty\x12P\n" +
+ "\rHandleRequest\x12\x1e.airgate.plugin.v2.HttpRequest\x1a\x1f.airgate.plugin.v2.HttpResponse2\xc9\x04\n" +
"\x0eGatewayService\x12J\n" +
- "\vGetPlatform\x12\x18.airgate.plugin.v1.Empty\x1a!.airgate.plugin.v1.StringResponse\x12H\n" +
- "\tGetModels\x12\x18.airgate.plugin.v1.Empty\x1a!.airgate.plugin.v1.ModelsResponse\x12H\n" +
- "\tGetRoutes\x12\x18.airgate.plugin.v1.Empty\x1a!.airgate.plugin.v1.RoutesResponse\x12O\n" +
- "\aForward\x12!.airgate.plugin.v1.ForwardRequest\x1a!.airgate.plugin.v1.ForwardOutcome\x12U\n" +
- "\rForwardStream\x12!.airgate.plugin.v1.ForwardRequest\x1a\x1f.airgate.plugin.v1.ForwardChunk0\x01\x12R\n" +
- "\x0fValidateAccount\x12%.airgate.plugin.v1.CredentialsRequest\x1a\x18.airgate.plugin.v1.Empty\x12Y\n" +
- "\n" +
- "QueryQuota\x12%.airgate.plugin.v1.CredentialsRequest\x1a$.airgate.plugin.v1.QuotaInfoResponse\x12[\n" +
- "\x0fHandleWebSocket\x12!.airgate.plugin.v1.WebSocketFrame\x1a!.airgate.plugin.v1.WebSocketFrame(\x010\x012\xba\x03\n" +
+ "\vGetPlatform\x12\x18.airgate.plugin.v2.Empty\x1a!.airgate.plugin.v2.StringResponse\x12H\n" +
+ "\tGetModels\x12\x18.airgate.plugin.v2.Empty\x1a!.airgate.plugin.v2.ModelsResponse\x12H\n" +
+ "\tGetRoutes\x12\x18.airgate.plugin.v2.Empty\x1a!.airgate.plugin.v2.RoutesResponse\x12O\n" +
+ "\aForward\x12!.airgate.plugin.v2.ForwardRequest\x1a!.airgate.plugin.v2.ForwardOutcome\x12U\n" +
+ "\rForwardStream\x12!.airgate.plugin.v2.ForwardRequest\x1a\x1f.airgate.plugin.v2.ForwardChunk0\x01\x12R\n" +
+ "\x0fValidateAccount\x12%.airgate.plugin.v2.CredentialsRequest\x1a\x18.airgate.plugin.v2.Empty\x12[\n" +
+ "\x0fHandleWebSocket\x12!.airgate.plugin.v2.WebSocketFrame\x1a!.airgate.plugin.v2.WebSocketFrame(\x010\x012\xe8\x04\n" +
"\x10ExtensionService\x12=\n" +
- "\aMigrate\x12\x18.airgate.plugin.v1.Empty\x1a\x18.airgate.plugin.v1.Empty\x12Z\n" +
- "\x12GetBackgroundTasks\x12\x18.airgate.plugin.v1.Empty\x1a*.airgate.plugin.v1.BackgroundTasksResponse\x12Z\n" +
- "\x11RunBackgroundTask\x12+.airgate.plugin.v1.RunBackgroundTaskRequest\x1a\x18.airgate.plugin.v1.Empty\x12P\n" +
- "\rHandleRequest\x12\x1e.airgate.plugin.v1.HttpRequest\x1a\x1f.airgate.plugin.v1.HttpResponse\x12]\n" +
- "\x13HandleStreamRequest\x12\x1e.airgate.plugin.v1.HttpRequest\x1a$.airgate.plugin.v1.HttpResponseChunk0\x012\xc0\x01\n" +
+ "\aMigrate\x12\x18.airgate.plugin.v2.Empty\x1a\x18.airgate.plugin.v2.Empty\x12Z\n" +
+ "\x12GetBackgroundTasks\x12\x18.airgate.plugin.v2.Empty\x1a*.airgate.plugin.v2.BackgroundTasksResponse\x12Z\n" +
+ "\x11RunBackgroundTask\x12+.airgate.plugin.v2.RunBackgroundTaskRequest\x1a\x18.airgate.plugin.v2.Empty\x12P\n" +
+ "\rHandleRequest\x12\x1e.airgate.plugin.v2.HttpRequest\x1a\x1f.airgate.plugin.v2.HttpResponse\x12]\n" +
+ "\x13HandleStreamRequest\x12\x1e.airgate.plugin.v2.HttpRequest\x1a$.airgate.plugin.v2.HttpResponseChunk0\x01\x12\\\n" +
+ "\vProcessTask\x12%.airgate.plugin.v2.ProcessTaskRequest\x1a&.airgate.plugin.v2.ProcessTaskResponse\x12N\n" +
+ "\fGetTaskTypes\x12\x18.airgate.plugin.v2.Empty\x1a$.airgate.plugin.v2.TaskTypesResponse2\xc0\x01\n" +
"\x11MiddlewareService\x12]\n" +
- "\x0eOnForwardBegin\x12$.airgate.plugin.v1.MiddlewareRequest\x1a%.airgate.plugin.v1.MiddlewareDecision\x12L\n" +
- "\fOnForwardEnd\x12\".airgate.plugin.v1.MiddlewareEvent\x1a\x18.airgate.plugin.v1.Empty2\xcc\t\n" +
- "\vHostService\x12j\n" +
- "\rSelectAccount\x12+.airgate.plugin.v1.HostSelectAccountRequest\x1a,.airgate.plugin.v1.HostSelectAccountResponse\x12b\n" +
- "\x13ReportAccountResult\x121.airgate.plugin.v1.HostReportAccountResultRequest\x1a\x18.airgate.plugin.v1.Empty\x12g\n" +
- "\fProbeForward\x12*.airgate.plugin.v1.HostProbeForwardRequest\x1a+.airgate.plugin.v1.HostProbeForwardResponse\x12X\n" +
- "\aForward\x12%.airgate.plugin.v1.HostForwardRequest\x1a&.airgate.plugin.v1.HostForwardResponse\x12]\n" +
- "\rForwardStream\x12%.airgate.plugin.v1.HostForwardRequest\x1a#.airgate.plugin.v1.HostForwardChunk0\x01\x12a\n" +
- "\n" +
- "ListGroups\x12(.airgate.plugin.v1.HostListGroupsRequest\x1a).airgate.plugin.v1.HostListGroupsResponse\x12j\n" +
- "\rListPlatforms\x12+.airgate.plugin.v1.HostListPlatformsRequest\x1a,.airgate.plugin.v1.HostListPlatformsResponse\x12a\n" +
- "\n" +
- "ListModels\x12(.airgate.plugin.v1.HostListModelsRequest\x1a).airgate.plugin.v1.HostListModelsResponse\x12d\n" +
- "\vGetUserInfo\x12).airgate.plugin.v1.HostGetUserInfoRequest\x1a*.airgate.plugin.v1.HostGetUserInfoResponse\x12a\n" +
- "\n" +
- "StoreAsset\x12(.airgate.plugin.v1.HostStoreAssetRequest\x1a).airgate.plugin.v1.HostStoreAssetResponse\x12d\n" +
- "\vGetAssetURL\x12).airgate.plugin.v1.HostGetAssetURLRequest\x1a*.airgate.plugin.v1.HostGetAssetURLResponse\x12j\n" +
- "\rGetAssetBytes\x12+.airgate.plugin.v1.HostGetAssetBytesRequest\x1a,.airgate.plugin.v1.HostGetAssetBytesResponseB+Z)github.com/DouDOU-start/airgate-sdk/protob\x06proto3"
+ "\x0eOnForwardBegin\x12$.airgate.plugin.v2.MiddlewareRequest\x1a%.airgate.plugin.v2.MiddlewareDecision\x12L\n" +
+ "\fOnForwardEnd\x12\".airgate.plugin.v2.MiddlewareEvent\x1a\x18.airgate.plugin.v2.Empty2\xc7\x01\n" +
+ "\fEventService\x12`\n" +
+ "\x15GetEventSubscriptions\x12\x18.airgate.plugin.v2.Empty\x1a-.airgate.plugin.v2.EventSubscriptionsResponse\x12U\n" +
+ "\vHandleEvent\x12\x1e.airgate.plugin.v2.PluginEvent\x1a&.airgate.plugin.v2.EventHandleResponse2\xc6\x01\n" +
+ "\x11CoreInvokeService\x12U\n" +
+ "\x06Invoke\x12$.airgate.plugin.v2.HostInvokeRequest\x1a%.airgate.plugin.v2.HostInvokeResponse\x12Z\n" +
+ "\fInvokeStream\x12\".airgate.plugin.v2.HostStreamFrame\x1a\".airgate.plugin.v2.HostStreamFrame(\x010\x01B4Z2github.com/DouDOU-start/airgate-sdk/protocol/protob\x06proto3"
var (
file_plugin_proto_rawDescOnce sync.Once
@@ -4985,223 +4624,244 @@ func file_plugin_proto_rawDescGZIP() []byte {
}
var file_plugin_proto_enumTypes = make([]protoimpl.EnumInfo, 3)
-var file_plugin_proto_msgTypes = make([]protoimpl.MessageInfo, 80)
+var file_plugin_proto_msgTypes = make([]protoimpl.MessageInfo, 88)
var file_plugin_proto_goTypes = []any{
- (OutcomeKind)(0), // 0: airgate.plugin.v1.OutcomeKind
- (WebSocketFrame_FrameType)(0), // 1: airgate.plugin.v1.WebSocketFrame.FrameType
- (MiddlewareDecision_Action)(0), // 2: airgate.plugin.v1.MiddlewareDecision.Action
- (*HostSelectAccountRequest)(nil), // 3: airgate.plugin.v1.HostSelectAccountRequest
- (*HostSelectAccountResponse)(nil), // 4: airgate.plugin.v1.HostSelectAccountResponse
- (*HostProbeForwardRequest)(nil), // 5: airgate.plugin.v1.HostProbeForwardRequest
- (*HostProbeForwardResponse)(nil), // 6: airgate.plugin.v1.HostProbeForwardResponse
- (*HostListGroupsRequest)(nil), // 7: airgate.plugin.v1.HostListGroupsRequest
- (*HostGroup)(nil), // 8: airgate.plugin.v1.HostGroup
- (*HostListGroupsResponse)(nil), // 9: airgate.plugin.v1.HostListGroupsResponse
- (*HostReportAccountResultRequest)(nil), // 10: airgate.plugin.v1.HostReportAccountResultRequest
- (*HostForwardRequest)(nil), // 11: airgate.plugin.v1.HostForwardRequest
- (*HostForwardResponse)(nil), // 12: airgate.plugin.v1.HostForwardResponse
- (*HostForwardChunk)(nil), // 13: airgate.plugin.v1.HostForwardChunk
- (*HostForwardUsage)(nil), // 14: airgate.plugin.v1.HostForwardUsage
- (*HostListPlatformsRequest)(nil), // 15: airgate.plugin.v1.HostListPlatformsRequest
- (*HostPlatform)(nil), // 16: airgate.plugin.v1.HostPlatform
- (*HostListPlatformsResponse)(nil), // 17: airgate.plugin.v1.HostListPlatformsResponse
- (*HostListModelsRequest)(nil), // 18: airgate.plugin.v1.HostListModelsRequest
- (*HostListModelsResponse)(nil), // 19: airgate.plugin.v1.HostListModelsResponse
- (*HostGetUserInfoRequest)(nil), // 20: airgate.plugin.v1.HostGetUserInfoRequest
- (*HostGetUserInfoResponse)(nil), // 21: airgate.plugin.v1.HostGetUserInfoResponse
- (*HostStoreAssetRequest)(nil), // 22: airgate.plugin.v1.HostStoreAssetRequest
- (*HostStoreAssetResponse)(nil), // 23: airgate.plugin.v1.HostStoreAssetResponse
- (*HostGetAssetURLRequest)(nil), // 24: airgate.plugin.v1.HostGetAssetURLRequest
- (*HostGetAssetURLResponse)(nil), // 25: airgate.plugin.v1.HostGetAssetURLResponse
- (*HostGetAssetBytesRequest)(nil), // 26: airgate.plugin.v1.HostGetAssetBytesRequest
- (*HostGetAssetBytesResponse)(nil), // 27: airgate.plugin.v1.HostGetAssetBytesResponse
- (*Empty)(nil), // 28: airgate.plugin.v1.Empty
- (*StringResponse)(nil), // 29: airgate.plugin.v1.StringResponse
- (*HeaderValues)(nil), // 30: airgate.plugin.v1.HeaderValues
- (*PluginInfoResponse)(nil), // 31: airgate.plugin.v1.PluginInfoResponse
- (*ConfigFieldProto)(nil), // 32: airgate.plugin.v1.ConfigFieldProto
- (*AccountTypeProto)(nil), // 33: airgate.plugin.v1.AccountTypeProto
- (*CredentialFieldProto)(nil), // 34: airgate.plugin.v1.CredentialFieldProto
- (*FrontendPageProto)(nil), // 35: airgate.plugin.v1.FrontendPageProto
- (*FrontendWidgetProto)(nil), // 36: airgate.plugin.v1.FrontendWidgetProto
- (*InitRequest)(nil), // 37: airgate.plugin.v1.InitRequest
- (*ModelInfoProto)(nil), // 38: airgate.plugin.v1.ModelInfoProto
- (*ModelsResponse)(nil), // 39: airgate.plugin.v1.ModelsResponse
- (*RouteDefinitionProto)(nil), // 40: airgate.plugin.v1.RouteDefinitionProto
- (*RoutesResponse)(nil), // 41: airgate.plugin.v1.RoutesResponse
- (*AccountProto)(nil), // 42: airgate.plugin.v1.AccountProto
- (*ForwardRequest)(nil), // 43: airgate.plugin.v1.ForwardRequest
- (*UpstreamResponse)(nil), // 44: airgate.plugin.v1.UpstreamResponse
- (*Usage)(nil), // 45: airgate.plugin.v1.Usage
- (*ForwardOutcome)(nil), // 46: airgate.plugin.v1.ForwardOutcome
- (*ForwardChunk)(nil), // 47: airgate.plugin.v1.ForwardChunk
- (*CredentialsRequest)(nil), // 48: airgate.plugin.v1.CredentialsRequest
- (*QuotaInfoResponse)(nil), // 49: airgate.plugin.v1.QuotaInfoResponse
- (*HttpRequest)(nil), // 50: airgate.plugin.v1.HttpRequest
- (*HttpResponse)(nil), // 51: airgate.plugin.v1.HttpResponse
- (*HttpResponseChunk)(nil), // 52: airgate.plugin.v1.HttpResponseChunk
- (*BackgroundTaskProto)(nil), // 53: airgate.plugin.v1.BackgroundTaskProto
- (*BackgroundTasksResponse)(nil), // 54: airgate.plugin.v1.BackgroundTasksResponse
- (*RunBackgroundTaskRequest)(nil), // 55: airgate.plugin.v1.RunBackgroundTaskRequest
- (*WebSocketFrame)(nil), // 56: airgate.plugin.v1.WebSocketFrame
- (*WebSocketConnectInfo)(nil), // 57: airgate.plugin.v1.WebSocketConnectInfo
- (*WebAssetFile)(nil), // 58: airgate.plugin.v1.WebAssetFile
- (*WebAssetsResponse)(nil), // 59: airgate.plugin.v1.WebAssetsResponse
- (*MiddlewareRequest)(nil), // 60: airgate.plugin.v1.MiddlewareRequest
- (*MiddlewareEvent)(nil), // 61: airgate.plugin.v1.MiddlewareEvent
- (*MiddlewareDecision)(nil), // 62: airgate.plugin.v1.MiddlewareDecision
- nil, // 63: airgate.plugin.v1.HostForwardRequest.HeadersEntry
- nil, // 64: airgate.plugin.v1.HostForwardResponse.HeadersEntry
- nil, // 65: airgate.plugin.v1.HostForwardChunk.HeadersEntry
- nil, // 66: airgate.plugin.v1.InitRequest.ConfigEntry
- nil, // 67: airgate.plugin.v1.ForwardRequest.HeadersEntry
- nil, // 68: airgate.plugin.v1.UpstreamResponse.HeadersEntry
- nil, // 69: airgate.plugin.v1.ForwardOutcome.UpdatedCredentialsEntry
- nil, // 70: airgate.plugin.v1.ForwardChunk.HeadersEntry
- nil, // 71: airgate.plugin.v1.CredentialsRequest.CredentialsEntry
- nil, // 72: airgate.plugin.v1.QuotaInfoResponse.ExtraEntry
- nil, // 73: airgate.plugin.v1.HttpRequest.HeadersEntry
- nil, // 74: airgate.plugin.v1.HttpResponse.HeadersEntry
- nil, // 75: airgate.plugin.v1.HttpResponseChunk.HeadersEntry
- nil, // 76: airgate.plugin.v1.WebSocketConnectInfo.HeadersEntry
- nil, // 77: airgate.plugin.v1.MiddlewareRequest.MetadataEntry
- nil, // 78: airgate.plugin.v1.MiddlewareRequest.RequestHeadersEntry
- nil, // 79: airgate.plugin.v1.MiddlewareEvent.MetadataEntry
- nil, // 80: airgate.plugin.v1.MiddlewareEvent.ResponseHeadersEntry
- nil, // 81: airgate.plugin.v1.MiddlewareDecision.SetHeadersEntry
- nil, // 82: airgate.plugin.v1.MiddlewareDecision.MetadataEntry
+ (OutcomeKind)(0), // 0: airgate.plugin.v2.OutcomeKind
+ (WebSocketFrame_FrameType)(0), // 1: airgate.plugin.v2.WebSocketFrame.FrameType
+ (MiddlewareDecision_Action)(0), // 2: airgate.plugin.v2.MiddlewareDecision.Action
+ (*Empty)(nil), // 3: airgate.plugin.v2.Empty
+ (*StringResponse)(nil), // 4: airgate.plugin.v2.StringResponse
+ (*HeaderValues)(nil), // 5: airgate.plugin.v2.HeaderValues
+ (*PluginInfoResponse)(nil), // 6: airgate.plugin.v2.PluginInfoResponse
+ (*ConfigFieldProto)(nil), // 7: airgate.plugin.v2.ConfigFieldProto
+ (*AccountTypeProto)(nil), // 8: airgate.plugin.v2.AccountTypeProto
+ (*CredentialFieldProto)(nil), // 9: airgate.plugin.v2.CredentialFieldProto
+ (*FrontendPageProto)(nil), // 10: airgate.plugin.v2.FrontendPageProto
+ (*FrontendWidgetProto)(nil), // 11: airgate.plugin.v2.FrontendWidgetProto
+ (*InitRequest)(nil), // 12: airgate.plugin.v2.InitRequest
+ (*ModelInfoProto)(nil), // 13: airgate.plugin.v2.ModelInfoProto
+ (*ModelsResponse)(nil), // 14: airgate.plugin.v2.ModelsResponse
+ (*RouteDefinitionProto)(nil), // 15: airgate.plugin.v2.RouteDefinitionProto
+ (*RoutesResponse)(nil), // 16: airgate.plugin.v2.RoutesResponse
+ (*AccountProto)(nil), // 17: airgate.plugin.v2.AccountProto
+ (*ForwardRequest)(nil), // 18: airgate.plugin.v2.ForwardRequest
+ (*UpstreamResponse)(nil), // 19: airgate.plugin.v2.UpstreamResponse
+ (*UsageAttribute)(nil), // 20: airgate.plugin.v2.UsageAttribute
+ (*UsageMetric)(nil), // 21: airgate.plugin.v2.UsageMetric
+ (*UsageCostDetail)(nil), // 22: airgate.plugin.v2.UsageCostDetail
+ (*Usage)(nil), // 23: airgate.plugin.v2.Usage
+ (*ForwardOutcome)(nil), // 24: airgate.plugin.v2.ForwardOutcome
+ (*ForwardChunk)(nil), // 25: airgate.plugin.v2.ForwardChunk
+ (*CredentialsRequest)(nil), // 26: airgate.plugin.v2.CredentialsRequest
+ (*HttpRequest)(nil), // 27: airgate.plugin.v2.HttpRequest
+ (*HttpResponse)(nil), // 28: airgate.plugin.v2.HttpResponse
+ (*HttpResponseChunk)(nil), // 29: airgate.plugin.v2.HttpResponseChunk
+ (*BackgroundTaskProto)(nil), // 30: airgate.plugin.v2.BackgroundTaskProto
+ (*BackgroundTasksResponse)(nil), // 31: airgate.plugin.v2.BackgroundTasksResponse
+ (*RunBackgroundTaskRequest)(nil), // 32: airgate.plugin.v2.RunBackgroundTaskRequest
+ (*WebSocketFrame)(nil), // 33: airgate.plugin.v2.WebSocketFrame
+ (*WebSocketConnectInfo)(nil), // 34: airgate.plugin.v2.WebSocketConnectInfo
+ (*WebAssetFile)(nil), // 35: airgate.plugin.v2.WebAssetFile
+ (*WebAssetsResponse)(nil), // 36: airgate.plugin.v2.WebAssetsResponse
+ (*PayloadSchemaProto)(nil), // 37: airgate.plugin.v2.PayloadSchemaProto
+ (*RouteSchemaProto)(nil), // 38: airgate.plugin.v2.RouteSchemaProto
+ (*TaskSchemaProto)(nil), // 39: airgate.plugin.v2.TaskSchemaProto
+ (*EventSchemaProto)(nil), // 40: airgate.plugin.v2.EventSchemaProto
+ (*InvokeSchemaProto)(nil), // 41: airgate.plugin.v2.InvokeSchemaProto
+ (*PluginSchemaResponse)(nil), // 42: airgate.plugin.v2.PluginSchemaResponse
+ (*MiddlewareRequest)(nil), // 43: airgate.plugin.v2.MiddlewareRequest
+ (*MiddlewareEvent)(nil), // 44: airgate.plugin.v2.MiddlewareEvent
+ (*MiddlewareDecision)(nil), // 45: airgate.plugin.v2.MiddlewareDecision
+ (*EventSubscriptionProto)(nil), // 46: airgate.plugin.v2.EventSubscriptionProto
+ (*EventSubscriptionsResponse)(nil), // 47: airgate.plugin.v2.EventSubscriptionsResponse
+ (*PluginEvent)(nil), // 48: airgate.plugin.v2.PluginEvent
+ (*EventHandleResponse)(nil), // 49: airgate.plugin.v2.EventHandleResponse
+ (*HostInvokeRequest)(nil), // 50: airgate.plugin.v2.HostInvokeRequest
+ (*HostInvokeResponse)(nil), // 51: airgate.plugin.v2.HostInvokeResponse
+ (*HostStreamFrame)(nil), // 52: airgate.plugin.v2.HostStreamFrame
+ (*ProcessTaskRequest)(nil), // 53: airgate.plugin.v2.ProcessTaskRequest
+ (*ProcessTaskResponse)(nil), // 54: airgate.plugin.v2.ProcessTaskResponse
+ (*TaskTypesResponse)(nil), // 55: airgate.plugin.v2.TaskTypesResponse
+ nil, // 56: airgate.plugin.v2.PluginInfoResponse.MetadataEntry
+ nil, // 57: airgate.plugin.v2.InitRequest.ConfigEntry
+ nil, // 58: airgate.plugin.v2.ModelInfoProto.MetadataEntry
+ nil, // 59: airgate.plugin.v2.RouteDefinitionProto.MetadataEntry
+ nil, // 60: airgate.plugin.v2.ForwardRequest.HeadersEntry
+ nil, // 61: airgate.plugin.v2.UpstreamResponse.HeadersEntry
+ nil, // 62: airgate.plugin.v2.UsageAttribute.MetadataEntry
+ nil, // 63: airgate.plugin.v2.UsageMetric.MetadataEntry
+ nil, // 64: airgate.plugin.v2.UsageCostDetail.MetadataEntry
+ nil, // 65: airgate.plugin.v2.Usage.MetadataEntry
+ nil, // 66: airgate.plugin.v2.ForwardOutcome.UpdatedCredentialsEntry
+ nil, // 67: airgate.plugin.v2.ForwardChunk.HeadersEntry
+ nil, // 68: airgate.plugin.v2.CredentialsRequest.CredentialsEntry
+ nil, // 69: airgate.plugin.v2.HttpRequest.HeadersEntry
+ nil, // 70: airgate.plugin.v2.HttpResponse.HeadersEntry
+ nil, // 71: airgate.plugin.v2.HttpResponseChunk.HeadersEntry
+ nil, // 72: airgate.plugin.v2.WebSocketConnectInfo.HeadersEntry
+ nil, // 73: airgate.plugin.v2.PayloadSchemaProto.MetadataEntry
+ nil, // 74: airgate.plugin.v2.RouteSchemaProto.MetadataEntry
+ nil, // 75: airgate.plugin.v2.TaskSchemaProto.MetadataEntry
+ nil, // 76: airgate.plugin.v2.EventSchemaProto.MetadataEntry
+ nil, // 77: airgate.plugin.v2.InvokeSchemaProto.MetadataEntry
+ nil, // 78: airgate.plugin.v2.PluginSchemaResponse.MetadataEntry
+ nil, // 79: airgate.plugin.v2.MiddlewareRequest.MetadataEntry
+ nil, // 80: airgate.plugin.v2.MiddlewareRequest.RequestHeadersEntry
+ nil, // 81: airgate.plugin.v2.MiddlewareEvent.MetadataEntry
+ nil, // 82: airgate.plugin.v2.MiddlewareEvent.ResponseHeadersEntry
+ nil, // 83: airgate.plugin.v2.MiddlewareDecision.SetHeadersEntry
+ nil, // 84: airgate.plugin.v2.MiddlewareDecision.MetadataEntry
+ nil, // 85: airgate.plugin.v2.EventSubscriptionProto.FilterEntry
+ nil, // 86: airgate.plugin.v2.EventSubscriptionProto.MetadataEntry
+ nil, // 87: airgate.plugin.v2.PluginEvent.MetadataEntry
+ nil, // 88: airgate.plugin.v2.HostInvokeRequest.MetadataEntry
+ nil, // 89: airgate.plugin.v2.HostInvokeResponse.MetadataEntry
+ nil, // 90: airgate.plugin.v2.HostStreamFrame.MetadataEntry
}
var file_plugin_proto_depIdxs = []int32{
- 8, // 0: airgate.plugin.v1.HostListGroupsResponse.groups:type_name -> airgate.plugin.v1.HostGroup
- 63, // 1: airgate.plugin.v1.HostForwardRequest.headers:type_name -> airgate.plugin.v1.HostForwardRequest.HeadersEntry
- 64, // 2: airgate.plugin.v1.HostForwardResponse.headers:type_name -> airgate.plugin.v1.HostForwardResponse.HeadersEntry
- 14, // 3: airgate.plugin.v1.HostForwardResponse.usage:type_name -> airgate.plugin.v1.HostForwardUsage
- 65, // 4: airgate.plugin.v1.HostForwardChunk.headers:type_name -> airgate.plugin.v1.HostForwardChunk.HeadersEntry
- 14, // 5: airgate.plugin.v1.HostForwardChunk.usage:type_name -> airgate.plugin.v1.HostForwardUsage
- 16, // 6: airgate.plugin.v1.HostListPlatformsResponse.platforms:type_name -> airgate.plugin.v1.HostPlatform
- 38, // 7: airgate.plugin.v1.HostListModelsResponse.models:type_name -> airgate.plugin.v1.ModelInfoProto
- 33, // 8: airgate.plugin.v1.PluginInfoResponse.account_types:type_name -> airgate.plugin.v1.AccountTypeProto
- 35, // 9: airgate.plugin.v1.PluginInfoResponse.frontend_pages:type_name -> airgate.plugin.v1.FrontendPageProto
- 36, // 10: airgate.plugin.v1.PluginInfoResponse.frontend_widgets:type_name -> airgate.plugin.v1.FrontendWidgetProto
- 32, // 11: airgate.plugin.v1.PluginInfoResponse.config_schema:type_name -> airgate.plugin.v1.ConfigFieldProto
- 34, // 12: airgate.plugin.v1.AccountTypeProto.fields:type_name -> airgate.plugin.v1.CredentialFieldProto
- 66, // 13: airgate.plugin.v1.InitRequest.config:type_name -> airgate.plugin.v1.InitRequest.ConfigEntry
- 38, // 14: airgate.plugin.v1.ModelsResponse.models:type_name -> airgate.plugin.v1.ModelInfoProto
- 40, // 15: airgate.plugin.v1.RoutesResponse.routes:type_name -> airgate.plugin.v1.RouteDefinitionProto
- 67, // 16: airgate.plugin.v1.ForwardRequest.headers:type_name -> airgate.plugin.v1.ForwardRequest.HeadersEntry
- 42, // 17: airgate.plugin.v1.ForwardRequest.account:type_name -> airgate.plugin.v1.AccountProto
- 68, // 18: airgate.plugin.v1.UpstreamResponse.headers:type_name -> airgate.plugin.v1.UpstreamResponse.HeadersEntry
- 0, // 19: airgate.plugin.v1.ForwardOutcome.kind:type_name -> airgate.plugin.v1.OutcomeKind
- 44, // 20: airgate.plugin.v1.ForwardOutcome.upstream:type_name -> airgate.plugin.v1.UpstreamResponse
- 45, // 21: airgate.plugin.v1.ForwardOutcome.usage:type_name -> airgate.plugin.v1.Usage
- 69, // 22: airgate.plugin.v1.ForwardOutcome.updated_credentials:type_name -> airgate.plugin.v1.ForwardOutcome.UpdatedCredentialsEntry
- 46, // 23: airgate.plugin.v1.ForwardChunk.final_outcome:type_name -> airgate.plugin.v1.ForwardOutcome
- 70, // 24: airgate.plugin.v1.ForwardChunk.headers:type_name -> airgate.plugin.v1.ForwardChunk.HeadersEntry
- 71, // 25: airgate.plugin.v1.CredentialsRequest.credentials:type_name -> airgate.plugin.v1.CredentialsRequest.CredentialsEntry
- 72, // 26: airgate.plugin.v1.QuotaInfoResponse.extra:type_name -> airgate.plugin.v1.QuotaInfoResponse.ExtraEntry
- 73, // 27: airgate.plugin.v1.HttpRequest.headers:type_name -> airgate.plugin.v1.HttpRequest.HeadersEntry
- 74, // 28: airgate.plugin.v1.HttpResponse.headers:type_name -> airgate.plugin.v1.HttpResponse.HeadersEntry
- 75, // 29: airgate.plugin.v1.HttpResponseChunk.headers:type_name -> airgate.plugin.v1.HttpResponseChunk.HeadersEntry
- 53, // 30: airgate.plugin.v1.BackgroundTasksResponse.tasks:type_name -> airgate.plugin.v1.BackgroundTaskProto
- 1, // 31: airgate.plugin.v1.WebSocketFrame.type:type_name -> airgate.plugin.v1.WebSocketFrame.FrameType
- 57, // 32: airgate.plugin.v1.WebSocketFrame.connect_info:type_name -> airgate.plugin.v1.WebSocketConnectInfo
- 46, // 33: airgate.plugin.v1.WebSocketFrame.outcome:type_name -> airgate.plugin.v1.ForwardOutcome
- 76, // 34: airgate.plugin.v1.WebSocketConnectInfo.headers:type_name -> airgate.plugin.v1.WebSocketConnectInfo.HeadersEntry
- 42, // 35: airgate.plugin.v1.WebSocketConnectInfo.account:type_name -> airgate.plugin.v1.AccountProto
- 58, // 36: airgate.plugin.v1.WebAssetsResponse.files:type_name -> airgate.plugin.v1.WebAssetFile
- 77, // 37: airgate.plugin.v1.MiddlewareRequest.metadata:type_name -> airgate.plugin.v1.MiddlewareRequest.MetadataEntry
- 78, // 38: airgate.plugin.v1.MiddlewareRequest.request_headers:type_name -> airgate.plugin.v1.MiddlewareRequest.RequestHeadersEntry
- 79, // 39: airgate.plugin.v1.MiddlewareEvent.metadata:type_name -> airgate.plugin.v1.MiddlewareEvent.MetadataEntry
- 80, // 40: airgate.plugin.v1.MiddlewareEvent.response_headers:type_name -> airgate.plugin.v1.MiddlewareEvent.ResponseHeadersEntry
- 2, // 41: airgate.plugin.v1.MiddlewareDecision.action:type_name -> airgate.plugin.v1.MiddlewareDecision.Action
- 81, // 42: airgate.plugin.v1.MiddlewareDecision.set_headers:type_name -> airgate.plugin.v1.MiddlewareDecision.SetHeadersEntry
- 82, // 43: airgate.plugin.v1.MiddlewareDecision.metadata:type_name -> airgate.plugin.v1.MiddlewareDecision.MetadataEntry
- 30, // 44: airgate.plugin.v1.HostForwardRequest.HeadersEntry.value:type_name -> airgate.plugin.v1.HeaderValues
- 30, // 45: airgate.plugin.v1.HostForwardResponse.HeadersEntry.value:type_name -> airgate.plugin.v1.HeaderValues
- 30, // 46: airgate.plugin.v1.HostForwardChunk.HeadersEntry.value:type_name -> airgate.plugin.v1.HeaderValues
- 30, // 47: airgate.plugin.v1.ForwardRequest.HeadersEntry.value:type_name -> airgate.plugin.v1.HeaderValues
- 30, // 48: airgate.plugin.v1.UpstreamResponse.HeadersEntry.value:type_name -> airgate.plugin.v1.HeaderValues
- 30, // 49: airgate.plugin.v1.ForwardChunk.HeadersEntry.value:type_name -> airgate.plugin.v1.HeaderValues
- 30, // 50: airgate.plugin.v1.HttpRequest.HeadersEntry.value:type_name -> airgate.plugin.v1.HeaderValues
- 30, // 51: airgate.plugin.v1.HttpResponse.HeadersEntry.value:type_name -> airgate.plugin.v1.HeaderValues
- 30, // 52: airgate.plugin.v1.HttpResponseChunk.HeadersEntry.value:type_name -> airgate.plugin.v1.HeaderValues
- 30, // 53: airgate.plugin.v1.WebSocketConnectInfo.HeadersEntry.value:type_name -> airgate.plugin.v1.HeaderValues
- 30, // 54: airgate.plugin.v1.MiddlewareRequest.RequestHeadersEntry.value:type_name -> airgate.plugin.v1.HeaderValues
- 30, // 55: airgate.plugin.v1.MiddlewareEvent.ResponseHeadersEntry.value:type_name -> airgate.plugin.v1.HeaderValues
- 30, // 56: airgate.plugin.v1.MiddlewareDecision.SetHeadersEntry.value:type_name -> airgate.plugin.v1.HeaderValues
- 28, // 57: airgate.plugin.v1.PluginService.GetInfo:input_type -> airgate.plugin.v1.Empty
- 37, // 58: airgate.plugin.v1.PluginService.Init:input_type -> airgate.plugin.v1.InitRequest
- 28, // 59: airgate.plugin.v1.PluginService.Start:input_type -> airgate.plugin.v1.Empty
- 28, // 60: airgate.plugin.v1.PluginService.Stop:input_type -> airgate.plugin.v1.Empty
- 28, // 61: airgate.plugin.v1.PluginService.GetWebAssets:input_type -> airgate.plugin.v1.Empty
- 28, // 62: airgate.plugin.v1.PluginService.HealthCheck:input_type -> airgate.plugin.v1.Empty
- 50, // 63: airgate.plugin.v1.PluginService.HandleRequest:input_type -> airgate.plugin.v1.HttpRequest
- 28, // 64: airgate.plugin.v1.GatewayService.GetPlatform:input_type -> airgate.plugin.v1.Empty
- 28, // 65: airgate.plugin.v1.GatewayService.GetModels:input_type -> airgate.plugin.v1.Empty
- 28, // 66: airgate.plugin.v1.GatewayService.GetRoutes:input_type -> airgate.plugin.v1.Empty
- 43, // 67: airgate.plugin.v1.GatewayService.Forward:input_type -> airgate.plugin.v1.ForwardRequest
- 43, // 68: airgate.plugin.v1.GatewayService.ForwardStream:input_type -> airgate.plugin.v1.ForwardRequest
- 48, // 69: airgate.plugin.v1.GatewayService.ValidateAccount:input_type -> airgate.plugin.v1.CredentialsRequest
- 48, // 70: airgate.plugin.v1.GatewayService.QueryQuota:input_type -> airgate.plugin.v1.CredentialsRequest
- 56, // 71: airgate.plugin.v1.GatewayService.HandleWebSocket:input_type -> airgate.plugin.v1.WebSocketFrame
- 28, // 72: airgate.plugin.v1.ExtensionService.Migrate:input_type -> airgate.plugin.v1.Empty
- 28, // 73: airgate.plugin.v1.ExtensionService.GetBackgroundTasks:input_type -> airgate.plugin.v1.Empty
- 55, // 74: airgate.plugin.v1.ExtensionService.RunBackgroundTask:input_type -> airgate.plugin.v1.RunBackgroundTaskRequest
- 50, // 75: airgate.plugin.v1.ExtensionService.HandleRequest:input_type -> airgate.plugin.v1.HttpRequest
- 50, // 76: airgate.plugin.v1.ExtensionService.HandleStreamRequest:input_type -> airgate.plugin.v1.HttpRequest
- 60, // 77: airgate.plugin.v1.MiddlewareService.OnForwardBegin:input_type -> airgate.plugin.v1.MiddlewareRequest
- 61, // 78: airgate.plugin.v1.MiddlewareService.OnForwardEnd:input_type -> airgate.plugin.v1.MiddlewareEvent
- 3, // 79: airgate.plugin.v1.HostService.SelectAccount:input_type -> airgate.plugin.v1.HostSelectAccountRequest
- 10, // 80: airgate.plugin.v1.HostService.ReportAccountResult:input_type -> airgate.plugin.v1.HostReportAccountResultRequest
- 5, // 81: airgate.plugin.v1.HostService.ProbeForward:input_type -> airgate.plugin.v1.HostProbeForwardRequest
- 11, // 82: airgate.plugin.v1.HostService.Forward:input_type -> airgate.plugin.v1.HostForwardRequest
- 11, // 83: airgate.plugin.v1.HostService.ForwardStream:input_type -> airgate.plugin.v1.HostForwardRequest
- 7, // 84: airgate.plugin.v1.HostService.ListGroups:input_type -> airgate.plugin.v1.HostListGroupsRequest
- 15, // 85: airgate.plugin.v1.HostService.ListPlatforms:input_type -> airgate.plugin.v1.HostListPlatformsRequest
- 18, // 86: airgate.plugin.v1.HostService.ListModels:input_type -> airgate.plugin.v1.HostListModelsRequest
- 20, // 87: airgate.plugin.v1.HostService.GetUserInfo:input_type -> airgate.plugin.v1.HostGetUserInfoRequest
- 22, // 88: airgate.plugin.v1.HostService.StoreAsset:input_type -> airgate.plugin.v1.HostStoreAssetRequest
- 24, // 89: airgate.plugin.v1.HostService.GetAssetURL:input_type -> airgate.plugin.v1.HostGetAssetURLRequest
- 26, // 90: airgate.plugin.v1.HostService.GetAssetBytes:input_type -> airgate.plugin.v1.HostGetAssetBytesRequest
- 31, // 91: airgate.plugin.v1.PluginService.GetInfo:output_type -> airgate.plugin.v1.PluginInfoResponse
- 28, // 92: airgate.plugin.v1.PluginService.Init:output_type -> airgate.plugin.v1.Empty
- 28, // 93: airgate.plugin.v1.PluginService.Start:output_type -> airgate.plugin.v1.Empty
- 28, // 94: airgate.plugin.v1.PluginService.Stop:output_type -> airgate.plugin.v1.Empty
- 59, // 95: airgate.plugin.v1.PluginService.GetWebAssets:output_type -> airgate.plugin.v1.WebAssetsResponse
- 28, // 96: airgate.plugin.v1.PluginService.HealthCheck:output_type -> airgate.plugin.v1.Empty
- 51, // 97: airgate.plugin.v1.PluginService.HandleRequest:output_type -> airgate.plugin.v1.HttpResponse
- 29, // 98: airgate.plugin.v1.GatewayService.GetPlatform:output_type -> airgate.plugin.v1.StringResponse
- 39, // 99: airgate.plugin.v1.GatewayService.GetModels:output_type -> airgate.plugin.v1.ModelsResponse
- 41, // 100: airgate.plugin.v1.GatewayService.GetRoutes:output_type -> airgate.plugin.v1.RoutesResponse
- 46, // 101: airgate.plugin.v1.GatewayService.Forward:output_type -> airgate.plugin.v1.ForwardOutcome
- 47, // 102: airgate.plugin.v1.GatewayService.ForwardStream:output_type -> airgate.plugin.v1.ForwardChunk
- 28, // 103: airgate.plugin.v1.GatewayService.ValidateAccount:output_type -> airgate.plugin.v1.Empty
- 49, // 104: airgate.plugin.v1.GatewayService.QueryQuota:output_type -> airgate.plugin.v1.QuotaInfoResponse
- 56, // 105: airgate.plugin.v1.GatewayService.HandleWebSocket:output_type -> airgate.plugin.v1.WebSocketFrame
- 28, // 106: airgate.plugin.v1.ExtensionService.Migrate:output_type -> airgate.plugin.v1.Empty
- 54, // 107: airgate.plugin.v1.ExtensionService.GetBackgroundTasks:output_type -> airgate.plugin.v1.BackgroundTasksResponse
- 28, // 108: airgate.plugin.v1.ExtensionService.RunBackgroundTask:output_type -> airgate.plugin.v1.Empty
- 51, // 109: airgate.plugin.v1.ExtensionService.HandleRequest:output_type -> airgate.plugin.v1.HttpResponse
- 52, // 110: airgate.plugin.v1.ExtensionService.HandleStreamRequest:output_type -> airgate.plugin.v1.HttpResponseChunk
- 62, // 111: airgate.plugin.v1.MiddlewareService.OnForwardBegin:output_type -> airgate.plugin.v1.MiddlewareDecision
- 28, // 112: airgate.plugin.v1.MiddlewareService.OnForwardEnd:output_type -> airgate.plugin.v1.Empty
- 4, // 113: airgate.plugin.v1.HostService.SelectAccount:output_type -> airgate.plugin.v1.HostSelectAccountResponse
- 28, // 114: airgate.plugin.v1.HostService.ReportAccountResult:output_type -> airgate.plugin.v1.Empty
- 6, // 115: airgate.plugin.v1.HostService.ProbeForward:output_type -> airgate.plugin.v1.HostProbeForwardResponse
- 12, // 116: airgate.plugin.v1.HostService.Forward:output_type -> airgate.plugin.v1.HostForwardResponse
- 13, // 117: airgate.plugin.v1.HostService.ForwardStream:output_type -> airgate.plugin.v1.HostForwardChunk
- 9, // 118: airgate.plugin.v1.HostService.ListGroups:output_type -> airgate.plugin.v1.HostListGroupsResponse
- 17, // 119: airgate.plugin.v1.HostService.ListPlatforms:output_type -> airgate.plugin.v1.HostListPlatformsResponse
- 19, // 120: airgate.plugin.v1.HostService.ListModels:output_type -> airgate.plugin.v1.HostListModelsResponse
- 21, // 121: airgate.plugin.v1.HostService.GetUserInfo:output_type -> airgate.plugin.v1.HostGetUserInfoResponse
- 23, // 122: airgate.plugin.v1.HostService.StoreAsset:output_type -> airgate.plugin.v1.HostStoreAssetResponse
- 25, // 123: airgate.plugin.v1.HostService.GetAssetURL:output_type -> airgate.plugin.v1.HostGetAssetURLResponse
- 27, // 124: airgate.plugin.v1.HostService.GetAssetBytes:output_type -> airgate.plugin.v1.HostGetAssetBytesResponse
- 91, // [91:125] is the sub-list for method output_type
- 57, // [57:91] is the sub-list for method input_type
- 57, // [57:57] is the sub-list for extension type_name
- 57, // [57:57] is the sub-list for extension extendee
- 0, // [0:57] is the sub-list for field type_name
+ 8, // 0: airgate.plugin.v2.PluginInfoResponse.account_types:type_name -> airgate.plugin.v2.AccountTypeProto
+ 10, // 1: airgate.plugin.v2.PluginInfoResponse.frontend_pages:type_name -> airgate.plugin.v2.FrontendPageProto
+ 11, // 2: airgate.plugin.v2.PluginInfoResponse.frontend_widgets:type_name -> airgate.plugin.v2.FrontendWidgetProto
+ 7, // 3: airgate.plugin.v2.PluginInfoResponse.config_schema:type_name -> airgate.plugin.v2.ConfigFieldProto
+ 56, // 4: airgate.plugin.v2.PluginInfoResponse.metadata:type_name -> airgate.plugin.v2.PluginInfoResponse.MetadataEntry
+ 9, // 5: airgate.plugin.v2.AccountTypeProto.fields:type_name -> airgate.plugin.v2.CredentialFieldProto
+ 57, // 6: airgate.plugin.v2.InitRequest.config:type_name -> airgate.plugin.v2.InitRequest.ConfigEntry
+ 58, // 7: airgate.plugin.v2.ModelInfoProto.metadata:type_name -> airgate.plugin.v2.ModelInfoProto.MetadataEntry
+ 13, // 8: airgate.plugin.v2.ModelsResponse.models:type_name -> airgate.plugin.v2.ModelInfoProto
+ 59, // 9: airgate.plugin.v2.RouteDefinitionProto.metadata:type_name -> airgate.plugin.v2.RouteDefinitionProto.MetadataEntry
+ 15, // 10: airgate.plugin.v2.RoutesResponse.routes:type_name -> airgate.plugin.v2.RouteDefinitionProto
+ 60, // 11: airgate.plugin.v2.ForwardRequest.headers:type_name -> airgate.plugin.v2.ForwardRequest.HeadersEntry
+ 17, // 12: airgate.plugin.v2.ForwardRequest.account:type_name -> airgate.plugin.v2.AccountProto
+ 61, // 13: airgate.plugin.v2.UpstreamResponse.headers:type_name -> airgate.plugin.v2.UpstreamResponse.HeadersEntry
+ 62, // 14: airgate.plugin.v2.UsageAttribute.metadata:type_name -> airgate.plugin.v2.UsageAttribute.MetadataEntry
+ 63, // 15: airgate.plugin.v2.UsageMetric.metadata:type_name -> airgate.plugin.v2.UsageMetric.MetadataEntry
+ 64, // 16: airgate.plugin.v2.UsageCostDetail.metadata:type_name -> airgate.plugin.v2.UsageCostDetail.MetadataEntry
+ 21, // 17: airgate.plugin.v2.Usage.metrics:type_name -> airgate.plugin.v2.UsageMetric
+ 20, // 18: airgate.plugin.v2.Usage.attributes:type_name -> airgate.plugin.v2.UsageAttribute
+ 22, // 19: airgate.plugin.v2.Usage.cost_details:type_name -> airgate.plugin.v2.UsageCostDetail
+ 65, // 20: airgate.plugin.v2.Usage.metadata:type_name -> airgate.plugin.v2.Usage.MetadataEntry
+ 0, // 21: airgate.plugin.v2.ForwardOutcome.kind:type_name -> airgate.plugin.v2.OutcomeKind
+ 19, // 22: airgate.plugin.v2.ForwardOutcome.upstream:type_name -> airgate.plugin.v2.UpstreamResponse
+ 23, // 23: airgate.plugin.v2.ForwardOutcome.usage:type_name -> airgate.plugin.v2.Usage
+ 66, // 24: airgate.plugin.v2.ForwardOutcome.updated_credentials:type_name -> airgate.plugin.v2.ForwardOutcome.UpdatedCredentialsEntry
+ 24, // 25: airgate.plugin.v2.ForwardChunk.final_outcome:type_name -> airgate.plugin.v2.ForwardOutcome
+ 67, // 26: airgate.plugin.v2.ForwardChunk.headers:type_name -> airgate.plugin.v2.ForwardChunk.HeadersEntry
+ 68, // 27: airgate.plugin.v2.CredentialsRequest.credentials:type_name -> airgate.plugin.v2.CredentialsRequest.CredentialsEntry
+ 69, // 28: airgate.plugin.v2.HttpRequest.headers:type_name -> airgate.plugin.v2.HttpRequest.HeadersEntry
+ 70, // 29: airgate.plugin.v2.HttpResponse.headers:type_name -> airgate.plugin.v2.HttpResponse.HeadersEntry
+ 71, // 30: airgate.plugin.v2.HttpResponseChunk.headers:type_name -> airgate.plugin.v2.HttpResponseChunk.HeadersEntry
+ 30, // 31: airgate.plugin.v2.BackgroundTasksResponse.tasks:type_name -> airgate.plugin.v2.BackgroundTaskProto
+ 1, // 32: airgate.plugin.v2.WebSocketFrame.type:type_name -> airgate.plugin.v2.WebSocketFrame.FrameType
+ 34, // 33: airgate.plugin.v2.WebSocketFrame.connect_info:type_name -> airgate.plugin.v2.WebSocketConnectInfo
+ 24, // 34: airgate.plugin.v2.WebSocketFrame.outcome:type_name -> airgate.plugin.v2.ForwardOutcome
+ 72, // 35: airgate.plugin.v2.WebSocketConnectInfo.headers:type_name -> airgate.plugin.v2.WebSocketConnectInfo.HeadersEntry
+ 17, // 36: airgate.plugin.v2.WebSocketConnectInfo.account:type_name -> airgate.plugin.v2.AccountProto
+ 35, // 37: airgate.plugin.v2.WebAssetsResponse.files:type_name -> airgate.plugin.v2.WebAssetFile
+ 73, // 38: airgate.plugin.v2.PayloadSchemaProto.metadata:type_name -> airgate.plugin.v2.PayloadSchemaProto.MetadataEntry
+ 37, // 39: airgate.plugin.v2.RouteSchemaProto.request:type_name -> airgate.plugin.v2.PayloadSchemaProto
+ 37, // 40: airgate.plugin.v2.RouteSchemaProto.response:type_name -> airgate.plugin.v2.PayloadSchemaProto
+ 74, // 41: airgate.plugin.v2.RouteSchemaProto.metadata:type_name -> airgate.plugin.v2.RouteSchemaProto.MetadataEntry
+ 37, // 42: airgate.plugin.v2.TaskSchemaProto.input:type_name -> airgate.plugin.v2.PayloadSchemaProto
+ 37, // 43: airgate.plugin.v2.TaskSchemaProto.output:type_name -> airgate.plugin.v2.PayloadSchemaProto
+ 75, // 44: airgate.plugin.v2.TaskSchemaProto.metadata:type_name -> airgate.plugin.v2.TaskSchemaProto.MetadataEntry
+ 37, // 45: airgate.plugin.v2.EventSchemaProto.payload:type_name -> airgate.plugin.v2.PayloadSchemaProto
+ 76, // 46: airgate.plugin.v2.EventSchemaProto.metadata:type_name -> airgate.plugin.v2.EventSchemaProto.MetadataEntry
+ 37, // 47: airgate.plugin.v2.InvokeSchemaProto.request:type_name -> airgate.plugin.v2.PayloadSchemaProto
+ 37, // 48: airgate.plugin.v2.InvokeSchemaProto.response:type_name -> airgate.plugin.v2.PayloadSchemaProto
+ 77, // 49: airgate.plugin.v2.InvokeSchemaProto.metadata:type_name -> airgate.plugin.v2.InvokeSchemaProto.MetadataEntry
+ 38, // 50: airgate.plugin.v2.PluginSchemaResponse.routes:type_name -> airgate.plugin.v2.RouteSchemaProto
+ 39, // 51: airgate.plugin.v2.PluginSchemaResponse.tasks:type_name -> airgate.plugin.v2.TaskSchemaProto
+ 40, // 52: airgate.plugin.v2.PluginSchemaResponse.events:type_name -> airgate.plugin.v2.EventSchemaProto
+ 41, // 53: airgate.plugin.v2.PluginSchemaResponse.invokes:type_name -> airgate.plugin.v2.InvokeSchemaProto
+ 78, // 54: airgate.plugin.v2.PluginSchemaResponse.metadata:type_name -> airgate.plugin.v2.PluginSchemaResponse.MetadataEntry
+ 21, // 55: airgate.plugin.v2.MiddlewareRequest.estimates:type_name -> airgate.plugin.v2.UsageMetric
+ 79, // 56: airgate.plugin.v2.MiddlewareRequest.metadata:type_name -> airgate.plugin.v2.MiddlewareRequest.MetadataEntry
+ 80, // 57: airgate.plugin.v2.MiddlewareRequest.request_headers:type_name -> airgate.plugin.v2.MiddlewareRequest.RequestHeadersEntry
+ 21, // 58: airgate.plugin.v2.MiddlewareEvent.estimates:type_name -> airgate.plugin.v2.UsageMetric
+ 23, // 59: airgate.plugin.v2.MiddlewareEvent.usage:type_name -> airgate.plugin.v2.Usage
+ 81, // 60: airgate.plugin.v2.MiddlewareEvent.metadata:type_name -> airgate.plugin.v2.MiddlewareEvent.MetadataEntry
+ 82, // 61: airgate.plugin.v2.MiddlewareEvent.response_headers:type_name -> airgate.plugin.v2.MiddlewareEvent.ResponseHeadersEntry
+ 2, // 62: airgate.plugin.v2.MiddlewareDecision.action:type_name -> airgate.plugin.v2.MiddlewareDecision.Action
+ 83, // 63: airgate.plugin.v2.MiddlewareDecision.set_headers:type_name -> airgate.plugin.v2.MiddlewareDecision.SetHeadersEntry
+ 84, // 64: airgate.plugin.v2.MiddlewareDecision.metadata:type_name -> airgate.plugin.v2.MiddlewareDecision.MetadataEntry
+ 85, // 65: airgate.plugin.v2.EventSubscriptionProto.filter:type_name -> airgate.plugin.v2.EventSubscriptionProto.FilterEntry
+ 86, // 66: airgate.plugin.v2.EventSubscriptionProto.metadata:type_name -> airgate.plugin.v2.EventSubscriptionProto.MetadataEntry
+ 46, // 67: airgate.plugin.v2.EventSubscriptionsResponse.subscriptions:type_name -> airgate.plugin.v2.EventSubscriptionProto
+ 87, // 68: airgate.plugin.v2.PluginEvent.metadata:type_name -> airgate.plugin.v2.PluginEvent.MetadataEntry
+ 88, // 69: airgate.plugin.v2.HostInvokeRequest.metadata:type_name -> airgate.plugin.v2.HostInvokeRequest.MetadataEntry
+ 89, // 70: airgate.plugin.v2.HostInvokeResponse.metadata:type_name -> airgate.plugin.v2.HostInvokeResponse.MetadataEntry
+ 90, // 71: airgate.plugin.v2.HostStreamFrame.metadata:type_name -> airgate.plugin.v2.HostStreamFrame.MetadataEntry
+ 5, // 72: airgate.plugin.v2.ForwardRequest.HeadersEntry.value:type_name -> airgate.plugin.v2.HeaderValues
+ 5, // 73: airgate.plugin.v2.UpstreamResponse.HeadersEntry.value:type_name -> airgate.plugin.v2.HeaderValues
+ 5, // 74: airgate.plugin.v2.ForwardChunk.HeadersEntry.value:type_name -> airgate.plugin.v2.HeaderValues
+ 5, // 75: airgate.plugin.v2.HttpRequest.HeadersEntry.value:type_name -> airgate.plugin.v2.HeaderValues
+ 5, // 76: airgate.plugin.v2.HttpResponse.HeadersEntry.value:type_name -> airgate.plugin.v2.HeaderValues
+ 5, // 77: airgate.plugin.v2.HttpResponseChunk.HeadersEntry.value:type_name -> airgate.plugin.v2.HeaderValues
+ 5, // 78: airgate.plugin.v2.WebSocketConnectInfo.HeadersEntry.value:type_name -> airgate.plugin.v2.HeaderValues
+ 5, // 79: airgate.plugin.v2.MiddlewareRequest.RequestHeadersEntry.value:type_name -> airgate.plugin.v2.HeaderValues
+ 5, // 80: airgate.plugin.v2.MiddlewareEvent.ResponseHeadersEntry.value:type_name -> airgate.plugin.v2.HeaderValues
+ 5, // 81: airgate.plugin.v2.MiddlewareDecision.SetHeadersEntry.value:type_name -> airgate.plugin.v2.HeaderValues
+ 3, // 82: airgate.plugin.v2.PluginService.GetInfo:input_type -> airgate.plugin.v2.Empty
+ 12, // 83: airgate.plugin.v2.PluginService.Init:input_type -> airgate.plugin.v2.InitRequest
+ 3, // 84: airgate.plugin.v2.PluginService.Start:input_type -> airgate.plugin.v2.Empty
+ 3, // 85: airgate.plugin.v2.PluginService.Stop:input_type -> airgate.plugin.v2.Empty
+ 3, // 86: airgate.plugin.v2.PluginService.GetWebAssets:input_type -> airgate.plugin.v2.Empty
+ 3, // 87: airgate.plugin.v2.PluginService.GetSchema:input_type -> airgate.plugin.v2.Empty
+ 3, // 88: airgate.plugin.v2.PluginService.HealthCheck:input_type -> airgate.plugin.v2.Empty
+ 27, // 89: airgate.plugin.v2.PluginService.HandleRequest:input_type -> airgate.plugin.v2.HttpRequest
+ 3, // 90: airgate.plugin.v2.GatewayService.GetPlatform:input_type -> airgate.plugin.v2.Empty
+ 3, // 91: airgate.plugin.v2.GatewayService.GetModels:input_type -> airgate.plugin.v2.Empty
+ 3, // 92: airgate.plugin.v2.GatewayService.GetRoutes:input_type -> airgate.plugin.v2.Empty
+ 18, // 93: airgate.plugin.v2.GatewayService.Forward:input_type -> airgate.plugin.v2.ForwardRequest
+ 18, // 94: airgate.plugin.v2.GatewayService.ForwardStream:input_type -> airgate.plugin.v2.ForwardRequest
+ 26, // 95: airgate.plugin.v2.GatewayService.ValidateAccount:input_type -> airgate.plugin.v2.CredentialsRequest
+ 33, // 96: airgate.plugin.v2.GatewayService.HandleWebSocket:input_type -> airgate.plugin.v2.WebSocketFrame
+ 3, // 97: airgate.plugin.v2.ExtensionService.Migrate:input_type -> airgate.plugin.v2.Empty
+ 3, // 98: airgate.plugin.v2.ExtensionService.GetBackgroundTasks:input_type -> airgate.plugin.v2.Empty
+ 32, // 99: airgate.plugin.v2.ExtensionService.RunBackgroundTask:input_type -> airgate.plugin.v2.RunBackgroundTaskRequest
+ 27, // 100: airgate.plugin.v2.ExtensionService.HandleRequest:input_type -> airgate.plugin.v2.HttpRequest
+ 27, // 101: airgate.plugin.v2.ExtensionService.HandleStreamRequest:input_type -> airgate.plugin.v2.HttpRequest
+ 53, // 102: airgate.plugin.v2.ExtensionService.ProcessTask:input_type -> airgate.plugin.v2.ProcessTaskRequest
+ 3, // 103: airgate.plugin.v2.ExtensionService.GetTaskTypes:input_type -> airgate.plugin.v2.Empty
+ 43, // 104: airgate.plugin.v2.MiddlewareService.OnForwardBegin:input_type -> airgate.plugin.v2.MiddlewareRequest
+ 44, // 105: airgate.plugin.v2.MiddlewareService.OnForwardEnd:input_type -> airgate.plugin.v2.MiddlewareEvent
+ 3, // 106: airgate.plugin.v2.EventService.GetEventSubscriptions:input_type -> airgate.plugin.v2.Empty
+ 48, // 107: airgate.plugin.v2.EventService.HandleEvent:input_type -> airgate.plugin.v2.PluginEvent
+ 50, // 108: airgate.plugin.v2.CoreInvokeService.Invoke:input_type -> airgate.plugin.v2.HostInvokeRequest
+ 52, // 109: airgate.plugin.v2.CoreInvokeService.InvokeStream:input_type -> airgate.plugin.v2.HostStreamFrame
+ 6, // 110: airgate.plugin.v2.PluginService.GetInfo:output_type -> airgate.plugin.v2.PluginInfoResponse
+ 3, // 111: airgate.plugin.v2.PluginService.Init:output_type -> airgate.plugin.v2.Empty
+ 3, // 112: airgate.plugin.v2.PluginService.Start:output_type -> airgate.plugin.v2.Empty
+ 3, // 113: airgate.plugin.v2.PluginService.Stop:output_type -> airgate.plugin.v2.Empty
+ 36, // 114: airgate.plugin.v2.PluginService.GetWebAssets:output_type -> airgate.plugin.v2.WebAssetsResponse
+ 42, // 115: airgate.plugin.v2.PluginService.GetSchema:output_type -> airgate.plugin.v2.PluginSchemaResponse
+ 3, // 116: airgate.plugin.v2.PluginService.HealthCheck:output_type -> airgate.plugin.v2.Empty
+ 28, // 117: airgate.plugin.v2.PluginService.HandleRequest:output_type -> airgate.plugin.v2.HttpResponse
+ 4, // 118: airgate.plugin.v2.GatewayService.GetPlatform:output_type -> airgate.plugin.v2.StringResponse
+ 14, // 119: airgate.plugin.v2.GatewayService.GetModels:output_type -> airgate.plugin.v2.ModelsResponse
+ 16, // 120: airgate.plugin.v2.GatewayService.GetRoutes:output_type -> airgate.plugin.v2.RoutesResponse
+ 24, // 121: airgate.plugin.v2.GatewayService.Forward:output_type -> airgate.plugin.v2.ForwardOutcome
+ 25, // 122: airgate.plugin.v2.GatewayService.ForwardStream:output_type -> airgate.plugin.v2.ForwardChunk
+ 3, // 123: airgate.plugin.v2.GatewayService.ValidateAccount:output_type -> airgate.plugin.v2.Empty
+ 33, // 124: airgate.plugin.v2.GatewayService.HandleWebSocket:output_type -> airgate.plugin.v2.WebSocketFrame
+ 3, // 125: airgate.plugin.v2.ExtensionService.Migrate:output_type -> airgate.plugin.v2.Empty
+ 31, // 126: airgate.plugin.v2.ExtensionService.GetBackgroundTasks:output_type -> airgate.plugin.v2.BackgroundTasksResponse
+ 3, // 127: airgate.plugin.v2.ExtensionService.RunBackgroundTask:output_type -> airgate.plugin.v2.Empty
+ 28, // 128: airgate.plugin.v2.ExtensionService.HandleRequest:output_type -> airgate.plugin.v2.HttpResponse
+ 29, // 129: airgate.plugin.v2.ExtensionService.HandleStreamRequest:output_type -> airgate.plugin.v2.HttpResponseChunk
+ 54, // 130: airgate.plugin.v2.ExtensionService.ProcessTask:output_type -> airgate.plugin.v2.ProcessTaskResponse
+ 55, // 131: airgate.plugin.v2.ExtensionService.GetTaskTypes:output_type -> airgate.plugin.v2.TaskTypesResponse
+ 45, // 132: airgate.plugin.v2.MiddlewareService.OnForwardBegin:output_type -> airgate.plugin.v2.MiddlewareDecision
+ 3, // 133: airgate.plugin.v2.MiddlewareService.OnForwardEnd:output_type -> airgate.plugin.v2.Empty
+ 47, // 134: airgate.plugin.v2.EventService.GetEventSubscriptions:output_type -> airgate.plugin.v2.EventSubscriptionsResponse
+ 49, // 135: airgate.plugin.v2.EventService.HandleEvent:output_type -> airgate.plugin.v2.EventHandleResponse
+ 51, // 136: airgate.plugin.v2.CoreInvokeService.Invoke:output_type -> airgate.plugin.v2.HostInvokeResponse
+ 52, // 137: airgate.plugin.v2.CoreInvokeService.InvokeStream:output_type -> airgate.plugin.v2.HostStreamFrame
+ 110, // [110:138] is the sub-list for method output_type
+ 82, // [82:110] is the sub-list for method input_type
+ 82, // [82:82] is the sub-list for extension type_name
+ 82, // [82:82] is the sub-list for extension extendee
+ 0, // [0:82] is the sub-list for field type_name
}
func init() { file_plugin_proto_init() }
@@ -5215,9 +4875,9 @@ func file_plugin_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_plugin_proto_rawDesc), len(file_plugin_proto_rawDesc)),
NumEnums: 3,
- NumMessages: 80,
+ NumMessages: 88,
NumExtensions: 0,
- NumServices: 5,
+ NumServices: 6,
},
GoTypes: file_plugin_proto_goTypes,
DependencyIndexes: file_plugin_proto_depIdxs,
diff --git a/proto/plugin.proto b/protocol/proto/plugin.proto
similarity index 50%
rename from proto/plugin.proto
rename to protocol/proto/plugin.proto
index 2bbe4f7..53d06ad 100644
--- a/proto/plugin.proto
+++ b/protocol/proto/plugin.proto
@@ -1,8 +1,8 @@
syntax = "proto3";
-package airgate.plugin.v1;
+package airgate.plugin.v2;
-option go_package = "github.com/DouDOU-start/airgate-sdk/proto";
+option go_package = "github.com/DouDOU-start/airgate-sdk/protocol/proto";
// ==================== 插件基础服务 ====================
@@ -13,6 +13,8 @@ service PluginService {
rpc Stop(Empty) returns (Empty);
// 获取插件的前端静态资源
rpc GetWebAssets(Empty) returns (WebAssetsResponse);
+ // 获取插件结构化能力清单(可选,未实现时返回空)
+ rpc GetSchema(Empty) returns (PluginSchemaResponse);
// 健康检查(可选,插件未实现时返回成功)
rpc HealthCheck(Empty) returns (Empty);
// 通用请求代理:Core 透传,插件自行路由(可选,插件未实现时返回 501)
@@ -28,7 +30,6 @@ service GatewayService {
rpc Forward(ForwardRequest) returns (ForwardOutcome);
rpc ForwardStream(ForwardRequest) returns (stream ForwardChunk);
rpc ValidateAccount(CredentialsRequest) returns (Empty);
- rpc QueryQuota(CredentialsRequest) returns (QuotaInfoResponse);
// WebSocket 双向流
rpc HandleWebSocket(stream WebSocketFrame) returns (stream WebSocketFrame);
}
@@ -43,6 +44,10 @@ service ExtensionService {
// HTTP 请求由核心代理到插件
rpc HandleRequest(HttpRequest) returns (HttpResponse);
rpc HandleStreamRequest(HttpRequest) returns (stream HttpResponseChunk);
+ // 异步任务:Core 分发 pending 任务给插件处理
+ rpc ProcessTask(ProcessTaskRequest) returns (ProcessTaskResponse);
+ // 返回此插件支持的任务类型列表
+ rpc GetTaskTypes(Empty) returns (TaskTypesResponse);
}
// ==================== 中间件服务(Core → Plugin,forward 路径拦截) ====================
@@ -51,14 +56,14 @@ service ExtensionService {
// - OnForwardBegin — 选完账号 / 还没调 upstream 之前,能改 headers / 拒绝请求
// - OnForwardEnd — upstream 返回之后 / 写 usage_log 之前,拿到完整 metadata
//
-// 设计原则(详见 ADR-0001 Decision 2/3):
+// 设计原则:
// 1. middleware 挂了不能 block 生产:返回 error 只 log warn,流程继续;
// 只有 OnForwardBegin 明确返回 Action=DENY 才会拒绝请求
// 2. 多个 middleware 按 priority 排序。Begin 升序、End 降序(LIFO)
// 3. payload 两段式:默认只传元数据,声明 middleware.read_body capability 的插件
// 才会收到 request_body / response_body
-// 4. 流式响应的 response_body 只给摘要(首次非空 chunk 拼装),完整流式内容留给
-// 未来的 OnStreamChunk(ADR-0002)
+// 4. 流式响应的 response_body 只给摘要(首次非空 chunk 拼装),完整流式内容应由
+// 独立的流式事件能力承载
//
// 新角色:middleware 插件不是 gateway(不替代 upstream),也不是 extension
//(不跑后台任务 + 自定义 HTTP)。它在 PluginInfo.type = "middleware" 中声明。
@@ -67,233 +72,21 @@ service MiddlewareService {
rpc OnForwardEnd(MiddlewareEvent) returns (Empty);
}
-// ==================== Host 服务(反向调用:插件 → Core) ====================
-//
-// 由 Core 实现,通过 hashicorp/go-plugin 的 GRPCBroker 暴露给插件子进程。
-// 替代旧的 admin HTTP API + admin_api_key 模式:插件不再需要 HTTP client、
-// 不再需要 Bearer 鉴权——broker 的子进程隧道天然互信。
-//
-// 设计原则:
-// 1. 覆盖插件常见需求的通用原语层——新增插件应只组合已有 RPC,无需扩 proto;
-// 2. 副作用类 RPC(Forward / ProbeForward / ReportAccountResult)明确语义边界;
-// 3. ProbeForward 与 Forward 分开,便于权限控制 + 计费跳过 + 日志区分;
-// 4. 每个 RPC 由 Capability 门控,Core interceptor 做准入校验。
-//
-// 能力分类:
-// ── 调度 ──
-// - SelectAccount — 调度选号(同用户路径)
-// - ReportAccountResult — 反馈账号调用结果到状态机
-// ── 探测 ──
-// - ProbeForward — 黑盒探测请求:走完整调度但跳过 usage_log / 余额扣款
-// ── Forward 管线(完整计费) ──
-// - Forward — 非流式业务转发(调度 → 网关 → 计费 → 记录)
-// - ForwardStream — 流式业务转发(SSE / chunked)
-// ── 数据查询 ──
-// - ListGroups — 所有分组
-// - ListPlatforms — 已加载的网关平台
-// - ListModels — 指定平台的模型列表
-// - GetUserInfo — 用户基本信息 + 余额
-
-service HostService {
- // ── 调度 ──
-
- // 选号:根据 (group_id, model) 走和真实用户请求完全相同的调度路径。
- rpc SelectAccount(HostSelectAccountRequest) returns (HostSelectAccountResponse);
-
- // 把账号调用结果反馈给 scheduler 的失败计数器/状态机。
- rpc ReportAccountResult(HostReportAccountResultRequest) returns (Empty);
-
- // ── 探测 ──
-
- // 黑盒探测:内部组装一次最小的 chat completion 请求并直接执行。
- // 跳过 usage_log 写入、跳过用户余额扣款,仍然 ReportResult 反哺账号状态机。
- rpc ProbeForward(HostProbeForwardRequest) returns (HostProbeForwardResponse);
-
- // ── Forward 管线(完整计费) ──
-
- // 非流式业务转发:走完整管线(调度 → 网关插件 → 计费 → usage_log)。
- // 调用方需提供 user_id + group_id 以确定计费主体和调度路径。
- rpc Forward(HostForwardRequest) returns (HostForwardResponse);
-
- // 流式业务转发:与 Forward 相同管线,结果通过 server stream 逐块返回。
- // 最后一块 done=true 携带 usage 信息(计费已在 Core 侧完成)。
- rpc ForwardStream(HostForwardRequest) returns (stream HostForwardChunk);
-
- // ── 数据查询 ──
-
- // 列出所有分组。
- rpc ListGroups(HostListGroupsRequest) returns (HostListGroupsResponse);
-
- // 列出已加载的网关平台(每个 gateway 插件对应一个 platform)。
- rpc ListPlatforms(HostListPlatformsRequest) returns (HostListPlatformsResponse);
-
- // 列出指定平台的模型列表。
- rpc ListModels(HostListModelsRequest) returns (HostListModelsResponse);
-
- // 获取用户基本信息(余额、角色、状态)。
- rpc GetUserInfo(HostGetUserInfoRequest) returns (HostGetUserInfoResponse);
-
- // 资产存储:由 Core 根据全局 storage 设置选择 MinIO/S3 或本地磁盘。
- rpc StoreAsset(HostStoreAssetRequest) returns (HostStoreAssetResponse);
- rpc GetAssetURL(HostGetAssetURLRequest) returns (HostGetAssetURLResponse);
- rpc GetAssetBytes(HostGetAssetBytesRequest) returns (HostGetAssetBytesResponse);
-}
-
-message HostSelectAccountRequest {
- int64 group_id = 1;
- string model = 2; // 可空:空时 Core 用 platform 的第一个 model
- string session_id = 3; // 可空
- repeated int64 exclude_account_ids = 4;
-}
-
-message HostSelectAccountResponse {
- int64 account_id = 1;
- string account_name = 2;
- string platform = 3;
-}
-
-message HostProbeForwardRequest {
- int64 group_id = 1;
- string model = 2; // 可空:自动取 platform 第一个 model
-}
-
-message HostProbeForwardResponse {
- bool success = 1;
- int64 account_id = 2; // 本次实际命中的账号 ID(用于运维诊断)
- string platform = 3;
- string model = 4; // 实际探测用的 model
- int64 status_code = 5;
- int64 latency_ms = 6;
- string error_kind = 7; // "" / "no_account" / "scheduler" / "upstream_5xx" / "timeout" / ...
- string error_msg = 8;
-}
-
-message HostListGroupsRequest {}
-
-message HostGroup {
- int64 id = 1;
- string name = 2;
- string platform = 3;
- bool is_exclusive = 4;
- double rate_multiplier = 5;
-}
+// ==================== 事件服务(Core → Plugin) ====================
-message HostListGroupsResponse {
- repeated HostGroup groups = 1;
+service EventService {
+ rpc GetEventSubscriptions(Empty) returns (EventSubscriptionsResponse);
+ rpc HandleEvent(PluginEvent) returns (EventHandleResponse);
}
-message HostReportAccountResultRequest {
- int64 account_id = 1;
- bool success = 2;
- string error_msg = 3; // 失败时上报,便于调试
-}
-
-// ── Host Forward 消息 ──
-
-// HostForwardRequest 业务转发入参。
-// user_id + group_id 共同确定计费主体和调度路径。
-message HostForwardRequest {
- int64 user_id = 1; // 计费主体(扣余额的用户)
- int64 group_id = 2; // 调度分组
- string model = 3; // 可空:空时 Core 取该 platform 的第一个 model
- string method = 4; // HTTP method(POST / GET / ...)
- string path = 5; // 请求路径(如 /v1/chat/completions)
- map headers = 6;
- bytes body = 7;
- bool stream = 8; // 是否流式
-}
-
-// HostForwardResponse 非流式转发结果。
-message HostForwardResponse {
- int32 status_code = 1;
- map headers = 2;
- bytes body = 3;
- HostForwardUsage usage = 4;
-}
-
-// HostForwardChunk 流式转发的单块数据。
-// 第一块携带 status_code + headers;最后一块 done=true 携带 usage。
-message HostForwardChunk {
- bytes data = 1;
- bool done = 2;
- int32 status_code = 3; // 仅首块
- map headers = 4; // 仅首块
- HostForwardUsage usage = 5; // 仅末块
-}
-
-// HostForwardUsage 转发的 token / 费用摘要(Core 侧计算后回传给调用方插件)。
-message HostForwardUsage {
- int64 input_tokens = 1;
- int64 output_tokens = 2;
- double cost = 3; // 总费用(已计入倍率)
- string model = 4; // 实际使用的 model(可能被 Core 改写)
-}
-
-// ── Host 数据查询消息 ──
-
-message HostListPlatformsRequest {}
-
-message HostPlatform {
- string name = 1; // 平台标识(如 "openai")
- string display_name = 2; // 展示名称(如 "OpenAI 网关")
-}
-
-message HostListPlatformsResponse {
- repeated HostPlatform platforms = 1;
-}
-
-message HostListModelsRequest {
- string platform = 1; // 必填:平台标识
-}
-
-message HostListModelsResponse {
- repeated ModelInfoProto models = 1; // 复用已有 ModelInfoProto,避免平行数据结构漂移
-}
-
-message HostGetUserInfoRequest {
- int64 user_id = 1;
-}
-
-message HostGetUserInfoResponse {
- int64 user_id = 1;
- string username = 2;
- string email = 3;
- string role = 4; // "admin" / "user"
- double balance = 5;
- string status = 6; // "active" / "disabled"
-}
-
-message HostStoreAssetRequest {
- int64 user_id = 1;
- string scope = 2;
- string content_type = 3;
- bytes data = 4;
- string file_extension = 5;
-}
-
-message HostStoreAssetResponse {
- string asset_id = 1;
- string object_key = 2;
- string public_url = 3;
- int64 size_bytes = 4;
- string content_type = 5;
-}
-
-message HostGetAssetURLRequest {
- string object_key = 1;
-}
-
-message HostGetAssetURLResponse {
- string public_url = 1;
-}
-
-message HostGetAssetBytesRequest {
- string object_key = 1;
-}
-
-message HostGetAssetBytesResponse {
- bytes data = 1;
- string content_type = 2;
+// ==================== Host 服务(反向调用:插件 → Core) ====================
+//
+// 由 Core 实现,通过 hashicorp/go-plugin 的 GRPCBroker 暴露给插件子进程。
+// SDK 只定义通用 Invoke / InvokeStream 通道;Core 通过方法注册表决定开放哪些方法、
+// 允许哪些插件调用、请求/响应 schema、是否支持流式以及幂等策略。
+service CoreInvokeService {
+ rpc Invoke(HostInvokeRequest) returns (HostInvokeResponse);
+ rpc InvokeStream(stream HostStreamFrame) returns (stream HostStreamFrame);
}
// ==================== 通用消息 ====================
@@ -325,13 +118,15 @@ message PluginInfoResponse {
repeated string dependencies = 11; // 依赖的其他插件 ID
repeated ConfigFieldProto config_schema = 12; // 配置项声明
repeated string instruction_presets = 13; // 可用的 instructions 预设名称列表
- // capabilities 声明的 HostService / Middleware 能力。Core 启动时按
- // "插件类型 → 允许集合" 做交集,gRPC interceptor 按此 set 做准入校验。
- // SDK 版本 <= 0.2.x 的插件豁免(兼容存量),0.3.x 起强制。详见 ADR-0001 Decision 4。
+ // capabilities 声明 Host.Invoke / Host.InvokeStream、method 级授权和 Middleware 能力。
+ // Core 启动时按插件类型、方法注册表和 RPC 调用做准入校验。
repeated string capabilities = 14;
// priority 中间件链中的排序权重(数值越小越早进 Begin、越晚出 End)。
// 仅 type="middleware" 插件使用;其他类型忽略。默认 100。
int32 priority = 15;
+ // metadata 保存插件声明层面的弱契约扩展信息,例如分类、市场标签、展示提示。
+ // 需要 Core 授权或参与调度的数据必须进入显式字段或 capability。
+ map metadata = 16;
}
message ConfigFieldProto {
@@ -377,11 +172,11 @@ message FrontendWidgetProto {
message InitRequest {
map config = 1;
string log_level = 2;
- // host_broker_id 是 Core 通过 hashicorp/go-plugin GRPCBroker 启动的
- // HostService stream 的 ID。插件 Init 时拿到 ID 后,可以通过 broker.Dial(id)
- // 拿到 HostService 的 grpc client,回调 Core 提供的能力(SelectAccount /
- // ProbeForward / ListGroups 等)。0 表示 Core 没启用 HostService。
- uint32 host_broker_id = 3;
+ // core_invoke_broker_id 是 Core 通过 hashicorp/go-plugin GRPCBroker 启动的
+ // 反向调用 stream ID。插件 Init 时拿到 ID 后,可以通过 broker.Dial(id)
+ // 拿到 CoreInvokeService grpc client,通过 Invoke / InvokeStream 回调 Core 开放的方法。
+ // 0 表示 Core 没启用反向调用。
+ uint32 core_invoke_broker_id = 3;
}
// ==================== 网关消息 ====================
@@ -389,17 +184,10 @@ message InitRequest {
message ModelInfoProto {
string id = 1;
string name = 2;
- reserved 3; // 原 max_tokens,拆分为 context_window + max_output_tokens
- double input_price = 4;
- double output_price = 5;
- double cached_input_price = 6;
- double input_price_priority = 7;
- double output_price_priority = 8;
- double cached_input_price_priority = 9;
- int64 context_window = 10;
- int64 max_output_tokens = 11;
- double cache_creation_price = 12; // 缓存写入 5m TTL 单价($/1M token,1.25x input)
- double cache_creation_1h_price = 13; // 缓存写入 1h TTL 单价($/1M token,2.00x input)
+ int64 context_window = 3;
+ int64 max_output_tokens = 4;
+ repeated string capabilities = 5;
+ map metadata = 6;
}
message ModelsResponse {
@@ -410,6 +198,7 @@ message RouteDefinitionProto {
string method = 1;
string path = 2;
string description = 3;
+ map metadata = 4;
}
message RoutesResponse {
@@ -453,36 +242,51 @@ message UpstreamResponse {
bytes body = 3;
}
-// Usage 单次调用的 token / 费用统计。非 Success 判决下应为空。
+// UsageAttribute 是插件计算后的通用审计维度。
+message UsageAttribute {
+ string key = 1;
+ string label = 2;
+ string kind = 3;
+ string value = 4;
+ map metadata = 5;
+}
+
+// UsageMetric 是插件计算后的通用计量结果。
+message UsageMetric {
+ string key = 1;
+ string label = 2;
+ string kind = 3;
+ string unit = 4;
+ double value = 5;
+ double account_cost = 6;
+ string currency = 7;
+ map metadata = 8;
+}
+
+// UsageCostDetail 是通用费用明细。
+message UsageCostDetail {
+ string key = 1;
+ string label = 2;
+ double account_cost = 3;
+ double user_cost = 4;
+ double billing_multiplier = 5;
+ string currency = 6;
+ map metadata = 7;
+}
+
+// Usage 单次调用的用量与费用结果。非 Success 判决下应为空。
message Usage {
- int64 input_tokens = 1;
- int64 output_tokens = 2;
- int64 cached_input_tokens = 3;
- int64 cache_creation_tokens = 4;
- int64 cache_creation_5m_tokens = 5;
- int64 cache_creation_1h_tokens = 6;
- int64 reasoning_output_tokens = 7;
-
- double input_cost = 10;
- double output_cost = 11;
- double cached_input_cost = 12;
- double cache_creation_cost = 13;
-
- double input_price = 20;
- double output_price = 21;
- double cached_input_price = 22;
- double cache_creation_price = 23;
- double cache_creation_1h_price = 24;
-
- string model = 30;
- string service_tier = 31;
- int64 first_token_ms = 32;
-
- // image_size 是图像生成请求实际出图的尺寸("WxH",例如 "1024x1024"、"3840x2160")。
- // 网关侧按 1K/2K/4K 三档计费,把分档来源(实际尺寸)记下来,admin 后台 usage_log
- // 显示费用时旁边带上 size,用户能直观看出"为什么这次扣了 0.40"。
- // 非图像请求留空。
- string image_size = 33;
+ string model = 1;
+ double account_cost = 2;
+ double user_cost = 3;
+ double billing_multiplier = 4;
+ string currency = 5;
+ string summary = 6;
+ int64 first_token_ms = 7;
+ repeated UsageMetric metrics = 8;
+ repeated UsageAttribute attributes = 9;
+ repeated UsageCostDetail cost_details = 10;
+ map metadata = 11;
}
// ForwardOutcome 插件对一次 Forward 的完整判决结果。
@@ -508,15 +312,6 @@ message CredentialsRequest {
map credentials = 1;
}
-message QuotaInfoResponse {
- double total = 1;
- double used = 2;
- double remaining = 3;
- string currency = 4;
- string expires_at = 5;
- map extra = 6;
-}
-
// ==================== HTTP 代理消息 ====================
message HttpRequest {
@@ -564,7 +359,7 @@ message WebSocketFrame {
TEXT = 1; // 文本消息
BINARY = 2; // 二进制消息
CLOSE = 3; // 关闭连接
- RESULT = 4; // 连接结束,携带 ForwardResult
+ RESULT = 4; // 连接结束,携带 ForwardOutcome
}
FrameType type = 1;
@@ -603,10 +398,59 @@ message WebAssetsResponse {
bool has_assets = 2;
}
+// ==================== 插件能力清单 ====================
+
+message PayloadSchemaProto {
+ string content_type = 1;
+ string schema = 2; // JSON Schema 字符串
+ string example = 3; // JSON 示例字符串
+ map metadata = 4;
+}
+
+message RouteSchemaProto {
+ string method = 1;
+ string path = 2;
+ string summary = 3;
+ PayloadSchemaProto request = 4;
+ PayloadSchemaProto response = 5;
+ map metadata = 6;
+}
+
+message TaskSchemaProto {
+ string type = 1;
+ string summary = 2;
+ PayloadSchemaProto input = 3;
+ PayloadSchemaProto output = 4;
+ map metadata = 5;
+}
+
+message EventSchemaProto {
+ string type = 1;
+ string source = 2;
+ string summary = 3;
+ PayloadSchemaProto payload = 4;
+ map metadata = 5;
+}
+
+message InvokeSchemaProto {
+ string method = 1;
+ string summary = 2;
+ PayloadSchemaProto request = 3;
+ PayloadSchemaProto response = 4;
+ map metadata = 5;
+}
+
+message PluginSchemaResponse {
+ repeated RouteSchemaProto routes = 1;
+ repeated TaskSchemaProto tasks = 2;
+ repeated EventSchemaProto events = 3;
+ repeated InvokeSchemaProto invokes = 4;
+ map metadata = 5;
+}
+
// ==================== 中间件消息 ====================
//
// MiddlewareRequest / MiddlewareEvent / MiddlewareDecision 用于 MiddlewareService。
-// 详细字段语义见 ADR-0001 Decision 3。
// MiddlewareRequest OnForwardBegin 的输入:请求元数据 + 可选的 request body。
message MiddlewareRequest {
@@ -618,7 +462,7 @@ message MiddlewareRequest {
string platform = 5;
string model = 6;
bool stream = 7;
- int64 input_tokens_est = 8; // core 侧粗略估算,仅用于早期决策
+ repeated UsageMetric estimates = 8; // Core 或插件提供的通用预估值,仅用于早期决策
// metadata KV bag:供多个 middleware 之间传递上下文(Open Question Q-open-3)。
// 命名空间规则暂不强制,未来可能收紧。
@@ -639,30 +483,20 @@ message MiddlewareEvent {
string platform = 5;
string model = 6;
bool stream = 7;
- int64 input_tokens_est = 8; // 与 MiddlewareRequest 字段对齐:core 侧粗略估算,
- // 便于 middleware 做 estimate vs actual 比对。
+ repeated UsageMetric estimates = 8; // 与 MiddlewareRequest 字段对齐,便于 estimate vs actual 比对。
// === 响应结果 ===
int64 status_code = 20;
int64 duration_ms = 21;
- int64 input_tokens = 22;
- int64 output_tokens = 23;
- int64 cached_input_tokens = 24;
- int64 first_token_ms = 25;
- string error_kind = 26; // "" / "upstream_5xx" / "timeout" / "no_account" / ...
- string error_msg = 27; // 限长 512,见 core 实现
-
- // 费用快照(core 已计算好)
- double input_cost = 30;
- double output_cost = 31;
- double cached_input_cost = 32;
+ Usage usage = 22;
+ string error_kind = 23; // "" / "upstream_5xx" / "timeout" / "no_account" / ...
+ string error_msg = 24; // 限长 512,见 core 实现
// metadata 延续自 OnForwardBegin 的 bag
map metadata = 40;
// === 按需字段(声明了 middleware.read_body capability 的插件才会收到)===
- // 流式响应时 response_body 只给摘要(首次非空 chunk 拼装),完整流式内容
- // 留给未来的 OnStreamChunk(ADR-0002)。
+ // 流式响应时 response_body 只给摘要(首次非空 chunk 拼装)。
bytes response_body = 100;
map response_headers = 101;
}
@@ -686,3 +520,78 @@ message MiddlewareDecision {
// 贯穿式 metadata:无论 allow/deny/mutate,都能往 bag 里写东西供后续 middleware / End 使用
map metadata = 30;
}
+
+// ==================== 事件消息 ====================
+
+message EventSubscriptionProto {
+ string type = 1;
+ string source = 2;
+ map filter = 3;
+ map metadata = 4;
+}
+
+message EventSubscriptionsResponse {
+ repeated EventSubscriptionProto subscriptions = 1;
+}
+
+message PluginEvent {
+ string id = 1;
+ string type = 2;
+ string source = 3;
+ string subject = 4;
+ int64 user_id = 5;
+ int64 group_id = 6;
+ bytes payload = 7; // JSON 编码的对象
+ map metadata = 8;
+ int64 occurred_at = 9; // unix millis, 0 = unset
+}
+
+message EventHandleResponse {
+ bool success = 1;
+ string error_message = 2;
+}
+
+// ==================== Host 调用消息 ====================
+
+message HostInvokeRequest {
+ string method = 1;
+ bytes payload = 2; // JSON 编码的对象,由 Core method 自己校验 schema
+ string idempotency_key = 3; // 副作用方法的幂等键;只读方法可留空
+ map metadata = 4; // 调用级辅助信息,不用于替代权限、调度或核心业务字段
+}
+
+message HostInvokeResponse {
+ string status = 1; // method 业务状态;传输/鉴权/schema 错误走 gRPC error
+ bytes payload = 2; // JSON 编码的对象
+ map metadata = 3; // 调用级辅助信息
+}
+
+message HostStreamFrame {
+ string method = 1; // 首个 client frame 必填;后续 frame 可留空
+ string event = 2; // method 内部约定的帧类型
+ bytes payload = 3; // JSON 编码的对象
+ string idempotency_key = 4; // 首个 client frame 使用
+ map metadata = 5; // 调用级或帧级辅助信息
+ bool done = 6; // method 业务最终帧;传输结束仍以 stream EOF 为准
+ string status = 7; // method 业务状态,通常只在最终帧使用
+}
+
+// ==================== 异步任务 ====================
+
+// Core → Extension (task dispatch)
+
+message ProcessTaskRequest {
+ int64 task_id = 1;
+ string task_type = 2;
+ bytes input = 3; // JSON
+ int64 user_id = 4;
+}
+
+message ProcessTaskResponse {
+ bool success = 1;
+ string error_message = 2;
+}
+
+message TaskTypesResponse {
+ repeated string types = 1;
+}
diff --git a/proto/plugin_grpc.pb.go b/protocol/proto/plugin_grpc.pb.go
similarity index 68%
rename from proto/plugin_grpc.pb.go
rename to protocol/proto/plugin_grpc.pb.go
index c1e1487..556b87b 100644
--- a/proto/plugin_grpc.pb.go
+++ b/protocol/proto/plugin_grpc.pb.go
@@ -19,13 +19,14 @@ import (
const _ = grpc.SupportPackageIsVersion9
const (
- PluginService_GetInfo_FullMethodName = "/airgate.plugin.v1.PluginService/GetInfo"
- PluginService_Init_FullMethodName = "/airgate.plugin.v1.PluginService/Init"
- PluginService_Start_FullMethodName = "/airgate.plugin.v1.PluginService/Start"
- PluginService_Stop_FullMethodName = "/airgate.plugin.v1.PluginService/Stop"
- PluginService_GetWebAssets_FullMethodName = "/airgate.plugin.v1.PluginService/GetWebAssets"
- PluginService_HealthCheck_FullMethodName = "/airgate.plugin.v1.PluginService/HealthCheck"
- PluginService_HandleRequest_FullMethodName = "/airgate.plugin.v1.PluginService/HandleRequest"
+ PluginService_GetInfo_FullMethodName = "/airgate.plugin.v2.PluginService/GetInfo"
+ PluginService_Init_FullMethodName = "/airgate.plugin.v2.PluginService/Init"
+ PluginService_Start_FullMethodName = "/airgate.plugin.v2.PluginService/Start"
+ PluginService_Stop_FullMethodName = "/airgate.plugin.v2.PluginService/Stop"
+ PluginService_GetWebAssets_FullMethodName = "/airgate.plugin.v2.PluginService/GetWebAssets"
+ PluginService_GetSchema_FullMethodName = "/airgate.plugin.v2.PluginService/GetSchema"
+ PluginService_HealthCheck_FullMethodName = "/airgate.plugin.v2.PluginService/HealthCheck"
+ PluginService_HandleRequest_FullMethodName = "/airgate.plugin.v2.PluginService/HandleRequest"
)
// PluginServiceClient is the client API for PluginService service.
@@ -38,6 +39,8 @@ type PluginServiceClient interface {
Stop(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*Empty, error)
// 获取插件的前端静态资源
GetWebAssets(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*WebAssetsResponse, error)
+ // 获取插件结构化能力清单(可选,未实现时返回空)
+ GetSchema(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*PluginSchemaResponse, error)
// 健康检查(可选,插件未实现时返回成功)
HealthCheck(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*Empty, error)
// 通用请求代理:Core 透传,插件自行路由(可选,插件未实现时返回 501)
@@ -102,6 +105,16 @@ func (c *pluginServiceClient) GetWebAssets(ctx context.Context, in *Empty, opts
return out, nil
}
+func (c *pluginServiceClient) GetSchema(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*PluginSchemaResponse, error) {
+ cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
+ out := new(PluginSchemaResponse)
+ err := c.cc.Invoke(ctx, PluginService_GetSchema_FullMethodName, in, out, cOpts...)
+ if err != nil {
+ return nil, err
+ }
+ return out, nil
+}
+
func (c *pluginServiceClient) HealthCheck(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(Empty)
@@ -132,6 +145,8 @@ type PluginServiceServer interface {
Stop(context.Context, *Empty) (*Empty, error)
// 获取插件的前端静态资源
GetWebAssets(context.Context, *Empty) (*WebAssetsResponse, error)
+ // 获取插件结构化能力清单(可选,未实现时返回空)
+ GetSchema(context.Context, *Empty) (*PluginSchemaResponse, error)
// 健康检查(可选,插件未实现时返回成功)
HealthCheck(context.Context, *Empty) (*Empty, error)
// 通用请求代理:Core 透传,插件自行路由(可选,插件未实现时返回 501)
@@ -161,6 +176,9 @@ func (UnimplementedPluginServiceServer) Stop(context.Context, *Empty) (*Empty, e
func (UnimplementedPluginServiceServer) GetWebAssets(context.Context, *Empty) (*WebAssetsResponse, error) {
return nil, status.Error(codes.Unimplemented, "method GetWebAssets not implemented")
}
+func (UnimplementedPluginServiceServer) GetSchema(context.Context, *Empty) (*PluginSchemaResponse, error) {
+ return nil, status.Error(codes.Unimplemented, "method GetSchema not implemented")
+}
func (UnimplementedPluginServiceServer) HealthCheck(context.Context, *Empty) (*Empty, error) {
return nil, status.Error(codes.Unimplemented, "method HealthCheck not implemented")
}
@@ -278,6 +296,24 @@ func _PluginService_GetWebAssets_Handler(srv interface{}, ctx context.Context, d
return interceptor(ctx, in, info, handler)
}
+func _PluginService_GetSchema_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+ in := new(Empty)
+ if err := dec(in); err != nil {
+ return nil, err
+ }
+ if interceptor == nil {
+ return srv.(PluginServiceServer).GetSchema(ctx, in)
+ }
+ info := &grpc.UnaryServerInfo{
+ Server: srv,
+ FullMethod: PluginService_GetSchema_FullMethodName,
+ }
+ handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+ return srv.(PluginServiceServer).GetSchema(ctx, req.(*Empty))
+ }
+ return interceptor(ctx, in, info, handler)
+}
+
func _PluginService_HealthCheck_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(Empty)
if err := dec(in); err != nil {
@@ -318,7 +354,7 @@ func _PluginService_HandleRequest_Handler(srv interface{}, ctx context.Context,
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var PluginService_ServiceDesc = grpc.ServiceDesc{
- ServiceName: "airgate.plugin.v1.PluginService",
+ ServiceName: "airgate.plugin.v2.PluginService",
HandlerType: (*PluginServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
@@ -341,6 +377,10 @@ var PluginService_ServiceDesc = grpc.ServiceDesc{
MethodName: "GetWebAssets",
Handler: _PluginService_GetWebAssets_Handler,
},
+ {
+ MethodName: "GetSchema",
+ Handler: _PluginService_GetSchema_Handler,
+ },
{
MethodName: "HealthCheck",
Handler: _PluginService_HealthCheck_Handler,
@@ -355,14 +395,13 @@ var PluginService_ServiceDesc = grpc.ServiceDesc{
}
const (
- GatewayService_GetPlatform_FullMethodName = "/airgate.plugin.v1.GatewayService/GetPlatform"
- GatewayService_GetModels_FullMethodName = "/airgate.plugin.v1.GatewayService/GetModels"
- GatewayService_GetRoutes_FullMethodName = "/airgate.plugin.v1.GatewayService/GetRoutes"
- GatewayService_Forward_FullMethodName = "/airgate.plugin.v1.GatewayService/Forward"
- GatewayService_ForwardStream_FullMethodName = "/airgate.plugin.v1.GatewayService/ForwardStream"
- GatewayService_ValidateAccount_FullMethodName = "/airgate.plugin.v1.GatewayService/ValidateAccount"
- GatewayService_QueryQuota_FullMethodName = "/airgate.plugin.v1.GatewayService/QueryQuota"
- GatewayService_HandleWebSocket_FullMethodName = "/airgate.plugin.v1.GatewayService/HandleWebSocket"
+ GatewayService_GetPlatform_FullMethodName = "/airgate.plugin.v2.GatewayService/GetPlatform"
+ GatewayService_GetModels_FullMethodName = "/airgate.plugin.v2.GatewayService/GetModels"
+ GatewayService_GetRoutes_FullMethodName = "/airgate.plugin.v2.GatewayService/GetRoutes"
+ GatewayService_Forward_FullMethodName = "/airgate.plugin.v2.GatewayService/Forward"
+ GatewayService_ForwardStream_FullMethodName = "/airgate.plugin.v2.GatewayService/ForwardStream"
+ GatewayService_ValidateAccount_FullMethodName = "/airgate.plugin.v2.GatewayService/ValidateAccount"
+ GatewayService_HandleWebSocket_FullMethodName = "/airgate.plugin.v2.GatewayService/HandleWebSocket"
)
// GatewayServiceClient is the client API for GatewayService service.
@@ -375,7 +414,6 @@ type GatewayServiceClient interface {
Forward(ctx context.Context, in *ForwardRequest, opts ...grpc.CallOption) (*ForwardOutcome, error)
ForwardStream(ctx context.Context, in *ForwardRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[ForwardChunk], error)
ValidateAccount(ctx context.Context, in *CredentialsRequest, opts ...grpc.CallOption) (*Empty, error)
- QueryQuota(ctx context.Context, in *CredentialsRequest, opts ...grpc.CallOption) (*QuotaInfoResponse, error)
// WebSocket 双向流
HandleWebSocket(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[WebSocketFrame, WebSocketFrame], error)
}
@@ -457,16 +495,6 @@ func (c *gatewayServiceClient) ValidateAccount(ctx context.Context, in *Credenti
return out, nil
}
-func (c *gatewayServiceClient) QueryQuota(ctx context.Context, in *CredentialsRequest, opts ...grpc.CallOption) (*QuotaInfoResponse, error) {
- cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
- out := new(QuotaInfoResponse)
- err := c.cc.Invoke(ctx, GatewayService_QueryQuota_FullMethodName, in, out, cOpts...)
- if err != nil {
- return nil, err
- }
- return out, nil
-}
-
func (c *gatewayServiceClient) HandleWebSocket(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[WebSocketFrame, WebSocketFrame], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &GatewayService_ServiceDesc.Streams[1], GatewayService_HandleWebSocket_FullMethodName, cOpts...)
@@ -490,7 +518,6 @@ type GatewayServiceServer interface {
Forward(context.Context, *ForwardRequest) (*ForwardOutcome, error)
ForwardStream(*ForwardRequest, grpc.ServerStreamingServer[ForwardChunk]) error
ValidateAccount(context.Context, *CredentialsRequest) (*Empty, error)
- QueryQuota(context.Context, *CredentialsRequest) (*QuotaInfoResponse, error)
// WebSocket 双向流
HandleWebSocket(grpc.BidiStreamingServer[WebSocketFrame, WebSocketFrame]) error
mustEmbedUnimplementedGatewayServiceServer()
@@ -521,9 +548,6 @@ func (UnimplementedGatewayServiceServer) ForwardStream(*ForwardRequest, grpc.Ser
func (UnimplementedGatewayServiceServer) ValidateAccount(context.Context, *CredentialsRequest) (*Empty, error) {
return nil, status.Error(codes.Unimplemented, "method ValidateAccount not implemented")
}
-func (UnimplementedGatewayServiceServer) QueryQuota(context.Context, *CredentialsRequest) (*QuotaInfoResponse, error) {
- return nil, status.Error(codes.Unimplemented, "method QueryQuota not implemented")
-}
func (UnimplementedGatewayServiceServer) HandleWebSocket(grpc.BidiStreamingServer[WebSocketFrame, WebSocketFrame]) error {
return status.Error(codes.Unimplemented, "method HandleWebSocket not implemented")
}
@@ -649,24 +673,6 @@ func _GatewayService_ValidateAccount_Handler(srv interface{}, ctx context.Contex
return interceptor(ctx, in, info, handler)
}
-func _GatewayService_QueryQuota_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
- in := new(CredentialsRequest)
- if err := dec(in); err != nil {
- return nil, err
- }
- if interceptor == nil {
- return srv.(GatewayServiceServer).QueryQuota(ctx, in)
- }
- info := &grpc.UnaryServerInfo{
- Server: srv,
- FullMethod: GatewayService_QueryQuota_FullMethodName,
- }
- handler := func(ctx context.Context, req interface{}) (interface{}, error) {
- return srv.(GatewayServiceServer).QueryQuota(ctx, req.(*CredentialsRequest))
- }
- return interceptor(ctx, in, info, handler)
-}
-
func _GatewayService_HandleWebSocket_Handler(srv interface{}, stream grpc.ServerStream) error {
return srv.(GatewayServiceServer).HandleWebSocket(&grpc.GenericServerStream[WebSocketFrame, WebSocketFrame]{ServerStream: stream})
}
@@ -678,7 +684,7 @@ type GatewayService_HandleWebSocketServer = grpc.BidiStreamingServer[WebSocketFr
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var GatewayService_ServiceDesc = grpc.ServiceDesc{
- ServiceName: "airgate.plugin.v1.GatewayService",
+ ServiceName: "airgate.plugin.v2.GatewayService",
HandlerType: (*GatewayServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
@@ -701,10 +707,6 @@ var GatewayService_ServiceDesc = grpc.ServiceDesc{
MethodName: "ValidateAccount",
Handler: _GatewayService_ValidateAccount_Handler,
},
- {
- MethodName: "QueryQuota",
- Handler: _GatewayService_QueryQuota_Handler,
- },
},
Streams: []grpc.StreamDesc{
{
@@ -723,11 +725,13 @@ var GatewayService_ServiceDesc = grpc.ServiceDesc{
}
const (
- ExtensionService_Migrate_FullMethodName = "/airgate.plugin.v1.ExtensionService/Migrate"
- ExtensionService_GetBackgroundTasks_FullMethodName = "/airgate.plugin.v1.ExtensionService/GetBackgroundTasks"
- ExtensionService_RunBackgroundTask_FullMethodName = "/airgate.plugin.v1.ExtensionService/RunBackgroundTask"
- ExtensionService_HandleRequest_FullMethodName = "/airgate.plugin.v1.ExtensionService/HandleRequest"
- ExtensionService_HandleStreamRequest_FullMethodName = "/airgate.plugin.v1.ExtensionService/HandleStreamRequest"
+ ExtensionService_Migrate_FullMethodName = "/airgate.plugin.v2.ExtensionService/Migrate"
+ ExtensionService_GetBackgroundTasks_FullMethodName = "/airgate.plugin.v2.ExtensionService/GetBackgroundTasks"
+ ExtensionService_RunBackgroundTask_FullMethodName = "/airgate.plugin.v2.ExtensionService/RunBackgroundTask"
+ ExtensionService_HandleRequest_FullMethodName = "/airgate.plugin.v2.ExtensionService/HandleRequest"
+ ExtensionService_HandleStreamRequest_FullMethodName = "/airgate.plugin.v2.ExtensionService/HandleStreamRequest"
+ ExtensionService_ProcessTask_FullMethodName = "/airgate.plugin.v2.ExtensionService/ProcessTask"
+ ExtensionService_GetTaskTypes_FullMethodName = "/airgate.plugin.v2.ExtensionService/GetTaskTypes"
)
// ExtensionServiceClient is the client API for ExtensionService service.
@@ -741,6 +745,10 @@ type ExtensionServiceClient interface {
// HTTP 请求由核心代理到插件
HandleRequest(ctx context.Context, in *HttpRequest, opts ...grpc.CallOption) (*HttpResponse, error)
HandleStreamRequest(ctx context.Context, in *HttpRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[HttpResponseChunk], error)
+ // 异步任务:Core 分发 pending 任务给插件处理
+ ProcessTask(ctx context.Context, in *ProcessTaskRequest, opts ...grpc.CallOption) (*ProcessTaskResponse, error)
+ // 返回此插件支持的任务类型列表
+ GetTaskTypes(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*TaskTypesResponse, error)
}
type extensionServiceClient struct {
@@ -810,6 +818,26 @@ func (c *extensionServiceClient) HandleStreamRequest(ctx context.Context, in *Ht
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type ExtensionService_HandleStreamRequestClient = grpc.ServerStreamingClient[HttpResponseChunk]
+func (c *extensionServiceClient) ProcessTask(ctx context.Context, in *ProcessTaskRequest, opts ...grpc.CallOption) (*ProcessTaskResponse, error) {
+ cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
+ out := new(ProcessTaskResponse)
+ err := c.cc.Invoke(ctx, ExtensionService_ProcessTask_FullMethodName, in, out, cOpts...)
+ if err != nil {
+ return nil, err
+ }
+ return out, nil
+}
+
+func (c *extensionServiceClient) GetTaskTypes(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*TaskTypesResponse, error) {
+ cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
+ out := new(TaskTypesResponse)
+ err := c.cc.Invoke(ctx, ExtensionService_GetTaskTypes_FullMethodName, in, out, cOpts...)
+ if err != nil {
+ return nil, err
+ }
+ return out, nil
+}
+
// ExtensionServiceServer is the server API for ExtensionService service.
// All implementations must embed UnimplementedExtensionServiceServer
// for forward compatibility.
@@ -821,6 +849,10 @@ type ExtensionServiceServer interface {
// HTTP 请求由核心代理到插件
HandleRequest(context.Context, *HttpRequest) (*HttpResponse, error)
HandleStreamRequest(*HttpRequest, grpc.ServerStreamingServer[HttpResponseChunk]) error
+ // 异步任务:Core 分发 pending 任务给插件处理
+ ProcessTask(context.Context, *ProcessTaskRequest) (*ProcessTaskResponse, error)
+ // 返回此插件支持的任务类型列表
+ GetTaskTypes(context.Context, *Empty) (*TaskTypesResponse, error)
mustEmbedUnimplementedExtensionServiceServer()
}
@@ -846,6 +878,12 @@ func (UnimplementedExtensionServiceServer) HandleRequest(context.Context, *HttpR
func (UnimplementedExtensionServiceServer) HandleStreamRequest(*HttpRequest, grpc.ServerStreamingServer[HttpResponseChunk]) error {
return status.Error(codes.Unimplemented, "method HandleStreamRequest not implemented")
}
+func (UnimplementedExtensionServiceServer) ProcessTask(context.Context, *ProcessTaskRequest) (*ProcessTaskResponse, error) {
+ return nil, status.Error(codes.Unimplemented, "method ProcessTask not implemented")
+}
+func (UnimplementedExtensionServiceServer) GetTaskTypes(context.Context, *Empty) (*TaskTypesResponse, error) {
+ return nil, status.Error(codes.Unimplemented, "method GetTaskTypes not implemented")
+}
func (UnimplementedExtensionServiceServer) mustEmbedUnimplementedExtensionServiceServer() {}
func (UnimplementedExtensionServiceServer) testEmbeddedByValue() {}
@@ -950,11 +988,47 @@ func _ExtensionService_HandleStreamRequest_Handler(srv interface{}, stream grpc.
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type ExtensionService_HandleStreamRequestServer = grpc.ServerStreamingServer[HttpResponseChunk]
+func _ExtensionService_ProcessTask_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+ in := new(ProcessTaskRequest)
+ if err := dec(in); err != nil {
+ return nil, err
+ }
+ if interceptor == nil {
+ return srv.(ExtensionServiceServer).ProcessTask(ctx, in)
+ }
+ info := &grpc.UnaryServerInfo{
+ Server: srv,
+ FullMethod: ExtensionService_ProcessTask_FullMethodName,
+ }
+ handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+ return srv.(ExtensionServiceServer).ProcessTask(ctx, req.(*ProcessTaskRequest))
+ }
+ return interceptor(ctx, in, info, handler)
+}
+
+func _ExtensionService_GetTaskTypes_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+ in := new(Empty)
+ if err := dec(in); err != nil {
+ return nil, err
+ }
+ if interceptor == nil {
+ return srv.(ExtensionServiceServer).GetTaskTypes(ctx, in)
+ }
+ info := &grpc.UnaryServerInfo{
+ Server: srv,
+ FullMethod: ExtensionService_GetTaskTypes_FullMethodName,
+ }
+ handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+ return srv.(ExtensionServiceServer).GetTaskTypes(ctx, req.(*Empty))
+ }
+ return interceptor(ctx, in, info, handler)
+}
+
// ExtensionService_ServiceDesc is the grpc.ServiceDesc for ExtensionService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var ExtensionService_ServiceDesc = grpc.ServiceDesc{
- ServiceName: "airgate.plugin.v1.ExtensionService",
+ ServiceName: "airgate.plugin.v2.ExtensionService",
HandlerType: (*ExtensionServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
@@ -973,6 +1047,14 @@ var ExtensionService_ServiceDesc = grpc.ServiceDesc{
MethodName: "HandleRequest",
Handler: _ExtensionService_HandleRequest_Handler,
},
+ {
+ MethodName: "ProcessTask",
+ Handler: _ExtensionService_ProcessTask_Handler,
+ },
+ {
+ MethodName: "GetTaskTypes",
+ Handler: _ExtensionService_GetTaskTypes_Handler,
+ },
},
Streams: []grpc.StreamDesc{
{
@@ -985,8 +1067,8 @@ var ExtensionService_ServiceDesc = grpc.ServiceDesc{
}
const (
- MiddlewareService_OnForwardBegin_FullMethodName = "/airgate.plugin.v1.MiddlewareService/OnForwardBegin"
- MiddlewareService_OnForwardEnd_FullMethodName = "/airgate.plugin.v1.MiddlewareService/OnForwardEnd"
+ MiddlewareService_OnForwardBegin_FullMethodName = "/airgate.plugin.v2.MiddlewareService/OnForwardBegin"
+ MiddlewareService_OnForwardEnd_FullMethodName = "/airgate.plugin.v2.MiddlewareService/OnForwardEnd"
)
// MiddlewareServiceClient is the client API for MiddlewareService service.
@@ -999,14 +1081,14 @@ const (
// - OnForwardBegin — 选完账号 / 还没调 upstream 之前,能改 headers / 拒绝请求
// - OnForwardEnd — upstream 返回之后 / 写 usage_log 之前,拿到完整 metadata
//
-// 设计原则(详见 ADR-0001 Decision 2/3):
+// 设计原则:
// 1. middleware 挂了不能 block 生产:返回 error 只 log warn,流程继续;
// 只有 OnForwardBegin 明确返回 Action=DENY 才会拒绝请求
// 2. 多个 middleware 按 priority 排序。Begin 升序、End 降序(LIFO)
// 3. payload 两段式:默认只传元数据,声明 middleware.read_body capability 的插件
// 才会收到 request_body / response_body
-// 4. 流式响应的 response_body 只给摘要(首次非空 chunk 拼装),完整流式内容留给
-// 未来的 OnStreamChunk(ADR-0002)
+// 4. 流式响应的 response_body 只给摘要(首次非空 chunk 拼装),完整流式内容应由
+// 独立的流式事件能力承载
//
// 新角色:middleware 插件不是 gateway(不替代 upstream),也不是 extension
// (不跑后台任务 + 自定义 HTTP)。它在 PluginInfo.type = "middleware" 中声明。
@@ -1053,14 +1135,14 @@ func (c *middlewareServiceClient) OnForwardEnd(ctx context.Context, in *Middlewa
// - OnForwardBegin — 选完账号 / 还没调 upstream 之前,能改 headers / 拒绝请求
// - OnForwardEnd — upstream 返回之后 / 写 usage_log 之前,拿到完整 metadata
//
-// 设计原则(详见 ADR-0001 Decision 2/3):
+// 设计原则:
// 1. middleware 挂了不能 block 生产:返回 error 只 log warn,流程继续;
// 只有 OnForwardBegin 明确返回 Action=DENY 才会拒绝请求
// 2. 多个 middleware 按 priority 排序。Begin 升序、End 降序(LIFO)
// 3. payload 两段式:默认只传元数据,声明 middleware.read_body capability 的插件
// 才会收到 request_body / response_body
-// 4. 流式响应的 response_body 只给摘要(首次非空 chunk 拼装),完整流式内容留给
-// 未来的 OnStreamChunk(ADR-0002)
+// 4. 流式响应的 response_body 只给摘要(首次非空 chunk 拼装),完整流式内容应由
+// 独立的流式事件能力承载
//
// 新角色:middleware 插件不是 gateway(不替代 upstream),也不是 extension
// (不跑后台任务 + 自定义 HTTP)。它在 PluginInfo.type = "middleware" 中声明。
@@ -1144,7 +1226,7 @@ func _MiddlewareService_OnForwardEnd_Handler(srv interface{}, ctx context.Contex
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var MiddlewareService_ServiceDesc = grpc.ServiceDesc{
- ServiceName: "airgate.plugin.v1.MiddlewareService",
+ ServiceName: "airgate.plugin.v2.MiddlewareService",
HandlerType: (*MiddlewareServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
@@ -1161,550 +1243,287 @@ var MiddlewareService_ServiceDesc = grpc.ServiceDesc{
}
const (
- HostService_SelectAccount_FullMethodName = "/airgate.plugin.v1.HostService/SelectAccount"
- HostService_ReportAccountResult_FullMethodName = "/airgate.plugin.v1.HostService/ReportAccountResult"
- HostService_ProbeForward_FullMethodName = "/airgate.plugin.v1.HostService/ProbeForward"
- HostService_Forward_FullMethodName = "/airgate.plugin.v1.HostService/Forward"
- HostService_ForwardStream_FullMethodName = "/airgate.plugin.v1.HostService/ForwardStream"
- HostService_ListGroups_FullMethodName = "/airgate.plugin.v1.HostService/ListGroups"
- HostService_ListPlatforms_FullMethodName = "/airgate.plugin.v1.HostService/ListPlatforms"
- HostService_ListModels_FullMethodName = "/airgate.plugin.v1.HostService/ListModels"
- HostService_GetUserInfo_FullMethodName = "/airgate.plugin.v1.HostService/GetUserInfo"
- HostService_StoreAsset_FullMethodName = "/airgate.plugin.v1.HostService/StoreAsset"
- HostService_GetAssetURL_FullMethodName = "/airgate.plugin.v1.HostService/GetAssetURL"
- HostService_GetAssetBytes_FullMethodName = "/airgate.plugin.v1.HostService/GetAssetBytes"
+ EventService_GetEventSubscriptions_FullMethodName = "/airgate.plugin.v2.EventService/GetEventSubscriptions"
+ EventService_HandleEvent_FullMethodName = "/airgate.plugin.v2.EventService/HandleEvent"
)
-// HostServiceClient is the client API for HostService service.
+// EventServiceClient is the client API for EventService service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
-type HostServiceClient interface {
- // 选号:根据 (group_id, model) 走和真实用户请求完全相同的调度路径。
- SelectAccount(ctx context.Context, in *HostSelectAccountRequest, opts ...grpc.CallOption) (*HostSelectAccountResponse, error)
- // 把账号调用结果反馈给 scheduler 的失败计数器/状态机。
- ReportAccountResult(ctx context.Context, in *HostReportAccountResultRequest, opts ...grpc.CallOption) (*Empty, error)
- // 黑盒探测:内部组装一次最小的 chat completion 请求并直接执行。
- // 跳过 usage_log 写入、跳过用户余额扣款,仍然 ReportResult 反哺账号状态机。
- ProbeForward(ctx context.Context, in *HostProbeForwardRequest, opts ...grpc.CallOption) (*HostProbeForwardResponse, error)
- // 非流式业务转发:走完整管线(调度 → 网关插件 → 计费 → usage_log)。
- // 调用方需提供 user_id + group_id 以确定计费主体和调度路径。
- Forward(ctx context.Context, in *HostForwardRequest, opts ...grpc.CallOption) (*HostForwardResponse, error)
- // 流式业务转发:与 Forward 相同管线,结果通过 server stream 逐块返回。
- // 最后一块 done=true 携带 usage 信息(计费已在 Core 侧完成)。
- ForwardStream(ctx context.Context, in *HostForwardRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[HostForwardChunk], error)
- // 列出所有分组。
- ListGroups(ctx context.Context, in *HostListGroupsRequest, opts ...grpc.CallOption) (*HostListGroupsResponse, error)
- // 列出已加载的网关平台(每个 gateway 插件对应一个 platform)。
- ListPlatforms(ctx context.Context, in *HostListPlatformsRequest, opts ...grpc.CallOption) (*HostListPlatformsResponse, error)
- // 列出指定平台的模型列表。
- ListModels(ctx context.Context, in *HostListModelsRequest, opts ...grpc.CallOption) (*HostListModelsResponse, error)
- // 获取用户基本信息(余额、角色、状态)。
- GetUserInfo(ctx context.Context, in *HostGetUserInfoRequest, opts ...grpc.CallOption) (*HostGetUserInfoResponse, error)
- // 资产存储:由 Core 根据全局 storage 设置选择 MinIO/S3 或本地磁盘。
- StoreAsset(ctx context.Context, in *HostStoreAssetRequest, opts ...grpc.CallOption) (*HostStoreAssetResponse, error)
- GetAssetURL(ctx context.Context, in *HostGetAssetURLRequest, opts ...grpc.CallOption) (*HostGetAssetURLResponse, error)
- GetAssetBytes(ctx context.Context, in *HostGetAssetBytesRequest, opts ...grpc.CallOption) (*HostGetAssetBytesResponse, error)
-}
-
-type hostServiceClient struct {
- cc grpc.ClientConnInterface
-}
-
-func NewHostServiceClient(cc grpc.ClientConnInterface) HostServiceClient {
- return &hostServiceClient{cc}
-}
-
-func (c *hostServiceClient) SelectAccount(ctx context.Context, in *HostSelectAccountRequest, opts ...grpc.CallOption) (*HostSelectAccountResponse, error) {
- cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
- out := new(HostSelectAccountResponse)
- err := c.cc.Invoke(ctx, HostService_SelectAccount_FullMethodName, in, out, cOpts...)
- if err != nil {
- return nil, err
- }
- return out, nil
-}
-
-func (c *hostServiceClient) ReportAccountResult(ctx context.Context, in *HostReportAccountResultRequest, opts ...grpc.CallOption) (*Empty, error) {
- cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
- out := new(Empty)
- err := c.cc.Invoke(ctx, HostService_ReportAccountResult_FullMethodName, in, out, cOpts...)
- if err != nil {
- return nil, err
- }
- return out, nil
-}
-
-func (c *hostServiceClient) ProbeForward(ctx context.Context, in *HostProbeForwardRequest, opts ...grpc.CallOption) (*HostProbeForwardResponse, error) {
- cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
- out := new(HostProbeForwardResponse)
- err := c.cc.Invoke(ctx, HostService_ProbeForward_FullMethodName, in, out, cOpts...)
- if err != nil {
- return nil, err
- }
- return out, nil
-}
-
-func (c *hostServiceClient) Forward(ctx context.Context, in *HostForwardRequest, opts ...grpc.CallOption) (*HostForwardResponse, error) {
- cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
- out := new(HostForwardResponse)
- err := c.cc.Invoke(ctx, HostService_Forward_FullMethodName, in, out, cOpts...)
- if err != nil {
- return nil, err
- }
- return out, nil
-}
-
-func (c *hostServiceClient) ForwardStream(ctx context.Context, in *HostForwardRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[HostForwardChunk], error) {
- cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
- stream, err := c.cc.NewStream(ctx, &HostService_ServiceDesc.Streams[0], HostService_ForwardStream_FullMethodName, cOpts...)
- if err != nil {
- return nil, err
- }
- x := &grpc.GenericClientStream[HostForwardRequest, HostForwardChunk]{ClientStream: stream}
- if err := x.ClientStream.SendMsg(in); err != nil {
- return nil, err
- }
- if err := x.ClientStream.CloseSend(); err != nil {
- return nil, err
- }
- return x, nil
+type EventServiceClient interface {
+ GetEventSubscriptions(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*EventSubscriptionsResponse, error)
+ HandleEvent(ctx context.Context, in *PluginEvent, opts ...grpc.CallOption) (*EventHandleResponse, error)
}
-// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
-type HostService_ForwardStreamClient = grpc.ServerStreamingClient[HostForwardChunk]
-
-func (c *hostServiceClient) ListGroups(ctx context.Context, in *HostListGroupsRequest, opts ...grpc.CallOption) (*HostListGroupsResponse, error) {
- cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
- out := new(HostListGroupsResponse)
- err := c.cc.Invoke(ctx, HostService_ListGroups_FullMethodName, in, out, cOpts...)
- if err != nil {
- return nil, err
- }
- return out, nil
-}
-
-func (c *hostServiceClient) ListPlatforms(ctx context.Context, in *HostListPlatformsRequest, opts ...grpc.CallOption) (*HostListPlatformsResponse, error) {
- cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
- out := new(HostListPlatformsResponse)
- err := c.cc.Invoke(ctx, HostService_ListPlatforms_FullMethodName, in, out, cOpts...)
- if err != nil {
- return nil, err
- }
- return out, nil
+type eventServiceClient struct {
+ cc grpc.ClientConnInterface
}
-func (c *hostServiceClient) ListModels(ctx context.Context, in *HostListModelsRequest, opts ...grpc.CallOption) (*HostListModelsResponse, error) {
- cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
- out := new(HostListModelsResponse)
- err := c.cc.Invoke(ctx, HostService_ListModels_FullMethodName, in, out, cOpts...)
- if err != nil {
- return nil, err
- }
- return out, nil
+func NewEventServiceClient(cc grpc.ClientConnInterface) EventServiceClient {
+ return &eventServiceClient{cc}
}
-func (c *hostServiceClient) GetUserInfo(ctx context.Context, in *HostGetUserInfoRequest, opts ...grpc.CallOption) (*HostGetUserInfoResponse, error) {
+func (c *eventServiceClient) GetEventSubscriptions(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*EventSubscriptionsResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
- out := new(HostGetUserInfoResponse)
- err := c.cc.Invoke(ctx, HostService_GetUserInfo_FullMethodName, in, out, cOpts...)
+ out := new(EventSubscriptionsResponse)
+ err := c.cc.Invoke(ctx, EventService_GetEventSubscriptions_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
-func (c *hostServiceClient) StoreAsset(ctx context.Context, in *HostStoreAssetRequest, opts ...grpc.CallOption) (*HostStoreAssetResponse, error) {
+func (c *eventServiceClient) HandleEvent(ctx context.Context, in *PluginEvent, opts ...grpc.CallOption) (*EventHandleResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
- out := new(HostStoreAssetResponse)
- err := c.cc.Invoke(ctx, HostService_StoreAsset_FullMethodName, in, out, cOpts...)
+ out := new(EventHandleResponse)
+ err := c.cc.Invoke(ctx, EventService_HandleEvent_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
-func (c *hostServiceClient) GetAssetURL(ctx context.Context, in *HostGetAssetURLRequest, opts ...grpc.CallOption) (*HostGetAssetURLResponse, error) {
- cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
- out := new(HostGetAssetURLResponse)
- err := c.cc.Invoke(ctx, HostService_GetAssetURL_FullMethodName, in, out, cOpts...)
- if err != nil {
- return nil, err
- }
- return out, nil
-}
-
-func (c *hostServiceClient) GetAssetBytes(ctx context.Context, in *HostGetAssetBytesRequest, opts ...grpc.CallOption) (*HostGetAssetBytesResponse, error) {
- cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
- out := new(HostGetAssetBytesResponse)
- err := c.cc.Invoke(ctx, HostService_GetAssetBytes_FullMethodName, in, out, cOpts...)
- if err != nil {
- return nil, err
- }
- return out, nil
+// EventServiceServer is the server API for EventService service.
+// All implementations must embed UnimplementedEventServiceServer
+// for forward compatibility.
+type EventServiceServer interface {
+ GetEventSubscriptions(context.Context, *Empty) (*EventSubscriptionsResponse, error)
+ HandleEvent(context.Context, *PluginEvent) (*EventHandleResponse, error)
+ mustEmbedUnimplementedEventServiceServer()
}
-// HostServiceServer is the server API for HostService service.
-// All implementations must embed UnimplementedHostServiceServer
-// for forward compatibility.
-type HostServiceServer interface {
- // 选号:根据 (group_id, model) 走和真实用户请求完全相同的调度路径。
- SelectAccount(context.Context, *HostSelectAccountRequest) (*HostSelectAccountResponse, error)
- // 把账号调用结果反馈给 scheduler 的失败计数器/状态机。
- ReportAccountResult(context.Context, *HostReportAccountResultRequest) (*Empty, error)
- // 黑盒探测:内部组装一次最小的 chat completion 请求并直接执行。
- // 跳过 usage_log 写入、跳过用户余额扣款,仍然 ReportResult 反哺账号状态机。
- ProbeForward(context.Context, *HostProbeForwardRequest) (*HostProbeForwardResponse, error)
- // 非流式业务转发:走完整管线(调度 → 网关插件 → 计费 → usage_log)。
- // 调用方需提供 user_id + group_id 以确定计费主体和调度路径。
- Forward(context.Context, *HostForwardRequest) (*HostForwardResponse, error)
- // 流式业务转发:与 Forward 相同管线,结果通过 server stream 逐块返回。
- // 最后一块 done=true 携带 usage 信息(计费已在 Core 侧完成)。
- ForwardStream(*HostForwardRequest, grpc.ServerStreamingServer[HostForwardChunk]) error
- // 列出所有分组。
- ListGroups(context.Context, *HostListGroupsRequest) (*HostListGroupsResponse, error)
- // 列出已加载的网关平台(每个 gateway 插件对应一个 platform)。
- ListPlatforms(context.Context, *HostListPlatformsRequest) (*HostListPlatformsResponse, error)
- // 列出指定平台的模型列表。
- ListModels(context.Context, *HostListModelsRequest) (*HostListModelsResponse, error)
- // 获取用户基本信息(余额、角色、状态)。
- GetUserInfo(context.Context, *HostGetUserInfoRequest) (*HostGetUserInfoResponse, error)
- // 资产存储:由 Core 根据全局 storage 设置选择 MinIO/S3 或本地磁盘。
- StoreAsset(context.Context, *HostStoreAssetRequest) (*HostStoreAssetResponse, error)
- GetAssetURL(context.Context, *HostGetAssetURLRequest) (*HostGetAssetURLResponse, error)
- GetAssetBytes(context.Context, *HostGetAssetBytesRequest) (*HostGetAssetBytesResponse, error)
- mustEmbedUnimplementedHostServiceServer()
-}
-
-// UnimplementedHostServiceServer must be embedded to have
+// UnimplementedEventServiceServer must be embedded to have
// forward compatible implementations.
//
// NOTE: this should be embedded by value instead of pointer to avoid a nil
// pointer dereference when methods are called.
-type UnimplementedHostServiceServer struct{}
+type UnimplementedEventServiceServer struct{}
-func (UnimplementedHostServiceServer) SelectAccount(context.Context, *HostSelectAccountRequest) (*HostSelectAccountResponse, error) {
- return nil, status.Error(codes.Unimplemented, "method SelectAccount not implemented")
-}
-func (UnimplementedHostServiceServer) ReportAccountResult(context.Context, *HostReportAccountResultRequest) (*Empty, error) {
- return nil, status.Error(codes.Unimplemented, "method ReportAccountResult not implemented")
-}
-func (UnimplementedHostServiceServer) ProbeForward(context.Context, *HostProbeForwardRequest) (*HostProbeForwardResponse, error) {
- return nil, status.Error(codes.Unimplemented, "method ProbeForward not implemented")
-}
-func (UnimplementedHostServiceServer) Forward(context.Context, *HostForwardRequest) (*HostForwardResponse, error) {
- return nil, status.Error(codes.Unimplemented, "method Forward not implemented")
-}
-func (UnimplementedHostServiceServer) ForwardStream(*HostForwardRequest, grpc.ServerStreamingServer[HostForwardChunk]) error {
- return status.Error(codes.Unimplemented, "method ForwardStream not implemented")
-}
-func (UnimplementedHostServiceServer) ListGroups(context.Context, *HostListGroupsRequest) (*HostListGroupsResponse, error) {
- return nil, status.Error(codes.Unimplemented, "method ListGroups not implemented")
-}
-func (UnimplementedHostServiceServer) ListPlatforms(context.Context, *HostListPlatformsRequest) (*HostListPlatformsResponse, error) {
- return nil, status.Error(codes.Unimplemented, "method ListPlatforms not implemented")
-}
-func (UnimplementedHostServiceServer) ListModels(context.Context, *HostListModelsRequest) (*HostListModelsResponse, error) {
- return nil, status.Error(codes.Unimplemented, "method ListModels not implemented")
+func (UnimplementedEventServiceServer) GetEventSubscriptions(context.Context, *Empty) (*EventSubscriptionsResponse, error) {
+ return nil, status.Error(codes.Unimplemented, "method GetEventSubscriptions not implemented")
}
-func (UnimplementedHostServiceServer) GetUserInfo(context.Context, *HostGetUserInfoRequest) (*HostGetUserInfoResponse, error) {
- return nil, status.Error(codes.Unimplemented, "method GetUserInfo not implemented")
+func (UnimplementedEventServiceServer) HandleEvent(context.Context, *PluginEvent) (*EventHandleResponse, error) {
+ return nil, status.Error(codes.Unimplemented, "method HandleEvent not implemented")
}
-func (UnimplementedHostServiceServer) StoreAsset(context.Context, *HostStoreAssetRequest) (*HostStoreAssetResponse, error) {
- return nil, status.Error(codes.Unimplemented, "method StoreAsset not implemented")
-}
-func (UnimplementedHostServiceServer) GetAssetURL(context.Context, *HostGetAssetURLRequest) (*HostGetAssetURLResponse, error) {
- return nil, status.Error(codes.Unimplemented, "method GetAssetURL not implemented")
-}
-func (UnimplementedHostServiceServer) GetAssetBytes(context.Context, *HostGetAssetBytesRequest) (*HostGetAssetBytesResponse, error) {
- return nil, status.Error(codes.Unimplemented, "method GetAssetBytes not implemented")
-}
-func (UnimplementedHostServiceServer) mustEmbedUnimplementedHostServiceServer() {}
-func (UnimplementedHostServiceServer) testEmbeddedByValue() {}
+func (UnimplementedEventServiceServer) mustEmbedUnimplementedEventServiceServer() {}
+func (UnimplementedEventServiceServer) testEmbeddedByValue() {}
-// UnsafeHostServiceServer may be embedded to opt out of forward compatibility for this service.
-// Use of this interface is not recommended, as added methods to HostServiceServer will
+// UnsafeEventServiceServer may be embedded to opt out of forward compatibility for this service.
+// Use of this interface is not recommended, as added methods to EventServiceServer will
// result in compilation errors.
-type UnsafeHostServiceServer interface {
- mustEmbedUnimplementedHostServiceServer()
+type UnsafeEventServiceServer interface {
+ mustEmbedUnimplementedEventServiceServer()
}
-func RegisterHostServiceServer(s grpc.ServiceRegistrar, srv HostServiceServer) {
- // If the following call panics, it indicates UnimplementedHostServiceServer was
+func RegisterEventServiceServer(s grpc.ServiceRegistrar, srv EventServiceServer) {
+ // If the following call panics, it indicates UnimplementedEventServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
t.testEmbeddedByValue()
}
- s.RegisterService(&HostService_ServiceDesc, srv)
+ s.RegisterService(&EventService_ServiceDesc, srv)
}
-func _HostService_SelectAccount_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
- in := new(HostSelectAccountRequest)
+func _EventService_GetEventSubscriptions_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+ in := new(Empty)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
- return srv.(HostServiceServer).SelectAccount(ctx, in)
+ return srv.(EventServiceServer).GetEventSubscriptions(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
- FullMethod: HostService_SelectAccount_FullMethodName,
+ FullMethod: EventService_GetEventSubscriptions_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
- return srv.(HostServiceServer).SelectAccount(ctx, req.(*HostSelectAccountRequest))
+ return srv.(EventServiceServer).GetEventSubscriptions(ctx, req.(*Empty))
}
return interceptor(ctx, in, info, handler)
}
-func _HostService_ReportAccountResult_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
- in := new(HostReportAccountResultRequest)
+func _EventService_HandleEvent_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+ in := new(PluginEvent)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
- return srv.(HostServiceServer).ReportAccountResult(ctx, in)
+ return srv.(EventServiceServer).HandleEvent(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
- FullMethod: HostService_ReportAccountResult_FullMethodName,
+ FullMethod: EventService_HandleEvent_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
- return srv.(HostServiceServer).ReportAccountResult(ctx, req.(*HostReportAccountResultRequest))
+ return srv.(EventServiceServer).HandleEvent(ctx, req.(*PluginEvent))
}
return interceptor(ctx, in, info, handler)
}
-func _HostService_ProbeForward_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
- in := new(HostProbeForwardRequest)
- if err := dec(in); err != nil {
- return nil, err
- }
- if interceptor == nil {
- return srv.(HostServiceServer).ProbeForward(ctx, in)
- }
- info := &grpc.UnaryServerInfo{
- Server: srv,
- FullMethod: HostService_ProbeForward_FullMethodName,
- }
- handler := func(ctx context.Context, req interface{}) (interface{}, error) {
- return srv.(HostServiceServer).ProbeForward(ctx, req.(*HostProbeForwardRequest))
- }
- return interceptor(ctx, in, info, handler)
+// EventService_ServiceDesc is the grpc.ServiceDesc for EventService service.
+// It's only intended for direct use with grpc.RegisterService,
+// and not to be introspected or modified (even as a copy)
+var EventService_ServiceDesc = grpc.ServiceDesc{
+ ServiceName: "airgate.plugin.v2.EventService",
+ HandlerType: (*EventServiceServer)(nil),
+ Methods: []grpc.MethodDesc{
+ {
+ MethodName: "GetEventSubscriptions",
+ Handler: _EventService_GetEventSubscriptions_Handler,
+ },
+ {
+ MethodName: "HandleEvent",
+ Handler: _EventService_HandleEvent_Handler,
+ },
+ },
+ Streams: []grpc.StreamDesc{},
+ Metadata: "plugin.proto",
}
-func _HostService_Forward_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
- in := new(HostForwardRequest)
- if err := dec(in); err != nil {
- return nil, err
- }
- if interceptor == nil {
- return srv.(HostServiceServer).Forward(ctx, in)
- }
- info := &grpc.UnaryServerInfo{
- Server: srv,
- FullMethod: HostService_Forward_FullMethodName,
- }
- handler := func(ctx context.Context, req interface{}) (interface{}, error) {
- return srv.(HostServiceServer).Forward(ctx, req.(*HostForwardRequest))
- }
- return interceptor(ctx, in, info, handler)
+const (
+ CoreInvokeService_Invoke_FullMethodName = "/airgate.plugin.v2.CoreInvokeService/Invoke"
+ CoreInvokeService_InvokeStream_FullMethodName = "/airgate.plugin.v2.CoreInvokeService/InvokeStream"
+)
+
+// CoreInvokeServiceClient is the client API for CoreInvokeService service.
+//
+// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
+//
+// ==================== Host 服务(反向调用:插件 → Core) ====================
+//
+// 由 Core 实现,通过 hashicorp/go-plugin 的 GRPCBroker 暴露给插件子进程。
+// SDK 只定义通用 Invoke / InvokeStream 通道;Core 通过方法注册表决定开放哪些方法、
+// 允许哪些插件调用、请求/响应 schema、是否支持流式以及幂等策略。
+type CoreInvokeServiceClient interface {
+ Invoke(ctx context.Context, in *HostInvokeRequest, opts ...grpc.CallOption) (*HostInvokeResponse, error)
+ InvokeStream(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[HostStreamFrame, HostStreamFrame], error)
}
-func _HostService_ForwardStream_Handler(srv interface{}, stream grpc.ServerStream) error {
- m := new(HostForwardRequest)
- if err := stream.RecvMsg(m); err != nil {
- return err
- }
- return srv.(HostServiceServer).ForwardStream(m, &grpc.GenericServerStream[HostForwardRequest, HostForwardChunk]{ServerStream: stream})
+type coreInvokeServiceClient struct {
+ cc grpc.ClientConnInterface
}
-// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
-type HostService_ForwardStreamServer = grpc.ServerStreamingServer[HostForwardChunk]
+func NewCoreInvokeServiceClient(cc grpc.ClientConnInterface) CoreInvokeServiceClient {
+ return &coreInvokeServiceClient{cc}
+}
-func _HostService_ListGroups_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
- in := new(HostListGroupsRequest)
- if err := dec(in); err != nil {
+func (c *coreInvokeServiceClient) Invoke(ctx context.Context, in *HostInvokeRequest, opts ...grpc.CallOption) (*HostInvokeResponse, error) {
+ cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
+ out := new(HostInvokeResponse)
+ err := c.cc.Invoke(ctx, CoreInvokeService_Invoke_FullMethodName, in, out, cOpts...)
+ if err != nil {
return nil, err
}
- if interceptor == nil {
- return srv.(HostServiceServer).ListGroups(ctx, in)
- }
- info := &grpc.UnaryServerInfo{
- Server: srv,
- FullMethod: HostService_ListGroups_FullMethodName,
- }
- handler := func(ctx context.Context, req interface{}) (interface{}, error) {
- return srv.(HostServiceServer).ListGroups(ctx, req.(*HostListGroupsRequest))
- }
- return interceptor(ctx, in, info, handler)
+ return out, nil
}
-func _HostService_ListPlatforms_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
- in := new(HostListPlatformsRequest)
- if err := dec(in); err != nil {
+func (c *coreInvokeServiceClient) InvokeStream(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[HostStreamFrame, HostStreamFrame], error) {
+ cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
+ stream, err := c.cc.NewStream(ctx, &CoreInvokeService_ServiceDesc.Streams[0], CoreInvokeService_InvokeStream_FullMethodName, cOpts...)
+ if err != nil {
return nil, err
}
- if interceptor == nil {
- return srv.(HostServiceServer).ListPlatforms(ctx, in)
- }
- info := &grpc.UnaryServerInfo{
- Server: srv,
- FullMethod: HostService_ListPlatforms_FullMethodName,
- }
- handler := func(ctx context.Context, req interface{}) (interface{}, error) {
- return srv.(HostServiceServer).ListPlatforms(ctx, req.(*HostListPlatformsRequest))
- }
- return interceptor(ctx, in, info, handler)
+ x := &grpc.GenericClientStream[HostStreamFrame, HostStreamFrame]{ClientStream: stream}
+ return x, nil
}
-func _HostService_ListModels_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
- in := new(HostListModelsRequest)
- if err := dec(in); err != nil {
- return nil, err
- }
- if interceptor == nil {
- return srv.(HostServiceServer).ListModels(ctx, in)
- }
- info := &grpc.UnaryServerInfo{
- Server: srv,
- FullMethod: HostService_ListModels_FullMethodName,
- }
- handler := func(ctx context.Context, req interface{}) (interface{}, error) {
- return srv.(HostServiceServer).ListModels(ctx, req.(*HostListModelsRequest))
- }
- return interceptor(ctx, in, info, handler)
+// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
+type CoreInvokeService_InvokeStreamClient = grpc.BidiStreamingClient[HostStreamFrame, HostStreamFrame]
+
+// CoreInvokeServiceServer is the server API for CoreInvokeService service.
+// All implementations must embed UnimplementedCoreInvokeServiceServer
+// for forward compatibility.
+//
+// ==================== Host 服务(反向调用:插件 → Core) ====================
+//
+// 由 Core 实现,通过 hashicorp/go-plugin 的 GRPCBroker 暴露给插件子进程。
+// SDK 只定义通用 Invoke / InvokeStream 通道;Core 通过方法注册表决定开放哪些方法、
+// 允许哪些插件调用、请求/响应 schema、是否支持流式以及幂等策略。
+type CoreInvokeServiceServer interface {
+ Invoke(context.Context, *HostInvokeRequest) (*HostInvokeResponse, error)
+ InvokeStream(grpc.BidiStreamingServer[HostStreamFrame, HostStreamFrame]) error
+ mustEmbedUnimplementedCoreInvokeServiceServer()
}
-func _HostService_GetUserInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
- in := new(HostGetUserInfoRequest)
- if err := dec(in); err != nil {
- return nil, err
- }
- if interceptor == nil {
- return srv.(HostServiceServer).GetUserInfo(ctx, in)
- }
- info := &grpc.UnaryServerInfo{
- Server: srv,
- FullMethod: HostService_GetUserInfo_FullMethodName,
- }
- handler := func(ctx context.Context, req interface{}) (interface{}, error) {
- return srv.(HostServiceServer).GetUserInfo(ctx, req.(*HostGetUserInfoRequest))
- }
- return interceptor(ctx, in, info, handler)
+// UnimplementedCoreInvokeServiceServer must be embedded to have
+// forward compatible implementations.
+//
+// NOTE: this should be embedded by value instead of pointer to avoid a nil
+// pointer dereference when methods are called.
+type UnimplementedCoreInvokeServiceServer struct{}
+
+func (UnimplementedCoreInvokeServiceServer) Invoke(context.Context, *HostInvokeRequest) (*HostInvokeResponse, error) {
+ return nil, status.Error(codes.Unimplemented, "method Invoke not implemented")
+}
+func (UnimplementedCoreInvokeServiceServer) InvokeStream(grpc.BidiStreamingServer[HostStreamFrame, HostStreamFrame]) error {
+ return status.Error(codes.Unimplemented, "method InvokeStream not implemented")
}
+func (UnimplementedCoreInvokeServiceServer) mustEmbedUnimplementedCoreInvokeServiceServer() {}
+func (UnimplementedCoreInvokeServiceServer) testEmbeddedByValue() {}
-func _HostService_StoreAsset_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
- in := new(HostStoreAssetRequest)
- if err := dec(in); err != nil {
- return nil, err
- }
- if interceptor == nil {
- return srv.(HostServiceServer).StoreAsset(ctx, in)
- }
- info := &grpc.UnaryServerInfo{
- Server: srv,
- FullMethod: HostService_StoreAsset_FullMethodName,
- }
- handler := func(ctx context.Context, req interface{}) (interface{}, error) {
- return srv.(HostServiceServer).StoreAsset(ctx, req.(*HostStoreAssetRequest))
- }
- return interceptor(ctx, in, info, handler)
+// UnsafeCoreInvokeServiceServer may be embedded to opt out of forward compatibility for this service.
+// Use of this interface is not recommended, as added methods to CoreInvokeServiceServer will
+// result in compilation errors.
+type UnsafeCoreInvokeServiceServer interface {
+ mustEmbedUnimplementedCoreInvokeServiceServer()
}
-func _HostService_GetAssetURL_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
- in := new(HostGetAssetURLRequest)
- if err := dec(in); err != nil {
- return nil, err
- }
- if interceptor == nil {
- return srv.(HostServiceServer).GetAssetURL(ctx, in)
- }
- info := &grpc.UnaryServerInfo{
- Server: srv,
- FullMethod: HostService_GetAssetURL_FullMethodName,
- }
- handler := func(ctx context.Context, req interface{}) (interface{}, error) {
- return srv.(HostServiceServer).GetAssetURL(ctx, req.(*HostGetAssetURLRequest))
+func RegisterCoreInvokeServiceServer(s grpc.ServiceRegistrar, srv CoreInvokeServiceServer) {
+ // If the following call panics, it indicates UnimplementedCoreInvokeServiceServer was
+ // embedded by pointer and is nil. This will cause panics if an
+ // unimplemented method is ever invoked, so we test this at initialization
+ // time to prevent it from happening at runtime later due to I/O.
+ if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
+ t.testEmbeddedByValue()
}
- return interceptor(ctx, in, info, handler)
+ s.RegisterService(&CoreInvokeService_ServiceDesc, srv)
}
-func _HostService_GetAssetBytes_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
- in := new(HostGetAssetBytesRequest)
+func _CoreInvokeService_Invoke_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+ in := new(HostInvokeRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
- return srv.(HostServiceServer).GetAssetBytes(ctx, in)
+ return srv.(CoreInvokeServiceServer).Invoke(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
- FullMethod: HostService_GetAssetBytes_FullMethodName,
+ FullMethod: CoreInvokeService_Invoke_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
- return srv.(HostServiceServer).GetAssetBytes(ctx, req.(*HostGetAssetBytesRequest))
+ return srv.(CoreInvokeServiceServer).Invoke(ctx, req.(*HostInvokeRequest))
}
return interceptor(ctx, in, info, handler)
}
-// HostService_ServiceDesc is the grpc.ServiceDesc for HostService service.
+func _CoreInvokeService_InvokeStream_Handler(srv interface{}, stream grpc.ServerStream) error {
+ return srv.(CoreInvokeServiceServer).InvokeStream(&grpc.GenericServerStream[HostStreamFrame, HostStreamFrame]{ServerStream: stream})
+}
+
+// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
+type CoreInvokeService_InvokeStreamServer = grpc.BidiStreamingServer[HostStreamFrame, HostStreamFrame]
+
+// CoreInvokeService_ServiceDesc is the grpc.ServiceDesc for CoreInvokeService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
-var HostService_ServiceDesc = grpc.ServiceDesc{
- ServiceName: "airgate.plugin.v1.HostService",
- HandlerType: (*HostServiceServer)(nil),
+var CoreInvokeService_ServiceDesc = grpc.ServiceDesc{
+ ServiceName: "airgate.plugin.v2.CoreInvokeService",
+ HandlerType: (*CoreInvokeServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
- MethodName: "SelectAccount",
- Handler: _HostService_SelectAccount_Handler,
- },
- {
- MethodName: "ReportAccountResult",
- Handler: _HostService_ReportAccountResult_Handler,
- },
- {
- MethodName: "ProbeForward",
- Handler: _HostService_ProbeForward_Handler,
- },
- {
- MethodName: "Forward",
- Handler: _HostService_Forward_Handler,
- },
- {
- MethodName: "ListGroups",
- Handler: _HostService_ListGroups_Handler,
- },
- {
- MethodName: "ListPlatforms",
- Handler: _HostService_ListPlatforms_Handler,
- },
- {
- MethodName: "ListModels",
- Handler: _HostService_ListModels_Handler,
- },
- {
- MethodName: "GetUserInfo",
- Handler: _HostService_GetUserInfo_Handler,
- },
- {
- MethodName: "StoreAsset",
- Handler: _HostService_StoreAsset_Handler,
- },
- {
- MethodName: "GetAssetURL",
- Handler: _HostService_GetAssetURL_Handler,
- },
- {
- MethodName: "GetAssetBytes",
- Handler: _HostService_GetAssetBytes_Handler,
+ MethodName: "Invoke",
+ Handler: _CoreInvokeService_Invoke_Handler,
},
},
Streams: []grpc.StreamDesc{
{
- StreamName: "ForwardStream",
- Handler: _HostService_ForwardStream_Handler,
+ StreamName: "InvokeStream",
+ Handler: _CoreInvokeService_InvokeStream_Handler,
ServerStreams: true,
+ ClientStreams: true,
},
},
Metadata: "plugin.proto",
diff --git a/grpc/common.go b/runtimego/grpc/common.go
similarity index 88%
rename from grpc/common.go
rename to runtimego/grpc/common.go
index c800740..d071f1b 100644
--- a/grpc/common.go
+++ b/runtimego/grpc/common.go
@@ -6,8 +6,8 @@ import (
"net/http"
"time"
- sdk "github.com/DouDOU-start/airgate-sdk"
- pb "github.com/DouDOU-start/airgate-sdk/proto"
+ pb "github.com/DouDOU-start/airgate-sdk/protocol/proto"
+ sdk "github.com/DouDOU-start/airgate-sdk/sdkgo"
)
// defaultGRPCTimeout gRPC 内部调用的默认超时时间
@@ -21,17 +21,18 @@ func withTimeout() (context.Context, context.CancelFunc) {
// pluginBase 封装所有 gRPC Client 共有的 Plugin 接口方法,
// 通过嵌入到各具体 Client 中消除重复代码
//
-// hostBrokerID 由 *GRPCPlugin.GRPCClient 钩子在启动 HostService stream 后填入,
-// 在 Init() 时透传给插件进程的 InitRequest。0 表示 host 不可用。
+// coreInvokeBrokerID 由 *GRPCPlugin.GRPCClient 钩子在启动反向调用 stream 后填入,
+// 在 Init() 时透传给插件进程的 InitRequest。0 表示反向调用不可用。
//
// 日志策略:core 通过 hashicorp/go-plugin 内部建的 grpc.ClientConn 拿到 RPC 句柄,
// 我们没法直接装 client 端拦截器(go-plugin 不暴露注入点)。所以在每个方法里手写
// 进入 / 失败 / 完成日志,配合 server 端拦截器一起把调用链覆盖到位。失败一定打 Error,
// 成功路径只打 Debug 防止污染 info 流。
type pluginBase struct {
- plugin pb.PluginServiceClient
- cachedInfo *sdk.PluginInfo
- hostBrokerID uint32
+ plugin pb.PluginServiceClient
+ event pb.EventServiceClient
+ cachedInfo *sdk.PluginInfo
+ coreInvokeBrokerID uint32
}
// pluginIDForLog 取 cached 的插件 ID 给日志用;若还没缓存则返回空串。
@@ -82,6 +83,7 @@ func (b *pluginBase) Info() sdk.PluginInfo {
Author: resp.Author,
Type: sdk.PluginType(resp.Type),
Dependencies: resp.Dependencies,
+ Metadata: resp.Metadata,
}
if len(resp.ConfigSchema) > 0 {
@@ -179,9 +181,9 @@ func (b *pluginBase) Init(ctx sdk.PluginContext) error {
logger, start := b.rpcLogger(grpcCtx, "Init")
logger.Debug("plugin_call_init_start")
_, err := b.plugin.Init(grpcCtx, &pb.InitRequest{
- Config: config,
- LogLevel: logLevel,
- HostBrokerId: b.hostBrokerID,
+ Config: config,
+ LogLevel: logLevel,
+ CoreInvokeBrokerId: b.coreInvokeBrokerID,
})
if err != nil {
logger.Error("plugin_call_init_failed",
@@ -256,6 +258,23 @@ func (b *pluginBase) GetWebAssets() (map[string][]byte, error) {
return assets, nil
}
+// Schema 获取插件结构化能力清单。
+func (b *pluginBase) Schema() sdk.PluginSchema {
+ ctx, cancel := withTimeout()
+ defer cancel()
+
+ logger, start := b.rpcLogger(ctx, "GetSchema")
+ resp, err := b.plugin.GetSchema(ctx, &pb.Empty{})
+ if err != nil {
+ logger.Error("plugin_call_get_schema_failed",
+ sdk.LogFieldDurationMs, time.Since(start).Milliseconds(),
+ sdk.LogFieldError, err,
+ )
+ return sdk.PluginSchema{}
+ }
+ return schemaFromProto(resp)
+}
+
// HealthCheck 健康检查(客户端侧调用)
func (b *pluginBase) HealthCheck(ctx context.Context) error {
logger, start := b.rpcLogger(ctx, "HealthCheck")
@@ -306,18 +325,12 @@ func convertModels(pbModels []*pb.ModelInfoProto) []sdk.ModelInfo {
models := make([]sdk.ModelInfo, len(pbModels))
for i, m := range pbModels {
models[i] = sdk.ModelInfo{
- ID: m.Id,
- Name: m.Name,
- ContextWindow: int(m.ContextWindow),
- MaxOutputTokens: int(m.MaxOutputTokens),
- InputPrice: m.InputPrice,
- OutputPrice: m.OutputPrice,
- CachedInputPrice: m.CachedInputPrice,
- CacheCreationPrice: m.CacheCreationPrice,
- CacheCreation1hPrice: m.GetCacheCreation_1HPrice(),
- InputPricePriority: m.InputPricePriority,
- OutputPricePriority: m.OutputPricePriority,
- CachedInputPricePriority: m.CachedInputPricePriority,
+ ID: m.Id,
+ Name: m.Name,
+ ContextWindow: int(m.ContextWindow),
+ MaxOutputTokens: int(m.MaxOutputTokens),
+ Capabilities: m.Capabilities,
+ Metadata: m.Metadata,
}
}
return models
diff --git a/grpc/context.go b/runtimego/grpc/context.go
similarity index 78%
rename from grpc/context.go
rename to runtimego/grpc/context.go
index cfa7425..5a5238c 100644
--- a/grpc/context.go
+++ b/runtimego/grpc/context.go
@@ -9,26 +9,26 @@ import (
goplugin "github.com/hashicorp/go-plugin"
- sdk "github.com/DouDOU-start/airgate-sdk"
- pb "github.com/DouDOU-start/airgate-sdk/proto"
+ pb "github.com/DouDOU-start/airgate-sdk/protocol/proto"
+ sdk "github.com/DouDOU-start/airgate-sdk/sdkgo"
)
// grpcPluginContext 通过 gRPC 传入的插件上下文(插件进程侧)。
//
// 实现了 sdk.PluginContext,并可选实现 sdk.HostAware:当 Core 通过 InitRequest
-// 注入了 host_broker_id 时,Host() 会通过 broker.Dial(id) 拿到一个反向 grpc
-// 连接,把 HostService client 包装成 sdk.Host 返回给插件。
+// 注入了 core_invoke_broker_id 时,Host() 会通过 broker.Dial(id) 拿到一个反向 grpc
+// 连接,把 CoreInvokeService client 包装成 sdk.Host 返回给插件。
//
// Host() 是 lazy 的:第一次调用时才 dial。理由:
// - 大多数插件根本不需要 host,避免无谓建立 stream
// - 即便需要,也常常是在 Start() 而非 Init() 阶段才用到
-// - dial 失败可以回退到 nil(与"Core 不支持 host"语义一致),不阻塞 Init
+// - dial 失败会记录错误并返回 nil,不阻塞 Init
type grpcPluginContext struct {
config sdk.PluginConfig
logger *slog.Logger
- broker *goplugin.GRPCBroker // 由 PluginGRPCServer 注入;plugin 进程侧才有
- hostBrokerID uint32 // 0 = host 不可用
+ broker *goplugin.GRPCBroker // 由 PluginGRPCServer 注入;plugin 进程侧才有
+ coreInvokeBrokerID uint32 // 0 = 反向调用不可用
hostOnce sync.Once
host sdk.Host
@@ -61,7 +61,7 @@ func (c *grpcPluginContext) PluginDSN() string {
// Host 实现 sdk.HostAware。返回 nil 表示不可用(非错误,调用方做 nil-check)。
//
// 失败情形(都返回 nil + 内部记录 err):
-// - host_broker_id == 0(Core 没启用 HostService)
+// - core_invoke_broker_id == 0(Core 没启用反向调用)
// - broker == nil(不在 plugin 进程内 / 测试 mock)
// - broker.Dial 失败(超时 / Core 进程退出)
func (c *grpcPluginContext) Host() sdk.Host {
@@ -70,16 +70,16 @@ func (c *grpcPluginContext) Host() sdk.Host {
c.hostErr = errors.New("host broker not available")
return
}
- if c.hostBrokerID == 0 {
- c.hostErr = errors.New("host service not enabled by core")
+ if c.coreInvokeBrokerID == 0 {
+ c.hostErr = errors.New("core invoke not enabled")
return
}
- conn, err := c.broker.Dial(c.hostBrokerID)
+ conn, err := c.broker.Dial(c.coreInvokeBrokerID)
if err != nil {
c.hostErr = err
return
}
- c.host = NewHostClient(pb.NewHostServiceClient(conn))
+ c.host = NewHostClient(pb.NewCoreInvokeServiceClient(conn))
})
return c.host
}
diff --git a/grpc/context_test.go b/runtimego/grpc/context_test.go
similarity index 100%
rename from grpc/context_test.go
rename to runtimego/grpc/context_test.go
diff --git a/grpc/convert_test.go b/runtimego/grpc/convert_test.go
similarity index 84%
rename from grpc/convert_test.go
rename to runtimego/grpc/convert_test.go
index 79ce88f..ad88b3b 100644
--- a/grpc/convert_test.go
+++ b/runtimego/grpc/convert_test.go
@@ -7,8 +7,8 @@ import (
"testing"
"time"
- sdk "github.com/DouDOU-start/airgate-sdk"
- pb "github.com/DouDOU-start/airgate-sdk/proto"
+ pb "github.com/DouDOU-start/airgate-sdk/protocol/proto"
+ sdk "github.com/DouDOU-start/airgate-sdk/sdkgo"
)
// ---------------------------------------------------------------------------
@@ -91,19 +91,47 @@ func TestForwardOutcome_RoundTrip(t *testing.T) {
Body: []byte(`{"ok":true}`),
},
Usage: &sdk.Usage{
- InputTokens: 150,
- OutputTokens: 300,
- CachedInputTokens: 50,
- CacheCreationTokens: 80,
- ReasoningOutputTokens: 25,
- Model: "claude-opus-4-20250514",
- FirstTokenMs: 120,
- ServiceTier: "priority",
- InputCost: 0.00375,
- OutputCost: 0.0225,
- CachedInputCost: 0.000125,
- CacheCreationCost: 0.0005,
- CacheCreationPrice: 6.25,
+ Model: "claude-opus-4-20250514",
+ AccountCost: 0.026875,
+ UserCost: 0.05375,
+ BillingMultiplier: 2,
+ Currency: "USD",
+ Summary: "输入 150 token,输出 300 token",
+ FirstTokenMs: 120,
+ Attributes: []sdk.UsageAttribute{
+ {Key: "reasoning_effort", Label: "思考层级", Kind: "reasoning", Value: "high"},
+ {Key: "resolution", Label: "分辨率", Kind: "resolution", Value: "1024x1024"},
+ },
+ Metrics: []sdk.UsageMetric{
+ {
+ Key: "image_generation",
+ Label: "图片生成",
+ Kind: "image",
+ Unit: "image",
+ Value: 2,
+ AccountCost: 0.08,
+ Currency: "USD",
+ Metadata: map[string]string{
+ "size": "1024x1024",
+ },
+ },
+ },
+ CostDetails: []sdk.UsageCostDetail{
+ {
+ Key: "image_generation",
+ Label: "图片生成费用",
+ AccountCost: 0.08,
+ UserCost: 0.16,
+ BillingMultiplier: 2,
+ Currency: "USD",
+ Metadata: map[string]string{
+ "tier": "standard",
+ },
+ },
+ },
+ Metadata: map[string]string{
+ "billing_mode": "mixed",
+ },
},
Duration: 2500 * time.Millisecond,
RetryAfter: 30000 * time.Millisecond,
@@ -269,16 +297,14 @@ func TestBuildAccount_EmptyCredentials(t *testing.T) {
func TestConvertModels(t *testing.T) {
pbModels := []*pb.ModelInfoProto{
{
- Id: "gpt-4",
- Name: "GPT-4",
- ContextWindow: 8192,
- MaxOutputTokens: 4096,
- InputPrice: 30.0,
- OutputPrice: 60.0,
- CachedInputPrice: 15.0,
- InputPricePriority: 45.0,
- OutputPricePriority: 90.0,
- CachedInputPricePriority: 22.5,
+ Id: "gpt-4",
+ Name: "GPT-4",
+ ContextWindow: 8192,
+ MaxOutputTokens: 4096,
+ Capabilities: []string{sdk.ModelCapChat},
+ Metadata: map[string]string{
+ "family": "gpt",
+ },
},
}
@@ -288,16 +314,14 @@ func TestConvertModels(t *testing.T) {
t.Fatalf("expected 1 model, got %d", len(models))
}
expected := sdk.ModelInfo{
- ID: "gpt-4",
- Name: "GPT-4",
- ContextWindow: 8192,
- MaxOutputTokens: 4096,
- InputPrice: 30.0,
- OutputPrice: 60.0,
- CachedInputPrice: 15.0,
- InputPricePriority: 45.0,
- OutputPricePriority: 90.0,
- CachedInputPricePriority: 22.5,
+ ID: "gpt-4",
+ Name: "GPT-4",
+ ContextWindow: 8192,
+ MaxOutputTokens: 4096,
+ Capabilities: []string{sdk.ModelCapChat},
+ Metadata: map[string]string{
+ "family": "gpt",
+ },
}
if !reflect.DeepEqual(models[0], expected) {
t.Fatalf("convertModels mismatch:\n got: %+v\n want: %+v", models[0], expected)
diff --git a/runtimego/grpc/doc.go b/runtimego/grpc/doc.go
new file mode 100644
index 0000000..aaf80bd
--- /dev/null
+++ b/runtimego/grpc/doc.go
@@ -0,0 +1,5 @@
+// Package grpc 提供 AirGate Go 插件运行时的 gRPC 适配层。
+//
+// 本包负责 hashicorp/go-plugin 集成、protobuf 与 sdkgo 类型转换、
+// Core 反向调用通道、流式响应桥接和插件进程启动。
+package grpc
diff --git a/runtimego/grpc/event_schema_test.go b/runtimego/grpc/event_schema_test.go
new file mode 100644
index 0000000..a430bb9
--- /dev/null
+++ b/runtimego/grpc/event_schema_test.go
@@ -0,0 +1,87 @@
+package grpc
+
+import (
+ "reflect"
+ "testing"
+ "time"
+
+ sdk "github.com/DouDOU-start/airgate-sdk/sdkgo"
+)
+
+func TestPluginEventRoundTrip(t *testing.T) {
+ original := sdk.PluginEvent{
+ ID: "evt_1",
+ Type: "account.updated",
+ Source: "core",
+ Subject: "account:42",
+ UserID: 7,
+ GroupID: 9,
+ Payload: map[string]interface{}{
+ "account_id": float64(42),
+ "status": "active",
+ },
+ Metadata: map[string]string{"trace": "abc"},
+ OccurredAt: time.UnixMilli(1700000000123),
+ }
+
+ restored := eventFromProto(eventToProto(original))
+ if !reflect.DeepEqual(original, restored) {
+ t.Fatalf("PluginEvent round-trip mismatch:\n original: %+v\n restored: %+v", original, restored)
+ }
+}
+
+func TestEventSubscriptionRoundTrip(t *testing.T) {
+ original := sdk.EventSubscription{
+ Type: "task.*",
+ Source: "core",
+ Filter: map[string]string{
+ "task_type": "image_generation",
+ },
+ Metadata: map[string]string{"note": "demo"},
+ }
+
+ restored := subscriptionFromProto(subscriptionToProto(original))
+ if !reflect.DeepEqual(original, restored) {
+ t.Fatalf("EventSubscription round-trip mismatch:\n original: %+v\n restored: %+v", original, restored)
+ }
+}
+
+func TestPluginSchemaRoundTrip(t *testing.T) {
+ original := sdk.PluginSchema{
+ Routes: []sdk.RouteSchema{{
+ Method: "POST",
+ Path: "/api/demo",
+ Summary: "演示接口",
+ Request: sdk.PayloadSchema{
+ ContentType: "application/json",
+ Schema: `{"type":"object"}`,
+ },
+ Response: sdk.PayloadSchema{Example: `{"ok":true}`},
+ Metadata: map[string]string{"group": "demo"},
+ }},
+ Tasks: []sdk.TaskSchema{{
+ Type: "image_generation",
+ Summary: "生成图片",
+ Input: sdk.PayloadSchema{Schema: `{"type":"object"}`},
+ Output: sdk.PayloadSchema{Schema: `{"type":"object"}`},
+ }},
+ Events: []sdk.EventSchema{{
+ Type: "account.updated",
+ Source: "core",
+ Summary: "账号更新",
+ Payload: sdk.PayloadSchema{Schema: `{"type":"object"}`},
+ }},
+ Invokes: []sdk.InvokeSchema{{
+ Method: "notifications.publish",
+ Summary: "发送通知",
+ Request: sdk.PayloadSchema{Schema: `{"type":"object"}`},
+ Response: sdk.PayloadSchema{Schema: `{"type":"object"}`},
+ }},
+ Metadata: map[string]string{"version": "1"},
+ }
+
+ restored := schemaFromProto(schemaToProto(original))
+ if !reflect.DeepEqual(original, restored) {
+ t.Fatalf("PluginSchema round-trip mismatch:\n original: %+v\n restored: %+v", original, restored)
+ }
+}
diff --git a/runtimego/grpc/event_server.go b/runtimego/grpc/event_server.go
new file mode 100644
index 0000000..b9e0d21
--- /dev/null
+++ b/runtimego/grpc/event_server.go
@@ -0,0 +1,136 @@
+package grpc
+
+import (
+ "context"
+ "errors"
+ "time"
+
+ pb "github.com/DouDOU-start/airgate-sdk/protocol/proto"
+ sdk "github.com/DouDOU-start/airgate-sdk/sdkgo"
+)
+
+// EventGRPCServer 将可选 EventHandler 包装为 gRPC 服务。
+type EventGRPCServer struct {
+ pb.UnimplementedEventServiceServer
+ Impl sdk.Plugin
+}
+
+func eventToProto(e sdk.PluginEvent) *pb.PluginEvent {
+ occurredAt := int64(0)
+ if !e.OccurredAt.IsZero() {
+ occurredAt = e.OccurredAt.UnixMilli()
+ }
+ return &pb.PluginEvent{
+ Id: e.ID,
+ Type: e.Type,
+ Source: e.Source,
+ Subject: e.Subject,
+ UserId: e.UserID,
+ GroupId: e.GroupID,
+ Payload: mapToJSONPayload(e.Payload),
+ Metadata: e.Metadata,
+ OccurredAt: occurredAt,
+ }
+}
+
+func eventFromProto(p *pb.PluginEvent) sdk.PluginEvent {
+ if p == nil {
+ return sdk.PluginEvent{}
+ }
+ event := sdk.PluginEvent{
+ ID: p.Id,
+ Type: p.Type,
+ Source: p.Source,
+ Subject: p.Subject,
+ UserID: p.UserId,
+ GroupID: p.GroupId,
+ Payload: jsonPayloadToMap(p.Payload),
+ Metadata: p.Metadata,
+ }
+ if p.OccurredAt > 0 {
+ event.OccurredAt = time.UnixMilli(p.OccurredAt)
+ }
+ return event
+}
+
+func subscriptionToProto(s sdk.EventSubscription) *pb.EventSubscriptionProto {
+ return &pb.EventSubscriptionProto{
+ Type: s.Type,
+ Source: s.Source,
+ Filter: s.Filter,
+ Metadata: s.Metadata,
+ }
+}
+
+func subscriptionFromProto(p *pb.EventSubscriptionProto) sdk.EventSubscription {
+ if p == nil {
+ return sdk.EventSubscription{}
+ }
+ return sdk.EventSubscription{
+ Type: p.Type,
+ Source: p.Source,
+ Filter: p.Filter,
+ Metadata: p.Metadata,
+ }
+}
+
+func (s *EventGRPCServer) GetEventSubscriptions(_ context.Context, _ *pb.Empty) (*pb.EventSubscriptionsResponse, error) {
+ handler, ok := s.Impl.(sdk.EventHandler)
+ if !ok {
+ return &pb.EventSubscriptionsResponse{}, nil
+ }
+ subscriptions := handler.EventSubscriptions()
+ resp := &pb.EventSubscriptionsResponse{}
+ if len(subscriptions) > 0 {
+ resp.Subscriptions = make([]*pb.EventSubscriptionProto, 0, len(subscriptions))
+ }
+ for _, sub := range subscriptions {
+ resp.Subscriptions = append(resp.Subscriptions, subscriptionToProto(sub))
+ }
+ return resp, nil
+}
+
+func (s *EventGRPCServer) HandleEvent(ctx context.Context, req *pb.PluginEvent) (*pb.EventHandleResponse, error) {
+ handler, ok := s.Impl.(sdk.EventHandler)
+ if !ok {
+ return &pb.EventHandleResponse{Success: false, ErrorMessage: "plugin does not implement EventHandler"}, nil
+ }
+ if err := handler.HandleEvent(ctx, eventFromProto(req)); err != nil {
+ return &pb.EventHandleResponse{Success: false, ErrorMessage: err.Error()}, nil
+ }
+ return &pb.EventHandleResponse{Success: true}, nil
+}
+
+// EventSubscriptions 获取插件订阅的事件列表。
+func (b *pluginBase) EventSubscriptions() []sdk.EventSubscription {
+ if b.event == nil {
+ return nil
+ }
+ ctx, cancel := withTimeout()
+ defer cancel()
+
+ resp, err := b.event.GetEventSubscriptions(ctx, &pb.Empty{})
+ if err != nil {
+ return nil
+ }
+ out := make([]sdk.EventSubscription, 0, len(resp.Subscriptions))
+ for _, sub := range resp.Subscriptions {
+ out = append(out, subscriptionFromProto(sub))
+ }
+ return out
+}
+
+// HandleEvent 将 Core 事件推送给插件。
+func (b *pluginBase) HandleEvent(ctx context.Context, event sdk.PluginEvent) error {
+ if b.event == nil {
+ return errors.New("event service is not initialized")
+ }
+ resp, err := b.event.HandleEvent(ctx, eventToProto(event))
+ if err != nil {
+ return err
+ }
+ if resp != nil && !resp.Success {
+ return errors.New(resp.ErrorMessage)
+ }
+ return nil
+}
diff --git a/grpc/extension_client.go b/runtimego/grpc/extension_client.go
similarity index 78%
rename from grpc/extension_client.go
rename to runtimego/grpc/extension_client.go
index 27f0b25..2148d7b 100644
--- a/grpc/extension_client.go
+++ b/runtimego/grpc/extension_client.go
@@ -4,8 +4,8 @@ import (
"context"
"time"
- sdk "github.com/DouDOU-start/airgate-sdk"
- pb "github.com/DouDOU-start/airgate-sdk/proto"
+ pb "github.com/DouDOU-start/airgate-sdk/protocol/proto"
+ sdk "github.com/DouDOU-start/airgate-sdk/sdkgo"
)
// migrateTimeout 数据库迁移的超时时间(迁移可能涉及大量数据,需要较长时间)
@@ -70,3 +70,19 @@ func (c *ExtensionGRPCClient) HandleHTTPRequest(ctx context.Context, req *pb.Htt
func (c *ExtensionGRPCClient) HandleHTTPStreamRequest(ctx context.Context, req *pb.HttpRequest) (pb.ExtensionService_HandleStreamRequestClient, error) {
return c.extension.HandleStreamRequest(ctx, req)
}
+
+// ── 异步任务 ──
+
+// ProcessTask 由 Core 任务分发器调用,将 pending 任务分发给插件处理。
+func (c *ExtensionGRPCClient) ProcessTask(ctx context.Context, req *pb.ProcessTaskRequest) (*pb.ProcessTaskResponse, error) {
+ return c.extension.ProcessTask(ctx, req)
+}
+
+// GetTaskTypes 返回此插件支持的任务类型列表。
+func (c *ExtensionGRPCClient) GetTaskTypes(ctx context.Context) ([]string, error) {
+ resp, err := c.extension.GetTaskTypes(ctx, &pb.Empty{})
+ if err != nil {
+ return nil, err
+ }
+ return resp.Types, nil
+}
diff --git a/grpc/extension_router.go b/runtimego/grpc/extension_router.go
similarity index 97%
rename from grpc/extension_router.go
rename to runtimego/grpc/extension_router.go
index 135ea03..db80eb0 100644
--- a/grpc/extension_router.go
+++ b/runtimego/grpc/extension_router.go
@@ -5,7 +5,7 @@ import (
"strings"
"sync"
- sdk "github.com/DouDOU-start/airgate-sdk"
+ sdk "github.com/DouDOU-start/airgate-sdk/sdkgo"
)
// extensionRouter 实现 sdk.RouteRegistrar,用于在 gRPC 模式下将 HTTP 请求分发到注册的处理函数
diff --git a/grpc/extension_server.go b/runtimego/grpc/extension_server.go
similarity index 86%
rename from grpc/extension_server.go
rename to runtimego/grpc/extension_server.go
index 20be0b2..efd2766 100644
--- a/grpc/extension_server.go
+++ b/runtimego/grpc/extension_server.go
@@ -3,14 +3,15 @@ package grpc
import (
"bytes"
"context"
+ "encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"strings"
"sync"
- sdk "github.com/DouDOU-start/airgate-sdk"
- pb "github.com/DouDOU-start/airgate-sdk/proto"
+ pb "github.com/DouDOU-start/airgate-sdk/protocol/proto"
+ sdk "github.com/DouDOU-start/airgate-sdk/sdkgo"
)
// streamResponseWriter 把每次 Write 调用转为 gRPC HttpResponseChunk 发送。
@@ -257,3 +258,37 @@ func httpResponseToPB(rec *httptest.ResponseRecorder) *pb.HttpResponse {
Body: rec.Body.Bytes(),
}
}
+
+// ── 异步任务 ──
+
+func (s *ExtensionGRPCServer) ProcessTask(ctx context.Context, req *pb.ProcessTaskRequest) (*pb.ProcessTaskResponse, error) {
+ tp, ok := s.Impl.(sdk.TaskProcessor)
+ if !ok {
+ return &pb.ProcessTaskResponse{Success: false, ErrorMessage: "plugin does not implement TaskProcessor"}, nil
+ }
+
+ var input map[string]interface{}
+ if len(req.Input) > 0 {
+ _ = json.Unmarshal(req.Input, &input)
+ }
+
+ task := sdk.HostTask{
+ ID: req.TaskId,
+ TaskType: req.TaskType,
+ UserID: req.UserId,
+ Input: input,
+ }
+
+ if err := tp.ProcessTask(ctx, task); err != nil {
+ return &pb.ProcessTaskResponse{Success: false, ErrorMessage: err.Error()}, nil
+ }
+ return &pb.ProcessTaskResponse{Success: true}, nil
+}
+
+func (s *ExtensionGRPCServer) GetTaskTypes(ctx context.Context, _ *pb.Empty) (*pb.TaskTypesResponse, error) {
+ tp, ok := s.Impl.(sdk.TaskProcessor)
+ if !ok {
+ return &pb.TaskTypesResponse{}, nil
+ }
+ return &pb.TaskTypesResponse{Types: tp.TaskTypes()}, nil
+}
diff --git a/grpc/gateway_client.go b/runtimego/grpc/gateway_client.go
similarity index 93%
rename from grpc/gateway_client.go
rename to runtimego/grpc/gateway_client.go
index e112ce4..133275c 100644
--- a/grpc/gateway_client.go
+++ b/runtimego/grpc/gateway_client.go
@@ -8,8 +8,8 @@ import (
"net/http"
"sync"
- sdk "github.com/DouDOU-start/airgate-sdk"
- pb "github.com/DouDOU-start/airgate-sdk/proto"
+ pb "github.com/DouDOU-start/airgate-sdk/protocol/proto"
+ sdk "github.com/DouDOU-start/airgate-sdk/sdkgo"
)
// GatewayGRPCClient 把 gRPC 客户端包装成 GatewayPlugin 接口,供 Core 消费。
@@ -95,6 +95,7 @@ func (c *GatewayGRPCClient) Routes() []sdk.RouteDefinition {
Method: r.Method,
Path: r.Path,
Description: r.Description,
+ Metadata: r.Metadata,
}
}
c.mu.Lock()
@@ -193,21 +194,6 @@ func (c *GatewayGRPCClient) ValidateAccount(ctx context.Context, credentials map
return err
}
-func (c *GatewayGRPCClient) QueryQuota(ctx context.Context, credentials map[string]string) (*sdk.QuotaInfo, error) {
- resp, err := c.gateway.QueryQuota(ctx, &pb.CredentialsRequest{Credentials: credentials})
- if err != nil {
- return nil, err
- }
- return &sdk.QuotaInfo{
- Total: resp.Total,
- Used: resp.Used,
- Remaining: resp.Remaining,
- Currency: resp.Currency,
- ExpiresAt: resp.ExpiresAt,
- Extra: resp.Extra,
- }, nil
-}
-
// HandleWebSocket 通过 gRPC 双向流处理 WebSocket(Core 侧调用)。
func (c *GatewayGRPCClient) HandleWebSocket(ctx context.Context, conn sdk.WebSocketConn) (sdk.ForwardOutcome, error) {
stream, err := c.gateway.HandleWebSocket(ctx)
diff --git a/grpc/gateway_server.go b/runtimego/grpc/gateway_server.go
similarity index 68%
rename from grpc/gateway_server.go
rename to runtimego/grpc/gateway_server.go
index 7b1ba4a..4caa22d 100644
--- a/grpc/gateway_server.go
+++ b/runtimego/grpc/gateway_server.go
@@ -6,8 +6,8 @@ import (
"net/http"
"time"
- sdk "github.com/DouDOU-start/airgate-sdk"
- pb "github.com/DouDOU-start/airgate-sdk/proto"
+ pb "github.com/DouDOU-start/airgate-sdk/protocol/proto"
+ sdk "github.com/DouDOU-start/airgate-sdk/sdkgo"
)
// GatewayGRPCServer 将 GatewayPlugin 包装为 gRPC 服务端。
@@ -28,18 +28,12 @@ func (s *GatewayGRPCServer) GetModels(_ context.Context, _ *pb.Empty) (*pb.Model
}
for _, m := range models {
resp.Models = append(resp.Models, &pb.ModelInfoProto{
- Id: m.ID,
- Name: m.Name,
- ContextWindow: int64(m.ContextWindow),
- MaxOutputTokens: int64(m.MaxOutputTokens),
- InputPrice: m.InputPrice,
- OutputPrice: m.OutputPrice,
- CachedInputPrice: m.CachedInputPrice,
- CacheCreationPrice: m.CacheCreationPrice,
- CacheCreation_1HPrice: m.CacheCreation1hPrice,
- InputPricePriority: m.InputPricePriority,
- OutputPricePriority: m.OutputPricePriority,
- CachedInputPricePriority: m.CachedInputPricePriority,
+ Id: m.ID,
+ Name: m.Name,
+ ContextWindow: int64(m.ContextWindow),
+ MaxOutputTokens: int64(m.MaxOutputTokens),
+ Capabilities: m.Capabilities,
+ Metadata: m.Metadata,
})
}
return resp, nil
@@ -56,6 +50,7 @@ func (s *GatewayGRPCServer) GetRoutes(_ context.Context, _ *pb.Empty) (*pb.Route
Method: r.Method,
Path: r.Path,
Description: r.Description,
+ Metadata: r.Metadata,
})
}
return resp, nil
@@ -176,52 +171,181 @@ func upstreamFromProto(p *pb.UpstreamResponse) sdk.UpstreamResponse {
}
func usageToProto(u sdk.Usage) *pb.Usage {
- return &pb.Usage{
- InputTokens: int64(u.InputTokens),
- OutputTokens: int64(u.OutputTokens),
- CachedInputTokens: int64(u.CachedInputTokens),
- CacheCreationTokens: int64(u.CacheCreationTokens),
- CacheCreation_5MTokens: int64(u.CacheCreation5mTokens),
- CacheCreation_1HTokens: int64(u.CacheCreation1hTokens),
- ReasoningOutputTokens: int64(u.ReasoningOutputTokens),
- InputCost: u.InputCost,
- OutputCost: u.OutputCost,
- CachedInputCost: u.CachedInputCost,
- CacheCreationCost: u.CacheCreationCost,
- InputPrice: u.InputPrice,
- OutputPrice: u.OutputPrice,
- CachedInputPrice: u.CachedInputPrice,
- CacheCreationPrice: u.CacheCreationPrice,
- CacheCreation_1HPrice: u.CacheCreation1hPrice,
- Model: u.Model,
- ServiceTier: u.ServiceTier,
- FirstTokenMs: u.FirstTokenMs,
- ImageSize: u.ImageSize,
- }
+ out := &pb.Usage{
+ Model: u.Model,
+ AccountCost: u.AccountCost,
+ UserCost: u.UserCost,
+ BillingMultiplier: u.BillingMultiplier,
+ Currency: u.Currency,
+ Summary: u.Summary,
+ FirstTokenMs: u.FirstTokenMs,
+ Metadata: u.Metadata,
+ }
+ out.Attributes = usageAttributesToProto(u.Attributes)
+ out.Metrics = usageMetricsToProto(u.Metrics)
+ out.CostDetails = usageCostDetailsToProto(u.CostDetails)
+ return out
}
func usageFromProto(p *pb.Usage) sdk.Usage {
- return sdk.Usage{
- InputTokens: int(p.InputTokens),
- OutputTokens: int(p.OutputTokens),
- CachedInputTokens: int(p.CachedInputTokens),
- CacheCreationTokens: int(p.CacheCreationTokens),
- CacheCreation5mTokens: int(p.CacheCreation_5MTokens),
- CacheCreation1hTokens: int(p.CacheCreation_1HTokens),
- ReasoningOutputTokens: int(p.ReasoningOutputTokens),
- InputCost: p.InputCost,
- OutputCost: p.OutputCost,
- CachedInputCost: p.CachedInputCost,
- CacheCreationCost: p.CacheCreationCost,
- InputPrice: p.InputPrice,
- OutputPrice: p.OutputPrice,
- CachedInputPrice: p.CachedInputPrice,
- CacheCreationPrice: p.CacheCreationPrice,
- CacheCreation1hPrice: p.CacheCreation_1HPrice,
- Model: p.Model,
- ServiceTier: p.ServiceTier,
- FirstTokenMs: p.FirstTokenMs,
- ImageSize: p.ImageSize,
+ out := sdk.Usage{
+ Model: p.Model,
+ AccountCost: p.AccountCost,
+ UserCost: p.UserCost,
+ BillingMultiplier: p.BillingMultiplier,
+ Currency: p.Currency,
+ Summary: p.Summary,
+ FirstTokenMs: p.FirstTokenMs,
+ Metadata: p.Metadata,
+ }
+ out.Attributes = usageAttributesFromProto(p.Attributes)
+ out.Metrics = usageMetricsFromProto(p.Metrics)
+ out.CostDetails = usageCostDetailsFromProto(p.CostDetails)
+ return out
+}
+
+func usageAttributesToProto(attrs []sdk.UsageAttribute) []*pb.UsageAttribute {
+ if len(attrs) == 0 {
+ return nil
+ }
+ out := make([]*pb.UsageAttribute, 0, len(attrs))
+ for _, a := range attrs {
+ out = append(out, usageAttributeToProto(a))
+ }
+ return out
+}
+
+func usageAttributesFromProto(attrs []*pb.UsageAttribute) []sdk.UsageAttribute {
+ if len(attrs) == 0 {
+ return nil
+ }
+ out := make([]sdk.UsageAttribute, 0, len(attrs))
+ for _, a := range attrs {
+ out = append(out, usageAttributeFromProto(a))
+ }
+ return out
+}
+
+func usageAttributeToProto(a sdk.UsageAttribute) *pb.UsageAttribute {
+ return &pb.UsageAttribute{
+ Key: a.Key,
+ Label: a.Label,
+ Kind: a.Kind,
+ Value: a.Value,
+ Metadata: a.Metadata,
+ }
+}
+
+func usageAttributeFromProto(p *pb.UsageAttribute) sdk.UsageAttribute {
+ if p == nil {
+ return sdk.UsageAttribute{}
+ }
+ return sdk.UsageAttribute{
+ Key: p.Key,
+ Label: p.Label,
+ Kind: p.Kind,
+ Value: p.Value,
+ Metadata: p.Metadata,
+ }
+}
+
+func usageMetricsToProto(metrics []sdk.UsageMetric) []*pb.UsageMetric {
+ if len(metrics) == 0 {
+ return nil
+ }
+ out := make([]*pb.UsageMetric, 0, len(metrics))
+ for _, m := range metrics {
+ out = append(out, usageMetricToProto(m))
+ }
+ return out
+}
+
+func usageMetricsFromProto(metrics []*pb.UsageMetric) []sdk.UsageMetric {
+ if len(metrics) == 0 {
+ return nil
+ }
+ out := make([]sdk.UsageMetric, 0, len(metrics))
+ for _, m := range metrics {
+ out = append(out, usageMetricFromProto(m))
+ }
+ return out
+}
+
+func usageMetricToProto(m sdk.UsageMetric) *pb.UsageMetric {
+ return &pb.UsageMetric{
+ Key: m.Key,
+ Label: m.Label,
+ Kind: m.Kind,
+ Unit: m.Unit,
+ Value: m.Value,
+ AccountCost: m.AccountCost,
+ Currency: m.Currency,
+ Metadata: m.Metadata,
+ }
+}
+
+func usageMetricFromProto(p *pb.UsageMetric) sdk.UsageMetric {
+ if p == nil {
+ return sdk.UsageMetric{}
+ }
+ return sdk.UsageMetric{
+ Key: p.Key,
+ Label: p.Label,
+ Kind: p.Kind,
+ Unit: p.Unit,
+ Value: p.Value,
+ AccountCost: p.AccountCost,
+ Currency: p.Currency,
+ Metadata: p.Metadata,
+ }
+}
+
+func usageCostDetailsToProto(details []sdk.UsageCostDetail) []*pb.UsageCostDetail {
+ if len(details) == 0 {
+ return nil
+ }
+ out := make([]*pb.UsageCostDetail, 0, len(details))
+ for _, c := range details {
+ out = append(out, usageCostDetailToProto(c))
+ }
+ return out
+}
+
+func usageCostDetailsFromProto(details []*pb.UsageCostDetail) []sdk.UsageCostDetail {
+ if len(details) == 0 {
+ return nil
+ }
+ out := make([]sdk.UsageCostDetail, 0, len(details))
+ for _, c := range details {
+ out = append(out, usageCostDetailFromProto(c))
+ }
+ return out
+}
+
+func usageCostDetailToProto(c sdk.UsageCostDetail) *pb.UsageCostDetail {
+ return &pb.UsageCostDetail{
+ Key: c.Key,
+ Label: c.Label,
+ AccountCost: c.AccountCost,
+ UserCost: c.UserCost,
+ BillingMultiplier: c.BillingMultiplier,
+ Currency: c.Currency,
+ Metadata: c.Metadata,
+ }
+}
+
+func usageCostDetailFromProto(p *pb.UsageCostDetail) sdk.UsageCostDetail {
+ if p == nil {
+ return sdk.UsageCostDetail{}
+ }
+ return sdk.UsageCostDetail{
+ Key: p.Key,
+ Label: p.Label,
+ AccountCost: p.AccountCost,
+ UserCost: p.UserCost,
+ BillingMultiplier: p.BillingMultiplier,
+ Currency: p.Currency,
+ Metadata: p.Metadata,
}
}
@@ -344,21 +468,6 @@ func (s *GatewayGRPCServer) ValidateAccount(ctx context.Context, req *pb.Credent
return &pb.Empty{}, nil
}
-func (s *GatewayGRPCServer) QueryQuota(ctx context.Context, req *pb.CredentialsRequest) (*pb.QuotaInfoResponse, error) {
- info, err := s.Impl.QueryQuota(ctx, req.Credentials)
- if err != nil {
- return nil, err
- }
- return &pb.QuotaInfoResponse{
- Total: info.Total,
- Used: info.Used,
- Remaining: info.Remaining,
- Currency: info.Currency,
- ExpiresAt: info.ExpiresAt,
- Extra: info.Extra,
- }, nil
-}
-
// streamWriter 把 gRPC 流包装成 http.ResponseWriter。
type streamWriter struct {
stream pb.GatewayService_ForwardStreamServer
diff --git a/grpc/gateway_stream_test.go b/runtimego/grpc/gateway_stream_test.go
similarity index 96%
rename from grpc/gateway_stream_test.go
rename to runtimego/grpc/gateway_stream_test.go
index 6fc1d9e..16374d3 100644
--- a/grpc/gateway_stream_test.go
+++ b/runtimego/grpc/gateway_stream_test.go
@@ -10,8 +10,8 @@ import (
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
- sdk "github.com/DouDOU-start/airgate-sdk"
- pb "github.com/DouDOU-start/airgate-sdk/proto"
+ pb "github.com/DouDOU-start/airgate-sdk/protocol/proto"
+ sdk "github.com/DouDOU-start/airgate-sdk/sdkgo"
)
type stubForwardStreamServer struct {
@@ -89,9 +89,6 @@ func (c *stubGatewayServiceClient) ForwardStream(context.Context, *pb.ForwardReq
func (c *stubGatewayServiceClient) ValidateAccount(context.Context, *pb.CredentialsRequest, ...grpc.CallOption) (*pb.Empty, error) {
return nil, nil
}
-func (c *stubGatewayServiceClient) QueryQuota(context.Context, *pb.CredentialsRequest, ...grpc.CallOption) (*pb.QuotaInfoResponse, error) {
- return nil, nil
-}
func (c *stubGatewayServiceClient) HandleWebSocket(context.Context, ...grpc.CallOption) (grpc.BidiStreamingClient[pb.WebSocketFrame, pb.WebSocketFrame], error) {
return nil, nil
}
diff --git a/grpc/go_plugin.go b/runtimego/grpc/go_plugin.go
similarity index 60%
rename from grpc/go_plugin.go
rename to runtimego/grpc/go_plugin.go
index 8aca307..ee3da63 100644
--- a/grpc/go_plugin.go
+++ b/runtimego/grpc/go_plugin.go
@@ -8,8 +8,8 @@ import (
goplugin "github.com/hashicorp/go-plugin"
"google.golang.org/grpc"
- sdk "github.com/DouDOU-start/airgate-sdk"
- pb "github.com/DouDOU-start/airgate-sdk/proto"
+ pb "github.com/DouDOU-start/airgate-sdk/protocol/proto"
+ sdk "github.com/DouDOU-start/airgate-sdk/sdkgo"
)
// 确保所有 Plugin 类型都实现了 goplugin.GRPCPlugin 接口
@@ -21,29 +21,30 @@ var (
// GatewayGRPCPlugin 实现 hashicorp/go-plugin.GRPCPlugin 接口
//
-// HostImpl 字段由 Core 在构造 ClientConfig 时注入。当 HostImpl 非 nil 时,
-// GRPCClient 钩子会通过 GRPCBroker 启一条新的 stream,注册 HostService server,
-// 把 stream id 通过 pluginBase.hostBrokerID 透传给后续 Init 调用。
+// CoreInvokeImpl 字段由 Core 在构造 ClientConfig 时注入。当 CoreInvokeImpl 非 nil 时,
+// GRPCClient 钩子会通过 GRPCBroker 启一条新的 stream,注册 CoreInvokeService server,
+// 把 stream id 通过 pluginBase.coreInvokeBrokerID 透传给后续 Init 调用。
//
-// 插件进程构造 GRPCServer 时不会用到 HostImpl(HostImpl 只在 host 侧有值),
-// 所以插件二进制 main.go 里 Serve(impl) 时不需要也不能填 HostImpl。
+// 插件进程构造 GRPCServer 时不会用到 CoreInvokeImpl(CoreInvokeImpl 只在 Core 侧有值),
+// 所以插件二进制 main.go 里 Serve(impl) 时不需要也不能填 CoreInvokeImpl。
type GatewayGRPCPlugin struct {
goplugin.Plugin
- Impl sdk.GatewayPlugin
- HostImpl pb.HostServiceServer // host 侧注入;plugin 侧为 nil
+ Impl sdk.GatewayPlugin
+ CoreInvokeImpl pb.CoreInvokeServiceServer // Core 侧注入;plugin 侧为 nil
}
func (p *GatewayGRPCPlugin) GRPCServer(broker *goplugin.GRPCBroker, s *grpc.Server) error {
pb.RegisterPluginServiceServer(s, &PluginGRPCServer{Impl: p.Impl, Broker: broker})
pb.RegisterGatewayServiceServer(s, &GatewayGRPCServer{Impl: p.Impl})
+ pb.RegisterEventServiceServer(s, &EventGRPCServer{Impl: p.Impl})
return nil
}
func (p *GatewayGRPCPlugin) GRPCClient(_ context.Context, broker *goplugin.GRPCBroker, c *grpc.ClientConn) (interface{}, error) {
- hostBrokerID := startHostStream(broker, p.HostImpl)
+ coreInvokeBrokerID := startCoreInvokeStream(broker, p.CoreInvokeImpl)
pluginClient := pb.NewPluginServiceClient(c)
return &GatewayGRPCClient{
- pluginBase: pluginBase{plugin: pluginClient, hostBrokerID: hostBrokerID},
+ pluginBase: pluginBase{plugin: pluginClient, event: pb.NewEventServiceClient(c), coreInvokeBrokerID: coreInvokeBrokerID},
gateway: pb.NewGatewayServiceClient(c),
}, nil
}
@@ -51,8 +52,8 @@ func (p *GatewayGRPCPlugin) GRPCClient(_ context.Context, broker *goplugin.GRPCB
// ExtensionGRPCPlugin 实现扩展插件的 go-plugin 接口
type ExtensionGRPCPlugin struct {
goplugin.Plugin
- Impl sdk.ExtensionPlugin
- HostImpl pb.HostServiceServer
+ Impl sdk.ExtensionPlugin
+ CoreInvokeImpl pb.CoreInvokeServiceServer
}
func (p *ExtensionGRPCPlugin) GRPCServer(broker *goplugin.GRPCBroker, s *grpc.Server) error {
@@ -60,54 +61,55 @@ func (p *ExtensionGRPCPlugin) GRPCServer(broker *goplugin.GRPCBroker, s *grpc.Se
extServer := &ExtensionGRPCServer{Impl: p.Impl}
extServer.initRouter()
pb.RegisterExtensionServiceServer(s, extServer)
+ pb.RegisterEventServiceServer(s, &EventGRPCServer{Impl: p.Impl})
return nil
}
func (p *ExtensionGRPCPlugin) GRPCClient(_ context.Context, broker *goplugin.GRPCBroker, c *grpc.ClientConn) (interface{}, error) {
- hostBrokerID := startHostStream(broker, p.HostImpl)
+ coreInvokeBrokerID := startCoreInvokeStream(broker, p.CoreInvokeImpl)
pluginClient := pb.NewPluginServiceClient(c)
return &ExtensionGRPCClient{
- pluginBase: pluginBase{plugin: pluginClient, hostBrokerID: hostBrokerID},
+ pluginBase: pluginBase{plugin: pluginClient, event: pb.NewEventServiceClient(c), coreInvokeBrokerID: coreInvokeBrokerID},
extension: pb.NewExtensionServiceClient(c),
}, nil
}
-// MiddlewareGRPCPlugin 实现中间件插件的 go-plugin 接口(ADR-0001 Decision 2)。
+// MiddlewareGRPCPlugin 实现中间件插件的 go-plugin 接口。
//
-// HostImpl 用法与 GatewayGRPCPlugin 相同:core 侧注入 HostService 实现,
+// CoreInvokeImpl 用法与 GatewayGRPCPlugin 相同:Core 侧注入反向调用实现,
// 在 GRPCClient 钩子里通过 GRPCBroker 启反向 stream。
type MiddlewareGRPCPlugin struct {
goplugin.Plugin
- Impl sdk.MiddlewarePlugin
- HostImpl pb.HostServiceServer
+ Impl sdk.MiddlewarePlugin
+ CoreInvokeImpl pb.CoreInvokeServiceServer
}
func (p *MiddlewareGRPCPlugin) GRPCServer(broker *goplugin.GRPCBroker, s *grpc.Server) error {
pb.RegisterPluginServiceServer(s, &PluginGRPCServer{Impl: p.Impl, Broker: broker})
pb.RegisterMiddlewareServiceServer(s, &MiddlewareGRPCServer{Impl: p.Impl})
+ pb.RegisterEventServiceServer(s, &EventGRPCServer{Impl: p.Impl})
return nil
}
func (p *MiddlewareGRPCPlugin) GRPCClient(_ context.Context, broker *goplugin.GRPCBroker, c *grpc.ClientConn) (interface{}, error) {
- hostBrokerID := startHostStream(broker, p.HostImpl)
+ coreInvokeBrokerID := startCoreInvokeStream(broker, p.CoreInvokeImpl)
pluginClient := pb.NewPluginServiceClient(c)
return &MiddlewareGRPCClient{
- pluginBase: pluginBase{plugin: pluginClient, hostBrokerID: hostBrokerID},
+ pluginBase: pluginBase{plugin: pluginClient, event: pb.NewEventServiceClient(c), coreInvokeBrokerID: coreInvokeBrokerID},
mw: pb.NewMiddlewareServiceClient(c),
}, nil
}
-// startHostStream 在 host 进程侧通过 GRPCBroker 启动一条新的 stream,
-// 注册 HostService server,返回 stream id(作为 host_broker_id 透传给插件)。
+// startCoreInvokeStream 在 Core 进程侧通过 GRPCBroker 启动一条新的 stream,
+// 注册 CoreInvokeService server,返回 stream id(作为 core_invoke_broker_id 透传给插件)。
//
-// hostImpl 为 nil 时表示 Core 没启用 HostService,返回 0;插件 Init 收到 0
-// 后会在 ctx.Host() 时返回 nil。这是软失败:旧版 Core / 不需要 host 的部署
-// 都正常工作。
+// coreInvokeImpl 为 nil 时表示 Core 没启用反向调用,返回 0;插件 Init 收到 0
+// 后会在 ctx.Host() 时返回 nil。
//
// 这里的 grpc.NewServer 会装上 LoggingUnaryServerInterceptor / LoggingStreamServerInterceptor,
// 插件→core 的 host 调用因此可观测。
-func startHostStream(broker *goplugin.GRPCBroker, hostImpl pb.HostServiceServer) uint32 {
- if hostImpl == nil || broker == nil {
+func startCoreInvokeStream(broker *goplugin.GRPCBroker, coreInvokeImpl pb.CoreInvokeServiceServer) uint32 {
+ if coreInvokeImpl == nil || broker == nil {
return 0
}
id := broker.NextId()
@@ -119,10 +121,10 @@ func startHostStream(broker *goplugin.GRPCBroker, hostImpl pb.HostServiceServer)
grpc.ChainStreamInterceptor(LoggingStreamServerInterceptor()),
)
s := grpc.NewServer(opts...)
- pb.RegisterHostServiceServer(s, hostImpl)
+ pb.RegisterCoreInvokeServiceServer(s, coreInvokeImpl)
return s
})
- slog.Debug("HostService stream 已就绪", "broker_id", id)
+ slog.Debug("CoreInvoke stream 已就绪", "broker_id", id)
return id
}
@@ -141,6 +143,9 @@ func Serve(impl interface{}) {
switch p := impl.(type) {
case sdk.GatewayPlugin:
pluginMap[PluginKeyGateway] = &GatewayGRPCPlugin{Impl: p}
+ if tp, ok := impl.(sdk.TaskProcessor); ok {
+ pluginMap[PluginKeyExtension] = &ExtensionGRPCPlugin{Impl: &gatewayTaskAdapter{GatewayPlugin: p, tp: tp}}
+ }
case sdk.ExtensionPlugin:
pluginMap[PluginKeyExtension] = &ExtensionGRPCPlugin{Impl: p}
case sdk.MiddlewarePlugin:
@@ -164,6 +169,26 @@ func Serve(impl interface{}) {
})
}
+// gatewayTaskAdapter 把同时实现了 TaskProcessor 的 GatewayPlugin 包装为
+// ExtensionPlugin,让 ExtensionGRPCServer 能通过类型断言调用 ProcessTask / GetTaskTypes。
+// RegisterRoutes / Migrate / BackgroundTasks 保持空操作——网关插件不需要扩展插件的这些能力。
+type gatewayTaskAdapter struct {
+ sdk.GatewayPlugin
+ tp sdk.TaskProcessor
+}
+
+func (a *gatewayTaskAdapter) RegisterRoutes(_ sdk.RouteRegistrar) {}
+func (a *gatewayTaskAdapter) Migrate() error { return nil }
+func (a *gatewayTaskAdapter) BackgroundTasks() []sdk.BackgroundTask {
+ return nil
+}
+func (a *gatewayTaskAdapter) ProcessTask(ctx context.Context, task sdk.HostTask) error {
+ return a.tp.ProcessTask(ctx, task)
+}
+func (a *gatewayTaskAdapter) TaskTypes() []string {
+ return a.tp.TaskTypes()
+}
+
// PluginGRPCMaxMessageBytes 是插件 gRPC 服务端单条消息最大字节数(收/发同值)。
// 默认 4 MB 经常被大段 LLM 响应或翻译后的 SSE 事件击穿,统一抬到 64 MB;
// 必须与 core 侧 ClientConfig.GRPCDialOptions 中的上限保持一致。
diff --git a/grpc/handshake.go b/runtimego/grpc/handshake.go
similarity index 87%
rename from grpc/handshake.go
rename to runtimego/grpc/handshake.go
index 741a1d5..d05def4 100644
--- a/grpc/handshake.go
+++ b/runtimego/grpc/handshake.go
@@ -4,9 +4,9 @@ import "github.com/hashicorp/go-plugin"
// Handshake 统一握手配置,核心和插件必须使用相同值
var Handshake = plugin.HandshakeConfig{
- ProtocolVersion: 1,
+ ProtocolVersion: 2,
MagicCookieKey: "AIRGATE_PLUGIN",
- MagicCookieValue: "airgate-v1",
+ MagicCookieValue: "airgate-v2",
}
// PluginMap 插件类型到 go-plugin.Plugin 的映射键名
diff --git a/runtimego/grpc/host_client.go b/runtimego/grpc/host_client.go
new file mode 100644
index 0000000..f42aca2
--- /dev/null
+++ b/runtimego/grpc/host_client.go
@@ -0,0 +1,122 @@
+package grpc
+
+import (
+ "context"
+ "log/slog"
+ "time"
+
+ "google.golang.org/grpc"
+
+ pb "github.com/DouDOU-start/airgate-sdk/protocol/proto"
+ sdk "github.com/DouDOU-start/airgate-sdk/sdkgo"
+)
+
+// hostClient 把 pb.CoreInvokeServiceClient 包装成 sdk.Host 接口。
+// 插件代码只看到 sdk.Host,不直接接触 protobuf 类型。
+type hostClient struct {
+ c pb.CoreInvokeServiceClient
+}
+
+// NewHostClient 用一个 grpc client 构造 sdk.Host。
+// 一般由 grpcPluginContext.Host() lazy 调用,不建议插件直接构造。
+func NewHostClient(c pb.CoreInvokeServiceClient) sdk.Host {
+ return &hostClient{c: c}
+}
+
+// hostRPCLogger 派生 host 调用专用 logger,并返回起始时间。
+func hostRPCLogger(ctx context.Context, method string) (*slog.Logger, time.Time) {
+ return sdk.LoggerFromContext(ctx).With("host_rpc", method), time.Now()
+}
+
+// Invoke 调用 Core 方法注册表中开放的方法。
+func (h *hostClient) Invoke(ctx context.Context, req sdk.HostInvokeRequest) (*sdk.HostInvokeResponse, error) {
+ logger, start := hostRPCLogger(ctx, "Invoke")
+ resp, err := h.c.Invoke(ctx, &pb.HostInvokeRequest{
+ Method: req.Method,
+ Payload: mapToJSONPayload(req.Payload),
+ IdempotencyKey: req.IdempotencyKey,
+ Metadata: req.Metadata,
+ })
+ if err != nil {
+ logger.Error("host_call_invoke_failed",
+ "method", req.Method,
+ sdk.LogFieldDurationMs, time.Since(start).Milliseconds(),
+ sdk.LogFieldError, err,
+ )
+ return nil, err
+ }
+ logger.Debug("host_call_invoke_completed",
+ "method", req.Method,
+ sdk.LogFieldDurationMs, time.Since(start).Milliseconds(),
+ )
+ return &sdk.HostInvokeResponse{
+ Status: resp.Status,
+ Payload: jsonPayloadToMap(resp.Payload),
+ Metadata: resp.Metadata,
+ }, nil
+}
+
+// InvokeStream 调用 Core 方法注册表中开放的流式方法。
+func (h *hostClient) InvokeStream(ctx context.Context, req sdk.HostStreamRequest) (sdk.HostStream, error) {
+ logger, start := hostRPCLogger(ctx, "InvokeStream")
+ stream, err := h.c.InvokeStream(ctx)
+ if err != nil {
+ logger.Error("host_call_invoke_stream_open_failed",
+ "method", req.Method,
+ sdk.LogFieldDurationMs, time.Since(start).Milliseconds(),
+ sdk.LogFieldError, err,
+ )
+ return nil, err
+ }
+ if err := stream.Send(&pb.HostStreamFrame{
+ Method: req.Method,
+ Payload: mapToJSONPayload(req.Payload),
+ IdempotencyKey: req.IdempotencyKey,
+ Metadata: req.Metadata,
+ }); err != nil {
+ _ = stream.CloseSend()
+ logger.Error("host_call_invoke_stream_start_failed",
+ "method", req.Method,
+ sdk.LogFieldDurationMs, time.Since(start).Milliseconds(),
+ sdk.LogFieldError, err,
+ )
+ return nil, err
+ }
+ logger.Debug("host_call_invoke_stream_opened",
+ "method", req.Method,
+ sdk.LogFieldDurationMs, time.Since(start).Milliseconds(),
+ )
+ return &hostStream{stream: stream}, nil
+}
+
+type hostStream struct {
+ stream grpc.BidiStreamingClient[pb.HostStreamFrame, pb.HostStreamFrame]
+}
+
+func (s *hostStream) Send(frame sdk.HostStreamFrame) error {
+ return s.stream.Send(&pb.HostStreamFrame{
+ Event: frame.Event,
+ Status: frame.Status,
+ Payload: mapToJSONPayload(frame.Payload),
+ Metadata: frame.Metadata,
+ Done: frame.Done,
+ })
+}
+
+func (s *hostStream) Recv() (*sdk.HostStreamFrame, error) {
+ frame, err := s.stream.Recv()
+ if err != nil {
+ return nil, err
+ }
+ return &sdk.HostStreamFrame{
+ Event: frame.Event,
+ Status: frame.Status,
+ Payload: jsonPayloadToMap(frame.Payload),
+ Metadata: frame.Metadata,
+ Done: frame.Done,
+ }, nil
+}
+
+func (s *hostStream) CloseSend() error {
+ return s.stream.CloseSend()
+}
diff --git a/runtimego/grpc/host_client_test.go b/runtimego/grpc/host_client_test.go
new file mode 100644
index 0000000..437c78f
--- /dev/null
+++ b/runtimego/grpc/host_client_test.go
@@ -0,0 +1,146 @@
+package grpc
+
+import (
+ "context"
+ "io"
+ "reflect"
+ "testing"
+
+ "google.golang.org/grpc"
+ "google.golang.org/grpc/metadata"
+
+ pb "github.com/DouDOU-start/airgate-sdk/protocol/proto"
+ sdk "github.com/DouDOU-start/airgate-sdk/sdkgo"
+)
+
+type stubCoreInvokeClient struct {
+ stream *stubHostStreamClient
+}
+
+func (c *stubCoreInvokeClient) Invoke(context.Context, *pb.HostInvokeRequest, ...grpc.CallOption) (*pb.HostInvokeResponse, error) {
+ return &pb.HostInvokeResponse{Status: "ok"}, nil
+}
+
+func (c *stubCoreInvokeClient) InvokeStream(context.Context, ...grpc.CallOption) (grpc.BidiStreamingClient[pb.HostStreamFrame, pb.HostStreamFrame], error) {
+ return c.stream, nil
+}
+
+type stubHostStreamClient struct {
+ ctx context.Context
+ sentFrames []*pb.HostStreamFrame
+ recvFrames []*pb.HostStreamFrame
+ recvIndex int
+ closed bool
+}
+
+func (s *stubHostStreamClient) Send(frame *pb.HostStreamFrame) error {
+ s.sentFrames = append(s.sentFrames, frame)
+ return nil
+}
+
+func (s *stubHostStreamClient) Recv() (*pb.HostStreamFrame, error) {
+ if s.recvIndex >= len(s.recvFrames) {
+ return nil, io.EOF
+ }
+ frame := s.recvFrames[s.recvIndex]
+ s.recvIndex++
+ return frame, nil
+}
+
+func (s *stubHostStreamClient) Header() (metadata.MD, error) { return metadata.MD{}, nil }
+func (s *stubHostStreamClient) Trailer() metadata.MD { return metadata.MD{} }
+func (s *stubHostStreamClient) CloseSend() error {
+ s.closed = true
+ return nil
+}
+func (s *stubHostStreamClient) Context() context.Context {
+ if s.ctx != nil {
+ return s.ctx
+ }
+ return context.Background()
+}
+func (s *stubHostStreamClient) SendMsg(any) error { return nil }
+func (s *stubHostStreamClient) RecvMsg(any) error { return nil }
+
+func TestHostClientInvokeStreamRoundTrip(t *testing.T) {
+ grpcStream := &stubHostStreamClient{
+ recvFrames: []*pb.HostStreamFrame{
+ {
+ Event: "chunk",
+ Payload: mapToJSONPayload(map[string]interface{}{"delta": "hello"}),
+ Metadata: map[string]string{"seq": "1"},
+ },
+ {
+ Event: "result",
+ Status: "ok",
+ Payload: mapToJSONPayload(map[string]interface{}{"done": true}),
+ Done: true,
+ },
+ },
+ }
+ host := NewHostClient(&stubCoreInvokeClient{stream: grpcStream})
+
+ stream, err := host.InvokeStream(context.Background(), sdk.HostStreamRequest{
+ Method: "chat.stream",
+ Payload: map[string]interface{}{"prompt": "hi"},
+ IdempotencyKey: "idem_1",
+ Metadata: map[string]string{"trace": "abc"},
+ })
+ if err != nil {
+ t.Fatalf("InvokeStream() error = %v", err)
+ }
+ if len(grpcStream.sentFrames) != 1 {
+ t.Fatalf("首帧数量 = %d,期望 1", len(grpcStream.sentFrames))
+ }
+ start := grpcStream.sentFrames[0]
+ if start.Method != "chat.stream" || start.IdempotencyKey != "idem_1" {
+ t.Fatalf("首帧 method/idempotency = %q/%q", start.Method, start.IdempotencyKey)
+ }
+ if got := jsonPayloadToMap(start.Payload); !reflect.DeepEqual(got, map[string]interface{}{"prompt": "hi"}) {
+ t.Fatalf("首帧 payload = %v", got)
+ }
+ if !reflect.DeepEqual(start.Metadata, map[string]string{"trace": "abc"}) {
+ t.Fatalf("首帧 metadata = %v", start.Metadata)
+ }
+
+ if err := stream.Send(sdk.HostStreamFrame{
+ Event: "client_ack",
+ Payload: map[string]interface{}{"received": float64(1)},
+ Metadata: map[string]string{"side": "plugin"},
+ }); err != nil {
+ t.Fatalf("Send() error = %v", err)
+ }
+ if len(grpcStream.sentFrames) != 2 {
+ t.Fatalf("发送帧数量 = %d,期望 2", len(grpcStream.sentFrames))
+ }
+ ack := grpcStream.sentFrames[1]
+ if ack.Method != "" || ack.Event != "client_ack" {
+ t.Fatalf("后续帧 method/event = %q/%q", ack.Method, ack.Event)
+ }
+ if got := jsonPayloadToMap(ack.Payload); !reflect.DeepEqual(got, map[string]interface{}{"received": float64(1)}) {
+ t.Fatalf("后续帧 payload = %v", got)
+ }
+
+ chunk, err := stream.Recv()
+ if err != nil {
+ t.Fatalf("Recv chunk error = %v", err)
+ }
+ if chunk.Event != "chunk" || !reflect.DeepEqual(chunk.Payload, map[string]interface{}{"delta": "hello"}) {
+ t.Fatalf("chunk = %+v", chunk)
+ }
+
+ final, err := stream.Recv()
+ if err != nil {
+ t.Fatalf("Recv final error = %v", err)
+ }
+ if !final.Done || final.Status != "ok" || !reflect.DeepEqual(final.Payload, map[string]interface{}{"done": true}) {
+ t.Fatalf("final = %+v", final)
+ }
+
+ if err := stream.CloseSend(); err != nil {
+ t.Fatalf("CloseSend() error = %v", err)
+ }
+ if !grpcStream.closed {
+ t.Fatal("底层 stream 未关闭发送方向")
+ }
+}
diff --git a/runtimego/grpc/json_payload.go b/runtimego/grpc/json_payload.go
new file mode 100644
index 0000000..2497e55
--- /dev/null
+++ b/runtimego/grpc/json_payload.go
@@ -0,0 +1,22 @@
+package grpc
+
+import "encoding/json"
+
+func mapToJSONPayload(m map[string]interface{}) []byte {
+ if len(m) == 0 {
+ return nil
+ }
+ data, _ := json.Marshal(m)
+ return data
+}
+
+func jsonPayloadToMap(data []byte) map[string]interface{} {
+ if len(data) == 0 {
+ return nil
+ }
+ var out map[string]interface{}
+ if err := json.Unmarshal(data, &out); err != nil {
+ return nil
+ }
+ return out
+}
diff --git a/grpc/logging_interceptor.go b/runtimego/grpc/logging_interceptor.go
similarity index 99%
rename from grpc/logging_interceptor.go
rename to runtimego/grpc/logging_interceptor.go
index e23efe3..5c371cc 100644
--- a/grpc/logging_interceptor.go
+++ b/runtimego/grpc/logging_interceptor.go
@@ -11,7 +11,7 @@ import (
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
- sdk "github.com/DouDOU-start/airgate-sdk"
+ sdk "github.com/DouDOU-start/airgate-sdk/sdkgo"
)
// MetadataRequestIDKey 是 gRPC metadata 中 request_id 的键。
diff --git a/grpc/middleware_client.go b/runtimego/grpc/middleware_client.go
similarity index 66%
rename from grpc/middleware_client.go
rename to runtimego/grpc/middleware_client.go
index 0521944..b5e27b0 100644
--- a/grpc/middleware_client.go
+++ b/runtimego/grpc/middleware_client.go
@@ -4,8 +4,8 @@ import (
"context"
"time"
- sdk "github.com/DouDOU-start/airgate-sdk"
- pb "github.com/DouDOU-start/airgate-sdk/proto"
+ pb "github.com/DouDOU-start/airgate-sdk/protocol/proto"
+ sdk "github.com/DouDOU-start/airgate-sdk/sdkgo"
)
// MiddlewareGRPCClient 把 pb.MiddlewareServiceClient 包装成给 core 用的 plain Go API。
@@ -13,7 +13,7 @@ import (
// 嵌入 pluginBase 自动获得 Info / Init / Start / Stop / GetWebAssets / HealthCheck
// 等基础能力,与 GatewayGRPCClient / ExtensionGRPCClient 平行。
//
-// 失败语义(ADR-0001 Decision 2):transport 层 error 在 core 侧由 manager 转化为
+// 失败语义:transport 层 error 在 core 侧由 manager 转化为
// log warn + 跳过本次调用。本 client 只做 protobuf 序列化,不吞 error。
type MiddlewareGRPCClient struct {
pluginBase
@@ -49,16 +49,16 @@ func middlewareRequestToProto(req *sdk.MiddlewareRequest) *pb.MiddlewareRequest
return &pb.MiddlewareRequest{}
}
out := &pb.MiddlewareRequest{
- RequestId: req.RequestID,
- UserId: req.UserID,
- GroupId: req.GroupID,
- AccountId: req.AccountID,
- Platform: req.Platform,
- Model: req.Model,
- Stream: req.Stream,
- InputTokensEst: req.InputTokensEst,
- Metadata: cloneStringMapMW(req.Metadata),
- RequestBody: req.RequestBody,
+ RequestId: req.RequestID,
+ UserId: req.UserID,
+ GroupId: req.GroupID,
+ AccountId: req.AccountID,
+ Platform: req.Platform,
+ Model: req.Model,
+ Stream: req.Stream,
+ Estimates: usageMetricsToProto(req.Estimates),
+ Metadata: cloneStringMapMW(req.Metadata),
+ RequestBody: req.RequestBody,
}
if len(req.RequestHeaders) > 0 {
out.RequestHeaders = httpHeadersToProto(req.RequestHeaders)
@@ -71,27 +71,23 @@ func middlewareEventToProto(evt *sdk.MiddlewareEvent) *pb.MiddlewareEvent {
return &pb.MiddlewareEvent{}
}
out := &pb.MiddlewareEvent{
- RequestId: evt.RequestID,
- UserId: evt.UserID,
- GroupId: evt.GroupID,
- AccountId: evt.AccountID,
- Platform: evt.Platform,
- Model: evt.Model,
- Stream: evt.Stream,
- InputTokensEst: evt.InputTokensEst,
- StatusCode: int64(evt.StatusCode),
- DurationMs: int64(evt.Duration / time.Millisecond),
- InputTokens: evt.InputTokens,
- OutputTokens: evt.OutputTokens,
- CachedInputTokens: evt.CachedInputTokens,
- FirstTokenMs: evt.FirstTokenMs,
- ErrorKind: evt.ErrorKind,
- ErrorMsg: evt.ErrorMsg,
- InputCost: evt.InputCost,
- OutputCost: evt.OutputCost,
- CachedInputCost: evt.CachedInputCost,
- Metadata: cloneStringMapMW(evt.Metadata),
- ResponseBody: evt.ResponseBody,
+ RequestId: evt.RequestID,
+ UserId: evt.UserID,
+ GroupId: evt.GroupID,
+ AccountId: evt.AccountID,
+ Platform: evt.Platform,
+ Model: evt.Model,
+ Stream: evt.Stream,
+ Estimates: usageMetricsToProto(evt.Estimates),
+ StatusCode: int64(evt.StatusCode),
+ DurationMs: int64(evt.Duration / time.Millisecond),
+ ErrorKind: evt.ErrorKind,
+ ErrorMsg: evt.ErrorMsg,
+ Metadata: cloneStringMapMW(evt.Metadata),
+ ResponseBody: evt.ResponseBody,
+ }
+ if evt.Usage != nil {
+ out.Usage = usageToProto(*evt.Usage)
}
if len(evt.ResponseHeaders) > 0 {
out.ResponseHeaders = httpHeadersToProto(evt.ResponseHeaders)
diff --git a/grpc/middleware_server.go b/runtimego/grpc/middleware_server.go
similarity index 70%
rename from grpc/middleware_server.go
rename to runtimego/grpc/middleware_server.go
index 390ed86..23c4b74 100644
--- a/grpc/middleware_server.go
+++ b/runtimego/grpc/middleware_server.go
@@ -4,16 +4,15 @@ import (
"context"
"time"
- sdk "github.com/DouDOU-start/airgate-sdk"
- pb "github.com/DouDOU-start/airgate-sdk/proto"
+ pb "github.com/DouDOU-start/airgate-sdk/protocol/proto"
+ sdk "github.com/DouDOU-start/airgate-sdk/sdkgo"
)
// MiddlewareGRPCServer 把 sdk.MiddlewarePlugin 实现包成 gRPC server。
//
// 失败语义:插件代码返回 error 时,server 仍然返回 (response, nil) 给 core。
// 这样 core 在 transport 层不会看到 error,由 core 自己根据 response 内容判断
-// (或在 core 侧做 deadline 超时控制)。这是 ADR-0001 Decision 2 "middleware 永远
-// 不能 block 生产" 的落地点。
+// (或在 core 侧做 deadline 超时控制)。middleware 调用不得阻塞生产流量。
//
// 目前 server 端不主动吞 error;client 端会把 transport error 转化为 log warn
// 然后让 core 跳过这个 middleware。两层都有保护。
@@ -63,16 +62,16 @@ func middlewareRequestFromProto(req *pb.MiddlewareRequest) *sdk.MiddlewareReques
return nil
}
out := &sdk.MiddlewareRequest{
- RequestID: req.RequestId,
- UserID: req.UserId,
- GroupID: req.GroupId,
- AccountID: req.AccountId,
- Platform: req.Platform,
- Model: req.Model,
- Stream: req.Stream,
- InputTokensEst: req.InputTokensEst,
- Metadata: cloneStringMapMW(req.Metadata),
- RequestBody: req.RequestBody,
+ RequestID: req.RequestId,
+ UserID: req.UserId,
+ GroupID: req.GroupId,
+ AccountID: req.AccountId,
+ Platform: req.Platform,
+ Model: req.Model,
+ Stream: req.Stream,
+ Estimates: usageMetricsFromProto(req.Estimates),
+ Metadata: cloneStringMapMW(req.Metadata),
+ RequestBody: req.RequestBody,
}
if len(req.RequestHeaders) > 0 {
out.RequestHeaders = protoHeadersToHTTP(req.RequestHeaders)
@@ -85,27 +84,24 @@ func middlewareEventFromProto(evt *pb.MiddlewareEvent) *sdk.MiddlewareEvent {
return nil
}
out := &sdk.MiddlewareEvent{
- RequestID: evt.RequestId,
- UserID: evt.UserId,
- GroupID: evt.GroupId,
- AccountID: evt.AccountId,
- Platform: evt.Platform,
- Model: evt.Model,
- Stream: evt.Stream,
- InputTokensEst: evt.InputTokensEst,
- StatusCode: int32(evt.StatusCode),
- Duration: time.Duration(evt.DurationMs) * time.Millisecond,
- InputTokens: evt.InputTokens,
- OutputTokens: evt.OutputTokens,
- CachedInputTokens: evt.CachedInputTokens,
- FirstTokenMs: evt.FirstTokenMs,
- ErrorKind: evt.ErrorKind,
- ErrorMsg: evt.ErrorMsg,
- InputCost: evt.InputCost,
- OutputCost: evt.OutputCost,
- CachedInputCost: evt.CachedInputCost,
- Metadata: cloneStringMapMW(evt.Metadata),
- ResponseBody: evt.ResponseBody,
+ RequestID: evt.RequestId,
+ UserID: evt.UserId,
+ GroupID: evt.GroupId,
+ AccountID: evt.AccountId,
+ Platform: evt.Platform,
+ Model: evt.Model,
+ Stream: evt.Stream,
+ Estimates: usageMetricsFromProto(evt.Estimates),
+ StatusCode: int32(evt.StatusCode),
+ Duration: time.Duration(evt.DurationMs) * time.Millisecond,
+ ErrorKind: evt.ErrorKind,
+ ErrorMsg: evt.ErrorMsg,
+ Metadata: cloneStringMapMW(evt.Metadata),
+ ResponseBody: evt.ResponseBody,
+ }
+ if evt.Usage != nil {
+ u := usageFromProto(evt.Usage)
+ out.Usage = &u
}
if len(evt.ResponseHeaders) > 0 {
out.ResponseHeaders = protoHeadersToHTTP(evt.ResponseHeaders)
diff --git a/grpc/plugin_server.go b/runtimego/grpc/plugin_server.go
similarity index 91%
rename from grpc/plugin_server.go
rename to runtimego/grpc/plugin_server.go
index 66b8dbb..2659fd9 100644
--- a/grpc/plugin_server.go
+++ b/runtimego/grpc/plugin_server.go
@@ -7,15 +7,15 @@ import (
goplugin "github.com/hashicorp/go-plugin"
- sdk "github.com/DouDOU-start/airgate-sdk"
- pb "github.com/DouDOU-start/airgate-sdk/proto"
+ pb "github.com/DouDOU-start/airgate-sdk/protocol/proto"
+ sdk "github.com/DouDOU-start/airgate-sdk/sdkgo"
)
// PluginGRPCServer 将 sdk.Plugin 实现包装为 gRPC 服务端
//
// Broker 字段由 GatewayGRPCPlugin / ExtensionGRPCPlugin 在 GRPCServer 钩子里注入。
// 它代表插件进程侧的 hashicorp/go-plugin GRPCBroker,用于通过 broker.Dial() 拿到
-// Core 暴露的 HostService 反向连接。
+// Core 暴露的反向调用连接。
type PluginGRPCServer struct {
pb.UnimplementedPluginServiceServer
Impl sdk.Plugin
@@ -33,6 +33,7 @@ func (s *PluginGRPCServer) GetInfo(_ context.Context, _ *pb.Empty) (*pb.PluginIn
Type: string(info.Type),
SdkVersion: info.SDKVersion,
Dependencies: info.Dependencies,
+ Metadata: info.Metadata,
}
if len(info.ConfigSchema) > 0 {
@@ -120,9 +121,9 @@ func (s *PluginGRPCServer) Init(_ context.Context, req *pb.InitRequest) (*pb.Emp
slog.Info("plugin_init_start", sdk.LogFieldPluginID, info.ID)
pctx := &grpcPluginContext{
- config: &mapConfig{data: req.Config},
- broker: s.Broker,
- hostBrokerID: req.HostBrokerId,
+ config: &mapConfig{data: req.Config},
+ broker: s.Broker,
+ coreInvokeBrokerID: req.CoreInvokeBrokerId,
}
if err := s.Impl.Init(pctx); err != nil {
slog.Error("plugin_init_failed",
@@ -191,6 +192,15 @@ func (s *PluginGRPCServer) GetWebAssets(_ context.Context, _ *pb.Empty) (*pb.Web
return resp, nil
}
+// GetSchema 获取插件结构化能力清单。
+func (s *PluginGRPCServer) GetSchema(_ context.Context, _ *pb.Empty) (*pb.PluginSchemaResponse, error) {
+ provider, ok := s.Impl.(sdk.SchemaProvider)
+ if !ok {
+ return &pb.PluginSchemaResponse{}, nil
+ }
+ return schemaToProto(provider.Schema()), nil
+}
+
// HandleRequest 通用请求代理,插件实现 RequestHandler 接口即可处理自定义请求
func (s *PluginGRPCServer) HandleRequest(ctx context.Context, req *pb.HttpRequest) (*pb.HttpResponse, error) {
handler, ok := s.Impl.(sdk.RequestHandler)
diff --git a/runtimego/grpc/schema.go b/runtimego/grpc/schema.go
new file mode 100644
index 0000000..8a42831
--- /dev/null
+++ b/runtimego/grpc/schema.go
@@ -0,0 +1,141 @@
+package grpc
+
+import (
+ pb "github.com/DouDOU-start/airgate-sdk/protocol/proto"
+ sdk "github.com/DouDOU-start/airgate-sdk/sdkgo"
+)
+
+func payloadSchemaToProto(s sdk.PayloadSchema) *pb.PayloadSchemaProto {
+ if s.ContentType == "" && s.Schema == "" && s.Example == "" && len(s.Metadata) == 0 {
+ return nil
+ }
+ return &pb.PayloadSchemaProto{
+ ContentType: s.ContentType,
+ Schema: s.Schema,
+ Example: s.Example,
+ Metadata: s.Metadata,
+ }
+}
+
+func payloadSchemaFromProto(p *pb.PayloadSchemaProto) sdk.PayloadSchema {
+ if p == nil {
+ return sdk.PayloadSchema{}
+ }
+ return sdk.PayloadSchema{
+ ContentType: p.ContentType,
+ Schema: p.Schema,
+ Example: p.Example,
+ Metadata: p.Metadata,
+ }
+}
+
+func schemaToProto(s sdk.PluginSchema) *pb.PluginSchemaResponse {
+ out := &pb.PluginSchemaResponse{Metadata: s.Metadata}
+ if len(s.Routes) > 0 {
+ out.Routes = make([]*pb.RouteSchemaProto, 0, len(s.Routes))
+ for _, r := range s.Routes {
+ out.Routes = append(out.Routes, &pb.RouteSchemaProto{
+ Method: r.Method,
+ Path: r.Path,
+ Summary: r.Summary,
+ Request: payloadSchemaToProto(r.Request),
+ Response: payloadSchemaToProto(r.Response),
+ Metadata: r.Metadata,
+ })
+ }
+ }
+ if len(s.Tasks) > 0 {
+ out.Tasks = make([]*pb.TaskSchemaProto, 0, len(s.Tasks))
+ for _, t := range s.Tasks {
+ out.Tasks = append(out.Tasks, &pb.TaskSchemaProto{
+ Type: t.Type,
+ Summary: t.Summary,
+ Input: payloadSchemaToProto(t.Input),
+ Output: payloadSchemaToProto(t.Output),
+ Metadata: t.Metadata,
+ })
+ }
+ }
+ if len(s.Events) > 0 {
+ out.Events = make([]*pb.EventSchemaProto, 0, len(s.Events))
+ for _, e := range s.Events {
+ out.Events = append(out.Events, &pb.EventSchemaProto{
+ Type: e.Type,
+ Source: e.Source,
+ Summary: e.Summary,
+ Payload: payloadSchemaToProto(e.Payload),
+ Metadata: e.Metadata,
+ })
+ }
+ }
+ if len(s.Invokes) > 0 {
+ out.Invokes = make([]*pb.InvokeSchemaProto, 0, len(s.Invokes))
+ for _, i := range s.Invokes {
+ out.Invokes = append(out.Invokes, &pb.InvokeSchemaProto{
+ Method: i.Method,
+ Summary: i.Summary,
+ Request: payloadSchemaToProto(i.Request),
+ Response: payloadSchemaToProto(i.Response),
+ Metadata: i.Metadata,
+ })
+ }
+ }
+ return out
+}
+
+func schemaFromProto(p *pb.PluginSchemaResponse) sdk.PluginSchema {
+ if p == nil {
+ return sdk.PluginSchema{}
+ }
+ out := sdk.PluginSchema{Metadata: p.Metadata}
+ if len(p.Routes) > 0 {
+ out.Routes = make([]sdk.RouteSchema, 0, len(p.Routes))
+ for _, r := range p.Routes {
+ out.Routes = append(out.Routes, sdk.RouteSchema{
+ Method: r.Method,
+ Path: r.Path,
+ Summary: r.Summary,
+ Request: payloadSchemaFromProto(r.Request),
+ Response: payloadSchemaFromProto(r.Response),
+ Metadata: r.Metadata,
+ })
+ }
+ }
+ if len(p.Tasks) > 0 {
+ out.Tasks = make([]sdk.TaskSchema, 0, len(p.Tasks))
+ for _, t := range p.Tasks {
+ out.Tasks = append(out.Tasks, sdk.TaskSchema{
+ Type: t.Type,
+ Summary: t.Summary,
+ Input: payloadSchemaFromProto(t.Input),
+ Output: payloadSchemaFromProto(t.Output),
+ Metadata: t.Metadata,
+ })
+ }
+ }
+ if len(p.Events) > 0 {
+ out.Events = make([]sdk.EventSchema, 0, len(p.Events))
+ for _, e := range p.Events {
+ out.Events = append(out.Events, sdk.EventSchema{
+ Type: e.Type,
+ Source: e.Source,
+ Summary: e.Summary,
+ Payload: payloadSchemaFromProto(e.Payload),
+ Metadata: e.Metadata,
+ })
+ }
+ }
+ if len(p.Invokes) > 0 {
+ out.Invokes = make([]sdk.InvokeSchema, 0, len(p.Invokes))
+ for _, i := range p.Invokes {
+ out.Invokes = append(out.Invokes, sdk.InvokeSchema{
+ Method: i.Method,
+ Summary: i.Summary,
+ Request: payloadSchemaFromProto(i.Request),
+ Response: payloadSchemaFromProto(i.Response),
+ Metadata: i.Metadata,
+ })
+ }
+ }
+ return out
+}
diff --git a/grpc/ws_server.go b/runtimego/grpc/ws_server.go
similarity index 96%
rename from grpc/ws_server.go
rename to runtimego/grpc/ws_server.go
index 75917dd..c7d9670 100644
--- a/grpc/ws_server.go
+++ b/runtimego/grpc/ws_server.go
@@ -5,8 +5,8 @@ import (
"fmt"
"io"
- sdk "github.com/DouDOU-start/airgate-sdk"
- pb "github.com/DouDOU-start/airgate-sdk/proto"
+ pb "github.com/DouDOU-start/airgate-sdk/protocol/proto"
+ sdk "github.com/DouDOU-start/airgate-sdk/sdkgo"
)
// HandleWebSocket 处理核心发来的 WebSocket 双向流
diff --git a/capability.go b/sdkgo/capability.go
similarity index 57%
rename from capability.go
rename to sdkgo/capability.go
index e63c3f7..5452a12 100644
--- a/capability.go
+++ b/sdkgo/capability.go
@@ -1,61 +1,68 @@
package sdk
-import "sort"
+import (
+ "sort"
+ "strings"
+)
// Capability 类型化的能力标识符(命名规范:.)。
// 所有 capability 常量必须用此类型以便编译期捕获拼写错误。
//
-// 运行时授权由 Core 的 gRPC interceptor 强制执行;本类型是 SDK 侧的强类型入口,
-// 并不绕过 Core 的准入校验。
+// 运行时授权由 Core 强制执行;SDK 只负责声明格式和基础类型自检。
type Capability string
func (c Capability) String() string { return string(c) }
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"
+ // CapabilityHostInvoke 允许插件通过 Host.Invoke / Host.InvokeStream 调用 Core 开放的方法。
+ //
+ // Core 可以进一步要求插件声明 method 级 capability:
+ //
+ // host.invoke.
+ //
+ // 例如:
+ //
+ // host.invoke.scheduler.select_account
+ // host.invoke.tasks.update
+ CapabilityHostInvoke Capability = "host.invoke"
CapabilityMiddlewareReadBody Capability = "middleware.read_body"
)
-// capabilityAllowedTypes "插件类型 → 允许声明的 capability" 权威表。
-// Core 侧的 interceptor 也应读这里,避免双份维护。新增 capability 同步更新。
+const hostInvokeMethodCapabilityPrefix = "host.invoke."
+
+// CapabilityForHostMethod 返回某个 Core method 对应的细粒度 capability。
+func CapabilityForHostMethod(method string) Capability {
+ if method == "" {
+ return CapabilityHostInvoke
+ }
+ return Capability(hostInvokeMethodCapabilityPrefix + method)
+}
+
+// capabilityAllowedTypes 是 SDK 侧的基础类型白名单。
+//
+// Core 仍必须按方法注册表做最终授权,例如校验插件 ID、插件类型、method
+// 是否开放、请求 schema、敏感字段和幂等策略。
var capabilityAllowedTypes = map[Capability]map[PluginType]bool{
- CapabilityHostListGroups: {
- PluginTypeExtension: true,
- PluginTypeMiddleware: true,
- },
- CapabilityHostSelectAccount: {PluginTypeExtension: true},
- CapabilityHostProbeForward: {PluginTypeExtension: true},
- CapabilityHostReportAccountResult: {PluginTypeExtension: true},
- CapabilityHostForward: {PluginTypeExtension: true},
- CapabilityHostListPlatforms: {
+ CapabilityHostInvoke: {
+ PluginTypeGateway: true,
PluginTypeExtension: true,
PluginTypeMiddleware: true,
},
- CapabilityHostListModels: {
- PluginTypeExtension: true,
- PluginTypeMiddleware: true,
- },
- CapabilityHostGetUserInfo: {PluginTypeExtension: true},
- CapabilityHostAssetStorage: {PluginTypeExtension: true},
CapabilityMiddlewareReadBody: {PluginTypeMiddleware: true},
}
// IsKnownCapability 判断 capability 是否在当前 SDK 版本的已知集合内。
func IsKnownCapability(c Capability) bool {
+ if isHostInvokeMethodCapability(c) {
+ return true
+ }
_, ok := capabilityAllowedTypes[c]
return ok
}
-// KnownCapabilities 返回所有已知 capability,按字典序排序。
+// KnownCapabilities 返回 SDK 内置 capability,按字典序排序。
+// host.invoke. 属于动态 capability,不会出现在此列表中。
func KnownCapabilities() []Capability {
out := make([]Capability, 0, len(capabilityAllowedTypes))
for c := range capabilityAllowedTypes {
@@ -65,6 +72,19 @@ func KnownCapabilities() []Capability {
return out
}
+func isHostInvokeMethodCapability(c Capability) bool {
+ v := string(c)
+ return strings.HasPrefix(v, hostInvokeMethodCapabilityPrefix) && len(v) > len(hostInvokeMethodCapabilityPrefix)
+}
+
+func allowedPluginTypesForCapability(c Capability) (map[PluginType]bool, bool) {
+ if isHostInvokeMethodCapability(c) {
+ return capabilityAllowedTypes[CapabilityHostInvoke], true
+ }
+ allowedTypes, known := capabilityAllowedTypes[c]
+ return allowedTypes, known
+}
+
// CapabilityValidationReport ValidateCapabilities 的输出。
type CapabilityValidationReport struct {
// Effective 当前 plugin type 下实际生效的 capability = 声明 ∩ 类型允许,去重+排序。
@@ -80,7 +100,7 @@ func (r CapabilityValidationReport) HasIssues() bool {
return len(r.Unknown) > 0 || len(r.Denied) > 0
}
-// ValidateCapabilities 对一组声明做 self-check。授权决策仍由 Core 的 interceptor 负责,
+// ValidateCapabilities 对一组声明做 self-check。授权决策仍由 Core 的方法注册表负责,
// 这里只做"声明 vs 已知 vs 类型允许"的纸面检查。
func ValidateCapabilities(typ PluginType, declared []Capability) CapabilityValidationReport {
seen := make(map[Capability]bool, len(declared))
@@ -95,7 +115,7 @@ func ValidateCapabilities(typ PluginType, declared []Capability) CapabilityValid
}
seen[c] = true
- allowedTypes, known := capabilityAllowedTypes[c]
+ allowedTypes, known := allowedPluginTypesForCapability(c)
if !known {
unknown = append(unknown, c)
continue
diff --git a/sdkgo/capability_test.go b/sdkgo/capability_test.go
new file mode 100644
index 0000000..a35744c
--- /dev/null
+++ b/sdkgo/capability_test.go
@@ -0,0 +1,110 @@
+package sdk_test
+
+import (
+ "reflect"
+ "testing"
+
+ sdk "github.com/DouDOU-start/airgate-sdk/sdkgo"
+)
+
+func TestIsKnownCapability(t *testing.T) {
+ known := []sdk.Capability{
+ sdk.CapabilityHostInvoke,
+ sdk.CapabilityForHostMethod("scheduler.select_account"),
+ sdk.CapabilityMiddlewareReadBody,
+ }
+ for _, c := range known {
+ if !sdk.IsKnownCapability(c) {
+ t.Errorf("IsKnownCapability(%q) = false,期望 true", c)
+ }
+ }
+
+ unknown := []sdk.Capability{
+ "host.invoke.",
+ "host.invokee.tasks.update",
+ }
+ for _, c := range unknown {
+ if sdk.IsKnownCapability(c) {
+ t.Errorf("IsKnownCapability(%q) = true,期望 false", c)
+ }
+ }
+}
+
+func TestKnownCapabilitiesSortedAndComplete(t *testing.T) {
+ caps := sdk.KnownCapabilities()
+ want := []sdk.Capability{
+ sdk.CapabilityHostInvoke,
+ sdk.CapabilityMiddlewareReadBody,
+ }
+ if !reflect.DeepEqual(caps, want) {
+ t.Fatalf("KnownCapabilities() = %v,期望 %v", caps, want)
+ }
+}
+
+func TestValidateCapabilities_HostInvoke(t *testing.T) {
+ report := sdk.ValidateCapabilities(sdk.PluginTypeExtension, []sdk.Capability{
+ sdk.CapabilityHostInvoke,
+ sdk.CapabilityForHostMethod("scheduler.select_account"),
+ })
+ if report.HasIssues() {
+ t.Fatalf("HasIssues() = true,期望 false;report=%+v", report)
+ }
+ want := []sdk.Capability{
+ sdk.CapabilityHostInvoke,
+ sdk.CapabilityForHostMethod("scheduler.select_account"),
+ }
+ if !reflect.DeepEqual(report.Effective, want) {
+ t.Errorf("Effective = %v,期望 %v", report.Effective, want)
+ }
+}
+
+func TestValidateCapabilities_Unknown(t *testing.T) {
+ report := sdk.ValidateCapabilities(sdk.PluginTypeExtension, []sdk.Capability{
+ sdk.CapabilityHostInvoke,
+ "host.invoke.",
+ "host.invokee.tasks.update",
+ })
+ if !report.HasIssues() {
+ t.Fatal("HasIssues() = false,期望检测到未知 capability")
+ }
+ wantUnknown := []sdk.Capability{"host.invoke.", "host.invokee.tasks.update"}
+ if !reflect.DeepEqual(report.Unknown, wantUnknown) {
+ t.Errorf("Unknown = %v,期望 %v", report.Unknown, wantUnknown)
+ }
+ if len(report.Effective) != 1 || report.Effective[0] != sdk.CapabilityHostInvoke {
+ t.Errorf("Effective = %v,期望 [%v]", report.Effective, sdk.CapabilityHostInvoke)
+ }
+}
+
+func TestValidateCapabilities_Denied(t *testing.T) {
+ report := sdk.ValidateCapabilities(sdk.PluginTypeGateway, []sdk.Capability{
+ sdk.CapabilityMiddlewareReadBody,
+ })
+ if !report.HasIssues() {
+ t.Fatal("HasIssues() = false,期望检测到插件类型不允许的 capability")
+ }
+ if len(report.Denied) != 1 || report.Denied[0] != sdk.CapabilityMiddlewareReadBody {
+ t.Errorf("Denied = %v,期望 [%v]", report.Denied, sdk.CapabilityMiddlewareReadBody)
+ }
+ if len(report.Effective) != 0 {
+ t.Errorf("Effective = %v,期望为空", report.Effective)
+ }
+}
+
+func TestValidateCapabilities_Dedup(t *testing.T) {
+ capability := sdk.CapabilityForHostMethod("tasks.update")
+ report := sdk.ValidateCapabilities(sdk.PluginTypeExtension, []sdk.Capability{
+ capability,
+ capability,
+ capability,
+ })
+ if len(report.Effective) != 1 {
+ t.Errorf("Effective = %v,期望去重后只有 1 个", report.Effective)
+ }
+}
+
+func TestGetPluginDSN_NilCtx(t *testing.T) {
+ if got := sdk.GetPluginDSN(nil); got != "" {
+ t.Errorf("GetPluginDSN(nil) = %q,期望空字符串", got)
+ }
+}
diff --git a/sdkgo/doc.go b/sdkgo/doc.go
new file mode 100644
index 0000000..c241ddf
--- /dev/null
+++ b/sdkgo/doc.go
@@ -0,0 +1,5 @@
+// Package sdk 定义 AirGate 插件作者直接使用的 Go SDK。
+//
+// 本包只放稳定插件契约、共享类型、capability 辅助和日志辅助。
+// gRPC、go-plugin、broker、protobuf 转换等运行时细节属于 runtimego/grpc。
+package sdk
diff --git a/errors.go b/sdkgo/errors.go
similarity index 88%
rename from errors.go
rename to sdkgo/errors.go
index 20a4a3c..dece12b 100644
--- a/errors.go
+++ b/sdkgo/errors.go
@@ -2,7 +2,7 @@ package sdk
import "errors"
-// ErrNotSupported 插件不支持某项可选能力时返回(如 QueryQuota / HandleWebSocket)。
+// ErrNotSupported 插件不支持某项可选能力时返回(如 HandleWebSocket)。
var ErrNotSupported = errors.New("not supported")
// ErrInvalidCredentials ValidateAccount 判定凭证格式/语义不合法时返回。
diff --git a/errors_test.go b/sdkgo/errors_test.go
similarity index 97%
rename from errors_test.go
rename to sdkgo/errors_test.go
index 7cdc0bb..68d05ac 100644
--- a/errors_test.go
+++ b/sdkgo/errors_test.go
@@ -5,7 +5,7 @@ import (
"fmt"
"testing"
- sdk "github.com/DouDOU-start/airgate-sdk"
+ sdk "github.com/DouDOU-start/airgate-sdk/sdkgo"
)
func TestSentinelErrorsAreDistinct(t *testing.T) {
diff --git a/sdkgo/event.go b/sdkgo/event.go
new file mode 100644
index 0000000..d50028f
--- /dev/null
+++ b/sdkgo/event.go
@@ -0,0 +1,38 @@
+package sdk
+
+import (
+ "context"
+ "time"
+)
+
+// EventSubscription 声明插件希望接收的 Core 事件。
+//
+// Type 支持精确事件名,也可由 Core 约定支持通配符,例如 "account.*"。
+// Filter 是弱契约过滤条件,只用于事件分发提示;安全过滤仍由 Core 负责。
+type EventSubscription struct {
+ Type string `json:"type"`
+ Source string `json:"source,omitempty"`
+ Filter map[string]string `json:"filter,omitempty"`
+ Metadata map[string]string `json:"metadata,omitempty"`
+}
+
+// PluginEvent 是 Core 推送给插件的标准事件结构。
+//
+// Payload 是事件类型相关的 JSON 对象;稳定事件应在插件 schema 中声明 payload schema。
+type PluginEvent struct {
+ ID string `json:"id,omitempty"`
+ Type string `json:"type"`
+ Source string `json:"source,omitempty"`
+ Subject string `json:"subject,omitempty"`
+ UserID int64 `json:"user_id,omitempty"`
+ GroupID int64 `json:"group_id,omitempty"`
+ Payload map[string]interface{} `json:"payload,omitempty"`
+ Metadata map[string]string `json:"metadata,omitempty"`
+ OccurredAt time.Time `json:"occurred_at,omitempty"`
+}
+
+// EventHandler 可选接口:插件实现后即可订阅并处理 Core 推送的事件。
+type EventHandler interface {
+ EventSubscriptions() []EventSubscription
+ HandleEvent(ctx context.Context, event PluginEvent) error
+}
diff --git a/extension.go b/sdkgo/extension.go
similarity index 100%
rename from extension.go
rename to sdkgo/extension.go
diff --git a/gateway.go b/sdkgo/gateway.go
similarity index 86%
rename from gateway.go
rename to sdkgo/gateway.go
index 0acde94..3f08f19 100644
--- a/gateway.go
+++ b/sdkgo/gateway.go
@@ -31,8 +31,9 @@ type WebSocketConnectInfo struct {
// GatewayPlugin 网关插件接口。
//
-// Core 负责:账号调度、并发/RPM 限流、计费、failover。
-// 插件负责:声明模型/路由、转发请求、验证凭证、查询额度。
+// Core 负责:账号调度、并发/RPM 限流、结算记录、failover。
+// 插件负责:声明模型/路由、转发请求、验证凭证;账号管理和使用记录页面通过
+// 插件前端静态资源与插件私有 API 承接。
//
// Forward 的返回契约:
// - ForwardOutcome 表达业务判决(成功 / 客户端错 / 账号限流 / 账号死 / 上游抖动 / 流中断)
@@ -49,7 +50,6 @@ type GatewayPlugin interface {
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。
@@ -65,6 +65,6 @@ type ForwardRequest struct {
Stream bool
// Writer 流式响应写入目标。
- // TODO(PR3): 替换为 StreamSink 抽象,让 Core 能在首字节落地前决定是否 failover。
+ // Core 负责把写入内容转发给用户,并在调用结束后根据 ForwardOutcome 写记录和更新账号状态。
Writer http.ResponseWriter
}
diff --git a/sdkgo/host.go b/sdkgo/host.go
new file mode 100644
index 0000000..f17180e
--- /dev/null
+++ b/sdkgo/host.go
@@ -0,0 +1,91 @@
+package sdk
+
+import (
+ "context"
+)
+
+// Host Core 暴露给插件的反向调用接口(plugin → core)。
+//
+// 通过 hashicorp/go-plugin 的 GRPCBroker 架起子进程隧道,插件无需 admin HTTP / Bearer 鉴权。
+// SDK 只定义通用调用通道,不定义 Core 方法清单;具体 method、入参和出参由 Core 的
+// 方法注册表和插件 schema 共同约定,并由 Core 按 capability 强制授权。
+//
+// 在插件 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 Host interface {
+ // Invoke 调用 Core 开放的方法。
+ // Method 使用点分命名,例如 "scheduler.select_account"、"tasks.update"。
+ // Payload 是 JSON 对象语义,运行时会通过 protobuf bytes 传输。
+ Invoke(ctx context.Context, req HostInvokeRequest) (*HostInvokeResponse, error)
+
+ // InvokeStream 调用 Core 开放的流式方法。
+ // 首帧由 SDK 根据 req 自动发送;后续由返回的 HostStream 发送和接收。
+ InvokeStream(ctx context.Context, req HostStreamRequest) (HostStream, error)
+}
+
+// HostAware 可选接口:PluginContext 实现它就能暴露 Host。
+type HostAware interface {
+ // Host 返回 Host 客户端;可能为 nil(Core 版本不支持 / 未启用)。
+ Host() Host
+}
+
+// HostInvokeRequest 是插件调用 Core 方法的通用请求。
+type HostInvokeRequest struct {
+ Method string
+ // Payload 是方法入参。SDK 不解释字段含义,Core method 自己校验 schema。
+ Payload map[string]interface{}
+ // IdempotencyKey 用于创建任务、下单等副作用方法的幂等控制;只读方法可留空。
+ IdempotencyKey string
+ // Metadata 是调用级辅助信息,不应用于替代权限、调度或核心业务字段。
+ Metadata map[string]string
+}
+
+// HostInvokeResponse 是 Core 方法调用的通用响应。
+type HostInvokeResponse struct {
+ // Status 是方法自己的业务状态;传输错误、鉴权错误和 schema 错误应通过 error 返回。
+ Status string
+ // Payload 是方法出参。SDK 不解释字段含义。
+ Payload map[string]interface{}
+ // Metadata 是调用级辅助信息。
+ Metadata map[string]string
+}
+
+// HostStreamRequest 是插件调用 Core 流式方法的起始请求。
+type HostStreamRequest struct {
+ Method string
+ // Payload 是首帧入参。SDK 不解释字段含义,Core method 自己校验 schema。
+ Payload map[string]interface{}
+ // IdempotencyKey 用于副作用类流式方法的幂等控制;只读方法可留空。
+ IdempotencyKey string
+ // Metadata 是调用级辅助信息,不应用于替代权限、调度或核心业务字段。
+ Metadata map[string]string
+}
+
+// HostStream 是插件与 Core 之间的双向流。
+//
+// Recv 在流结束时返回 io.EOF。调用方不再发送数据时应调用 CloseSend。
+type HostStream interface {
+ Send(frame HostStreamFrame) error
+ Recv() (*HostStreamFrame, error)
+ CloseSend() error
+}
+
+// HostStreamFrame 是 InvokeStream 的单帧数据。
+type HostStreamFrame struct {
+ // Event 是 method 内部约定的帧类型,例如 "chunk"、"progress"、"error"、"result"。
+ Event string
+ // Status 是 method 自己的业务状态,通常只在最终帧使用。
+ Status string
+ // Payload 是当前帧的 JSON 对象语义数据。
+ Payload map[string]interface{}
+ // Metadata 是帧级辅助信息。
+ Metadata map[string]string
+ // Done 表示这是当前流的最终业务帧;传输层结束仍以 io.EOF 为准。
+ Done bool
+}
diff --git a/log.go b/sdkgo/log.go
similarity index 100%
rename from log.go
rename to sdkgo/log.go
diff --git a/log_pretty.go b/sdkgo/log_pretty.go
similarity index 100%
rename from log_pretty.go
rename to sdkgo/log_pretty.go
diff --git a/middleware.go b/sdkgo/middleware.go
similarity index 75%
rename from middleware.go
rename to sdkgo/middleware.go
index 909abca..370de54 100644
--- a/middleware.go
+++ b/sdkgo/middleware.go
@@ -7,7 +7,7 @@ import (
)
// DefaultMiddlewareDeadline / DefaultMiddlewareChainBudget 单 hook / 整条链的默认超时预算。
-// Core 侧按此兜底,middleware 超时不得 block 主流程,只会被跳过并 log warn。
+// Core 侧按此控制超时,middleware 超时不得 block 主流程,只会被跳过并 log warn。
const (
DefaultMiddlewareDeadline = 200 * time.Millisecond
DefaultMiddlewareChainBudget = 500 * time.Millisecond
@@ -41,14 +41,14 @@ const (
// MiddlewareRequest OnForwardBegin 的入参。
type MiddlewareRequest struct {
- RequestID string
- UserID int64
- GroupID int64
- AccountID int64
- Platform string
- Model string
- Stream bool
- InputTokensEst int64
+ RequestID string
+ UserID int64
+ GroupID int64
+ AccountID int64
+ Platform string
+ Model string
+ Stream bool
+ Estimates []UsageMetric
// Metadata 贯穿 Begin/End 的 KV bag,多个 middleware 之间共享。
Metadata map[string]string
@@ -60,27 +60,20 @@ type MiddlewareRequest struct {
// MiddlewareEvent OnForwardEnd 的入参。
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
+ RequestID string
+ UserID int64
+ GroupID int64
+ AccountID int64
+ Platform string
+ Model string
+ Stream bool
+ Estimates []UsageMetric
+
+ StatusCode int32
+ Duration time.Duration
+ Usage *Usage
+ ErrorKind string // "" / "upstream_5xx" / "timeout" / "no_account" / ...
+ ErrorMsg string
Metadata map[string]string
diff --git a/sdkgo/models.go b/sdkgo/models.go
new file mode 100644
index 0000000..f779581
--- /dev/null
+++ b/sdkgo/models.go
@@ -0,0 +1,117 @@
+package sdk
+
+import "net/http"
+
+// 标准模型能力常量。网关插件在声明 Models() 时应为每个模型填充 Capabilities 字段。
+// Playground / Core 按此分类展示和过滤。新增能力类型在此追加即可,无需改 proto。
+const (
+ ModelCapChat = "chat" // 文本对话
+ ModelCapReasoning = "reasoning" // 推理 / 思维链
+ ModelCapImageGeneration = "image_generation" // 图像生成
+ ModelCapImageEdit = "image_edit" // 图像编辑
+ ModelCapVideoGeneration = "video_generation" // 视频生成
+ ModelCapAudioGeneration = "audio_generation" // 音频/音乐生成
+ ModelCapTTS = "tts" // 文本转语音
+ ModelCapSTT = "stt" // 语音转文字
+ ModelCapCodeExecution = "code_execution" // 代码执行
+ ModelCapEmbedding = "embedding" // 向量嵌入
+)
+
+// Account 上游账户(Core 调度后传给插件的最小视图)。
+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"`
+}
+
+// ModelInfo 插件声明的模型信息。
+//
+// Core 可用这些字段做展示、基础选择和能力过滤;具体价格、倍率、套餐和用量算法
+// 由网关插件在 Forward 中计算后写入 Usage。
+type ModelInfo struct {
+ ID string `json:"id"`
+ Name string `json:"name"`
+ ContextWindow int `json:"context_window"`
+ MaxOutputTokens int `json:"max_output_tokens"`
+ Capabilities []string `json:"capabilities,omitempty"`
+
+ // Metadata 保存展示、分类、供应商标签等非核心扩展信息。
+ // Core 不应依赖这里做调度、计费或权限判断。
+ Metadata map[string]string `json:"metadata,omitempty"`
+}
+
+// HasCapability 检查模型是否具备指定能力。
+func (m *ModelInfo) HasCapability(cap string) bool {
+ for _, c := range m.Capabilities {
+ if c == cap {
+ return true
+ }
+ }
+ return false
+}
+
+// RouteDefinition 网关插件声明的 API 端点。
+type RouteDefinition struct {
+ Method string `json:"method"`
+ Path string `json:"path"`
+ Description string `json:"description"`
+ // Metadata 保存路由展示、分类、文档链接等非核心扩展信息。
+ Metadata map[string]string `json:"metadata,omitempty"`
+}
+
+// RouteRegistrar 扩展插件使用的路由注册器。
+type RouteRegistrar interface {
+ Handle(method, path string, handler http.HandlerFunc)
+ Group(prefix string) RouteRegistrar
+}
+
+// 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"`
+}
+
+// AccountType 账号类型声明。
+type AccountType struct {
+ Key string `json:"key"`
+ Label string `json:"label"`
+ Description string `json:"description"`
+ Fields []CredentialField `json:"fields"`
+}
+
+// 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"`
+}
+
+// 前端组件插槽。
+const (
+ SlotAccountIdentity = "account-identity"
+ SlotAccountCreate = "account-create"
+ SlotAccountEdit = "account-edit"
+ SlotAccountUsageWindow = "account-usage-window"
+ SlotUsageMetricDetail = "usage-metric-detail"
+ SlotUsageCostDetail = "usage-cost-detail"
+)
+
+// FrontendWidget 前端组件嵌入声明。
+type FrontendWidget struct {
+ Slot string `json:"slot"`
+ EntryFile string `json:"entry_file"`
+ Title string `json:"title"`
+}
diff --git a/models_test.go b/sdkgo/models_test.go
similarity index 58%
rename from models_test.go
rename to sdkgo/models_test.go
index 6615e02..c9f79c1 100644
--- a/models_test.go
+++ b/sdkgo/models_test.go
@@ -4,7 +4,7 @@ import (
"encoding/json"
"testing"
- sdk "github.com/DouDOU-start/airgate-sdk"
+ sdk "github.com/DouDOU-start/airgate-sdk/sdkgo"
)
func TestConfigFieldJSONRoundTrip(t *testing.T) {
@@ -35,8 +35,8 @@ func TestConfigFieldJSONRoundTrip(t *testing.T) {
func TestConfigFieldJSONTags(t *testing.T) {
cf := sdk.ConfigField{
- Key: "db_dsn",
- Label: "Database DSN",
+ Key: "plugin_dsn",
+ Label: "插件数据库 DSN",
Type: "password",
Required: true,
// Default, Description, Placeholder left empty (omitempty)
@@ -164,113 +164,3 @@ func TestCredentialFieldJSONKeys(t *testing.T) {
}
}
}
-
-func TestQuotaInfoExtraMapNil(t *testing.T) {
- qi := sdk.QuotaInfo{
- Total: 100.0,
- Used: 25.5,
- Remaining: 74.5,
- Currency: "USD",
- }
-
- if qi.Extra != nil {
- t.Errorf("Extra should be nil when not initialized, got %v", qi.Extra)
- }
-
- // JSON round-trip with nil Extra
- data, err := json.Marshal(qi)
- if err != nil {
- t.Fatalf("Marshal: %v", err)
- }
-
- var got sdk.QuotaInfo
- if err := json.Unmarshal(data, &got); err != nil {
- t.Fatalf("Unmarshal: %v", err)
- }
-
- if got.Total != qi.Total {
- t.Errorf("Total = %v, want %v", got.Total, qi.Total)
- }
- if got.Currency != qi.Currency {
- t.Errorf("Currency = %q, want %q", got.Currency, qi.Currency)
- }
-}
-
-func TestQuotaInfoExtraMapPopulated(t *testing.T) {
- qi := sdk.QuotaInfo{
- Total: 1000.0,
- Used: 200.0,
- Remaining: 800.0,
- Currency: "CNY",
- ExpiresAt: "2026-12-31T23:59:59Z",
- Extra: map[string]string{
- "plan": "enterprise",
- "region": "us-east-1",
- "tier": "premium",
- },
- }
-
- data, err := json.Marshal(qi)
- if err != nil {
- t.Fatalf("Marshal: %v", err)
- }
-
- var got sdk.QuotaInfo
- if err := json.Unmarshal(data, &got); err != nil {
- t.Fatalf("Unmarshal: %v", err)
- }
-
- if len(got.Extra) != 3 {
- t.Fatalf("Extra length = %d, want 3", len(got.Extra))
- }
-
- for k, want := range qi.Extra {
- if gotVal, ok := got.Extra[k]; !ok {
- t.Errorf("Extra missing key %q", k)
- } else if gotVal != want {
- t.Errorf("Extra[%q] = %q, want %q", k, gotVal, want)
- }
- }
-}
-
-func TestQuotaInfoExtraMapMutation(t *testing.T) {
- qi := sdk.QuotaInfo{
- Extra: make(map[string]string),
- }
-
- qi.Extra["key1"] = "val1"
- qi.Extra["key2"] = "val2"
-
- if len(qi.Extra) != 2 {
- t.Fatalf("Extra length = %d, want 2", len(qi.Extra))
- }
-
- delete(qi.Extra, "key1")
- if len(qi.Extra) != 1 {
- t.Fatalf("Extra length after delete = %d, want 1", len(qi.Extra))
- }
-
- if _, ok := qi.Extra["key1"]; ok {
- t.Error("key1 should have been deleted")
- }
- if v := qi.Extra["key2"]; v != "val2" {
- t.Errorf("Extra[key2] = %q, want %q", v, "val2")
- }
-}
-
-func TestQuotaInfoJSONWithNullExtra(t *testing.T) {
- // Simulate JSON with explicit null for extra
- raw := `{"total":50,"used":10,"remaining":40,"currency":"EUR","expires_at":"","extra":null}`
-
- var qi sdk.QuotaInfo
- if err := json.Unmarshal([]byte(raw), &qi); err != nil {
- t.Fatalf("Unmarshal: %v", err)
- }
-
- if qi.Total != 50 {
- t.Errorf("Total = %v, want 50", qi.Total)
- }
- if qi.Extra != nil {
- t.Errorf("Extra should be nil for JSON null, got %v", qi.Extra)
- }
-}
diff --git a/outcome.go b/sdkgo/outcome.go
similarity index 79%
rename from outcome.go
rename to sdkgo/outcome.go
index 8c83034..1bfd4bc 100644
--- a/outcome.go
+++ b/sdkgo/outcome.go
@@ -91,41 +91,27 @@ type UpstreamResponse struct {
Body []byte
}
-// Usage 单次调用的 token / 费用统计。
+// Usage 是插件计算后的单次调用用量与费用结果。
//
// 只有 OutcomeSuccess 下 Usage 必填;OutcomeClientError 如果上游也计费(如部分重置 context
// 后仍计 token)可填;其他 Kind 下应为 nil。
//
-// 费用字段(*Cost)由插件根据单价 × token 计算后传回,Core 不再关心模型定价。
-// 单价字段(*Price)纯粹透传存储,便于 usage_log 审计。
+// 平台价格、token 拆分、图片分档等标准计费规则全部由网关插件自己实现。
+// 插件填 AccountCost / Currency;Core 统一入库后按用户、分组、模型等倍率
+// 写入 UserCost / BillingMultiplier。
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
+ Model string
+ AccountCost float64
+ UserCost float64
+ BillingMultiplier float64
+ Currency string
+ Summary string
+ FirstTokenMs int64
+
+ Attributes []UsageAttribute
+ Metrics []UsageMetric
+ CostDetails []UsageCostDetail
+ Metadata map[string]string
}
// ForwardOutcome 是插件对一次 Forward 的完整判决结果。
diff --git a/outcome_test.go b/sdkgo/outcome_test.go
similarity index 98%
rename from outcome_test.go
rename to sdkgo/outcome_test.go
index 37abb44..3ada0ed 100644
--- a/outcome_test.go
+++ b/sdkgo/outcome_test.go
@@ -3,7 +3,7 @@ package sdk_test
import (
"testing"
- sdk "github.com/DouDOU-start/airgate-sdk"
+ sdk "github.com/DouDOU-start/airgate-sdk/sdkgo"
)
func TestOutcomeKind_String(t *testing.T) {
diff --git a/plugin.go b/sdkgo/plugin.go
similarity index 90%
rename from plugin.go
rename to sdkgo/plugin.go
index cd00452..ae5bea8 100644
--- a/plugin.go
+++ b/sdkgo/plugin.go
@@ -29,8 +29,7 @@ const (
)
// SDKVersion 当前 SDK 版本,插件编译时嵌入到 PluginInfo。
-// 0.3.0 起强制 Capability 声明:未声明 capability 的插件调用 HostService 会被拒绝。
-const SDKVersion = "0.3.0"
+const SDKVersion = "2.0.0"
// PluginInfo 插件元信息。
type PluginInfo struct {
@@ -48,6 +47,9 @@ type PluginInfo struct {
FrontendWidgets []FrontendWidget `json:"frontend_widgets"`
InstructionPresets []string `json:"instruction_presets"`
Capabilities []Capability `json:"capabilities"`
+ // Metadata 保存插件声明层面的非核心扩展信息。
+ // 只放展示、分类、市场索引等弱契约字段;需要 Core 授权或参与调度的字段必须进入显式 SDK 契约。
+ Metadata map[string]string `json:"metadata,omitempty"`
// Priority 仅对 type=middleware 生效:Begin 升序、End 降序(LIFO)。默认 100。
Priority int32 `json:"priority"`
}
@@ -64,8 +66,7 @@ type ConfigField struct {
}
// PluginContext Core 注入给插件的最小上下文:Logger + Config。
-// 其它能力(Host 反向调用、插件专属 DB DSN 等)通过可选接口(HostAware / PluginDSNAware)暴露,
-// 避免给 PluginContext 加方法造成 breaking change。
+// 其它能力(Host 反向调用、插件专属 DB DSN 等)通过可选接口暴露。
type PluginContext interface {
Logger() *slog.Logger
Config() PluginConfig
@@ -82,7 +83,7 @@ type PluginDSNAware interface {
PluginDSN() string
}
-// GetPluginDSN PluginDSNAware 的便利访问器:ctx 实现了接口就返回 DSN,否则回退读 Config。
+// GetPluginDSN PluginDSNAware 的便利访问器。
func GetPluginDSN(ctx PluginContext) string {
if ctx == nil {
return ""
@@ -90,9 +91,6 @@ func GetPluginDSN(ctx PluginContext) string {
if d, ok := ctx.(PluginDSNAware); ok {
return d.PluginDSN()
}
- if cfg := ctx.Config(); cfg != nil {
- return cfg.GetString(PluginDSNConfigKey)
- }
return ""
}
diff --git a/plugin_test.go b/sdkgo/plugin_test.go
similarity index 90%
rename from plugin_test.go
rename to sdkgo/plugin_test.go
index 91a48e5..4481cd7 100644
--- a/plugin_test.go
+++ b/sdkgo/plugin_test.go
@@ -5,7 +5,7 @@ import (
"strings"
"testing"
- sdk "github.com/DouDOU-start/airgate-sdk"
+ sdk "github.com/DouDOU-start/airgate-sdk/sdkgo"
)
func TestSDKVersion(t *testing.T) {
@@ -59,11 +59,14 @@ func TestPluginInfoJSON(t *testing.T) {
},
FrontendWidgets: []sdk.FrontendWidget{
{
- Slot: "account-form",
+ Slot: sdk.SlotAccountCreate,
EntryFile: "form.js",
Title: "Account Form",
},
},
+ Metadata: map[string]string{
+ "category": "gateway",
+ },
}
data, err := json.Marshal(info)
@@ -92,6 +95,9 @@ func TestPluginInfoJSON(t *testing.T) {
if decoded.Type != info.Type {
t.Errorf("Type = %q, want %q", decoded.Type, info.Type)
}
+ if decoded.Metadata["category"] != "gateway" {
+ t.Errorf("Metadata[category] = %q, want gateway", decoded.Metadata["category"])
+ }
// Verify Dependencies
if len(decoded.Dependencies) != 2 {
@@ -118,7 +124,7 @@ func TestPluginInfoJSON(t *testing.T) {
// Verify JSON keys exist in raw output
raw := string(data)
- for _, key := range []string{`"sdk_version"`, `"dependencies"`, `"config_schema"`} {
+ for _, key := range []string{`"sdk_version"`, `"dependencies"`, `"config_schema"`, `"metadata"`} {
if !strings.Contains(raw, key) {
t.Errorf("JSON output missing key %s", key)
}
diff --git a/sdkgo/schema.go b/sdkgo/schema.go
new file mode 100644
index 0000000..db2ca34
--- /dev/null
+++ b/sdkgo/schema.go
@@ -0,0 +1,62 @@
+package sdk
+
+// PayloadSchema 描述一个 JSON payload 的结构。
+//
+// Schema 通常是 JSON Schema 字符串;Example 是 JSON 示例字符串。
+type PayloadSchema struct {
+ ContentType string `json:"content_type,omitempty"`
+ Schema string `json:"schema,omitempty"`
+ Example string `json:"example,omitempty"`
+ Metadata map[string]string `json:"metadata,omitempty"`
+}
+
+// RouteSchema 描述插件公开的自定义 HTTP API。
+type RouteSchema struct {
+ Method string `json:"method"`
+ Path string `json:"path"`
+ Summary string `json:"summary,omitempty"`
+ Request PayloadSchema `json:"request,omitempty"`
+ Response PayloadSchema `json:"response,omitempty"`
+ Metadata map[string]string `json:"metadata,omitempty"`
+}
+
+// TaskSchema 描述插件支持的异步任务类型。
+type TaskSchema struct {
+ Type string `json:"type"`
+ Summary string `json:"summary,omitempty"`
+ Input PayloadSchema `json:"input,omitempty"`
+ Output PayloadSchema `json:"output,omitempty"`
+ Metadata map[string]string `json:"metadata,omitempty"`
+}
+
+// EventSchema 描述插件订阅或发布的事件类型。
+type EventSchema struct {
+ Type string `json:"type"`
+ Source string `json:"source,omitempty"`
+ Summary string `json:"summary,omitempty"`
+ Payload PayloadSchema `json:"payload,omitempty"`
+ Metadata map[string]string `json:"metadata,omitempty"`
+}
+
+// InvokeSchema 描述通过 Host.Invoke 或 Host.InvokeStream 调用的扩展动作。
+type InvokeSchema struct {
+ Method string `json:"method"`
+ Summary string `json:"summary,omitempty"`
+ Request PayloadSchema `json:"request,omitempty"`
+ Response PayloadSchema `json:"response,omitempty"`
+ Metadata map[string]string `json:"metadata,omitempty"`
+}
+
+// PluginSchema 是插件的能力发现清单,用于 Core、管理后台和开发工具了解插件可用能力。
+type PluginSchema struct {
+ Routes []RouteSchema `json:"routes,omitempty"`
+ Tasks []TaskSchema `json:"tasks,omitempty"`
+ Events []EventSchema `json:"events,omitempty"`
+ Invokes []InvokeSchema `json:"invokes,omitempty"`
+ Metadata map[string]string `json:"metadata,omitempty"`
+}
+
+// SchemaProvider 可选接口:插件实现后可向 Core 暴露结构化能力清单。
+type SchemaProvider interface {
+ Schema() PluginSchema
+}
diff --git a/sdkgo/task.go b/sdkgo/task.go
new file mode 100644
index 0000000..d44d5db
--- /dev/null
+++ b/sdkgo/task.go
@@ -0,0 +1,74 @@
+package sdk
+
+import (
+ "context"
+ "time"
+)
+
+type TaskStatus string
+
+const (
+ TaskStatusPending TaskStatus = "pending"
+ TaskStatusProcessing TaskStatus = "processing"
+ TaskStatusCompleted TaskStatus = "completed"
+ TaskStatusFailed TaskStatus = "failed"
+ TaskStatusCancelled TaskStatus = "cancelled"
+)
+
+func (s TaskStatus) String() string { return string(s) }
+
+// TaskProcessor 可选接口:扩展插件实现它来处理 Core 分发的异步任务。
+//
+// Core 在后台轮询 pending 任务,按 plugin_id 找到对应插件调用 ProcessTask。
+// 插件内部如需回写进度,应通过 Host.Invoke 调用 Core 开放的任务方法。
+//
+// 使用方式:
+//
+// func (p *MyPlugin) ProcessTask(ctx context.Context, task sdk.HostTask) error {
+// _, _ = p.host.Invoke(ctx, sdk.HostInvokeRequest{
+// Method: "tasks.update",
+// Payload: map[string]interface{}{
+// "task_id": task.ID,
+// "status": sdk.TaskStatusProcessing.String(),
+// "progress": 10,
+// },
+// })
+// // ... 执行任务逻辑 ...
+// _, _ = p.host.Invoke(ctx, sdk.HostInvokeRequest{
+// Method: "tasks.update",
+// Payload: map[string]interface{}{
+// "task_id": task.ID,
+// "status": sdk.TaskStatusCompleted.String(),
+// "output": result,
+// },
+// })
+// return nil
+// }
+//
+// func (p *MyPlugin) TaskTypes() []string { return []string{"image_generation"} }
+type TaskProcessor interface {
+ // ProcessTask 处理一个异步任务。Context 带有超时。
+ ProcessTask(ctx context.Context, task HostTask) error
+
+ // TaskTypes 返回此插件能处理的任务类型列表。
+ TaskTypes() []string
+}
+
+// HostTask 任务完整信息。
+type HostTask struct {
+ ID int64
+ PluginID string
+ TaskType string
+ Status TaskStatus // pending, processing, completed, failed, cancelled
+ UserID int64
+ Input map[string]interface{}
+ Output map[string]interface{}
+ ErrorMessage string
+ Progress int // 0-100
+ Attempts int
+ MaxAttempts int
+ CreatedAt time.Time
+ UpdatedAt time.Time
+ StartedAt *time.Time
+ CompletedAt *time.Time
+}
diff --git a/sdkgo/usage.go b/sdkgo/usage.go
new file mode 100644
index 0000000..5c176c3
--- /dev/null
+++ b/sdkgo/usage.go
@@ -0,0 +1,42 @@
+package sdk
+
+// UsageAttribute 是一次调用的通用审计维度。
+//
+// 适合存储模型、思考层级、分辨率、质量档、服务档位等非数值或枚举型信息。
+// Core 可统一入库和检索,但不根据这些字段推导平台计费规则。
+type UsageAttribute struct {
+ Key string `json:"key,omitempty"`
+ Label string `json:"label"`
+ Kind string `json:"kind,omitempty"` // model / reasoning / resolution / tier / quality / custom
+ Value string `json:"value"`
+ Metadata map[string]string `json:"metadata,omitempty"`
+}
+
+// UsageMetric 是一次调用的通用计量结果。
+//
+// 插件负责根据平台标准计费规则计算 Value 和 AccountCost。SDK 不提供价格字段、倍率规则或 token
+// 公式,Core 不应基于 Key 推导平台计费语义。
+type UsageMetric struct {
+ Key string `json:"key,omitempty"`
+ Label string `json:"label"`
+ Kind string `json:"kind,omitempty"` // token / request / image / audio / video / custom
+ Unit string `json:"unit,omitempty"`
+ Value float64 `json:"value"`
+ AccountCost float64 `json:"account_cost,omitempty"`
+ Currency string `json:"currency,omitempty"`
+ Metadata map[string]string `json:"metadata,omitempty"`
+}
+
+// UsageCostDetail 是一次调用的通用费用明细。
+//
+// 插件负责把平台价格、套餐规则和折扣计算成 AccountCost。Core 可统一入库,
+// 再按用户、分组、模型等倍率写入 UserCost / BillingMultiplier。
+type UsageCostDetail struct {
+ Key string `json:"key,omitempty"`
+ Label string `json:"label"`
+ AccountCost float64 `json:"account_cost"`
+ UserCost float64 `json:"user_cost,omitempty"`
+ BillingMultiplier float64 `json:"billing_multiplier,omitempty"`
+ Currency string `json:"currency,omitempty"`
+ Metadata map[string]string `json:"metadata,omitempty"`
+}
diff --git a/sdkgo/usage_test.go b/sdkgo/usage_test.go
new file mode 100644
index 0000000..d10fbaa
--- /dev/null
+++ b/sdkgo/usage_test.go
@@ -0,0 +1,61 @@
+package sdk_test
+
+import (
+ "encoding/json"
+ "testing"
+
+ sdk "github.com/DouDOU-start/airgate-sdk/sdkgo"
+)
+
+func TestUsageJSONRoundTrip(t *testing.T) {
+ usage := sdk.Usage{
+ Model: "demo-model",
+ AccountCost: 0.042,
+ UserCost: 0.084,
+ BillingMultiplier: 2,
+ Currency: "USD",
+ Summary: "输入 10 token,输出 5 token",
+ FirstTokenMs: 120,
+ 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,
+ AccountCost: 0.01,
+ Currency: "USD",
+ },
+ },
+ CostDetails: []sdk.UsageCostDetail{
+ {Key: "input", Label: "输入费用", AccountCost: 0.01, UserCost: 0.02, BillingMultiplier: 2, Currency: "USD"},
+ },
+ Metadata: map[string]string{"provider": "demo"},
+ }
+
+ data, err := json.Marshal(usage)
+ if err != nil {
+ t.Fatalf("Marshal: %v", err)
+ }
+
+ var got sdk.Usage
+ if err := json.Unmarshal(data, &got); err != nil {
+ t.Fatalf("Unmarshal: %v", err)
+ }
+ if got.Model != usage.Model || got.AccountCost != usage.AccountCost || got.UserCost != usage.UserCost || got.BillingMultiplier != usage.BillingMultiplier || got.Currency != usage.Currency {
+ t.Fatalf("Usage round-trip mismatch: got %+v, want %+v", got, usage)
+ }
+ if len(got.Metrics) != 1 || got.Metrics[0].Key != "input_tokens" {
+ t.Fatalf("Metrics round-trip mismatch: %+v", got.Metrics)
+ }
+ if len(got.Attributes) != 2 || got.Attributes[0].Key != "reasoning_effort" {
+ t.Fatalf("Attributes round-trip mismatch: %+v", got.Attributes)
+ }
+ if len(got.CostDetails) != 1 || got.CostDetails[0].Key != "input" {
+ t.Fatalf("CostDetails round-trip mismatch: %+v", got.CostDetails)
+ }
+}
From 701f1cc65a05ce54598daa2dcccb966d008cca73 Mon Sep 17 00:00:00 2001
From: huangenjun <1021217094@qq.com>
Date: Tue, 12 May 2026 12:22:03 +0800
Subject: [PATCH 02/11] fix sdk contract drift
---
.github/workflows/ci.yml | 10 ++
Makefile | 18 ++-
README.md | 4 +-
devkit/devserver/static/theme.css | 189 +++++++++++++----------
docs/plugin-style-guide.md | 63 ++++----
docs/sdk-package-boundaries.md | 2 +-
frontend/package.json | 1 +
frontend/pnpm-lock.yaml | 48 ------
protocol/proto/plugin.pb.go | 223 ++++++++++++++++------------
protocol/proto/plugin.proto | 11 +-
runtimego/grpc/event_schema_test.go | 11 +-
runtimego/grpc/schema.go | 26 ++--
sdkgo/outcome.go | 23 ++-
sdkgo/plugin.go | 2 +-
sdkgo/schema.go | 25 +++-
sdkgo/usage_test.go | 12 ++
16 files changed, 371 insertions(+), 297 deletions(-)
delete mode 100644 frontend/pnpm-lock.yaml
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index ad7e04f..5dc8ce7 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -17,6 +17,16 @@ jobs:
with:
go-version-file: go.mod
+ - uses: actions/setup-node@v6
+ with:
+ node-version: 22
+ cache: npm
+ cache-dependency-path: frontend/package-lock.json
+
+ - name: Install frontend dependencies
+ working-directory: frontend
+ run: npm ci
+
- name: Install golangci-lint
run: go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest
diff --git a/Makefile b/Makefile
index c387f04..3bd10c3 100644
--- a/Makefile
+++ b/Makefile
@@ -11,14 +11,14 @@ PROTOC_GEN_GRPC_VER := v1.6.0
TOOLS_DIR := $(CURDIR)/.tools
PROTOC_BIN := $(TOOLS_DIR)/bin/protoc
-.PHONY: help ci pre-commit lint fmt test vet build proto proto-tools clean setup-hooks
+.PHONY: help ci pre-commit lint fmt test vet build frontend-build theme theme-check proto proto-check proto-tools clean setup-hooks
help: ## 显示帮助信息
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-18s\033[0m %s\n", $$1, $$2}'
# ===================== 质量检查 =====================
-ci: lint test vet build ## 本地运行与 CI 完全一致的检查
+ci: lint test vet build proto-check frontend-build theme-check ## 本地运行与 CI 完全一致的检查
pre-commit: lint vet build ## pre-commit hook 调用(跳过耗时的 race 测试)
@@ -51,9 +51,11 @@ build: ## 编译检查
# ===================== 前端主题 =====================
-theme: ## 构建前端主题包并生成 DevServer 用 theme.css
+frontend-build: ## 构建前端主题包
cd frontend && npm run build
- node --input-type=module -e "import{generateThemeCSS}from'./frontend/dist/css.js';process.stdout.write(generateThemeCSS())" > devkit/devserver/static/theme.css
+
+theme: frontend-build ## 构建前端主题包并生成 DevServer 用 theme.css
+ node --input-type=module -e "import{generateThemeCSS}from'./frontend/dist/css.js';process.stdout.write(generateThemeCSS() + '\n')" > devkit/devserver/static/theme.css
@echo "theme.css 已生成"
# ===================== 代码生成 =====================
@@ -85,6 +87,14 @@ proto: proto-tools ## 重新生成 protobuf 代码
plugin.proto
@echo "Proto 代码生成完成"
+proto-check: proto ## 校验 protobuf 生成代码无漂移
+ @git diff --exit-code -- protocol/proto/plugin.pb.go protocol/proto/plugin_grpc.pb.go protocol/proto/plugin.proto
+ @echo "Proto 代码无漂移"
+
+theme-check: theme ## 校验 DevServer 主题 CSS 无漂移
+ @git diff --exit-code -- devkit/devserver/static/theme.css
+ @echo "theme.css 无漂移"
+
# ===================== Git Hooks =====================
setup-hooks: ## 安装 Git pre-commit hook
diff --git a/README.md b/README.md
index 4047d14..0b36b4e 100644
--- a/README.md
+++ b/README.md
@@ -340,9 +340,9 @@ import {
## 开发命令
```bash
+make ci # 运行 Go、proto、前端和主题漂移检查
make proto # 重新生成 protocol/proto
-GOTOOLCHAIN=local go test ./... # 运行 Go 测试
-GOTOOLCHAIN=local go build ./... # 验证 Go 包可构建
+make theme # 重新生成 DevServer 主题 CSS
cd frontend && npm run build # 构建 @airgate/theme
```
diff --git a/devkit/devserver/static/theme.css b/devkit/devserver/static/theme.css
index d66ac5d..2e67685 100644
--- a/devkit/devserver/static/theme.css
+++ b/devkit/devserver/static/theme.css
@@ -1,10 +1,11 @@
:root {
- --ag-radius-sm: 12px;
- --ag-radius-md: 18px;
- --ag-radius-lg: 22px;
- --ag-radius-xl: 28px;
- --ag-font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
- --ag-font-mono: 'JetBrains Mono', 'SF Mono', 'Cascadia Code', monospace;
+ --ag-radius-sm: 0.25rem;
+ --ag-radius-md: 0.25rem;
+ --ag-radius-lg: 0.25rem;
+ --ag-radius-xl: 0.25rem;
+ --ag-field-radius: 0.5rem;
+ --ag-font-sans: 'Geist Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
+ --ag-font-mono: 'Geist Mono', 'SF Mono', 'Cascadia Code', monospace;
--ag-transition: 200ms cubic-bezier(0.4, 0, 0.2, 1);
--ag-transition-slow: 400ms cubic-bezier(0.4, 0, 0.2, 1);
--ag-sidebar-width: 260px;
@@ -13,79 +14,111 @@
}
:root[data-theme="dark"] {
- --ag-primary: #3ecfb4;
- --ag-primary-hover: #62dcc4;
- --ag-primary-subtle: rgba(62, 207, 180, 0.08);
- --ag-primary-glow: rgba(62, 207, 180, 0.14);
- --ag-success: #34d399;
- --ag-success-subtle: rgba(52, 211, 153, 0.12);
- --ag-warning: #fbbf24;
- --ag-warning-subtle: rgba(251, 191, 36, 0.12);
- --ag-danger: #fb7185;
- --ag-danger-subtle: rgba(251, 113, 133, 0.12);
- --ag-info: #7dd3fc;
- --ag-info-subtle: rgba(125, 211, 252, 0.12);
- --ag-bg-deep: #06080e;
- --ag-bg: #0c0f17;
- --ag-bg-elevated: #131722;
- --ag-bg-surface: #1a1e2a;
- --ag-bg-hover: #232836;
- --ag-bg-active: #2c3240;
- --ag-border: rgba(148, 175, 225, 0.08);
- --ag-border-subtle: rgba(148, 175, 225, 0.05);
- --ag-border-focus: rgba(62, 207, 180, 0.40);
- --ag-text: #e2e6f0;
- --ag-text-secondary: #8d93a8;
- --ag-text-tertiary: #565d73;
- --ag-text-inverse: #06080e;
- --ag-glass: rgba(148, 175, 225, 0.03);
- --ag-glass-border: rgba(148, 175, 225, 0.06);
- --ag-shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.36);
- --ag-shadow-md: 0 8px 24px rgba(0, 0, 0, 0.48);
- --ag-shadow-lg: 0 20px 48px rgba(0, 0, 0, 0.60);
- --ag-shadow-glow: 0 0 0 1px rgba(62, 207, 180, 0.08), 0 8px 32px rgba(62, 207, 180, 0.10);
+ --ag-primary: oklch(0.9848 0 0);
+ --ag-primary-foreground: oklch(15% 0.0000 0.00);
+ --ag-primary-hover: color-mix(in oklab, oklch(0.9848 0 0) 88%, oklch(15% 0.0000 0.00) 12%);
+ --ag-primary-subtle: color-mix(in oklab, oklch(0.9848 0 0) 14%, transparent);
+ --ag-primary-glow: color-mix(in oklab, oklch(0.9848 0 0) 22%, transparent);
+ --ag-success: oklch(73.29% 0.1935 120.35);
+ --ag-success-foreground: oklch(21.03% 0.0059 120.35);
+ --ag-success-subtle: color-mix(in oklab, oklch(73.29% 0.1935 120.35) 15%, transparent);
+ --ag-warning: oklch(0.8803 0.1348 86.06);
+ --ag-warning-foreground: oklch(15% 0.0404 86.06);
+ --ag-warning-subtle: color-mix(in oklab, oklch(0.8803 0.1348 86.06) 15%, transparent);
+ --ag-danger: oklch(0.7044 0.1872 23.19);
+ --ag-danger-foreground: oklch(15% 0.0500 23.19);
+ --ag-danger-subtle: color-mix(in oklab, oklch(0.7044 0.1872 23.19) 15%, transparent);
+ --ag-info: oklch(0.9848 0 0);
+ --ag-info-subtle: color-mix(in oklab, oklch(0.9848 0 0) 14%, transparent);
+ --ag-default-bg: oklch(27.40% 0.0000 0.00);
+ --ag-default-foreground: oklch(99.11% 0 0);
+ --ag-field-background: oklch(21.03% 0.0000 0.00);
+ --ag-field-foreground: oklch(99.11% 0.0000 0.00);
+ --ag-field-placeholder: oklch(70.50% 0.0000 0.00);
+ --ag-muted: oklch(70.50% 0.0000 0.00);
+ --ag-overlay: oklch(21.03% 0.0000 0.00);
+ --ag-overlay-foreground: oklch(99.11% 0.0000 0.00);
+ --ag-scrollbar: oklch(70.50% 0.0000 0.00);
+ --ag-segment: oklch(39.64% 0.0000 0.00);
+ --ag-segment-foreground: oklch(99.11% 0.0000 0.00);
+ --ag-surface: oklch(21.03% 0.0000 0.00);
+ --ag-surface-foreground: oklch(99.11% 0.0000 0.00);
+ --ag-surface-secondary: oklch(25.70% 0.0000 0.00);
+ --ag-surface-secondary-foreground: oklch(99.11% 0.0000 0.00);
+ --ag-surface-tertiary: oklch(27.21% 0.0000 0.00);
+ --ag-surface-tertiary-foreground: oklch(99.11% 0.0000 0.00);
+ --ag-bg-deep: oklch(12.00% 0.0000 0.00);
+ --ag-bg: oklch(12.00% 0.0000 0.00);
+ --ag-bg-elevated: oklch(21.03% 0.0000 0.00);
+ --ag-bg-surface: oklch(21.03% 0.0000 0.00);
+ --ag-bg-hover: oklch(25.70% 0.0000 0.00);
+ --ag-bg-active: oklch(27.21% 0.0000 0.00);
+ --ag-border: oklch(28.00% 0.0000 0.00);
+ --ag-border-subtle: oklch(25.00% 0.0000 0.00);
+ --ag-border-focus: oklch(0.9848 0 0);
+ --ag-text: oklch(99.11% 0.0000 0.00);
+ --ag-text-secondary: oklch(70.50% 0.0000 0.00);
+ --ag-text-tertiary: oklch(70.50% 0.0000 0.00);
+ --ag-text-inverse: oklch(15% 0.0000 0.00);
+ --ag-glass: color-mix(in oklab, oklch(21.03% 0.0000 0.00) 92%, transparent);
+ --ag-glass-border: oklch(28.00% 0.0000 0.00);
+ --ag-shadow-sm: 0 0 0 0 transparent inset;
+ --ag-shadow-md: 0 0 0 0 transparent inset;
+ --ag-shadow-lg: 0 0 1px 0 #ffffff4d inset;
+ --ag-shadow-glow: 0 0 0 1px color-mix(in oklab, oklch(0.9848 0 0) 18%, transparent);
}
:root[data-theme="light"] {
- --ag-primary: #0d9488;
- --ag-primary-hover: #0b7e74;
- --ag-primary-subtle: rgba(13, 148, 136, 0.05);
- --ag-primary-glow: rgba(13, 148, 136, 0.10);
- --ag-success: #16a34a;
- --ag-success-subtle: rgba(22, 163, 74, 0.06);
- --ag-warning: #d97706;
- --ag-warning-subtle: rgba(217, 119, 6, 0.06);
- --ag-danger: #e11d48;
- --ag-danger-subtle: rgba(225, 29, 72, 0.06);
- --ag-info: #2563eb;
- --ag-info-subtle: rgba(37, 99, 235, 0.06);
- --ag-bg-deep: #f1f3f8;
- --ag-bg: #f6f7fb;
- --ag-bg-elevated: #ffffff;
- --ag-bg-surface: #ffffff;
- --ag-bg-hover: #eaedf5;
- --ag-bg-active: #e0e4ee;
- --ag-border: #d6dae6;
- --ag-border-subtle: #e6e9f2;
- --ag-border-focus: rgba(13, 148, 136, 0.45);
- --ag-text: #131830;
- --ag-text-secondary: #424866;
- --ag-text-tertiary: #6e7490;
- --ag-text-inverse: #ffffff;
- --ag-glass: rgba(255, 255, 255, 0.92);
- --ag-glass-border: rgba(10, 20, 60, 0.06);
- --ag-shadow-sm: 0 1px 3px rgba(10, 20, 60, 0.06), 0 1px 2px rgba(10, 20, 60, 0.04);
- --ag-shadow-md: 0 4px 12px rgba(10, 20, 60, 0.07), 0 2px 4px rgba(10, 20, 60, 0.04);
- --ag-shadow-lg: 0 16px 40px rgba(10, 20, 60, 0.09), 0 4px 8px rgba(10, 20, 60, 0.04);
- --ag-shadow-glow: 0 0 0 1px rgba(13, 148, 136, 0.10), 0 8px 24px rgba(13, 148, 136, 0.07);
-}
-
-:root[data-theme="light"] .ag-elevation-modal {
- --ag-bg-elevated: #eef0f7;
- --ag-bg-surface: #e6e9f2;
- --ag-bg-hover: #e0e4ee;
- --ag-glass-border: #d2d6e3;
- --ag-border: #c8cdd9;
- --ag-shadow-sm: none;
- --ag-shadow-md: none;
+ --ag-primary: oklch(0 0 0);
+ --ag-primary-foreground: oklch(99.11% 0 0);
+ --ag-primary-hover: color-mix(in oklab, oklch(0 0 0) 88%, oklch(99.11% 0 0) 12%);
+ --ag-primary-subtle: color-mix(in oklab, oklch(0 0 0) 10%, transparent);
+ --ag-primary-glow: color-mix(in oklab, oklch(0 0 0) 16%, transparent);
+ --ag-success: oklch(73.29% 0.1935 120.35);
+ --ag-success-foreground: oklch(21.03% 0.0059 120.35);
+ --ag-success-subtle: color-mix(in oklab, oklch(73.29% 0.1935 120.35) 15%, transparent);
+ --ag-warning: oklch(0.8446 0.1525 80.6);
+ --ag-warning-foreground: oklch(15% 0.0457 80.60);
+ --ag-warning-subtle: color-mix(in oklab, oklch(0.8446 0.1525 80.6) 15%, transparent);
+ --ag-danger: oklch(0.573 0.2249 21.97);
+ --ag-danger-foreground: oklch(98% 0.0200 21.97);
+ --ag-danger-subtle: color-mix(in oklab, oklch(0.573 0.2249 21.97) 15%, transparent);
+ --ag-info: oklch(0 0 0);
+ --ag-info-subtle: color-mix(in oklab, oklch(0 0 0) 10%, transparent);
+ --ag-default-bg: oklch(94.00% 0.0000 0.00);
+ --ag-default-foreground: oklch(21.03% 0.0059 0.00);
+ --ag-field-background: oklch(100.00% 0.0000 0.00);
+ --ag-field-foreground: oklch(21.03% 0.0000 0.00);
+ --ag-field-placeholder: oklch(55.17% 0.0000 0.00);
+ --ag-muted: oklch(55.17% 0.0000 0.00);
+ --ag-overlay: oklch(100.00% 0.0000 0.00);
+ --ag-overlay-foreground: oklch(21.03% 0.0000 0.00);
+ --ag-scrollbar: oklch(87.10% 0.0000 0.00);
+ --ag-segment: oklch(100.00% 0.0000 0.00);
+ --ag-segment-foreground: oklch(21.03% 0.0000 0.00);
+ --ag-surface: oklch(100.00% 0.0000 0.00);
+ --ag-surface-foreground: oklch(21.03% 0.0000 0.00);
+ --ag-surface-secondary: oklch(95.24% 0.0000 0.00);
+ --ag-surface-secondary-foreground: oklch(21.03% 0.0000 0.00);
+ --ag-surface-tertiary: oklch(93.73% 0.0000 0.00);
+ --ag-surface-tertiary-foreground: oklch(21.03% 0.0000 0.00);
+ --ag-bg-deep: oklch(97.02% 0.0000 0.00);
+ --ag-bg: oklch(97.02% 0.0000 0.00);
+ --ag-bg-elevated: oklch(100.00% 0.0000 0.00);
+ --ag-bg-surface: oklch(100.00% 0.0000 0.00);
+ --ag-bg-hover: oklch(95.24% 0.0000 0.00);
+ --ag-bg-active: oklch(93.73% 0.0000 0.00);
+ --ag-border: oklch(90.00% 0.0000 0.00);
+ --ag-border-subtle: oklch(92.00% 0.0000 0.00);
+ --ag-border-focus: oklch(0 0 0);
+ --ag-text: oklch(21.03% 0.0000 0.00);
+ --ag-text-secondary: oklch(55.17% 0.0000 0.00);
+ --ag-text-tertiary: oklch(55.17% 0.0000 0.00);
+ --ag-text-inverse: oklch(99.11% 0 0);
+ --ag-glass: color-mix(in oklab, oklch(100.00% 0.0000 0.00) 92%, transparent);
+ --ag-glass-border: oklch(90.00% 0.0000 0.00);
+ --ag-shadow-sm: 0 2px 4px 0 #0000000a, 0 1px 2px 0 #0000000f, 0 0 1px 0 #0000000f;
+ --ag-shadow-md: 0 2px 4px 0 #0000000a, 0 1px 2px 0 #0000000f, 0 0 1px 0 #0000000f;
+ --ag-shadow-lg: 0 2px 8px 0 #0000000f, 0 -6px 12px 0 #00000008, 0 14px 28px 0 #00000014;
+ --ag-shadow-glow: 0 0 0 1px color-mix(in oklab, oklch(0 0 0) 12%, transparent);
}
diff --git a/docs/plugin-style-guide.md b/docs/plugin-style-guide.md
index b935af5..d05945d 100644
--- a/docs/plugin-style-guide.md
+++ b/docs/plugin-style-guide.md
@@ -189,42 +189,42 @@ export function YourComponent(props) {
## 5. 设计 Token 参考
-所有插件样式通过 CSS 变量 `--ag-*` 引用,Tailwind 工具类已映射好(带 `agw-` 前缀)。
+所有插件样式通过 CSS 变量 `--ag-*` 引用,Tailwind 工具类已映射好(带 `agw-` 前缀)。具体 token 值由 `frontend/src/tokens.ts` 生成,文档只说明语义,避免样式值漂移。
### 颜色
-| Token | Tailwind 类 | 暗色值 | 用途 |
-|---|---|---|---|
-| `--ag-primary` | `agw-text-primary` / `agw-bg-primary` | `#2dd4a8` | 主操作、链接、选中态 |
-| `--ag-primary-hover` | `agw-bg-primary-hover` | `#5de8c2` | 主色悬停 |
-| `--ag-primary-subtle` | `agw-bg-primary-subtle` | `rgba(45,212,168,0.10)` | 主色背景/高亮 |
-| `--ag-primary-glow` | — | `rgba(45,212,168,0.18)` | 发光阴影 |
-| `--ag-success` | `agw-text-success` | `#22c55e` | 成功状态 |
-| `--ag-warning` | `agw-text-warning` | `#f59e0b` | 警告状态 |
-| `--ag-danger` | `agw-text-danger` | `#ef4444` | 错误/删除 |
-| `--ag-info` | `agw-text-info` | `#60a5fa` | 信息/辅助色 |
+| Token | Tailwind 类 | 用途 |
+|---|---|---|
+| `--ag-primary` | `agw-text-primary` / `agw-bg-primary` | 主操作、链接、选中态 |
+| `--ag-primary-hover` | `agw-bg-primary-hover` | 主色悬停 |
+| `--ag-primary-subtle` | `agw-bg-primary-subtle` | 主色背景/高亮 |
+| `--ag-primary-glow` | — | 发光阴影 |
+| `--ag-success` | `agw-text-success` | 成功状态 |
+| `--ag-warning` | `agw-text-warning` | 警告状态 |
+| `--ag-danger` | `agw-text-danger` | 错误/删除 |
+| `--ag-info` | `agw-text-info` | 信息/辅助色 |
### 背景层级
从深到浅,形成空间层次:
-| Token | Tailwind 类 | 暗色值 | 用途 |
-|---|---|---|---|
-| `--ag-bg-deep` | `agw-bg-bg-deep` | `#0a0a0c` | 页面最底层 |
-| `--ag-bg` | `agw-bg-bg` | `#111113` | 侧边栏/主面板 |
-| `--ag-bg-elevated` | `agw-bg-bg-elevated` | `#18181b` | 卡片/弹窗/下拉 |
-| `--ag-bg-surface` | `agw-bg-surface` | `#1e1e21` | 输入框/表单区域 |
-| `--ag-bg-hover` | `agw-bg-bg-hover` | `#27272a` | 悬停态背景 |
-| `--ag-bg-active` | `agw-bg-bg-active` | `#303033` | 按下/激活态 |
+| Token | Tailwind 类 | 用途 |
+|---|---|---|
+| `--ag-bg-deep` | `agw-bg-bg-deep` | 页面最底层 |
+| `--ag-bg` | `agw-bg-bg` | 侧边栏/主面板 |
+| `--ag-bg-elevated` | `agw-bg-bg-elevated` | 卡片/弹窗/下拉 |
+| `--ag-bg-surface` | `agw-bg-surface` | 输入框/表单区域 |
+| `--ag-bg-hover` | `agw-bg-bg-hover` | 悬停态背景 |
+| `--ag-bg-active` | `agw-bg-bg-active` | 按下/激活态 |
### 文字
-| Token | Tailwind 类 | 暗色值 | 用途 |
-|---|---|---|---|
-| `--ag-text` | `agw-text-text` | `#ececf0` | 主文字 |
-| `--ag-text-secondary` | `agw-text-text-secondary` | `#a1a1aa` | 次要文字/标签 |
-| `--ag-text-tertiary` | `agw-text-text-tertiary` | `#63636e` | 提示文字/占位符 |
-| `--ag-text-inverse` | `agw-text-text-inverse` | `#0a0a0c` | 反色(主色按钮文字) |
+| Token | Tailwind 类 | 用途 |
+|---|---|---|
+| `--ag-text` | `agw-text-text` | 主文字 |
+| `--ag-text-secondary` | `agw-text-text-secondary` | 次要文字/标签 |
+| `--ag-text-tertiary` | `agw-text-text-tertiary` | 提示文字/占位符 |
+| `--ag-text-inverse` | `agw-text-text-inverse` | 反色(主色按钮文字) |
### 边框
@@ -237,13 +237,14 @@ export function YourComponent(props) {
### 其他
-| Token | Tailwind 类 | 值 |
+| Token | Tailwind 类 | 用途 |
|---|---|---|
-| `--ag-radius-sm` | `agw-rounded-sm` | `6px` |
-| `--ag-radius-md` | `agw-rounded-md` | `10px` |
-| `--ag-radius-lg` | `agw-rounded-lg` | `14px` |
-| `--ag-font-sans` | `agw-font-sans` | Inter |
-| `--ag-font-mono` | `agw-font-mono` | JetBrains Mono |
+| `--ag-radius-sm` | `agw-rounded-sm` | 小圆角 |
+| `--ag-radius-md` | `agw-rounded-md` | 中圆角 |
+| `--ag-radius-lg` | `agw-rounded-lg` | 大圆角 |
+| `--ag-field-radius` | `agw-rounded-field` | 表单控件圆角 |
+| `--ag-font-sans` | `agw-font-sans` | 正文字体 |
+| `--ag-font-mono` | `agw-font-mono` | 等宽字体 |
## 6. SDK 提供的 UI 组件
diff --git a/docs/sdk-package-boundaries.md b/docs/sdk-package-boundaries.md
index 351d09f..5c14a9f 100644
--- a/docs/sdk-package-boundaries.md
+++ b/docs/sdk-package-boundaries.md
@@ -54,7 +54,7 @@ SDK 提供少量弱契约扩展点,用来承接展示、分类和通用计量
- `Usage.Metadata`:单次调用的展示或审计辅助信息。
- `EventHandler`:Core 向插件推送标准事件。
- `Host.Invoke` / `Host.InvokeStream`:插件用 `method + payload` 调用 Core 开放的方法,必须由 `host.invoke` 或 `host.invoke.` capability 门控。
-- `SchemaProvider`:插件声明 routes、tasks、events、invokes 的 payload schema。
+- `SchemaProvider`:插件声明 routes、tasks、events、invokes 的 payload schema;流式 Host method 用 `InvokeSchema.Transport`、`ClientFrame`、`ServerFrame` 描述传输模式和帧结构。
这些字段不能用于权限、调度、账号状态机或敏感数据传递。平台计费规则不得进入 SDK;网关插件负责计算标准账号成本 `Usage.AccountCost` / `Currency` 和审计明细。Core 统一入库、索引、汇总,并写入 `UserCost` / `BillingMultiplier`。
diff --git a/frontend/package.json b/frontend/package.json
index 6c721e9..d5c6850 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -2,6 +2,7 @@
"name": "@airgate/theme",
"version": "2.0.0",
"type": "module",
+ "packageManager": "npm@11.6.2",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"exports": {
diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml
deleted file mode 100644
index b5ba770..0000000
--- a/frontend/pnpm-lock.yaml
+++ /dev/null
@@ -1,48 +0,0 @@
-lockfileVersion: '9.0'
-
-settings:
- autoInstallPeers: true
- excludeLinksFromLockfile: false
-
-importers:
-
- .:
- devDependencies:
- '@types/react':
- specifier: ^19.0.0
- version: 19.2.14
- react:
- specifier: ^19.0.0
- version: 19.2.5
- typescript:
- specifier: ^5.7.0
- version: 5.9.3
-
-packages:
-
- '@types/react@19.2.14':
- resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==}
-
- csstype@3.2.3:
- resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
-
- react@19.2.5:
- resolution: {integrity: sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==}
- engines: {node: '>=0.10.0'}
-
- typescript@5.9.3:
- resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
- engines: {node: '>=14.17'}
- hasBin: true
-
-snapshots:
-
- '@types/react@19.2.14':
- dependencies:
- csstype: 3.2.3
-
- csstype@3.2.3: {}
-
- react@19.2.5: {}
-
- typescript@5.9.3: {}
diff --git a/protocol/proto/plugin.pb.go b/protocol/proto/plugin.pb.go
index f9a5c66..5e8a4b4 100644
--- a/protocol/proto/plugin.pb.go
+++ b/protocol/proto/plugin.pb.go
@@ -2935,6 +2935,9 @@ type InvokeSchemaProto struct {
Request *PayloadSchemaProto `protobuf:"bytes,3,opt,name=request,proto3" json:"request,omitempty"`
Response *PayloadSchemaProto `protobuf:"bytes,4,opt,name=response,proto3" json:"response,omitempty"`
Metadata map[string]string `protobuf:"bytes,5,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
+ Transport string `protobuf:"bytes,6,opt,name=transport,proto3" json:"transport,omitempty"` // unary / server_stream / client_stream / bidirectional_stream
+ ClientFrame *PayloadSchemaProto `protobuf:"bytes,7,opt,name=client_frame,json=clientFrame,proto3" json:"client_frame,omitempty"` // InvokeStream client frame payload schema
+ ServerFrame *PayloadSchemaProto `protobuf:"bytes,8,opt,name=server_frame,json=serverFrame,proto3" json:"server_frame,omitempty"` // InvokeStream server frame payload schema
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@@ -3004,6 +3007,27 @@ func (x *InvokeSchemaProto) GetMetadata() map[string]string {
return nil
}
+func (x *InvokeSchemaProto) GetTransport() string {
+ if x != nil {
+ return x.Transport
+ }
+ return ""
+}
+
+func (x *InvokeSchemaProto) GetClientFrame() *PayloadSchemaProto {
+ if x != nil {
+ return x.ClientFrame
+ }
+ return nil
+}
+
+func (x *InvokeSchemaProto) GetServerFrame() *PayloadSchemaProto {
+ if x != nil {
+ return x.ServerFrame
+ }
+ return nil
+}
+
type PluginSchemaResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
Routes []*RouteSchemaProto `protobuf:"bytes,1,rep,name=routes,proto3" json:"routes,omitempty"`
@@ -3092,8 +3116,8 @@ type MiddlewareRequest struct {
Model string `protobuf:"bytes,6,opt,name=model,proto3" json:"model,omitempty"`
Stream bool `protobuf:"varint,7,opt,name=stream,proto3" json:"stream,omitempty"`
Estimates []*UsageMetric `protobuf:"bytes,8,rep,name=estimates,proto3" json:"estimates,omitempty"` // Core 或插件提供的通用预估值,仅用于早期决策
- // metadata KV bag:供多个 middleware 之间传递上下文(Open Question Q-open-3)。
- // 命名空间规则暂不强制,未来可能收紧。
+ // metadata KV bag:供多个 middleware 之间传递上下文。
+ // 命名空间规则由 Core 管理,插件不应依赖未声明的敏感字段。
Metadata map[string]string `protobuf:"bytes,9,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
// === 按需字段(声明了 middleware.read_body capability 的插件才会收到)===
RequestBody []byte `protobuf:"bytes,100,opt,name=request_body,json=requestBody,proto3" json:"request_body,omitempty"`
@@ -4416,13 +4440,16 @@ const file_plugin_proto_rawDesc = "" +
"\bmetadata\x18\x05 \x03(\v21.airgate.plugin.v2.EventSchemaProto.MetadataEntryR\bmetadata\x1a;\n" +
"\rMetadataEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
- "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xd6\x02\n" +
+ "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\x88\x04\n" +
"\x11InvokeSchemaProto\x12\x16\n" +
"\x06method\x18\x01 \x01(\tR\x06method\x12\x18\n" +
"\asummary\x18\x02 \x01(\tR\asummary\x12?\n" +
"\arequest\x18\x03 \x01(\v2%.airgate.plugin.v2.PayloadSchemaProtoR\arequest\x12A\n" +
"\bresponse\x18\x04 \x01(\v2%.airgate.plugin.v2.PayloadSchemaProtoR\bresponse\x12N\n" +
- "\bmetadata\x18\x05 \x03(\v22.airgate.plugin.v2.InvokeSchemaProto.MetadataEntryR\bmetadata\x1a;\n" +
+ "\bmetadata\x18\x05 \x03(\v22.airgate.plugin.v2.InvokeSchemaProto.MetadataEntryR\bmetadata\x12\x1c\n" +
+ "\ttransport\x18\x06 \x01(\tR\ttransport\x12H\n" +
+ "\fclient_frame\x18\a \x01(\v2%.airgate.plugin.v2.PayloadSchemaProtoR\vclientFrame\x12H\n" +
+ "\fserver_frame\x18\b \x01(\v2%.airgate.plugin.v2.PayloadSchemaProtoR\vserverFrame\x1a;\n" +
"\rMetadataEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\x9a\x03\n" +
@@ -4769,99 +4796,101 @@ var file_plugin_proto_depIdxs = []int32{
37, // 47: airgate.plugin.v2.InvokeSchemaProto.request:type_name -> airgate.plugin.v2.PayloadSchemaProto
37, // 48: airgate.plugin.v2.InvokeSchemaProto.response:type_name -> airgate.plugin.v2.PayloadSchemaProto
77, // 49: airgate.plugin.v2.InvokeSchemaProto.metadata:type_name -> airgate.plugin.v2.InvokeSchemaProto.MetadataEntry
- 38, // 50: airgate.plugin.v2.PluginSchemaResponse.routes:type_name -> airgate.plugin.v2.RouteSchemaProto
- 39, // 51: airgate.plugin.v2.PluginSchemaResponse.tasks:type_name -> airgate.plugin.v2.TaskSchemaProto
- 40, // 52: airgate.plugin.v2.PluginSchemaResponse.events:type_name -> airgate.plugin.v2.EventSchemaProto
- 41, // 53: airgate.plugin.v2.PluginSchemaResponse.invokes:type_name -> airgate.plugin.v2.InvokeSchemaProto
- 78, // 54: airgate.plugin.v2.PluginSchemaResponse.metadata:type_name -> airgate.plugin.v2.PluginSchemaResponse.MetadataEntry
- 21, // 55: airgate.plugin.v2.MiddlewareRequest.estimates:type_name -> airgate.plugin.v2.UsageMetric
- 79, // 56: airgate.plugin.v2.MiddlewareRequest.metadata:type_name -> airgate.plugin.v2.MiddlewareRequest.MetadataEntry
- 80, // 57: airgate.plugin.v2.MiddlewareRequest.request_headers:type_name -> airgate.plugin.v2.MiddlewareRequest.RequestHeadersEntry
- 21, // 58: airgate.plugin.v2.MiddlewareEvent.estimates:type_name -> airgate.plugin.v2.UsageMetric
- 23, // 59: airgate.plugin.v2.MiddlewareEvent.usage:type_name -> airgate.plugin.v2.Usage
- 81, // 60: airgate.plugin.v2.MiddlewareEvent.metadata:type_name -> airgate.plugin.v2.MiddlewareEvent.MetadataEntry
- 82, // 61: airgate.plugin.v2.MiddlewareEvent.response_headers:type_name -> airgate.plugin.v2.MiddlewareEvent.ResponseHeadersEntry
- 2, // 62: airgate.plugin.v2.MiddlewareDecision.action:type_name -> airgate.plugin.v2.MiddlewareDecision.Action
- 83, // 63: airgate.plugin.v2.MiddlewareDecision.set_headers:type_name -> airgate.plugin.v2.MiddlewareDecision.SetHeadersEntry
- 84, // 64: airgate.plugin.v2.MiddlewareDecision.metadata:type_name -> airgate.plugin.v2.MiddlewareDecision.MetadataEntry
- 85, // 65: airgate.plugin.v2.EventSubscriptionProto.filter:type_name -> airgate.plugin.v2.EventSubscriptionProto.FilterEntry
- 86, // 66: airgate.plugin.v2.EventSubscriptionProto.metadata:type_name -> airgate.plugin.v2.EventSubscriptionProto.MetadataEntry
- 46, // 67: airgate.plugin.v2.EventSubscriptionsResponse.subscriptions:type_name -> airgate.plugin.v2.EventSubscriptionProto
- 87, // 68: airgate.plugin.v2.PluginEvent.metadata:type_name -> airgate.plugin.v2.PluginEvent.MetadataEntry
- 88, // 69: airgate.plugin.v2.HostInvokeRequest.metadata:type_name -> airgate.plugin.v2.HostInvokeRequest.MetadataEntry
- 89, // 70: airgate.plugin.v2.HostInvokeResponse.metadata:type_name -> airgate.plugin.v2.HostInvokeResponse.MetadataEntry
- 90, // 71: airgate.plugin.v2.HostStreamFrame.metadata:type_name -> airgate.plugin.v2.HostStreamFrame.MetadataEntry
- 5, // 72: airgate.plugin.v2.ForwardRequest.HeadersEntry.value:type_name -> airgate.plugin.v2.HeaderValues
- 5, // 73: airgate.plugin.v2.UpstreamResponse.HeadersEntry.value:type_name -> airgate.plugin.v2.HeaderValues
- 5, // 74: airgate.plugin.v2.ForwardChunk.HeadersEntry.value:type_name -> airgate.plugin.v2.HeaderValues
- 5, // 75: airgate.plugin.v2.HttpRequest.HeadersEntry.value:type_name -> airgate.plugin.v2.HeaderValues
- 5, // 76: airgate.plugin.v2.HttpResponse.HeadersEntry.value:type_name -> airgate.plugin.v2.HeaderValues
- 5, // 77: airgate.plugin.v2.HttpResponseChunk.HeadersEntry.value:type_name -> airgate.plugin.v2.HeaderValues
- 5, // 78: airgate.plugin.v2.WebSocketConnectInfo.HeadersEntry.value:type_name -> airgate.plugin.v2.HeaderValues
- 5, // 79: airgate.plugin.v2.MiddlewareRequest.RequestHeadersEntry.value:type_name -> airgate.plugin.v2.HeaderValues
- 5, // 80: airgate.plugin.v2.MiddlewareEvent.ResponseHeadersEntry.value:type_name -> airgate.plugin.v2.HeaderValues
- 5, // 81: airgate.plugin.v2.MiddlewareDecision.SetHeadersEntry.value:type_name -> airgate.plugin.v2.HeaderValues
- 3, // 82: airgate.plugin.v2.PluginService.GetInfo:input_type -> airgate.plugin.v2.Empty
- 12, // 83: airgate.plugin.v2.PluginService.Init:input_type -> airgate.plugin.v2.InitRequest
- 3, // 84: airgate.plugin.v2.PluginService.Start:input_type -> airgate.plugin.v2.Empty
- 3, // 85: airgate.plugin.v2.PluginService.Stop:input_type -> airgate.plugin.v2.Empty
- 3, // 86: airgate.plugin.v2.PluginService.GetWebAssets:input_type -> airgate.plugin.v2.Empty
- 3, // 87: airgate.plugin.v2.PluginService.GetSchema:input_type -> airgate.plugin.v2.Empty
- 3, // 88: airgate.plugin.v2.PluginService.HealthCheck:input_type -> airgate.plugin.v2.Empty
- 27, // 89: airgate.plugin.v2.PluginService.HandleRequest:input_type -> airgate.plugin.v2.HttpRequest
- 3, // 90: airgate.plugin.v2.GatewayService.GetPlatform:input_type -> airgate.plugin.v2.Empty
- 3, // 91: airgate.plugin.v2.GatewayService.GetModels:input_type -> airgate.plugin.v2.Empty
- 3, // 92: airgate.plugin.v2.GatewayService.GetRoutes:input_type -> airgate.plugin.v2.Empty
- 18, // 93: airgate.plugin.v2.GatewayService.Forward:input_type -> airgate.plugin.v2.ForwardRequest
- 18, // 94: airgate.plugin.v2.GatewayService.ForwardStream:input_type -> airgate.plugin.v2.ForwardRequest
- 26, // 95: airgate.plugin.v2.GatewayService.ValidateAccount:input_type -> airgate.plugin.v2.CredentialsRequest
- 33, // 96: airgate.plugin.v2.GatewayService.HandleWebSocket:input_type -> airgate.plugin.v2.WebSocketFrame
- 3, // 97: airgate.plugin.v2.ExtensionService.Migrate:input_type -> airgate.plugin.v2.Empty
- 3, // 98: airgate.plugin.v2.ExtensionService.GetBackgroundTasks:input_type -> airgate.plugin.v2.Empty
- 32, // 99: airgate.plugin.v2.ExtensionService.RunBackgroundTask:input_type -> airgate.plugin.v2.RunBackgroundTaskRequest
- 27, // 100: airgate.plugin.v2.ExtensionService.HandleRequest:input_type -> airgate.plugin.v2.HttpRequest
- 27, // 101: airgate.plugin.v2.ExtensionService.HandleStreamRequest:input_type -> airgate.plugin.v2.HttpRequest
- 53, // 102: airgate.plugin.v2.ExtensionService.ProcessTask:input_type -> airgate.plugin.v2.ProcessTaskRequest
- 3, // 103: airgate.plugin.v2.ExtensionService.GetTaskTypes:input_type -> airgate.plugin.v2.Empty
- 43, // 104: airgate.plugin.v2.MiddlewareService.OnForwardBegin:input_type -> airgate.plugin.v2.MiddlewareRequest
- 44, // 105: airgate.plugin.v2.MiddlewareService.OnForwardEnd:input_type -> airgate.plugin.v2.MiddlewareEvent
- 3, // 106: airgate.plugin.v2.EventService.GetEventSubscriptions:input_type -> airgate.plugin.v2.Empty
- 48, // 107: airgate.plugin.v2.EventService.HandleEvent:input_type -> airgate.plugin.v2.PluginEvent
- 50, // 108: airgate.plugin.v2.CoreInvokeService.Invoke:input_type -> airgate.plugin.v2.HostInvokeRequest
- 52, // 109: airgate.plugin.v2.CoreInvokeService.InvokeStream:input_type -> airgate.plugin.v2.HostStreamFrame
- 6, // 110: airgate.plugin.v2.PluginService.GetInfo:output_type -> airgate.plugin.v2.PluginInfoResponse
- 3, // 111: airgate.plugin.v2.PluginService.Init:output_type -> airgate.plugin.v2.Empty
- 3, // 112: airgate.plugin.v2.PluginService.Start:output_type -> airgate.plugin.v2.Empty
- 3, // 113: airgate.plugin.v2.PluginService.Stop:output_type -> airgate.plugin.v2.Empty
- 36, // 114: airgate.plugin.v2.PluginService.GetWebAssets:output_type -> airgate.plugin.v2.WebAssetsResponse
- 42, // 115: airgate.plugin.v2.PluginService.GetSchema:output_type -> airgate.plugin.v2.PluginSchemaResponse
- 3, // 116: airgate.plugin.v2.PluginService.HealthCheck:output_type -> airgate.plugin.v2.Empty
- 28, // 117: airgate.plugin.v2.PluginService.HandleRequest:output_type -> airgate.plugin.v2.HttpResponse
- 4, // 118: airgate.plugin.v2.GatewayService.GetPlatform:output_type -> airgate.plugin.v2.StringResponse
- 14, // 119: airgate.plugin.v2.GatewayService.GetModels:output_type -> airgate.plugin.v2.ModelsResponse
- 16, // 120: airgate.plugin.v2.GatewayService.GetRoutes:output_type -> airgate.plugin.v2.RoutesResponse
- 24, // 121: airgate.plugin.v2.GatewayService.Forward:output_type -> airgate.plugin.v2.ForwardOutcome
- 25, // 122: airgate.plugin.v2.GatewayService.ForwardStream:output_type -> airgate.plugin.v2.ForwardChunk
- 3, // 123: airgate.plugin.v2.GatewayService.ValidateAccount:output_type -> airgate.plugin.v2.Empty
- 33, // 124: airgate.plugin.v2.GatewayService.HandleWebSocket:output_type -> airgate.plugin.v2.WebSocketFrame
- 3, // 125: airgate.plugin.v2.ExtensionService.Migrate:output_type -> airgate.plugin.v2.Empty
- 31, // 126: airgate.plugin.v2.ExtensionService.GetBackgroundTasks:output_type -> airgate.plugin.v2.BackgroundTasksResponse
- 3, // 127: airgate.plugin.v2.ExtensionService.RunBackgroundTask:output_type -> airgate.plugin.v2.Empty
- 28, // 128: airgate.plugin.v2.ExtensionService.HandleRequest:output_type -> airgate.plugin.v2.HttpResponse
- 29, // 129: airgate.plugin.v2.ExtensionService.HandleStreamRequest:output_type -> airgate.plugin.v2.HttpResponseChunk
- 54, // 130: airgate.plugin.v2.ExtensionService.ProcessTask:output_type -> airgate.plugin.v2.ProcessTaskResponse
- 55, // 131: airgate.plugin.v2.ExtensionService.GetTaskTypes:output_type -> airgate.plugin.v2.TaskTypesResponse
- 45, // 132: airgate.plugin.v2.MiddlewareService.OnForwardBegin:output_type -> airgate.plugin.v2.MiddlewareDecision
- 3, // 133: airgate.plugin.v2.MiddlewareService.OnForwardEnd:output_type -> airgate.plugin.v2.Empty
- 47, // 134: airgate.plugin.v2.EventService.GetEventSubscriptions:output_type -> airgate.plugin.v2.EventSubscriptionsResponse
- 49, // 135: airgate.plugin.v2.EventService.HandleEvent:output_type -> airgate.plugin.v2.EventHandleResponse
- 51, // 136: airgate.plugin.v2.CoreInvokeService.Invoke:output_type -> airgate.plugin.v2.HostInvokeResponse
- 52, // 137: airgate.plugin.v2.CoreInvokeService.InvokeStream:output_type -> airgate.plugin.v2.HostStreamFrame
- 110, // [110:138] is the sub-list for method output_type
- 82, // [82:110] is the sub-list for method input_type
- 82, // [82:82] is the sub-list for extension type_name
- 82, // [82:82] is the sub-list for extension extendee
- 0, // [0:82] is the sub-list for field type_name
+ 37, // 50: airgate.plugin.v2.InvokeSchemaProto.client_frame:type_name -> airgate.plugin.v2.PayloadSchemaProto
+ 37, // 51: airgate.plugin.v2.InvokeSchemaProto.server_frame:type_name -> airgate.plugin.v2.PayloadSchemaProto
+ 38, // 52: airgate.plugin.v2.PluginSchemaResponse.routes:type_name -> airgate.plugin.v2.RouteSchemaProto
+ 39, // 53: airgate.plugin.v2.PluginSchemaResponse.tasks:type_name -> airgate.plugin.v2.TaskSchemaProto
+ 40, // 54: airgate.plugin.v2.PluginSchemaResponse.events:type_name -> airgate.plugin.v2.EventSchemaProto
+ 41, // 55: airgate.plugin.v2.PluginSchemaResponse.invokes:type_name -> airgate.plugin.v2.InvokeSchemaProto
+ 78, // 56: airgate.plugin.v2.PluginSchemaResponse.metadata:type_name -> airgate.plugin.v2.PluginSchemaResponse.MetadataEntry
+ 21, // 57: airgate.plugin.v2.MiddlewareRequest.estimates:type_name -> airgate.plugin.v2.UsageMetric
+ 79, // 58: airgate.plugin.v2.MiddlewareRequest.metadata:type_name -> airgate.plugin.v2.MiddlewareRequest.MetadataEntry
+ 80, // 59: airgate.plugin.v2.MiddlewareRequest.request_headers:type_name -> airgate.plugin.v2.MiddlewareRequest.RequestHeadersEntry
+ 21, // 60: airgate.plugin.v2.MiddlewareEvent.estimates:type_name -> airgate.plugin.v2.UsageMetric
+ 23, // 61: airgate.plugin.v2.MiddlewareEvent.usage:type_name -> airgate.plugin.v2.Usage
+ 81, // 62: airgate.plugin.v2.MiddlewareEvent.metadata:type_name -> airgate.plugin.v2.MiddlewareEvent.MetadataEntry
+ 82, // 63: airgate.plugin.v2.MiddlewareEvent.response_headers:type_name -> airgate.plugin.v2.MiddlewareEvent.ResponseHeadersEntry
+ 2, // 64: airgate.plugin.v2.MiddlewareDecision.action:type_name -> airgate.plugin.v2.MiddlewareDecision.Action
+ 83, // 65: airgate.plugin.v2.MiddlewareDecision.set_headers:type_name -> airgate.plugin.v2.MiddlewareDecision.SetHeadersEntry
+ 84, // 66: airgate.plugin.v2.MiddlewareDecision.metadata:type_name -> airgate.plugin.v2.MiddlewareDecision.MetadataEntry
+ 85, // 67: airgate.plugin.v2.EventSubscriptionProto.filter:type_name -> airgate.plugin.v2.EventSubscriptionProto.FilterEntry
+ 86, // 68: airgate.plugin.v2.EventSubscriptionProto.metadata:type_name -> airgate.plugin.v2.EventSubscriptionProto.MetadataEntry
+ 46, // 69: airgate.plugin.v2.EventSubscriptionsResponse.subscriptions:type_name -> airgate.plugin.v2.EventSubscriptionProto
+ 87, // 70: airgate.plugin.v2.PluginEvent.metadata:type_name -> airgate.plugin.v2.PluginEvent.MetadataEntry
+ 88, // 71: airgate.plugin.v2.HostInvokeRequest.metadata:type_name -> airgate.plugin.v2.HostInvokeRequest.MetadataEntry
+ 89, // 72: airgate.plugin.v2.HostInvokeResponse.metadata:type_name -> airgate.plugin.v2.HostInvokeResponse.MetadataEntry
+ 90, // 73: airgate.plugin.v2.HostStreamFrame.metadata:type_name -> airgate.plugin.v2.HostStreamFrame.MetadataEntry
+ 5, // 74: airgate.plugin.v2.ForwardRequest.HeadersEntry.value:type_name -> airgate.plugin.v2.HeaderValues
+ 5, // 75: airgate.plugin.v2.UpstreamResponse.HeadersEntry.value:type_name -> airgate.plugin.v2.HeaderValues
+ 5, // 76: airgate.plugin.v2.ForwardChunk.HeadersEntry.value:type_name -> airgate.plugin.v2.HeaderValues
+ 5, // 77: airgate.plugin.v2.HttpRequest.HeadersEntry.value:type_name -> airgate.plugin.v2.HeaderValues
+ 5, // 78: airgate.plugin.v2.HttpResponse.HeadersEntry.value:type_name -> airgate.plugin.v2.HeaderValues
+ 5, // 79: airgate.plugin.v2.HttpResponseChunk.HeadersEntry.value:type_name -> airgate.plugin.v2.HeaderValues
+ 5, // 80: airgate.plugin.v2.WebSocketConnectInfo.HeadersEntry.value:type_name -> airgate.plugin.v2.HeaderValues
+ 5, // 81: airgate.plugin.v2.MiddlewareRequest.RequestHeadersEntry.value:type_name -> airgate.plugin.v2.HeaderValues
+ 5, // 82: airgate.plugin.v2.MiddlewareEvent.ResponseHeadersEntry.value:type_name -> airgate.plugin.v2.HeaderValues
+ 5, // 83: airgate.plugin.v2.MiddlewareDecision.SetHeadersEntry.value:type_name -> airgate.plugin.v2.HeaderValues
+ 3, // 84: airgate.plugin.v2.PluginService.GetInfo:input_type -> airgate.plugin.v2.Empty
+ 12, // 85: airgate.plugin.v2.PluginService.Init:input_type -> airgate.plugin.v2.InitRequest
+ 3, // 86: airgate.plugin.v2.PluginService.Start:input_type -> airgate.plugin.v2.Empty
+ 3, // 87: airgate.plugin.v2.PluginService.Stop:input_type -> airgate.plugin.v2.Empty
+ 3, // 88: airgate.plugin.v2.PluginService.GetWebAssets:input_type -> airgate.plugin.v2.Empty
+ 3, // 89: airgate.plugin.v2.PluginService.GetSchema:input_type -> airgate.plugin.v2.Empty
+ 3, // 90: airgate.plugin.v2.PluginService.HealthCheck:input_type -> airgate.plugin.v2.Empty
+ 27, // 91: airgate.plugin.v2.PluginService.HandleRequest:input_type -> airgate.plugin.v2.HttpRequest
+ 3, // 92: airgate.plugin.v2.GatewayService.GetPlatform:input_type -> airgate.plugin.v2.Empty
+ 3, // 93: airgate.plugin.v2.GatewayService.GetModels:input_type -> airgate.plugin.v2.Empty
+ 3, // 94: airgate.plugin.v2.GatewayService.GetRoutes:input_type -> airgate.plugin.v2.Empty
+ 18, // 95: airgate.plugin.v2.GatewayService.Forward:input_type -> airgate.plugin.v2.ForwardRequest
+ 18, // 96: airgate.plugin.v2.GatewayService.ForwardStream:input_type -> airgate.plugin.v2.ForwardRequest
+ 26, // 97: airgate.plugin.v2.GatewayService.ValidateAccount:input_type -> airgate.plugin.v2.CredentialsRequest
+ 33, // 98: airgate.plugin.v2.GatewayService.HandleWebSocket:input_type -> airgate.plugin.v2.WebSocketFrame
+ 3, // 99: airgate.plugin.v2.ExtensionService.Migrate:input_type -> airgate.plugin.v2.Empty
+ 3, // 100: airgate.plugin.v2.ExtensionService.GetBackgroundTasks:input_type -> airgate.plugin.v2.Empty
+ 32, // 101: airgate.plugin.v2.ExtensionService.RunBackgroundTask:input_type -> airgate.plugin.v2.RunBackgroundTaskRequest
+ 27, // 102: airgate.plugin.v2.ExtensionService.HandleRequest:input_type -> airgate.plugin.v2.HttpRequest
+ 27, // 103: airgate.plugin.v2.ExtensionService.HandleStreamRequest:input_type -> airgate.plugin.v2.HttpRequest
+ 53, // 104: airgate.plugin.v2.ExtensionService.ProcessTask:input_type -> airgate.plugin.v2.ProcessTaskRequest
+ 3, // 105: airgate.plugin.v2.ExtensionService.GetTaskTypes:input_type -> airgate.plugin.v2.Empty
+ 43, // 106: airgate.plugin.v2.MiddlewareService.OnForwardBegin:input_type -> airgate.plugin.v2.MiddlewareRequest
+ 44, // 107: airgate.plugin.v2.MiddlewareService.OnForwardEnd:input_type -> airgate.plugin.v2.MiddlewareEvent
+ 3, // 108: airgate.plugin.v2.EventService.GetEventSubscriptions:input_type -> airgate.plugin.v2.Empty
+ 48, // 109: airgate.plugin.v2.EventService.HandleEvent:input_type -> airgate.plugin.v2.PluginEvent
+ 50, // 110: airgate.plugin.v2.CoreInvokeService.Invoke:input_type -> airgate.plugin.v2.HostInvokeRequest
+ 52, // 111: airgate.plugin.v2.CoreInvokeService.InvokeStream:input_type -> airgate.plugin.v2.HostStreamFrame
+ 6, // 112: airgate.plugin.v2.PluginService.GetInfo:output_type -> airgate.plugin.v2.PluginInfoResponse
+ 3, // 113: airgate.plugin.v2.PluginService.Init:output_type -> airgate.plugin.v2.Empty
+ 3, // 114: airgate.plugin.v2.PluginService.Start:output_type -> airgate.plugin.v2.Empty
+ 3, // 115: airgate.plugin.v2.PluginService.Stop:output_type -> airgate.plugin.v2.Empty
+ 36, // 116: airgate.plugin.v2.PluginService.GetWebAssets:output_type -> airgate.plugin.v2.WebAssetsResponse
+ 42, // 117: airgate.plugin.v2.PluginService.GetSchema:output_type -> airgate.plugin.v2.PluginSchemaResponse
+ 3, // 118: airgate.plugin.v2.PluginService.HealthCheck:output_type -> airgate.plugin.v2.Empty
+ 28, // 119: airgate.plugin.v2.PluginService.HandleRequest:output_type -> airgate.plugin.v2.HttpResponse
+ 4, // 120: airgate.plugin.v2.GatewayService.GetPlatform:output_type -> airgate.plugin.v2.StringResponse
+ 14, // 121: airgate.plugin.v2.GatewayService.GetModels:output_type -> airgate.plugin.v2.ModelsResponse
+ 16, // 122: airgate.plugin.v2.GatewayService.GetRoutes:output_type -> airgate.plugin.v2.RoutesResponse
+ 24, // 123: airgate.plugin.v2.GatewayService.Forward:output_type -> airgate.plugin.v2.ForwardOutcome
+ 25, // 124: airgate.plugin.v2.GatewayService.ForwardStream:output_type -> airgate.plugin.v2.ForwardChunk
+ 3, // 125: airgate.plugin.v2.GatewayService.ValidateAccount:output_type -> airgate.plugin.v2.Empty
+ 33, // 126: airgate.plugin.v2.GatewayService.HandleWebSocket:output_type -> airgate.plugin.v2.WebSocketFrame
+ 3, // 127: airgate.plugin.v2.ExtensionService.Migrate:output_type -> airgate.plugin.v2.Empty
+ 31, // 128: airgate.plugin.v2.ExtensionService.GetBackgroundTasks:output_type -> airgate.plugin.v2.BackgroundTasksResponse
+ 3, // 129: airgate.plugin.v2.ExtensionService.RunBackgroundTask:output_type -> airgate.plugin.v2.Empty
+ 28, // 130: airgate.plugin.v2.ExtensionService.HandleRequest:output_type -> airgate.plugin.v2.HttpResponse
+ 29, // 131: airgate.plugin.v2.ExtensionService.HandleStreamRequest:output_type -> airgate.plugin.v2.HttpResponseChunk
+ 54, // 132: airgate.plugin.v2.ExtensionService.ProcessTask:output_type -> airgate.plugin.v2.ProcessTaskResponse
+ 55, // 133: airgate.plugin.v2.ExtensionService.GetTaskTypes:output_type -> airgate.plugin.v2.TaskTypesResponse
+ 45, // 134: airgate.plugin.v2.MiddlewareService.OnForwardBegin:output_type -> airgate.plugin.v2.MiddlewareDecision
+ 3, // 135: airgate.plugin.v2.MiddlewareService.OnForwardEnd:output_type -> airgate.plugin.v2.Empty
+ 47, // 136: airgate.plugin.v2.EventService.GetEventSubscriptions:output_type -> airgate.plugin.v2.EventSubscriptionsResponse
+ 49, // 137: airgate.plugin.v2.EventService.HandleEvent:output_type -> airgate.plugin.v2.EventHandleResponse
+ 51, // 138: airgate.plugin.v2.CoreInvokeService.Invoke:output_type -> airgate.plugin.v2.HostInvokeResponse
+ 52, // 139: airgate.plugin.v2.CoreInvokeService.InvokeStream:output_type -> airgate.plugin.v2.HostStreamFrame
+ 112, // [112:140] is the sub-list for method output_type
+ 84, // [84:112] is the sub-list for method input_type
+ 84, // [84:84] is the sub-list for extension type_name
+ 84, // [84:84] is the sub-list for extension extendee
+ 0, // [0:84] is the sub-list for field type_name
}
func init() { file_plugin_proto_init() }
diff --git a/protocol/proto/plugin.proto b/protocol/proto/plugin.proto
index 53d06ad..4d57667 100644
--- a/protocol/proto/plugin.proto
+++ b/protocol/proto/plugin.proto
@@ -215,7 +215,7 @@ message AccountProto {
}
message ForwardRequest {
- reserved 1, 2, 3, 4, 5, 6; // 原散落的 account 字段,已合并为 AccountProto
+ reserved 1, 2, 3, 4, 5, 6; // 保留历史字段号,避免 wire 级复用
bytes body = 7;
map headers = 8;
string model = 9;
@@ -382,7 +382,7 @@ message WebSocketConnectInfo {
map headers = 3;
string remote_addr = 4;
string connection_id = 5;
- reserved 6, 7, 8, 9, 10, 11; // 原散落的 account 字段,已合并为 AccountProto
+ reserved 6, 7, 8, 9, 10, 11; // 保留历史字段号,避免 wire 级复用
AccountProto account = 12;
}
@@ -438,6 +438,9 @@ message InvokeSchemaProto {
PayloadSchemaProto request = 3;
PayloadSchemaProto response = 4;
map metadata = 5;
+ string transport = 6; // unary / server_stream / client_stream / bidirectional_stream
+ PayloadSchemaProto client_frame = 7; // InvokeStream client frame payload schema
+ PayloadSchemaProto server_frame = 8; // InvokeStream server frame payload schema
}
message PluginSchemaResponse {
@@ -464,8 +467,8 @@ message MiddlewareRequest {
bool stream = 7;
repeated UsageMetric estimates = 8; // Core 或插件提供的通用预估值,仅用于早期决策
- // metadata KV bag:供多个 middleware 之间传递上下文(Open Question Q-open-3)。
- // 命名空间规则暂不强制,未来可能收紧。
+ // metadata KV bag:供多个 middleware 之间传递上下文。
+ // 命名空间规则由 Core 管理,插件不应依赖未声明的敏感字段。
map metadata = 9;
// === 按需字段(声明了 middleware.read_body capability 的插件才会收到)===
diff --git a/runtimego/grpc/event_schema_test.go b/runtimego/grpc/event_schema_test.go
index a430bb9..09f8052 100644
--- a/runtimego/grpc/event_schema_test.go
+++ b/runtimego/grpc/event_schema_test.go
@@ -72,10 +72,13 @@ func TestPluginSchemaRoundTrip(t *testing.T) {
Payload: sdk.PayloadSchema{Schema: `{"type":"object"}`},
}},
Invokes: []sdk.InvokeSchema{{
- Method: "notifications.publish",
- Summary: "发送通知",
- Request: sdk.PayloadSchema{Schema: `{"type":"object"}`},
- Response: sdk.PayloadSchema{Schema: `{"type":"object"}`},
+ Method: "chat.stream",
+ Summary: "流式对话",
+ Transport: sdk.InvokeTransportBidirectionalStream,
+ Request: sdk.PayloadSchema{Schema: `{"type":"object"}`},
+ Response: sdk.PayloadSchema{Schema: `{"type":"object"}`},
+ ClientFrame: sdk.PayloadSchema{Schema: `{"type":"object","properties":{"delta":{"type":"string"}}}`},
+ ServerFrame: sdk.PayloadSchema{Schema: `{"type":"object","properties":{"text":{"type":"string"}}}`},
}},
Metadata: map[string]string{"version": "1"},
}
diff --git a/runtimego/grpc/schema.go b/runtimego/grpc/schema.go
index 8a42831..68a0a5a 100644
--- a/runtimego/grpc/schema.go
+++ b/runtimego/grpc/schema.go
@@ -72,11 +72,14 @@ func schemaToProto(s sdk.PluginSchema) *pb.PluginSchemaResponse {
out.Invokes = make([]*pb.InvokeSchemaProto, 0, len(s.Invokes))
for _, i := range s.Invokes {
out.Invokes = append(out.Invokes, &pb.InvokeSchemaProto{
- Method: i.Method,
- Summary: i.Summary,
- Request: payloadSchemaToProto(i.Request),
- Response: payloadSchemaToProto(i.Response),
- Metadata: i.Metadata,
+ Method: i.Method,
+ Summary: i.Summary,
+ Request: payloadSchemaToProto(i.Request),
+ Response: payloadSchemaToProto(i.Response),
+ Transport: string(i.Transport),
+ ClientFrame: payloadSchemaToProto(i.ClientFrame),
+ ServerFrame: payloadSchemaToProto(i.ServerFrame),
+ Metadata: i.Metadata,
})
}
}
@@ -129,11 +132,14 @@ func schemaFromProto(p *pb.PluginSchemaResponse) sdk.PluginSchema {
out.Invokes = make([]sdk.InvokeSchema, 0, len(p.Invokes))
for _, i := range p.Invokes {
out.Invokes = append(out.Invokes, sdk.InvokeSchema{
- Method: i.Method,
- Summary: i.Summary,
- Request: payloadSchemaFromProto(i.Request),
- Response: payloadSchemaFromProto(i.Response),
- Metadata: i.Metadata,
+ Method: i.Method,
+ Summary: i.Summary,
+ Request: payloadSchemaFromProto(i.Request),
+ Response: payloadSchemaFromProto(i.Response),
+ Transport: sdk.InvokeTransport(i.Transport),
+ ClientFrame: payloadSchemaFromProto(i.ClientFrame),
+ ServerFrame: payloadSchemaFromProto(i.ServerFrame),
+ Metadata: i.Metadata,
})
}
}
diff --git a/sdkgo/outcome.go b/sdkgo/outcome.go
index 1bfd4bc..812b874 100644
--- a/sdkgo/outcome.go
+++ b/sdkgo/outcome.go
@@ -100,18 +100,17 @@ type UpstreamResponse struct {
// 插件填 AccountCost / Currency;Core 统一入库后按用户、分组、模型等倍率
// 写入 UserCost / BillingMultiplier。
type Usage struct {
- Model string
- AccountCost float64
- UserCost float64
- BillingMultiplier float64
- Currency string
- Summary string
- FirstTokenMs int64
-
- Attributes []UsageAttribute
- Metrics []UsageMetric
- CostDetails []UsageCostDetail
- Metadata map[string]string
+ Model string `json:"model,omitempty"`
+ AccountCost float64 `json:"account_cost,omitempty"`
+ UserCost float64 `json:"user_cost,omitempty"`
+ BillingMultiplier float64 `json:"billing_multiplier,omitempty"`
+ Currency string `json:"currency,omitempty"`
+ Summary string `json:"summary,omitempty"`
+ FirstTokenMs int64 `json:"first_token_ms,omitempty"`
+ Attributes []UsageAttribute `json:"attributes,omitempty"`
+ Metrics []UsageMetric `json:"metrics,omitempty"`
+ CostDetails []UsageCostDetail `json:"cost_details,omitempty"`
+ Metadata map[string]string `json:"metadata,omitempty"`
}
// ForwardOutcome 是插件对一次 Forward 的完整判决结果。
diff --git a/sdkgo/plugin.go b/sdkgo/plugin.go
index ae5bea8..e109c92 100644
--- a/sdkgo/plugin.go
+++ b/sdkgo/plugin.go
@@ -109,7 +109,7 @@ type WebAssetsProvider interface {
GetWebAssets() map[string][]byte
}
-// RequestHandler 可选:Core 将 /api/v1/admin/plugins/:name/rpc/* 透传给插件自行路由。
+// RequestHandler 可选:Core 将插件私有 API 请求透传给插件自行路由。
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)
}
diff --git a/sdkgo/schema.go b/sdkgo/schema.go
index db2ca34..da575d5 100644
--- a/sdkgo/schema.go
+++ b/sdkgo/schema.go
@@ -38,13 +38,28 @@ type EventSchema struct {
Metadata map[string]string `json:"metadata,omitempty"`
}
+// InvokeTransport 描述 Host.Invoke method 的传输模式。
+type InvokeTransport string
+
+const (
+ InvokeTransportUnary InvokeTransport = "unary"
+ InvokeTransportServerStream InvokeTransport = "server_stream"
+ InvokeTransportClientStream InvokeTransport = "client_stream"
+ InvokeTransportBidirectionalStream InvokeTransport = "bidirectional_stream"
+)
+
// InvokeSchema 描述通过 Host.Invoke 或 Host.InvokeStream 调用的扩展动作。
type InvokeSchema struct {
- Method string `json:"method"`
- Summary string `json:"summary,omitempty"`
- Request PayloadSchema `json:"request,omitempty"`
- Response PayloadSchema `json:"response,omitempty"`
- Metadata map[string]string `json:"metadata,omitempty"`
+ Method string `json:"method"`
+ Summary string `json:"summary,omitempty"`
+ // Transport 为空时等价于 unary。
+ Transport InvokeTransport `json:"transport,omitempty"`
+ Request PayloadSchema `json:"request,omitempty"`
+ Response PayloadSchema `json:"response,omitempty"`
+ // ClientFrame / ServerFrame 只用于 InvokeStream,描述双方 frame payload。
+ ClientFrame PayloadSchema `json:"client_frame,omitempty"`
+ ServerFrame PayloadSchema `json:"server_frame,omitempty"`
+ Metadata map[string]string `json:"metadata,omitempty"`
}
// PluginSchema 是插件的能力发现清单,用于 Core、管理后台和开发工具了解插件可用能力。
diff --git a/sdkgo/usage_test.go b/sdkgo/usage_test.go
index d10fbaa..572800c 100644
--- a/sdkgo/usage_test.go
+++ b/sdkgo/usage_test.go
@@ -2,6 +2,7 @@ package sdk_test
import (
"encoding/json"
+ "strings"
"testing"
sdk "github.com/DouDOU-start/airgate-sdk/sdkgo"
@@ -49,6 +50,17 @@ func TestUsageJSONRoundTrip(t *testing.T) {
if got.Model != usage.Model || got.AccountCost != usage.AccountCost || got.UserCost != usage.UserCost || got.BillingMultiplier != usage.BillingMultiplier || got.Currency != usage.Currency {
t.Fatalf("Usage round-trip mismatch: got %+v, want %+v", got, usage)
}
+ raw := string(data)
+ for _, key := range []string{"account_cost", "user_cost", "billing_multiplier", "first_token_ms", "cost_details"} {
+ if !strings.Contains(raw, `"`+key+`"`) {
+ t.Fatalf("Usage JSON 缺少 snake_case key %q: %s", key, raw)
+ }
+ }
+ for _, key := range []string{"AccountCost", "UserCost", "BillingMultiplier", "FirstTokenMs", "CostDetails"} {
+ if strings.Contains(raw, `"`+key+`"`) {
+ t.Fatalf("Usage JSON 不应包含 PascalCase key %q: %s", key, raw)
+ }
+ }
if len(got.Metrics) != 1 || got.Metrics[0].Key != "input_tokens" {
t.Fatalf("Metrics round-trip mismatch: %+v", got.Metrics)
}
From a9c7c3ca11d11407c68408802e1baf625faf9927 Mon Sep 17 00:00:00 2001
From: huangenjun <1021217094@qq.com>
Date: Tue, 12 May 2026 13:10:30 +0800
Subject: [PATCH 03/11] fix sdk review findings
---
README.md | 22 +++++-
docs/plugin-style-guide.md | 2 -
frontend/src/plugin.tsx | 12 +--
runtimego/grpc/event_schema_test.go | 9 ++-
runtimego/grpc/event_server.go | 34 ++++++---
runtimego/grpc/host_client.go | 45 ++++++++++--
runtimego/grpc/host_client_test.go | 109 ++++++++++++++++++++++++++--
runtimego/grpc/json_payload.go | 24 +++---
8 files changed, 218 insertions(+), 39 deletions(-)
diff --git a/README.md b/README.md
index 0b36b4e..80b962f 100644
--- a/README.md
+++ b/README.md
@@ -134,9 +134,26 @@ func (g *Gateway) Routes() []sdk.RouteDefinition {
}
func (g *Gateway) Forward(ctx context.Context, req *sdk.ForwardRequest) (sdk.ForwardOutcome, error) {
- // 这里请求真实上游,并把响应写入 req.Writer。
+ // 这里请求真实上游;示例用固定响应代替。
+ body := []byte(`{"id":"demo","choices":[]}`)
+ headers := http.Header{"Content-Type": []string{"application/json"}}
+ if req.Writer != nil {
+ for key, values := range headers {
+ for _, value := range values {
+ req.Writer.Header().Add(key, value)
+ }
+ }
+ req.Writer.WriteHeader(http.StatusOK)
+ _, _ = req.Writer.Write(body)
+ }
+
return sdk.ForwardOutcome{
Kind: sdk.OutcomeSuccess,
+ Upstream: sdk.UpstreamResponse{
+ StatusCode: http.StatusOK,
+ Headers: headers,
+ Body: body,
+ },
Usage: &sdk.Usage{
Model: "demo-model",
AccountCost: 0.000035,
@@ -264,6 +281,9 @@ defer stream.CloseSend()
for {
frame, err := stream.Recv()
+ if err == io.EOF {
+ break
+ }
if err != nil {
return err
}
diff --git a/docs/plugin-style-guide.md b/docs/plugin-style-guide.md
index d05945d..d551156 100644
--- a/docs/plugin-style-guide.md
+++ b/docs/plugin-style-guide.md
@@ -126,13 +126,11 @@ export const THEME_ATTRIBUTE = 'data-theme';
export const STYLE_ID = 'ag-YOUR_PLUGIN-theme-vars';
export const FOUNDATION_STYLE_ID = 'ag-YOUR_PLUGIN-plugin-foundation';
export const TAILWIND_STYLE_ID = 'ag-YOUR_PLUGIN-tailwind';
-export const STORAGE_KEY = 'ag-YOUR_PLUGIN-theme';
export function ensurePluginStyles(): void {
ensurePluginStyleFoundation({
scopeSelector: THEME_SCOPE_SELECTOR,
themeAttribute: THEME_ATTRIBUTE,
- storageKey: STORAGE_KEY,
themeStyleId: STYLE_ID,
foundationStyleId: FOUNDATION_STYLE_ID,
extraCssText: tailwindCssText, // 注入编译好的 Tailwind CSS
diff --git a/frontend/src/plugin.tsx b/frontend/src/plugin.tsx
index aea01ff..382dfe4 100644
--- a/frontend/src/plugin.tsx
+++ b/frontend/src/plugin.tsx
@@ -19,7 +19,6 @@ export const DEFAULT_PLUGIN_FOUNDATION_STYLE_ID = 'ag-plugin-foundation';
export interface PluginStyleFoundationOptions {
scopeSelector: string;
themeAttribute?: string;
- storageKey?: string;
themeStyleId?: string;
foundationStyleId?: string;
extraCssText?: string;
@@ -458,14 +457,15 @@ export const pluginFoundationCssText = `
}
`;
-export function injectStyle(id: string, cssText: string, targetDocument: Document = document): void {
- if (typeof document === 'undefined') return;
+export function injectStyle(id: string, cssText: string, targetDocument?: Document): void {
+ const doc = targetDocument || (typeof document !== 'undefined' ? document : undefined);
+ if (!doc) return;
- let element = targetDocument.getElementById(id) as HTMLStyleElement | null;
+ let element = doc.getElementById(id) as HTMLStyleElement | null;
if (!element) {
- element = targetDocument.createElement('style');
+ element = doc.createElement('style');
element.id = id;
- targetDocument.head.appendChild(element);
+ doc.head.appendChild(element);
}
if (element.textContent !== cssText) {
diff --git a/runtimego/grpc/event_schema_test.go b/runtimego/grpc/event_schema_test.go
index 09f8052..ef55a64 100644
--- a/runtimego/grpc/event_schema_test.go
+++ b/runtimego/grpc/event_schema_test.go
@@ -24,7 +24,14 @@ func TestPluginEventRoundTrip(t *testing.T) {
OccurredAt: time.UnixMilli(1700000000123),
}
- restored := eventFromProto(eventToProto(original))
+ protoEvent, err := eventToProto(original)
+ if err != nil {
+ t.Fatalf("eventToProto() error = %v", err)
+ }
+ restored, err := eventFromProto(protoEvent)
+ if err != nil {
+ t.Fatalf("eventFromProto() error = %v", err)
+ }
if !reflect.DeepEqual(original, restored) {
t.Fatalf("PluginEvent round-trip mismatch:\n original: %+v\n restored: %+v", original, restored)
}
diff --git a/runtimego/grpc/event_server.go b/runtimego/grpc/event_server.go
index b9e0d21..45919b6 100644
--- a/runtimego/grpc/event_server.go
+++ b/runtimego/grpc/event_server.go
@@ -15,11 +15,15 @@ type EventGRPCServer struct {
Impl sdk.Plugin
}
-func eventToProto(e sdk.PluginEvent) *pb.PluginEvent {
+func eventToProto(e sdk.PluginEvent) (*pb.PluginEvent, error) {
occurredAt := int64(0)
if !e.OccurredAt.IsZero() {
occurredAt = e.OccurredAt.UnixMilli()
}
+ payload, err := mapToJSONPayload(e.Payload)
+ if err != nil {
+ return nil, err
+ }
return &pb.PluginEvent{
Id: e.ID,
Type: e.Type,
@@ -27,15 +31,19 @@ func eventToProto(e sdk.PluginEvent) *pb.PluginEvent {
Subject: e.Subject,
UserId: e.UserID,
GroupId: e.GroupID,
- Payload: mapToJSONPayload(e.Payload),
+ Payload: payload,
Metadata: e.Metadata,
OccurredAt: occurredAt,
- }
+ }, nil
}
-func eventFromProto(p *pb.PluginEvent) sdk.PluginEvent {
+func eventFromProto(p *pb.PluginEvent) (sdk.PluginEvent, error) {
if p == nil {
- return sdk.PluginEvent{}
+ return sdk.PluginEvent{}, nil
+ }
+ payload, err := jsonPayloadToMap(p.Payload)
+ if err != nil {
+ return sdk.PluginEvent{}, err
}
event := sdk.PluginEvent{
ID: p.Id,
@@ -44,13 +52,13 @@ func eventFromProto(p *pb.PluginEvent) sdk.PluginEvent {
Subject: p.Subject,
UserID: p.UserId,
GroupID: p.GroupId,
- Payload: jsonPayloadToMap(p.Payload),
+ Payload: payload,
Metadata: p.Metadata,
}
if p.OccurredAt > 0 {
event.OccurredAt = time.UnixMilli(p.OccurredAt)
}
- return event
+ return event, nil
}
func subscriptionToProto(s sdk.EventSubscription) *pb.EventSubscriptionProto {
@@ -95,7 +103,11 @@ func (s *EventGRPCServer) HandleEvent(ctx context.Context, req *pb.PluginEvent)
if !ok {
return &pb.EventHandleResponse{Success: false, ErrorMessage: "plugin does not implement EventHandler"}, nil
}
- if err := handler.HandleEvent(ctx, eventFromProto(req)); err != nil {
+ event, err := eventFromProto(req)
+ if err != nil {
+ return &pb.EventHandleResponse{Success: false, ErrorMessage: err.Error()}, nil
+ }
+ if err := handler.HandleEvent(ctx, event); err != nil {
return &pb.EventHandleResponse{Success: false, ErrorMessage: err.Error()}, nil
}
return &pb.EventHandleResponse{Success: true}, nil
@@ -125,7 +137,11 @@ func (b *pluginBase) HandleEvent(ctx context.Context, event sdk.PluginEvent) err
if b.event == nil {
return errors.New("event service is not initialized")
}
- resp, err := b.event.HandleEvent(ctx, eventToProto(event))
+ protoEvent, err := eventToProto(event)
+ if err != nil {
+ return err
+ }
+ resp, err := b.event.HandleEvent(ctx, protoEvent)
if err != nil {
return err
}
diff --git a/runtimego/grpc/host_client.go b/runtimego/grpc/host_client.go
index f42aca2..e85a7e5 100644
--- a/runtimego/grpc/host_client.go
+++ b/runtimego/grpc/host_client.go
@@ -31,9 +31,18 @@ func hostRPCLogger(ctx context.Context, method string) (*slog.Logger, time.Time)
// Invoke 调用 Core 方法注册表中开放的方法。
func (h *hostClient) Invoke(ctx context.Context, req sdk.HostInvokeRequest) (*sdk.HostInvokeResponse, error) {
logger, start := hostRPCLogger(ctx, "Invoke")
+ payload, err := mapToJSONPayload(req.Payload)
+ if err != nil {
+ logger.Error("host_call_invoke_payload_encode_failed",
+ "method", req.Method,
+ sdk.LogFieldDurationMs, time.Since(start).Milliseconds(),
+ sdk.LogFieldError, err,
+ )
+ return nil, err
+ }
resp, err := h.c.Invoke(ctx, &pb.HostInvokeRequest{
Method: req.Method,
- Payload: mapToJSONPayload(req.Payload),
+ Payload: payload,
IdempotencyKey: req.IdempotencyKey,
Metadata: req.Metadata,
})
@@ -45,13 +54,22 @@ func (h *hostClient) Invoke(ctx context.Context, req sdk.HostInvokeRequest) (*sd
)
return nil, err
}
+ responsePayload, err := jsonPayloadToMap(resp.Payload)
+ if err != nil {
+ logger.Error("host_call_invoke_payload_decode_failed",
+ "method", req.Method,
+ sdk.LogFieldDurationMs, time.Since(start).Milliseconds(),
+ sdk.LogFieldError, err,
+ )
+ return nil, err
+ }
logger.Debug("host_call_invoke_completed",
"method", req.Method,
sdk.LogFieldDurationMs, time.Since(start).Milliseconds(),
)
return &sdk.HostInvokeResponse{
Status: resp.Status,
- Payload: jsonPayloadToMap(resp.Payload),
+ Payload: responsePayload,
Metadata: resp.Metadata,
}, nil
}
@@ -59,6 +77,15 @@ func (h *hostClient) Invoke(ctx context.Context, req sdk.HostInvokeRequest) (*sd
// InvokeStream 调用 Core 方法注册表中开放的流式方法。
func (h *hostClient) InvokeStream(ctx context.Context, req sdk.HostStreamRequest) (sdk.HostStream, error) {
logger, start := hostRPCLogger(ctx, "InvokeStream")
+ payload, err := mapToJSONPayload(req.Payload)
+ if err != nil {
+ logger.Error("host_call_invoke_stream_payload_encode_failed",
+ "method", req.Method,
+ sdk.LogFieldDurationMs, time.Since(start).Milliseconds(),
+ sdk.LogFieldError, err,
+ )
+ return nil, err
+ }
stream, err := h.c.InvokeStream(ctx)
if err != nil {
logger.Error("host_call_invoke_stream_open_failed",
@@ -70,7 +97,7 @@ func (h *hostClient) InvokeStream(ctx context.Context, req sdk.HostStreamRequest
}
if err := stream.Send(&pb.HostStreamFrame{
Method: req.Method,
- Payload: mapToJSONPayload(req.Payload),
+ Payload: payload,
IdempotencyKey: req.IdempotencyKey,
Metadata: req.Metadata,
}); err != nil {
@@ -94,10 +121,14 @@ type hostStream struct {
}
func (s *hostStream) Send(frame sdk.HostStreamFrame) error {
+ payload, err := mapToJSONPayload(frame.Payload)
+ if err != nil {
+ return err
+ }
return s.stream.Send(&pb.HostStreamFrame{
Event: frame.Event,
Status: frame.Status,
- Payload: mapToJSONPayload(frame.Payload),
+ Payload: payload,
Metadata: frame.Metadata,
Done: frame.Done,
})
@@ -108,10 +139,14 @@ func (s *hostStream) Recv() (*sdk.HostStreamFrame, error) {
if err != nil {
return nil, err
}
+ payload, err := jsonPayloadToMap(frame.Payload)
+ if err != nil {
+ return nil, err
+ }
return &sdk.HostStreamFrame{
Event: frame.Event,
Status: frame.Status,
- Payload: jsonPayloadToMap(frame.Payload),
+ Payload: payload,
Metadata: frame.Metadata,
Done: frame.Done,
}, nil
diff --git a/runtimego/grpc/host_client_test.go b/runtimego/grpc/host_client_test.go
index 437c78f..85303f6 100644
--- a/runtimego/grpc/host_client_test.go
+++ b/runtimego/grpc/host_client_test.go
@@ -14,10 +14,16 @@ import (
)
type stubCoreInvokeClient struct {
- stream *stubHostStreamClient
+ stream *stubHostStreamClient
+ invokeReq *pb.HostInvokeRequest
+ invokeResp *pb.HostInvokeResponse
}
-func (c *stubCoreInvokeClient) Invoke(context.Context, *pb.HostInvokeRequest, ...grpc.CallOption) (*pb.HostInvokeResponse, error) {
+func (c *stubCoreInvokeClient) Invoke(_ context.Context, req *pb.HostInvokeRequest, _ ...grpc.CallOption) (*pb.HostInvokeResponse, error) {
+ c.invokeReq = req
+ if c.invokeResp != nil {
+ return c.invokeResp, nil
+ }
return &pb.HostInvokeResponse{Status: "ok"}, nil
}
@@ -62,18 +68,36 @@ func (s *stubHostStreamClient) Context() context.Context {
func (s *stubHostStreamClient) SendMsg(any) error { return nil }
func (s *stubHostStreamClient) RecvMsg(any) error { return nil }
+func mustJSONPayload(t *testing.T, payload map[string]interface{}) []byte {
+ t.Helper()
+ data, err := mapToJSONPayload(payload)
+ if err != nil {
+ t.Fatalf("mapToJSONPayload() error = %v", err)
+ }
+ return data
+}
+
+func mustJSONPayloadMap(t *testing.T, data []byte) map[string]interface{} {
+ t.Helper()
+ payload, err := jsonPayloadToMap(data)
+ if err != nil {
+ t.Fatalf("jsonPayloadToMap() error = %v", err)
+ }
+ return payload
+}
+
func TestHostClientInvokeStreamRoundTrip(t *testing.T) {
grpcStream := &stubHostStreamClient{
recvFrames: []*pb.HostStreamFrame{
{
Event: "chunk",
- Payload: mapToJSONPayload(map[string]interface{}{"delta": "hello"}),
+ Payload: mustJSONPayload(t, map[string]interface{}{"delta": "hello"}),
Metadata: map[string]string{"seq": "1"},
},
{
Event: "result",
Status: "ok",
- Payload: mapToJSONPayload(map[string]interface{}{"done": true}),
+ Payload: mustJSONPayload(t, map[string]interface{}{"done": true}),
Done: true,
},
},
@@ -96,7 +120,7 @@ func TestHostClientInvokeStreamRoundTrip(t *testing.T) {
if start.Method != "chat.stream" || start.IdempotencyKey != "idem_1" {
t.Fatalf("首帧 method/idempotency = %q/%q", start.Method, start.IdempotencyKey)
}
- if got := jsonPayloadToMap(start.Payload); !reflect.DeepEqual(got, map[string]interface{}{"prompt": "hi"}) {
+ if got := mustJSONPayloadMap(t, start.Payload); !reflect.DeepEqual(got, map[string]interface{}{"prompt": "hi"}) {
t.Fatalf("首帧 payload = %v", got)
}
if !reflect.DeepEqual(start.Metadata, map[string]string{"trace": "abc"}) {
@@ -117,7 +141,7 @@ func TestHostClientInvokeStreamRoundTrip(t *testing.T) {
if ack.Method != "" || ack.Event != "client_ack" {
t.Fatalf("后续帧 method/event = %q/%q", ack.Method, ack.Event)
}
- if got := jsonPayloadToMap(ack.Payload); !reflect.DeepEqual(got, map[string]interface{}{"received": float64(1)}) {
+ if got := mustJSONPayloadMap(t, ack.Payload); !reflect.DeepEqual(got, map[string]interface{}{"received": float64(1)}) {
t.Fatalf("后续帧 payload = %v", got)
}
@@ -144,3 +168,76 @@ func TestHostClientInvokeStreamRoundTrip(t *testing.T) {
t.Fatal("底层 stream 未关闭发送方向")
}
}
+
+func TestHostClientInvokeRejectsInvalidPayload(t *testing.T) {
+ client := &stubCoreInvokeClient{}
+ host := NewHostClient(client)
+
+ _, err := host.Invoke(context.Background(), sdk.HostInvokeRequest{
+ Method: "tasks.update",
+ Payload: map[string]interface{}{"bad": func() {}},
+ })
+ if err == nil {
+ t.Fatal("Invoke() 应拒绝不可 JSON 编码的 payload")
+ }
+ if client.invokeReq != nil {
+ t.Fatal("payload 编码失败后不应发起 gRPC 调用")
+ }
+}
+
+func TestHostClientInvokeRejectsMalformedResponsePayload(t *testing.T) {
+ host := NewHostClient(&stubCoreInvokeClient{
+ invokeResp: &pb.HostInvokeResponse{
+ Status: "ok",
+ Payload: []byte("{bad"),
+ },
+ })
+
+ _, err := host.Invoke(context.Background(), sdk.HostInvokeRequest{Method: "tasks.get"})
+ if err == nil {
+ t.Fatal("Invoke() 应拒绝 Core 返回的非法 JSON payload")
+ }
+}
+
+func TestHostClientInvokeStreamRejectsInvalidInitialPayload(t *testing.T) {
+ grpcStream := &stubHostStreamClient{}
+ host := NewHostClient(&stubCoreInvokeClient{stream: grpcStream})
+
+ _, err := host.InvokeStream(context.Background(), sdk.HostStreamRequest{
+ Method: "chat.stream",
+ Payload: map[string]interface{}{"bad": func() {}},
+ })
+ if err == nil {
+ t.Fatal("InvokeStream() 应拒绝不可 JSON 编码的首帧 payload")
+ }
+ if len(grpcStream.sentFrames) != 0 {
+ t.Fatalf("payload 编码失败后发送帧数量 = %d,期望 0", len(grpcStream.sentFrames))
+ }
+}
+
+func TestHostStreamSendRejectsInvalidPayload(t *testing.T) {
+ grpcStream := &stubHostStreamClient{}
+ stream := &hostStream{stream: grpcStream}
+
+ err := stream.Send(sdk.HostStreamFrame{
+ Event: "client_chunk",
+ Payload: map[string]interface{}{"bad": func() {}},
+ })
+ if err == nil {
+ t.Fatal("Send() 应拒绝不可 JSON 编码的 payload")
+ }
+ if len(grpcStream.sentFrames) != 0 {
+ t.Fatalf("payload 编码失败后发送帧数量 = %d,期望 0", len(grpcStream.sentFrames))
+ }
+}
+
+func TestHostStreamRecvRejectsMalformedPayload(t *testing.T) {
+ stream := &hostStream{stream: &stubHostStreamClient{
+ recvFrames: []*pb.HostStreamFrame{{Event: "chunk", Payload: []byte("{bad")}},
+ }}
+
+ _, err := stream.Recv()
+ if err == nil {
+ t.Fatal("Recv() 应拒绝 Core 返回的非法 JSON payload")
+ }
+}
diff --git a/runtimego/grpc/json_payload.go b/runtimego/grpc/json_payload.go
index 2497e55..c3ad6a0 100644
--- a/runtimego/grpc/json_payload.go
+++ b/runtimego/grpc/json_payload.go
@@ -1,22 +1,28 @@
package grpc
-import "encoding/json"
+import (
+ "encoding/json"
+ "fmt"
+)
-func mapToJSONPayload(m map[string]interface{}) []byte {
+func mapToJSONPayload(m map[string]interface{}) ([]byte, error) {
if len(m) == 0 {
- return nil
+ return nil, nil
}
- data, _ := json.Marshal(m)
- return data
+ data, err := json.Marshal(m)
+ if err != nil {
+ return nil, fmt.Errorf("JSON payload 编码失败: %w", err)
+ }
+ return data, nil
}
-func jsonPayloadToMap(data []byte) map[string]interface{} {
+func jsonPayloadToMap(data []byte) (map[string]interface{}, error) {
if len(data) == 0 {
- return nil
+ return nil, nil
}
var out map[string]interface{}
if err := json.Unmarshal(data, &out); err != nil {
- return nil
+ return nil, fmt.Errorf("JSON payload 解码失败: %w", err)
}
- return out
+ return out, nil
}
From ff19fc6d0dba609f76c4578e91865fd386bd782c Mon Sep 17 00:00:00 2001
From: huangenjun <1021217094@qq.com>
Date: Tue, 12 May 2026 14:31:28 +0800
Subject: [PATCH 04/11] =?UTF-8?q?=E9=87=8D=E6=9E=84=20SDK=20=E4=B8=BB?=
=?UTF-8?q?=E9=A2=98=E5=8C=85=E4=B8=8E=E6=8F=92=E4=BB=B6=E5=A5=91=E7=BA=A6?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.github/workflows/ci.yml | 14 +-
.github/workflows/publish-theme.yml | 41 +++
.gitignore | 5 +-
Makefile | 18 +-
README.md | 35 +-
devkit/devserver/proxy.go | 71 +++-
devkit/devserver/proxy_test.go | 115 +++++++
devkit/devserver/server.go | 74 +++-
devkit/devserver/server_test.go | 44 ++-
docs/plugin-style-guide.md | 22 +-
docs/sdk-package-boundaries.md | 10 +-
runtimego/grpc/context.go | 36 +-
runtimego/grpc/context_test.go | 11 +
sdkgo/plugin.go | 2 +-
theme/dist/css.d.ts | 77 +++++
theme/dist/css.js | 191 +++++++++++
theme/dist/helpers.d.ts | 20 ++
theme/dist/helpers.js | 37 ++
theme/dist/index.d.ts | 7 +
theme/dist/index.js | 7 +
theme/dist/plugin.d.ts | 224 ++++++++++++
theme/dist/plugin.js | 467 ++++++++++++++++++++++++++
theme/dist/tailwind.d.ts | 54 +++
theme/dist/tailwind.js | 4 +
theme/dist/tokens.d.ts | 23 ++
theme/dist/tokens.js | 172 ++++++++++
theme/dist/types.d.ts | 108 ++++++
theme/dist/types.js | 1 +
{frontend => theme}/package-lock.json | 9 +-
{frontend => theme}/package.json | 16 +-
{frontend => theme}/src/css.ts | 0
{frontend => theme}/src/helpers.ts | 0
{frontend => theme}/src/index.ts | 0
{frontend => theme}/src/plugin.tsx | 0
{frontend => theme}/src/tailwind.ts | 0
{frontend => theme}/src/tokens.ts | 0
{frontend => theme}/src/types.ts | 0
{frontend => theme}/tsconfig.json | 0
38 files changed, 1839 insertions(+), 76 deletions(-)
create mode 100644 .github/workflows/publish-theme.yml
create mode 100644 devkit/devserver/proxy_test.go
create mode 100644 theme/dist/css.d.ts
create mode 100644 theme/dist/css.js
create mode 100644 theme/dist/helpers.d.ts
create mode 100644 theme/dist/helpers.js
create mode 100644 theme/dist/index.d.ts
create mode 100644 theme/dist/index.js
create mode 100644 theme/dist/plugin.d.ts
create mode 100644 theme/dist/plugin.js
create mode 100644 theme/dist/tailwind.d.ts
create mode 100644 theme/dist/tailwind.js
create mode 100644 theme/dist/tokens.d.ts
create mode 100644 theme/dist/tokens.js
create mode 100644 theme/dist/types.d.ts
create mode 100644 theme/dist/types.js
rename {frontend => theme}/package-lock.json (91%)
rename {frontend => theme}/package.json (68%)
rename {frontend => theme}/src/css.ts (100%)
rename {frontend => theme}/src/helpers.ts (100%)
rename {frontend => theme}/src/index.ts (100%)
rename {frontend => theme}/src/plugin.tsx (100%)
rename {frontend => theme}/src/tailwind.ts (100%)
rename {frontend => theme}/src/tokens.ts (100%)
rename {frontend => theme}/src/types.ts (100%)
rename {frontend => theme}/tsconfig.json (100%)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 5dc8ce7..8c49748 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,4 +1,4 @@
-name: CI
+name: 持续集成
on:
push:
@@ -8,7 +8,7 @@ on:
jobs:
ci:
- name: Lint & Test & Build
+ name: 检查、测试和构建
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
@@ -21,14 +21,14 @@ jobs:
with:
node-version: 22
cache: npm
- cache-dependency-path: frontend/package-lock.json
+ cache-dependency-path: theme/package-lock.json
- - name: Install frontend dependencies
- working-directory: frontend
+ - name: 安装主题包依赖
+ working-directory: theme
run: npm ci
- - name: Install golangci-lint
+ - name: 安装 golangci-lint
run: go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest
- - name: Run CI checks
+ - name: 运行 CI 检查
run: make ci
diff --git a/.github/workflows/publish-theme.yml b/.github/workflows/publish-theme.yml
new file mode 100644
index 0000000..cb43644
--- /dev/null
+++ b/.github/workflows/publish-theme.yml
@@ -0,0 +1,41 @@
+name: 发布 npm 公共包
+
+on:
+ release:
+ types: [published]
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+jobs:
+ publish:
+ name: 发布 @doudou-start/airgate-theme
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v5
+
+ - uses: actions/setup-node@v6
+ with:
+ node-version: 22
+ registry-url: https://registry.npmjs.org
+ cache: npm
+ cache-dependency-path: theme/package-lock.json
+
+ - name: 安装依赖
+ working-directory: theme
+ run: npm ci
+
+ - name: 构建包
+ working-directory: theme
+ run: npm run build
+
+ - name: 检查包内容
+ working-directory: theme
+ run: npm pack --dry-run
+
+ - name: 发布包
+ working-directory: theme
+ run: npm publish --access public
+ env:
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
diff --git a/.gitignore b/.gitignore
index da07a7d..310ada1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,6 +4,5 @@ coverage.out
# 本地工具链
.tools/
-# 前端依赖和构建产物
-frontend/node_modules/
-frontend/dist/
+# 主题包依赖
+theme/node_modules/
diff --git a/Makefile b/Makefile
index 3bd10c3..2c902f0 100644
--- a/Makefile
+++ b/Makefile
@@ -11,14 +11,14 @@ PROTOC_GEN_GRPC_VER := v1.6.0
TOOLS_DIR := $(CURDIR)/.tools
PROTOC_BIN := $(TOOLS_DIR)/bin/protoc
-.PHONY: help ci pre-commit lint fmt test vet build frontend-build theme theme-check proto proto-check proto-tools clean setup-hooks
+.PHONY: help ci pre-commit lint fmt test vet build theme-package-build theme-package-check theme theme-check proto proto-check proto-tools clean setup-hooks
help: ## 显示帮助信息
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-18s\033[0m %s\n", $$1, $$2}'
# ===================== 质量检查 =====================
-ci: lint test vet build proto-check frontend-build theme-check ## 本地运行与 CI 完全一致的检查
+ci: lint test vet build proto-check theme-package-check theme-check ## 本地运行与 CI 完全一致的检查
pre-commit: lint vet build ## pre-commit hook 调用(跳过耗时的 race 测试)
@@ -49,13 +49,17 @@ vet: ## 静态分析
build: ## 编译检查
$(GO) build ./...
-# ===================== 前端主题 =====================
+# ===================== 主题包 =====================
-frontend-build: ## 构建前端主题包
- cd frontend && npm run build
+theme-package-build: ## 构建 AirGate 主题包
+ cd theme && npm run build
-theme: frontend-build ## 构建前端主题包并生成 DevServer 用 theme.css
- node --input-type=module -e "import{generateThemeCSS}from'./frontend/dist/css.js';process.stdout.write(generateThemeCSS() + '\n')" > devkit/devserver/static/theme.css
+theme-package-check: theme-package-build ## 校验 AirGate 主题包构建产物无漂移
+ @git diff --exit-code -- theme/dist
+ @echo "AirGate 主题包构建产物无漂移"
+
+theme: theme-package-build ## 构建 AirGate 主题包并生成 DevServer 用 theme.css
+ node --input-type=module -e "import{generateThemeCSS}from'./theme/dist/css.js';process.stdout.write(generateThemeCSS() + '\n')" > devkit/devserver/static/theme.css
@echo "theme.css 已生成"
# ===================== 代码生成 =====================
diff --git a/README.md b/README.md
index 80b962f..03251d0 100644
--- a/README.md
+++ b/README.md
@@ -37,12 +37,22 @@ import (
)
```
-前端插件使用:
+前端插件正式使用 npm 公共包:
```json
{
"dependencies": {
- "@airgate/theme": "file:../../airgate-sdk/frontend"
+ "@doudou-start/airgate-theme": "^1.0.0"
+ }
+}
+```
+
+本地联调 SDK 源码时可以临时改为:
+
+```json
+{
+ "dependencies": {
+ "@doudou-start/airgate-theme": "file:../../airgate-sdk/theme"
}
}
```
@@ -55,7 +65,7 @@ import (
| `protocol/proto/` | `airgate.plugin.v2` protobuf 协议和生成代码 | Core / runtime |
| `runtimego/grpc/` | hashicorp/go-plugin、gRPC bridge、proto 转换、Core 反向调用通道 | 插件入口 / Core 加载器 |
| `devkit/devserver/` | 本地开发服务器,无需启动完整 Core 即可调试插件 | 插件作者 |
-| `frontend/` | `@airgate/theme`:主题 token、样式隔离、Tailwind helper、公共组件 | 插件前端 |
+| `theme/` | `@doudou-start/airgate-theme`:主题 token、样式隔离、Tailwind helper、公共组件 | 插件前端 |
| `docs/` | 设计边界和前端样式规范 | 维护者 |
普通插件业务代码不直接依赖 `protocol/proto`。
@@ -137,7 +147,7 @@ func (g *Gateway) Forward(ctx context.Context, req *sdk.ForwardRequest) (sdk.For
// 这里请求真实上游;示例用固定响应代替。
body := []byte(`{"id":"demo","choices":[]}`)
headers := http.Header{"Content-Type": []string{"application/json"}}
- if req.Writer != nil {
+ if req.Stream && req.Writer != nil {
for key, values := range headers {
for _, value := range values {
req.Writer.Header().Add(key, value)
@@ -321,14 +331,14 @@ func (p *Plugin) Init(ctx sdk.PluginContext) error {
## 前端插件 SDK
-`frontend/` 发布为 `@airgate/theme`,用于插件前端复用 AirGate 的主题和公共组件。
+`theme/` 发布为 npm 公共包 `@doudou-start/airgate-theme`,用于插件前端复用 AirGate 的主题和公共组件。
| 入口 | 用途 |
|---|---|
-| `@airgate/theme` | token、CSS 工具、Tailwind bridge、插件前端类型和公共组件统一出口 |
-| `@airgate/theme/plugin` | 插件样式隔离、主题同步、Tailwind helper、公共 UI 组件 |
-| `@airgate/theme/css` | CSS 变量生成和运行时主题注入 |
-| `@airgate/theme/tailwind` | Tailwind 主题桥接 |
+| `@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 主题桥接 |
推荐插件前端使用:
@@ -340,13 +350,14 @@ import {
createPluginTailwindConfig,
ensurePluginStyleFoundation,
useScopedPluginTheme,
-} from "@airgate/theme/plugin";
+} from "@doudou-start/airgate-theme/plugin";
```
完整样式规则见 [插件前端样式规范](docs/plugin-style-guide.md)。
网关插件账号相关 UI 建议使用这些稳定插槽。Core 仍提供通用账号列表和详情框架,
-插件只补平台差异片段;需要完整独立页面时使用 `FrontendPages`。
+插件只补平台差异片段;需要完整独立页面时使用 `FrontendPages`。devserver
+可直接预览 `account-create` / `account-edit`,其他插槽由 Core 对应页面加载。
| Slot | 用途 |
|---|---|
@@ -363,7 +374,7 @@ import {
make ci # 运行 Go、proto、前端和主题漂移检查
make proto # 重新生成 protocol/proto
make theme # 重新生成 DevServer 主题 CSS
-cd frontend && npm run build # 构建 @airgate/theme
+cd theme && npm run build # 构建 @doudou-start/airgate-theme
```
## License
diff --git a/devkit/devserver/proxy.go b/devkit/devserver/proxy.go
index 1516a78..7bbf369 100644
--- a/devkit/devserver/proxy.go
+++ b/devkit/devserver/proxy.go
@@ -74,6 +74,13 @@ func (p *ProxyHandler) handleHTTP(w http.ResponseWriter, r *http.Request) {
}
tried[account.ID] = true
+ var bufferedWriter *devBufferWriter
+ responseWriter := w
+ if !stream {
+ bufferedWriter = &devBufferWriter{}
+ responseWriter = bufferedWriter
+ }
+
fwdReq := &sdk.ForwardRequest{
Account: &sdk.Account{
ID: account.ID,
@@ -83,7 +90,7 @@ func (p *ProxyHandler) handleHTTP(w http.ResponseWriter, r *http.Request) {
Body: body,
Headers: headers.Clone(),
Stream: stream,
- Writer: w,
+ Writer: responseWriter,
}
outcome, fwdErr := p.plugin.Forward(r.Context(), fwdReq)
@@ -102,6 +109,10 @@ func (p *ProxyHandler) handleHTTP(w http.ResponseWriter, r *http.Request) {
log.Printf("Forward 失败: %v", fwdErr)
if outcome.Upstream.StatusCode == 0 {
http.Error(w, `{"error":"`+fwdErr.Error()+`"}`, http.StatusBadGateway)
+ return
+ }
+ if !stream {
+ writeForwardOutcome(w, outcome, bufferedWriter)
}
return
}
@@ -117,6 +128,9 @@ func (p *ProxyHandler) handleHTTP(w http.ResponseWriter, r *http.Request) {
log.Printf("Forward 完成: kind=%s status=%d model=%s account_cost=%.6f%s usage=%s duration=%s account=%d(%s)",
outcome.Kind, outcome.Upstream.StatusCode, model, accountCost, currency, usageSummary, outcome.Duration,
account.ID, account.Name)
+ if !stream {
+ writeForwardOutcome(w, outcome, bufferedWriter)
+ }
return
}
@@ -174,6 +188,61 @@ func (p *ProxyHandler) selectAccount() *DevAccount {
return p.store.First()
}
+type devBufferWriter struct {
+ headers http.Header
+ code int
+ body []byte
+}
+
+func (w *devBufferWriter) Header() http.Header {
+ if w.headers == nil {
+ w.headers = make(http.Header)
+ }
+ return w.headers
+}
+
+func (w *devBufferWriter) Write(data []byte) (int, error) {
+ w.body = append(w.body, data...)
+ return len(data), nil
+}
+
+func (w *devBufferWriter) WriteHeader(statusCode int) {
+ w.code = statusCode
+}
+
+func writeForwardOutcome(w http.ResponseWriter, outcome sdk.ForwardOutcome, buffered *devBufferWriter) {
+ statusCode := outcome.Upstream.StatusCode
+ headers := outcome.Upstream.Headers
+ body := outcome.Upstream.Body
+
+ if buffered != nil {
+ if statusCode == 0 && buffered.code > 0 {
+ statusCode = buffered.code
+ }
+ if len(headers) == 0 {
+ headers = buffered.Header()
+ }
+ if len(body) == 0 && len(buffered.body) > 0 {
+ body = buffered.body
+ }
+ }
+ if statusCode == 0 {
+ statusCode = http.StatusOK
+ }
+ for key, values := range headers {
+ for _, value := range values {
+ w.Header().Add(key, value)
+ }
+ }
+ w.WriteHeader(statusCode)
+ if len(body) == 0 {
+ return
+ }
+ if _, err := w.Write(body); err != nil {
+ log.Printf("写入 Forward 响应失败: %v", err)
+ }
+}
+
// devWebSocketConn 包装 gorilla/websocket.Conn 为 sdk.WebSocketConn
type devWebSocketConn struct {
conn *websocket.Conn
diff --git a/devkit/devserver/proxy_test.go b/devkit/devserver/proxy_test.go
new file mode 100644
index 0000000..11f9e96
--- /dev/null
+++ b/devkit/devserver/proxy_test.go
@@ -0,0 +1,115 @@
+package devserver
+
+import (
+ "context"
+ "net/http"
+ "net/http/httptest"
+ "path/filepath"
+ "testing"
+
+ sdk "github.com/DouDOU-start/airgate-sdk/sdkgo"
+)
+
+type proxyTestGateway struct {
+ outcome sdk.ForwardOutcome
+ write bool
+}
+
+func (g *proxyTestGateway) Info() sdk.PluginInfo {
+ return sdk.PluginInfo{
+ ID: "proxy-test",
+ Name: "Proxy Test",
+ Version: "0.1.0",
+ SDKVersion: sdk.SDKVersion,
+ Type: sdk.PluginTypeGateway,
+ }
+}
+
+func (g *proxyTestGateway) Init(sdk.PluginContext) error { return nil }
+func (g *proxyTestGateway) Start(context.Context) error { return nil }
+func (g *proxyTestGateway) Stop(context.Context) error { return nil }
+func (g *proxyTestGateway) Platform() string { return "test" }
+func (g *proxyTestGateway) Models() []sdk.ModelInfo { return nil }
+func (g *proxyTestGateway) Routes() []sdk.RouteDefinition { return nil }
+func (g *proxyTestGateway) ValidateAccount(context.Context, map[string]string) error {
+ return nil
+}
+func (g *proxyTestGateway) HandleWebSocket(context.Context, sdk.WebSocketConn) (sdk.ForwardOutcome, error) {
+ return sdk.ForwardOutcome{}, sdk.ErrNotSupported
+}
+
+func (g *proxyTestGateway) Forward(_ context.Context, req *sdk.ForwardRequest) (sdk.ForwardOutcome, error) {
+ if g.write && req.Writer != nil {
+ req.Writer.Header().Set("X-Buffered", "yes")
+ req.Writer.WriteHeader(http.StatusCreated)
+ _, _ = req.Writer.Write([]byte("buffered"))
+ }
+ return g.outcome, nil
+}
+
+func newProxyTestStore(t *testing.T) *AccountStore {
+ t.Helper()
+ store := NewAccountStore(filepath.Join(t.TempDir(), "accounts.json"))
+ store.Create(DevAccount{
+ Name: "test",
+ AccountType: "apikey",
+ Credentials: map[string]string{"api_key": "sk-test"},
+ })
+ return store
+}
+
+func TestProxyWritesForwardOutcomeForNonStream(t *testing.T) {
+ t.Parallel()
+
+ handler := &ProxyHandler{
+ plugin: &proxyTestGateway{outcome: sdk.ForwardOutcome{
+ Kind: sdk.OutcomeSuccess,
+ Upstream: sdk.UpstreamResponse{
+ StatusCode: http.StatusAccepted,
+ Headers: http.Header{"X-Outcome": []string{"yes"}},
+ Body: []byte("from outcome"),
+ },
+ }},
+ store: newProxyTestStore(t),
+ }
+
+ rec := httptest.NewRecorder()
+ req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", nil)
+ handler.ServeHTTP(rec, req)
+
+ if rec.Code != http.StatusAccepted {
+ t.Fatalf("status = %d,期望 %d", rec.Code, http.StatusAccepted)
+ }
+ if rec.Header().Get("X-Outcome") != "yes" {
+ t.Fatalf("X-Outcome = %q,期望 yes", rec.Header().Get("X-Outcome"))
+ }
+ if rec.Body.String() != "from outcome" {
+ t.Fatalf("body = %q,期望 from outcome", rec.Body.String())
+ }
+}
+
+func TestProxyCapturesBufferedWriterForNonStream(t *testing.T) {
+ t.Parallel()
+
+ handler := &ProxyHandler{
+ plugin: &proxyTestGateway{
+ write: true,
+ outcome: sdk.ForwardOutcome{Kind: sdk.OutcomeSuccess},
+ },
+ store: newProxyTestStore(t),
+ }
+
+ rec := httptest.NewRecorder()
+ req := httptest.NewRequest(http.MethodPost, "/v1/chat/completions", nil)
+ handler.ServeHTTP(rec, req)
+
+ if rec.Code != http.StatusCreated {
+ t.Fatalf("status = %d,期望 %d", rec.Code, http.StatusCreated)
+ }
+ if rec.Header().Get("X-Buffered") != "yes" {
+ t.Fatalf("X-Buffered = %q,期望 yes", rec.Header().Get("X-Buffered"))
+ }
+ if rec.Body.String() != "buffered" {
+ t.Fatalf("body = %q,期望 buffered", rec.Body.String())
+ }
+}
diff --git a/devkit/devserver/server.go b/devkit/devserver/server.go
index 570e046..f41ea30 100644
--- a/devkit/devserver/server.go
+++ b/devkit/devserver/server.go
@@ -10,6 +10,7 @@ import (
"log/slog"
"net/http"
"os"
+ "path"
"path/filepath"
"strconv"
"strings"
@@ -143,7 +144,12 @@ func Run(cfg Config) error {
// 从 plugin.Routes() 提取路径前缀注册代理
proxy := &ProxyHandler{plugin: cfg.Plugin, store: store, scheduler: scheduler}
prefixes := routePrefixes(cfg.Plugin.Routes())
+ hasRootProxy := false
for _, prefix := range prefixes {
+ if prefix == "/" {
+ hasRootProxy = true
+ continue
+ }
mux.Handle(prefix, proxy)
}
@@ -175,7 +181,16 @@ func Run(cfg Config) error {
if err != nil {
return err
}
- mux.Handle("/", http.FileServer(http.FS(staticFS)))
+ staticHandler := http.FileServer(http.FS(staticFS))
+ if hasRootProxy {
+ mux.Handle("/", &rootRouteHandler{
+ proxy: proxy,
+ static: staticHandler,
+ routes: cfg.Plugin.Routes(),
+ })
+ } else {
+ mux.Handle("/", staticHandler)
+ }
info := cfg.Plugin.Info()
log.Printf("devserver 启动: http://localhost%s", *addr)
@@ -185,14 +200,15 @@ func Run(cfg Config) error {
return http.ListenAndServe(*addr, mux)
}
-// routePrefixes 从路由声明中提取不重复的路径前缀(如 /v1/)
+// routePrefixes 从路由声明中提取不重复的路径前缀(如 /v1/)。
func routePrefixes(routes []sdk.RouteDefinition) []string {
seen := make(map[string]bool)
prefixes := make([]string, 0, len(routes))
for _, r := range routes {
- // 取第二个 / 之前的部分作为前缀
- parts := strings.SplitN(strings.TrimPrefix(r.Path, "/"), "/", 2)
- prefix := "/" + parts[0] + "/"
+ prefix, ok := routePrefix(r.Path)
+ if !ok {
+ continue
+ }
if !seen[prefix] {
seen[prefix] = true
prefixes = append(prefixes, prefix)
@@ -200,3 +216,51 @@ func routePrefixes(routes []sdk.RouteDefinition) []string {
}
return prefixes
}
+
+func routePrefix(routePath string) (string, bool) {
+ routePath = strings.TrimSpace(routePath)
+ if routePath == "" {
+ return "", false
+ }
+ if !strings.HasPrefix(routePath, "/") {
+ routePath = "/" + routePath
+ }
+ cleaned := path.Clean(routePath)
+ if cleaned == "/" {
+ return "/", true
+ }
+ parts := strings.SplitN(strings.TrimPrefix(cleaned, "/"), "/", 2)
+ return "/" + parts[0] + "/", true
+}
+
+type rootRouteHandler struct {
+ proxy http.Handler
+ static http.Handler
+ routes []sdk.RouteDefinition
+}
+
+func (h *rootRouteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ for _, route := range h.routes {
+ prefix, ok := routePrefix(route.Path)
+ if !ok || prefix != "/" {
+ continue
+ }
+ if path.Clean(r.URL.Path) != "/" {
+ continue
+ }
+ if !routeMethodMatches(route.Method, r.Method) {
+ continue
+ }
+ h.proxy.ServeHTTP(w, r)
+ return
+ }
+ h.static.ServeHTTP(w, r)
+}
+
+func routeMethodMatches(routeMethod, requestMethod string) bool {
+ routeMethod = strings.TrimSpace(routeMethod)
+ if routeMethod == "" {
+ return true
+ }
+ return strings.EqualFold(routeMethod, requestMethod)
+}
diff --git a/devkit/devserver/server_test.go b/devkit/devserver/server_test.go
index 0c352a9..0ad6019 100644
--- a/devkit/devserver/server_test.go
+++ b/devkit/devserver/server_test.go
@@ -1,6 +1,8 @@
package devserver
import (
+ "net/http"
+ "net/http/httptest"
"testing"
sdk "github.com/DouDOU-start/airgate-sdk/sdkgo"
@@ -14,8 +16,10 @@ func TestRoutePrefixesDeduplicates(t *testing.T) {
{Path: "/v1/models"},
{Path: "/oauth/callback"},
{Path: "/"},
+ {Path: "v2/messages"},
+ {Path: ""},
})
- want := []string{"/v1/", "/oauth/", "//"}
+ want := []string{"/v1/", "/oauth/", "/", "/v2/"}
if len(got) != len(want) {
t.Fatalf("routePrefixes length = %d, want %d (%v)", len(got), len(want), got)
}
@@ -25,3 +29,41 @@ func TestRoutePrefixesDeduplicates(t *testing.T) {
}
}
}
+
+func TestRootRouteHandlerOnlyProxiesRootMatch(t *testing.T) {
+ t.Parallel()
+
+ var proxied, servedStatic int
+ h := &rootRouteHandler{
+ routes: []sdk.RouteDefinition{{Method: http.MethodPost, Path: "/"}},
+ proxy: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
+ proxied++
+ w.WriteHeader(http.StatusAccepted)
+ }),
+ static: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
+ servedStatic++
+ w.WriteHeader(http.StatusOK)
+ }),
+ }
+
+ req := httptest.NewRequest(http.MethodGet, "/", nil)
+ rec := httptest.NewRecorder()
+ h.ServeHTTP(rec, req)
+ if rec.Code != http.StatusOK || servedStatic != 1 || proxied != 0 {
+ t.Fatalf("GET / 应走静态页面,code=%d static=%d proxy=%d", rec.Code, servedStatic, proxied)
+ }
+
+ req = httptest.NewRequest(http.MethodPost, "/", nil)
+ rec = httptest.NewRecorder()
+ h.ServeHTTP(rec, req)
+ if rec.Code != http.StatusAccepted || proxied != 1 {
+ t.Fatalf("POST / 应走插件代理,code=%d proxy=%d", rec.Code, proxied)
+ }
+
+ req = httptest.NewRequest(http.MethodPost, "/theme.css", nil)
+ rec = httptest.NewRecorder()
+ h.ServeHTTP(rec, req)
+ if rec.Code != http.StatusOK || servedStatic != 2 || proxied != 1 {
+ t.Fatalf("非根路径应走静态页面,code=%d static=%d proxy=%d", rec.Code, servedStatic, proxied)
+ }
+}
diff --git a/docs/plugin-style-guide.md b/docs/plugin-style-guide.md
index d551156..bc6bfec 100644
--- a/docs/plugin-style-guide.md
+++ b/docs/plugin-style-guide.md
@@ -27,7 +27,7 @@ your-plugin/
## 2. 依赖配置
-插件前端 SDK 的正式包名是 `@airgate/theme`。插件业务代码优先使用 `@airgate/theme/plugin`,该入口提供主题初始化、样式作用域、Tailwind bridge、插件前端类型和公共 UI 组件。
+插件前端 SDK 的正式包名是 `@doudou-start/airgate-theme`。插件业务代码优先使用 `@doudou-start/airgate-theme/plugin`,该入口提供主题初始化、样式作用域、Tailwind bridge、插件前端类型和公共 UI 组件。
### package.json
@@ -39,7 +39,7 @@ your-plugin/
"dev": "vite build --watch"
},
"dependencies": {
- "@airgate/theme": "file:../../airgate-sdk/frontend",
+ "@doudou-start/airgate-theme": "^1.0.0",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
@@ -55,7 +55,7 @@ your-plugin/
}
```
-> **注意**:`react` 和 `react-dom` 仅用于类型,运行时由 Core 通过 `window.__airgate_shared` 提供。
+> **注意**:`react` 和 `react-dom` 不打包进插件产物;运行时由 Core 或 devserver 通过 import map 或等价模块别名提供。
### vite.config.ts
@@ -74,7 +74,7 @@ export default defineConfig({
outDir: 'dist',
rollupOptions: {
// React 由 Core 提供,不要打包
- external: ['react', 'react-dom', 'react/jsx-runtime'],
+ external: ['react', 'react-dom', 'react-dom/client', 'react/jsx-runtime'],
},
},
});
@@ -84,7 +84,7 @@ export default defineConfig({
```ts
import type { Config } from 'tailwindcss';
-import { createPluginTailwindConfig } from '@airgate/theme/plugin';
+import { createPluginTailwindConfig } from '@doudou-start/airgate-theme/plugin';
const config: Config = {
content: ['./src/**/*.{ts,tsx}'],
@@ -118,7 +118,7 @@ module.exports = {
### theme/runtime.ts
```ts
-import { ensurePluginStyleFoundation } from '@airgate/theme/plugin';
+import { ensurePluginStyleFoundation } from '@doudou-start/airgate-theme/plugin';
import tailwindCssText from '../styles/tailwind.css?inline';
export const THEME_SCOPE_SELECTOR = '[data-ag-YOUR_PLUGIN-root]';
@@ -162,7 +162,7 @@ export default {
2. 使用 `useScopedPluginTheme` 跟随 Core 的明暗切换
```tsx
-import { useScopedPluginTheme } from '@airgate/theme/plugin';
+import { useScopedPluginTheme } from '@doudou-start/airgate-theme/plugin';
const THEME_ATTRIBUTE = 'data-theme';
const STORAGE_KEY = 'ag-YOUR_PLUGIN-theme';
@@ -187,7 +187,7 @@ export function YourComponent(props) {
## 5. 设计 Token 参考
-所有插件样式通过 CSS 变量 `--ag-*` 引用,Tailwind 工具类已映射好(带 `agw-` 前缀)。具体 token 值由 `frontend/src/tokens.ts` 生成,文档只说明语义,避免样式值漂移。
+所有插件样式通过 CSS 变量 `--ag-*` 引用,Tailwind 工具类已映射好(带 `agw-` 前缀)。具体 token 值由 `theme/src/tokens.ts` 生成,文档只说明语义,避免样式值漂移。
### 颜色
@@ -246,7 +246,7 @@ export function YourComponent(props) {
## 6. SDK 提供的 UI 组件
-SDK 提供了一套预制组件(`@airgate/theme/plugin`),样式与 Core 保持一致,属于插件前端稳定公共契约。插件业务 UI **优先使用这些组件**:
+SDK 提供了一套预制组件(`@doudou-start/airgate-theme/plugin`),样式与 Core 保持一致,属于插件前端稳定公共契约。插件业务 UI **优先使用这些组件**:
```tsx
import {
@@ -262,7 +262,7 @@ import {
FormActions, // 表单操作区(flex wrap)
Badge, // 标签徽章(neutral / success / violet / info)
StatusText, // 内联状态文字(info / success / error)
-} from '@airgate/theme/plugin';
+} from '@doudou-start/airgate-theme/plugin';
```
### 使用示例
@@ -377,7 +377,7 @@ npm run build
修改 SDK token 后的刷新流程:
```bash
-cd airgate-sdk/frontend && npm run build # 1. 编译 SDK
+cd airgate-sdk/theme && npm run build # 1. 编译 SDK
cd your-plugin/web && npm run build # 2. 重建插件(打包新 token)
# 3. 刷新浏览器
```
diff --git a/docs/sdk-package-boundaries.md b/docs/sdk-package-boundaries.md
index 5c14a9f..f5c02fb 100644
--- a/docs/sdk-package-boundaries.md
+++ b/docs/sdk-package-boundaries.md
@@ -10,7 +10,7 @@
| `protocol/proto` | protobuf schema 与生成代码 | protobuf runtime | 插件业务 helper、Core 实现细节 |
| `runtimego/grpc` | go-plugin/gRPC 适配、stream bridge、proto 转换、Core 反向调用 broker | `sdkgo`、`protocol/proto` | 插件业务逻辑、devserver UI |
| `devkit/devserver` | 本地开发服务器和 fake core 能力 | `sdkgo` | 生产运行时依赖、Core 数据库访问 |
-| `frontend` | 前端插件 API、主题 token、样式注入、Tailwind bridge、公共 UI 组件 | TypeScript 生态、React peer dependency | Go runtime、Core 后端逻辑、具体插件业务页面 |
+| `theme` | 前端插件 API、主题 token、样式注入、Tailwind bridge、公共 UI 组件 | TypeScript 生态、React peer dependency | Go runtime、Core 后端逻辑、具体插件业务页面 |
## 新需求判断
@@ -21,7 +21,7 @@
- 新增需要 SDK 表达的跨插件通用 capability。
- 修正已有公开契约的错误或缺口。
-只有以下变化可以修改 `frontend` 稳定接口:
+只有以下变化可以修改 `theme` 稳定接口:
- 新增跨插件通用的主题 token、CSS 变量或 Tailwind bridge 能力。
- 新增多个插件都会复用的基础 UI 组件。
@@ -39,7 +39,7 @@
这些变化应优先放到 manifest、插件私有 metadata、Core 方法注册表、插件私有数据库或明确 schema 的插件 API 中。
-前端页面需求应优先进入具体插件前端代码。只有当样式、组件或类型能被多个插件稳定复用时,才进入 `frontend`。
+前端页面需求应优先进入具体插件前端代码。只有当样式、组件或类型能被多个插件稳定复用时,才进入 `theme`。
## 弱契约扩展点
@@ -107,5 +107,5 @@ SDK 只提供传输契约和自检 helper,不承载 Core 方法枚举。这样
- 插件运行入口使用 `github.com/DouDOU-start/airgate-sdk/runtimego/grpc`。
- 本地开发工具使用 `github.com/DouDOU-start/airgate-sdk/devkit/devserver`。
- 普通插件业务代码不直接导入 `protocol/proto`。
-- 插件前端使用 `@airgate/theme/plugin` 引用样式隔离、主题同步、Tailwind helper 和公共 UI 组件。
-- 宿主前端或工具代码可使用 `@airgate/theme`、`@airgate/theme/css`、`@airgate/theme/tailwind` 引用 token 与主题桥接能力。
+- 插件前端使用 `@doudou-start/airgate-theme/plugin` 引用样式隔离、主题同步、Tailwind helper 和公共 UI 组件。
+- 宿主前端或工具代码可使用 `@doudou-start/airgate-theme`、`@doudou-start/airgate-theme/css`、`@doudou-start/airgate-theme/tailwind` 引用 token 与主题桥接能力。
diff --git a/runtimego/grpc/context.go b/runtimego/grpc/context.go
index 5a5238c..cadbd09 100644
--- a/runtimego/grpc/context.go
+++ b/runtimego/grpc/context.go
@@ -35,6 +35,23 @@ type grpcPluginContext struct {
hostErr error
}
+func (c *grpcPluginContext) initHost() {
+ if c.broker == nil {
+ c.hostErr = errors.New("host broker not available")
+ return
+ }
+ if c.coreInvokeBrokerID == 0 {
+ c.hostErr = errors.New("core invoke not enabled")
+ return
+ }
+ conn, err := c.broker.Dial(c.coreInvokeBrokerID)
+ if err != nil {
+ c.hostErr = err
+ return
+ }
+ c.host = NewHostClient(pb.NewCoreInvokeServiceClient(conn))
+}
+
func (c *grpcPluginContext) Logger() *slog.Logger {
if c.logger == nil {
return slog.Default()
@@ -65,22 +82,7 @@ func (c *grpcPluginContext) PluginDSN() string {
// - broker == nil(不在 plugin 进程内 / 测试 mock)
// - broker.Dial 失败(超时 / Core 进程退出)
func (c *grpcPluginContext) Host() sdk.Host {
- c.hostOnce.Do(func() {
- if c.broker == nil {
- c.hostErr = errors.New("host broker not available")
- return
- }
- if c.coreInvokeBrokerID == 0 {
- c.hostErr = errors.New("core invoke not enabled")
- return
- }
- conn, err := c.broker.Dial(c.coreInvokeBrokerID)
- if err != nil {
- c.hostErr = err
- return
- }
- c.host = NewHostClient(pb.NewCoreInvokeServiceClient(conn))
- })
+ c.hostOnce.Do(c.initHost)
return c.host
}
@@ -89,7 +91,7 @@ func (c *grpcPluginContext) Host() sdk.Host {
//
// if hc, ok := ctx.(interface{ HostError() error }); ok { ... }
func (c *grpcPluginContext) HostError() error {
- c.hostOnce.Do(func() {}) // 确保 Once 已经 fire 过
+ c.hostOnce.Do(c.initHost)
return c.hostErr
}
diff --git a/runtimego/grpc/context_test.go b/runtimego/grpc/context_test.go
index b347eda..731200d 100644
--- a/runtimego/grpc/context_test.go
+++ b/runtimego/grpc/context_test.go
@@ -167,3 +167,14 @@ func TestGetAll(t *testing.T) {
}
}
}
+
+func TestPluginContextHostErrorInitializesHostState(t *testing.T) {
+ ctx := &grpcPluginContext{coreInvokeBrokerID: 1}
+
+ if err := ctx.HostError(); err == nil {
+ t.Fatal("HostError() 应返回 Host 初始化失败原因")
+ }
+ if host := ctx.Host(); host != nil {
+ t.Fatalf("Host() = %T,期望 nil", host)
+ }
+}
diff --git a/sdkgo/plugin.go b/sdkgo/plugin.go
index e109c92..838c617 100644
--- a/sdkgo/plugin.go
+++ b/sdkgo/plugin.go
@@ -29,7 +29,7 @@ const (
)
// SDKVersion 当前 SDK 版本,插件编译时嵌入到 PluginInfo。
-const SDKVersion = "2.0.0"
+const SDKVersion = "1.0.0"
// PluginInfo 插件元信息。
type PluginInfo struct {
diff --git a/theme/dist/css.d.ts b/theme/dist/css.d.ts
new file mode 100644
index 0000000..b9559ee
--- /dev/null
+++ b/theme/dist/css.d.ts
@@ -0,0 +1,77 @@
+import type { AppShellTokens, CssVarOptions, FoundationTokens, StaticTokens, TailwindBridgeOptions, ThemeCSSOptions, ThemeInjectionOptions, ThemeName, ThemeSetOptions, ThemeStorageOptions, ThemeTokens } from './types.js';
+/** 主题 token → CSS 变量名映射 */
+export declare const tokenToCssVar: Record;
+/** 静态 token → CSS 变量名映射 */
+export declare const staticToCssVar: Record;
+/** 生成基础 token 的 CSS 变量名映射 */
+export declare function createFoundationCssVarMap(options?: CssVarOptions): Record;
+/** 生成应用壳层 token 的 CSS 变量名映射 */
+export declare function createAppShellCssVarMap(options?: CssVarOptions): Record;
+/** 生成主题 token 的 CSS 变量名映射 */
+export declare function createThemeCssVarMap(options?: CssVarOptions): Record;
+/** 生成静态 token 的 CSS 变量名映射 */
+export declare function createStaticCssVarMap(options?: CssVarOptions): Record;
+/**
+ * 生成完整的 CSS 变量定义字符串。
+ * 默认输出::root(静态)+ :root[data-theme="dark"] + :root[data-theme="light"]
+ * 也支持在局部容器下生成作用域主题。
+ */
+export declare function generateThemeCSS(options?: ThemeCSSOptions): string;
+/** 运行时注入主题 CSS 到 */
+export declare function injectThemeStyle(options?: ThemeInjectionOptions | string): void;
+/** 设置当前主题(data-theme 属性 + localStorage) */
+export declare function setTheme(theme: ThemeName, options?: ThemeSetOptions): void;
+/** 读取已保存的主题偏好,默认 dark */
+export declare function getStoredTheme(options?: ThemeStorageOptions): ThemeName;
+/** 生成 Tailwind 可消费的 theme bridge */
+export declare function createTailwindThemeBridge(options?: TailwindBridgeOptions): {
+ readonly colors: {
+ readonly primary: `var(${string})`;
+ readonly 'primary-hover': `var(${string})`;
+ readonly 'primary-subtle': `var(${string})`;
+ readonly success: `var(${string})`;
+ readonly 'success-subtle': `var(${string})`;
+ readonly warning: `var(${string})`;
+ readonly 'warning-subtle': `var(${string})`;
+ readonly danger: `var(${string})`;
+ readonly 'danger-subtle': `var(${string})`;
+ readonly info: `var(${string})`;
+ readonly 'info-subtle': `var(${string})`;
+ readonly bg: `var(${string})`;
+ readonly 'bg-deep': `var(${string})`;
+ readonly 'bg-elevated': `var(${string})`;
+ readonly surface: `var(${string})`;
+ readonly 'bg-hover': `var(${string})`;
+ readonly 'bg-active': `var(${string})`;
+ readonly border: `var(${string})`;
+ readonly 'border-subtle': `var(${string})`;
+ readonly 'border-focus': `var(${string})`;
+ readonly text: `var(${string})`;
+ readonly 'text-secondary': `var(${string})`;
+ readonly 'text-tertiary': `var(${string})`;
+ readonly 'text-inverse': `var(${string})`;
+ readonly glass: `var(${string})`;
+ readonly 'glass-border': `var(${string})`;
+ };
+ readonly borderRadius: {
+ readonly sm: `var(${string})`;
+ readonly md: `var(${string})`;
+ readonly lg: `var(${string})`;
+ readonly xl: `var(${string})`;
+ readonly field: `var(${string})`;
+ };
+ readonly fontFamily: {
+ readonly sans: `var(${string})`;
+ readonly mono: `var(${string})`;
+ };
+ readonly boxShadow: {
+ readonly sm: `var(${string})`;
+ readonly md: `var(${string})`;
+ readonly lg: `var(${string})`;
+ readonly glow: `var(${string})`;
+ };
+ readonly transitionDuration: {
+ readonly DEFAULT: `var(${string})`;
+ readonly slow: `var(${string})`;
+ };
+};
diff --git a/theme/dist/css.js b/theme/dist/css.js
new file mode 100644
index 0000000..00d0bba
--- /dev/null
+++ b/theme/dist/css.js
@@ -0,0 +1,191 @@
+import { appShellTokens, foundationTokens, themes, staticTokens, lightElevationContexts } from './tokens.js';
+/** camelCase → kebab-case */
+function toKebab(key) {
+ return key.replace(/[A-Z]/g, (m) => '-' + m.toLowerCase());
+}
+function resolvePrefix(prefix = 'ag') {
+ return prefix.trim() || 'ag';
+}
+function variableName(prefix, key) {
+ return `--${prefix}-${toKebab(key)}`;
+}
+function selectorForScope(scopeSelector = ':root', themeAttribute = 'data-theme', theme) {
+ if (!theme)
+ return scopeSelector;
+ if (scopeSelector === ':root') {
+ return `:root[${themeAttribute}="${theme}"]`;
+ }
+ return `${scopeSelector}[${themeAttribute}="${theme}"]`;
+}
+function varsBlock(values, prefix) {
+ return Object.entries(values)
+ .map(([key, value]) => ` ${variableName(prefix, key)}: ${value};`)
+ .join('\n');
+}
+/** 主题 token → CSS 变量名映射 */
+export const tokenToCssVar = Object.keys(themes.dark).reduce((acc, key) => {
+ acc[key] = variableName('ag', key);
+ return acc;
+}, {});
+/** 静态 token → CSS 变量名映射 */
+export const staticToCssVar = Object.keys(staticTokens).reduce((acc, key) => {
+ acc[key] = variableName('ag', key);
+ return acc;
+}, {});
+/** 生成基础 token 的 CSS 变量名映射 */
+export function createFoundationCssVarMap(options = {}) {
+ const prefix = resolvePrefix(options.prefix);
+ return Object.keys(foundationTokens).reduce((acc, key) => {
+ acc[key] = variableName(prefix, key);
+ return acc;
+ }, {});
+}
+/** 生成应用壳层 token 的 CSS 变量名映射 */
+export function createAppShellCssVarMap(options = {}) {
+ const prefix = resolvePrefix(options.prefix);
+ return Object.keys(appShellTokens).reduce((acc, key) => {
+ acc[key] = variableName(prefix, key);
+ return acc;
+ }, {});
+}
+/** 生成主题 token 的 CSS 变量名映射 */
+export function createThemeCssVarMap(options = {}) {
+ const prefix = resolvePrefix(options.prefix);
+ return Object.keys(themes.dark).reduce((acc, key) => {
+ acc[key] = variableName(prefix, key);
+ return acc;
+ }, {});
+}
+/** 生成静态 token 的 CSS 变量名映射 */
+export function createStaticCssVarMap(options = {}) {
+ const prefix = resolvePrefix(options.prefix);
+ return Object.keys(staticTokens).reduce((acc, key) => {
+ acc[key] = variableName(prefix, key);
+ return acc;
+ }, {});
+}
+/**
+ * 生成完整的 CSS 变量定义字符串。
+ * 默认输出::root(静态)+ :root[data-theme="dark"] + :root[data-theme="light"]
+ * 也支持在局部容器下生成作用域主题。
+ */
+export function generateThemeCSS(options = {}) {
+ const prefix = resolvePrefix(options.prefix);
+ const scopeSelector = options.scopeSelector || ':root';
+ const themeAttribute = options.themeAttribute || 'data-theme';
+ const blocks = [
+ `${selectorForScope(scopeSelector)} {\n${varsBlock(staticTokens, prefix)}\n}`,
+ `${selectorForScope(scopeSelector, themeAttribute, 'dark')} {\n${varsBlock(themes.dark, prefix)}\n}`,
+ `${selectorForScope(scopeSelector, themeAttribute, 'light')} {\n${varsBlock(themes.light, prefix)}\n}`,
+ ];
+ // Elevation context blocks (light theme only)
+ const lightSelector = selectorForScope(scopeSelector, themeAttribute, 'light');
+ for (const [ctx, overrides] of Object.entries(lightElevationContexts)) {
+ if (Object.keys(overrides).length === 0)
+ continue;
+ blocks.push(`${lightSelector} .ag-elevation-${ctx} {\n${varsBlock(overrides, prefix)}\n}`);
+ }
+ return blocks.join('\n\n');
+}
+/** 运行时注入主题 CSS 到 */
+export function injectThemeStyle(options = 'ag-theme-vars') {
+ if (typeof document === 'undefined')
+ return;
+ const resolvedOptions = typeof options === 'string'
+ ? { styleId: options }
+ : options;
+ const targetDocument = resolvedOptions.targetDocument || document;
+ const styleId = resolvedOptions.styleId || 'ag-theme-vars';
+ let el = targetDocument.getElementById(styleId);
+ if (!el) {
+ el = targetDocument.createElement('style');
+ el.id = styleId;
+ targetDocument.head.appendChild(el);
+ }
+ el.textContent = generateThemeCSS(resolvedOptions);
+}
+/** 设置当前主题(data-theme 属性 + localStorage) */
+export function setTheme(theme, options = {}) {
+ if (typeof document === 'undefined')
+ return;
+ const themeAttribute = options.themeAttribute || 'data-theme';
+ const target = options.target || document.documentElement;
+ target.setAttribute(themeAttribute, theme);
+ try {
+ if (typeof localStorage !== 'undefined') {
+ localStorage.setItem(options.storageKey || 'ag-theme', theme);
+ }
+ }
+ catch {
+ // Theme switching should keep working when storage is unavailable.
+ }
+}
+/** 读取已保存的主题偏好,默认 dark */
+export function getStoredTheme(options = {}) {
+ if (typeof localStorage === 'undefined')
+ return 'dark';
+ try {
+ return localStorage.getItem(options.storageKey || 'ag-theme') || 'dark';
+ }
+ catch {
+ return 'dark';
+ }
+}
+/** 生成 Tailwind 可消费的 theme bridge */
+export function createTailwindThemeBridge(options = {}) {
+ const prefix = resolvePrefix(options.prefix);
+ const themeVars = createThemeCssVarMap({ prefix });
+ const staticVars = createStaticCssVarMap({ prefix });
+ const foundationVars = createFoundationCssVarMap({ prefix });
+ return {
+ colors: {
+ primary: `var(${themeVars.primary})`,
+ 'primary-hover': `var(${themeVars.primaryHover})`,
+ 'primary-subtle': `var(${themeVars.primarySubtle})`,
+ success: `var(${themeVars.success})`,
+ 'success-subtle': `var(${themeVars.successSubtle})`,
+ warning: `var(${themeVars.warning})`,
+ 'warning-subtle': `var(${themeVars.warningSubtle})`,
+ danger: `var(${themeVars.danger})`,
+ 'danger-subtle': `var(${themeVars.dangerSubtle})`,
+ info: `var(${themeVars.info})`,
+ 'info-subtle': `var(${themeVars.infoSubtle})`,
+ bg: `var(${themeVars.bg})`,
+ 'bg-deep': `var(${themeVars.bgDeep})`,
+ 'bg-elevated': `var(${themeVars.bgElevated})`,
+ surface: `var(${themeVars.bgSurface})`,
+ 'bg-hover': `var(${themeVars.bgHover})`,
+ 'bg-active': `var(${themeVars.bgActive})`,
+ border: `var(${themeVars.border})`,
+ 'border-subtle': `var(${themeVars.borderSubtle})`,
+ 'border-focus': `var(${themeVars.borderFocus})`,
+ text: `var(${themeVars.text})`,
+ 'text-secondary': `var(${themeVars.textSecondary})`,
+ 'text-tertiary': `var(${themeVars.textTertiary})`,
+ 'text-inverse': `var(${themeVars.textInverse})`,
+ glass: `var(${themeVars.glass})`,
+ 'glass-border': `var(${themeVars.glassBorder})`,
+ },
+ borderRadius: {
+ sm: `var(${foundationVars.radiusSm})`,
+ md: `var(${foundationVars.radiusMd})`,
+ lg: `var(${foundationVars.radiusLg})`,
+ xl: `var(${foundationVars.radiusXl})`,
+ field: `var(${foundationVars.fieldRadius})`,
+ },
+ fontFamily: {
+ sans: `var(${staticVars.fontSans})`,
+ mono: `var(${staticVars.fontMono})`,
+ },
+ boxShadow: {
+ sm: `var(${themeVars.shadowSm})`,
+ md: `var(${themeVars.shadowMd})`,
+ lg: `var(${themeVars.shadowLg})`,
+ glow: `var(${themeVars.shadowGlow})`,
+ },
+ transitionDuration: {
+ DEFAULT: `var(${staticVars.transition})`,
+ slow: `var(${staticVars.transitionSlow})`,
+ },
+ };
+}
diff --git a/theme/dist/helpers.d.ts b/theme/dist/helpers.d.ts
new file mode 100644
index 0000000..18984e6
--- /dev/null
+++ b/theme/dist/helpers.d.ts
@@ -0,0 +1,20 @@
+import type { CssVarOptions, StaticTokens, ThemeTokens } from './types.js';
+/** 所有可用 token 名称 */
+export type TokenName = keyof ThemeTokens | keyof StaticTokens;
+/**
+ * 获取带默认值的 CSS var() 引用。
+ * 同时支持主题 token 和静态 token。
+ *
+ * @example
+ * cssVar('primary') // → 'var(--ag-primary, #3b82f6)'
+ * cssVar('bgSurface') // → 'var(--ag-bg-surface, #1c2237)'
+ * cssVar('fieldRadius') // → 'var(--ag-field-radius, 0.5rem)'
+ */
+export declare function cssVar(token: TokenName, options?: CssVarOptions): string;
+/**
+ * 批量生成 CSSProperties 对象。
+ *
+ * @example
+ * themeStyle({ color: 'text', backgroundColor: 'bgSurface', borderRadius: 'radiusMd' })
+ */
+export declare function themeStyle(mapping: Partial>, options?: CssVarOptions): Record;
diff --git a/theme/dist/helpers.js b/theme/dist/helpers.js
new file mode 100644
index 0000000..1652351
--- /dev/null
+++ b/theme/dist/helpers.js
@@ -0,0 +1,37 @@
+import { darkTheme, staticTokens } from './tokens.js';
+import { createStaticCssVarMap, createThemeCssVarMap } from './css.js';
+const defaultThemeCssVarMap = createThemeCssVarMap();
+const defaultStaticCssVarMap = createStaticCssVarMap();
+/**
+ * 获取带默认值的 CSS var() 引用。
+ * 同时支持主题 token 和静态 token。
+ *
+ * @example
+ * cssVar('primary') // → 'var(--ag-primary, #3b82f6)'
+ * cssVar('bgSurface') // → 'var(--ag-bg-surface, #1c2237)'
+ * cssVar('fieldRadius') // → 'var(--ag-field-radius, 0.5rem)'
+ */
+export function cssVar(token, options = {}) {
+ const themeCssVarMap = options.prefix ? createThemeCssVarMap(options) : defaultThemeCssVarMap;
+ const staticCssVarMap = options.prefix ? createStaticCssVarMap(options) : defaultStaticCssVarMap;
+ if (token in themeCssVarMap) {
+ const t = token;
+ return `var(${themeCssVarMap[t]}, ${darkTheme[t]})`;
+ }
+ const s = token;
+ return `var(${staticCssVarMap[s]}, ${staticTokens[s]})`;
+}
+/**
+ * 批量生成 CSSProperties 对象。
+ *
+ * @example
+ * themeStyle({ color: 'text', backgroundColor: 'bgSurface', borderRadius: 'radiusMd' })
+ */
+export function themeStyle(mapping, options = {}) {
+ const result = {};
+ for (const [cssProp, token] of Object.entries(mapping)) {
+ if (token)
+ result[cssProp] = cssVar(token, options);
+ }
+ return result;
+}
diff --git a/theme/dist/index.d.ts b/theme/dist/index.d.ts
new file mode 100644
index 0000000..ab4d1d6
--- /dev/null
+++ b/theme/dist/index.d.ts
@@ -0,0 +1,7 @@
+export type { AppShellTokens, CssVarOptions, ElevationContext, FoundationTokens, StaticTokenGroups, StaticTokens, TailwindBridgeOptions, ThemeCSSOptions, ThemeInjectionOptions, ThemeName, ThemeSetOptions, ThemeStorageOptions, ThemeTokens, } from './types.js';
+export { appShellTokens, darkTheme, foundationTokens, lightElevationContexts, lightTheme, staticTokenGroups, staticTokens, decorativePalette, themes, } from './tokens.js';
+export { createAppShellCssVarMap, createFoundationCssVarMap, createStaticCssVarMap, createTailwindThemeBridge, createThemeCssVarMap, generateThemeCSS, getStoredTheme, injectThemeStyle, setTheme, staticToCssVar, tokenToCssVar, } from './css.js';
+export type { TokenName } from './helpers.js';
+export { cssVar, themeStyle } from './helpers.js';
+export type { AccountSurfaceProps, AccountFormProps, BadgeProps, ButtonProps, ButtonVariant, CardProps, FieldProps, FormActionsProps, PluginBatchAccountInput, PluginBatchImportResult, PluginFrontendModule, PluginMenuItemDefinition, PluginOAuthBatchExchangeResult, PluginOAuthBridge, PluginOAuthExchangeResult, PluginOAuthStartResult, PanelHeaderProps, PluginPlatformIconProps, PluginStatusKind, PluginStyleFoundationOptions, PluginTailwindConfigOptions, PluginRouteDefinition, ResolvePluginThemeOptions, ScopedPluginThemeOptions, SectionProps, SelectableCardProps, StatusTextProps, UsageRecordSurfaceProps, } from './plugin.js';
+export { DEFAULT_PLUGIN_CLASS_PREFIX, DEFAULT_PLUGIN_FOUNDATION_STYLE_ID, DEFAULT_PLUGIN_THEME_ATTRIBUTE, DEFAULT_PLUGIN_THEME_STYLE_ID, Badge, Button, Card, Field, FormActions, PanelHeader, Section, SecretInput, SelectableCard, StatusText, TextInput, TextArea, cn, createPluginTailwindConfig, ensurePluginStyleFoundation, injectStyle, pluginFoundationCssText, resolvePluginTheme, useScopedPluginTheme, } from './plugin.js';
diff --git a/theme/dist/index.js b/theme/dist/index.js
new file mode 100644
index 0000000..b497dd1
--- /dev/null
+++ b/theme/dist/index.js
@@ -0,0 +1,7 @@
+// Token 常量
+export { appShellTokens, darkTheme, foundationTokens, lightElevationContexts, lightTheme, staticTokenGroups, staticTokens, decorativePalette, themes, } from './tokens.js';
+// CSS 生成与运行时
+export { createAppShellCssVarMap, createFoundationCssVarMap, createStaticCssVarMap, createTailwindThemeBridge, createThemeCssVarMap, generateThemeCSS, getStoredTheme, injectThemeStyle, setTheme, staticToCssVar, tokenToCssVar, } from './css.js';
+export { cssVar, themeStyle } from './helpers.js';
+// 插件前端 SDK:样式注入、主题同步、Tailwind bridge 和公共组件
+export { DEFAULT_PLUGIN_CLASS_PREFIX, DEFAULT_PLUGIN_FOUNDATION_STYLE_ID, DEFAULT_PLUGIN_THEME_ATTRIBUTE, DEFAULT_PLUGIN_THEME_STYLE_ID, Badge, Button, Card, Field, FormActions, PanelHeader, Section, SecretInput, SelectableCard, StatusText, TextInput, TextArea, cn, createPluginTailwindConfig, ensurePluginStyleFoundation, injectStyle, pluginFoundationCssText, resolvePluginTheme, useScopedPluginTheme, } from './plugin.js';
diff --git a/theme/dist/plugin.d.ts b/theme/dist/plugin.d.ts
new file mode 100644
index 0000000..abf202c
--- /dev/null
+++ b/theme/dist/plugin.d.ts
@@ -0,0 +1,224 @@
+import { type ButtonHTMLAttributes, type ComponentType, type CSSProperties, type InputHTMLAttributes, type ReactNode, type TextareaHTMLAttributes } from 'react';
+import type { ThemeName } from './types.js';
+export declare const DEFAULT_PLUGIN_THEME_ATTRIBUTE = "data-theme";
+export declare const DEFAULT_PLUGIN_CLASS_PREFIX = "agw-";
+export declare const DEFAULT_PLUGIN_THEME_STYLE_ID = "ag-plugin-theme-vars";
+export declare const DEFAULT_PLUGIN_FOUNDATION_STYLE_ID = "ag-plugin-foundation";
+export interface PluginStyleFoundationOptions {
+ scopeSelector: string;
+ themeAttribute?: string;
+ themeStyleId?: string;
+ foundationStyleId?: string;
+ extraCssText?: string;
+ extraStyleId?: string;
+ targetDocument?: Document;
+}
+export interface ResolvePluginThemeOptions {
+ storageKey?: string;
+}
+export interface ScopedPluginThemeOptions {
+ themeAttribute?: string;
+ storageKey?: string;
+}
+export interface PluginTailwindConfigOptions {
+ scopeSelector: string;
+ classPrefix?: string;
+ tokenPrefix?: string;
+}
+export type PluginStatusKind = 'info' | 'success' | 'error';
+export interface PluginOAuthStartResult {
+ authorizeURL: string;
+ state: string;
+}
+export interface PluginOAuthExchangeResult {
+ accountType: string;
+ accountName: string;
+ credentials: Record;
+}
+export interface PluginOAuthBatchExchangeResult {
+ accountType: string;
+ accountName: string;
+ credentials: Record;
+ status: 'ok' | 'failed';
+ error?: string;
+}
+export interface PluginOAuthBridge {
+ start: () => Promise;
+ exchange: (callbackURL: string) => Promise;
+ batchExchange?: (sessionKeys: string[]) => Promise;
+ importRefresh?: (refreshToken: string, clientId?: string) => Promise;
+ batchImportRefresh?: (refreshTokens: string[], clientId?: string) => Promise;
+}
+export interface PluginBatchAccountInput {
+ name: string;
+ type: string;
+ credentials: Record;
+}
+export interface PluginBatchImportResult {
+ imported: number;
+ failed: number;
+}
+export interface AccountFormProps {
+ credentials: Record;
+ onChange: (credentials: Record) => void;
+ mode: 'create' | 'edit';
+ accountType?: string;
+ onAccountTypeChange?: (type: string) => void;
+ onSuggestedName?: (name: string) => void;
+ onBatchModeChange?: (isBatch: boolean) => void;
+ onBatchImport?: (accounts: PluginBatchAccountInput[]) => Promise;
+ oauth?: PluginOAuthBridge;
+}
+export interface PluginRouteDefinition {
+ path: string;
+ component: ComponentType;
+}
+export interface PluginMenuItemDefinition {
+ path: string;
+ title: string;
+ icon: string;
+}
+export interface PluginPlatformIconProps {
+ className?: string;
+ style?: CSSProperties;
+}
+export interface AccountSurfaceProps {
+ accountId?: string | number;
+ accountType?: string;
+ context?: Record;
+}
+export interface UsageRecordSurfaceProps {
+ recordId?: string | number;
+ context?: Record;
+}
+export interface PluginFrontendModule {
+ routes?: PluginRouteDefinition[];
+ menuItems?: PluginMenuItemDefinition[];
+ accountIdentity?: ComponentType;
+ accountCreate?: ComponentType;
+ accountEdit?: ComponentType;
+ accountUsageWindow?: ComponentType;
+ usageMetricDetail?: ComponentType;
+ usageCostDetail?: ComponentType;
+ platformIcon?: ComponentType;
+}
+export declare const pluginFoundationCssText = "\n/* \u2500\u2500 AirGate \u2014 Plugin Foundation \u2500\u2500 */\n\n.agw-form-shell {\n display: flex;\n flex-direction: column;\n gap: 1rem;\n min-width: 0;\n font-family: var(--ag-font-sans);\n font-size: 0.875rem;\n color: var(--ag-text);\n letter-spacing: 0;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n\n.agw-form-shell > * {\n min-width: 0;\n}\n\n.agw-field {\n display: flex;\n flex-direction: column;\n gap: 0.375rem;\n}\n\n.agw-section {\n display: flex;\n flex-direction: column;\n gap: 0.75rem;\n}\n\n.agw-section-content {\n display: flex;\n flex-direction: column;\n gap: 0.75rem;\n}\n\n.agw-panel-header {\n display: flex;\n flex-direction: column;\n gap: 0.25rem;\n}\n\n.agw-panel-title {\n font-size: 0.875rem;\n font-weight: 600;\n letter-spacing: 0;\n color: var(--ag-text);\n}\n\n.agw-panel-eyebrow {\n font-size: 0.625rem;\n font-weight: 500;\n text-transform: uppercase;\n letter-spacing: 0;\n color: var(--ag-text-tertiary);\n font-family: var(--ag-font-mono);\n}\n\n.agw-panel-description {\n font-size: 0.75rem;\n line-height: 1.65;\n color: var(--ag-text-secondary);\n}\n\n.agw-label {\n font-size: 0.6875rem;\n font-weight: 500;\n text-transform: uppercase;\n letter-spacing: 0;\n color: var(--ag-text-secondary);\n}\n\n.agw-label-required {\n margin-left: 0.25rem;\n color: var(--ag-danger);\n}\n\n.agw-hint {\n font-size: 0.75rem;\n line-height: 1.65;\n color: var(--ag-text-tertiary);\n}\n\n.agw-input {\n display: block;\n width: 100%;\n border: 1px solid color-mix(in oklab, var(--ag-border) 88%, transparent);\n border-radius: var(--ag-field-radius, 0.5rem);\n background: var(--ag-field-background);\n padding: 0.5rem 0.75rem;\n color: var(--ag-field-foreground);\n font-size: 0.875rem;\n outline: none;\n box-shadow: var(--ag-shadow-sm);\n transition: border-color var(--ag-transition), box-shadow var(--ag-transition), background-color var(--ag-transition);\n}\n\n.agw-input::placeholder {\n color: var(--ag-field-placeholder);\n}\n\n.agw-input:hover {\n background: color-mix(in oklab, var(--ag-field-background) 86%, var(--ag-surface) 14%);\n border-color: color-mix(in oklab, var(--ag-border) 92%, var(--ag-text) 8%);\n}\n\n.agw-input:focus,\n.agw-input:focus-visible {\n border-color: var(--ag-border-focus);\n box-shadow: 0 0 0 2px color-mix(in oklab, var(--ag-primary) 22%, transparent);\n}\n\n.agw-input-mono {\n font-family: var(--ag-font-mono);\n}\n\n.agw-textarea {\n min-height: 76px;\n resize: vertical;\n}\n\n.agw-card {\n border: 1px solid var(--ag-border);\n border-radius: var(--ag-radius-sm);\n background: var(--ag-surface);\n padding: 1rem;\n transition: border-color var(--ag-transition), background-color var(--ag-transition), box-shadow var(--ag-transition);\n}\n\n.agw-status-inline {\n display: inline-flex;\n align-items: center;\n padding: 0.25rem 0.75rem;\n border: 1px solid var(--ag-border);\n border-radius: var(--ag-radius-sm);\n background: var(--ag-surface-secondary);\n font-size: 0.75rem;\n font-weight: 500;\n}\n\n.agw-status-inline-info {\n color: var(--ag-text-secondary);\n}\n\n.agw-status-inline-success {\n color: var(--ag-success);\n}\n\n.agw-status-inline-error {\n color: var(--ag-danger);\n}\n\n.agw-panel {\n gap: 0;\n padding: 1.25rem;\n background: var(--ag-surface);\n border: 1px solid var(--ag-border);\n border-radius: var(--ag-radius-sm);\n}\n\n.agw-card-active {\n border-color: var(--ag-border-focus);\n background: var(--ag-primary-subtle);\n box-shadow: 0 0 0 1px color-mix(in oklab, var(--ag-primary) 22%, transparent);\n}\n\n.agw-selectable-card {\n position: relative;\n width: 100%;\n overflow: hidden;\n text-align: left;\n cursor: pointer;\n}\n\n.agw-selectable-card:hover {\n border-color: var(--ag-border);\n background: var(--ag-bg-surface);\n}\n\n.agw-focus-ring:focus-visible {\n outline: 1.5px solid var(--ag-primary);\n outline-offset: 2px;\n box-shadow: 0 0 0 2px color-mix(in oklab, var(--ag-primary) 18%, transparent);\n}\n\n.agw-button-primary,\n.agw-button-secondary,\n.agw-button-outline {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 0.5rem;\n border-radius: var(--ag-radius-sm);\n padding: 0.5rem 1rem;\n font-size: 0.875rem;\n font-weight: 500;\n cursor: pointer;\n transition: border-color 200ms, color 200ms, background-color 200ms, opacity 200ms, box-shadow 200ms;\n}\n\n.agw-button-primary {\n border: 1px solid transparent;\n background: var(--ag-primary);\n color: var(--ag-primary-foreground);\n box-shadow: none;\n}\n\n.agw-button-primary:hover {\n background: var(--ag-primary-hover);\n}\n\n.agw-button-secondary {\n border: 1px solid var(--ag-border);\n background: var(--ag-default-bg);\n color: var(--ag-default-foreground);\n}\n\n.agw-button-secondary:hover {\n border-color: var(--ag-border);\n background: var(--ag-bg-hover);\n}\n\n.agw-button-outline {\n border: 1px solid var(--ag-border);\n background: transparent;\n color: var(--ag-text);\n}\n\n.agw-button-outline:hover {\n background: var(--ag-primary-subtle);\n}\n\n.agw-form-actions {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 0.625rem;\n}\n\n.agw-badge {\n display: inline-flex;\n align-items: center;\n border-radius: var(--ag-radius-sm);\n padding: 0.25rem 0.625rem;\n font-size: 0.6875rem;\n font-weight: 500;\n letter-spacing: 0;\n}\n\n.agw-badge-neutral {\n background: var(--ag-default-bg);\n color: var(--ag-default-foreground);\n}\n\n.agw-badge-success {\n background: var(--ag-success-subtle);\n color: var(--ag-success);\n}\n\n.agw-badge-violet {\n background: var(--ag-info-subtle);\n color: var(--ag-info);\n}\n\n.agw-badge-info {\n background: var(--ag-primary-subtle);\n color: var(--ag-primary);\n}\n\n.agw-button-primary:disabled,\n.agw-button-secondary:disabled,\n.agw-button-outline:disabled,\n.agw-input:disabled,\n.agw-selectable-card:disabled {\n cursor: not-allowed;\n opacity: 0.45;\n}\n\n/* \u2500\u2500 Light theme and modal elevation follow HeroUI bridge tokens. \u2500\u2500 */\n\n[data-theme=\"light\"] .agw-card,\n[data-theme=\"light\"] .agw-panel {\n box-shadow: var(--ag-shadow-sm);\n}\n\n.ag-elevation-modal .agw-input {\n background: var(--ag-field-background);\n border-color: color-mix(in oklab, var(--ag-border) 88%, transparent);\n box-shadow: var(--ag-shadow-sm);\n}\n\n.ag-elevation-modal .agw-card,\n.ag-elevation-modal .agw-panel {\n background: var(--ag-surface);\n border-color: var(--ag-border);\n box-shadow: none;\n}\n\n.ag-elevation-modal .agw-card:hover,\n.ag-elevation-modal .agw-selectable-card:hover {\n background: var(--ag-surface-secondary);\n border-color: var(--ag-border);\n}\n\n.ag-elevation-modal .agw-card-active {\n background: var(--ag-primary-subtle);\n border-color: var(--ag-border-focus);\n}\n\n.ag-elevation-modal .agw-button-secondary {\n background: var(--ag-default-bg);\n border-color: var(--ag-border);\n}\n\n.ag-elevation-modal .agw-button-secondary:hover {\n background: var(--ag-bg-hover);\n border-color: var(--ag-border);\n}\n";
+export declare function injectStyle(id: string, cssText: string, targetDocument?: Document): void;
+export declare function ensurePluginStyleFoundation({ scopeSelector, themeAttribute, themeStyleId, foundationStyleId, extraCssText, extraStyleId, targetDocument, }: PluginStyleFoundationOptions): void;
+export declare function resolvePluginTheme({ storageKey }?: ResolvePluginThemeOptions): ThemeName;
+export declare function useScopedPluginTheme(options?: ScopedPluginThemeOptions): import("react").RefObject;
+export declare function createPluginTailwindConfig({ scopeSelector, classPrefix, tokenPrefix, }: PluginTailwindConfigOptions): {
+ readonly prefix: string;
+ readonly important: string;
+ readonly theme: {
+ readonly extend: {
+ readonly colors: {
+ readonly primary: `var(${string})`;
+ readonly 'primary-hover': `var(${string})`;
+ readonly 'primary-subtle': `var(${string})`;
+ readonly success: `var(${string})`;
+ readonly 'success-subtle': `var(${string})`;
+ readonly warning: `var(${string})`;
+ readonly 'warning-subtle': `var(${string})`;
+ readonly danger: `var(${string})`;
+ readonly 'danger-subtle': `var(${string})`;
+ readonly info: `var(${string})`;
+ readonly 'info-subtle': `var(${string})`;
+ readonly bg: `var(${string})`;
+ readonly 'bg-deep': `var(${string})`;
+ readonly 'bg-elevated': `var(${string})`;
+ readonly surface: `var(${string})`;
+ readonly 'bg-hover': `var(${string})`;
+ readonly 'bg-active': `var(${string})`;
+ readonly border: `var(${string})`;
+ readonly 'border-subtle': `var(${string})`;
+ readonly 'border-focus': `var(${string})`;
+ readonly text: `var(${string})`;
+ readonly 'text-secondary': `var(${string})`;
+ readonly 'text-tertiary': `var(${string})`;
+ readonly 'text-inverse': `var(${string})`;
+ readonly glass: `var(${string})`;
+ readonly 'glass-border': `var(${string})`;
+ };
+ readonly borderRadius: {
+ readonly sm: `var(${string})`;
+ readonly md: `var(${string})`;
+ readonly lg: `var(${string})`;
+ readonly xl: `var(${string})`;
+ readonly field: `var(${string})`;
+ };
+ readonly fontFamily: {
+ readonly sans: `var(${string})`;
+ readonly mono: `var(${string})`;
+ };
+ readonly boxShadow: {
+ readonly sm: `var(${string})`;
+ readonly md: `var(${string})`;
+ readonly lg: `var(${string})`;
+ readonly glow: `var(${string})`;
+ };
+ readonly transitionDuration: {
+ readonly DEFAULT: `var(${string})`;
+ readonly slow: `var(${string})`;
+ };
+ };
+ };
+ readonly corePlugins: {
+ readonly preflight: false;
+ };
+};
+export declare function cn(...values: Array): string;
+export interface FieldProps {
+ label: ReactNode;
+ required?: boolean;
+ hint?: ReactNode;
+ children: ReactNode;
+ className?: string;
+}
+export declare function Field({ label, required, hint, children, className }: FieldProps): import("react/jsx-runtime").JSX.Element;
+export declare function TextInput({ className, ...props }: InputHTMLAttributes): import("react/jsx-runtime").JSX.Element;
+export declare function SecretInput({ className, ...props }: InputHTMLAttributes): import("react/jsx-runtime").JSX.Element;
+export declare function TextArea({ className, ...props }: TextareaHTMLAttributes): import("react/jsx-runtime").JSX.Element;
+export interface PanelHeaderProps {
+ title: ReactNode;
+ description?: ReactNode;
+ eyebrow?: ReactNode;
+ className?: string;
+}
+export declare function PanelHeader({ title, description, eyebrow, className }: PanelHeaderProps): import("react/jsx-runtime").JSX.Element;
+export interface SectionProps extends PanelHeaderProps {
+ children: ReactNode;
+ panel?: boolean;
+ contentClassName?: string;
+}
+export declare function Section({ title, description, eyebrow, children, panel, className, contentClassName, }: SectionProps): import("react/jsx-runtime").JSX.Element;
+export interface CardProps {
+ children: ReactNode;
+ className?: string;
+}
+export declare function Card({ children, className }: CardProps): import("react/jsx-runtime").JSX.Element;
+export interface SelectableCardProps extends ButtonHTMLAttributes {
+ active?: boolean;
+}
+export declare function SelectableCard({ active, className, children, ...props }: SelectableCardProps): import("react/jsx-runtime").JSX.Element;
+export type ButtonVariant = 'primary' | 'secondary' | 'outline';
+export interface ButtonProps extends ButtonHTMLAttributes {
+ variant?: ButtonVariant;
+}
+export declare function Button({ variant, className, children, ...props }: ButtonProps): import("react/jsx-runtime").JSX.Element;
+export interface FormActionsProps {
+ children: ReactNode;
+ className?: string;
+}
+export declare function FormActions({ children, className }: FormActionsProps): import("react/jsx-runtime").JSX.Element;
+export interface BadgeProps {
+ children: ReactNode;
+ tone?: 'neutral' | 'success' | 'violet' | 'info';
+ className?: string;
+}
+export declare function Badge({ children, tone, className }: BadgeProps): import("react/jsx-runtime").JSX.Element;
+export interface StatusTextProps {
+ type: PluginStatusKind;
+ text: string;
+}
+export declare function StatusText({ type, text }: StatusTextProps): import("react/jsx-runtime").JSX.Element;
diff --git a/theme/dist/plugin.js b/theme/dist/plugin.js
new file mode 100644
index 0000000..64c6681
--- /dev/null
+++ b/theme/dist/plugin.js
@@ -0,0 +1,467 @@
+import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
+import { useLayoutEffect, useRef, } from 'react';
+import { createTailwindThemeBridge, getStoredTheme, injectThemeStyle, setTheme } from './css.js';
+export const DEFAULT_PLUGIN_THEME_ATTRIBUTE = 'data-theme';
+export const DEFAULT_PLUGIN_CLASS_PREFIX = 'agw-';
+export const DEFAULT_PLUGIN_THEME_STYLE_ID = 'ag-plugin-theme-vars';
+export const DEFAULT_PLUGIN_FOUNDATION_STYLE_ID = 'ag-plugin-foundation';
+export const pluginFoundationCssText = `
+/* ── AirGate — Plugin Foundation ── */
+
+.agw-form-shell {
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+ min-width: 0;
+ font-family: var(--ag-font-sans);
+ font-size: 0.875rem;
+ color: var(--ag-text);
+ letter-spacing: 0;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+.agw-form-shell > * {
+ min-width: 0;
+}
+
+.agw-field {
+ display: flex;
+ flex-direction: column;
+ gap: 0.375rem;
+}
+
+.agw-section {
+ display: flex;
+ flex-direction: column;
+ gap: 0.75rem;
+}
+
+.agw-section-content {
+ display: flex;
+ flex-direction: column;
+ gap: 0.75rem;
+}
+
+.agw-panel-header {
+ display: flex;
+ flex-direction: column;
+ gap: 0.25rem;
+}
+
+.agw-panel-title {
+ font-size: 0.875rem;
+ font-weight: 600;
+ letter-spacing: 0;
+ color: var(--ag-text);
+}
+
+.agw-panel-eyebrow {
+ font-size: 0.625rem;
+ font-weight: 500;
+ text-transform: uppercase;
+ letter-spacing: 0;
+ color: var(--ag-text-tertiary);
+ font-family: var(--ag-font-mono);
+}
+
+.agw-panel-description {
+ font-size: 0.75rem;
+ line-height: 1.65;
+ color: var(--ag-text-secondary);
+}
+
+.agw-label {
+ font-size: 0.6875rem;
+ font-weight: 500;
+ text-transform: uppercase;
+ letter-spacing: 0;
+ color: var(--ag-text-secondary);
+}
+
+.agw-label-required {
+ margin-left: 0.25rem;
+ color: var(--ag-danger);
+}
+
+.agw-hint {
+ font-size: 0.75rem;
+ line-height: 1.65;
+ color: var(--ag-text-tertiary);
+}
+
+.agw-input {
+ display: block;
+ width: 100%;
+ border: 1px solid color-mix(in oklab, var(--ag-border) 88%, transparent);
+ border-radius: var(--ag-field-radius, 0.5rem);
+ background: var(--ag-field-background);
+ padding: 0.5rem 0.75rem;
+ color: var(--ag-field-foreground);
+ font-size: 0.875rem;
+ outline: none;
+ box-shadow: var(--ag-shadow-sm);
+ transition: border-color var(--ag-transition), box-shadow var(--ag-transition), background-color var(--ag-transition);
+}
+
+.agw-input::placeholder {
+ color: var(--ag-field-placeholder);
+}
+
+.agw-input:hover {
+ background: color-mix(in oklab, var(--ag-field-background) 86%, var(--ag-surface) 14%);
+ border-color: color-mix(in oklab, var(--ag-border) 92%, var(--ag-text) 8%);
+}
+
+.agw-input:focus,
+.agw-input:focus-visible {
+ border-color: var(--ag-border-focus);
+ box-shadow: 0 0 0 2px color-mix(in oklab, var(--ag-primary) 22%, transparent);
+}
+
+.agw-input-mono {
+ font-family: var(--ag-font-mono);
+}
+
+.agw-textarea {
+ min-height: 76px;
+ resize: vertical;
+}
+
+.agw-card {
+ border: 1px solid var(--ag-border);
+ border-radius: var(--ag-radius-sm);
+ background: var(--ag-surface);
+ padding: 1rem;
+ transition: border-color var(--ag-transition), background-color var(--ag-transition), box-shadow var(--ag-transition);
+}
+
+.agw-status-inline {
+ display: inline-flex;
+ align-items: center;
+ padding: 0.25rem 0.75rem;
+ border: 1px solid var(--ag-border);
+ border-radius: var(--ag-radius-sm);
+ background: var(--ag-surface-secondary);
+ font-size: 0.75rem;
+ font-weight: 500;
+}
+
+.agw-status-inline-info {
+ color: var(--ag-text-secondary);
+}
+
+.agw-status-inline-success {
+ color: var(--ag-success);
+}
+
+.agw-status-inline-error {
+ color: var(--ag-danger);
+}
+
+.agw-panel {
+ gap: 0;
+ padding: 1.25rem;
+ background: var(--ag-surface);
+ border: 1px solid var(--ag-border);
+ border-radius: var(--ag-radius-sm);
+}
+
+.agw-card-active {
+ border-color: var(--ag-border-focus);
+ background: var(--ag-primary-subtle);
+ box-shadow: 0 0 0 1px color-mix(in oklab, var(--ag-primary) 22%, transparent);
+}
+
+.agw-selectable-card {
+ position: relative;
+ width: 100%;
+ overflow: hidden;
+ text-align: left;
+ cursor: pointer;
+}
+
+.agw-selectable-card:hover {
+ border-color: var(--ag-border);
+ background: var(--ag-bg-surface);
+}
+
+.agw-focus-ring:focus-visible {
+ outline: 1.5px solid var(--ag-primary);
+ outline-offset: 2px;
+ box-shadow: 0 0 0 2px color-mix(in oklab, var(--ag-primary) 18%, transparent);
+}
+
+.agw-button-primary,
+.agw-button-secondary,
+.agw-button-outline {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0.5rem;
+ border-radius: var(--ag-radius-sm);
+ padding: 0.5rem 1rem;
+ font-size: 0.875rem;
+ font-weight: 500;
+ cursor: pointer;
+ transition: border-color 200ms, color 200ms, background-color 200ms, opacity 200ms, box-shadow 200ms;
+}
+
+.agw-button-primary {
+ border: 1px solid transparent;
+ background: var(--ag-primary);
+ color: var(--ag-primary-foreground);
+ box-shadow: none;
+}
+
+.agw-button-primary:hover {
+ background: var(--ag-primary-hover);
+}
+
+.agw-button-secondary {
+ border: 1px solid var(--ag-border);
+ background: var(--ag-default-bg);
+ color: var(--ag-default-foreground);
+}
+
+.agw-button-secondary:hover {
+ border-color: var(--ag-border);
+ background: var(--ag-bg-hover);
+}
+
+.agw-button-outline {
+ border: 1px solid var(--ag-border);
+ background: transparent;
+ color: var(--ag-text);
+}
+
+.agw-button-outline:hover {
+ background: var(--ag-primary-subtle);
+}
+
+.agw-form-actions {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 0.625rem;
+}
+
+.agw-badge {
+ display: inline-flex;
+ align-items: center;
+ border-radius: var(--ag-radius-sm);
+ padding: 0.25rem 0.625rem;
+ font-size: 0.6875rem;
+ font-weight: 500;
+ letter-spacing: 0;
+}
+
+.agw-badge-neutral {
+ background: var(--ag-default-bg);
+ color: var(--ag-default-foreground);
+}
+
+.agw-badge-success {
+ background: var(--ag-success-subtle);
+ color: var(--ag-success);
+}
+
+.agw-badge-violet {
+ background: var(--ag-info-subtle);
+ color: var(--ag-info);
+}
+
+.agw-badge-info {
+ background: var(--ag-primary-subtle);
+ color: var(--ag-primary);
+}
+
+.agw-button-primary:disabled,
+.agw-button-secondary:disabled,
+.agw-button-outline:disabled,
+.agw-input:disabled,
+.agw-selectable-card:disabled {
+ cursor: not-allowed;
+ opacity: 0.45;
+}
+
+/* ── Light theme and modal elevation follow HeroUI bridge tokens. ── */
+
+[data-theme="light"] .agw-card,
+[data-theme="light"] .agw-panel {
+ box-shadow: var(--ag-shadow-sm);
+}
+
+.ag-elevation-modal .agw-input {
+ background: var(--ag-field-background);
+ border-color: color-mix(in oklab, var(--ag-border) 88%, transparent);
+ box-shadow: var(--ag-shadow-sm);
+}
+
+.ag-elevation-modal .agw-card,
+.ag-elevation-modal .agw-panel {
+ background: var(--ag-surface);
+ border-color: var(--ag-border);
+ box-shadow: none;
+}
+
+.ag-elevation-modal .agw-card:hover,
+.ag-elevation-modal .agw-selectable-card:hover {
+ background: var(--ag-surface-secondary);
+ border-color: var(--ag-border);
+}
+
+.ag-elevation-modal .agw-card-active {
+ background: var(--ag-primary-subtle);
+ border-color: var(--ag-border-focus);
+}
+
+.ag-elevation-modal .agw-button-secondary {
+ background: var(--ag-default-bg);
+ border-color: var(--ag-border);
+}
+
+.ag-elevation-modal .agw-button-secondary:hover {
+ background: var(--ag-bg-hover);
+ border-color: var(--ag-border);
+}
+`;
+export function injectStyle(id, cssText, targetDocument) {
+ const doc = targetDocument || (typeof document !== 'undefined' ? document : undefined);
+ if (!doc)
+ return;
+ let element = doc.getElementById(id);
+ if (!element) {
+ element = doc.createElement('style');
+ element.id = id;
+ doc.head.appendChild(element);
+ }
+ if (element.textContent !== cssText) {
+ element.textContent = cssText;
+ }
+}
+export function ensurePluginStyleFoundation({ scopeSelector, themeAttribute = DEFAULT_PLUGIN_THEME_ATTRIBUTE, themeStyleId = DEFAULT_PLUGIN_THEME_STYLE_ID, foundationStyleId = DEFAULT_PLUGIN_FOUNDATION_STYLE_ID, extraCssText, extraStyleId, targetDocument, }) {
+ injectThemeStyle({
+ styleId: themeStyleId,
+ scopeSelector,
+ themeAttribute,
+ targetDocument,
+ });
+ injectStyle(foundationStyleId, pluginFoundationCssText, targetDocument);
+ if (extraCssText && extraStyleId) {
+ injectStyle(extraStyleId, extraCssText, targetDocument);
+ }
+}
+export function resolvePluginTheme({ storageKey } = {}) {
+ const theme = getStoredTheme({ storageKey });
+ return theme === 'light' ? 'light' : 'dark';
+}
+function resolveInheritedTheme(element, themeAttribute, storageKey) {
+ const scopedAncestor = element.parentElement?.closest(`[${themeAttribute}]`);
+ const hostTheme = scopedAncestor?.getAttribute(themeAttribute)
+ || document.documentElement.getAttribute(themeAttribute);
+ return hostTheme === 'light'
+ ? 'light'
+ : hostTheme === 'dark'
+ ? 'dark'
+ : resolvePluginTheme({ storageKey });
+}
+export function useScopedPluginTheme(options = {}) {
+ const { themeAttribute = DEFAULT_PLUGIN_THEME_ATTRIBUTE, storageKey } = options;
+ const ref = useRef(null);
+ useLayoutEffect(() => {
+ const element = ref.current;
+ if (!element)
+ return;
+ const applyTheme = () => {
+ setTheme(resolveInheritedTheme(element, themeAttribute, storageKey), {
+ target: element,
+ themeAttribute,
+ storageKey,
+ });
+ };
+ applyTheme();
+ const hostElement = element.parentElement?.closest(`[${themeAttribute}]`)
+ || document.documentElement;
+ const observer = new MutationObserver((mutations) => {
+ for (const mutation of mutations) {
+ if (mutation.type === 'attributes' && mutation.attributeName === themeAttribute) {
+ applyTheme();
+ break;
+ }
+ }
+ });
+ observer.observe(hostElement, { attributes: true, attributeFilter: [themeAttribute] });
+ return () => observer.disconnect();
+ }, [themeAttribute, storageKey]);
+ return ref;
+}
+export function createPluginTailwindConfig({ scopeSelector, classPrefix = DEFAULT_PLUGIN_CLASS_PREFIX, tokenPrefix, }) {
+ return {
+ prefix: classPrefix,
+ important: scopeSelector,
+ theme: {
+ extend: {
+ ...createTailwindThemeBridge(tokenPrefix ? { prefix: tokenPrefix } : {}),
+ },
+ },
+ corePlugins: {
+ preflight: false,
+ },
+ };
+}
+export function cn(...values) {
+ return values.filter(Boolean).join(' ');
+}
+export function Field({ label, required = false, hint, children, className }) {
+ return (_jsxs("div", { className: cn('agw-field', className), children: [_jsxs("label", { className: "agw-label", children: [label, required && _jsx("span", { className: "agw-label-required", children: "*" })] }), children, hint ? _jsx("div", { className: "agw-hint", children: hint }) : null] }));
+}
+export function TextInput({ className, ...props }) {
+ return _jsx("input", { ...props, className: cn('agw-input', className) });
+}
+export function SecretInput({ className, ...props }) {
+ return _jsx("input", { ...props, type: "password", className: cn('agw-input agw-input-mono', className) });
+}
+export function TextArea({ className, ...props }) {
+ return _jsx("textarea", { ...props, className: cn('agw-input agw-input-mono agw-textarea', className) });
+}
+export function PanelHeader({ title, description, eyebrow, className }) {
+ return (_jsxs("div", { className: cn('agw-panel-header', className), children: [eyebrow ? _jsx("div", { className: "agw-panel-eyebrow", children: eyebrow }) : null, _jsx("div", { className: "agw-panel-title", children: title }), description ? _jsx("div", { className: "agw-panel-description", children: description }) : null] }));
+}
+export function Section({ title, description, eyebrow, children, panel = false, className, contentClassName, }) {
+ return (_jsxs("div", { className: cn(panel ? 'agw-panel agw-section' : 'agw-section', className), children: [_jsx(PanelHeader, { title: title, description: description, eyebrow: eyebrow }), _jsx("div", { className: cn('agw-section-content', contentClassName), children: children })] }));
+}
+export function Card({ children, className }) {
+ return _jsx("div", { className: cn('agw-card', className), children: children });
+}
+export function SelectableCard({ active = false, className, children, ...props }) {
+ return (_jsx("button", { ...props, type: props.type || 'button', className: cn('agw-card agw-selectable-card agw-focus-ring', active && 'agw-card-active', className), children: children }));
+}
+const buttonClassMap = {
+ primary: 'agw-button-primary',
+ secondary: 'agw-button-secondary',
+ outline: 'agw-button-outline',
+};
+export function Button({ variant = 'secondary', className, children, ...props }) {
+ return (_jsx("button", { ...props, type: props.type || 'button', className: cn('agw-focus-ring', buttonClassMap[variant], className), children: children }));
+}
+export function FormActions({ children, className }) {
+ return _jsx("div", { className: cn('agw-form-actions', className), children: children });
+}
+const badgeToneClassMap = {
+ neutral: 'agw-badge-neutral',
+ success: 'agw-badge-success',
+ violet: 'agw-badge-violet',
+ info: 'agw-badge-info',
+};
+export function Badge({ children, tone = 'neutral', className }) {
+ return _jsx("span", { className: cn('agw-badge', badgeToneClassMap[tone], className), children: children });
+}
+const statusClassMap = {
+ info: 'agw-status-inline-info',
+ success: 'agw-status-inline-success',
+ error: 'agw-status-inline-error',
+};
+export function StatusText({ type, text }) {
+ return _jsx("div", { className: cn('agw-status-inline', statusClassMap[type]), children: text });
+}
diff --git a/theme/dist/tailwind.d.ts b/theme/dist/tailwind.d.ts
new file mode 100644
index 0000000..0237631
--- /dev/null
+++ b/theme/dist/tailwind.d.ts
@@ -0,0 +1,54 @@
+import { createTailwindThemeBridge } from './css.js';
+declare const tailwindThemeBridge: {
+ readonly colors: {
+ readonly primary: `var(${string})`;
+ readonly 'primary-hover': `var(${string})`;
+ readonly 'primary-subtle': `var(${string})`;
+ readonly success: `var(${string})`;
+ readonly 'success-subtle': `var(${string})`;
+ readonly warning: `var(${string})`;
+ readonly 'warning-subtle': `var(${string})`;
+ readonly danger: `var(${string})`;
+ readonly 'danger-subtle': `var(${string})`;
+ readonly info: `var(${string})`;
+ readonly 'info-subtle': `var(${string})`;
+ readonly bg: `var(${string})`;
+ readonly 'bg-deep': `var(${string})`;
+ readonly 'bg-elevated': `var(${string})`;
+ readonly surface: `var(${string})`;
+ readonly 'bg-hover': `var(${string})`;
+ readonly 'bg-active': `var(${string})`;
+ readonly border: `var(${string})`;
+ readonly 'border-subtle': `var(${string})`;
+ readonly 'border-focus': `var(${string})`;
+ readonly text: `var(${string})`;
+ readonly 'text-secondary': `var(${string})`;
+ readonly 'text-tertiary': `var(${string})`;
+ readonly 'text-inverse': `var(${string})`;
+ readonly glass: `var(${string})`;
+ readonly 'glass-border': `var(${string})`;
+ };
+ readonly borderRadius: {
+ readonly sm: `var(${string})`;
+ readonly md: `var(${string})`;
+ readonly lg: `var(${string})`;
+ readonly xl: `var(${string})`;
+ readonly field: `var(${string})`;
+ };
+ readonly fontFamily: {
+ readonly sans: `var(${string})`;
+ readonly mono: `var(${string})`;
+ };
+ readonly boxShadow: {
+ readonly sm: `var(${string})`;
+ readonly md: `var(${string})`;
+ readonly lg: `var(${string})`;
+ readonly glow: `var(${string})`;
+ };
+ readonly transitionDuration: {
+ readonly DEFAULT: `var(${string})`;
+ readonly slow: `var(${string})`;
+ };
+};
+export default tailwindThemeBridge;
+export { createTailwindThemeBridge };
diff --git a/theme/dist/tailwind.js b/theme/dist/tailwind.js
new file mode 100644
index 0000000..953a43f
--- /dev/null
+++ b/theme/dist/tailwind.js
@@ -0,0 +1,4 @@
+import { createTailwindThemeBridge } from './css.js';
+const tailwindThemeBridge = createTailwindThemeBridge();
+export default tailwindThemeBridge;
+export { createTailwindThemeBridge };
diff --git a/theme/dist/tokens.d.ts b/theme/dist/tokens.d.ts
new file mode 100644
index 0000000..1e24e4a
--- /dev/null
+++ b/theme/dist/tokens.d.ts
@@ -0,0 +1,23 @@
+import type { AppShellTokens, ElevationContext, FoundationTokens, StaticTokenGroups, StaticTokens, ThemeName, ThemeTokens } from './types.js';
+/** 暗色主题 — HeroUI Theme Builder preset */
+export declare const darkTheme: ThemeTokens;
+/** 亮色主题 — HeroUI Theme Builder preset */
+export declare const lightTheme: ThemeTokens;
+/** 通用基础 token:HeroUI Radius 为 Small,Radius Form 为 Medium。 */
+export declare const foundationTokens: FoundationTokens;
+/** 应用壳层 token */
+export declare const appShellTokens: AppShellTokens;
+/** 分组后的静态 token */
+export declare const staticTokenGroups: StaticTokenGroups;
+/** 不随主题变化的静态 token */
+export declare const staticTokens: StaticTokens;
+/** 图表/头像装饰色(与主题无关的固定调色板) */
+export declare const decorativePalette: readonly ["#3b82f6", "#10b981", "#f59e0b", "#ef4444", "#8b5cf6", "#06b6d4", "#ec4899", "#84cc16", "#f97316", "#6366f1", "#0d9488", "#a855f7"];
+/** 主题集合 */
+export declare const themes: Record;
+/**
+ * 亮色主题 elevation 上下文覆盖
+ * 不同 UI 层级(页面 → 弹窗 → 下拉)需要不同的背景/边框/阴影值。
+ * 宿主在容器上设置 .ag-elevation-{context} class,子组件自动继承正确的 token 值。
+ */
+export declare const lightElevationContexts: Record>;
diff --git a/theme/dist/tokens.js b/theme/dist/tokens.js
new file mode 100644
index 0000000..3daf5d7
--- /dev/null
+++ b/theme/dist/tokens.js
@@ -0,0 +1,172 @@
+/** 暗色主题 — HeroUI Theme Builder preset */
+export const darkTheme = {
+ primary: 'oklch(0.9848 0 0)',
+ primaryForeground: 'oklch(15% 0.0000 0.00)',
+ primaryHover: 'color-mix(in oklab, oklch(0.9848 0 0) 88%, oklch(15% 0.0000 0.00) 12%)',
+ primarySubtle: 'color-mix(in oklab, oklch(0.9848 0 0) 14%, transparent)',
+ primaryGlow: 'color-mix(in oklab, oklch(0.9848 0 0) 22%, transparent)',
+ success: 'oklch(73.29% 0.1935 120.35)',
+ successForeground: 'oklch(21.03% 0.0059 120.35)',
+ successSubtle: 'color-mix(in oklab, oklch(73.29% 0.1935 120.35) 15%, transparent)',
+ warning: 'oklch(0.8803 0.1348 86.06)',
+ warningForeground: 'oklch(15% 0.0404 86.06)',
+ warningSubtle: 'color-mix(in oklab, oklch(0.8803 0.1348 86.06) 15%, transparent)',
+ danger: 'oklch(0.7044 0.1872 23.19)',
+ dangerForeground: 'oklch(15% 0.0500 23.19)',
+ dangerSubtle: 'color-mix(in oklab, oklch(0.7044 0.1872 23.19) 15%, transparent)',
+ info: 'oklch(0.9848 0 0)',
+ infoSubtle: 'color-mix(in oklab, oklch(0.9848 0 0) 14%, transparent)',
+ defaultBg: 'oklch(27.40% 0.0000 0.00)',
+ defaultForeground: 'oklch(99.11% 0 0)',
+ fieldBackground: 'oklch(21.03% 0.0000 0.00)',
+ fieldForeground: 'oklch(99.11% 0.0000 0.00)',
+ fieldPlaceholder: 'oklch(70.50% 0.0000 0.00)',
+ muted: 'oklch(70.50% 0.0000 0.00)',
+ overlay: 'oklch(21.03% 0.0000 0.00)',
+ overlayForeground: 'oklch(99.11% 0.0000 0.00)',
+ scrollbar: 'oklch(70.50% 0.0000 0.00)',
+ segment: 'oklch(39.64% 0.0000 0.00)',
+ segmentForeground: 'oklch(99.11% 0.0000 0.00)',
+ surface: 'oklch(21.03% 0.0000 0.00)',
+ surfaceForeground: 'oklch(99.11% 0.0000 0.00)',
+ surfaceSecondary: 'oklch(25.70% 0.0000 0.00)',
+ surfaceSecondaryForeground: 'oklch(99.11% 0.0000 0.00)',
+ surfaceTertiary: 'oklch(27.21% 0.0000 0.00)',
+ surfaceTertiaryForeground: 'oklch(99.11% 0.0000 0.00)',
+ bgDeep: 'oklch(12.00% 0.0000 0.00)',
+ bg: 'oklch(12.00% 0.0000 0.00)',
+ bgElevated: 'oklch(21.03% 0.0000 0.00)',
+ bgSurface: 'oklch(21.03% 0.0000 0.00)',
+ bgHover: 'oklch(25.70% 0.0000 0.00)',
+ bgActive: 'oklch(27.21% 0.0000 0.00)',
+ border: 'oklch(28.00% 0.0000 0.00)',
+ borderSubtle: 'oklch(25.00% 0.0000 0.00)',
+ borderFocus: 'oklch(0.9848 0 0)',
+ text: 'oklch(99.11% 0.0000 0.00)',
+ textSecondary: 'oklch(70.50% 0.0000 0.00)',
+ textTertiary: 'oklch(70.50% 0.0000 0.00)',
+ textInverse: 'oklch(15% 0.0000 0.00)',
+ glass: 'color-mix(in oklab, oklch(21.03% 0.0000 0.00) 92%, transparent)',
+ glassBorder: 'oklch(28.00% 0.0000 0.00)',
+ shadowSm: '0 0 0 0 transparent inset',
+ shadowMd: '0 0 0 0 transparent inset',
+ shadowLg: '0 0 1px 0 #ffffff4d inset',
+ shadowGlow: '0 0 0 1px color-mix(in oklab, oklch(0.9848 0 0) 18%, transparent)',
+};
+/** 亮色主题 — HeroUI Theme Builder preset */
+export const lightTheme = {
+ primary: 'oklch(0 0 0)',
+ primaryForeground: 'oklch(99.11% 0 0)',
+ primaryHover: 'color-mix(in oklab, oklch(0 0 0) 88%, oklch(99.11% 0 0) 12%)',
+ primarySubtle: 'color-mix(in oklab, oklch(0 0 0) 10%, transparent)',
+ primaryGlow: 'color-mix(in oklab, oklch(0 0 0) 16%, transparent)',
+ success: 'oklch(73.29% 0.1935 120.35)',
+ successForeground: 'oklch(21.03% 0.0059 120.35)',
+ successSubtle: 'color-mix(in oklab, oklch(73.29% 0.1935 120.35) 15%, transparent)',
+ warning: 'oklch(0.8446 0.1525 80.6)',
+ warningForeground: 'oklch(15% 0.0457 80.60)',
+ warningSubtle: 'color-mix(in oklab, oklch(0.8446 0.1525 80.6) 15%, transparent)',
+ danger: 'oklch(0.573 0.2249 21.97)',
+ dangerForeground: 'oklch(98% 0.0200 21.97)',
+ dangerSubtle: 'color-mix(in oklab, oklch(0.573 0.2249 21.97) 15%, transparent)',
+ info: 'oklch(0 0 0)',
+ infoSubtle: 'color-mix(in oklab, oklch(0 0 0) 10%, transparent)',
+ defaultBg: 'oklch(94.00% 0.0000 0.00)',
+ defaultForeground: 'oklch(21.03% 0.0059 0.00)',
+ fieldBackground: 'oklch(100.00% 0.0000 0.00)',
+ fieldForeground: 'oklch(21.03% 0.0000 0.00)',
+ fieldPlaceholder: 'oklch(55.17% 0.0000 0.00)',
+ muted: 'oklch(55.17% 0.0000 0.00)',
+ overlay: 'oklch(100.00% 0.0000 0.00)',
+ overlayForeground: 'oklch(21.03% 0.0000 0.00)',
+ scrollbar: 'oklch(87.10% 0.0000 0.00)',
+ segment: 'oklch(100.00% 0.0000 0.00)',
+ segmentForeground: 'oklch(21.03% 0.0000 0.00)',
+ surface: 'oklch(100.00% 0.0000 0.00)',
+ surfaceForeground: 'oklch(21.03% 0.0000 0.00)',
+ surfaceSecondary: 'oklch(95.24% 0.0000 0.00)',
+ surfaceSecondaryForeground: 'oklch(21.03% 0.0000 0.00)',
+ surfaceTertiary: 'oklch(93.73% 0.0000 0.00)',
+ surfaceTertiaryForeground: 'oklch(21.03% 0.0000 0.00)',
+ bgDeep: 'oklch(97.02% 0.0000 0.00)',
+ bg: 'oklch(97.02% 0.0000 0.00)',
+ bgElevated: 'oklch(100.00% 0.0000 0.00)',
+ bgSurface: 'oklch(100.00% 0.0000 0.00)',
+ bgHover: 'oklch(95.24% 0.0000 0.00)',
+ bgActive: 'oklch(93.73% 0.0000 0.00)',
+ border: 'oklch(90.00% 0.0000 0.00)',
+ borderSubtle: 'oklch(92.00% 0.0000 0.00)',
+ borderFocus: 'oklch(0 0 0)',
+ text: 'oklch(21.03% 0.0000 0.00)',
+ textSecondary: 'oklch(55.17% 0.0000 0.00)',
+ textTertiary: 'oklch(55.17% 0.0000 0.00)',
+ textInverse: 'oklch(99.11% 0 0)',
+ glass: 'color-mix(in oklab, oklch(100.00% 0.0000 0.00) 92%, transparent)',
+ glassBorder: 'oklch(90.00% 0.0000 0.00)',
+ shadowSm: '0 2px 4px 0 #0000000a, 0 1px 2px 0 #0000000f, 0 0 1px 0 #0000000f',
+ shadowMd: '0 2px 4px 0 #0000000a, 0 1px 2px 0 #0000000f, 0 0 1px 0 #0000000f',
+ shadowLg: '0 2px 8px 0 #0000000f, 0 -6px 12px 0 #00000008, 0 14px 28px 0 #00000014',
+ shadowGlow: '0 0 0 1px color-mix(in oklab, oklch(0 0 0) 12%, transparent)',
+};
+/** 通用基础 token:HeroUI Radius 为 Small,Radius Form 为 Medium。 */
+export const foundationTokens = {
+ radiusSm: '0.25rem',
+ radiusMd: '0.25rem',
+ radiusLg: '0.25rem',
+ radiusXl: '0.25rem',
+ fieldRadius: '0.5rem',
+ fontSans: "'Geist Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",
+ fontMono: "'Geist Mono', 'SF Mono', 'Cascadia Code', monospace",
+ transition: '200ms cubic-bezier(0.4, 0, 0.2, 1)',
+ transitionSlow: '400ms cubic-bezier(0.4, 0, 0.2, 1)',
+};
+/** 应用壳层 token */
+export const appShellTokens = {
+ sidebarWidth: '260px',
+ sidebarCollapsed: '72px',
+ topbarHeight: '64px',
+};
+/** 分组后的静态 token */
+export const staticTokenGroups = {
+ foundation: foundationTokens,
+ appShell: appShellTokens,
+};
+/** 不随主题变化的静态 token */
+export const staticTokens = {
+ ...foundationTokens,
+ ...appShellTokens,
+};
+/** 图表/头像装饰色(与主题无关的固定调色板) */
+export const decorativePalette = [
+ '#3b82f6', // blue
+ '#10b981', // emerald
+ '#f59e0b', // amber
+ '#ef4444', // red
+ '#8b5cf6', // violet
+ '#06b6d4', // cyan
+ '#ec4899', // pink
+ '#84cc16', // lime
+ '#f97316', // orange
+ '#6366f1', // indigo
+ '#0d9488', // teal
+ '#a855f7', // purple
+];
+/** 主题集合 */
+export const themes = {
+ dark: darkTheme,
+ light: lightTheme,
+};
+/**
+ * 亮色主题 elevation 上下文覆盖
+ * 不同 UI 层级(页面 → 弹窗 → 下拉)需要不同的背景/边框/阴影值。
+ * 宿主在容器上设置 .ag-elevation-{context} class,子组件自动继承正确的 token 值。
+ */
+export const lightElevationContexts = {
+ modal: {
+ // HeroUI preset already provides overlay/surface tokens for modal elevation.
+ // Keep this empty unless a plugin foundation rule needs a scoped correction.
+ },
+ dropdown: {
+ // HeroUI dropdown/tooltip surfaces are now handled by the host bridge.
+ },
+};
diff --git a/theme/dist/types.d.ts b/theme/dist/types.d.ts
new file mode 100644
index 0000000..b5c544a
--- /dev/null
+++ b/theme/dist/types.d.ts
@@ -0,0 +1,108 @@
+/** 随主题变化的语义 token(颜色、阴影等) */
+export interface ThemeTokens {
+ primary: string;
+ primaryForeground: string;
+ primaryHover: string;
+ primarySubtle: string;
+ primaryGlow: string;
+ success: string;
+ successForeground: string;
+ successSubtle: string;
+ warning: string;
+ warningForeground: string;
+ warningSubtle: string;
+ danger: string;
+ dangerForeground: string;
+ dangerSubtle: string;
+ info: string;
+ infoSubtle: string;
+ defaultBg: string;
+ defaultForeground: string;
+ fieldBackground: string;
+ fieldForeground: string;
+ fieldPlaceholder: string;
+ muted: string;
+ overlay: string;
+ overlayForeground: string;
+ scrollbar: string;
+ segment: string;
+ segmentForeground: string;
+ surface: string;
+ surfaceForeground: string;
+ surfaceSecondary: string;
+ surfaceSecondaryForeground: string;
+ surfaceTertiary: string;
+ surfaceTertiaryForeground: string;
+ bgDeep: string;
+ bg: string;
+ bgElevated: string;
+ bgSurface: string;
+ bgHover: string;
+ bgActive: string;
+ border: string;
+ borderSubtle: string;
+ borderFocus: string;
+ text: string;
+ textSecondary: string;
+ textTertiary: string;
+ textInverse: string;
+ glass: string;
+ glassBorder: string;
+ shadowSm: string;
+ shadowMd: string;
+ shadowLg: string;
+ shadowGlow: string;
+}
+/** 通用基础 token:组件和布局都可复用 */
+export interface FoundationTokens {
+ radiusSm: string;
+ radiusMd: string;
+ radiusLg: string;
+ radiusXl: string;
+ fieldRadius: string;
+ fontSans: string;
+ fontMono: string;
+ transition: string;
+ transitionSlow: string;
+}
+/** 应用壳层 token:不建议在通用组件中直接依赖 */
+export interface AppShellTokens {
+ sidebarWidth: string;
+ sidebarCollapsed: string;
+ topbarHeight: string;
+}
+/** 不随主题变化的 token */
+export interface StaticTokens extends FoundationTokens, AppShellTokens {
+}
+export interface StaticTokenGroups {
+ foundation: FoundationTokens;
+ appShell: AppShellTokens;
+}
+export type ThemeName = 'dark' | 'light';
+export interface ThemeScopeOptions {
+ scopeSelector?: string;
+ themeAttribute?: string;
+ prefix?: string;
+}
+export interface ThemeCSSOptions extends ThemeScopeOptions {
+}
+export interface ThemeInjectionOptions extends ThemeScopeOptions {
+ styleId?: string;
+ targetDocument?: Document;
+}
+export interface ThemeSetOptions {
+ target?: HTMLElement;
+ themeAttribute?: string;
+ storageKey?: string;
+}
+export interface ThemeStorageOptions {
+ storageKey?: string;
+}
+export interface CssVarOptions {
+ prefix?: string;
+}
+export interface TailwindBridgeOptions {
+ prefix?: string;
+}
+/** Elevation context — UI 层级上下文,用于自动调整子组件的 token 值 */
+export type ElevationContext = 'modal' | 'dropdown';
diff --git a/theme/dist/types.js b/theme/dist/types.js
new file mode 100644
index 0000000..cb0ff5c
--- /dev/null
+++ b/theme/dist/types.js
@@ -0,0 +1 @@
+export {};
diff --git a/frontend/package-lock.json b/theme/package-lock.json
similarity index 91%
rename from frontend/package-lock.json
rename to theme/package-lock.json
index 02c227f..0107030 100644
--- a/frontend/package-lock.json
+++ b/theme/package-lock.json
@@ -1,12 +1,13 @@
{
- "name": "@airgate/theme",
- "version": "2.0.0",
+ "name": "@doudou-start/airgate-theme",
+ "version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
- "name": "@airgate/theme",
- "version": "2.0.0",
+ "name": "@doudou-start/airgate-theme",
+ "version": "1.0.0",
+ "license": "MIT",
"devDependencies": {
"@types/react": "^19.0.0",
"react": "^19.0.0",
diff --git a/frontend/package.json b/theme/package.json
similarity index 68%
rename from frontend/package.json
rename to theme/package.json
index d5c6850..c69c029 100644
--- a/frontend/package.json
+++ b/theme/package.json
@@ -1,8 +1,19 @@
{
- "name": "@airgate/theme",
- "version": "2.0.0",
+ "name": "@doudou-start/airgate-theme",
+ "version": "1.0.0",
+ "description": "AirGate 插件前端主题、样式隔离和公共组件包",
"type": "module",
"packageManager": "npm@11.6.2",
+ "license": "MIT",
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/DouDOU-start/airgate-sdk.git",
+ "directory": "theme"
+ },
+ "publishConfig": {
+ "registry": "https://registry.npmjs.org",
+ "access": "public"
+ },
"main": "dist/index.js",
"types": "dist/index.d.ts",
"exports": {
@@ -32,6 +43,7 @@
],
"scripts": {
"build": "tsc",
+ "prepack": "npm run build",
"dev": "tsc --watch"
},
"peerDependencies": {
diff --git a/frontend/src/css.ts b/theme/src/css.ts
similarity index 100%
rename from frontend/src/css.ts
rename to theme/src/css.ts
diff --git a/frontend/src/helpers.ts b/theme/src/helpers.ts
similarity index 100%
rename from frontend/src/helpers.ts
rename to theme/src/helpers.ts
diff --git a/frontend/src/index.ts b/theme/src/index.ts
similarity index 100%
rename from frontend/src/index.ts
rename to theme/src/index.ts
diff --git a/frontend/src/plugin.tsx b/theme/src/plugin.tsx
similarity index 100%
rename from frontend/src/plugin.tsx
rename to theme/src/plugin.tsx
diff --git a/frontend/src/tailwind.ts b/theme/src/tailwind.ts
similarity index 100%
rename from frontend/src/tailwind.ts
rename to theme/src/tailwind.ts
diff --git a/frontend/src/tokens.ts b/theme/src/tokens.ts
similarity index 100%
rename from frontend/src/tokens.ts
rename to theme/src/tokens.ts
diff --git a/frontend/src/types.ts b/theme/src/types.ts
similarity index 100%
rename from frontend/src/types.ts
rename to theme/src/types.ts
diff --git a/frontend/tsconfig.json b/theme/tsconfig.json
similarity index 100%
rename from frontend/tsconfig.json
rename to theme/tsconfig.json
From 1e0ada5ac9ff852c832a663101da813fc447cc6b Mon Sep 17 00:00:00 2001
From: huangenjun <1021217094@qq.com>
Date: Tue, 12 May 2026 14:42:02 +0800
Subject: [PATCH 05/11] =?UTF-8?q?=E5=AF=B9=E9=BD=90=E6=8F=92=E4=BB=B6?=
=?UTF-8?q?=E5=8D=8F=E8=AE=AE=E7=89=88=E6=9C=AC=E4=B8=BA=20v1?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
README.md | 2 +-
protocol/proto/plugin.pb.go | 694 +++++++++++++++----------------
protocol/proto/plugin.proto | 2 +-
protocol/proto/plugin_grpc.pb.go | 68 +--
runtimego/grpc/handshake.go | 4 +-
5 files changed, 385 insertions(+), 385 deletions(-)
diff --git a/README.md b/README.md
index 03251d0..745ab82 100644
--- a/README.md
+++ b/README.md
@@ -62,7 +62,7 @@ import (
| 目录 | 用途 | 谁会用 |
|---|---|---|
| `sdkgo/` | Go 插件接口、共享类型、Capability、Host 调用类型 | 插件作者 |
-| `protocol/proto/` | `airgate.plugin.v2` protobuf 协议和生成代码 | Core / runtime |
+| `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、公共组件 | 插件前端 |
diff --git a/protocol/proto/plugin.pb.go b/protocol/proto/plugin.pb.go
index 5e8a4b4..a471acc 100644
--- a/protocol/proto/plugin.pb.go
+++ b/protocol/proto/plugin.pb.go
@@ -1787,7 +1787,7 @@ func (x *Usage) GetMetadata() map[string]string {
// ForwardOutcome 插件对一次 Forward 的完整判决结果。
type ForwardOutcome struct {
state protoimpl.MessageState `protogen:"open.v1"`
- Kind OutcomeKind `protobuf:"varint,1,opt,name=kind,proto3,enum=airgate.plugin.v2.OutcomeKind" json:"kind,omitempty"`
+ Kind OutcomeKind `protobuf:"varint,1,opt,name=kind,proto3,enum=airgate.plugin.v1.OutcomeKind" json:"kind,omitempty"`
Upstream *UpstreamResponse `protobuf:"bytes,2,opt,name=upstream,proto3" json:"upstream,omitempty"`
Usage *Usage `protobuf:"bytes,3,opt,name=usage,proto3" json:"usage,omitempty"`
DurationMs int64 `protobuf:"varint,4,opt,name=duration_ms,json=durationMs,proto3" json:"duration_ms,omitempty"`
@@ -2351,7 +2351,7 @@ func (x *RunBackgroundTaskRequest) GetName() string {
type WebSocketFrame struct {
state protoimpl.MessageState `protogen:"open.v1"`
- Type WebSocketFrame_FrameType `protobuf:"varint,1,opt,name=type,proto3,enum=airgate.plugin.v2.WebSocketFrame_FrameType" json:"type,omitempty"`
+ Type WebSocketFrame_FrameType `protobuf:"varint,1,opt,name=type,proto3,enum=airgate.plugin.v1.WebSocketFrame_FrameType" json:"type,omitempty"`
Data []byte `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"`
// 仅 CONNECT 帧使用
ConnectInfo *WebSocketConnectInfo `protobuf:"bytes,3,opt,name=connect_info,json=connectInfo,proto3" json:"connect_info,omitempty"`
@@ -3406,7 +3406,7 @@ func (x *MiddlewareEvent) GetResponseHeaders() map[string]*HeaderValues {
// MiddlewareDecision OnForwardBegin 的输出:放行 / 拒绝 / 改请求。
type MiddlewareDecision struct {
state protoimpl.MessageState `protogen:"open.v1"`
- Action MiddlewareDecision_Action `protobuf:"varint,1,opt,name=action,proto3,enum=airgate.plugin.v2.MiddlewareDecision_Action" json:"action,omitempty"`
+ Action MiddlewareDecision_Action `protobuf:"varint,1,opt,name=action,proto3,enum=airgate.plugin.v1.MiddlewareDecision_Action" json:"action,omitempty"`
// action=DENY 时的错误码 / 文案(对用户可见)
DenyStatusCode int32 `protobuf:"varint,10,opt,name=deny_status_code,json=denyStatusCode,proto3" json:"deny_status_code,omitempty"` // 默认 403 if Action=DENY and 未指定
DenyMessage string `protobuf:"bytes,11,opt,name=deny_message,json=denyMessage,proto3" json:"deny_message,omitempty"`
@@ -4143,7 +4143,7 @@ var File_plugin_proto protoreflect.FileDescriptor
const file_plugin_proto_rawDesc = "" +
"\n" +
- "\fplugin.proto\x12\x11airgate.plugin.v2\"\a\n" +
+ "\fplugin.proto\x12\x11airgate.plugin.v1\"\a\n" +
"\x05Empty\"&\n" +
"\x0eStringResponse\x12\x14\n" +
"\x05value\x18\x01 \x01(\tR\x05value\"&\n" +
@@ -4156,18 +4156,18 @@ const file_plugin_proto_rawDesc = "" +
"\vdescription\x18\x04 \x01(\tR\vdescription\x12\x16\n" +
"\x06author\x18\x05 \x01(\tR\x06author\x12\x12\n" +
"\x04type\x18\x06 \x01(\tR\x04type\x12H\n" +
- "\raccount_types\x18\a \x03(\v2#.airgate.plugin.v2.AccountTypeProtoR\faccountTypes\x12K\n" +
- "\x0efrontend_pages\x18\b \x03(\v2$.airgate.plugin.v2.FrontendPageProtoR\rfrontendPages\x12Q\n" +
- "\x10frontend_widgets\x18\t \x03(\v2&.airgate.plugin.v2.FrontendWidgetProtoR\x0ffrontendWidgets\x12\x1f\n" +
+ "\raccount_types\x18\a \x03(\v2#.airgate.plugin.v1.AccountTypeProtoR\faccountTypes\x12K\n" +
+ "\x0efrontend_pages\x18\b \x03(\v2$.airgate.plugin.v1.FrontendPageProtoR\rfrontendPages\x12Q\n" +
+ "\x10frontend_widgets\x18\t \x03(\v2&.airgate.plugin.v1.FrontendWidgetProtoR\x0ffrontendWidgets\x12\x1f\n" +
"\vsdk_version\x18\n" +
" \x01(\tR\n" +
"sdkVersion\x12\"\n" +
"\fdependencies\x18\v \x03(\tR\fdependencies\x12H\n" +
- "\rconfig_schema\x18\f \x03(\v2#.airgate.plugin.v2.ConfigFieldProtoR\fconfigSchema\x12/\n" +
+ "\rconfig_schema\x18\f \x03(\v2#.airgate.plugin.v1.ConfigFieldProtoR\fconfigSchema\x12/\n" +
"\x13instruction_presets\x18\r \x03(\tR\x12instructionPresets\x12\"\n" +
"\fcapabilities\x18\x0e \x03(\tR\fcapabilities\x12\x1a\n" +
"\bpriority\x18\x0f \x01(\x05R\bpriority\x12O\n" +
- "\bmetadata\x18\x10 \x03(\v23.airgate.plugin.v2.PluginInfoResponse.MetadataEntryR\bmetadata\x1a;\n" +
+ "\bmetadata\x18\x10 \x03(\v23.airgate.plugin.v1.PluginInfoResponse.MetadataEntryR\bmetadata\x1a;\n" +
"\rMetadataEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xd3\x01\n" +
@@ -4183,7 +4183,7 @@ const file_plugin_proto_rawDesc = "" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
"\x05label\x18\x02 \x01(\tR\x05label\x12 \n" +
"\vdescription\x18\x03 \x01(\tR\vdescription\x12?\n" +
- "\x06fields\x18\x04 \x03(\v2'.airgate.plugin.v2.CredentialFieldProtoR\x06fields\"\xb5\x01\n" +
+ "\x06fields\x18\x04 \x03(\v2'.airgate.plugin.v1.CredentialFieldProtoR\x06fields\"\xb5\x01\n" +
"\x14CredentialFieldProto\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
"\x05label\x18\x02 \x01(\tR\x05label\x12\x12\n" +
@@ -4203,7 +4203,7 @@ const file_plugin_proto_rawDesc = "" +
"entry_file\x18\x02 \x01(\tR\tentryFile\x12\x14\n" +
"\x05title\x18\x03 \x01(\tR\x05title\"\xdc\x01\n" +
"\vInitRequest\x12B\n" +
- "\x06config\x18\x01 \x03(\v2*.airgate.plugin.v2.InitRequest.ConfigEntryR\x06config\x12\x1b\n" +
+ "\x06config\x18\x01 \x03(\v2*.airgate.plugin.v1.InitRequest.ConfigEntryR\x06config\x12\x1b\n" +
"\tlog_level\x18\x02 \x01(\tR\blogLevel\x121\n" +
"\x15core_invoke_broker_id\x18\x03 \x01(\rR\x12coreInvokeBrokerId\x1a9\n" +
"\vConfigEntry\x12\x10\n" +
@@ -4215,22 +4215,22 @@ const file_plugin_proto_rawDesc = "" +
"\x0econtext_window\x18\x03 \x01(\x03R\rcontextWindow\x12*\n" +
"\x11max_output_tokens\x18\x04 \x01(\x03R\x0fmaxOutputTokens\x12\"\n" +
"\fcapabilities\x18\x05 \x03(\tR\fcapabilities\x12K\n" +
- "\bmetadata\x18\x06 \x03(\v2/.airgate.plugin.v2.ModelInfoProto.MetadataEntryR\bmetadata\x1a;\n" +
+ "\bmetadata\x18\x06 \x03(\v2/.airgate.plugin.v1.ModelInfoProto.MetadataEntryR\bmetadata\x1a;\n" +
"\rMetadataEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"K\n" +
"\x0eModelsResponse\x129\n" +
- "\x06models\x18\x01 \x03(\v2!.airgate.plugin.v2.ModelInfoProtoR\x06models\"\xf4\x01\n" +
+ "\x06models\x18\x01 \x03(\v2!.airgate.plugin.v1.ModelInfoProtoR\x06models\"\xf4\x01\n" +
"\x14RouteDefinitionProto\x12\x16\n" +
"\x06method\x18\x01 \x01(\tR\x06method\x12\x12\n" +
"\x04path\x18\x02 \x01(\tR\x04path\x12 \n" +
"\vdescription\x18\x03 \x01(\tR\vdescription\x12Q\n" +
- "\bmetadata\x18\x04 \x03(\v25.airgate.plugin.v2.RouteDefinitionProto.MetadataEntryR\bmetadata\x1a;\n" +
+ "\bmetadata\x18\x04 \x03(\v25.airgate.plugin.v1.RouteDefinitionProto.MetadataEntryR\bmetadata\x1a;\n" +
"\rMetadataEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"Q\n" +
"\x0eRoutesResponse\x12?\n" +
- "\x06routes\x18\x01 \x03(\v2'.airgate.plugin.v2.RouteDefinitionProtoR\x06routes\"\xaa\x01\n" +
+ "\x06routes\x18\x01 \x03(\v2'.airgate.plugin.v1.RouteDefinitionProtoR\x06routes\"\xaa\x01\n" +
"\fAccountProto\x12\x0e\n" +
"\x02id\x18\x01 \x01(\x03R\x02id\x12\x12\n" +
"\x04name\x18\x02 \x01(\tR\x04name\x12\x1a\n" +
@@ -4240,28 +4240,28 @@ const file_plugin_proto_rawDesc = "" +
"\tproxy_url\x18\x06 \x01(\tR\bproxyUrl\"\xd8\x02\n" +
"\x0eForwardRequest\x12\x12\n" +
"\x04body\x18\a \x01(\fR\x04body\x12H\n" +
- "\aheaders\x18\b \x03(\v2..airgate.plugin.v2.ForwardRequest.HeadersEntryR\aheaders\x12\x14\n" +
+ "\aheaders\x18\b \x03(\v2..airgate.plugin.v1.ForwardRequest.HeadersEntryR\aheaders\x12\x14\n" +
"\x05model\x18\t \x01(\tR\x05model\x12\x16\n" +
"\x06stream\x18\n" +
" \x01(\bR\x06stream\x129\n" +
- "\aaccount\x18\v \x01(\v2\x1f.airgate.plugin.v2.AccountProtoR\aaccount\x1a[\n" +
+ "\aaccount\x18\v \x01(\v2\x1f.airgate.plugin.v1.AccountProtoR\aaccount\x1a[\n" +
"\fHeadersEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x125\n" +
- "\x05value\x18\x02 \x01(\v2\x1f.airgate.plugin.v2.HeaderValuesR\x05value:\x028\x01J\x04\b\x01\x10\x02J\x04\b\x02\x10\x03J\x04\b\x03\x10\x04J\x04\b\x04\x10\x05J\x04\b\x05\x10\x06J\x04\b\x06\x10\a\"\xf0\x01\n" +
+ "\x05value\x18\x02 \x01(\v2\x1f.airgate.plugin.v1.HeaderValuesR\x05value:\x028\x01J\x04\b\x01\x10\x02J\x04\b\x02\x10\x03J\x04\b\x03\x10\x04J\x04\b\x04\x10\x05J\x04\b\x05\x10\x06J\x04\b\x06\x10\a\"\xf0\x01\n" +
"\x10UpstreamResponse\x12\x1f\n" +
"\vstatus_code\x18\x01 \x01(\x05R\n" +
"statusCode\x12J\n" +
- "\aheaders\x18\x02 \x03(\v20.airgate.plugin.v2.UpstreamResponse.HeadersEntryR\aheaders\x12\x12\n" +
+ "\aheaders\x18\x02 \x03(\v20.airgate.plugin.v1.UpstreamResponse.HeadersEntryR\aheaders\x12\x12\n" +
"\x04body\x18\x03 \x01(\fR\x04body\x1a[\n" +
"\fHeadersEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x125\n" +
- "\x05value\x18\x02 \x01(\v2\x1f.airgate.plugin.v2.HeaderValuesR\x05value:\x028\x01\"\xec\x01\n" +
+ "\x05value\x18\x02 \x01(\v2\x1f.airgate.plugin.v1.HeaderValuesR\x05value:\x028\x01\"\xec\x01\n" +
"\x0eUsageAttribute\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
"\x05label\x18\x02 \x01(\tR\x05label\x12\x12\n" +
"\x04kind\x18\x03 \x01(\tR\x04kind\x12\x14\n" +
"\x05value\x18\x04 \x01(\tR\x05value\x12K\n" +
- "\bmetadata\x18\x05 \x03(\v2/.airgate.plugin.v2.UsageAttribute.MetadataEntryR\bmetadata\x1a;\n" +
+ "\bmetadata\x18\x05 \x03(\v2/.airgate.plugin.v1.UsageAttribute.MetadataEntryR\bmetadata\x1a;\n" +
"\rMetadataEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xb9\x02\n" +
@@ -4273,7 +4273,7 @@ const file_plugin_proto_rawDesc = "" +
"\x05value\x18\x05 \x01(\x01R\x05value\x12!\n" +
"\faccount_cost\x18\x06 \x01(\x01R\vaccountCost\x12\x1a\n" +
"\bcurrency\x18\a \x01(\tR\bcurrency\x12H\n" +
- "\bmetadata\x18\b \x03(\v2,.airgate.plugin.v2.UsageMetric.MetadataEntryR\bmetadata\x1a;\n" +
+ "\bmetadata\x18\b \x03(\v2,.airgate.plugin.v1.UsageMetric.MetadataEntryR\bmetadata\x1a;\n" +
"\rMetadataEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xcf\x02\n" +
@@ -4284,7 +4284,7 @@ const file_plugin_proto_rawDesc = "" +
"\tuser_cost\x18\x04 \x01(\x01R\buserCost\x12-\n" +
"\x12billing_multiplier\x18\x05 \x01(\x01R\x11billingMultiplier\x12\x1a\n" +
"\bcurrency\x18\x06 \x01(\tR\bcurrency\x12L\n" +
- "\bmetadata\x18\a \x03(\v20.airgate.plugin.v2.UsageCostDetail.MetadataEntryR\bmetadata\x1a;\n" +
+ "\bmetadata\x18\a \x03(\v20.airgate.plugin.v1.UsageCostDetail.MetadataEntryR\bmetadata\x1a;\n" +
"\rMetadataEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xad\x04\n" +
@@ -4296,40 +4296,40 @@ const file_plugin_proto_rawDesc = "" +
"\bcurrency\x18\x05 \x01(\tR\bcurrency\x12\x18\n" +
"\asummary\x18\x06 \x01(\tR\asummary\x12$\n" +
"\x0efirst_token_ms\x18\a \x01(\x03R\ffirstTokenMs\x128\n" +
- "\ametrics\x18\b \x03(\v2\x1e.airgate.plugin.v2.UsageMetricR\ametrics\x12A\n" +
+ "\ametrics\x18\b \x03(\v2\x1e.airgate.plugin.v1.UsageMetricR\ametrics\x12A\n" +
"\n" +
- "attributes\x18\t \x03(\v2!.airgate.plugin.v2.UsageAttributeR\n" +
+ "attributes\x18\t \x03(\v2!.airgate.plugin.v1.UsageAttributeR\n" +
"attributes\x12E\n" +
"\fcost_details\x18\n" +
- " \x03(\v2\".airgate.plugin.v2.UsageCostDetailR\vcostDetails\x12B\n" +
- "\bmetadata\x18\v \x03(\v2&.airgate.plugin.v2.Usage.MetadataEntryR\bmetadata\x1a;\n" +
+ " \x03(\v2\".airgate.plugin.v1.UsageCostDetailR\vcostDetails\x12B\n" +
+ "\bmetadata\x18\v \x03(\v2&.airgate.plugin.v1.Usage.MetadataEntryR\bmetadata\x1a;\n" +
"\rMetadataEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xc7\x03\n" +
"\x0eForwardOutcome\x122\n" +
- "\x04kind\x18\x01 \x01(\x0e2\x1e.airgate.plugin.v2.OutcomeKindR\x04kind\x12?\n" +
- "\bupstream\x18\x02 \x01(\v2#.airgate.plugin.v2.UpstreamResponseR\bupstream\x12.\n" +
- "\x05usage\x18\x03 \x01(\v2\x18.airgate.plugin.v2.UsageR\x05usage\x12\x1f\n" +
+ "\x04kind\x18\x01 \x01(\x0e2\x1e.airgate.plugin.v1.OutcomeKindR\x04kind\x12?\n" +
+ "\bupstream\x18\x02 \x01(\v2#.airgate.plugin.v1.UpstreamResponseR\bupstream\x12.\n" +
+ "\x05usage\x18\x03 \x01(\v2\x18.airgate.plugin.v1.UsageR\x05usage\x12\x1f\n" +
"\vduration_ms\x18\x04 \x01(\x03R\n" +
"durationMs\x12$\n" +
"\x0eretry_after_ms\x18\x05 \x01(\x03R\fretryAfterMs\x12\x16\n" +
"\x06reason\x18\x06 \x01(\tR\x06reason\x12j\n" +
- "\x13updated_credentials\x18\a \x03(\v29.airgate.plugin.v2.ForwardOutcome.UpdatedCredentialsEntryR\x12updatedCredentials\x1aE\n" +
+ "\x13updated_credentials\x18\a \x03(\v29.airgate.plugin.v1.ForwardOutcome.UpdatedCredentialsEntryR\x12updatedCredentials\x1aE\n" +
"\x17UpdatedCredentialsEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xc4\x02\n" +
"\fForwardChunk\x12\x12\n" +
"\x04data\x18\x01 \x01(\fR\x04data\x12\x12\n" +
"\x04done\x18\x02 \x01(\bR\x04done\x12F\n" +
- "\rfinal_outcome\x18\x03 \x01(\v2!.airgate.plugin.v2.ForwardOutcomeR\ffinalOutcome\x12\x1f\n" +
+ "\rfinal_outcome\x18\x03 \x01(\v2!.airgate.plugin.v1.ForwardOutcomeR\ffinalOutcome\x12\x1f\n" +
"\vstatus_code\x18\x04 \x01(\x05R\n" +
"statusCode\x12F\n" +
- "\aheaders\x18\x05 \x03(\v2,.airgate.plugin.v2.ForwardChunk.HeadersEntryR\aheaders\x1a[\n" +
+ "\aheaders\x18\x05 \x03(\v2,.airgate.plugin.v1.ForwardChunk.HeadersEntryR\aheaders\x1a[\n" +
"\fHeadersEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x125\n" +
- "\x05value\x18\x02 \x01(\v2\x1f.airgate.plugin.v2.HeaderValuesR\x05value:\x028\x01\"\xae\x01\n" +
+ "\x05value\x18\x02 \x01(\v2\x1f.airgate.plugin.v1.HeaderValuesR\x05value:\x028\x01\"\xae\x01\n" +
"\x12CredentialsRequest\x12X\n" +
- "\vcredentials\x18\x01 \x03(\v26.airgate.plugin.v2.CredentialsRequest.CredentialsEntryR\vcredentials\x1a>\n" +
+ "\vcredentials\x18\x01 \x03(\v26.airgate.plugin.v1.CredentialsRequest.CredentialsEntryR\vcredentials\x1a>\n" +
"\x10CredentialsEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xa8\x02\n" +
@@ -4337,46 +4337,46 @@ const file_plugin_proto_rawDesc = "" +
"\x06method\x18\x01 \x01(\tR\x06method\x12\x12\n" +
"\x04path\x18\x02 \x01(\tR\x04path\x12\x14\n" +
"\x05query\x18\x03 \x01(\tR\x05query\x12E\n" +
- "\aheaders\x18\x04 \x03(\v2+.airgate.plugin.v2.HttpRequest.HeadersEntryR\aheaders\x12\x12\n" +
+ "\aheaders\x18\x04 \x03(\v2+.airgate.plugin.v1.HttpRequest.HeadersEntryR\aheaders\x12\x12\n" +
"\x04body\x18\x05 \x01(\fR\x04body\x12\x1f\n" +
"\vremote_addr\x18\x06 \x01(\tR\n" +
"remoteAddr\x1a[\n" +
"\fHeadersEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x125\n" +
- "\x05value\x18\x02 \x01(\v2\x1f.airgate.plugin.v2.HeaderValuesR\x05value:\x028\x01\"\xe8\x01\n" +
+ "\x05value\x18\x02 \x01(\v2\x1f.airgate.plugin.v1.HeaderValuesR\x05value:\x028\x01\"\xe8\x01\n" +
"\fHttpResponse\x12\x1f\n" +
"\vstatus_code\x18\x01 \x01(\x05R\n" +
"statusCode\x12F\n" +
- "\aheaders\x18\x02 \x03(\v2,.airgate.plugin.v2.HttpResponse.HeadersEntryR\aheaders\x12\x12\n" +
+ "\aheaders\x18\x02 \x03(\v2,.airgate.plugin.v1.HttpResponse.HeadersEntryR\aheaders\x12\x12\n" +
"\x04body\x18\x03 \x01(\fR\x04body\x1a[\n" +
"\fHeadersEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x125\n" +
- "\x05value\x18\x02 \x01(\v2\x1f.airgate.plugin.v2.HeaderValuesR\x05value:\x028\x01\"\x86\x02\n" +
+ "\x05value\x18\x02 \x01(\v2\x1f.airgate.plugin.v1.HeaderValuesR\x05value:\x028\x01\"\x86\x02\n" +
"\x11HttpResponseChunk\x12\x12\n" +
"\x04data\x18\x01 \x01(\fR\x04data\x12\x12\n" +
"\x04done\x18\x02 \x01(\bR\x04done\x12\x1f\n" +
"\vstatus_code\x18\x03 \x01(\x05R\n" +
"statusCode\x12K\n" +
- "\aheaders\x18\x04 \x03(\v21.airgate.plugin.v2.HttpResponseChunk.HeadersEntryR\aheaders\x1a[\n" +
+ "\aheaders\x18\x04 \x03(\v21.airgate.plugin.v1.HttpResponseChunk.HeadersEntryR\aheaders\x1a[\n" +
"\fHeadersEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x125\n" +
- "\x05value\x18\x02 \x01(\v2\x1f.airgate.plugin.v2.HeaderValuesR\x05value:\x028\x01\"J\n" +
+ "\x05value\x18\x02 \x01(\v2\x1f.airgate.plugin.v1.HeaderValuesR\x05value:\x028\x01\"J\n" +
"\x13BackgroundTaskProto\x12\x12\n" +
"\x04name\x18\x01 \x01(\tR\x04name\x12\x1f\n" +
"\vinterval_ms\x18\x02 \x01(\x03R\n" +
"intervalMs\"W\n" +
"\x17BackgroundTasksResponse\x12<\n" +
- "\x05tasks\x18\x01 \x03(\v2&.airgate.plugin.v2.BackgroundTaskProtoR\x05tasks\".\n" +
+ "\x05tasks\x18\x01 \x03(\v2&.airgate.plugin.v1.BackgroundTaskProtoR\x05tasks\".\n" +
"\x18RunBackgroundTaskRequest\x12\x12\n" +
"\x04name\x18\x01 \x01(\tR\x04name\"\xf7\x02\n" +
"\x0eWebSocketFrame\x12?\n" +
- "\x04type\x18\x01 \x01(\x0e2+.airgate.plugin.v2.WebSocketFrame.FrameTypeR\x04type\x12\x12\n" +
+ "\x04type\x18\x01 \x01(\x0e2+.airgate.plugin.v1.WebSocketFrame.FrameTypeR\x04type\x12\x12\n" +
"\x04data\x18\x02 \x01(\fR\x04data\x12J\n" +
- "\fconnect_info\x18\x03 \x01(\v2'.airgate.plugin.v2.WebSocketConnectInfoR\vconnectInfo\x12\x1d\n" +
+ "\fconnect_info\x18\x03 \x01(\v2'.airgate.plugin.v1.WebSocketConnectInfoR\vconnectInfo\x12\x1d\n" +
"\n" +
"close_code\x18\x04 \x01(\x05R\tcloseCode\x12!\n" +
"\fclose_reason\x18\x05 \x01(\tR\vcloseReason\x12;\n" +
- "\aoutcome\x18\x06 \x01(\v2!.airgate.plugin.v2.ForwardOutcomeR\aoutcome\"E\n" +
+ "\aoutcome\x18\x06 \x01(\v2!.airgate.plugin.v1.ForwardOutcomeR\aoutcome\"E\n" +
"\tFrameType\x12\v\n" +
"\aCONNECT\x10\x00\x12\b\n" +
"\x04TEXT\x10\x01\x12\n" +
@@ -4388,28 +4388,28 @@ const file_plugin_proto_rawDesc = "" +
"\x14WebSocketConnectInfo\x12\x12\n" +
"\x04path\x18\x01 \x01(\tR\x04path\x12\x14\n" +
"\x05query\x18\x02 \x01(\tR\x05query\x12N\n" +
- "\aheaders\x18\x03 \x03(\v24.airgate.plugin.v2.WebSocketConnectInfo.HeadersEntryR\aheaders\x12\x1f\n" +
+ "\aheaders\x18\x03 \x03(\v24.airgate.plugin.v1.WebSocketConnectInfo.HeadersEntryR\aheaders\x12\x1f\n" +
"\vremote_addr\x18\x04 \x01(\tR\n" +
"remoteAddr\x12#\n" +
"\rconnection_id\x18\x05 \x01(\tR\fconnectionId\x129\n" +
- "\aaccount\x18\f \x01(\v2\x1f.airgate.plugin.v2.AccountProtoR\aaccount\x1a[\n" +
+ "\aaccount\x18\f \x01(\v2\x1f.airgate.plugin.v1.AccountProtoR\aaccount\x1a[\n" +
"\fHeadersEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x125\n" +
- "\x05value\x18\x02 \x01(\v2\x1f.airgate.plugin.v2.HeaderValuesR\x05value:\x028\x01J\x04\b\x06\x10\aJ\x04\b\a\x10\bJ\x04\b\b\x10\tJ\x04\b\t\x10\n" +
+ "\x05value\x18\x02 \x01(\v2\x1f.airgate.plugin.v1.HeaderValuesR\x05value:\x028\x01J\x04\b\x06\x10\aJ\x04\b\a\x10\bJ\x04\b\b\x10\tJ\x04\b\t\x10\n" +
"J\x04\b\n" +
"\x10\vJ\x04\b\v\x10\f\"<\n" +
"\fWebAssetFile\x12\x12\n" +
"\x04path\x18\x01 \x01(\tR\x04path\x12\x18\n" +
"\acontent\x18\x02 \x01(\fR\acontent\"i\n" +
"\x11WebAssetsResponse\x125\n" +
- "\x05files\x18\x01 \x03(\v2\x1f.airgate.plugin.v2.WebAssetFileR\x05files\x12\x1d\n" +
+ "\x05files\x18\x01 \x03(\v2\x1f.airgate.plugin.v1.WebAssetFileR\x05files\x12\x1d\n" +
"\n" +
"has_assets\x18\x02 \x01(\bR\thasAssets\"\xf7\x01\n" +
"\x12PayloadSchemaProto\x12!\n" +
"\fcontent_type\x18\x01 \x01(\tR\vcontentType\x12\x16\n" +
"\x06schema\x18\x02 \x01(\tR\x06schema\x12\x18\n" +
"\aexample\x18\x03 \x01(\tR\aexample\x12O\n" +
- "\bmetadata\x18\x04 \x03(\v23.airgate.plugin.v2.PayloadSchemaProto.MetadataEntryR\bmetadata\x1a;\n" +
+ "\bmetadata\x18\x04 \x03(\v23.airgate.plugin.v1.PayloadSchemaProto.MetadataEntryR\bmetadata\x1a;\n" +
"\rMetadataEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xe8\x02\n" +
@@ -4417,18 +4417,18 @@ const file_plugin_proto_rawDesc = "" +
"\x06method\x18\x01 \x01(\tR\x06method\x12\x12\n" +
"\x04path\x18\x02 \x01(\tR\x04path\x12\x18\n" +
"\asummary\x18\x03 \x01(\tR\asummary\x12?\n" +
- "\arequest\x18\x04 \x01(\v2%.airgate.plugin.v2.PayloadSchemaProtoR\arequest\x12A\n" +
- "\bresponse\x18\x05 \x01(\v2%.airgate.plugin.v2.PayloadSchemaProtoR\bresponse\x12M\n" +
- "\bmetadata\x18\x06 \x03(\v21.airgate.plugin.v2.RouteSchemaProto.MetadataEntryR\bmetadata\x1a;\n" +
+ "\arequest\x18\x04 \x01(\v2%.airgate.plugin.v1.PayloadSchemaProtoR\arequest\x12A\n" +
+ "\bresponse\x18\x05 \x01(\v2%.airgate.plugin.v1.PayloadSchemaProtoR\bresponse\x12M\n" +
+ "\bmetadata\x18\x06 \x03(\v21.airgate.plugin.v1.RouteSchemaProto.MetadataEntryR\bmetadata\x1a;\n" +
"\rMetadataEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xc6\x02\n" +
"\x0fTaskSchemaProto\x12\x12\n" +
"\x04type\x18\x01 \x01(\tR\x04type\x12\x18\n" +
"\asummary\x18\x02 \x01(\tR\asummary\x12;\n" +
- "\x05input\x18\x03 \x01(\v2%.airgate.plugin.v2.PayloadSchemaProtoR\x05input\x12=\n" +
- "\x06output\x18\x04 \x01(\v2%.airgate.plugin.v2.PayloadSchemaProtoR\x06output\x12L\n" +
- "\bmetadata\x18\x05 \x03(\v20.airgate.plugin.v2.TaskSchemaProto.MetadataEntryR\bmetadata\x1a;\n" +
+ "\x05input\x18\x03 \x01(\v2%.airgate.plugin.v1.PayloadSchemaProtoR\x05input\x12=\n" +
+ "\x06output\x18\x04 \x01(\v2%.airgate.plugin.v1.PayloadSchemaProtoR\x06output\x12L\n" +
+ "\bmetadata\x18\x05 \x03(\v20.airgate.plugin.v1.TaskSchemaProto.MetadataEntryR\bmetadata\x1a;\n" +
"\rMetadataEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xa5\x02\n" +
@@ -4436,29 +4436,29 @@ const file_plugin_proto_rawDesc = "" +
"\x04type\x18\x01 \x01(\tR\x04type\x12\x16\n" +
"\x06source\x18\x02 \x01(\tR\x06source\x12\x18\n" +
"\asummary\x18\x03 \x01(\tR\asummary\x12?\n" +
- "\apayload\x18\x04 \x01(\v2%.airgate.plugin.v2.PayloadSchemaProtoR\apayload\x12M\n" +
- "\bmetadata\x18\x05 \x03(\v21.airgate.plugin.v2.EventSchemaProto.MetadataEntryR\bmetadata\x1a;\n" +
+ "\apayload\x18\x04 \x01(\v2%.airgate.plugin.v1.PayloadSchemaProtoR\apayload\x12M\n" +
+ "\bmetadata\x18\x05 \x03(\v21.airgate.plugin.v1.EventSchemaProto.MetadataEntryR\bmetadata\x1a;\n" +
"\rMetadataEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\x88\x04\n" +
"\x11InvokeSchemaProto\x12\x16\n" +
"\x06method\x18\x01 \x01(\tR\x06method\x12\x18\n" +
"\asummary\x18\x02 \x01(\tR\asummary\x12?\n" +
- "\arequest\x18\x03 \x01(\v2%.airgate.plugin.v2.PayloadSchemaProtoR\arequest\x12A\n" +
- "\bresponse\x18\x04 \x01(\v2%.airgate.plugin.v2.PayloadSchemaProtoR\bresponse\x12N\n" +
- "\bmetadata\x18\x05 \x03(\v22.airgate.plugin.v2.InvokeSchemaProto.MetadataEntryR\bmetadata\x12\x1c\n" +
+ "\arequest\x18\x03 \x01(\v2%.airgate.plugin.v1.PayloadSchemaProtoR\arequest\x12A\n" +
+ "\bresponse\x18\x04 \x01(\v2%.airgate.plugin.v1.PayloadSchemaProtoR\bresponse\x12N\n" +
+ "\bmetadata\x18\x05 \x03(\v22.airgate.plugin.v1.InvokeSchemaProto.MetadataEntryR\bmetadata\x12\x1c\n" +
"\ttransport\x18\x06 \x01(\tR\ttransport\x12H\n" +
- "\fclient_frame\x18\a \x01(\v2%.airgate.plugin.v2.PayloadSchemaProtoR\vclientFrame\x12H\n" +
- "\fserver_frame\x18\b \x01(\v2%.airgate.plugin.v2.PayloadSchemaProtoR\vserverFrame\x1a;\n" +
+ "\fclient_frame\x18\a \x01(\v2%.airgate.plugin.v1.PayloadSchemaProtoR\vclientFrame\x12H\n" +
+ "\fserver_frame\x18\b \x01(\v2%.airgate.plugin.v1.PayloadSchemaProtoR\vserverFrame\x1a;\n" +
"\rMetadataEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\x9a\x03\n" +
"\x14PluginSchemaResponse\x12;\n" +
- "\x06routes\x18\x01 \x03(\v2#.airgate.plugin.v2.RouteSchemaProtoR\x06routes\x128\n" +
- "\x05tasks\x18\x02 \x03(\v2\".airgate.plugin.v2.TaskSchemaProtoR\x05tasks\x12;\n" +
- "\x06events\x18\x03 \x03(\v2#.airgate.plugin.v2.EventSchemaProtoR\x06events\x12>\n" +
- "\ainvokes\x18\x04 \x03(\v2$.airgate.plugin.v2.InvokeSchemaProtoR\ainvokes\x12Q\n" +
- "\bmetadata\x18\x05 \x03(\v25.airgate.plugin.v2.PluginSchemaResponse.MetadataEntryR\bmetadata\x1a;\n" +
+ "\x06routes\x18\x01 \x03(\v2#.airgate.plugin.v1.RouteSchemaProtoR\x06routes\x128\n" +
+ "\x05tasks\x18\x02 \x03(\v2\".airgate.plugin.v1.TaskSchemaProtoR\x05tasks\x12;\n" +
+ "\x06events\x18\x03 \x03(\v2#.airgate.plugin.v1.EventSchemaProtoR\x06events\x12>\n" +
+ "\ainvokes\x18\x04 \x03(\v2$.airgate.plugin.v1.InvokeSchemaProtoR\ainvokes\x12Q\n" +
+ "\bmetadata\x18\x05 \x03(\v25.airgate.plugin.v1.PluginSchemaResponse.MetadataEntryR\bmetadata\x1a;\n" +
"\rMetadataEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\x84\x05\n" +
@@ -4472,16 +4472,16 @@ const file_plugin_proto_rawDesc = "" +
"\bplatform\x18\x05 \x01(\tR\bplatform\x12\x14\n" +
"\x05model\x18\x06 \x01(\tR\x05model\x12\x16\n" +
"\x06stream\x18\a \x01(\bR\x06stream\x12<\n" +
- "\testimates\x18\b \x03(\v2\x1e.airgate.plugin.v2.UsageMetricR\testimates\x12N\n" +
- "\bmetadata\x18\t \x03(\v22.airgate.plugin.v2.MiddlewareRequest.MetadataEntryR\bmetadata\x12!\n" +
+ "\testimates\x18\b \x03(\v2\x1e.airgate.plugin.v1.UsageMetricR\testimates\x12N\n" +
+ "\bmetadata\x18\t \x03(\v22.airgate.plugin.v1.MiddlewareRequest.MetadataEntryR\bmetadata\x12!\n" +
"\frequest_body\x18d \x01(\fR\vrequestBody\x12a\n" +
- "\x0frequest_headers\x18e \x03(\v28.airgate.plugin.v2.MiddlewareRequest.RequestHeadersEntryR\x0erequestHeaders\x1a;\n" +
+ "\x0frequest_headers\x18e \x03(\v28.airgate.plugin.v1.MiddlewareRequest.RequestHeadersEntryR\x0erequestHeaders\x1a;\n" +
"\rMetadataEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\x1ab\n" +
"\x13RequestHeadersEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x125\n" +
- "\x05value\x18\x02 \x01(\v2\x1f.airgate.plugin.v2.HeaderValuesR\x05value:\x028\x01\"\xb2\x06\n" +
+ "\x05value\x18\x02 \x01(\v2\x1f.airgate.plugin.v1.HeaderValuesR\x05value:\x028\x01\"\xb2\x06\n" +
"\x0fMiddlewareEvent\x12\x1d\n" +
"\n" +
"request_id\x18\x01 \x01(\tR\trequestId\x12\x17\n" +
@@ -4492,35 +4492,35 @@ const file_plugin_proto_rawDesc = "" +
"\bplatform\x18\x05 \x01(\tR\bplatform\x12\x14\n" +
"\x05model\x18\x06 \x01(\tR\x05model\x12\x16\n" +
"\x06stream\x18\a \x01(\bR\x06stream\x12<\n" +
- "\testimates\x18\b \x03(\v2\x1e.airgate.plugin.v2.UsageMetricR\testimates\x12\x1f\n" +
+ "\testimates\x18\b \x03(\v2\x1e.airgate.plugin.v1.UsageMetricR\testimates\x12\x1f\n" +
"\vstatus_code\x18\x14 \x01(\x03R\n" +
"statusCode\x12\x1f\n" +
"\vduration_ms\x18\x15 \x01(\x03R\n" +
"durationMs\x12.\n" +
- "\x05usage\x18\x16 \x01(\v2\x18.airgate.plugin.v2.UsageR\x05usage\x12\x1d\n" +
+ "\x05usage\x18\x16 \x01(\v2\x18.airgate.plugin.v1.UsageR\x05usage\x12\x1d\n" +
"\n" +
"error_kind\x18\x17 \x01(\tR\terrorKind\x12\x1b\n" +
"\terror_msg\x18\x18 \x01(\tR\berrorMsg\x12L\n" +
- "\bmetadata\x18( \x03(\v20.airgate.plugin.v2.MiddlewareEvent.MetadataEntryR\bmetadata\x12#\n" +
+ "\bmetadata\x18( \x03(\v20.airgate.plugin.v1.MiddlewareEvent.MetadataEntryR\bmetadata\x12#\n" +
"\rresponse_body\x18d \x01(\fR\fresponseBody\x12b\n" +
- "\x10response_headers\x18e \x03(\v27.airgate.plugin.v2.MiddlewareEvent.ResponseHeadersEntryR\x0fresponseHeaders\x1a;\n" +
+ "\x10response_headers\x18e \x03(\v27.airgate.plugin.v1.MiddlewareEvent.ResponseHeadersEntryR\x0fresponseHeaders\x1a;\n" +
"\rMetadataEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\x1ac\n" +
"\x14ResponseHeadersEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x125\n" +
- "\x05value\x18\x02 \x01(\v2\x1f.airgate.plugin.v2.HeaderValuesR\x05value:\x028\x01\"\x98\x04\n" +
+ "\x05value\x18\x02 \x01(\v2\x1f.airgate.plugin.v1.HeaderValuesR\x05value:\x028\x01\"\x98\x04\n" +
"\x12MiddlewareDecision\x12D\n" +
- "\x06action\x18\x01 \x01(\x0e2,.airgate.plugin.v2.MiddlewareDecision.ActionR\x06action\x12(\n" +
+ "\x06action\x18\x01 \x01(\x0e2,.airgate.plugin.v1.MiddlewareDecision.ActionR\x06action\x12(\n" +
"\x10deny_status_code\x18\n" +
" \x01(\x05R\x0edenyStatusCode\x12!\n" +
"\fdeny_message\x18\v \x01(\tR\vdenyMessage\x12V\n" +
- "\vset_headers\x18\x14 \x03(\v25.airgate.plugin.v2.MiddlewareDecision.SetHeadersEntryR\n" +
+ "\vset_headers\x18\x14 \x03(\v25.airgate.plugin.v1.MiddlewareDecision.SetHeadersEntryR\n" +
"setHeaders\x12O\n" +
- "\bmetadata\x18\x1e \x03(\v23.airgate.plugin.v2.MiddlewareDecision.MetadataEntryR\bmetadata\x1a^\n" +
+ "\bmetadata\x18\x1e \x03(\v23.airgate.plugin.v1.MiddlewareDecision.MetadataEntryR\bmetadata\x1a^\n" +
"\x0fSetHeadersEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x125\n" +
- "\x05value\x18\x02 \x01(\v2\x1f.airgate.plugin.v2.HeaderValuesR\x05value:\x028\x01\x1a;\n" +
+ "\x05value\x18\x02 \x01(\v2\x1f.airgate.plugin.v1.HeaderValuesR\x05value:\x028\x01\x1a;\n" +
"\rMetadataEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\")\n" +
@@ -4532,8 +4532,8 @@ const file_plugin_proto_rawDesc = "" +
"\x16EventSubscriptionProto\x12\x12\n" +
"\x04type\x18\x01 \x01(\tR\x04type\x12\x16\n" +
"\x06source\x18\x02 \x01(\tR\x06source\x12M\n" +
- "\x06filter\x18\x03 \x03(\v25.airgate.plugin.v2.EventSubscriptionProto.FilterEntryR\x06filter\x12S\n" +
- "\bmetadata\x18\x04 \x03(\v27.airgate.plugin.v2.EventSubscriptionProto.MetadataEntryR\bmetadata\x1a9\n" +
+ "\x06filter\x18\x03 \x03(\v25.airgate.plugin.v1.EventSubscriptionProto.FilterEntryR\x06filter\x12S\n" +
+ "\bmetadata\x18\x04 \x03(\v27.airgate.plugin.v1.EventSubscriptionProto.MetadataEntryR\bmetadata\x1a9\n" +
"\vFilterEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\x1a;\n" +
@@ -4541,7 +4541,7 @@ const file_plugin_proto_rawDesc = "" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"m\n" +
"\x1aEventSubscriptionsResponse\x12O\n" +
- "\rsubscriptions\x18\x01 \x03(\v2).airgate.plugin.v2.EventSubscriptionProtoR\rsubscriptions\"\xd9\x02\n" +
+ "\rsubscriptions\x18\x01 \x03(\v2).airgate.plugin.v1.EventSubscriptionProtoR\rsubscriptions\"\xd9\x02\n" +
"\vPluginEvent\x12\x0e\n" +
"\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n" +
"\x04type\x18\x02 \x01(\tR\x04type\x12\x16\n" +
@@ -4550,7 +4550,7 @@ const file_plugin_proto_rawDesc = "" +
"\auser_id\x18\x05 \x01(\x03R\x06userId\x12\x19\n" +
"\bgroup_id\x18\x06 \x01(\x03R\agroupId\x12\x18\n" +
"\apayload\x18\a \x01(\fR\apayload\x12H\n" +
- "\bmetadata\x18\b \x03(\v2,.airgate.plugin.v2.PluginEvent.MetadataEntryR\bmetadata\x12\x1f\n" +
+ "\bmetadata\x18\b \x03(\v2,.airgate.plugin.v1.PluginEvent.MetadataEntryR\bmetadata\x12\x1f\n" +
"\voccurred_at\x18\t \x01(\x03R\n" +
"occurredAt\x1a;\n" +
"\rMetadataEntry\x12\x10\n" +
@@ -4563,14 +4563,14 @@ const file_plugin_proto_rawDesc = "" +
"\x06method\x18\x01 \x01(\tR\x06method\x12\x18\n" +
"\apayload\x18\x02 \x01(\fR\apayload\x12'\n" +
"\x0fidempotency_key\x18\x03 \x01(\tR\x0eidempotencyKey\x12N\n" +
- "\bmetadata\x18\x04 \x03(\v22.airgate.plugin.v2.HostInvokeRequest.MetadataEntryR\bmetadata\x1a;\n" +
+ "\bmetadata\x18\x04 \x03(\v22.airgate.plugin.v1.HostInvokeRequest.MetadataEntryR\bmetadata\x1a;\n" +
"\rMetadataEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xd4\x01\n" +
"\x12HostInvokeResponse\x12\x16\n" +
"\x06status\x18\x01 \x01(\tR\x06status\x12\x18\n" +
"\apayload\x18\x02 \x01(\fR\apayload\x12O\n" +
- "\bmetadata\x18\x03 \x03(\v23.airgate.plugin.v2.HostInvokeResponse.MetadataEntryR\bmetadata\x1a;\n" +
+ "\bmetadata\x18\x03 \x03(\v23.airgate.plugin.v1.HostInvokeResponse.MetadataEntryR\bmetadata\x1a;\n" +
"\rMetadataEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xb9\x02\n" +
@@ -4579,7 +4579,7 @@ const file_plugin_proto_rawDesc = "" +
"\x05event\x18\x02 \x01(\tR\x05event\x12\x18\n" +
"\apayload\x18\x03 \x01(\fR\apayload\x12'\n" +
"\x0fidempotency_key\x18\x04 \x01(\tR\x0eidempotencyKey\x12L\n" +
- "\bmetadata\x18\x05 \x03(\v20.airgate.plugin.v2.HostStreamFrame.MetadataEntryR\bmetadata\x12\x12\n" +
+ "\bmetadata\x18\x05 \x03(\v20.airgate.plugin.v1.HostStreamFrame.MetadataEntryR\bmetadata\x12\x12\n" +
"\x04done\x18\x06 \x01(\bR\x04done\x12\x16\n" +
"\x06status\x18\a \x01(\tR\x06status\x1a;\n" +
"\rMetadataEntry\x12\x10\n" +
@@ -4604,39 +4604,39 @@ const file_plugin_proto_rawDesc = "" +
"\x1aOUTCOME_UPSTREAM_TRANSIENT\x10\x05\x12\x1a\n" +
"\x16OUTCOME_STREAM_ABORTED\x10\x062\xcb\x04\n" +
"\rPluginService\x12J\n" +
- "\aGetInfo\x12\x18.airgate.plugin.v2.Empty\x1a%.airgate.plugin.v2.PluginInfoResponse\x12@\n" +
- "\x04Init\x12\x1e.airgate.plugin.v2.InitRequest\x1a\x18.airgate.plugin.v2.Empty\x12;\n" +
- "\x05Start\x12\x18.airgate.plugin.v2.Empty\x1a\x18.airgate.plugin.v2.Empty\x12:\n" +
- "\x04Stop\x12\x18.airgate.plugin.v2.Empty\x1a\x18.airgate.plugin.v2.Empty\x12N\n" +
- "\fGetWebAssets\x12\x18.airgate.plugin.v2.Empty\x1a$.airgate.plugin.v2.WebAssetsResponse\x12N\n" +
- "\tGetSchema\x12\x18.airgate.plugin.v2.Empty\x1a'.airgate.plugin.v2.PluginSchemaResponse\x12A\n" +
- "\vHealthCheck\x12\x18.airgate.plugin.v2.Empty\x1a\x18.airgate.plugin.v2.Empty\x12P\n" +
- "\rHandleRequest\x12\x1e.airgate.plugin.v2.HttpRequest\x1a\x1f.airgate.plugin.v2.HttpResponse2\xc9\x04\n" +
+ "\aGetInfo\x12\x18.airgate.plugin.v1.Empty\x1a%.airgate.plugin.v1.PluginInfoResponse\x12@\n" +
+ "\x04Init\x12\x1e.airgate.plugin.v1.InitRequest\x1a\x18.airgate.plugin.v1.Empty\x12;\n" +
+ "\x05Start\x12\x18.airgate.plugin.v1.Empty\x1a\x18.airgate.plugin.v1.Empty\x12:\n" +
+ "\x04Stop\x12\x18.airgate.plugin.v1.Empty\x1a\x18.airgate.plugin.v1.Empty\x12N\n" +
+ "\fGetWebAssets\x12\x18.airgate.plugin.v1.Empty\x1a$.airgate.plugin.v1.WebAssetsResponse\x12N\n" +
+ "\tGetSchema\x12\x18.airgate.plugin.v1.Empty\x1a'.airgate.plugin.v1.PluginSchemaResponse\x12A\n" +
+ "\vHealthCheck\x12\x18.airgate.plugin.v1.Empty\x1a\x18.airgate.plugin.v1.Empty\x12P\n" +
+ "\rHandleRequest\x12\x1e.airgate.plugin.v1.HttpRequest\x1a\x1f.airgate.plugin.v1.HttpResponse2\xc9\x04\n" +
"\x0eGatewayService\x12J\n" +
- "\vGetPlatform\x12\x18.airgate.plugin.v2.Empty\x1a!.airgate.plugin.v2.StringResponse\x12H\n" +
- "\tGetModels\x12\x18.airgate.plugin.v2.Empty\x1a!.airgate.plugin.v2.ModelsResponse\x12H\n" +
- "\tGetRoutes\x12\x18.airgate.plugin.v2.Empty\x1a!.airgate.plugin.v2.RoutesResponse\x12O\n" +
- "\aForward\x12!.airgate.plugin.v2.ForwardRequest\x1a!.airgate.plugin.v2.ForwardOutcome\x12U\n" +
- "\rForwardStream\x12!.airgate.plugin.v2.ForwardRequest\x1a\x1f.airgate.plugin.v2.ForwardChunk0\x01\x12R\n" +
- "\x0fValidateAccount\x12%.airgate.plugin.v2.CredentialsRequest\x1a\x18.airgate.plugin.v2.Empty\x12[\n" +
- "\x0fHandleWebSocket\x12!.airgate.plugin.v2.WebSocketFrame\x1a!.airgate.plugin.v2.WebSocketFrame(\x010\x012\xe8\x04\n" +
+ "\vGetPlatform\x12\x18.airgate.plugin.v1.Empty\x1a!.airgate.plugin.v1.StringResponse\x12H\n" +
+ "\tGetModels\x12\x18.airgate.plugin.v1.Empty\x1a!.airgate.plugin.v1.ModelsResponse\x12H\n" +
+ "\tGetRoutes\x12\x18.airgate.plugin.v1.Empty\x1a!.airgate.plugin.v1.RoutesResponse\x12O\n" +
+ "\aForward\x12!.airgate.plugin.v1.ForwardRequest\x1a!.airgate.plugin.v1.ForwardOutcome\x12U\n" +
+ "\rForwardStream\x12!.airgate.plugin.v1.ForwardRequest\x1a\x1f.airgate.plugin.v1.ForwardChunk0\x01\x12R\n" +
+ "\x0fValidateAccount\x12%.airgate.plugin.v1.CredentialsRequest\x1a\x18.airgate.plugin.v1.Empty\x12[\n" +
+ "\x0fHandleWebSocket\x12!.airgate.plugin.v1.WebSocketFrame\x1a!.airgate.plugin.v1.WebSocketFrame(\x010\x012\xe8\x04\n" +
"\x10ExtensionService\x12=\n" +
- "\aMigrate\x12\x18.airgate.plugin.v2.Empty\x1a\x18.airgate.plugin.v2.Empty\x12Z\n" +
- "\x12GetBackgroundTasks\x12\x18.airgate.plugin.v2.Empty\x1a*.airgate.plugin.v2.BackgroundTasksResponse\x12Z\n" +
- "\x11RunBackgroundTask\x12+.airgate.plugin.v2.RunBackgroundTaskRequest\x1a\x18.airgate.plugin.v2.Empty\x12P\n" +
- "\rHandleRequest\x12\x1e.airgate.plugin.v2.HttpRequest\x1a\x1f.airgate.plugin.v2.HttpResponse\x12]\n" +
- "\x13HandleStreamRequest\x12\x1e.airgate.plugin.v2.HttpRequest\x1a$.airgate.plugin.v2.HttpResponseChunk0\x01\x12\\\n" +
- "\vProcessTask\x12%.airgate.plugin.v2.ProcessTaskRequest\x1a&.airgate.plugin.v2.ProcessTaskResponse\x12N\n" +
- "\fGetTaskTypes\x12\x18.airgate.plugin.v2.Empty\x1a$.airgate.plugin.v2.TaskTypesResponse2\xc0\x01\n" +
+ "\aMigrate\x12\x18.airgate.plugin.v1.Empty\x1a\x18.airgate.plugin.v1.Empty\x12Z\n" +
+ "\x12GetBackgroundTasks\x12\x18.airgate.plugin.v1.Empty\x1a*.airgate.plugin.v1.BackgroundTasksResponse\x12Z\n" +
+ "\x11RunBackgroundTask\x12+.airgate.plugin.v1.RunBackgroundTaskRequest\x1a\x18.airgate.plugin.v1.Empty\x12P\n" +
+ "\rHandleRequest\x12\x1e.airgate.plugin.v1.HttpRequest\x1a\x1f.airgate.plugin.v1.HttpResponse\x12]\n" +
+ "\x13HandleStreamRequest\x12\x1e.airgate.plugin.v1.HttpRequest\x1a$.airgate.plugin.v1.HttpResponseChunk0\x01\x12\\\n" +
+ "\vProcessTask\x12%.airgate.plugin.v1.ProcessTaskRequest\x1a&.airgate.plugin.v1.ProcessTaskResponse\x12N\n" +
+ "\fGetTaskTypes\x12\x18.airgate.plugin.v1.Empty\x1a$.airgate.plugin.v1.TaskTypesResponse2\xc0\x01\n" +
"\x11MiddlewareService\x12]\n" +
- "\x0eOnForwardBegin\x12$.airgate.plugin.v2.MiddlewareRequest\x1a%.airgate.plugin.v2.MiddlewareDecision\x12L\n" +
- "\fOnForwardEnd\x12\".airgate.plugin.v2.MiddlewareEvent\x1a\x18.airgate.plugin.v2.Empty2\xc7\x01\n" +
+ "\x0eOnForwardBegin\x12$.airgate.plugin.v1.MiddlewareRequest\x1a%.airgate.plugin.v1.MiddlewareDecision\x12L\n" +
+ "\fOnForwardEnd\x12\".airgate.plugin.v1.MiddlewareEvent\x1a\x18.airgate.plugin.v1.Empty2\xc7\x01\n" +
"\fEventService\x12`\n" +
- "\x15GetEventSubscriptions\x12\x18.airgate.plugin.v2.Empty\x1a-.airgate.plugin.v2.EventSubscriptionsResponse\x12U\n" +
- "\vHandleEvent\x12\x1e.airgate.plugin.v2.PluginEvent\x1a&.airgate.plugin.v2.EventHandleResponse2\xc6\x01\n" +
+ "\x15GetEventSubscriptions\x12\x18.airgate.plugin.v1.Empty\x1a-.airgate.plugin.v1.EventSubscriptionsResponse\x12U\n" +
+ "\vHandleEvent\x12\x1e.airgate.plugin.v1.PluginEvent\x1a&.airgate.plugin.v1.EventHandleResponse2\xc6\x01\n" +
"\x11CoreInvokeService\x12U\n" +
- "\x06Invoke\x12$.airgate.plugin.v2.HostInvokeRequest\x1a%.airgate.plugin.v2.HostInvokeResponse\x12Z\n" +
- "\fInvokeStream\x12\".airgate.plugin.v2.HostStreamFrame\x1a\".airgate.plugin.v2.HostStreamFrame(\x010\x01B4Z2github.com/DouDOU-start/airgate-sdk/protocol/protob\x06proto3"
+ "\x06Invoke\x12$.airgate.plugin.v1.HostInvokeRequest\x1a%.airgate.plugin.v1.HostInvokeResponse\x12Z\n" +
+ "\fInvokeStream\x12\".airgate.plugin.v1.HostStreamFrame\x1a\".airgate.plugin.v1.HostStreamFrame(\x010\x01B4Z2github.com/DouDOU-start/airgate-sdk/protocol/protob\x06proto3"
var (
file_plugin_proto_rawDescOnce sync.Once
@@ -4653,239 +4653,239 @@ func file_plugin_proto_rawDescGZIP() []byte {
var file_plugin_proto_enumTypes = make([]protoimpl.EnumInfo, 3)
var file_plugin_proto_msgTypes = make([]protoimpl.MessageInfo, 88)
var file_plugin_proto_goTypes = []any{
- (OutcomeKind)(0), // 0: airgate.plugin.v2.OutcomeKind
- (WebSocketFrame_FrameType)(0), // 1: airgate.plugin.v2.WebSocketFrame.FrameType
- (MiddlewareDecision_Action)(0), // 2: airgate.plugin.v2.MiddlewareDecision.Action
- (*Empty)(nil), // 3: airgate.plugin.v2.Empty
- (*StringResponse)(nil), // 4: airgate.plugin.v2.StringResponse
- (*HeaderValues)(nil), // 5: airgate.plugin.v2.HeaderValues
- (*PluginInfoResponse)(nil), // 6: airgate.plugin.v2.PluginInfoResponse
- (*ConfigFieldProto)(nil), // 7: airgate.plugin.v2.ConfigFieldProto
- (*AccountTypeProto)(nil), // 8: airgate.plugin.v2.AccountTypeProto
- (*CredentialFieldProto)(nil), // 9: airgate.plugin.v2.CredentialFieldProto
- (*FrontendPageProto)(nil), // 10: airgate.plugin.v2.FrontendPageProto
- (*FrontendWidgetProto)(nil), // 11: airgate.plugin.v2.FrontendWidgetProto
- (*InitRequest)(nil), // 12: airgate.plugin.v2.InitRequest
- (*ModelInfoProto)(nil), // 13: airgate.plugin.v2.ModelInfoProto
- (*ModelsResponse)(nil), // 14: airgate.plugin.v2.ModelsResponse
- (*RouteDefinitionProto)(nil), // 15: airgate.plugin.v2.RouteDefinitionProto
- (*RoutesResponse)(nil), // 16: airgate.plugin.v2.RoutesResponse
- (*AccountProto)(nil), // 17: airgate.plugin.v2.AccountProto
- (*ForwardRequest)(nil), // 18: airgate.plugin.v2.ForwardRequest
- (*UpstreamResponse)(nil), // 19: airgate.plugin.v2.UpstreamResponse
- (*UsageAttribute)(nil), // 20: airgate.plugin.v2.UsageAttribute
- (*UsageMetric)(nil), // 21: airgate.plugin.v2.UsageMetric
- (*UsageCostDetail)(nil), // 22: airgate.plugin.v2.UsageCostDetail
- (*Usage)(nil), // 23: airgate.plugin.v2.Usage
- (*ForwardOutcome)(nil), // 24: airgate.plugin.v2.ForwardOutcome
- (*ForwardChunk)(nil), // 25: airgate.plugin.v2.ForwardChunk
- (*CredentialsRequest)(nil), // 26: airgate.plugin.v2.CredentialsRequest
- (*HttpRequest)(nil), // 27: airgate.plugin.v2.HttpRequest
- (*HttpResponse)(nil), // 28: airgate.plugin.v2.HttpResponse
- (*HttpResponseChunk)(nil), // 29: airgate.plugin.v2.HttpResponseChunk
- (*BackgroundTaskProto)(nil), // 30: airgate.plugin.v2.BackgroundTaskProto
- (*BackgroundTasksResponse)(nil), // 31: airgate.plugin.v2.BackgroundTasksResponse
- (*RunBackgroundTaskRequest)(nil), // 32: airgate.plugin.v2.RunBackgroundTaskRequest
- (*WebSocketFrame)(nil), // 33: airgate.plugin.v2.WebSocketFrame
- (*WebSocketConnectInfo)(nil), // 34: airgate.plugin.v2.WebSocketConnectInfo
- (*WebAssetFile)(nil), // 35: airgate.plugin.v2.WebAssetFile
- (*WebAssetsResponse)(nil), // 36: airgate.plugin.v2.WebAssetsResponse
- (*PayloadSchemaProto)(nil), // 37: airgate.plugin.v2.PayloadSchemaProto
- (*RouteSchemaProto)(nil), // 38: airgate.plugin.v2.RouteSchemaProto
- (*TaskSchemaProto)(nil), // 39: airgate.plugin.v2.TaskSchemaProto
- (*EventSchemaProto)(nil), // 40: airgate.plugin.v2.EventSchemaProto
- (*InvokeSchemaProto)(nil), // 41: airgate.plugin.v2.InvokeSchemaProto
- (*PluginSchemaResponse)(nil), // 42: airgate.plugin.v2.PluginSchemaResponse
- (*MiddlewareRequest)(nil), // 43: airgate.plugin.v2.MiddlewareRequest
- (*MiddlewareEvent)(nil), // 44: airgate.plugin.v2.MiddlewareEvent
- (*MiddlewareDecision)(nil), // 45: airgate.plugin.v2.MiddlewareDecision
- (*EventSubscriptionProto)(nil), // 46: airgate.plugin.v2.EventSubscriptionProto
- (*EventSubscriptionsResponse)(nil), // 47: airgate.plugin.v2.EventSubscriptionsResponse
- (*PluginEvent)(nil), // 48: airgate.plugin.v2.PluginEvent
- (*EventHandleResponse)(nil), // 49: airgate.plugin.v2.EventHandleResponse
- (*HostInvokeRequest)(nil), // 50: airgate.plugin.v2.HostInvokeRequest
- (*HostInvokeResponse)(nil), // 51: airgate.plugin.v2.HostInvokeResponse
- (*HostStreamFrame)(nil), // 52: airgate.plugin.v2.HostStreamFrame
- (*ProcessTaskRequest)(nil), // 53: airgate.plugin.v2.ProcessTaskRequest
- (*ProcessTaskResponse)(nil), // 54: airgate.plugin.v2.ProcessTaskResponse
- (*TaskTypesResponse)(nil), // 55: airgate.plugin.v2.TaskTypesResponse
- nil, // 56: airgate.plugin.v2.PluginInfoResponse.MetadataEntry
- nil, // 57: airgate.plugin.v2.InitRequest.ConfigEntry
- nil, // 58: airgate.plugin.v2.ModelInfoProto.MetadataEntry
- nil, // 59: airgate.plugin.v2.RouteDefinitionProto.MetadataEntry
- nil, // 60: airgate.plugin.v2.ForwardRequest.HeadersEntry
- nil, // 61: airgate.plugin.v2.UpstreamResponse.HeadersEntry
- nil, // 62: airgate.plugin.v2.UsageAttribute.MetadataEntry
- nil, // 63: airgate.plugin.v2.UsageMetric.MetadataEntry
- nil, // 64: airgate.plugin.v2.UsageCostDetail.MetadataEntry
- nil, // 65: airgate.plugin.v2.Usage.MetadataEntry
- nil, // 66: airgate.plugin.v2.ForwardOutcome.UpdatedCredentialsEntry
- nil, // 67: airgate.plugin.v2.ForwardChunk.HeadersEntry
- nil, // 68: airgate.plugin.v2.CredentialsRequest.CredentialsEntry
- nil, // 69: airgate.plugin.v2.HttpRequest.HeadersEntry
- nil, // 70: airgate.plugin.v2.HttpResponse.HeadersEntry
- nil, // 71: airgate.plugin.v2.HttpResponseChunk.HeadersEntry
- nil, // 72: airgate.plugin.v2.WebSocketConnectInfo.HeadersEntry
- nil, // 73: airgate.plugin.v2.PayloadSchemaProto.MetadataEntry
- nil, // 74: airgate.plugin.v2.RouteSchemaProto.MetadataEntry
- nil, // 75: airgate.plugin.v2.TaskSchemaProto.MetadataEntry
- nil, // 76: airgate.plugin.v2.EventSchemaProto.MetadataEntry
- nil, // 77: airgate.plugin.v2.InvokeSchemaProto.MetadataEntry
- nil, // 78: airgate.plugin.v2.PluginSchemaResponse.MetadataEntry
- nil, // 79: airgate.plugin.v2.MiddlewareRequest.MetadataEntry
- nil, // 80: airgate.plugin.v2.MiddlewareRequest.RequestHeadersEntry
- nil, // 81: airgate.plugin.v2.MiddlewareEvent.MetadataEntry
- nil, // 82: airgate.plugin.v2.MiddlewareEvent.ResponseHeadersEntry
- nil, // 83: airgate.plugin.v2.MiddlewareDecision.SetHeadersEntry
- nil, // 84: airgate.plugin.v2.MiddlewareDecision.MetadataEntry
- nil, // 85: airgate.plugin.v2.EventSubscriptionProto.FilterEntry
- nil, // 86: airgate.plugin.v2.EventSubscriptionProto.MetadataEntry
- nil, // 87: airgate.plugin.v2.PluginEvent.MetadataEntry
- nil, // 88: airgate.plugin.v2.HostInvokeRequest.MetadataEntry
- nil, // 89: airgate.plugin.v2.HostInvokeResponse.MetadataEntry
- nil, // 90: airgate.plugin.v2.HostStreamFrame.MetadataEntry
+ (OutcomeKind)(0), // 0: airgate.plugin.v1.OutcomeKind
+ (WebSocketFrame_FrameType)(0), // 1: airgate.plugin.v1.WebSocketFrame.FrameType
+ (MiddlewareDecision_Action)(0), // 2: airgate.plugin.v1.MiddlewareDecision.Action
+ (*Empty)(nil), // 3: airgate.plugin.v1.Empty
+ (*StringResponse)(nil), // 4: airgate.plugin.v1.StringResponse
+ (*HeaderValues)(nil), // 5: airgate.plugin.v1.HeaderValues
+ (*PluginInfoResponse)(nil), // 6: airgate.plugin.v1.PluginInfoResponse
+ (*ConfigFieldProto)(nil), // 7: airgate.plugin.v1.ConfigFieldProto
+ (*AccountTypeProto)(nil), // 8: airgate.plugin.v1.AccountTypeProto
+ (*CredentialFieldProto)(nil), // 9: airgate.plugin.v1.CredentialFieldProto
+ (*FrontendPageProto)(nil), // 10: airgate.plugin.v1.FrontendPageProto
+ (*FrontendWidgetProto)(nil), // 11: airgate.plugin.v1.FrontendWidgetProto
+ (*InitRequest)(nil), // 12: airgate.plugin.v1.InitRequest
+ (*ModelInfoProto)(nil), // 13: airgate.plugin.v1.ModelInfoProto
+ (*ModelsResponse)(nil), // 14: airgate.plugin.v1.ModelsResponse
+ (*RouteDefinitionProto)(nil), // 15: airgate.plugin.v1.RouteDefinitionProto
+ (*RoutesResponse)(nil), // 16: airgate.plugin.v1.RoutesResponse
+ (*AccountProto)(nil), // 17: airgate.plugin.v1.AccountProto
+ (*ForwardRequest)(nil), // 18: airgate.plugin.v1.ForwardRequest
+ (*UpstreamResponse)(nil), // 19: airgate.plugin.v1.UpstreamResponse
+ (*UsageAttribute)(nil), // 20: airgate.plugin.v1.UsageAttribute
+ (*UsageMetric)(nil), // 21: airgate.plugin.v1.UsageMetric
+ (*UsageCostDetail)(nil), // 22: airgate.plugin.v1.UsageCostDetail
+ (*Usage)(nil), // 23: airgate.plugin.v1.Usage
+ (*ForwardOutcome)(nil), // 24: airgate.plugin.v1.ForwardOutcome
+ (*ForwardChunk)(nil), // 25: airgate.plugin.v1.ForwardChunk
+ (*CredentialsRequest)(nil), // 26: airgate.plugin.v1.CredentialsRequest
+ (*HttpRequest)(nil), // 27: airgate.plugin.v1.HttpRequest
+ (*HttpResponse)(nil), // 28: airgate.plugin.v1.HttpResponse
+ (*HttpResponseChunk)(nil), // 29: airgate.plugin.v1.HttpResponseChunk
+ (*BackgroundTaskProto)(nil), // 30: airgate.plugin.v1.BackgroundTaskProto
+ (*BackgroundTasksResponse)(nil), // 31: airgate.plugin.v1.BackgroundTasksResponse
+ (*RunBackgroundTaskRequest)(nil), // 32: airgate.plugin.v1.RunBackgroundTaskRequest
+ (*WebSocketFrame)(nil), // 33: airgate.plugin.v1.WebSocketFrame
+ (*WebSocketConnectInfo)(nil), // 34: airgate.plugin.v1.WebSocketConnectInfo
+ (*WebAssetFile)(nil), // 35: airgate.plugin.v1.WebAssetFile
+ (*WebAssetsResponse)(nil), // 36: airgate.plugin.v1.WebAssetsResponse
+ (*PayloadSchemaProto)(nil), // 37: airgate.plugin.v1.PayloadSchemaProto
+ (*RouteSchemaProto)(nil), // 38: airgate.plugin.v1.RouteSchemaProto
+ (*TaskSchemaProto)(nil), // 39: airgate.plugin.v1.TaskSchemaProto
+ (*EventSchemaProto)(nil), // 40: airgate.plugin.v1.EventSchemaProto
+ (*InvokeSchemaProto)(nil), // 41: airgate.plugin.v1.InvokeSchemaProto
+ (*PluginSchemaResponse)(nil), // 42: airgate.plugin.v1.PluginSchemaResponse
+ (*MiddlewareRequest)(nil), // 43: airgate.plugin.v1.MiddlewareRequest
+ (*MiddlewareEvent)(nil), // 44: airgate.plugin.v1.MiddlewareEvent
+ (*MiddlewareDecision)(nil), // 45: airgate.plugin.v1.MiddlewareDecision
+ (*EventSubscriptionProto)(nil), // 46: airgate.plugin.v1.EventSubscriptionProto
+ (*EventSubscriptionsResponse)(nil), // 47: airgate.plugin.v1.EventSubscriptionsResponse
+ (*PluginEvent)(nil), // 48: airgate.plugin.v1.PluginEvent
+ (*EventHandleResponse)(nil), // 49: airgate.plugin.v1.EventHandleResponse
+ (*HostInvokeRequest)(nil), // 50: airgate.plugin.v1.HostInvokeRequest
+ (*HostInvokeResponse)(nil), // 51: airgate.plugin.v1.HostInvokeResponse
+ (*HostStreamFrame)(nil), // 52: airgate.plugin.v1.HostStreamFrame
+ (*ProcessTaskRequest)(nil), // 53: airgate.plugin.v1.ProcessTaskRequest
+ (*ProcessTaskResponse)(nil), // 54: airgate.plugin.v1.ProcessTaskResponse
+ (*TaskTypesResponse)(nil), // 55: airgate.plugin.v1.TaskTypesResponse
+ nil, // 56: airgate.plugin.v1.PluginInfoResponse.MetadataEntry
+ nil, // 57: airgate.plugin.v1.InitRequest.ConfigEntry
+ nil, // 58: airgate.plugin.v1.ModelInfoProto.MetadataEntry
+ nil, // 59: airgate.plugin.v1.RouteDefinitionProto.MetadataEntry
+ nil, // 60: airgate.plugin.v1.ForwardRequest.HeadersEntry
+ nil, // 61: airgate.plugin.v1.UpstreamResponse.HeadersEntry
+ nil, // 62: airgate.plugin.v1.UsageAttribute.MetadataEntry
+ nil, // 63: airgate.plugin.v1.UsageMetric.MetadataEntry
+ nil, // 64: airgate.plugin.v1.UsageCostDetail.MetadataEntry
+ nil, // 65: airgate.plugin.v1.Usage.MetadataEntry
+ nil, // 66: airgate.plugin.v1.ForwardOutcome.UpdatedCredentialsEntry
+ nil, // 67: airgate.plugin.v1.ForwardChunk.HeadersEntry
+ nil, // 68: airgate.plugin.v1.CredentialsRequest.CredentialsEntry
+ nil, // 69: airgate.plugin.v1.HttpRequest.HeadersEntry
+ nil, // 70: airgate.plugin.v1.HttpResponse.HeadersEntry
+ nil, // 71: airgate.plugin.v1.HttpResponseChunk.HeadersEntry
+ nil, // 72: airgate.plugin.v1.WebSocketConnectInfo.HeadersEntry
+ nil, // 73: airgate.plugin.v1.PayloadSchemaProto.MetadataEntry
+ nil, // 74: airgate.plugin.v1.RouteSchemaProto.MetadataEntry
+ nil, // 75: airgate.plugin.v1.TaskSchemaProto.MetadataEntry
+ nil, // 76: airgate.plugin.v1.EventSchemaProto.MetadataEntry
+ nil, // 77: airgate.plugin.v1.InvokeSchemaProto.MetadataEntry
+ nil, // 78: airgate.plugin.v1.PluginSchemaResponse.MetadataEntry
+ nil, // 79: airgate.plugin.v1.MiddlewareRequest.MetadataEntry
+ nil, // 80: airgate.plugin.v1.MiddlewareRequest.RequestHeadersEntry
+ nil, // 81: airgate.plugin.v1.MiddlewareEvent.MetadataEntry
+ nil, // 82: airgate.plugin.v1.MiddlewareEvent.ResponseHeadersEntry
+ nil, // 83: airgate.plugin.v1.MiddlewareDecision.SetHeadersEntry
+ nil, // 84: airgate.plugin.v1.MiddlewareDecision.MetadataEntry
+ nil, // 85: airgate.plugin.v1.EventSubscriptionProto.FilterEntry
+ nil, // 86: airgate.plugin.v1.EventSubscriptionProto.MetadataEntry
+ nil, // 87: airgate.plugin.v1.PluginEvent.MetadataEntry
+ nil, // 88: airgate.plugin.v1.HostInvokeRequest.MetadataEntry
+ nil, // 89: airgate.plugin.v1.HostInvokeResponse.MetadataEntry
+ nil, // 90: airgate.plugin.v1.HostStreamFrame.MetadataEntry
}
var file_plugin_proto_depIdxs = []int32{
- 8, // 0: airgate.plugin.v2.PluginInfoResponse.account_types:type_name -> airgate.plugin.v2.AccountTypeProto
- 10, // 1: airgate.plugin.v2.PluginInfoResponse.frontend_pages:type_name -> airgate.plugin.v2.FrontendPageProto
- 11, // 2: airgate.plugin.v2.PluginInfoResponse.frontend_widgets:type_name -> airgate.plugin.v2.FrontendWidgetProto
- 7, // 3: airgate.plugin.v2.PluginInfoResponse.config_schema:type_name -> airgate.plugin.v2.ConfigFieldProto
- 56, // 4: airgate.plugin.v2.PluginInfoResponse.metadata:type_name -> airgate.plugin.v2.PluginInfoResponse.MetadataEntry
- 9, // 5: airgate.plugin.v2.AccountTypeProto.fields:type_name -> airgate.plugin.v2.CredentialFieldProto
- 57, // 6: airgate.plugin.v2.InitRequest.config:type_name -> airgate.plugin.v2.InitRequest.ConfigEntry
- 58, // 7: airgate.plugin.v2.ModelInfoProto.metadata:type_name -> airgate.plugin.v2.ModelInfoProto.MetadataEntry
- 13, // 8: airgate.plugin.v2.ModelsResponse.models:type_name -> airgate.plugin.v2.ModelInfoProto
- 59, // 9: airgate.plugin.v2.RouteDefinitionProto.metadata:type_name -> airgate.plugin.v2.RouteDefinitionProto.MetadataEntry
- 15, // 10: airgate.plugin.v2.RoutesResponse.routes:type_name -> airgate.plugin.v2.RouteDefinitionProto
- 60, // 11: airgate.plugin.v2.ForwardRequest.headers:type_name -> airgate.plugin.v2.ForwardRequest.HeadersEntry
- 17, // 12: airgate.plugin.v2.ForwardRequest.account:type_name -> airgate.plugin.v2.AccountProto
- 61, // 13: airgate.plugin.v2.UpstreamResponse.headers:type_name -> airgate.plugin.v2.UpstreamResponse.HeadersEntry
- 62, // 14: airgate.plugin.v2.UsageAttribute.metadata:type_name -> airgate.plugin.v2.UsageAttribute.MetadataEntry
- 63, // 15: airgate.plugin.v2.UsageMetric.metadata:type_name -> airgate.plugin.v2.UsageMetric.MetadataEntry
- 64, // 16: airgate.plugin.v2.UsageCostDetail.metadata:type_name -> airgate.plugin.v2.UsageCostDetail.MetadataEntry
- 21, // 17: airgate.plugin.v2.Usage.metrics:type_name -> airgate.plugin.v2.UsageMetric
- 20, // 18: airgate.plugin.v2.Usage.attributes:type_name -> airgate.plugin.v2.UsageAttribute
- 22, // 19: airgate.plugin.v2.Usage.cost_details:type_name -> airgate.plugin.v2.UsageCostDetail
- 65, // 20: airgate.plugin.v2.Usage.metadata:type_name -> airgate.plugin.v2.Usage.MetadataEntry
- 0, // 21: airgate.plugin.v2.ForwardOutcome.kind:type_name -> airgate.plugin.v2.OutcomeKind
- 19, // 22: airgate.plugin.v2.ForwardOutcome.upstream:type_name -> airgate.plugin.v2.UpstreamResponse
- 23, // 23: airgate.plugin.v2.ForwardOutcome.usage:type_name -> airgate.plugin.v2.Usage
- 66, // 24: airgate.plugin.v2.ForwardOutcome.updated_credentials:type_name -> airgate.plugin.v2.ForwardOutcome.UpdatedCredentialsEntry
- 24, // 25: airgate.plugin.v2.ForwardChunk.final_outcome:type_name -> airgate.plugin.v2.ForwardOutcome
- 67, // 26: airgate.plugin.v2.ForwardChunk.headers:type_name -> airgate.plugin.v2.ForwardChunk.HeadersEntry
- 68, // 27: airgate.plugin.v2.CredentialsRequest.credentials:type_name -> airgate.plugin.v2.CredentialsRequest.CredentialsEntry
- 69, // 28: airgate.plugin.v2.HttpRequest.headers:type_name -> airgate.plugin.v2.HttpRequest.HeadersEntry
- 70, // 29: airgate.plugin.v2.HttpResponse.headers:type_name -> airgate.plugin.v2.HttpResponse.HeadersEntry
- 71, // 30: airgate.plugin.v2.HttpResponseChunk.headers:type_name -> airgate.plugin.v2.HttpResponseChunk.HeadersEntry
- 30, // 31: airgate.plugin.v2.BackgroundTasksResponse.tasks:type_name -> airgate.plugin.v2.BackgroundTaskProto
- 1, // 32: airgate.plugin.v2.WebSocketFrame.type:type_name -> airgate.plugin.v2.WebSocketFrame.FrameType
- 34, // 33: airgate.plugin.v2.WebSocketFrame.connect_info:type_name -> airgate.plugin.v2.WebSocketConnectInfo
- 24, // 34: airgate.plugin.v2.WebSocketFrame.outcome:type_name -> airgate.plugin.v2.ForwardOutcome
- 72, // 35: airgate.plugin.v2.WebSocketConnectInfo.headers:type_name -> airgate.plugin.v2.WebSocketConnectInfo.HeadersEntry
- 17, // 36: airgate.plugin.v2.WebSocketConnectInfo.account:type_name -> airgate.plugin.v2.AccountProto
- 35, // 37: airgate.plugin.v2.WebAssetsResponse.files:type_name -> airgate.plugin.v2.WebAssetFile
- 73, // 38: airgate.plugin.v2.PayloadSchemaProto.metadata:type_name -> airgate.plugin.v2.PayloadSchemaProto.MetadataEntry
- 37, // 39: airgate.plugin.v2.RouteSchemaProto.request:type_name -> airgate.plugin.v2.PayloadSchemaProto
- 37, // 40: airgate.plugin.v2.RouteSchemaProto.response:type_name -> airgate.plugin.v2.PayloadSchemaProto
- 74, // 41: airgate.plugin.v2.RouteSchemaProto.metadata:type_name -> airgate.plugin.v2.RouteSchemaProto.MetadataEntry
- 37, // 42: airgate.plugin.v2.TaskSchemaProto.input:type_name -> airgate.plugin.v2.PayloadSchemaProto
- 37, // 43: airgate.plugin.v2.TaskSchemaProto.output:type_name -> airgate.plugin.v2.PayloadSchemaProto
- 75, // 44: airgate.plugin.v2.TaskSchemaProto.metadata:type_name -> airgate.plugin.v2.TaskSchemaProto.MetadataEntry
- 37, // 45: airgate.plugin.v2.EventSchemaProto.payload:type_name -> airgate.plugin.v2.PayloadSchemaProto
- 76, // 46: airgate.plugin.v2.EventSchemaProto.metadata:type_name -> airgate.plugin.v2.EventSchemaProto.MetadataEntry
- 37, // 47: airgate.plugin.v2.InvokeSchemaProto.request:type_name -> airgate.plugin.v2.PayloadSchemaProto
- 37, // 48: airgate.plugin.v2.InvokeSchemaProto.response:type_name -> airgate.plugin.v2.PayloadSchemaProto
- 77, // 49: airgate.plugin.v2.InvokeSchemaProto.metadata:type_name -> airgate.plugin.v2.InvokeSchemaProto.MetadataEntry
- 37, // 50: airgate.plugin.v2.InvokeSchemaProto.client_frame:type_name -> airgate.plugin.v2.PayloadSchemaProto
- 37, // 51: airgate.plugin.v2.InvokeSchemaProto.server_frame:type_name -> airgate.plugin.v2.PayloadSchemaProto
- 38, // 52: airgate.plugin.v2.PluginSchemaResponse.routes:type_name -> airgate.plugin.v2.RouteSchemaProto
- 39, // 53: airgate.plugin.v2.PluginSchemaResponse.tasks:type_name -> airgate.plugin.v2.TaskSchemaProto
- 40, // 54: airgate.plugin.v2.PluginSchemaResponse.events:type_name -> airgate.plugin.v2.EventSchemaProto
- 41, // 55: airgate.plugin.v2.PluginSchemaResponse.invokes:type_name -> airgate.plugin.v2.InvokeSchemaProto
- 78, // 56: airgate.plugin.v2.PluginSchemaResponse.metadata:type_name -> airgate.plugin.v2.PluginSchemaResponse.MetadataEntry
- 21, // 57: airgate.plugin.v2.MiddlewareRequest.estimates:type_name -> airgate.plugin.v2.UsageMetric
- 79, // 58: airgate.plugin.v2.MiddlewareRequest.metadata:type_name -> airgate.plugin.v2.MiddlewareRequest.MetadataEntry
- 80, // 59: airgate.plugin.v2.MiddlewareRequest.request_headers:type_name -> airgate.plugin.v2.MiddlewareRequest.RequestHeadersEntry
- 21, // 60: airgate.plugin.v2.MiddlewareEvent.estimates:type_name -> airgate.plugin.v2.UsageMetric
- 23, // 61: airgate.plugin.v2.MiddlewareEvent.usage:type_name -> airgate.plugin.v2.Usage
- 81, // 62: airgate.plugin.v2.MiddlewareEvent.metadata:type_name -> airgate.plugin.v2.MiddlewareEvent.MetadataEntry
- 82, // 63: airgate.plugin.v2.MiddlewareEvent.response_headers:type_name -> airgate.plugin.v2.MiddlewareEvent.ResponseHeadersEntry
- 2, // 64: airgate.plugin.v2.MiddlewareDecision.action:type_name -> airgate.plugin.v2.MiddlewareDecision.Action
- 83, // 65: airgate.plugin.v2.MiddlewareDecision.set_headers:type_name -> airgate.plugin.v2.MiddlewareDecision.SetHeadersEntry
- 84, // 66: airgate.plugin.v2.MiddlewareDecision.metadata:type_name -> airgate.plugin.v2.MiddlewareDecision.MetadataEntry
- 85, // 67: airgate.plugin.v2.EventSubscriptionProto.filter:type_name -> airgate.plugin.v2.EventSubscriptionProto.FilterEntry
- 86, // 68: airgate.plugin.v2.EventSubscriptionProto.metadata:type_name -> airgate.plugin.v2.EventSubscriptionProto.MetadataEntry
- 46, // 69: airgate.plugin.v2.EventSubscriptionsResponse.subscriptions:type_name -> airgate.plugin.v2.EventSubscriptionProto
- 87, // 70: airgate.plugin.v2.PluginEvent.metadata:type_name -> airgate.plugin.v2.PluginEvent.MetadataEntry
- 88, // 71: airgate.plugin.v2.HostInvokeRequest.metadata:type_name -> airgate.plugin.v2.HostInvokeRequest.MetadataEntry
- 89, // 72: airgate.plugin.v2.HostInvokeResponse.metadata:type_name -> airgate.plugin.v2.HostInvokeResponse.MetadataEntry
- 90, // 73: airgate.plugin.v2.HostStreamFrame.metadata:type_name -> airgate.plugin.v2.HostStreamFrame.MetadataEntry
- 5, // 74: airgate.plugin.v2.ForwardRequest.HeadersEntry.value:type_name -> airgate.plugin.v2.HeaderValues
- 5, // 75: airgate.plugin.v2.UpstreamResponse.HeadersEntry.value:type_name -> airgate.plugin.v2.HeaderValues
- 5, // 76: airgate.plugin.v2.ForwardChunk.HeadersEntry.value:type_name -> airgate.plugin.v2.HeaderValues
- 5, // 77: airgate.plugin.v2.HttpRequest.HeadersEntry.value:type_name -> airgate.plugin.v2.HeaderValues
- 5, // 78: airgate.plugin.v2.HttpResponse.HeadersEntry.value:type_name -> airgate.plugin.v2.HeaderValues
- 5, // 79: airgate.plugin.v2.HttpResponseChunk.HeadersEntry.value:type_name -> airgate.plugin.v2.HeaderValues
- 5, // 80: airgate.plugin.v2.WebSocketConnectInfo.HeadersEntry.value:type_name -> airgate.plugin.v2.HeaderValues
- 5, // 81: airgate.plugin.v2.MiddlewareRequest.RequestHeadersEntry.value:type_name -> airgate.plugin.v2.HeaderValues
- 5, // 82: airgate.plugin.v2.MiddlewareEvent.ResponseHeadersEntry.value:type_name -> airgate.plugin.v2.HeaderValues
- 5, // 83: airgate.plugin.v2.MiddlewareDecision.SetHeadersEntry.value:type_name -> airgate.plugin.v2.HeaderValues
- 3, // 84: airgate.plugin.v2.PluginService.GetInfo:input_type -> airgate.plugin.v2.Empty
- 12, // 85: airgate.plugin.v2.PluginService.Init:input_type -> airgate.plugin.v2.InitRequest
- 3, // 86: airgate.plugin.v2.PluginService.Start:input_type -> airgate.plugin.v2.Empty
- 3, // 87: airgate.plugin.v2.PluginService.Stop:input_type -> airgate.plugin.v2.Empty
- 3, // 88: airgate.plugin.v2.PluginService.GetWebAssets:input_type -> airgate.plugin.v2.Empty
- 3, // 89: airgate.plugin.v2.PluginService.GetSchema:input_type -> airgate.plugin.v2.Empty
- 3, // 90: airgate.plugin.v2.PluginService.HealthCheck:input_type -> airgate.plugin.v2.Empty
- 27, // 91: airgate.plugin.v2.PluginService.HandleRequest:input_type -> airgate.plugin.v2.HttpRequest
- 3, // 92: airgate.plugin.v2.GatewayService.GetPlatform:input_type -> airgate.plugin.v2.Empty
- 3, // 93: airgate.plugin.v2.GatewayService.GetModels:input_type -> airgate.plugin.v2.Empty
- 3, // 94: airgate.plugin.v2.GatewayService.GetRoutes:input_type -> airgate.plugin.v2.Empty
- 18, // 95: airgate.plugin.v2.GatewayService.Forward:input_type -> airgate.plugin.v2.ForwardRequest
- 18, // 96: airgate.plugin.v2.GatewayService.ForwardStream:input_type -> airgate.plugin.v2.ForwardRequest
- 26, // 97: airgate.plugin.v2.GatewayService.ValidateAccount:input_type -> airgate.plugin.v2.CredentialsRequest
- 33, // 98: airgate.plugin.v2.GatewayService.HandleWebSocket:input_type -> airgate.plugin.v2.WebSocketFrame
- 3, // 99: airgate.plugin.v2.ExtensionService.Migrate:input_type -> airgate.plugin.v2.Empty
- 3, // 100: airgate.plugin.v2.ExtensionService.GetBackgroundTasks:input_type -> airgate.plugin.v2.Empty
- 32, // 101: airgate.plugin.v2.ExtensionService.RunBackgroundTask:input_type -> airgate.plugin.v2.RunBackgroundTaskRequest
- 27, // 102: airgate.plugin.v2.ExtensionService.HandleRequest:input_type -> airgate.plugin.v2.HttpRequest
- 27, // 103: airgate.plugin.v2.ExtensionService.HandleStreamRequest:input_type -> airgate.plugin.v2.HttpRequest
- 53, // 104: airgate.plugin.v2.ExtensionService.ProcessTask:input_type -> airgate.plugin.v2.ProcessTaskRequest
- 3, // 105: airgate.plugin.v2.ExtensionService.GetTaskTypes:input_type -> airgate.plugin.v2.Empty
- 43, // 106: airgate.plugin.v2.MiddlewareService.OnForwardBegin:input_type -> airgate.plugin.v2.MiddlewareRequest
- 44, // 107: airgate.plugin.v2.MiddlewareService.OnForwardEnd:input_type -> airgate.plugin.v2.MiddlewareEvent
- 3, // 108: airgate.plugin.v2.EventService.GetEventSubscriptions:input_type -> airgate.plugin.v2.Empty
- 48, // 109: airgate.plugin.v2.EventService.HandleEvent:input_type -> airgate.plugin.v2.PluginEvent
- 50, // 110: airgate.plugin.v2.CoreInvokeService.Invoke:input_type -> airgate.plugin.v2.HostInvokeRequest
- 52, // 111: airgate.plugin.v2.CoreInvokeService.InvokeStream:input_type -> airgate.plugin.v2.HostStreamFrame
- 6, // 112: airgate.plugin.v2.PluginService.GetInfo:output_type -> airgate.plugin.v2.PluginInfoResponse
- 3, // 113: airgate.plugin.v2.PluginService.Init:output_type -> airgate.plugin.v2.Empty
- 3, // 114: airgate.plugin.v2.PluginService.Start:output_type -> airgate.plugin.v2.Empty
- 3, // 115: airgate.plugin.v2.PluginService.Stop:output_type -> airgate.plugin.v2.Empty
- 36, // 116: airgate.plugin.v2.PluginService.GetWebAssets:output_type -> airgate.plugin.v2.WebAssetsResponse
- 42, // 117: airgate.plugin.v2.PluginService.GetSchema:output_type -> airgate.plugin.v2.PluginSchemaResponse
- 3, // 118: airgate.plugin.v2.PluginService.HealthCheck:output_type -> airgate.plugin.v2.Empty
- 28, // 119: airgate.plugin.v2.PluginService.HandleRequest:output_type -> airgate.plugin.v2.HttpResponse
- 4, // 120: airgate.plugin.v2.GatewayService.GetPlatform:output_type -> airgate.plugin.v2.StringResponse
- 14, // 121: airgate.plugin.v2.GatewayService.GetModels:output_type -> airgate.plugin.v2.ModelsResponse
- 16, // 122: airgate.plugin.v2.GatewayService.GetRoutes:output_type -> airgate.plugin.v2.RoutesResponse
- 24, // 123: airgate.plugin.v2.GatewayService.Forward:output_type -> airgate.plugin.v2.ForwardOutcome
- 25, // 124: airgate.plugin.v2.GatewayService.ForwardStream:output_type -> airgate.plugin.v2.ForwardChunk
- 3, // 125: airgate.plugin.v2.GatewayService.ValidateAccount:output_type -> airgate.plugin.v2.Empty
- 33, // 126: airgate.plugin.v2.GatewayService.HandleWebSocket:output_type -> airgate.plugin.v2.WebSocketFrame
- 3, // 127: airgate.plugin.v2.ExtensionService.Migrate:output_type -> airgate.plugin.v2.Empty
- 31, // 128: airgate.plugin.v2.ExtensionService.GetBackgroundTasks:output_type -> airgate.plugin.v2.BackgroundTasksResponse
- 3, // 129: airgate.plugin.v2.ExtensionService.RunBackgroundTask:output_type -> airgate.plugin.v2.Empty
- 28, // 130: airgate.plugin.v2.ExtensionService.HandleRequest:output_type -> airgate.plugin.v2.HttpResponse
- 29, // 131: airgate.plugin.v2.ExtensionService.HandleStreamRequest:output_type -> airgate.plugin.v2.HttpResponseChunk
- 54, // 132: airgate.plugin.v2.ExtensionService.ProcessTask:output_type -> airgate.plugin.v2.ProcessTaskResponse
- 55, // 133: airgate.plugin.v2.ExtensionService.GetTaskTypes:output_type -> airgate.plugin.v2.TaskTypesResponse
- 45, // 134: airgate.plugin.v2.MiddlewareService.OnForwardBegin:output_type -> airgate.plugin.v2.MiddlewareDecision
- 3, // 135: airgate.plugin.v2.MiddlewareService.OnForwardEnd:output_type -> airgate.plugin.v2.Empty
- 47, // 136: airgate.plugin.v2.EventService.GetEventSubscriptions:output_type -> airgate.plugin.v2.EventSubscriptionsResponse
- 49, // 137: airgate.plugin.v2.EventService.HandleEvent:output_type -> airgate.plugin.v2.EventHandleResponse
- 51, // 138: airgate.plugin.v2.CoreInvokeService.Invoke:output_type -> airgate.plugin.v2.HostInvokeResponse
- 52, // 139: airgate.plugin.v2.CoreInvokeService.InvokeStream:output_type -> airgate.plugin.v2.HostStreamFrame
+ 8, // 0: airgate.plugin.v1.PluginInfoResponse.account_types:type_name -> airgate.plugin.v1.AccountTypeProto
+ 10, // 1: airgate.plugin.v1.PluginInfoResponse.frontend_pages:type_name -> airgate.plugin.v1.FrontendPageProto
+ 11, // 2: airgate.plugin.v1.PluginInfoResponse.frontend_widgets:type_name -> airgate.plugin.v1.FrontendWidgetProto
+ 7, // 3: airgate.plugin.v1.PluginInfoResponse.config_schema:type_name -> airgate.plugin.v1.ConfigFieldProto
+ 56, // 4: airgate.plugin.v1.PluginInfoResponse.metadata:type_name -> airgate.plugin.v1.PluginInfoResponse.MetadataEntry
+ 9, // 5: airgate.plugin.v1.AccountTypeProto.fields:type_name -> airgate.plugin.v1.CredentialFieldProto
+ 57, // 6: airgate.plugin.v1.InitRequest.config:type_name -> airgate.plugin.v1.InitRequest.ConfigEntry
+ 58, // 7: airgate.plugin.v1.ModelInfoProto.metadata:type_name -> airgate.plugin.v1.ModelInfoProto.MetadataEntry
+ 13, // 8: airgate.plugin.v1.ModelsResponse.models:type_name -> airgate.plugin.v1.ModelInfoProto
+ 59, // 9: airgate.plugin.v1.RouteDefinitionProto.metadata:type_name -> airgate.plugin.v1.RouteDefinitionProto.MetadataEntry
+ 15, // 10: airgate.plugin.v1.RoutesResponse.routes:type_name -> airgate.plugin.v1.RouteDefinitionProto
+ 60, // 11: airgate.plugin.v1.ForwardRequest.headers:type_name -> airgate.plugin.v1.ForwardRequest.HeadersEntry
+ 17, // 12: airgate.plugin.v1.ForwardRequest.account:type_name -> airgate.plugin.v1.AccountProto
+ 61, // 13: airgate.plugin.v1.UpstreamResponse.headers:type_name -> airgate.plugin.v1.UpstreamResponse.HeadersEntry
+ 62, // 14: airgate.plugin.v1.UsageAttribute.metadata:type_name -> airgate.plugin.v1.UsageAttribute.MetadataEntry
+ 63, // 15: airgate.plugin.v1.UsageMetric.metadata:type_name -> airgate.plugin.v1.UsageMetric.MetadataEntry
+ 64, // 16: airgate.plugin.v1.UsageCostDetail.metadata:type_name -> airgate.plugin.v1.UsageCostDetail.MetadataEntry
+ 21, // 17: airgate.plugin.v1.Usage.metrics:type_name -> airgate.plugin.v1.UsageMetric
+ 20, // 18: airgate.plugin.v1.Usage.attributes:type_name -> airgate.plugin.v1.UsageAttribute
+ 22, // 19: airgate.plugin.v1.Usage.cost_details:type_name -> airgate.plugin.v1.UsageCostDetail
+ 65, // 20: airgate.plugin.v1.Usage.metadata:type_name -> airgate.plugin.v1.Usage.MetadataEntry
+ 0, // 21: airgate.plugin.v1.ForwardOutcome.kind:type_name -> airgate.plugin.v1.OutcomeKind
+ 19, // 22: airgate.plugin.v1.ForwardOutcome.upstream:type_name -> airgate.plugin.v1.UpstreamResponse
+ 23, // 23: airgate.plugin.v1.ForwardOutcome.usage:type_name -> airgate.plugin.v1.Usage
+ 66, // 24: airgate.plugin.v1.ForwardOutcome.updated_credentials:type_name -> airgate.plugin.v1.ForwardOutcome.UpdatedCredentialsEntry
+ 24, // 25: airgate.plugin.v1.ForwardChunk.final_outcome:type_name -> airgate.plugin.v1.ForwardOutcome
+ 67, // 26: airgate.plugin.v1.ForwardChunk.headers:type_name -> airgate.plugin.v1.ForwardChunk.HeadersEntry
+ 68, // 27: airgate.plugin.v1.CredentialsRequest.credentials:type_name -> airgate.plugin.v1.CredentialsRequest.CredentialsEntry
+ 69, // 28: airgate.plugin.v1.HttpRequest.headers:type_name -> airgate.plugin.v1.HttpRequest.HeadersEntry
+ 70, // 29: airgate.plugin.v1.HttpResponse.headers:type_name -> airgate.plugin.v1.HttpResponse.HeadersEntry
+ 71, // 30: airgate.plugin.v1.HttpResponseChunk.headers:type_name -> airgate.plugin.v1.HttpResponseChunk.HeadersEntry
+ 30, // 31: airgate.plugin.v1.BackgroundTasksResponse.tasks:type_name -> airgate.plugin.v1.BackgroundTaskProto
+ 1, // 32: airgate.plugin.v1.WebSocketFrame.type:type_name -> airgate.plugin.v1.WebSocketFrame.FrameType
+ 34, // 33: airgate.plugin.v1.WebSocketFrame.connect_info:type_name -> airgate.plugin.v1.WebSocketConnectInfo
+ 24, // 34: airgate.plugin.v1.WebSocketFrame.outcome:type_name -> airgate.plugin.v1.ForwardOutcome
+ 72, // 35: airgate.plugin.v1.WebSocketConnectInfo.headers:type_name -> airgate.plugin.v1.WebSocketConnectInfo.HeadersEntry
+ 17, // 36: airgate.plugin.v1.WebSocketConnectInfo.account:type_name -> airgate.plugin.v1.AccountProto
+ 35, // 37: airgate.plugin.v1.WebAssetsResponse.files:type_name -> airgate.plugin.v1.WebAssetFile
+ 73, // 38: airgate.plugin.v1.PayloadSchemaProto.metadata:type_name -> airgate.plugin.v1.PayloadSchemaProto.MetadataEntry
+ 37, // 39: airgate.plugin.v1.RouteSchemaProto.request:type_name -> airgate.plugin.v1.PayloadSchemaProto
+ 37, // 40: airgate.plugin.v1.RouteSchemaProto.response:type_name -> airgate.plugin.v1.PayloadSchemaProto
+ 74, // 41: airgate.plugin.v1.RouteSchemaProto.metadata:type_name -> airgate.plugin.v1.RouteSchemaProto.MetadataEntry
+ 37, // 42: airgate.plugin.v1.TaskSchemaProto.input:type_name -> airgate.plugin.v1.PayloadSchemaProto
+ 37, // 43: airgate.plugin.v1.TaskSchemaProto.output:type_name -> airgate.plugin.v1.PayloadSchemaProto
+ 75, // 44: airgate.plugin.v1.TaskSchemaProto.metadata:type_name -> airgate.plugin.v1.TaskSchemaProto.MetadataEntry
+ 37, // 45: airgate.plugin.v1.EventSchemaProto.payload:type_name -> airgate.plugin.v1.PayloadSchemaProto
+ 76, // 46: airgate.plugin.v1.EventSchemaProto.metadata:type_name -> airgate.plugin.v1.EventSchemaProto.MetadataEntry
+ 37, // 47: airgate.plugin.v1.InvokeSchemaProto.request:type_name -> airgate.plugin.v1.PayloadSchemaProto
+ 37, // 48: airgate.plugin.v1.InvokeSchemaProto.response:type_name -> airgate.plugin.v1.PayloadSchemaProto
+ 77, // 49: airgate.plugin.v1.InvokeSchemaProto.metadata:type_name -> airgate.plugin.v1.InvokeSchemaProto.MetadataEntry
+ 37, // 50: airgate.plugin.v1.InvokeSchemaProto.client_frame:type_name -> airgate.plugin.v1.PayloadSchemaProto
+ 37, // 51: airgate.plugin.v1.InvokeSchemaProto.server_frame:type_name -> airgate.plugin.v1.PayloadSchemaProto
+ 38, // 52: airgate.plugin.v1.PluginSchemaResponse.routes:type_name -> airgate.plugin.v1.RouteSchemaProto
+ 39, // 53: airgate.plugin.v1.PluginSchemaResponse.tasks:type_name -> airgate.plugin.v1.TaskSchemaProto
+ 40, // 54: airgate.plugin.v1.PluginSchemaResponse.events:type_name -> airgate.plugin.v1.EventSchemaProto
+ 41, // 55: airgate.plugin.v1.PluginSchemaResponse.invokes:type_name -> airgate.plugin.v1.InvokeSchemaProto
+ 78, // 56: airgate.plugin.v1.PluginSchemaResponse.metadata:type_name -> airgate.plugin.v1.PluginSchemaResponse.MetadataEntry
+ 21, // 57: airgate.plugin.v1.MiddlewareRequest.estimates:type_name -> airgate.plugin.v1.UsageMetric
+ 79, // 58: airgate.plugin.v1.MiddlewareRequest.metadata:type_name -> airgate.plugin.v1.MiddlewareRequest.MetadataEntry
+ 80, // 59: airgate.plugin.v1.MiddlewareRequest.request_headers:type_name -> airgate.plugin.v1.MiddlewareRequest.RequestHeadersEntry
+ 21, // 60: airgate.plugin.v1.MiddlewareEvent.estimates:type_name -> airgate.plugin.v1.UsageMetric
+ 23, // 61: airgate.plugin.v1.MiddlewareEvent.usage:type_name -> airgate.plugin.v1.Usage
+ 81, // 62: airgate.plugin.v1.MiddlewareEvent.metadata:type_name -> airgate.plugin.v1.MiddlewareEvent.MetadataEntry
+ 82, // 63: airgate.plugin.v1.MiddlewareEvent.response_headers:type_name -> airgate.plugin.v1.MiddlewareEvent.ResponseHeadersEntry
+ 2, // 64: airgate.plugin.v1.MiddlewareDecision.action:type_name -> airgate.plugin.v1.MiddlewareDecision.Action
+ 83, // 65: airgate.plugin.v1.MiddlewareDecision.set_headers:type_name -> airgate.plugin.v1.MiddlewareDecision.SetHeadersEntry
+ 84, // 66: airgate.plugin.v1.MiddlewareDecision.metadata:type_name -> airgate.plugin.v1.MiddlewareDecision.MetadataEntry
+ 85, // 67: airgate.plugin.v1.EventSubscriptionProto.filter:type_name -> airgate.plugin.v1.EventSubscriptionProto.FilterEntry
+ 86, // 68: airgate.plugin.v1.EventSubscriptionProto.metadata:type_name -> airgate.plugin.v1.EventSubscriptionProto.MetadataEntry
+ 46, // 69: airgate.plugin.v1.EventSubscriptionsResponse.subscriptions:type_name -> airgate.plugin.v1.EventSubscriptionProto
+ 87, // 70: airgate.plugin.v1.PluginEvent.metadata:type_name -> airgate.plugin.v1.PluginEvent.MetadataEntry
+ 88, // 71: airgate.plugin.v1.HostInvokeRequest.metadata:type_name -> airgate.plugin.v1.HostInvokeRequest.MetadataEntry
+ 89, // 72: airgate.plugin.v1.HostInvokeResponse.metadata:type_name -> airgate.plugin.v1.HostInvokeResponse.MetadataEntry
+ 90, // 73: airgate.plugin.v1.HostStreamFrame.metadata:type_name -> airgate.plugin.v1.HostStreamFrame.MetadataEntry
+ 5, // 74: airgate.plugin.v1.ForwardRequest.HeadersEntry.value:type_name -> airgate.plugin.v1.HeaderValues
+ 5, // 75: airgate.plugin.v1.UpstreamResponse.HeadersEntry.value:type_name -> airgate.plugin.v1.HeaderValues
+ 5, // 76: airgate.plugin.v1.ForwardChunk.HeadersEntry.value:type_name -> airgate.plugin.v1.HeaderValues
+ 5, // 77: airgate.plugin.v1.HttpRequest.HeadersEntry.value:type_name -> airgate.plugin.v1.HeaderValues
+ 5, // 78: airgate.plugin.v1.HttpResponse.HeadersEntry.value:type_name -> airgate.plugin.v1.HeaderValues
+ 5, // 79: airgate.plugin.v1.HttpResponseChunk.HeadersEntry.value:type_name -> airgate.plugin.v1.HeaderValues
+ 5, // 80: airgate.plugin.v1.WebSocketConnectInfo.HeadersEntry.value:type_name -> airgate.plugin.v1.HeaderValues
+ 5, // 81: airgate.plugin.v1.MiddlewareRequest.RequestHeadersEntry.value:type_name -> airgate.plugin.v1.HeaderValues
+ 5, // 82: airgate.plugin.v1.MiddlewareEvent.ResponseHeadersEntry.value:type_name -> airgate.plugin.v1.HeaderValues
+ 5, // 83: airgate.plugin.v1.MiddlewareDecision.SetHeadersEntry.value:type_name -> airgate.plugin.v1.HeaderValues
+ 3, // 84: airgate.plugin.v1.PluginService.GetInfo:input_type -> airgate.plugin.v1.Empty
+ 12, // 85: airgate.plugin.v1.PluginService.Init:input_type -> airgate.plugin.v1.InitRequest
+ 3, // 86: airgate.plugin.v1.PluginService.Start:input_type -> airgate.plugin.v1.Empty
+ 3, // 87: airgate.plugin.v1.PluginService.Stop:input_type -> airgate.plugin.v1.Empty
+ 3, // 88: airgate.plugin.v1.PluginService.GetWebAssets:input_type -> airgate.plugin.v1.Empty
+ 3, // 89: airgate.plugin.v1.PluginService.GetSchema:input_type -> airgate.plugin.v1.Empty
+ 3, // 90: airgate.plugin.v1.PluginService.HealthCheck:input_type -> airgate.plugin.v1.Empty
+ 27, // 91: airgate.plugin.v1.PluginService.HandleRequest:input_type -> airgate.plugin.v1.HttpRequest
+ 3, // 92: airgate.plugin.v1.GatewayService.GetPlatform:input_type -> airgate.plugin.v1.Empty
+ 3, // 93: airgate.plugin.v1.GatewayService.GetModels:input_type -> airgate.plugin.v1.Empty
+ 3, // 94: airgate.plugin.v1.GatewayService.GetRoutes:input_type -> airgate.plugin.v1.Empty
+ 18, // 95: airgate.plugin.v1.GatewayService.Forward:input_type -> airgate.plugin.v1.ForwardRequest
+ 18, // 96: airgate.plugin.v1.GatewayService.ForwardStream:input_type -> airgate.plugin.v1.ForwardRequest
+ 26, // 97: airgate.plugin.v1.GatewayService.ValidateAccount:input_type -> airgate.plugin.v1.CredentialsRequest
+ 33, // 98: airgate.plugin.v1.GatewayService.HandleWebSocket:input_type -> airgate.plugin.v1.WebSocketFrame
+ 3, // 99: airgate.plugin.v1.ExtensionService.Migrate:input_type -> airgate.plugin.v1.Empty
+ 3, // 100: airgate.plugin.v1.ExtensionService.GetBackgroundTasks:input_type -> airgate.plugin.v1.Empty
+ 32, // 101: airgate.plugin.v1.ExtensionService.RunBackgroundTask:input_type -> airgate.plugin.v1.RunBackgroundTaskRequest
+ 27, // 102: airgate.plugin.v1.ExtensionService.HandleRequest:input_type -> airgate.plugin.v1.HttpRequest
+ 27, // 103: airgate.plugin.v1.ExtensionService.HandleStreamRequest:input_type -> airgate.plugin.v1.HttpRequest
+ 53, // 104: airgate.plugin.v1.ExtensionService.ProcessTask:input_type -> airgate.plugin.v1.ProcessTaskRequest
+ 3, // 105: airgate.plugin.v1.ExtensionService.GetTaskTypes:input_type -> airgate.plugin.v1.Empty
+ 43, // 106: airgate.plugin.v1.MiddlewareService.OnForwardBegin:input_type -> airgate.plugin.v1.MiddlewareRequest
+ 44, // 107: airgate.plugin.v1.MiddlewareService.OnForwardEnd:input_type -> airgate.plugin.v1.MiddlewareEvent
+ 3, // 108: airgate.plugin.v1.EventService.GetEventSubscriptions:input_type -> airgate.plugin.v1.Empty
+ 48, // 109: airgate.plugin.v1.EventService.HandleEvent:input_type -> airgate.plugin.v1.PluginEvent
+ 50, // 110: airgate.plugin.v1.CoreInvokeService.Invoke:input_type -> airgate.plugin.v1.HostInvokeRequest
+ 52, // 111: airgate.plugin.v1.CoreInvokeService.InvokeStream:input_type -> airgate.plugin.v1.HostStreamFrame
+ 6, // 112: airgate.plugin.v1.PluginService.GetInfo:output_type -> airgate.plugin.v1.PluginInfoResponse
+ 3, // 113: airgate.plugin.v1.PluginService.Init:output_type -> airgate.plugin.v1.Empty
+ 3, // 114: airgate.plugin.v1.PluginService.Start:output_type -> airgate.plugin.v1.Empty
+ 3, // 115: airgate.plugin.v1.PluginService.Stop:output_type -> airgate.plugin.v1.Empty
+ 36, // 116: airgate.plugin.v1.PluginService.GetWebAssets:output_type -> airgate.plugin.v1.WebAssetsResponse
+ 42, // 117: airgate.plugin.v1.PluginService.GetSchema:output_type -> airgate.plugin.v1.PluginSchemaResponse
+ 3, // 118: airgate.plugin.v1.PluginService.HealthCheck:output_type -> airgate.plugin.v1.Empty
+ 28, // 119: airgate.plugin.v1.PluginService.HandleRequest:output_type -> airgate.plugin.v1.HttpResponse
+ 4, // 120: airgate.plugin.v1.GatewayService.GetPlatform:output_type -> airgate.plugin.v1.StringResponse
+ 14, // 121: airgate.plugin.v1.GatewayService.GetModels:output_type -> airgate.plugin.v1.ModelsResponse
+ 16, // 122: airgate.plugin.v1.GatewayService.GetRoutes:output_type -> airgate.plugin.v1.RoutesResponse
+ 24, // 123: airgate.plugin.v1.GatewayService.Forward:output_type -> airgate.plugin.v1.ForwardOutcome
+ 25, // 124: airgate.plugin.v1.GatewayService.ForwardStream:output_type -> airgate.plugin.v1.ForwardChunk
+ 3, // 125: airgate.plugin.v1.GatewayService.ValidateAccount:output_type -> airgate.plugin.v1.Empty
+ 33, // 126: airgate.plugin.v1.GatewayService.HandleWebSocket:output_type -> airgate.plugin.v1.WebSocketFrame
+ 3, // 127: airgate.plugin.v1.ExtensionService.Migrate:output_type -> airgate.plugin.v1.Empty
+ 31, // 128: airgate.plugin.v1.ExtensionService.GetBackgroundTasks:output_type -> airgate.plugin.v1.BackgroundTasksResponse
+ 3, // 129: airgate.plugin.v1.ExtensionService.RunBackgroundTask:output_type -> airgate.plugin.v1.Empty
+ 28, // 130: airgate.plugin.v1.ExtensionService.HandleRequest:output_type -> airgate.plugin.v1.HttpResponse
+ 29, // 131: airgate.plugin.v1.ExtensionService.HandleStreamRequest:output_type -> airgate.plugin.v1.HttpResponseChunk
+ 54, // 132: airgate.plugin.v1.ExtensionService.ProcessTask:output_type -> airgate.plugin.v1.ProcessTaskResponse
+ 55, // 133: airgate.plugin.v1.ExtensionService.GetTaskTypes:output_type -> airgate.plugin.v1.TaskTypesResponse
+ 45, // 134: airgate.plugin.v1.MiddlewareService.OnForwardBegin:output_type -> airgate.plugin.v1.MiddlewareDecision
+ 3, // 135: airgate.plugin.v1.MiddlewareService.OnForwardEnd:output_type -> airgate.plugin.v1.Empty
+ 47, // 136: airgate.plugin.v1.EventService.GetEventSubscriptions:output_type -> airgate.plugin.v1.EventSubscriptionsResponse
+ 49, // 137: airgate.plugin.v1.EventService.HandleEvent:output_type -> airgate.plugin.v1.EventHandleResponse
+ 51, // 138: airgate.plugin.v1.CoreInvokeService.Invoke:output_type -> airgate.plugin.v1.HostInvokeResponse
+ 52, // 139: airgate.plugin.v1.CoreInvokeService.InvokeStream:output_type -> airgate.plugin.v1.HostStreamFrame
112, // [112:140] is the sub-list for method output_type
84, // [84:112] is the sub-list for method input_type
84, // [84:84] is the sub-list for extension type_name
diff --git a/protocol/proto/plugin.proto b/protocol/proto/plugin.proto
index 4d57667..8c7b80c 100644
--- a/protocol/proto/plugin.proto
+++ b/protocol/proto/plugin.proto
@@ -1,6 +1,6 @@
syntax = "proto3";
-package airgate.plugin.v2;
+package airgate.plugin.v1;
option go_package = "github.com/DouDOU-start/airgate-sdk/protocol/proto";
diff --git a/protocol/proto/plugin_grpc.pb.go b/protocol/proto/plugin_grpc.pb.go
index 556b87b..db9b4ce 100644
--- a/protocol/proto/plugin_grpc.pb.go
+++ b/protocol/proto/plugin_grpc.pb.go
@@ -19,14 +19,14 @@ import (
const _ = grpc.SupportPackageIsVersion9
const (
- PluginService_GetInfo_FullMethodName = "/airgate.plugin.v2.PluginService/GetInfo"
- PluginService_Init_FullMethodName = "/airgate.plugin.v2.PluginService/Init"
- PluginService_Start_FullMethodName = "/airgate.plugin.v2.PluginService/Start"
- PluginService_Stop_FullMethodName = "/airgate.plugin.v2.PluginService/Stop"
- PluginService_GetWebAssets_FullMethodName = "/airgate.plugin.v2.PluginService/GetWebAssets"
- PluginService_GetSchema_FullMethodName = "/airgate.plugin.v2.PluginService/GetSchema"
- PluginService_HealthCheck_FullMethodName = "/airgate.plugin.v2.PluginService/HealthCheck"
- PluginService_HandleRequest_FullMethodName = "/airgate.plugin.v2.PluginService/HandleRequest"
+ PluginService_GetInfo_FullMethodName = "/airgate.plugin.v1.PluginService/GetInfo"
+ PluginService_Init_FullMethodName = "/airgate.plugin.v1.PluginService/Init"
+ PluginService_Start_FullMethodName = "/airgate.plugin.v1.PluginService/Start"
+ PluginService_Stop_FullMethodName = "/airgate.plugin.v1.PluginService/Stop"
+ PluginService_GetWebAssets_FullMethodName = "/airgate.plugin.v1.PluginService/GetWebAssets"
+ PluginService_GetSchema_FullMethodName = "/airgate.plugin.v1.PluginService/GetSchema"
+ PluginService_HealthCheck_FullMethodName = "/airgate.plugin.v1.PluginService/HealthCheck"
+ PluginService_HandleRequest_FullMethodName = "/airgate.plugin.v1.PluginService/HandleRequest"
)
// PluginServiceClient is the client API for PluginService service.
@@ -354,7 +354,7 @@ func _PluginService_HandleRequest_Handler(srv interface{}, ctx context.Context,
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var PluginService_ServiceDesc = grpc.ServiceDesc{
- ServiceName: "airgate.plugin.v2.PluginService",
+ ServiceName: "airgate.plugin.v1.PluginService",
HandlerType: (*PluginServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
@@ -395,13 +395,13 @@ var PluginService_ServiceDesc = grpc.ServiceDesc{
}
const (
- GatewayService_GetPlatform_FullMethodName = "/airgate.plugin.v2.GatewayService/GetPlatform"
- GatewayService_GetModels_FullMethodName = "/airgate.plugin.v2.GatewayService/GetModels"
- GatewayService_GetRoutes_FullMethodName = "/airgate.plugin.v2.GatewayService/GetRoutes"
- GatewayService_Forward_FullMethodName = "/airgate.plugin.v2.GatewayService/Forward"
- GatewayService_ForwardStream_FullMethodName = "/airgate.plugin.v2.GatewayService/ForwardStream"
- GatewayService_ValidateAccount_FullMethodName = "/airgate.plugin.v2.GatewayService/ValidateAccount"
- GatewayService_HandleWebSocket_FullMethodName = "/airgate.plugin.v2.GatewayService/HandleWebSocket"
+ GatewayService_GetPlatform_FullMethodName = "/airgate.plugin.v1.GatewayService/GetPlatform"
+ GatewayService_GetModels_FullMethodName = "/airgate.plugin.v1.GatewayService/GetModels"
+ GatewayService_GetRoutes_FullMethodName = "/airgate.plugin.v1.GatewayService/GetRoutes"
+ GatewayService_Forward_FullMethodName = "/airgate.plugin.v1.GatewayService/Forward"
+ GatewayService_ForwardStream_FullMethodName = "/airgate.plugin.v1.GatewayService/ForwardStream"
+ GatewayService_ValidateAccount_FullMethodName = "/airgate.plugin.v1.GatewayService/ValidateAccount"
+ GatewayService_HandleWebSocket_FullMethodName = "/airgate.plugin.v1.GatewayService/HandleWebSocket"
)
// GatewayServiceClient is the client API for GatewayService service.
@@ -684,7 +684,7 @@ type GatewayService_HandleWebSocketServer = grpc.BidiStreamingServer[WebSocketFr
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var GatewayService_ServiceDesc = grpc.ServiceDesc{
- ServiceName: "airgate.plugin.v2.GatewayService",
+ ServiceName: "airgate.plugin.v1.GatewayService",
HandlerType: (*GatewayServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
@@ -725,13 +725,13 @@ var GatewayService_ServiceDesc = grpc.ServiceDesc{
}
const (
- ExtensionService_Migrate_FullMethodName = "/airgate.plugin.v2.ExtensionService/Migrate"
- ExtensionService_GetBackgroundTasks_FullMethodName = "/airgate.plugin.v2.ExtensionService/GetBackgroundTasks"
- ExtensionService_RunBackgroundTask_FullMethodName = "/airgate.plugin.v2.ExtensionService/RunBackgroundTask"
- ExtensionService_HandleRequest_FullMethodName = "/airgate.plugin.v2.ExtensionService/HandleRequest"
- ExtensionService_HandleStreamRequest_FullMethodName = "/airgate.plugin.v2.ExtensionService/HandleStreamRequest"
- ExtensionService_ProcessTask_FullMethodName = "/airgate.plugin.v2.ExtensionService/ProcessTask"
- ExtensionService_GetTaskTypes_FullMethodName = "/airgate.plugin.v2.ExtensionService/GetTaskTypes"
+ ExtensionService_Migrate_FullMethodName = "/airgate.plugin.v1.ExtensionService/Migrate"
+ ExtensionService_GetBackgroundTasks_FullMethodName = "/airgate.plugin.v1.ExtensionService/GetBackgroundTasks"
+ ExtensionService_RunBackgroundTask_FullMethodName = "/airgate.plugin.v1.ExtensionService/RunBackgroundTask"
+ ExtensionService_HandleRequest_FullMethodName = "/airgate.plugin.v1.ExtensionService/HandleRequest"
+ ExtensionService_HandleStreamRequest_FullMethodName = "/airgate.plugin.v1.ExtensionService/HandleStreamRequest"
+ ExtensionService_ProcessTask_FullMethodName = "/airgate.plugin.v1.ExtensionService/ProcessTask"
+ ExtensionService_GetTaskTypes_FullMethodName = "/airgate.plugin.v1.ExtensionService/GetTaskTypes"
)
// ExtensionServiceClient is the client API for ExtensionService service.
@@ -1028,7 +1028,7 @@ func _ExtensionService_GetTaskTypes_Handler(srv interface{}, ctx context.Context
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var ExtensionService_ServiceDesc = grpc.ServiceDesc{
- ServiceName: "airgate.plugin.v2.ExtensionService",
+ ServiceName: "airgate.plugin.v1.ExtensionService",
HandlerType: (*ExtensionServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
@@ -1067,8 +1067,8 @@ var ExtensionService_ServiceDesc = grpc.ServiceDesc{
}
const (
- MiddlewareService_OnForwardBegin_FullMethodName = "/airgate.plugin.v2.MiddlewareService/OnForwardBegin"
- MiddlewareService_OnForwardEnd_FullMethodName = "/airgate.plugin.v2.MiddlewareService/OnForwardEnd"
+ MiddlewareService_OnForwardBegin_FullMethodName = "/airgate.plugin.v1.MiddlewareService/OnForwardBegin"
+ MiddlewareService_OnForwardEnd_FullMethodName = "/airgate.plugin.v1.MiddlewareService/OnForwardEnd"
)
// MiddlewareServiceClient is the client API for MiddlewareService service.
@@ -1226,7 +1226,7 @@ func _MiddlewareService_OnForwardEnd_Handler(srv interface{}, ctx context.Contex
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var MiddlewareService_ServiceDesc = grpc.ServiceDesc{
- ServiceName: "airgate.plugin.v2.MiddlewareService",
+ ServiceName: "airgate.plugin.v1.MiddlewareService",
HandlerType: (*MiddlewareServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
@@ -1243,8 +1243,8 @@ var MiddlewareService_ServiceDesc = grpc.ServiceDesc{
}
const (
- EventService_GetEventSubscriptions_FullMethodName = "/airgate.plugin.v2.EventService/GetEventSubscriptions"
- EventService_HandleEvent_FullMethodName = "/airgate.plugin.v2.EventService/HandleEvent"
+ EventService_GetEventSubscriptions_FullMethodName = "/airgate.plugin.v1.EventService/GetEventSubscriptions"
+ EventService_HandleEvent_FullMethodName = "/airgate.plugin.v1.EventService/HandleEvent"
)
// EventServiceClient is the client API for EventService service.
@@ -1366,7 +1366,7 @@ func _EventService_HandleEvent_Handler(srv interface{}, ctx context.Context, dec
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var EventService_ServiceDesc = grpc.ServiceDesc{
- ServiceName: "airgate.plugin.v2.EventService",
+ ServiceName: "airgate.plugin.v1.EventService",
HandlerType: (*EventServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
@@ -1383,8 +1383,8 @@ var EventService_ServiceDesc = grpc.ServiceDesc{
}
const (
- CoreInvokeService_Invoke_FullMethodName = "/airgate.plugin.v2.CoreInvokeService/Invoke"
- CoreInvokeService_InvokeStream_FullMethodName = "/airgate.plugin.v2.CoreInvokeService/InvokeStream"
+ CoreInvokeService_Invoke_FullMethodName = "/airgate.plugin.v1.CoreInvokeService/Invoke"
+ CoreInvokeService_InvokeStream_FullMethodName = "/airgate.plugin.v1.CoreInvokeService/InvokeStream"
)
// CoreInvokeServiceClient is the client API for CoreInvokeService service.
@@ -1510,7 +1510,7 @@ type CoreInvokeService_InvokeStreamServer = grpc.BidiStreamingServer[HostStreamF
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var CoreInvokeService_ServiceDesc = grpc.ServiceDesc{
- ServiceName: "airgate.plugin.v2.CoreInvokeService",
+ ServiceName: "airgate.plugin.v1.CoreInvokeService",
HandlerType: (*CoreInvokeServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
diff --git a/runtimego/grpc/handshake.go b/runtimego/grpc/handshake.go
index d05def4..741a1d5 100644
--- a/runtimego/grpc/handshake.go
+++ b/runtimego/grpc/handshake.go
@@ -4,9 +4,9 @@ import "github.com/hashicorp/go-plugin"
// Handshake 统一握手配置,核心和插件必须使用相同值
var Handshake = plugin.HandshakeConfig{
- ProtocolVersion: 2,
+ ProtocolVersion: 1,
MagicCookieKey: "AIRGATE_PLUGIN",
- MagicCookieValue: "airgate-v2",
+ MagicCookieValue: "airgate-v1",
}
// PluginMap 插件类型到 go-plugin.Plugin 的映射键名
From 48f71a36842c2ec4320d8dbc9021c6475a92fb88 Mon Sep 17 00:00:00 2001
From: huangenjun <1021217094@qq.com>
Date: Wed, 13 May 2026 08:47:31 +0800
Subject: [PATCH 06/11] =?UTF-8?q?docs:=20=E8=AE=BE=E8=AE=A1=E5=BC=82?=
=?UTF-8?q?=E6=AD=A5=E4=BB=BB=E5=8A=A1=E7=8A=B6=E6=80=81=E6=9C=BA?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/async-task-state-machine.md | 1424 ++++++++++++++++++++++++++++++
1 file changed, 1424 insertions(+)
create mode 100644 docs/async-task-state-machine.md
diff --git a/docs/async-task-state-machine.md b/docs/async-task-state-machine.md
new file mode 100644
index 0000000..dc5d6a0
--- /dev/null
+++ b/docs/async-task-state-machine.md
@@ -0,0 +1,1424 @@
+# 异步任务状态机设计
+
+本文定义 AirGate 在 Core、SDK、插件之间处理长耗时生成任务的统一设计。目标是让客户端无差别使用同步或异步调用,让 Core 无差别承接同步上游、异步上游和流式上游,让插件只负责任务类型的业务编解码。
+
+## 背景
+
+当前 `airgate-openai` 的生图任务已经暴露出几个结构性问题:
+
+- 状态查询路径写死在图片语义里,例如 `/v1/images/tasks`、`/v1/images/tasks/list`。
+- 插件里的任务创建、执行、查询、列表、响应格式都绑定 `image_generation`。
+- 有些上游同步返回图片,有些上游先返回自己的 `task_id` 再轮询,有些上游通过 SSE / WebSocket 流式返回,当前代码需要在具体图片逻辑里混合处理这些差异。
+- 后续视频生成、音乐生成、语音合成、文件处理等都可能需要“先返回任务 ID,后查状态”的交互,如果继续按资源类型复制一套任务代码,会很快变成多套状态机。
+- 任务能力不是 OpenAI 独有,其他平台插件也可能需要同样的异步执行模型。
+
+因此需要先抽象“任务生命周期”和“上游执行策略”,再把图片任务迁进去。
+
+## 设计目标
+
+1. Core 对客户端提供统一任务能力,不要求客户端知道上游是同步、异步还是流式。
+2. Core 内部维护唯一的任务状态机,负责创建、排队、执行、重试、取消、查询、列表和权限校验。
+3. SDK 提供稳定任务契约,让插件声明任务类型、输入输出 schema、执行能力和查询展示语义。
+4. 插件只实现任务业务逻辑:如何识别请求、如何构造任务 input、如何调用上游、如何标准化 output。
+5. 图片、视频、音乐、语音、文件处理等长耗时任务都能复用同一套 Core 机制。
+6. 对外响应中的 `task_id` 必须是 AirGate 自己的任务 ID;上游自己的任务 ID 只能作为内部执行细节存储。
+7. 支持同步等待模式和后台任务模式,并允许 Core 在等待超时后自动转为后台任务。
+8. 保持现有 OpenAI 兼容 API 可迁移,不要求一次性废弃 `/v1/images/generations` 等现有入口。
+
+## 非目标
+
+- 不在 SDK 中编码 OpenAI、Anthropic 或任意平台的具体参数。
+- 不让 Core 理解图片、视频、音乐的业务字段含义。
+- 不把某个平台的定价、模型规则、轮询接口写进 Core。
+- 不要求所有任务都必须异步执行。短任务仍然可以同步返回。
+- 不要求所有插件都立即实现新任务协议。应支持分阶段迁移。
+
+## 核心概念
+
+### AirGate Task
+
+AirGate Task 是 Core 管理的统一任务实体。它是客户端和 Core 之间的稳定对象。
+
+任务对象建议如下:
+
+```json
+{
+ "id": "ag_task_123",
+ "plugin_id": "gateway-openai",
+ "type": "image.generate",
+ "status": "processing",
+ "progress": 35,
+ "stage": "polling_upstream",
+ "attributes": {
+ "platform": "openai",
+ "model": "gpt-image-2",
+ "size": "1024x1024"
+ },
+ "input": {
+ "model": "gpt-image-2",
+ "prompt": "一只柴犬坐在樱花树下",
+ "size": "1024x1024"
+ },
+ "output": null,
+ "error": null,
+ "created_at": "2026-05-12T10:00:00Z",
+ "updated_at": "2026-05-12T10:00:10Z",
+ "started_at": "2026-05-12T10:00:01Z",
+ "completed_at": null
+}
+```
+
+`id` 是 AirGate 任务 ID,不等于上游任务 ID。
+
+Core 固定字段必须保持最小化,只包含任务生命周期、归属和安全边界。模型、平台、分辨率、时长、质量、音色等都不应成为 Core 任务表的硬编码列;它们属于插件声明的 `input`、`attributes`、`execution` 或最终 `Usage`。Core 可以保存、返回、按声明做弱索引,但不理解这些字段的业务含义。
+
+模型很重要,但它是“使用记录维度”,不是“任务生命周期字段”。任务和使用记录统一只记录一个主模型字段:`model`。这个字段始终表示实际执行和计费的模型。模型映射、降级和工具链细节对用户透明,不进入通用任务或使用记录字段。
+
+建议模型按以下位置记录:
+
+| 位置 | 用途 | 示例 |
+| --- | --- | --- |
+| `input.model` | 任务请求里的模型,用于任务复现 | `gpt-image-2` |
+| `attributes.model` | 未完成任务列表里的展示/粗筛选 | `gpt-image-2` |
+| `execution.*` | 插件内部工具链细节,不作为用户侧模型维度 | `tool_model=gpt-5.4` |
+| `usage.Model` / `usage.Attributes.model` | 完成后的审计、计费、统计事实 | `gpt-5.4` |
+
+`Usage` 是最终事实来源。任务完成前可以用 `attributes` 暂存展示值;任务完成后应以关联的使用记录为准。
+
+示例:
+
+- 用户请求 `gpt-image-2`,上游也按 `gpt-image-2` 执行:`model=gpt-image-2`。
+- 视频或音乐任务如存在模型字段,同样只记录实际执行和计费模型。
+
+生图的分辨率、视频的时长和帧率、音乐的时长和质量、语音的音色和格式也不能进入 Core 固定字段。它们都属于任务类型自己的 input schema。Core 只保存和透传这些结构化 JSON,不理解字段语义。
+
+通用字段与类型参数的边界:
+
+| 层级 | 字段示例 | Core 是否理解 | 用途 |
+| --- | --- | --- | --- |
+| 固定生命周期字段 | `id`、`plugin_id`、`type`、`status`、`progress`、`user_id` | 是 | 权限、状态机、查询、列表 |
+| 类型化 input | `size`、`duration_seconds`、`quality`、`aspect_ratio`、`voice` | 否 | 插件校验、上游请求构造、任务复现 |
+| 展示/弱索引字段 | `attributes.model`、`attributes.duration_seconds`、`attributes.resolution` | 只理解字符串键值 | 任务未完成时的列表页、管理后台、粗筛选 |
+| 执行细节 | `execution` | 否 | 插件内部轮询、上游 task id、阶段信息 |
+
+例如图片任务 input:
+
+```json
+{
+ "prompt": "一只柴犬坐在樱花树下",
+ "size": "1024x1024",
+ "quality": "high",
+ "background": "transparent",
+ "output_format": "png"
+}
+```
+
+视频任务 input:
+
+```json
+{
+ "prompt": "城市夜景航拍",
+ "duration_seconds": 8,
+ "aspect_ratio": "16:9",
+ "resolution": "1080p",
+ "fps": 24
+}
+```
+
+音乐任务 input:
+
+```json
+{
+ "prompt": "轻快的电子流行音乐",
+ "duration_seconds": 30,
+ "quality": "high",
+ "format": "mp3",
+ "loopable": false
+}
+```
+
+Core 不为这些字段建固定列。需要列表展示时,插件可以在创建任务时写入少量字符串化的 `attributes`:
+
+```json
+{
+ "attributes": {
+ "duration_seconds": "8",
+ "resolution": "1080p",
+ "aspect_ratio": "16:9"
+ }
+}
+```
+
+`attributes` 只放少量字符串化、可展示、可粗筛选的维度;完整参数仍以 `input` 为准。
+
+### Upstream Task
+
+Upstream Task 是某些上游平台返回的任务对象,例如:
+
+```json
+{
+ "task_id": "img_abc",
+ "status_url": "https://provider.example/tasks/img_abc"
+}
+```
+
+它只属于插件执行细节,必须持久化到任务的 `execution` 字段,不作为客户端主 ID。
+
+持久化 `execution` 的原因:
+
+- Core worker 或插件进程重启后,需要继续按上游任务 ID 轮询。
+- 等待模式超时转后台后,需要恢复上游任务状态。
+- 上游短暂失败后重试时,需要判断是继续轮询已有上游任务,还是重新创建。
+- 取消任务时,如果上游支持取消,需要拿上游任务 ID 调用取消接口。
+
+客户端永远查询 AirGate Task:
+
+```text
+GET /v1/tasks/ag_task_123
+```
+
+而不是直接查询上游:
+
+```text
+GET /provider/tasks/img_abc
+```
+
+### Task Type
+
+任务类型使用领域动作命名,而不是平台命名:
+
+```text
+image.generate
+image.edit
+video.generate
+music.generate
+audio.speech
+file.process
+```
+
+平台差异由 `plugin_id`、插件 schema、`input` 和 `attributes` 表达,不应把平台或模型塞进类型名里。
+
+不推荐:
+
+```text
+openai_image_generation
+gpt_image_task
+```
+
+推荐:
+
+```text
+image.generate
+```
+
+### Execution Mode
+
+Execution Mode 表示上游实际如何产出结果。
+
+```go
+type TaskExecutionMode string
+
+const (
+ TaskExecutionSyncResult TaskExecutionMode = "sync_result"
+ TaskExecutionUpstreamTask TaskExecutionMode = "upstream_task"
+ TaskExecutionStreamResult TaskExecutionMode = "stream_result"
+)
+```
+
+含义:
+
+| 模式 | 上游行为 | 插件行为 |
+| --- | --- | --- |
+| `sync_result` | 单次 HTTP 调用直接返回最终结果 | 解析响应并完成 AirGate Task |
+| `upstream_task` | 上游返回自己的 `task_id` | 保存上游任务信息,轮询直到完成 |
+| `stream_result` | 上游通过 SSE / WebSocket 持续返回 | 消费流并聚合最终结果 |
+
+Execution Mode 是插件内部执行策略,不直接决定客户端使用同步还是异步接口。
+
+### Client Wait Mode
+
+Client Wait Mode 表示客户端希望 Core 如何响应本次请求。
+
+```go
+type TaskClientMode string
+
+const (
+ TaskClientModeWait TaskClientMode = "wait"
+ TaskClientModeBackground TaskClientMode = "background"
+ TaskClientModeAuto TaskClientMode = "auto"
+)
+```
+
+| 模式 | 对客户端语义 |
+| --- | --- |
+| `wait` | Core 尽量等待任务完成并返回最终结果 |
+| `background` | Core 创建任务后立即返回 AirGate `task_id` |
+| `auto` | Core 等待一小段时间;超时未完成则返回 `task_id` |
+
+Client Wait Mode 与 Execution Mode 独立。
+
+例如:
+
+- 客户端 `wait` + 上游 `sync_result`:通常直接返回最终结果。
+- 客户端 `wait` + 上游 `upstream_task`:Core 可以轮询一段时间,完成则返回结果,超时则返回 `202 + task_id`。
+- 客户端 `background` + 上游 `sync_result`:Core 仍然立即返回 `task_id`,后台 worker 同步调上游并完成任务。
+- 客户端 `auto` + 上游 `stream_result`:Core 聚合流;超过等待窗口就转后台。
+
+## 状态机
+
+Core 维护统一状态机:
+
+```text
+pending
+ -> processing
+ -> completed
+ -> failed
+ -> cancelling -> cancelled
+ -> retrying -> pending
+```
+
+建议状态定义:
+
+| 状态 | 含义 | 可被客户端查询 |
+| --- | --- | --- |
+| `pending` | 已创建,等待调度 | 是 |
+| `processing` | 插件正在执行 | 是 |
+| `retrying` | 本次执行失败,等待下一次重试 | 是 |
+| `completed` | 最终结果已写入 output | 是 |
+| `failed` | 最终失败,已无重试 | 是 |
+| `cancelling` | 正在请求取消 | 是 |
+| `cancelled` | 已取消 | 是 |
+
+状态迁移规则:
+
+```text
+pending -> processing
+processing -> completed
+processing -> failed
+processing -> retrying
+retrying -> pending
+processing -> cancelling
+cancelling -> cancelled
+cancelling -> failed
+```
+
+禁止迁移:
+
+```text
+completed -> processing
+failed -> processing
+cancelled -> processing
+```
+
+终态:
+
+```text
+completed
+failed
+cancelled
+```
+
+Core 应校验状态迁移,插件不能随意把任意状态写入任务。
+
+## 对外 API 设计
+
+### 创建任务
+
+建议新增通用任务 API:
+
+```text
+POST /v1/tasks
+```
+
+请求示例:
+
+```json
+{
+ "plugin_id": "gateway-openai",
+ "type": "image.generate",
+ "input": {
+ "model": "gpt-image-2",
+ "prompt": "一只柴犬坐在樱花树下",
+ "size": "1024x1024"
+ },
+ "mode": "background"
+}
+```
+
+响应示例:
+
+```json
+{
+ "id": "ag_task_123",
+ "object": "task",
+ "type": "image.generate",
+ "status": "pending",
+ "progress": 0
+}
+```
+
+### 查询任务
+
+```text
+GET /v1/tasks/{task_id}
+```
+
+处理中:
+
+```json
+{
+ "id": "ag_task_123",
+ "object": "task",
+ "type": "image.generate",
+ "status": "processing",
+ "progress": 40,
+ "output": null,
+ "error": null
+}
+```
+
+完成:
+
+```json
+{
+ "id": "ag_task_123",
+ "object": "task",
+ "type": "image.generate",
+ "status": "completed",
+ "progress": 100,
+ "output": {
+ "kind": "image",
+ "images": [
+ {
+ "b64_json": "...",
+ "mime_type": "image/png"
+ }
+ ],
+ "usage": {
+ "input_tokens": 50,
+ "output_tokens": 1056
+ }
+ },
+ "error": null
+}
+```
+
+失败:
+
+```json
+{
+ "id": "ag_task_123",
+ "object": "task",
+ "type": "image.generate",
+ "status": "failed",
+ "progress": 0,
+ "output": null,
+ "error": {
+ "type": "upstream_error",
+ "code": "provider_rejected",
+ "message": "请求暂时无法完成,请稍后重试"
+ }
+}
+```
+
+### 列表任务
+
+```text
+GET /v1/tasks?type=image.generate&status=completed&limit=20&offset=0
+```
+
+响应:
+
+```json
+{
+ "object": "list",
+ "data": [],
+ "total": 0
+}
+```
+
+### 取消任务
+
+```text
+POST /v1/tasks/{task_id}/cancel
+```
+
+取消能力取决于插件声明:
+
+- 如果上游支持取消,插件调用上游取消接口。
+- 如果上游不支持取消,Core 标记 `cancelling` 后尽力中止本地轮询或流消费。
+- 如果任务已完成,取消返回当前终态,不应破坏结果。
+
+## 兼容现有 OpenAI 风格入口
+
+现有资源入口不必立即删除:
+
+```text
+POST /v1/images/generations
+POST /v1/images/edits
+```
+
+这些入口可以映射到任务系统:
+
+```text
+/v1/images/generations -> type=image.generate
+/v1/images/edits -> type=image.edit
+```
+
+客户端可以通过请求体或 Header 指定模式。
+
+请求体方式:
+
+```json
+{
+ "model": "gpt-image-2",
+ "prompt": "...",
+ "task_mode": "background"
+}
+```
+
+Header 方式:
+
+```text
+Prefer: respond-async
+```
+
+或:
+
+```text
+Prefer: wait=120
+```
+
+建议优先支持 Header,因为它不污染 OpenAI 兼容 body;但也可以保留 `task_mode` 作为 AirGate 扩展参数。
+
+响应策略:
+
+| 客户端模式 | 任务完成前响应 | 任务完成后响应 |
+| --- | --- | --- |
+| `background` | `202 Accepted + task_id` | 查询任务拿结果 |
+| `wait` | 等待完成;超过最大等待返回 `202 + task_id` | 直接返回原协议结果或任务结果 |
+| `auto` | 短时间内完成就返回结果,否则返回 `202 + task_id` | 查询任务拿结果 |
+
+对于 OpenAI Images 兼容接口,如果任务在等待窗口内完成,可以返回原 Images API schema:
+
+```json
+{
+ "created": 1713833628,
+ "data": [
+ {
+ "b64_json": "..."
+ }
+ ],
+ "usage": {}
+}
+```
+
+如果未完成,返回 AirGate 任务 schema:
+
+```json
+{
+ "id": "ag_task_123",
+ "object": "task",
+ "status": "processing"
+}
+```
+
+这会带来一个客户端解析问题:严格 OpenAI SDK 可能不认识 task schema。因此兼容 OpenAI SDK 的路径默认应保持 `wait` 或 `auto`;只有明确传 `Prefer: respond-async` 或 AirGate 扩展参数时才返回任务对象。
+
+## Core 职责
+
+Core 负责以下能力:
+
+1. 任务表结构。
+2. 状态机校验。
+3. 用户和分组权限校验。
+4. 账号调度和 failover。
+5. 后台 worker 分发任务给插件。
+6. 根据任务类型找到插件和 handler。
+7. 记录任务 attempts、max_attempts、priority。
+8. 写入 output、error、progress。
+9. 查询和列表接口。
+10. 取消任务接口。
+11. 任务结果与使用记录关联。
+12. 任务过期清理策略。
+13. 对同步等待模式实现 wait / timeout / fallback to task。
+
+Core 不应理解:
+
+- 图片 prompt 字段怎么解析。
+- 视频时长如何映射到上游参数。
+- 音乐质量、音色、格式、循环等字段是否合法。
+- 某个模型支持哪些扩展参数。
+- 上游任务状态字段叫什么。
+- 某个平台的失败码是否可重试。
+- 某个平台的具体计费公式。
+
+这些都属于插件。
+
+## SDK 职责
+
+SDK 需要表达三类契约。
+
+### 任务 schema 声明
+
+任务 schema 在新协议中的最小形态:
+
+```go
+type TaskSchema struct {
+ Type string
+ Input PayloadSchema
+ Output PayloadSchema
+ Metadata map[string]string
+}
+```
+
+任务类型展示名称和说明不是状态机必要字段,可以由管理后台根据 `type`、`Metadata` 或插件前端 schema 生成,不进入 Core 任务协议。
+
+建议扩展或通过 `Metadata` 先承载执行策略:
+
+```go
+type TaskSchema struct {
+ Type string
+ Input PayloadSchema
+ Output PayloadSchema
+ DefaultMode string
+ MaxAttempts int
+ Cancellable bool
+ ProgressMode string
+ ResultProtocol string
+ Metadata map[string]string
+}
+```
+
+字段含义:
+
+| 字段 | 含义 |
+| --- | --- |
+| `DefaultMode` | 默认客户端模式,通常是 `auto` 或 `wait` |
+| `MaxAttempts` | 默认最大重试次数 |
+| `Cancellable` | 是否支持取消 |
+| `ProgressMode` | `none`、`percent`、`stage` |
+| `ResultProtocol` | `task`、`openai.images`、`openai.responses` 等展示/兼容提示 |
+
+不同任务类型和模型的扩展参数通过 `Input` 的 JSON Schema 表达。Core 不为这些参数新增 Go 字段或数据库列。
+
+图片任务 schema 示例:
+
+```json
+{
+ "type": "object",
+ "required": ["prompt"],
+ "properties": {
+ "prompt": { "type": "string" },
+ "size": { "type": "string", "examples": ["1024x1024", "1536x1024"] },
+ "quality": { "type": "string", "enum": ["low", "medium", "high", "auto"] },
+ "background": { "type": "string", "enum": ["opaque", "transparent"] },
+ "output_format": { "type": "string", "enum": ["png", "jpeg", "webp"] }
+ },
+ "additionalProperties": true
+}
+```
+
+视频任务 schema 示例:
+
+```json
+{
+ "type": "object",
+ "required": ["prompt"],
+ "properties": {
+ "prompt": { "type": "string" },
+ "duration_seconds": { "type": "integer", "minimum": 1, "maximum": 60 },
+ "resolution": { "type": "string" },
+ "aspect_ratio": { "type": "string" },
+ "fps": { "type": "integer" }
+ },
+ "additionalProperties": true
+}
+```
+
+音乐任务 schema 示例:
+
+```json
+{
+ "type": "object",
+ "required": ["prompt"],
+ "properties": {
+ "prompt": { "type": "string" },
+ "duration_seconds": { "type": "integer", "minimum": 1 },
+ "quality": { "type": "string" },
+ "format": { "type": "string" },
+ "loopable": { "type": "boolean" }
+ },
+ "additionalProperties": true
+}
+```
+
+这里建议 `additionalProperties: true`,原因是上游能力变化很快,插件需要能先透传新参数;插件可以按模型能力做更严格的运行时校验。管理后台和开发工具使用 schema 渲染表单和提示,Core 后端只保存 JSON。
+
+如果短期不想改 SDK 强类型,可以先放入 `Metadata`:
+
+```go
+Metadata: map[string]string{
+ "default_mode": "auto",
+ "max_attempts": "3",
+ "cancellable": "false",
+ "progress_mode": "percent",
+ "result_protocol": "openai.images",
+}
+```
+
+### 任务执行接口
+
+当前 SDK 只有:
+
+```go
+type TaskProcessor interface {
+ ProcessTask(ctx context.Context, task HostTask) error
+ TaskTypes() []string
+}
+```
+
+中期建议演进为:
+
+```go
+type TaskDefinitionProvider interface {
+ TaskDefinitions() []TaskDefinition
+}
+
+type TaskDefinition struct {
+ Schema TaskSchema
+ Handler TaskHandler
+}
+
+type TaskHandler interface {
+ Execute(ctx context.Context, task HostTask, runtime TaskRuntime) (*TaskResult, error)
+}
+```
+
+其中 `TaskRuntime` 由 SDK 或插件本地封装,负责安全地更新状态:
+
+```go
+type TaskRuntime interface {
+ SetProcessing(ctx context.Context, progress int, stage string) error
+ SetProgress(ctx context.Context, progress int, stage string) error
+ Complete(ctx context.Context, output map[string]any, usage *Usage) error
+ Fail(ctx context.Context, err TaskError) error
+ IsCancellationRequested(ctx context.Context) bool
+}
+```
+
+短期为了减少 SDK 破坏,可以先在插件内部实现这个 runtime,Core 仍然通过 `tasks.update` 接收状态更新。
+
+### Host method
+
+现有 Host method:
+
+```text
+tasks.create
+tasks.update
+tasks.get
+tasks.list
+gateway.forward
+```
+
+建议补齐或标准化:
+
+```text
+tasks.cancel
+tasks.append_event
+tasks.get_cancellation
+```
+
+`tasks.append_event` 用于记录阶段事件,例如:
+
+```json
+{
+ "task_id": "ag_task_123",
+ "event": "upstream_task_created",
+ "payload": {
+ "mode": "upstream_task"
+ }
+}
+```
+
+如果暂时不做 event 表,也可以把 execution 信息存进任务 output 的内部字段,但不建议长期这么做。
+
+## 插件职责
+
+插件实现任务定义,不实现通用状态机。
+
+建议插件内部结构:
+
+```go
+type TaskHandler interface {
+ Type() string
+ BuildInput(ctx context.Context, req *sdk.ForwardRequest) (map[string]any, error)
+ Execute(ctx context.Context, task sdk.HostTask, runtime TaskRuntime) error
+ BuildResponse(task *sdk.HostTask) map[string]any
+}
+```
+
+如果同一插件有多个任务:
+
+```go
+type TaskRegistry struct {
+ handlers map[string]TaskHandler
+}
+```
+
+OpenAI 插件注册:
+
+```go
+registry.Register(OpenAIImageGenerateTask{})
+registry.Register(OpenAIImageEditTask{})
+registry.Register(OpenAIVideoGenerateTask{})
+registry.Register(OpenAIMusicGenerateTask{})
+```
+
+`ProcessTask` 只做分发:
+
+```go
+func (g *OpenAIGateway) ProcessTask(ctx context.Context, task sdk.HostTask) error {
+ handler := g.tasks.Get(task.TaskType)
+ if handler == nil {
+ return fmt.Errorf("不支持的任务类型: %s", task.TaskType)
+ }
+ return g.runner.Run(ctx, task, handler)
+}
+```
+
+## 上游执行策略
+
+### 同步上游
+
+流程:
+
+```text
+Core 创建 AirGate Task
+Core worker 分发给插件
+插件调用上游
+上游直接返回最终结果
+插件标准化 output
+Core 标记 completed
+```
+
+适合:
+
+- 当前很多 Images API 直连上游。
+- 小文件处理。
+- 短音频生成。
+
+### 异步上游
+
+流程:
+
+```text
+Core 创建 AirGate Task
+插件调用上游创建任务
+上游返回 upstream_task_id
+插件保存 execution 信息
+插件轮询上游状态
+上游完成
+插件获取最终结果
+Core 标记 completed
+```
+
+插件内部 execution 示例:
+
+```json
+{
+ "mode": "upstream_task",
+ "provider": "apimart",
+ "upstream_task_id": "img_abc",
+ "status_url": "https://provider.example/tasks/img_abc",
+ "poll_interval_ms": 3000,
+ "last_status": "running",
+ "next_poll_at": "2026-05-12T10:00:20Z",
+ "created_at": "2026-05-12T10:00:01Z",
+ "updated_at": "2026-05-12T10:00:10Z"
+}
+```
+
+这类字段是任务恢复所必需的持久化状态,不应直接作为公共 output 暴露给普通用户;管理后台可以按权限查看脱敏信息。
+
+`execution` 更新策略:
+
+- 插件拿到上游任务 ID 后应立即写入 `execution`,再进入轮询。
+- 每次轮询后更新 `last_status`、`updated_at`、`next_poll_at` 等恢复所需字段。
+- 如果上游返回结果 URL、文件 ID 或下载 token,优先保存可恢复的引用,不要只保存在内存里。
+- 如果字段包含签名 URL、token 或账号相关敏感信息,应加密、脱敏或只保存可重新获取结果的非敏感 ID。
+- Core 不解析 `execution` 业务字段,只负责持久化、权限隔离和传回同一插件继续执行。
+
+### 流式上游
+
+流程:
+
+```text
+Core 创建 AirGate Task
+插件打开 SSE / WebSocket
+插件消费事件
+插件更新 progress / stage
+插件聚合最终结果
+Core 标记 completed
+```
+
+适合:
+
+- ChatGPT OAuth WebSocket 图片工具。
+- 后续可能的流式视频生成进度。
+
+## 任务与使用记录
+
+任务表不是审计和计费事实表。AirGate 里所有可计费或需要审计的调用都应该落到统一使用记录里,包括:
+
+- 图片生成和图片编辑。
+- 视频生成。
+- 音乐生成。
+- 语音合成。
+- 未来其它工具型、媒体型或文件型任务。
+
+普通对话模型调用、Responses / Chat Completions 调用、Anthropic Messages 协议翻译等不需要任务状态机。它们仍然通过现有同步或流式 Forward 路径直接写 Usage。任务状态机只处理长耗时、可后台化、需要查询状态的生成或处理类任务。
+
+任务和使用记录的关系:
+
+```text
+同步普通调用
+ ForwardOutcome.Usage -> Core 写使用记录
+
+异步任务调用
+ Core 创建 Task
+ 插件执行 Task
+ 插件返回 Usage
+ Core 写使用记录
+ Task.usage_id -> Usage.id
+```
+
+也就是说,任务只回答“这件事做到哪一步了”,使用记录回答“这次调用实际用了什么、产出了什么计量、花了多少钱”。
+
+### Usage 统一维度
+
+SDK 已经提供通用用量结构:
+
+```go
+type Usage struct {
+ Model string
+ Summary string
+ Attributes []UsageAttribute
+ Metrics []UsageMetric
+ CostDetails []UsageCostDetail
+ Metadata map[string]string
+}
+```
+
+模型和扩展参数应进入 Usage,而不是 Core 任务固定列。对话类调用也使用同一个 Usage 结构,但不经过 Task。
+
+图片生成示例:
+
+```json
+{
+ "model": "gpt-image-2",
+ "summary": "图片生成 · gpt-image-2 · 1024x1024",
+ "attributes": [
+ { "key": "modality", "kind": "custom", "label": "类型", "value": "image" },
+ { "key": "model", "kind": "model", "label": "模型", "value": "gpt-image-2" },
+ { "key": "resolution", "kind": "resolution", "label": "分辨率", "value": "1024x1024" },
+ { "key": "quality", "kind": "quality", "label": "质量", "value": "high" }
+ ],
+ "metrics": [
+ { "key": "image_count", "kind": "image", "label": "图片张数", "unit": "image", "value": 1 },
+ { "key": "output_tokens", "kind": "token", "label": "图像输出 Token", "unit": "token", "value": 4160 }
+ ]
+}
+```
+
+视频生成示例:
+
+```json
+{
+ "model": "video-model",
+ "summary": "视频生成 · 8s · 1080p",
+ "attributes": [
+ { "key": "modality", "kind": "custom", "label": "类型", "value": "video" },
+ { "key": "model", "kind": "model", "label": "模型", "value": "video-model" },
+ { "key": "resolution", "kind": "resolution", "label": "分辨率", "value": "1080p" },
+ { "key": "quality", "kind": "quality", "label": "质量", "value": "standard" }
+ ],
+ "metrics": [
+ { "key": "video_seconds", "kind": "video", "label": "视频时长", "unit": "second", "value": 8 }
+ ]
+}
+```
+
+音乐生成示例:
+
+```json
+{
+ "model": "music-model",
+ "summary": "音乐生成 · 30s · high",
+ "attributes": [
+ { "key": "modality", "kind": "custom", "label": "类型", "value": "music" },
+ { "key": "model", "kind": "model", "label": "模型", "value": "music-model" },
+ { "key": "quality", "kind": "quality", "label": "质量", "value": "high" },
+ { "key": "format", "kind": "custom", "label": "格式", "value": "mp3" }
+ ],
+ "metrics": [
+ { "key": "audio_seconds", "kind": "audio", "label": "音频时长", "unit": "second", "value": 30 }
+ ]
+}
+```
+
+### Task 与 Usage 的字段边界
+
+| 信息 | Task 中的位置 | Usage 中的位置 | 说明 |
+| --- | --- | --- | --- |
+| 生命周期状态 | `status`、`progress`、`stage` | 不存 | Task 独有 |
+| 客户端请求参数 | `input` | 可按需复制到 `Attributes` | 完整参数以 Task input 为准 |
+| 上游执行细节 | `execution` | 可脱敏后进入 `Metadata` | 普通用户默认不看 execution |
+| 模型 | `input.model` / `attributes.model` 临时展示 | `Model` / `Attributes` | 计费与审计以 Usage 为准 |
+| 分辨率、时长、质量 | `input` / `attributes` 临时展示 | `Attributes` / `Metrics` | 统计以 Usage 为准 |
+| token、图片张数、视频秒数 | 不建议存 Task 固定列 | `Metrics` | 统一统计入口 |
+| 成本 | 不建议存 Task 固定列 | `AccountCost` / `CostDetails` | 统一扣费入口 |
+| 使用记录关联 | `usage_id` | `id` | Task 完成后关联 |
+
+列表页如果要展示未完成任务,可以读取 Task 的 `attributes`,或按插件声明的 schema 组合展示文案。完成后的历史账单、统计图、成本明细必须读取 Usage。
+
+## OpenAI 图片迁移映射
+
+当前行为:
+
+```text
+/v1/images/generations
+/v1/images/edits
+/v1/images/tasks
+/v1/images/tasks/list
+```
+
+目标映射:
+
+| 当前入口 | 目标任务类型 |
+| --- | --- |
+| `/v1/images/generations` | `image.generate` |
+| `/v1/images/edits` | `image.edit` |
+
+当前上游路径:
+
+| 上游情况 | Execution Mode |
+| --- | --- |
+| API Key 上游直接返回 Images JSON | `sync_result` |
+| API Key 中转返回上游 `task_id` | `upstream_task` |
+| OAuth Responses tool / WebSocket 聚合图片 | `stream_result` |
+| Web Reverse 生成后返回图片 | `sync_result` 或 `stream_result`,取决于实现细节 |
+
+迁移后的插件内部结构:
+
+```text
+task_registry.go
+task_runner.go
+task_http.go
+task_image_generate.go
+task_image_edit.go
+```
+
+`task_images.go` 不再同时承担所有职责。
+
+## 数据模型建议
+
+Core 任务表建议字段:
+
+```text
+id
+plugin_id
+task_type
+status
+progress
+stage
+user_id
+input
+output
+attributes
+execution
+error_type
+error_code
+error_message
+usage_id
+attempts
+max_attempts
+priority
+idempotency_key
+created_at
+updated_at
+started_at
+completed_at
+cancel_requested_at
+expires_at
+```
+
+字段说明:
+
+| 字段 | 说明 |
+| --- | --- |
+| `input` | 插件标准化后的任务输入 |
+| `output` | 插件标准化后的任务输出 |
+| `attributes` | 插件提供的少量展示/筛选维度,值建议统一转字符串 |
+| `execution` | 插件内部执行状态,例如 upstream task id、轮询状态;必须持久化以支持重启恢复 |
+| `usage_id` | 关联使用记录;完成后的模型、计量和费用事实以 usage 为准 |
+| `idempotency_key` | 防止客户端重复创建相同任务 |
+| `expires_at` | 任务结果保留时间 |
+
+`execution` 应有权限边界。普通用户查询任务时不返回或只返回脱敏摘要。
+
+## 错误模型
+
+任务错误建议统一为:
+
+```json
+{
+ "type": "upstream_error",
+ "code": "rate_limited",
+ "message": "请求暂时无法完成,请稍后重试",
+ "retryable": true
+}
+```
+
+错误类型建议:
+
+| type | 含义 |
+| --- | --- |
+| `invalid_request` | 客户端参数错误 |
+| `auth_error` | 上游认证失败 |
+| `rate_limited` | 上游限流 |
+| `quota_exceeded` | 额度不足 |
+| `upstream_error` | 上游服务错误 |
+| `timeout` | 执行超时 |
+| `cancelled` | 用户取消 |
+| `internal_error` | Core 或插件内部错误 |
+
+插件负责把上游错误映射到标准错误。Core 负责按错误类型决定是否重试和如何展示。
+
+## 重试策略
+
+Core 应控制重试次数,插件提供错误是否可重试的建议。
+
+建议:
+
+- `invalid_request` 不重试。
+- `auth_error` 不重试,并可能标记账号不可用。
+- `rate_limited` 可以按 `retry_after` 重试。
+- `upstream_error` 可以重试。
+- `timeout` 可以重试,但要限制总耗时。
+- 用户取消不重试。
+
+任务 attempt 不应重复扣费。只有上游成功返回可计费用量时才写使用记录;失败费用是否记录由插件按平台规则决定。
+
+## 同步等待策略
+
+Core 对 `wait` / `auto` 模式应有统一等待窗口。
+
+建议配置:
+
+```text
+default_wait_timeout = 120s
+max_wait_timeout = 300s
+background_poll_interval = 1s
+```
+
+等待模式流程:
+
+```text
+创建任务
+立即调度
+等待 completed / failed / cancelled
+如果超时:
+ 返回 202 + task_id
+如果完成:
+ 根据 result_protocol 返回兼容响应或 task 响应
+```
+
+这使同步上游和异步上游都可以对外表现为同步接口。
+
+## 结果协议
+
+任务 output 应统一,但也要支持兼容原协议返回。
+
+统一任务 output:
+
+```json
+{
+ "kind": "image",
+ "items": [
+ {
+ "mime_type": "image/png",
+ "b64_json": "..."
+ }
+ ],
+ "usage": {}
+}
+```
+
+OpenAI Images 兼容响应:
+
+```json
+{
+ "created": 1713833628,
+ "data": [
+ {
+ "b64_json": "..."
+ }
+ ],
+ "usage": {}
+}
+```
+
+插件应提供 output 到协议响应的转换器。Core 不理解图片字段,只调用插件声明的转换能力,或让插件在任务完成时同时写入:
+
+```json
+{
+ "task_output": {},
+ "protocol_outputs": {
+ "openai.images": {}
+ }
+}
+```
+
+短期可以先让插件的旧入口查询任务后自行转换;长期再把协议转换纳入 SDK 能力。
+
+## 权限与安全
+
+Core 查询任务时必须校验:
+
+- 当前用户只能查询自己的任务。
+- 管理员可以查询所有任务。
+- API Key 只能查询该 Key 所属用户/分组创建的任务。
+- 插件只能更新自己创建的任务。
+- 插件不能越权读取其他插件任务。
+
+敏感字段:
+
+- 上游 access token 不得进入 task input / output。
+- 上游 `upstream_task_id` 默认不返回普通用户。
+- 上游原始错误需要脱敏后再写入 `error.message`。
+- 原始响应如包含签名 URL,需要设置过期和权限边界。
+
+## 幂等性
+
+创建任务应支持幂等键:
+
+```text
+Idempotency-Key: xxx
+```
+
+或:
+
+```json
+{
+ "idempotency_key": "xxx"
+}
+```
+
+同一用户、同一插件、同一任务类型、同一幂等键,在任务未过期前应返回同一个 AirGate Task。
+
+这能避免客户端超时重试时重复生成图片/视频并重复扣费。
+
+## 进度语义
+
+进度应是 Core 通用字段,但含义由插件声明。
+
+建议:
+
+| progress | 通用含义 |
+| --- | --- |
+| 0 | 已创建 |
+| 10 | 开始处理 |
+| 30 | 已发送到上游 |
+| 50 | 上游处理中 |
+| 80 | 正在下载或整理结果 |
+| 100 | 完成 |
+
+插件可以附加 `stage`:
+
+```json
+{
+ "progress": 50,
+ "stage": "polling_upstream"
+}
+```
+
+普通客户端可以只看百分比,高级 UI 可以展示 stage。
+
+## 迁移计划
+
+### 阶段一:文档与内部抽象
+
+目标:不改外部行为,先让 `airgate-openai` 内部不再写死图片状态机。
+
+改动:
+
+- 新增插件内部 `TaskRegistry`。
+- 新增插件内部 `TaskRunner`。
+- 把当前 `image_generation` 迁为 `image.generate` / `image.edit` 的 handler。
+- `ProcessTask` 改为按 `task.TaskType` 分发。
+- 查询响应改为通用 `buildTaskResponse`,图片字段由 handler 注入。
+- 保留 `/v1/images/tasks` 和 `/v1/images/tasks/list`,但内部调用通用查询函数。
+
+验收:
+
+- 现有图片任务行为不变。
+- 新增任务类型不需要改 `ProcessTask` 主流程。
+- 后端测试通过。
+
+### 阶段二:SDK schema 补齐
+
+目标:插件能声明结构化任务能力。
+
+改动:
+
+- 扩展 `TaskSchema` 或先用 `Metadata` 标准化字段。
+- `SchemaProvider` 返回 task schema。
+- devserver 展示任务 schema。
+- Core 能读取插件 task schema。
+
+验收:
+
+- `airgate-openai` 能声明 `image.generate` / `image.edit`。
+- Core 管理后台能看到插件支持的任务类型。
+
+### 阶段三:Core 通用任务 API
+
+目标:Core 对外提供 `/v1/tasks`。
+
+改动:
+
+- Core 新增通用任务创建、查询、列表、取消 API。
+- Core 实现 wait/background/auto。
+- Core 实现幂等键。
+- Core worker 支持按 task type 分发给插件。
+- Core 对 task output 做权限过滤。
+
+验收:
+
+- 客户端可以不走 `/v1/images/tasks`,直接查 `/v1/tasks/{id}`。
+- 同步上游和异步上游在客户端表现一致。
+
+### 阶段四:插件迁移旧入口
+
+目标:旧 OpenAI Images 入口复用 Core 任务 API。
+
+改动:
+
+- `/v1/images/generations` 可根据 `Prefer` 或 `task_mode` 选择 wait/background/auto。
+- `/v1/images/tasks` 标记为兼容别名。
+- 新增视频/音乐任务时只加 handler 和 schema。
+
+验收:
+
+- OpenAI SDK 默认路径仍可同步拿结果。
+- 显式异步模式返回 AirGate task。
+- 视频/音乐不新增专属状态机。
+
+### 阶段五:删除旧任务专用路由
+
+目标:清理历史兼容代码。
+
+前提:
+
+- Core 通用任务 API 已稳定。
+- 前端和文档已切换。
+- 旧客户端有迁移窗口。
+
+改动:
+
+- 删除 `/v1/images/tasks` 和 `/v1/images/tasks/list`。
+- 删除插件里的图片专用查询函数。
+
+## 当前 `airgate-openai` 应先怎么改
+
+建议先做插件内部重构,不等待 Core 完整改造。
+
+第一步文件结构:
+
+```text
+backend/internal/gateway/task_registry.go
+backend/internal/gateway/task_runner.go
+backend/internal/gateway/task_http.go
+backend/internal/gateway/task_image.go
+```
+
+职责:
+
+| 文件 | 职责 |
+| --- | --- |
+| `task_registry.go` | 注册和查找 task handler |
+| `task_runner.go` | 通用状态迁移、调用 `forwardViaHost`、错误处理 |
+| `task_http.go` | 从 HTTP 请求解析 task_id、列表参数、写 task 响应 |
+| `task_image.go` | OpenAI 图片 input/output 编解码 |
+
+第二步替换点:
+
+- `TaskTypes()` 从 registry 生成。
+- `ProcessTask()` 从 registry 查 handler。
+- `forwardImagesViaTask()` 改成 `forwardTask()`,图片 handler 负责 `BuildInput`。
+- `buildTaskRequestBody()` 移入图片 handler。
+- `buildTaskResponse()` 支持 handler 自定义 output projection。
+
+第三步保留行为:
+
+- 仍然支持 `/v1/images/tasks`。
+- 仍然返回当前图片任务响应字段。
+- 仍然支持当前 API Key 同步上游和上游异步 `task_id` 轮询。
+
+这样后续加视频时只做:
+
+```go
+registry.Register(OpenAIVideoGenerateTask{})
+```
+
+不改状态机。
+
+## 测试计划
+
+Core 测试:
+
+- 创建任务返回 task_id。
+- wait 模式任务完成时返回最终结果。
+- wait 超时返回 `202 + task_id`。
+- background 模式立即返回 task_id。
+- 查询任务权限校验。
+- 状态机非法迁移被拒绝。
+- 幂等键重复请求返回同一任务。
+- 取消 pending / processing / completed 任务。
+
+SDK 测试:
+
+- `TaskSchema` protobuf 往返。
+- `TaskTypes` / `TaskDefinitions` 能被 gRPC runtime 正确暴露。
+- Host method capability 校验。
+
+插件测试:
+
+- 图片文生图请求能构造 `image.generate` input。
+- 图片编辑请求能构造 `image.edit` input。
+- 同步上游响应能完成任务。
+- 上游 `task_id` 响应能进入轮询并完成任务。
+- 上游错误能写入标准 error。
+- `ProcessTask` 遇到未知 task type 返回明确错误。
+- 旧 `/v1/images/tasks` 查询仍然兼容。
+
+## 开放问题
+
+1. 通用 `/v1/tasks` 是否放在所有平台根路径下,还是只在 Core 管理 API 下暴露?
+2. `task_id` 使用数字 ID 还是字符串 ID?建议对外使用字符串,例如 `ag_task_123`,内部可继续用 bigint。
+3. OpenAI SDK 兼容路径默认应该是 `wait` 还是 `auto`?建议默认 `wait`,显式请求才异步。
+4. task output 是否长期保存完整 base64?图片/视频结果可能很大,后续需要对象存储或结果过期策略。
+5. 插件是否需要实现协议响应转换接口,还是由插件在 output 中同时写入兼容响应?
+6. 取消任务是否必须进入 SDK 强类型接口,还是先通过 Host method 实现?
+
+## 结论
+
+异步任务不应该作为图片功能的附属能力存在。它应该是 AirGate Core 的通用执行模型:
+
+- Core 管任务生命周期。
+- SDK 管任务契约声明。
+- 插件管任务业务执行。
+- 客户端只看 AirGate Task,不看上游同步/异步差异。
+
+当前最稳妥的落地路径是先在 `airgate-openai` 内部抽出任务注册表和 runner,保持现有接口不变;然后补 SDK schema;最后让 Core 接管通用 `/v1/tasks` 对外协议。
From 3ffe502f4d61de95ec09def43c508145753e579d Mon Sep 17 00:00:00 2001
From: huangenjun <1021217094@qq.com>
Date: Wed, 13 May 2026 09:00:03 +0800
Subject: [PATCH 07/11] =?UTF-8?q?docs:=20=E6=98=8E=E7=A1=AE=E4=BB=BB?=
=?UTF-8?q?=E5=8A=A1=E5=AF=B9=E5=A4=96=E5=8D=8F=E8=AE=AE=E4=BF=9D=E6=8C=81?=
=?UTF-8?q?=E5=85=BC=E5=AE=B9?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/async-task-state-machine.md | 336 ++++++++++++-------------------
1 file changed, 134 insertions(+), 202 deletions(-)
diff --git a/docs/async-task-state-machine.md b/docs/async-task-state-machine.md
index dc5d6a0..23b9ef7 100644
--- a/docs/async-task-state-machine.md
+++ b/docs/async-task-state-machine.md
@@ -1,6 +1,6 @@
# 异步任务状态机设计
-本文定义 AirGate 在 Core、SDK、插件之间处理长耗时生成任务的统一设计。目标是让客户端无差别使用同步或异步调用,让 Core 无差别承接同步上游、异步上游和流式上游,让插件只负责任务类型的业务编解码。
+本文定义 AirGate 在 Core、SDK、插件之间处理长耗时生成任务的统一设计。目标是让对外业务 API 继续保持原有协议形态,只在响应中追加 AirGate `task_id` 作为追踪字段;Core 内部再把同步上游、异步上游和流式上游统一转换成任务状态机,插件只负责任务类型的业务编解码。
## 背景
@@ -16,12 +16,12 @@
## 设计目标
-1. Core 对客户端提供统一任务能力,不要求客户端知道上游是同步、异步还是流式。
+1. Core 对插件和协议适配层提供统一任务能力;对业务客户端保持原 API 标准,不要求客户端知道上游是同步、异步还是流式。
2. Core 内部维护唯一的任务状态机,负责创建、排队、执行、重试、取消、查询、列表和权限校验。
3. SDK 提供稳定任务契约,让插件声明任务类型、输入输出 schema、执行能力和查询展示语义。
4. 插件只实现任务业务逻辑:如何识别请求、如何构造任务 input、如何调用上游、如何标准化 output。
5. 图片、视频、音乐、语音、文件处理等长耗时任务都能复用同一套 Core 机制。
-6. 对外响应中的 `task_id` 必须是 AirGate 自己的任务 ID;上游自己的任务 ID 只能作为内部执行细节存储。
+6. 对外兼容响应中追加的 `task_id` 必须是 AirGate 自己的任务 ID;上游自己的任务 ID 只能作为内部执行细节存储。
7. 支持同步等待模式和后台任务模式,并允许 Core 在等待超时后自动转为后台任务。
8. 保持现有 OpenAI 兼容 API 可迁移,不要求一次性废弃 `/v1/images/generations` 等现有入口。
@@ -32,12 +32,13 @@
- 不把某个平台的定价、模型规则、轮询接口写进 Core。
- 不要求所有任务都必须异步执行。短任务仍然可以同步返回。
- 不要求所有插件都立即实现新任务协议。应支持分阶段迁移。
+- 不把通用 `/v1/tasks` 作为业务客户端的默认新标准。原来是 OpenAI Images、某个平台视频 API 或现有 AirGate 图片查询 API,对外就继续按原协议返回。
## 核心概念
### AirGate Task
-AirGate Task 是 Core 管理的统一任务实体。它是客户端和 Core 之间的稳定对象。
+AirGate Task 是 Core 管理的统一任务实体。它是 Core、SDK、插件、管理后台之间的内部稳定对象,不是所有业务客户端都必须直接消费的外部响应格式。
任务对象建议如下:
@@ -68,7 +69,7 @@ AirGate Task 是 Core 管理的统一任务实体。它是客户端和 Core 之
}
```
-`id` 是 AirGate 任务 ID,不等于上游任务 ID。
+`id` 是 AirGate 任务 ID,不等于上游任务 ID。协议适配层对外返回时通常命名为 `task_id`,并追加到原协议响应中。
Core 固定字段必须保持最小化,只包含任务生命周期、归属和安全边界。模型、平台、分辨率、时长、质量、音色等都不应成为 Core 任务表的硬编码列;它们属于插件声明的 `input`、`attributes`、`execution` 或最终 `Usage`。Core 可以保存、返回、按声明做弱索引,但不理解这些字段的业务含义。
@@ -171,13 +172,19 @@ Upstream Task 是某些上游平台返回的任务对象,例如:
- 上游短暂失败后重试时,需要判断是继续轮询已有上游任务,还是重新创建。
- 取消任务时,如果上游支持取消,需要拿上游任务 ID 调用取消接口。
-客户端永远查询 AirGate Task:
+客户端查询时也不应直接使用上游任务 ID。查询入口由协议适配层决定:
+
+```text
+GET /v1/images/tasks/ag_task_123
+```
+
+或在未来的 Core 管理 API 中查询:
```text
GET /v1/tasks/ag_task_123
```
-而不是直接查询上游:
+但无论入口路径如何,最终查询的都是 AirGate Task,而不是直接查询上游:
```text
GET /provider/tasks/img_abc
@@ -235,34 +242,34 @@ const (
Execution Mode 是插件内部执行策略,不直接决定客户端使用同步还是异步接口。
-### Client Wait Mode
+### Response Mode
-Client Wait Mode 表示客户端希望 Core 如何响应本次请求。
+Response Mode 表示协议适配层希望 Core 如何处理本次请求的等待策略。它可以来自网关配置、插件默认值、Header 或显式 AirGate 扩展参数,但不要求所有业务客户端都感知这个概念。
```go
-type TaskClientMode string
+type TaskResponseMode string
const (
- TaskClientModeWait TaskClientMode = "wait"
- TaskClientModeBackground TaskClientMode = "background"
- TaskClientModeAuto TaskClientMode = "auto"
+ TaskResponseModeWait TaskResponseMode = "wait"
+ TaskResponseModeBackground TaskResponseMode = "background"
+ TaskResponseModeAuto TaskResponseMode = "auto"
)
```
-| 模式 | 对客户端语义 |
+| 模式 | 协议适配层语义 |
| --- | --- |
-| `wait` | Core 尽量等待任务完成并返回最终结果 |
-| `background` | Core 创建任务后立即返回 AirGate `task_id` |
-| `auto` | Core 等待一小段时间;超时未完成则返回 `task_id` |
+| `wait` | Core 尽量等待任务完成,适配层返回原协议最终结果并追加 `task_id` |
+| `background` | Core 创建任务后立即返回,适配层返回该协议自己的异步响应并带上 `task_id` |
+| `auto` | Core 等待一小段时间;超时未完成则由适配层返回该协议自己的异步响应并带上 `task_id` |
-Client Wait Mode 与 Execution Mode 独立。
+Response Mode 与 Execution Mode 独立。
例如:
-- 客户端 `wait` + 上游 `sync_result`:通常直接返回最终结果。
-- 客户端 `wait` + 上游 `upstream_task`:Core 可以轮询一段时间,完成则返回结果,超时则返回 `202 + task_id`。
-- 客户端 `background` + 上游 `sync_result`:Core 仍然立即返回 `task_id`,后台 worker 同步调上游并完成任务。
-- 客户端 `auto` + 上游 `stream_result`:Core 聚合流;超过等待窗口就转后台。
+- `wait` + 上游 `sync_result`:通常直接返回原协议最终结果,并追加 `task_id`。
+- `wait` + 上游 `upstream_task`:Core 可以轮询一段时间,完成则返回原协议最终结果;超时则由适配层返回原协议的异步响应。
+- `background` + 上游 `sync_result`:Core 仍然立即创建后台任务,适配层返回异步响应,后台 worker 同步调上游并完成任务。
+- `auto` + 上游 `stream_result`:Core 聚合流;超过等待窗口就转后台,适配层返回异步响应。
## 状态机
@@ -279,7 +286,7 @@ pending
建议状态定义:
-| 状态 | 含义 | 可被客户端查询 |
+| 状态 | 含义 | 可被查询 |
| --- | --- | --- |
| `pending` | 已创建,等待调度 | 是 |
| `processing` | 插件正在执行 | 是 |
@@ -320,210 +327,127 @@ cancelled
Core 应校验状态迁移,插件不能随意把任意状态写入任务。
-## 对外 API 设计
+## 对外 API 兼容原则
-### 创建任务
+业务客户端看到的 API 应继续保持原平台或当前项目已经定义的标准。Core 内部可以统一成 AirGate Task,但协议适配层对外返回时必须投影回原协议响应,只额外追加 `task_id` 作为 AirGate 追踪 ID。
-建议新增通用任务 API:
+核心原则:
-```text
-POST /v1/tasks
-```
+1. 创建入口不改。原来调用 `POST /v1/images/generations`,仍然调用这个入口;未来视频、音乐也按各自平台或插件已经定义的入口暴露。
+2. 请求 body 不强制新增通用任务字段。模型、分辨率、时长、质量等仍按原协议传递。
+3. 响应 schema 不替换成通用 Task schema。完成、处理中、失败、查询、列表都由对应协议适配层决定响应结构。
+4. 响应中允许追加 `task_id`。这个字段始终是 AirGate Task ID,不是上游任务 ID。
+5. 上游同步、上游异步、上游流式只影响 Core 内部执行模式,不影响外部 API 标准。
+6. 通用 `/v1/tasks` 可以作为 Core 管理 API、调试 API 或后台管理 API,但不作为业务客户端迁移目标。
-请求示例:
+### 创建入口映射
-```json
-{
- "plugin_id": "gateway-openai",
- "type": "image.generate",
- "input": {
- "model": "gpt-image-2",
- "prompt": "一只柴犬坐在樱花树下",
- "size": "1024x1024"
- },
- "mode": "background"
-}
-```
+现有 OpenAI 风格入口继续保留:
-响应示例:
-
-```json
-{
- "id": "ag_task_123",
- "object": "task",
- "type": "image.generate",
- "status": "pending",
- "progress": 0
-}
+```text
+POST /v1/images/generations
+POST /v1/images/edits
```
-### 查询任务
+协议适配层在内部映射到任务类型:
```text
-GET /v1/tasks/{task_id}
+/v1/images/generations -> type=image.generate
+/v1/images/edits -> type=image.edit
```
-处理中:
+流程:
-```json
-{
- "id": "ag_task_123",
- "object": "task",
- "type": "image.generate",
- "status": "processing",
- "progress": 40,
- "output": null,
- "error": null
-}
+```text
+客户端调用原入口
+协议适配层解析原协议请求
+协议适配层构造 Task input
+Core 创建 AirGate Task
+Core 按状态机调度执行
+插件调用上游并写入 output / error / usage
+协议适配层把 Task 结果投影回原协议响应
+响应中追加 task_id
```
-完成:
+### 完成响应
+
+如果 OpenAI Images 兼容接口在等待窗口内完成,响应仍然是 Images API schema,只追加 `task_id`:
```json
{
- "id": "ag_task_123",
- "object": "task",
- "type": "image.generate",
- "status": "completed",
- "progress": 100,
- "output": {
- "kind": "image",
- "images": [
- {
- "b64_json": "...",
- "mime_type": "image/png"
- }
- ],
- "usage": {
- "input_tokens": 50,
- "output_tokens": 1056
+ "created": 1713833628,
+ "data": [
+ {
+ "b64_json": "..."
}
- },
- "error": null
+ ],
+ "usage": {},
+ "task_id": "ag_task_123"
}
```
-失败:
-
-```json
-{
- "id": "ag_task_123",
- "object": "task",
- "type": "image.generate",
- "status": "failed",
- "progress": 0,
- "output": null,
- "error": {
- "type": "upstream_error",
- "code": "provider_rejected",
- "message": "请求暂时无法完成,请稍后重试"
- }
-}
-```
+严格兼容模式下,如果某些官方 SDK 对未知字段非常敏感,可以通过网关配置决定是否追加 `task_id`;但 AirGate 自己的默认响应建议追加,便于记录、排查和后续查询。
-### 列表任务
+### 未完成响应
-```text
-GET /v1/tasks?type=image.generate&status=completed&limit=20&offset=0
-```
+如果调用选择后台执行,或 `wait` / `auto` 等待超时,响应也不应强制改成通用 Task schema,而应使用当前入口已有的异步响应形态。
-响应:
+例如当前项目若已有图片任务响应:
```json
{
- "object": "list",
- "data": [],
- "total": 0
+ "task_id": "ag_task_123",
+ "status": "processing"
}
```
-### 取消任务
+就继续保持这个响应。未来视频、音乐如果某个平台标准本身有 job / task / generation 对象,也映射成对应平台对象,只把其中的 ID 换成 AirGate `task_id` 或额外追加 AirGate `task_id`。
-```text
-POST /v1/tasks/{task_id}/cancel
-```
+如果某个同步协议本身没有异步响应标准,默认不应突然返回 AirGate Task 对象。只有在客户端显式选择异步模式,或该插件文档明确声明 AirGate 扩展响应时,才返回该协议适配层定义的异步响应。
-取消能力取决于插件声明:
+### 查询和列表
-- 如果上游支持取消,插件调用上游取消接口。
-- 如果上游不支持取消,Core 标记 `cancelling` 后尽力中止本地轮询或流消费。
-- 如果任务已完成,取消返回当前终态,不应破坏结果。
-
-## 兼容现有 OpenAI 风格入口
-
-现有资源入口不必立即删除:
+查询入口也保持原有协议或当前项目已有路径。例如当前图片任务可以继续:
```text
-POST /v1/images/generations
-POST /v1/images/edits
+GET /v1/images/tasks/{task_id}
+GET /v1/images/tasks/list
```
-这些入口可以映射到任务系统:
+这些入口内部查询 Core Task,但响应仍由图片协议适配层生成。后续视频、音乐如果需要查询,也按各自协议风格暴露,例如:
```text
-/v1/images/generations -> type=image.generate
-/v1/images/edits -> type=image.edit
-```
-
-客户端可以通过请求体或 Header 指定模式。
-
-请求体方式:
-
-```json
-{
- "model": "gpt-image-2",
- "prompt": "...",
- "task_mode": "background"
-}
+GET /v1/videos/tasks/{task_id}
+GET /v1/music/tasks/{task_id}
```
-Header 方式:
+是否真的使用这些路径由对应插件协议决定,Core 不要求所有媒体类型共享同一条外部查询路径。
-```text
-Prefer: respond-async
-```
+### 取消任务
-或:
+取消同样由协议适配层决定是否暴露以及如何命名。Core 只提供内部取消能力:
```text
-Prefer: wait=120
+tasks.cancel
```
-建议优先支持 Header,因为它不污染 OpenAI 兼容 body;但也可以保留 `task_mode` 作为 AirGate 扩展参数。
+取消能力取决于插件声明:
-响应策略:
+- 如果上游支持取消,插件调用上游取消接口。
+- 如果上游不支持取消,Core 标记 `cancelling` 后尽力中止本地轮询或流消费。
+- 如果任务已完成,取消返回当前终态,不应破坏结果。
-| 客户端模式 | 任务完成前响应 | 任务完成后响应 |
-| --- | --- | --- |
-| `background` | `202 Accepted + task_id` | 查询任务拿结果 |
-| `wait` | 等待完成;超过最大等待返回 `202 + task_id` | 直接返回原协议结果或任务结果 |
-| `auto` | 短时间内完成就返回结果,否则返回 `202 + task_id` | 查询任务拿结果 |
+### Core 管理 API
-对于 OpenAI Images 兼容接口,如果任务在等待窗口内完成,可以返回原 Images API schema:
+通用任务 API 可以存在,但它的定位是内部管理、后台页面、调试、测试或运维,不是业务客户端的标准协议:
-```json
-{
- "created": 1713833628,
- "data": [
- {
- "b64_json": "..."
- }
- ],
- "usage": {}
-}
-```
-
-如果未完成,返回 AirGate 任务 schema:
-
-```json
-{
- "id": "ag_task_123",
- "object": "task",
- "status": "processing"
-}
+```text
+GET /v1/tasks/{task_id}
+GET /v1/tasks?type=image.generate&status=completed&limit=20&offset=0
+POST /v1/tasks/{task_id}/cancel
```
-这会带来一个客户端解析问题:严格 OpenAI SDK 可能不认识 task schema。因此兼容 OpenAI SDK 的路径默认应保持 `wait` 或 `auto`;只有明确传 `Prefer: respond-async` 或 AirGate 扩展参数时才返回任务对象。
+这类 API 可以返回 AirGate Task schema,因为调用方明确知道自己在访问 AirGate 管理能力。普通业务 API 不应直接返回这个 schema。
## Core 职责
@@ -594,7 +518,7 @@ type TaskSchema struct {
| 字段 | 含义 |
| --- | --- |
-| `DefaultMode` | 默认客户端模式,通常是 `auto` 或 `wait` |
+| `DefaultMode` | 默认响应模式,通常是 `auto` 或 `wait` |
| `MaxAttempts` | 默认最大重试次数 |
| `Cancellable` | 是否支持取消 |
| `ProgressMode` | `none`、`percent`、`stage` |
@@ -1129,16 +1053,16 @@ background_poll_interval = 1s
立即调度
等待 completed / failed / cancelled
如果超时:
- 返回 202 + task_id
+ 协议适配层返回原协议的异步响应 + task_id
如果完成:
- 根据 result_protocol 返回兼容响应或 task 响应
+ 协议适配层返回原协议最终响应 + task_id
```
-这使同步上游和异步上游都可以对外表现为同步接口。
+这使同步上游和异步上游都可以对外表现为原同步接口;只有等待超时或显式后台模式时,才返回该协议自己的异步响应。
## 结果协议
-任务 output 应统一,但也要支持兼容原协议返回。
+任务 output 应在 Core 内部统一,但对外必须支持原协议返回。业务客户端不直接消费 `task_output`,而是消费协议适配层生成的响应。
统一任务 output:
@@ -1282,53 +1206,58 @@ Idempotency-Key: xxx
- `airgate-openai` 能声明 `image.generate` / `image.edit`。
- Core 管理后台能看到插件支持的任务类型。
-### 阶段三:Core 通用任务 API
+### 阶段三:Core 通用任务服务
-目标:Core 对外提供 `/v1/tasks`。
+目标:Core 提供内部统一任务服务,协议适配层通过它创建、查询、取消和更新任务;业务客户端仍走原 API。
改动:
-- Core 新增通用任务创建、查询、列表、取消 API。
+- Core 新增通用任务创建、查询、列表、取消 service。
- Core 实现 wait/background/auto。
- Core 实现幂等键。
- Core worker 支持按 task type 分发给插件。
- Core 对 task output 做权限过滤。
+- 可选提供受权限保护的 `/v1/tasks` 管理 API,用于后台、调试和运维。
验收:
-- 客户端可以不走 `/v1/images/tasks`,直接查 `/v1/tasks/{id}`。
-- 同步上游和异步上游在客户端表现一致。
+- 图片协议适配层可以通过 Core service 查询 `task_id` 对应的内部任务。
+- 同步上游和异步上游在原图片 API 中表现一致。
+- 普通业务客户端不需要迁移到 `/v1/tasks`。
-### 阶段四:插件迁移旧入口
+### 阶段四:协议入口接入 Core 任务服务
-目标:旧 OpenAI Images 入口复用 Core 任务 API。
+目标:OpenAI Images 等原有入口复用 Core 任务服务,但对外响应保持原协议。
改动:
-- `/v1/images/generations` 可根据 `Prefer` 或 `task_mode` 选择 wait/background/auto。
-- `/v1/images/tasks` 标记为兼容别名。
+- `/v1/images/generations` 内部创建 `image.generate` 任务,完成时返回 Images schema 并追加 `task_id`。
+- `/v1/images/edits` 内部创建 `image.edit` 任务,完成时返回 Images schema 并追加 `task_id`。
+- `/v1/images/tasks` 和 `/v1/images/tasks/list` 继续保持当前响应结构,内部改为查询 Core Task。
- 新增视频/音乐任务时只加 handler 和 schema。
验收:
- OpenAI SDK 默认路径仍可同步拿结果。
-- 显式异步模式返回 AirGate task。
+- 返回中能看到 AirGate `task_id`。
+- 显式异步模式返回当前协议定义的异步响应,而不是通用 AirGate Task schema。
- 视频/音乐不新增专属状态机。
-### 阶段五:删除旧任务专用路由
+### 阶段五:清理内部重复状态机
-目标:清理历史兼容代码。
+目标:清理插件内部按图片写死的状态机和轮询代码,保留对外兼容路由。
前提:
-- Core 通用任务 API 已稳定。
-- 前端和文档已切换。
-- 旧客户端有迁移窗口。
+- Core 通用任务服务已稳定。
+- 图片入口已通过协议适配层读写 Core Task。
+- 视频、音乐或其他任务类型能复用同一套 runner。
改动:
-- 删除 `/v1/images/tasks` 和 `/v1/images/tasks/list`。
-- 删除插件里的图片专用查询函数。
+- 删除插件里的图片专用状态迁移、轮询和重试分支。
+- 保留 `/v1/images/tasks` 和 `/v1/images/tasks/list` 的对外兼容壳。
+- 查询函数只负责协议响应投影,不再自己管理状态机。
## 当前 `airgate-openai` 应先怎么改
@@ -1349,7 +1278,7 @@ backend/internal/gateway/task_image.go
| --- | --- |
| `task_registry.go` | 注册和查找 task handler |
| `task_runner.go` | 通用状态迁移、调用 `forwardViaHost`、错误处理 |
-| `task_http.go` | 从 HTTP 请求解析 task_id、列表参数、写 task 响应 |
+| `task_http.go` | 从 HTTP 请求解析 task_id、列表参数、写当前协议的任务查询响应 |
| `task_image.go` | OpenAI 图片 input/output 编解码 |
第二步替换点:
@@ -1378,10 +1307,10 @@ registry.Register(OpenAIVideoGenerateTask{})
Core 测试:
-- 创建任务返回 task_id。
+- 创建内部任务后能返回 AirGate `task_id` 给协议适配层。
- wait 模式任务完成时返回最终结果。
-- wait 超时返回 `202 + task_id`。
-- background 模式立即返回 task_id。
+- wait 超时返回待处理状态和 `task_id`,由协议适配层投影成原协议异步响应。
+- background 模式立即返回 `task_id` 给协议适配层。
- 查询任务权限校验。
- 状态机非法迁移被拒绝。
- 幂等键重复请求返回同一任务。
@@ -1399,13 +1328,15 @@ SDK 测试:
- 图片编辑请求能构造 `image.edit` input。
- 同步上游响应能完成任务。
- 上游 `task_id` 响应能进入轮询并完成任务。
+- 完成响应保持 OpenAI Images schema,并额外包含 AirGate `task_id`。
+- 未完成响应保持当前图片任务响应结构,不返回通用 AirGate Task schema。
- 上游错误能写入标准 error。
- `ProcessTask` 遇到未知 task type 返回明确错误。
- 旧 `/v1/images/tasks` 查询仍然兼容。
## 开放问题
-1. 通用 `/v1/tasks` 是否放在所有平台根路径下,还是只在 Core 管理 API 下暴露?
+1. 通用 `/v1/tasks` 是否需要对外暴露为管理 API;如果暴露,权限边界和返回字段需要单独设计。
2. `task_id` 使用数字 ID 还是字符串 ID?建议对外使用字符串,例如 `ag_task_123`,内部可继续用 bigint。
3. OpenAI SDK 兼容路径默认应该是 `wait` 还是 `auto`?建议默认 `wait`,显式请求才异步。
4. task output 是否长期保存完整 base64?图片/视频结果可能很大,后续需要对象存储或结果过期策略。
@@ -1419,6 +1350,7 @@ SDK 测试:
- Core 管任务生命周期。
- SDK 管任务契约声明。
- 插件管任务业务执行。
-- 客户端只看 AirGate Task,不看上游同步/异步差异。
+- 协议适配层把原 API 请求转成内部 Task,再把 Task 结果投影回原 API 响应。
+- 业务客户端继续看原协议响应,只额外拿到 AirGate `task_id`,不看上游同步/异步差异。
-当前最稳妥的落地路径是先在 `airgate-openai` 内部抽出任务注册表和 runner,保持现有接口不变;然后补 SDK schema;最后让 Core 接管通用 `/v1/tasks` 对外协议。
+当前最稳妥的落地路径是先在 `airgate-openai` 内部抽出任务注册表和 runner,保持现有接口不变并追加 `task_id`;然后补 SDK schema;最后让 Core 接管统一任务服务。通用 `/v1/tasks` 只作为管理和调试能力考虑,不作为业务客户端的新标准。
From c0d8f5d9986b7164e93092558fb7324b9178c237 Mon Sep 17 00:00:00 2001
From: huangenjun <1021217094@qq.com>
Date: Wed, 13 May 2026 10:42:44 +0800
Subject: [PATCH 08/11] =?UTF-8?q?fix:=20=E9=81=BF=E5=85=8D=E4=BB=BB?=
=?UTF-8?q?=E5=8A=A1=E7=BD=91=E5=85=B3=E9=87=8D=E5=A4=8D=E6=B3=A8=E5=86=8C?=
=?UTF-8?q?=E6=8F=92=E4=BB=B6=E6=9C=8D=E5=8A=A1?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
runtimego/grpc/go_plugin.go | 8 ++-
runtimego/grpc/go_plugin_test.go | 97 ++++++++++++++++++++++++++++++++
2 files changed, 102 insertions(+), 3 deletions(-)
create mode 100644 runtimego/grpc/go_plugin_test.go
diff --git a/runtimego/grpc/go_plugin.go b/runtimego/grpc/go_plugin.go
index ee3da63..f386c60 100644
--- a/runtimego/grpc/go_plugin.go
+++ b/runtimego/grpc/go_plugin.go
@@ -37,6 +37,11 @@ func (p *GatewayGRPCPlugin) GRPCServer(broker *goplugin.GRPCBroker, s *grpc.Serv
pb.RegisterPluginServiceServer(s, &PluginGRPCServer{Impl: p.Impl, Broker: broker})
pb.RegisterGatewayServiceServer(s, &GatewayGRPCServer{Impl: p.Impl})
pb.RegisterEventServiceServer(s, &EventGRPCServer{Impl: p.Impl})
+ if tp, ok := p.Impl.(sdk.TaskProcessor); ok {
+ extServer := &ExtensionGRPCServer{Impl: &gatewayTaskAdapter{GatewayPlugin: p.Impl, tp: tp}}
+ extServer.initRouter()
+ pb.RegisterExtensionServiceServer(s, extServer)
+ }
return nil
}
@@ -143,9 +148,6 @@ func Serve(impl interface{}) {
switch p := impl.(type) {
case sdk.GatewayPlugin:
pluginMap[PluginKeyGateway] = &GatewayGRPCPlugin{Impl: p}
- if tp, ok := impl.(sdk.TaskProcessor); ok {
- pluginMap[PluginKeyExtension] = &ExtensionGRPCPlugin{Impl: &gatewayTaskAdapter{GatewayPlugin: p, tp: tp}}
- }
case sdk.ExtensionPlugin:
pluginMap[PluginKeyExtension] = &ExtensionGRPCPlugin{Impl: p}
case sdk.MiddlewarePlugin:
diff --git a/runtimego/grpc/go_plugin_test.go b/runtimego/grpc/go_plugin_test.go
new file mode 100644
index 0000000..f97b849
--- /dev/null
+++ b/runtimego/grpc/go_plugin_test.go
@@ -0,0 +1,97 @@
+package grpc
+
+import (
+ "context"
+ "testing"
+
+ gogrpc "google.golang.org/grpc"
+
+ pb "github.com/DouDOU-start/airgate-sdk/protocol/proto"
+ sdk "github.com/DouDOU-start/airgate-sdk/sdkgo"
+)
+
+type taskGatewayPlugin struct{}
+
+func (taskGatewayPlugin) Info() sdk.PluginInfo {
+ return sdk.PluginInfo{ID: "task-gateway", Type: sdk.PluginTypeGateway}
+}
+
+func (taskGatewayPlugin) Init(sdk.PluginContext) error { return nil }
+func (taskGatewayPlugin) Start(context.Context) error { return nil }
+func (taskGatewayPlugin) Stop(context.Context) error { return nil }
+
+func (taskGatewayPlugin) Platform() string { return "demo" }
+func (taskGatewayPlugin) Models() []sdk.ModelInfo { return nil }
+func (taskGatewayPlugin) Routes() []sdk.RouteDefinition { return nil }
+
+func (taskGatewayPlugin) Forward(context.Context, *sdk.ForwardRequest) (sdk.ForwardOutcome, error) {
+ return sdk.ForwardOutcome{}, nil
+}
+
+func (taskGatewayPlugin) ValidateAccount(context.Context, map[string]string) error { return nil }
+
+func (taskGatewayPlugin) HandleWebSocket(context.Context, sdk.WebSocketConn) (sdk.ForwardOutcome, error) {
+ return sdk.ForwardOutcome{}, sdk.ErrNotSupported
+}
+
+func (taskGatewayPlugin) ProcessTask(context.Context, sdk.HostTask) error { return nil }
+func (taskGatewayPlugin) TaskTypes() []string { return []string{"image_generation"} }
+
+func TestGatewayGRPCPlugin_RegistersTaskExtensionService(t *testing.T) {
+ server := gogrpc.NewServer()
+ t.Cleanup(server.Stop)
+
+ if err := (&GatewayGRPCPlugin{Impl: taskGatewayPlugin{}}).GRPCServer(nil, server); err != nil {
+ t.Fatalf("GRPCServer() error = %v", err)
+ }
+
+ services := server.GetServiceInfo()
+ for _, name := range []string{
+ pb.PluginService_ServiceDesc.ServiceName,
+ pb.GatewayService_ServiceDesc.ServiceName,
+ pb.EventService_ServiceDesc.ServiceName,
+ pb.ExtensionService_ServiceDesc.ServiceName,
+ } {
+ if _, ok := services[name]; !ok {
+ t.Fatalf("未注册服务 %s,已注册: %#v", name, services)
+ }
+ }
+}
+
+func TestGatewayGRPCPlugin_DoesNotRegisterTaskExtensionForPlainGateway(t *testing.T) {
+ server := gogrpc.NewServer()
+ t.Cleanup(server.Stop)
+
+ if err := (&GatewayGRPCPlugin{Impl: plainGatewayPlugin{}}).GRPCServer(nil, server); err != nil {
+ t.Fatalf("GRPCServer() error = %v", err)
+ }
+
+ services := server.GetServiceInfo()
+ if _, ok := services[pb.ExtensionService_ServiceDesc.ServiceName]; ok {
+ t.Fatalf("普通网关不应注册 ExtensionService")
+ }
+}
+
+type plainGatewayPlugin struct{}
+
+func (plainGatewayPlugin) Info() sdk.PluginInfo {
+ return sdk.PluginInfo{ID: "plain-gateway", Type: sdk.PluginTypeGateway}
+}
+
+func (plainGatewayPlugin) Init(sdk.PluginContext) error { return nil }
+func (plainGatewayPlugin) Start(context.Context) error { return nil }
+func (plainGatewayPlugin) Stop(context.Context) error { return nil }
+
+func (plainGatewayPlugin) Platform() string { return "demo" }
+func (plainGatewayPlugin) Models() []sdk.ModelInfo { return nil }
+func (plainGatewayPlugin) Routes() []sdk.RouteDefinition { return nil }
+
+func (plainGatewayPlugin) Forward(context.Context, *sdk.ForwardRequest) (sdk.ForwardOutcome, error) {
+ return sdk.ForwardOutcome{}, nil
+}
+
+func (plainGatewayPlugin) ValidateAccount(context.Context, map[string]string) error { return nil }
+
+func (plainGatewayPlugin) HandleWebSocket(context.Context, sdk.WebSocketConn) (sdk.ForwardOutcome, error) {
+ return sdk.ForwardOutcome{}, sdk.ErrNotSupported
+}
From 5a127a9cff0b11492c7c538b84c3b40c7b450361 Mon Sep 17 00:00:00 2001
From: huangenjun <1021217094@qq.com>
Date: Wed, 13 May 2026 17:48:01 +0800
Subject: [PATCH 09/11] =?UTF-8?q?feat:=20=E6=89=A9=E5=B1=95=E6=8F=92?=
=?UTF-8?q?=E4=BB=B6=E4=B8=BB=E9=A2=98=E7=94=A8=E9=87=8F=E5=AD=97=E6=AE=B5?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
docs/plugin-style-guide.md | 3 +++
theme/dist/plugin.d.ts | 7 +++++++
theme/src/plugin.tsx | 7 +++++++
3 files changed, 17 insertions(+)
diff --git a/docs/plugin-style-guide.md b/docs/plugin-style-guide.md
index bc6bfec..578a8d4 100644
--- a/docs/plugin-style-guide.md
+++ b/docs/plugin-style-guide.md
@@ -153,6 +153,9 @@ ensurePluginStyles();
export default {
accountCreate: YourComponent, // 或 accountEdit / accountIdentity / accountUsageWindow / usageMetricDetail / usageCostDetail / routes / menuItems
};
+
+// 使用记录“模型”列行级别扩展展示(可选)
+// usageModelMeta: (props: UsageRecordSurfaceProps) => ReactNode
```
## 4. 组件根节点 — 作用域绑定
diff --git a/theme/dist/plugin.d.ts b/theme/dist/plugin.d.ts
index abf202c..07eb669 100644
--- a/theme/dist/plugin.d.ts
+++ b/theme/dist/plugin.d.ts
@@ -98,6 +98,13 @@ export interface PluginFrontendModule {
accountCreate?: ComponentType;
accountEdit?: ComponentType;
accountUsageWindow?: ComponentType;
+ /**
+ * 使用记录列表中“模型”列的行级别扩展渲染器。
+ *
+ * 用途:为平台补充展示模型分档信息(例如图像分辨率、推理强度、服务层级等),
+ * Core 只提供表格骨架与回退渲染,不内置平台语义。
+ */
+ usageModelMeta?: ComponentType;
usageMetricDetail?: ComponentType;
usageCostDetail?: ComponentType;
platformIcon?: ComponentType;
diff --git a/theme/src/plugin.tsx b/theme/src/plugin.tsx
index 382dfe4..1e5e250 100644
--- a/theme/src/plugin.tsx
+++ b/theme/src/plugin.tsx
@@ -130,6 +130,13 @@ export interface PluginFrontendModule {
accountCreate?: ComponentType;
accountEdit?: ComponentType;
accountUsageWindow?: ComponentType;
+ /**
+ * 使用记录列表中“模型”列的行级别扩展渲染器。
+ *
+ * 用途:为平台补充展示模型分档信息(例如图像分辨率、推理强度、服务层级等),
+ * Core 只提供表格骨架与回退渲染,不内置平台语义。
+ */
+ usageModelMeta?: ComponentType;
usageMetricDetail?: ComponentType;
usageCostDetail?: ComponentType;
platformIcon?: ComponentType;
From 8aeafa2afbd9cfa9c398e31794294d0cd0de38bd Mon Sep 17 00:00:00 2001
From: huangenjun <1021217094@qq.com>
Date: Thu, 14 May 2026 15:13:57 +0800
Subject: [PATCH 10/11] =?UTF-8?q?refactor:=20=E5=BA=9F=E5=BC=83=20OutcomeA?=
=?UTF-8?q?ccountModelUnsupported=EF=BC=8C=E5=BD=92=E5=85=A5=20ClientError?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
模型不支持本质是客户端请求问题,不应触发 failover。
- OutcomeAccountModelUnsupported 常量保留(避免编译失败),行为等同 ClientError
- ShouldFailover() 不再包含 AccountModelUnsupported
- gRPC 映射层:proto 枚举值保留,运行时归并为 ClientError
---
protocol/proto/plugin.pb.go | 36 +++++++++++---------
protocol/proto/plugin.proto | 1 +
runtimego/grpc/convert_test.go | 9 +++++
runtimego/grpc/gateway_server.go | 35 +++++++++++++------
runtimego/grpc/gateway_stream_test.go | 49 +++++++++++++++++++++++++++
sdkgo/outcome.go | 12 +++++--
sdkgo/outcome_test.go | 12 ++++---
7 files changed, 120 insertions(+), 34 deletions(-)
diff --git a/protocol/proto/plugin.pb.go b/protocol/proto/plugin.pb.go
index a471acc..52c4782 100644
--- a/protocol/proto/plugin.pb.go
+++ b/protocol/proto/plugin.pb.go
@@ -26,13 +26,14 @@ const (
type OutcomeKind int32
const (
- OutcomeKind_OUTCOME_UNKNOWN OutcomeKind = 0
- OutcomeKind_OUTCOME_SUCCESS OutcomeKind = 1
- OutcomeKind_OUTCOME_CLIENT_ERROR OutcomeKind = 2
- OutcomeKind_OUTCOME_ACCOUNT_RATE_LIMITED OutcomeKind = 3
- OutcomeKind_OUTCOME_ACCOUNT_DEAD OutcomeKind = 4
- OutcomeKind_OUTCOME_UPSTREAM_TRANSIENT OutcomeKind = 5
- OutcomeKind_OUTCOME_STREAM_ABORTED OutcomeKind = 6
+ OutcomeKind_OUTCOME_UNKNOWN OutcomeKind = 0
+ OutcomeKind_OUTCOME_SUCCESS OutcomeKind = 1
+ OutcomeKind_OUTCOME_CLIENT_ERROR OutcomeKind = 2
+ OutcomeKind_OUTCOME_ACCOUNT_RATE_LIMITED OutcomeKind = 3
+ OutcomeKind_OUTCOME_ACCOUNT_DEAD OutcomeKind = 4
+ OutcomeKind_OUTCOME_UPSTREAM_TRANSIENT OutcomeKind = 5
+ OutcomeKind_OUTCOME_STREAM_ABORTED OutcomeKind = 6
+ OutcomeKind_OUTCOME_ACCOUNT_MODEL_UNSUPPORTED OutcomeKind = 7
)
// Enum value maps for OutcomeKind.
@@ -45,15 +46,17 @@ var (
4: "OUTCOME_ACCOUNT_DEAD",
5: "OUTCOME_UPSTREAM_TRANSIENT",
6: "OUTCOME_STREAM_ABORTED",
+ 7: "OUTCOME_ACCOUNT_MODEL_UNSUPPORTED",
}
OutcomeKind_value = map[string]int32{
- "OUTCOME_UNKNOWN": 0,
- "OUTCOME_SUCCESS": 1,
- "OUTCOME_CLIENT_ERROR": 2,
- "OUTCOME_ACCOUNT_RATE_LIMITED": 3,
- "OUTCOME_ACCOUNT_DEAD": 4,
- "OUTCOME_UPSTREAM_TRANSIENT": 5,
- "OUTCOME_STREAM_ABORTED": 6,
+ "OUTCOME_UNKNOWN": 0,
+ "OUTCOME_SUCCESS": 1,
+ "OUTCOME_CLIENT_ERROR": 2,
+ "OUTCOME_ACCOUNT_RATE_LIMITED": 3,
+ "OUTCOME_ACCOUNT_DEAD": 4,
+ "OUTCOME_UPSTREAM_TRANSIENT": 5,
+ "OUTCOME_STREAM_ABORTED": 6,
+ "OUTCOME_ACCOUNT_MODEL_UNSUPPORTED": 7,
}
)
@@ -4594,7 +4597,7 @@ const file_plugin_proto_rawDesc = "" +
"\asuccess\x18\x01 \x01(\bR\asuccess\x12#\n" +
"\rerror_message\x18\x02 \x01(\tR\ferrorMessage\")\n" +
"\x11TaskTypesResponse\x12\x14\n" +
- "\x05types\x18\x01 \x03(\tR\x05types*\xc9\x01\n" +
+ "\x05types\x18\x01 \x03(\tR\x05types*\xf0\x01\n" +
"\vOutcomeKind\x12\x13\n" +
"\x0fOUTCOME_UNKNOWN\x10\x00\x12\x13\n" +
"\x0fOUTCOME_SUCCESS\x10\x01\x12\x18\n" +
@@ -4602,7 +4605,8 @@ const file_plugin_proto_rawDesc = "" +
"\x1cOUTCOME_ACCOUNT_RATE_LIMITED\x10\x03\x12\x18\n" +
"\x14OUTCOME_ACCOUNT_DEAD\x10\x04\x12\x1e\n" +
"\x1aOUTCOME_UPSTREAM_TRANSIENT\x10\x05\x12\x1a\n" +
- "\x16OUTCOME_STREAM_ABORTED\x10\x062\xcb\x04\n" +
+ "\x16OUTCOME_STREAM_ABORTED\x10\x06\x12%\n" +
+ "!OUTCOME_ACCOUNT_MODEL_UNSUPPORTED\x10\a2\xcb\x04\n" +
"\rPluginService\x12J\n" +
"\aGetInfo\x12\x18.airgate.plugin.v1.Empty\x1a%.airgate.plugin.v1.PluginInfoResponse\x12@\n" +
"\x04Init\x12\x1e.airgate.plugin.v1.InitRequest\x1a\x18.airgate.plugin.v1.Empty\x12;\n" +
diff --git a/protocol/proto/plugin.proto b/protocol/proto/plugin.proto
index 8c7b80c..ce2dda3 100644
--- a/protocol/proto/plugin.proto
+++ b/protocol/proto/plugin.proto
@@ -233,6 +233,7 @@ enum OutcomeKind {
OUTCOME_ACCOUNT_DEAD = 4;
OUTCOME_UPSTREAM_TRANSIENT = 5;
OUTCOME_STREAM_ABORTED = 6;
+ OUTCOME_ACCOUNT_MODEL_UNSUPPORTED = 7;
}
// UpstreamResponse 上游返回的原始 HTTP 快照。
diff --git a/runtimego/grpc/convert_test.go b/runtimego/grpc/convert_test.go
index ad88b3b..65fdd04 100644
--- a/runtimego/grpc/convert_test.go
+++ b/runtimego/grpc/convert_test.go
@@ -192,6 +192,15 @@ func TestForwardOutcome_KindPreserved(t *testing.T) {
t.Errorf("Kind %q not preserved: got %q", k, restored.Kind)
}
}
+ // AccountModelUnsupported 已归入 ClientError,round-trip 后变为 ClientError
+ {
+ original := sdk.ForwardOutcome{Kind: sdk.OutcomeAccountModelUnsupported} //nolint:staticcheck // 测试兼容映射
+ proto := outcomeToProto(original)
+ restored := outcomeFromProto(proto)
+ if restored.Kind != sdk.OutcomeClientError {
+ t.Errorf("AccountModelUnsupported should map to ClientError, got %q", restored.Kind)
+ }
+ }
}
func TestForwardOutcome_ZeroValues(t *testing.T) {
diff --git a/runtimego/grpc/gateway_server.go b/runtimego/grpc/gateway_server.go
index 4caa22d..d702ca6 100644
--- a/runtimego/grpc/gateway_server.go
+++ b/runtimego/grpc/gateway_server.go
@@ -127,6 +127,8 @@ func outcomeKindToProto(k sdk.OutcomeKind) pb.OutcomeKind {
return pb.OutcomeKind_OUTCOME_UPSTREAM_TRANSIENT
case sdk.OutcomeStreamAborted:
return pb.OutcomeKind_OUTCOME_STREAM_ABORTED
+ case sdk.OutcomeAccountModelUnsupported: //nolint:staticcheck // 兼容旧插件
+ return pb.OutcomeKind_OUTCOME_CLIENT_ERROR
default:
return pb.OutcomeKind_OUTCOME_UNKNOWN
}
@@ -146,6 +148,8 @@ func outcomeKindFromProto(k pb.OutcomeKind) sdk.OutcomeKind {
return sdk.OutcomeUpstreamTransient
case pb.OutcomeKind_OUTCOME_STREAM_ABORTED:
return sdk.OutcomeStreamAborted
+ case pb.OutcomeKind_OUTCOME_ACCOUNT_MODEL_UNSUPPORTED:
+ return sdk.OutcomeClientError
default:
return sdk.OutcomeUnknown
}
@@ -441,12 +445,14 @@ func (s *GatewayGRPCServer) ForwardStream(req *pb.ForwardRequest, stream pb.Gate
outcome.Duration = time.Since(startTime)
}
- if err := sw.flushMeta(); err != nil {
- sdk.LoggerFromContext(ctx).Error("gateway_forward_stream_meta_flush_failed",
- sdk.LogFieldModel, req.Model,
- sdk.LogFieldError, err,
- )
- return err
+ if sw.sent || sw.wroteHeader {
+ if err := sw.flushMeta(); err != nil {
+ sdk.LoggerFromContext(ctx).Error("gateway_forward_stream_meta_flush_failed",
+ sdk.LogFieldModel, req.Model,
+ sdk.LogFieldError, err,
+ )
+ return err
+ }
}
if err := stream.Send(&pb.ForwardChunk{
Done: true,
@@ -470,10 +476,11 @@ func (s *GatewayGRPCServer) ValidateAccount(ctx context.Context, req *pb.Credent
// streamWriter 把 gRPC 流包装成 http.ResponseWriter。
type streamWriter struct {
- stream pb.GatewayService_ForwardStreamServer
- headers http.Header
- code int
- sent bool
+ stream pb.GatewayService_ForwardStreamServer
+ headers http.Header
+ code int
+ wroteHeader bool
+ sent bool
}
func (w *streamWriter) Header() http.Header {
@@ -510,7 +517,13 @@ func (w *streamWriter) Write(data []byte) (int, error) {
return total, nil
}
-func (w *streamWriter) WriteHeader(statusCode int) { w.code = statusCode }
+func (w *streamWriter) WriteHeader(statusCode int) {
+ if w.sent || w.wroteHeader {
+ return
+ }
+ w.code = statusCode
+ w.wroteHeader = true
+}
func (w *streamWriter) flushMeta() error {
if w.sent {
diff --git a/runtimego/grpc/gateway_stream_test.go b/runtimego/grpc/gateway_stream_test.go
index 16374d3..e3b3482 100644
--- a/runtimego/grpc/gateway_stream_test.go
+++ b/runtimego/grpc/gateway_stream_test.go
@@ -93,6 +93,25 @@ func (c *stubGatewayServiceClient) HandleWebSocket(context.Context, ...grpc.Call
return nil, nil
}
+type stubGatewayPlugin struct {
+ forward func(context.Context, *sdk.ForwardRequest) (sdk.ForwardOutcome, error)
+}
+
+func (p stubGatewayPlugin) Info() sdk.PluginInfo { return sdk.PluginInfo{} }
+func (p stubGatewayPlugin) Init(sdk.PluginContext) error { return nil }
+func (p stubGatewayPlugin) Start(context.Context) error { return nil }
+func (p stubGatewayPlugin) Stop(context.Context) error { return nil }
+func (p stubGatewayPlugin) Platform() string { return "test" }
+func (p stubGatewayPlugin) Models() []sdk.ModelInfo { return nil }
+func (p stubGatewayPlugin) Routes() []sdk.RouteDefinition { return nil }
+func (p stubGatewayPlugin) ValidateAccount(context.Context, map[string]string) error { return nil }
+func (p stubGatewayPlugin) HandleWebSocket(context.Context, sdk.WebSocketConn) (sdk.ForwardOutcome, error) {
+ return sdk.ForwardOutcome{}, sdk.ErrNotSupported
+}
+func (p stubGatewayPlugin) Forward(ctx context.Context, req *sdk.ForwardRequest) (sdk.ForwardOutcome, error) {
+ return p.forward(ctx, req)
+}
+
type captureWriter struct {
header http.Header
status int
@@ -148,6 +167,36 @@ func TestStreamWriterFlushMetaBeforeBody(t *testing.T) {
}
}
+func TestGatewayGRPCServerForwardStreamDoesNotFlushMetaWithoutCommittedResponse(t *testing.T) {
+ server := &GatewayGRPCServer{
+ Impl: stubGatewayPlugin{
+ forward: func(_ context.Context, req *sdk.ForwardRequest) (sdk.ForwardOutcome, error) {
+ req.Writer.Header().Set("Content-Type", "text/event-stream")
+ return sdk.ForwardOutcome{
+ Kind: sdk.OutcomeUpstreamTransient,
+ Upstream: sdk.UpstreamResponse{StatusCode: http.StatusBadGateway},
+ Reason: "空流",
+ }, nil
+ },
+ },
+ }
+ stream := &stubForwardStreamServer{}
+
+ if err := server.ForwardStream(&pb.ForwardRequest{}, stream); err != nil {
+ t.Fatalf("ForwardStream() error = %v", err)
+ }
+ if len(stream.chunks) != 1 {
+ t.Fatalf("expected only final outcome chunk, got %d chunks: %+v", len(stream.chunks), stream.chunks)
+ }
+ final := stream.chunks[0]
+ if !final.Done || final.FinalOutcome == nil {
+ t.Fatalf("expected final outcome chunk, got %+v", final)
+ }
+ if final.StatusCode != 0 || len(final.Headers) != 0 || len(final.Data) != 0 {
+ t.Fatalf("final chunk should not commit HTTP response, got %+v", final)
+ }
+}
+
func TestGatewayGRPCClientForwardStreamAppliesStatusAndHeaders(t *testing.T) {
client := &GatewayGRPCClient{
gateway: &stubGatewayServiceClient{
diff --git a/sdkgo/outcome.go b/sdkgo/outcome.go
index 812b874..f86e619 100644
--- a/sdkgo/outcome.go
+++ b/sdkgo/outcome.go
@@ -19,7 +19,7 @@ const (
// OutcomeSuccess 上游返回 2xx,Usage 必填。
OutcomeSuccess
- // OutcomeClientError 4xx,错在客户端请求本身(model 不存在、context 过长、参数非法)。
+ // OutcomeClientError 4xx,错在客户端请求本身(context 过长、参数非法等)。
// 换账号救不回来,Core 会把 Upstream 原样透传给客户端,不罚账号。
OutcomeClientError
@@ -38,6 +38,10 @@ const (
// OutcomeStreamAborted 流式响应已经开始写入客户端,中途断开。
// 不能 failover(字节已经发出去了),也不能把账号直接标死。
OutcomeStreamAborted
+
+ // Deprecated: OutcomeAccountModelUnsupported 已归入 OutcomeClientError。
+ // 保留常量避免编译失败,运行时等同于 ClientError。
+ OutcomeAccountModelUnsupported
)
// String 返回人类可读名称,用于日志。
@@ -55,6 +59,8 @@ func (k OutcomeKind) String() string {
return "upstream_transient"
case OutcomeStreamAborted:
return "stream_aborted"
+ case OutcomeAccountModelUnsupported:
+ return "client_error"
default:
return "unknown"
}
@@ -82,8 +88,8 @@ func (k OutcomeKind) ShouldFailover() bool {
// UpstreamResponse 上游返回的原始 HTTP 快照。
//
-// 语义:Success / ClientError 时 Core 会把 Body + Headers 原样透传给客户端。
-// 其他 Kind 下 Upstream 仅作为诊断信息保留,不透传。
+// 语义:插件应尽量保存上游实际响应。Core 会先基于 Kind 组织调度 / failover;
+// 最终不再重试时,若 Upstream 有可返回响应,则优先原样返回给客户端。
// StreamAborted 场景 Body 通常为空(字节已经流给客户端)。
type UpstreamResponse struct {
StatusCode int
diff --git a/sdkgo/outcome_test.go b/sdkgo/outcome_test.go
index 3ada0ed..e0317a2 100644
--- a/sdkgo/outcome_test.go
+++ b/sdkgo/outcome_test.go
@@ -18,6 +18,7 @@ func TestOutcomeKind_String(t *testing.T) {
{sdk.OutcomeAccountDead, "account_dead"},
{sdk.OutcomeUpstreamTransient, "upstream_transient"},
{sdk.OutcomeStreamAborted, "stream_aborted"},
+ {sdk.OutcomeAccountModelUnsupported, "client_error"},
{sdk.OutcomeKind(99), "unknown"}, // 非枚举值回退到 unknown
}
for _, tc := range cases {
@@ -38,6 +39,7 @@ func TestOutcomeKind_IsSuccess(t *testing.T) {
sdk.OutcomeAccountDead,
sdk.OutcomeUpstreamTransient,
sdk.OutcomeStreamAborted,
+ sdk.OutcomeAccountModelUnsupported,
}
for _, k := range nonSuccess {
if k.IsSuccess() {
@@ -62,6 +64,7 @@ func TestOutcomeKind_IsAccountFault(t *testing.T) {
sdk.OutcomeClientError,
sdk.OutcomeUpstreamTransient,
sdk.OutcomeStreamAborted,
+ sdk.OutcomeAccountModelUnsupported,
}
for _, k := range notAccountFaults {
if k.IsAccountFault() {
@@ -82,10 +85,11 @@ func TestOutcomeKind_ShouldFailover(t *testing.T) {
}
}
noFailover := []sdk.OutcomeKind{
- sdk.OutcomeUnknown, // 插件没说的情况下不盲目重试
- sdk.OutcomeSuccess, // 成功无需 failover
- sdk.OutcomeClientError, // 换号也救不回来
- sdk.OutcomeStreamAborted, // 字节已发给客户端
+ sdk.OutcomeUnknown, // 插件没说的情况下不盲目重试
+ sdk.OutcomeSuccess, // 成功无需 failover
+ sdk.OutcomeClientError, // 换号也救不回来
+ sdk.OutcomeStreamAborted, // 字节已发给客户端
+ sdk.OutcomeAccountModelUnsupported, // 已归入 ClientError
}
for _, k := range noFailover {
if k.ShouldFailover() {
From 7ed6e63284064addb7615d91817efe825f9d82f4 Mon Sep 17 00:00:00 2001
From: huangenjun <1021217094@qq.com>
Date: Thu, 14 May 2026 16:25:26 +0800
Subject: [PATCH 11/11] =?UTF-8?q?docs:=20=E8=A1=A5=E5=85=85=E8=B5=84?=
=?UTF-8?q?=E4=BA=A7=E5=AD=98=E5=82=A8=E8=81=8C=E8=B4=A3=E8=BE=B9=E7=95=8C?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
在 async-task-state-machine.md 中新增资产存储章节,明确:
- Core 提供 assets.store / assets.store_url / assets.get_url / assets.get_bytes
- 插件负责识别哪些字段需要存储、调用对应方法
- Core 不解析 output 结构
---
docs/async-task-state-machine.md | 28 ++++++++++++++++++++++++++++
1 file changed, 28 insertions(+)
diff --git a/docs/async-task-state-machine.md b/docs/async-task-state-machine.md
index 23b9ef7..f84c934 100644
--- a/docs/async-task-state-machine.md
+++ b/docs/async-task-state-machine.md
@@ -667,6 +667,34 @@ tasks.get_cancellation
如果暂时不做 event 表,也可以把 execution 信息存进任务 output 的内部字段,但不建议长期这么做。
+### 资产存储
+
+Core 提供统一的资产存储能力,插件不应自行实现文件下载和持久化。
+
+已有 Host method:
+
+```text
+assets.store 存储原始字节(插件已有数据在内存中)
+assets.store_url 从外部 URL 下载并存储(Core 负责 HTTP 下载、大小限制、Content-Type 检测)
+assets.get_url 获取已存储资产的可访问 URL
+assets.get_bytes 获取已存储资产的原始字节
+```
+
+职责边界:
+
+| 层级 | 职责 | 示例 |
+| --- | --- | --- |
+| Core 资产存储 | HTTP 下载、大小限制、Content-Type 检测、持久化(本地 / S3)、URL 签发 | `assets.store_url` 下载 50MB 以内的外部图片 |
+| 插件 | 识别 output 中哪些字段含可下载媒体、调用对应 Host method、注册到自己的资产跟踪表 | 遍历 Images API 响应的 `data[]`,对 `b64_json` 调 `assets.store`,对外部 `url` 调 `assets.store_url` |
+| Core 任务系统 | 持久化 output JSON、状态机、权限 | 不解析 output 中的 URL 或 base64 字段 |
+
+设计原则:
+
+- Core 不理解任务 output 的结构,不自动扫描或处理 output 中的媒体字段。
+- 插件在执行任务时主动调用 `assets.store` 或 `assets.store_url`,把外部 URL 或 base64 转为 Core 管理的本地 URL,再写入 output。
+- 任务完成后 output 中应只包含稳定的本地 URL,不应包含可能过期的外部签名 URL。
+- 后续新增视频、音乐等媒体类型时,插件只需调用同一组 Host method,不需要各自实现下载逻辑。
+
## 插件职责
插件实现任务定义,不实现通用状态机。