diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5c8e5997..1deffa90 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,7 +7,7 @@ on: branches: [master] env: - GOPRIVATE: github.com/DouDOU-start/airgate-sdk + GOPRIVATE: github.com/DevilGenius/airgate-sdk jobs: ci: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 40f00066..2428dfa7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,9 +15,9 @@ permissions: packages: write env: - GOPRIVATE: github.com/DouDOU-start/airgate-sdk + GOPRIVATE: github.com/DevilGenius/airgate-sdk REGISTRY: ghcr.io - IMAGE_NAME: doudou-start/airgate-core + IMAGE_NAME: devilgenius/airgate-core jobs: # ------------------------------------------------------------ @@ -222,7 +222,7 @@ jobs: echo "Injecting Version=${VERSION}" mkdir -p bin cd backend && go build -buildvcs=false -trimpath \ - -ldflags "-X 'github.com/DouDOU-start/airgate-core/internal/version.Version=${VERSION}' -s -w" \ + -ldflags "-X 'github.com/DevilGenius/airgate-core/internal/version.Version=${VERSION}' -s -w" \ -o ../bin/airgate-core-${{ matrix.goos }}-${{ matrix.goarch }} ./cmd/server - name: Generate SHA256 diff --git a/LICENSE b/LICENSE index 30c35bff..18f224f1 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2026 DouDOU-start +Copyright (c) 2026 DevilGenius Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Makefile b/Makefile index d4121aef..9ab8531c 100644 --- a/Makefile +++ b/Makefile @@ -29,7 +29,7 @@ GO := GOTOOLCHAIN=local go # 版本号:默认从 git 派生(dirty 检测),release workflow 通过 -ldflags 注入。 VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo dev) -LDFLAGS := -X github.com/DouDOU-start/airgate-core/internal/version.Version=$(VERSION) +LDFLAGS := -X github.com/DevilGenius/airgate-core/internal/version.Version=$(VERSION) .PHONY: help dev dev-backend dev-frontend dev-plugins dev-plugin-openai dev-plugin-claude dev-plugin-playground dev-plugin-epay dev-plugin-health dev-plugin-kiro dev-plugin-studio \ build build-backend build-frontend \ @@ -266,7 +266,7 @@ lint: ## 代码检查(需要安装 golangci-lint) fmt: ## 格式化代码 @cd $(BACKEND_DIR) && \ if command -v goimports > /dev/null 2>&1; then \ - goimports -w -local github.com/DouDOU-start .; \ + goimports -w -local github.com/DevilGenius .; \ else \ $(GO) fmt ./...; \ fi diff --git a/README.md b/README.md index 59a297b2..c4841fbf 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,9 @@

可插件化的统一 AI 网关运行时

- release - ghcr.io - license + release + ghcr.io + license go react

@@ -42,13 +42,13 @@ AirGate 不是又一个"集成了 N 个 AI 平台"的网关,而是一套**把 | 插件 | 类型 | 能力 | 仓库 | |---|---|---|---| -| **gateway-openai** | gateway | OpenAI Responses / Chat Completions / ChatGPT OAuth / Anthropic 协议翻译 / WebSocket | [DouDOU-start/airgate-openai](https://github.com/DouDOU-start/airgate-openai) | -| **gateway-claude** | gateway | Claude Messages API 网关:OAuth 授权、TLS 指纹、用量监控 | [DouDOU-start/airgate-claude](https://github.com/DouDOU-start/airgate-claude) | -| **gateway-kiro** | gateway | Kiro (AWS CodeWhisperer) 反代网关,兼容 Anthropic Messages API | [DouDOU-start/airgate-kiro](https://github.com/DouDOU-start/airgate-kiro) | -| **airgate-playground** | extension | AI 对话插件:网页聊天、多模型切换、会话管理 | [DouDOU-start/airgate-playground](https://github.com/DouDOU-start/airgate-playground) | -| **airgate-studio** | extension | 面向图片、视频、音频等多模态内容生成的统一创作中心 | [DouDOU-start/airgate-studio](https://github.com/DouDOU-start/airgate-studio) | -| **payment-epay** | extension | 多渠道支付:易支付(虎皮椒/彩虹)/ 支付宝官方 / 微信支付官方,含充值页、订单管理、服务商配置 | [DouDOU-start/airgate-epay](https://github.com/DouDOU-start/airgate-epay) | -| **airgate-health** | extension | AI 提供商健康监控:主动探测、可用率/延迟聚合、对外公开状态页 | [DouDOU-start/airgate-health](https://github.com/DouDOU-start/airgate-health) | +| **gateway-openai** | gateway | OpenAI Responses / Chat Completions / ChatGPT OAuth / Anthropic 协议翻译 / WebSocket | [DevilGenius/airgate-openai](https://github.com/DevilGenius/airgate-openai) | +| **gateway-claude** | gateway | Claude Messages API 网关:OAuth 授权、TLS 指纹、用量监控 | [DevilGenius/airgate-claude](https://github.com/DevilGenius/airgate-claude) | +| **gateway-kiro** | gateway | Kiro (AWS CodeWhisperer) 反代网关,兼容 Anthropic Messages API | [DevilGenius/airgate-kiro](https://github.com/DevilGenius/airgate-kiro) | +| **airgate-playground** | extension | AI 对话插件:网页聊天、多模型切换、会话管理 | [DevilGenius/airgate-playground](https://github.com/DevilGenius/airgate-playground) | +| **airgate-studio** | extension | 面向图片、视频、音频等多模态内容生成的统一创作中心 | [DevilGenius/airgate-studio](https://github.com/DevilGenius/airgate-studio) | +| **payment-epay** | extension | 多渠道支付:易支付(虎皮椒/彩虹)/ 支付宝官方 / 微信支付官方,含充值页、订单管理、服务商配置 | [DevilGenius/airgate-epay](https://github.com/DevilGenius/airgate-epay) | +| **airgate-health** | extension | AI 提供商健康监控:主动探测、可用率/延迟聚合、对外公开状态页 | [DevilGenius/airgate-health](https://github.com/DevilGenius/airgate-health) | ### 安装插件 @@ -64,7 +64,7 @@ AirGate 不是又一个"集成了 N 个 AI 平台"的网关,而是一套**把 ### 写一个自己的插件 -只需依赖 [airgate-sdk](https://github.com/DouDOU-start/airgate-sdk),实现 `GatewayPlugin` 接口的几个方法即可: +只需依赖 [airgate-sdk](https://github.com/DevilGenius/airgate-sdk),实现 `GatewayPlugin` 接口的几个方法即可: ```go type GatewayPlugin interface { @@ -76,7 +76,7 @@ type GatewayPlugin interface { } ``` -参考 [airgate-openai](https://github.com/DouDOU-start/airgate-openai) 完整范例,含 Makefile、release workflow、前端嵌入。 +参考 [airgate-openai](https://github.com/DevilGenius/airgate-openai) 完整范例,含 Makefile、release workflow、前端嵌入。 ## 🛠 技术栈 @@ -102,7 +102,7 @@ type GatewayPlugin interface { ### 方式 1A:裸金属安装(systemd,自备 PostgreSQL + Redis) ```bash -curl -sSL https://raw.githubusercontent.com/DouDOU-start/airgate-core/master/deploy/install.sh | sudo bash +curl -sSL https://raw.githubusercontent.com/DevilGenius/airgate-core/master/deploy/install.sh | sudo bash ``` [install.sh](deploy/install.sh) 会: @@ -132,13 +132,13 @@ sudo systemctl enable airgate-core ```bash # 升级到最新版本(保留配置和数据) -curl -sSL https://raw.githubusercontent.com/DouDOU-start/airgate-core/master/deploy/install.sh | sudo bash -s -- upgrade +curl -sSL https://raw.githubusercontent.com/DevilGenius/airgate-core/master/deploy/install.sh | sudo bash -s -- upgrade # 安装指定版本 -curl -sSL https://raw.githubusercontent.com/DouDOU-start/airgate-core/master/deploy/install.sh | sudo bash -s -- -v v0.1.0 +curl -sSL https://raw.githubusercontent.com/DevilGenius/airgate-core/master/deploy/install.sh | sudo bash -s -- -v v0.1.0 # 卸载(默认保留 /etc/airgate-core 与 /var/lib/airgate-core) -curl -sSL https://raw.githubusercontent.com/DouDOU-start/airgate-core/master/deploy/install.sh | sudo bash -s -- uninstall -y +curl -sSL https://raw.githubusercontent.com/DevilGenius/airgate-core/master/deploy/install.sh | sudo bash -s -- uninstall -y ``` **常用命令**: @@ -153,7 +153,7 @@ sudo systemctl restart airgate-core # 重启 ```bash mkdir airgate && cd airgate -curl -sSL https://raw.githubusercontent.com/DouDOU-start/airgate-core/master/deploy/docker-deploy.sh | bash +curl -sSL https://raw.githubusercontent.com/DevilGenius/airgate-core/master/deploy/docker-deploy.sh | bash # 检查生成的文件后启动 docker compose up -d @@ -289,12 +289,12 @@ sudo journalctl -u caddy -f # 看证书签发日志 **A. 全容器(推荐,宿主机零依赖)** -宿主机只需要 Docker。父目录同时克隆 [`airgate-sdk`](https://github.com/DouDOU-start/airgate-sdk) 与 [`airgate-core`](https://github.com/DouDOU-start/airgate-core): +宿主机只需要 Docker。父目录同时克隆 [`airgate-sdk`](https://github.com/DevilGenius/airgate-sdk) 与 [`airgate-core`](https://github.com/DevilGenius/airgate-core): ```bash mkdir airgate && cd airgate -git clone https://github.com/DouDOU-start/airgate-sdk.git -git clone https://github.com/DouDOU-start/airgate-core.git +git clone https://github.com/DevilGenius/airgate-sdk.git +git clone https://github.com/DevilGenius/airgate-core.git cd airgate-core docker compose -f deploy/docker-compose.dev.yml up @@ -304,11 +304,11 @@ docker compose -f deploy/docker-compose.dev.yml up **B. 宿主机直跑** -需要 Go 1.25+、Node 22+、本地 Postgres + Redis,以及兄弟目录 [`airgate-sdk`](https://github.com/DouDOU-start/airgate-sdk): +需要 Go 1.25+、Node 22+、本地 Postgres + Redis,以及兄弟目录 [`airgate-sdk`](https://github.com/DevilGenius/airgate-sdk): ```bash -git clone https://github.com/DouDOU-start/airgate-sdk.git -git clone https://github.com/DouDOU-start/airgate-core.git +git clone https://github.com/DevilGenius/airgate-sdk.git +git clone https://github.com/DevilGenius/airgate-core.git cd airgate-core make install # 安装前后端依赖 @@ -411,7 +411,7 @@ airgate-core/ > docker run --rm -v _redis_data:/from -v $(pwd)/data/redis:/to alpine cp -a /from/. /to/ > docker run --rm -v _airgate_plugins:/from -v $(pwd)/data/plugins:/to alpine cp -a /from/. /to/ > docker run --rm -v _airgate_uploads:/from -v $(pwd)/data/uploads:/to alpine cp -a /from/. /to/ -> curl -O https://raw.githubusercontent.com/DouDOU-start/airgate-core/master/deploy/docker-compose.yml +> curl -O https://raw.githubusercontent.com/DevilGenius/airgate-core/master/deploy/docker-compose.yml > docker compose up -d > # 验证一切正常后再删除旧的命名 volume > docker volume rm _postgres_data _redis_data _airgate_plugins _airgate_uploads @@ -420,9 +420,9 @@ airgate-core/ ## 🤝 贡献 / 反馈 -- Bug / Feature: [Issues](https://github.com/DouDOU-start/airgate-core/issues) -- 插件开发文档: [airgate-sdk](https://github.com/DouDOU-start/airgate-sdk) -- 参考插件实现: [airgate-openai](https://github.com/DouDOU-start/airgate-openai) +- Bug / Feature: [Issues](https://github.com/DevilGenius/airgate-core/issues) +- 插件开发文档: [airgate-sdk](https://github.com/DevilGenius/airgate-sdk) +- 参考插件实现: [airgate-openai](https://github.com/DevilGenius/airgate-openai) ## 📜 License diff --git a/README_EN.md b/README_EN.md index bffb00a3..04ffeda2 100644 --- a/README_EN.md +++ b/README_EN.md @@ -6,9 +6,9 @@

A pluggable runtime for unified AI gateways

- release - ghcr.io - license + release + ghcr.io + license go react

@@ -42,13 +42,13 @@ Plugins can be **released, installed, uninstalled, and hot-reloaded independentl | Plugin | Type | Capabilities | Repository | |---|---|---|---| -| **gateway-openai** | gateway | OpenAI Responses / Chat Completions / ChatGPT OAuth / Anthropic protocol translation / WebSocket | [DouDOU-start/airgate-openai](https://github.com/DouDOU-start/airgate-openai) | -| **gateway-claude** | gateway | Claude Messages API gateway: OAuth authorization, TLS fingerprinting, usage monitoring | [DouDOU-start/airgate-claude](https://github.com/DouDOU-start/airgate-claude) | -| **gateway-kiro** | gateway | Kiro (AWS CodeWhisperer) reverse proxy gateway compatible with Anthropic Messages API | [DouDOU-start/airgate-kiro](https://github.com/DouDOU-start/airgate-kiro) | -| **airgate-playground** | extension | AI chat plugin: web chat, multi-model switching, conversation management | [DouDOU-start/airgate-playground](https://github.com/DouDOU-start/airgate-playground) | -| **airgate-studio** | extension | Unified creation center for multimodal image, video, and audio generation | [DouDOU-start/airgate-studio](https://github.com/DouDOU-start/airgate-studio) | -| **payment-epay** | extension | Multi-channel payment: EPay (Xunhu/Rainbow) / Alipay Official / WeChat Pay Official, with recharge page, order management, provider configuration | [DouDOU-start/airgate-epay](https://github.com/DouDOU-start/airgate-epay) | -| **airgate-health** | extension | AI provider health monitoring: active probing, availability/latency aggregation, public status page | [DouDOU-start/airgate-health](https://github.com/DouDOU-start/airgate-health) | +| **gateway-openai** | gateway | OpenAI Responses / Chat Completions / ChatGPT OAuth / Anthropic protocol translation / WebSocket | [DevilGenius/airgate-openai](https://github.com/DevilGenius/airgate-openai) | +| **gateway-claude** | gateway | Claude Messages API gateway: OAuth authorization, TLS fingerprinting, usage monitoring | [DevilGenius/airgate-claude](https://github.com/DevilGenius/airgate-claude) | +| **gateway-kiro** | gateway | Kiro (AWS CodeWhisperer) reverse proxy gateway compatible with Anthropic Messages API | [DevilGenius/airgate-kiro](https://github.com/DevilGenius/airgate-kiro) | +| **airgate-playground** | extension | AI chat plugin: web chat, multi-model switching, conversation management | [DevilGenius/airgate-playground](https://github.com/DevilGenius/airgate-playground) | +| **airgate-studio** | extension | Unified creation center for multimodal image, video, and audio generation | [DevilGenius/airgate-studio](https://github.com/DevilGenius/airgate-studio) | +| **payment-epay** | extension | Multi-channel payment: EPay (Xunhu/Rainbow) / Alipay Official / WeChat Pay Official, with recharge page, order management, provider configuration | [DevilGenius/airgate-epay](https://github.com/DevilGenius/airgate-epay) | +| **airgate-health** | extension | AI provider health monitoring: active probing, availability/latency aggregation, public status page | [DevilGenius/airgate-health](https://github.com/DevilGenius/airgate-health) | ### Installing a plugin @@ -64,7 +64,7 @@ The marketplace **periodically syncs** the latest release of each plugin via the ### Building your own plugin -Pull in [airgate-sdk](https://github.com/DouDOU-start/airgate-sdk) and implement the `GatewayPlugin` interface: +Pull in [airgate-sdk](https://github.com/DevilGenius/airgate-sdk) and implement the `GatewayPlugin` interface: ```go type GatewayPlugin interface { @@ -76,7 +76,7 @@ type GatewayPlugin interface { } ``` -See [airgate-openai](https://github.com/DouDOU-start/airgate-openai) for a complete reference, including Makefile, release workflow, and embedded frontend. +See [airgate-openai](https://github.com/DevilGenius/airgate-openai) for a complete reference, including Makefile, release workflow, and embedded frontend. ## 🛠 Tech Stack @@ -102,7 +102,7 @@ Pick one. Both are production-ready. ### Method 1A: Bare-metal install (systemd; bring your own PostgreSQL + Redis) ```bash -curl -sSL https://raw.githubusercontent.com/DouDOU-start/airgate-core/master/deploy/install.sh | sudo bash +curl -sSL https://raw.githubusercontent.com/DevilGenius/airgate-core/master/deploy/install.sh | sudo bash ``` [install.sh](deploy/install.sh) will: @@ -132,13 +132,13 @@ After the admin UI is up, go to **Plugin Management → Marketplace** to install ```bash # Upgrade to latest (config and data preserved) -curl -sSL https://raw.githubusercontent.com/DouDOU-start/airgate-core/master/deploy/install.sh | sudo bash -s -- upgrade +curl -sSL https://raw.githubusercontent.com/DevilGenius/airgate-core/master/deploy/install.sh | sudo bash -s -- upgrade # Pin a specific version -curl -sSL https://raw.githubusercontent.com/DouDOU-start/airgate-core/master/deploy/install.sh | sudo bash -s -- -v v0.1.0 +curl -sSL https://raw.githubusercontent.com/DevilGenius/airgate-core/master/deploy/install.sh | sudo bash -s -- -v v0.1.0 # Uninstall (keeps /etc/airgate-core and /var/lib/airgate-core by default) -curl -sSL https://raw.githubusercontent.com/DouDOU-start/airgate-core/master/deploy/install.sh | sudo bash -s -- uninstall -y +curl -sSL https://raw.githubusercontent.com/DevilGenius/airgate-core/master/deploy/install.sh | sudo bash -s -- uninstall -y ``` **Common commands**: @@ -153,7 +153,7 @@ sudo systemctl restart airgate-core # restart ```bash mkdir airgate && cd airgate -curl -sSL https://raw.githubusercontent.com/DouDOU-start/airgate-core/master/deploy/docker-deploy.sh | bash +curl -sSL https://raw.githubusercontent.com/DevilGenius/airgate-core/master/deploy/docker-deploy.sh | bash # Review the generated files, then start docker compose up -d @@ -289,12 +289,12 @@ For development or contributions. Pick one of the two paths: **A. Fully containerized (recommended, zero host dependencies)** -The host only needs Docker. Clone [`airgate-sdk`](https://github.com/DouDOU-start/airgate-sdk) and [`airgate-core`](https://github.com/DouDOU-start/airgate-core) into a shared parent directory: +The host only needs Docker. Clone [`airgate-sdk`](https://github.com/DevilGenius/airgate-sdk) and [`airgate-core`](https://github.com/DevilGenius/airgate-core) into a shared parent directory: ```bash mkdir airgate && cd airgate -git clone https://github.com/DouDOU-start/airgate-sdk.git -git clone https://github.com/DouDOU-start/airgate-core.git +git clone https://github.com/DevilGenius/airgate-sdk.git +git clone https://github.com/DevilGenius/airgate-core.git cd airgate-core docker compose -f deploy/docker-compose.dev.yml up @@ -304,11 +304,11 @@ docker compose -f deploy/docker-compose.dev.yml up **B. Run on the host directly** -Requires Go 1.25+, Node 22+, local Postgres + Redis, and the sibling [`airgate-sdk`](https://github.com/DouDOU-start/airgate-sdk) repo: +Requires Go 1.25+, Node 22+, local Postgres + Redis, and the sibling [`airgate-sdk`](https://github.com/DevilGenius/airgate-sdk) repo: ```bash -git clone https://github.com/DouDOU-start/airgate-sdk.git -git clone https://github.com/DouDOU-start/airgate-core.git +git clone https://github.com/DevilGenius/airgate-sdk.git +git clone https://github.com/DevilGenius/airgate-core.git cd airgate-core make install # Install backend & frontend dependencies @@ -411,7 +411,7 @@ airgate-core/ > docker run --rm -v _redis_data:/from -v $(pwd)/data/redis:/to alpine cp -a /from/. /to/ > docker run --rm -v _airgate_plugins:/from -v $(pwd)/data/plugins:/to alpine cp -a /from/. /to/ > docker run --rm -v _airgate_uploads:/from -v $(pwd)/data/uploads:/to alpine cp -a /from/. /to/ -> curl -O https://raw.githubusercontent.com/DouDOU-start/airgate-core/master/deploy/docker-compose.yml +> curl -O https://raw.githubusercontent.com/DevilGenius/airgate-core/master/deploy/docker-compose.yml > docker compose up -d > # After verifying everything works, drop the old named volumes > docker volume rm _postgres_data _redis_data _airgate_plugins _airgate_uploads @@ -420,9 +420,9 @@ airgate-core/ ## 🤝 Contributing / Feedback -- Bugs / Features: [Issues](https://github.com/DouDOU-start/airgate-core/issues) -- Plugin development docs: [airgate-sdk](https://github.com/DouDOU-start/airgate-sdk) -- Reference plugin implementation: [airgate-openai](https://github.com/DouDOU-start/airgate-openai) +- Bugs / Features: [Issues](https://github.com/DevilGenius/airgate-core/issues) +- Plugin development docs: [airgate-sdk](https://github.com/DevilGenius/airgate-sdk) +- Reference plugin implementation: [airgate-openai](https://github.com/DevilGenius/airgate-openai) ## 📜 License diff --git a/backend/.golangci.yml b/backend/.golangci.yml index 5063b8b5..3a0eb0f8 100644 --- a/backend/.golangci.yml +++ b/backend/.golangci.yml @@ -16,7 +16,7 @@ formatters: settings: goimports: local-prefixes: - - github.com/DouDOU-start + - github.com/DevilGenius run: exclude-dirs: diff --git a/backend/cmd/server/main.go b/backend/cmd/server/main.go index 45c64e4a..55306390 100644 --- a/backend/cmd/server/main.go +++ b/backend/cmd/server/main.go @@ -19,18 +19,18 @@ import ( _ "github.com/lib/pq" "github.com/redis/go-redis/v9" - sdk "github.com/DouDOU-start/airgate-sdk/sdkgo" - - "github.com/DouDOU-start/airgate-core/ent" - "github.com/DouDOU-start/airgate-core/ent/migrate" - "github.com/DouDOU-start/airgate-core/internal/bootstrap" - "github.com/DouDOU-start/airgate-core/internal/config" - "github.com/DouDOU-start/airgate-core/internal/i18n" - "github.com/DouDOU-start/airgate-core/internal/infra/store" - "github.com/DouDOU-start/airgate-core/internal/server" - "github.com/DouDOU-start/airgate-core/internal/setup" - "github.com/DouDOU-start/airgate-core/internal/version" - webfs "github.com/DouDOU-start/airgate-core/internal/web" + sdk "github.com/DevilGenius/airgate-sdk/sdkgo" + + "github.com/DevilGenius/airgate-core/ent" + "github.com/DevilGenius/airgate-core/ent/migrate" + "github.com/DevilGenius/airgate-core/internal/bootstrap" + "github.com/DevilGenius/airgate-core/internal/config" + "github.com/DevilGenius/airgate-core/internal/i18n" + "github.com/DevilGenius/airgate-core/internal/infra/store" + "github.com/DevilGenius/airgate-core/internal/server" + "github.com/DevilGenius/airgate-core/internal/setup" + "github.com/DevilGenius/airgate-core/internal/version" + webfs "github.com/DevilGenius/airgate-core/internal/web" ) func main() { diff --git a/backend/ent/account.go b/backend/ent/account.go index bc532999..e8461033 100644 --- a/backend/ent/account.go +++ b/backend/ent/account.go @@ -10,8 +10,8 @@ import ( "entgo.io/ent" "entgo.io/ent/dialect/sql" - "github.com/DouDOU-start/airgate-core/ent/account" - "github.com/DouDOU-start/airgate-core/ent/proxy" + "github.com/DevilGenius/airgate-core/ent/account" + "github.com/DevilGenius/airgate-core/ent/proxy" ) // Account is the model entity for the Account schema. diff --git a/backend/ent/account/where.go b/backend/ent/account/where.go index 760da469..0148066d 100644 --- a/backend/ent/account/where.go +++ b/backend/ent/account/where.go @@ -7,7 +7,7 @@ import ( "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" - "github.com/DouDOU-start/airgate-core/ent/predicate" + "github.com/DevilGenius/airgate-core/ent/predicate" ) // ID filters vertices based on their ID field. diff --git a/backend/ent/account_create.go b/backend/ent/account_create.go index 3f35258b..35e3b7de 100644 --- a/backend/ent/account_create.go +++ b/backend/ent/account_create.go @@ -10,10 +10,10 @@ import ( "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" - "github.com/DouDOU-start/airgate-core/ent/account" - "github.com/DouDOU-start/airgate-core/ent/group" - "github.com/DouDOU-start/airgate-core/ent/proxy" - "github.com/DouDOU-start/airgate-core/ent/usagelog" + "github.com/DevilGenius/airgate-core/ent/account" + "github.com/DevilGenius/airgate-core/ent/group" + "github.com/DevilGenius/airgate-core/ent/proxy" + "github.com/DevilGenius/airgate-core/ent/usagelog" ) // AccountCreate is the builder for creating a Account entity. diff --git a/backend/ent/account_delete.go b/backend/ent/account_delete.go index 4cfec0de..1d618680 100644 --- a/backend/ent/account_delete.go +++ b/backend/ent/account_delete.go @@ -8,8 +8,8 @@ import ( "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" - "github.com/DouDOU-start/airgate-core/ent/account" - "github.com/DouDOU-start/airgate-core/ent/predicate" + "github.com/DevilGenius/airgate-core/ent/account" + "github.com/DevilGenius/airgate-core/ent/predicate" ) // AccountDelete is the builder for deleting a Account entity. diff --git a/backend/ent/account_query.go b/backend/ent/account_query.go index 12779c4f..182bffaa 100644 --- a/backend/ent/account_query.go +++ b/backend/ent/account_query.go @@ -11,11 +11,11 @@ import ( "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" - "github.com/DouDOU-start/airgate-core/ent/account" - "github.com/DouDOU-start/airgate-core/ent/group" - "github.com/DouDOU-start/airgate-core/ent/predicate" - "github.com/DouDOU-start/airgate-core/ent/proxy" - "github.com/DouDOU-start/airgate-core/ent/usagelog" + "github.com/DevilGenius/airgate-core/ent/account" + "github.com/DevilGenius/airgate-core/ent/group" + "github.com/DevilGenius/airgate-core/ent/predicate" + "github.com/DevilGenius/airgate-core/ent/proxy" + "github.com/DevilGenius/airgate-core/ent/usagelog" ) // AccountQuery is the builder for querying Account entities. diff --git a/backend/ent/account_update.go b/backend/ent/account_update.go index 20464652..e51fdbc3 100644 --- a/backend/ent/account_update.go +++ b/backend/ent/account_update.go @@ -11,11 +11,11 @@ import ( "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" - "github.com/DouDOU-start/airgate-core/ent/account" - "github.com/DouDOU-start/airgate-core/ent/group" - "github.com/DouDOU-start/airgate-core/ent/predicate" - "github.com/DouDOU-start/airgate-core/ent/proxy" - "github.com/DouDOU-start/airgate-core/ent/usagelog" + "github.com/DevilGenius/airgate-core/ent/account" + "github.com/DevilGenius/airgate-core/ent/group" + "github.com/DevilGenius/airgate-core/ent/predicate" + "github.com/DevilGenius/airgate-core/ent/proxy" + "github.com/DevilGenius/airgate-core/ent/usagelog" ) // AccountUpdate is the builder for updating Account entities. diff --git a/backend/ent/apikey.go b/backend/ent/apikey.go index d90fbfb5..b7bd8e82 100644 --- a/backend/ent/apikey.go +++ b/backend/ent/apikey.go @@ -10,9 +10,9 @@ import ( "entgo.io/ent" "entgo.io/ent/dialect/sql" - "github.com/DouDOU-start/airgate-core/ent/apikey" - "github.com/DouDOU-start/airgate-core/ent/group" - "github.com/DouDOU-start/airgate-core/ent/user" + "github.com/DevilGenius/airgate-core/ent/apikey" + "github.com/DevilGenius/airgate-core/ent/group" + "github.com/DevilGenius/airgate-core/ent/user" ) // APIKey is the model entity for the APIKey schema. diff --git a/backend/ent/apikey/where.go b/backend/ent/apikey/where.go index fc78a797..2079933a 100644 --- a/backend/ent/apikey/where.go +++ b/backend/ent/apikey/where.go @@ -7,7 +7,7 @@ import ( "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" - "github.com/DouDOU-start/airgate-core/ent/predicate" + "github.com/DevilGenius/airgate-core/ent/predicate" ) // ID filters vertices based on their ID field. diff --git a/backend/ent/apikey_create.go b/backend/ent/apikey_create.go index f8b43ca0..7d465001 100644 --- a/backend/ent/apikey_create.go +++ b/backend/ent/apikey_create.go @@ -10,10 +10,10 @@ import ( "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" - "github.com/DouDOU-start/airgate-core/ent/apikey" - "github.com/DouDOU-start/airgate-core/ent/group" - "github.com/DouDOU-start/airgate-core/ent/usagelog" - "github.com/DouDOU-start/airgate-core/ent/user" + "github.com/DevilGenius/airgate-core/ent/apikey" + "github.com/DevilGenius/airgate-core/ent/group" + "github.com/DevilGenius/airgate-core/ent/usagelog" + "github.com/DevilGenius/airgate-core/ent/user" ) // APIKeyCreate is the builder for creating a APIKey entity. diff --git a/backend/ent/apikey_delete.go b/backend/ent/apikey_delete.go index 18b4f0ac..b775950c 100644 --- a/backend/ent/apikey_delete.go +++ b/backend/ent/apikey_delete.go @@ -8,8 +8,8 @@ import ( "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" - "github.com/DouDOU-start/airgate-core/ent/apikey" - "github.com/DouDOU-start/airgate-core/ent/predicate" + "github.com/DevilGenius/airgate-core/ent/apikey" + "github.com/DevilGenius/airgate-core/ent/predicate" ) // APIKeyDelete is the builder for deleting a APIKey entity. diff --git a/backend/ent/apikey_query.go b/backend/ent/apikey_query.go index c0a2e06c..86266479 100644 --- a/backend/ent/apikey_query.go +++ b/backend/ent/apikey_query.go @@ -11,11 +11,11 @@ import ( "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" - "github.com/DouDOU-start/airgate-core/ent/apikey" - "github.com/DouDOU-start/airgate-core/ent/group" - "github.com/DouDOU-start/airgate-core/ent/predicate" - "github.com/DouDOU-start/airgate-core/ent/usagelog" - "github.com/DouDOU-start/airgate-core/ent/user" + "github.com/DevilGenius/airgate-core/ent/apikey" + "github.com/DevilGenius/airgate-core/ent/group" + "github.com/DevilGenius/airgate-core/ent/predicate" + "github.com/DevilGenius/airgate-core/ent/usagelog" + "github.com/DevilGenius/airgate-core/ent/user" ) // APIKeyQuery is the builder for querying APIKey entities. diff --git a/backend/ent/apikey_update.go b/backend/ent/apikey_update.go index 24e88be1..684a1c8c 100644 --- a/backend/ent/apikey_update.go +++ b/backend/ent/apikey_update.go @@ -12,11 +12,11 @@ import ( "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/dialect/sql/sqljson" "entgo.io/ent/schema/field" - "github.com/DouDOU-start/airgate-core/ent/apikey" - "github.com/DouDOU-start/airgate-core/ent/group" - "github.com/DouDOU-start/airgate-core/ent/predicate" - "github.com/DouDOU-start/airgate-core/ent/usagelog" - "github.com/DouDOU-start/airgate-core/ent/user" + "github.com/DevilGenius/airgate-core/ent/apikey" + "github.com/DevilGenius/airgate-core/ent/group" + "github.com/DevilGenius/airgate-core/ent/predicate" + "github.com/DevilGenius/airgate-core/ent/usagelog" + "github.com/DevilGenius/airgate-core/ent/user" ) // APIKeyUpdate is the builder for updating APIKey entities. diff --git a/backend/ent/balancelog.go b/backend/ent/balancelog.go index a123a733..fd1e1331 100644 --- a/backend/ent/balancelog.go +++ b/backend/ent/balancelog.go @@ -9,8 +9,8 @@ import ( "entgo.io/ent" "entgo.io/ent/dialect/sql" - "github.com/DouDOU-start/airgate-core/ent/balancelog" - "github.com/DouDOU-start/airgate-core/ent/user" + "github.com/DevilGenius/airgate-core/ent/balancelog" + "github.com/DevilGenius/airgate-core/ent/user" ) // BalanceLog is the model entity for the BalanceLog schema. diff --git a/backend/ent/balancelog/where.go b/backend/ent/balancelog/where.go index f3cd1ed7..9749fa50 100644 --- a/backend/ent/balancelog/where.go +++ b/backend/ent/balancelog/where.go @@ -7,7 +7,7 @@ import ( "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" - "github.com/DouDOU-start/airgate-core/ent/predicate" + "github.com/DevilGenius/airgate-core/ent/predicate" ) // ID filters vertices based on their ID field. diff --git a/backend/ent/balancelog_create.go b/backend/ent/balancelog_create.go index bef19eb2..e0894947 100644 --- a/backend/ent/balancelog_create.go +++ b/backend/ent/balancelog_create.go @@ -10,8 +10,8 @@ import ( "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" - "github.com/DouDOU-start/airgate-core/ent/balancelog" - "github.com/DouDOU-start/airgate-core/ent/user" + "github.com/DevilGenius/airgate-core/ent/balancelog" + "github.com/DevilGenius/airgate-core/ent/user" ) // BalanceLogCreate is the builder for creating a BalanceLog entity. diff --git a/backend/ent/balancelog_delete.go b/backend/ent/balancelog_delete.go index 60b0306b..0facbbb7 100644 --- a/backend/ent/balancelog_delete.go +++ b/backend/ent/balancelog_delete.go @@ -8,8 +8,8 @@ import ( "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" - "github.com/DouDOU-start/airgate-core/ent/balancelog" - "github.com/DouDOU-start/airgate-core/ent/predicate" + "github.com/DevilGenius/airgate-core/ent/balancelog" + "github.com/DevilGenius/airgate-core/ent/predicate" ) // BalanceLogDelete is the builder for deleting a BalanceLog entity. diff --git a/backend/ent/balancelog_query.go b/backend/ent/balancelog_query.go index 7c30c305..1661f6b8 100644 --- a/backend/ent/balancelog_query.go +++ b/backend/ent/balancelog_query.go @@ -10,9 +10,9 @@ import ( "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" - "github.com/DouDOU-start/airgate-core/ent/balancelog" - "github.com/DouDOU-start/airgate-core/ent/predicate" - "github.com/DouDOU-start/airgate-core/ent/user" + "github.com/DevilGenius/airgate-core/ent/balancelog" + "github.com/DevilGenius/airgate-core/ent/predicate" + "github.com/DevilGenius/airgate-core/ent/user" ) // BalanceLogQuery is the builder for querying BalanceLog entities. diff --git a/backend/ent/balancelog_update.go b/backend/ent/balancelog_update.go index 2f7b6439..77864ca1 100644 --- a/backend/ent/balancelog_update.go +++ b/backend/ent/balancelog_update.go @@ -10,9 +10,9 @@ import ( "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" - "github.com/DouDOU-start/airgate-core/ent/balancelog" - "github.com/DouDOU-start/airgate-core/ent/predicate" - "github.com/DouDOU-start/airgate-core/ent/user" + "github.com/DevilGenius/airgate-core/ent/balancelog" + "github.com/DevilGenius/airgate-core/ent/predicate" + "github.com/DevilGenius/airgate-core/ent/user" ) // BalanceLogUpdate is the builder for updating BalanceLog entities. diff --git a/backend/ent/client.go b/backend/ent/client.go index a7afc5a7..0d50e564 100644 --- a/backend/ent/client.go +++ b/backend/ent/client.go @@ -9,24 +9,24 @@ import ( "log" "reflect" - "github.com/DouDOU-start/airgate-core/ent/migrate" + "github.com/DevilGenius/airgate-core/ent/migrate" "entgo.io/ent" "entgo.io/ent/dialect" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" - "github.com/DouDOU-start/airgate-core/ent/account" - "github.com/DouDOU-start/airgate-core/ent/apikey" - "github.com/DouDOU-start/airgate-core/ent/balancelog" - "github.com/DouDOU-start/airgate-core/ent/group" - "github.com/DouDOU-start/airgate-core/ent/plugin" - "github.com/DouDOU-start/airgate-core/ent/pluginsource" - "github.com/DouDOU-start/airgate-core/ent/proxy" - "github.com/DouDOU-start/airgate-core/ent/setting" - "github.com/DouDOU-start/airgate-core/ent/task" - "github.com/DouDOU-start/airgate-core/ent/usagelog" - "github.com/DouDOU-start/airgate-core/ent/user" - "github.com/DouDOU-start/airgate-core/ent/usersubscription" + "github.com/DevilGenius/airgate-core/ent/account" + "github.com/DevilGenius/airgate-core/ent/apikey" + "github.com/DevilGenius/airgate-core/ent/balancelog" + "github.com/DevilGenius/airgate-core/ent/group" + "github.com/DevilGenius/airgate-core/ent/plugin" + "github.com/DevilGenius/airgate-core/ent/pluginsource" + "github.com/DevilGenius/airgate-core/ent/proxy" + "github.com/DevilGenius/airgate-core/ent/setting" + "github.com/DevilGenius/airgate-core/ent/task" + "github.com/DevilGenius/airgate-core/ent/usagelog" + "github.com/DevilGenius/airgate-core/ent/user" + "github.com/DevilGenius/airgate-core/ent/usersubscription" ) // Client is the client that holds all ent builders. diff --git a/backend/ent/ent.go b/backend/ent/ent.go index 02dfc665..c6427a47 100644 --- a/backend/ent/ent.go +++ b/backend/ent/ent.go @@ -12,18 +12,18 @@ import ( "entgo.io/ent" "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" - "github.com/DouDOU-start/airgate-core/ent/account" - "github.com/DouDOU-start/airgate-core/ent/apikey" - "github.com/DouDOU-start/airgate-core/ent/balancelog" - "github.com/DouDOU-start/airgate-core/ent/group" - "github.com/DouDOU-start/airgate-core/ent/plugin" - "github.com/DouDOU-start/airgate-core/ent/pluginsource" - "github.com/DouDOU-start/airgate-core/ent/proxy" - "github.com/DouDOU-start/airgate-core/ent/setting" - "github.com/DouDOU-start/airgate-core/ent/task" - "github.com/DouDOU-start/airgate-core/ent/usagelog" - "github.com/DouDOU-start/airgate-core/ent/user" - "github.com/DouDOU-start/airgate-core/ent/usersubscription" + "github.com/DevilGenius/airgate-core/ent/account" + "github.com/DevilGenius/airgate-core/ent/apikey" + "github.com/DevilGenius/airgate-core/ent/balancelog" + "github.com/DevilGenius/airgate-core/ent/group" + "github.com/DevilGenius/airgate-core/ent/plugin" + "github.com/DevilGenius/airgate-core/ent/pluginsource" + "github.com/DevilGenius/airgate-core/ent/proxy" + "github.com/DevilGenius/airgate-core/ent/setting" + "github.com/DevilGenius/airgate-core/ent/task" + "github.com/DevilGenius/airgate-core/ent/usagelog" + "github.com/DevilGenius/airgate-core/ent/user" + "github.com/DevilGenius/airgate-core/ent/usersubscription" ) // ent aliases to avoid import conflicts in user's code. diff --git a/backend/ent/enttest/enttest.go b/backend/ent/enttest/enttest.go index 5431fe41..2e636eab 100644 --- a/backend/ent/enttest/enttest.go +++ b/backend/ent/enttest/enttest.go @@ -5,12 +5,12 @@ package enttest import ( "context" - "github.com/DouDOU-start/airgate-core/ent" + "github.com/DevilGenius/airgate-core/ent" // required by schema hooks. - _ "github.com/DouDOU-start/airgate-core/ent/runtime" + _ "github.com/DevilGenius/airgate-core/ent/runtime" "entgo.io/ent/dialect/sql/schema" - "github.com/DouDOU-start/airgate-core/ent/migrate" + "github.com/DevilGenius/airgate-core/ent/migrate" ) type ( diff --git a/backend/ent/group.go b/backend/ent/group.go index e1a39c9e..e2868fb7 100644 --- a/backend/ent/group.go +++ b/backend/ent/group.go @@ -10,7 +10,7 @@ import ( "entgo.io/ent" "entgo.io/ent/dialect/sql" - "github.com/DouDOU-start/airgate-core/ent/group" + "github.com/DevilGenius/airgate-core/ent/group" ) // Group is the model entity for the Group schema. diff --git a/backend/ent/group/where.go b/backend/ent/group/where.go index 662c68a8..be1ddb06 100644 --- a/backend/ent/group/where.go +++ b/backend/ent/group/where.go @@ -7,7 +7,7 @@ import ( "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" - "github.com/DouDOU-start/airgate-core/ent/predicate" + "github.com/DevilGenius/airgate-core/ent/predicate" ) // ID filters vertices based on their ID field. diff --git a/backend/ent/group_create.go b/backend/ent/group_create.go index d79d02ec..816981dc 100644 --- a/backend/ent/group_create.go +++ b/backend/ent/group_create.go @@ -10,12 +10,12 @@ import ( "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" - "github.com/DouDOU-start/airgate-core/ent/account" - "github.com/DouDOU-start/airgate-core/ent/apikey" - "github.com/DouDOU-start/airgate-core/ent/group" - "github.com/DouDOU-start/airgate-core/ent/usagelog" - "github.com/DouDOU-start/airgate-core/ent/user" - "github.com/DouDOU-start/airgate-core/ent/usersubscription" + "github.com/DevilGenius/airgate-core/ent/account" + "github.com/DevilGenius/airgate-core/ent/apikey" + "github.com/DevilGenius/airgate-core/ent/group" + "github.com/DevilGenius/airgate-core/ent/usagelog" + "github.com/DevilGenius/airgate-core/ent/user" + "github.com/DevilGenius/airgate-core/ent/usersubscription" ) // GroupCreate is the builder for creating a Group entity. diff --git a/backend/ent/group_delete.go b/backend/ent/group_delete.go index 1295308b..5a1c6cfe 100644 --- a/backend/ent/group_delete.go +++ b/backend/ent/group_delete.go @@ -8,8 +8,8 @@ import ( "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" - "github.com/DouDOU-start/airgate-core/ent/group" - "github.com/DouDOU-start/airgate-core/ent/predicate" + "github.com/DevilGenius/airgate-core/ent/group" + "github.com/DevilGenius/airgate-core/ent/predicate" ) // GroupDelete is the builder for deleting a Group entity. diff --git a/backend/ent/group_query.go b/backend/ent/group_query.go index c1200a3e..ea5d8220 100644 --- a/backend/ent/group_query.go +++ b/backend/ent/group_query.go @@ -11,13 +11,13 @@ import ( "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" - "github.com/DouDOU-start/airgate-core/ent/account" - "github.com/DouDOU-start/airgate-core/ent/apikey" - "github.com/DouDOU-start/airgate-core/ent/group" - "github.com/DouDOU-start/airgate-core/ent/predicate" - "github.com/DouDOU-start/airgate-core/ent/usagelog" - "github.com/DouDOU-start/airgate-core/ent/user" - "github.com/DouDOU-start/airgate-core/ent/usersubscription" + "github.com/DevilGenius/airgate-core/ent/account" + "github.com/DevilGenius/airgate-core/ent/apikey" + "github.com/DevilGenius/airgate-core/ent/group" + "github.com/DevilGenius/airgate-core/ent/predicate" + "github.com/DevilGenius/airgate-core/ent/usagelog" + "github.com/DevilGenius/airgate-core/ent/user" + "github.com/DevilGenius/airgate-core/ent/usersubscription" ) // GroupQuery is the builder for querying Group entities. diff --git a/backend/ent/group_update.go b/backend/ent/group_update.go index d9ac2346..e0ed135f 100644 --- a/backend/ent/group_update.go +++ b/backend/ent/group_update.go @@ -11,13 +11,13 @@ import ( "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" - "github.com/DouDOU-start/airgate-core/ent/account" - "github.com/DouDOU-start/airgate-core/ent/apikey" - "github.com/DouDOU-start/airgate-core/ent/group" - "github.com/DouDOU-start/airgate-core/ent/predicate" - "github.com/DouDOU-start/airgate-core/ent/usagelog" - "github.com/DouDOU-start/airgate-core/ent/user" - "github.com/DouDOU-start/airgate-core/ent/usersubscription" + "github.com/DevilGenius/airgate-core/ent/account" + "github.com/DevilGenius/airgate-core/ent/apikey" + "github.com/DevilGenius/airgate-core/ent/group" + "github.com/DevilGenius/airgate-core/ent/predicate" + "github.com/DevilGenius/airgate-core/ent/usagelog" + "github.com/DevilGenius/airgate-core/ent/user" + "github.com/DevilGenius/airgate-core/ent/usersubscription" ) // GroupUpdate is the builder for updating Group entities. diff --git a/backend/ent/hook/hook.go b/backend/ent/hook/hook.go index f52a887c..739407d2 100644 --- a/backend/ent/hook/hook.go +++ b/backend/ent/hook/hook.go @@ -6,7 +6,7 @@ import ( "context" "fmt" - "github.com/DouDOU-start/airgate-core/ent" + "github.com/DevilGenius/airgate-core/ent" ) // The APIKeyFunc type is an adapter to allow the use of ordinary diff --git a/backend/ent/migrate/schema.go b/backend/ent/migrate/schema.go index 14f3c52b..60b4f92f 100644 --- a/backend/ent/migrate/schema.go +++ b/backend/ent/migrate/schema.go @@ -275,14 +275,11 @@ var ( {Name: "output_tokens", Type: field.TypeInt, Default: 0}, {Name: "cached_input_tokens", Type: field.TypeInt, Default: 0}, {Name: "cache_creation_tokens", Type: field.TypeInt, Default: 0}, - {Name: "cache_creation_5m_tokens", Type: field.TypeInt, Default: 0}, - {Name: "cache_creation_1h_tokens", Type: field.TypeInt, Default: 0}, {Name: "reasoning_output_tokens", Type: field.TypeInt, Default: 0}, {Name: "input_price", Type: field.TypeFloat64, Default: 0, SchemaType: map[string]string{"postgres": "decimal(20,8)"}}, {Name: "output_price", Type: field.TypeFloat64, Default: 0, SchemaType: map[string]string{"postgres": "decimal(20,8)"}}, {Name: "cached_input_price", Type: field.TypeFloat64, Default: 0, SchemaType: map[string]string{"postgres": "decimal(20,8)"}}, {Name: "cache_creation_price", Type: field.TypeFloat64, Default: 0, SchemaType: map[string]string{"postgres": "decimal(20,8)"}}, - {Name: "cache_creation_1h_price", Type: field.TypeFloat64, Default: 0, SchemaType: map[string]string{"postgres": "decimal(20,8)"}}, {Name: "input_cost", Type: field.TypeFloat64, Default: 0, SchemaType: map[string]string{"postgres": "decimal(20,8)"}}, {Name: "output_cost", Type: field.TypeFloat64, Default: 0, SchemaType: map[string]string{"postgres": "decimal(20,8)"}}, {Name: "cached_input_cost", Type: field.TypeFloat64, Default: 0, SchemaType: map[string]string{"postgres": "decimal(20,8)"}}, @@ -295,7 +292,6 @@ var ( {Name: "sell_rate", Type: field.TypeFloat64, Default: 0}, {Name: "account_rate_multiplier", Type: field.TypeFloat64, Default: 1}, {Name: "service_tier", Type: field.TypeString, Default: ""}, - {Name: "image_size", Type: field.TypeString, Default: ""}, {Name: "stream", Type: field.TypeBool, Default: false}, {Name: "duration_ms", Type: field.TypeInt64, Default: 0}, {Name: "first_token_ms", Type: field.TypeInt64, Default: 0}, @@ -303,9 +299,6 @@ var ( {Name: "ip_address", Type: field.TypeString, Default: ""}, {Name: "endpoint", Type: field.TypeString, Default: ""}, {Name: "reasoning_effort", Type: field.TypeString, Default: ""}, - {Name: "usage_attributes", Type: field.TypeJSON, Nullable: true}, - {Name: "usage_metrics", Type: field.TypeJSON, Nullable: true}, - {Name: "usage_cost_details", Type: field.TypeJSON, Nullable: true}, {Name: "usage_metadata", Type: field.TypeJSON, Nullable: true}, {Name: "user_id_snapshot", Type: field.TypeInt, Default: 0}, {Name: "user_email_snapshot", Type: field.TypeString, Default: ""}, @@ -323,64 +316,69 @@ var ( ForeignKeys: []*schema.ForeignKey{ { Symbol: "usage_logs_api_keys_usage_logs", - Columns: []*schema.Column{UsageLogsColumns[42]}, + Columns: []*schema.Column{UsageLogsColumns[35]}, RefColumns: []*schema.Column{APIKeysColumns[0]}, OnDelete: schema.SetNull, }, { Symbol: "usage_logs_accounts_usage_logs", - Columns: []*schema.Column{UsageLogsColumns[43]}, + Columns: []*schema.Column{UsageLogsColumns[36]}, RefColumns: []*schema.Column{AccountsColumns[0]}, OnDelete: schema.SetNull, }, { Symbol: "usage_logs_groups_usage_logs", - Columns: []*schema.Column{UsageLogsColumns[44]}, + Columns: []*schema.Column{UsageLogsColumns[37]}, RefColumns: []*schema.Column{GroupsColumns[0]}, OnDelete: schema.SetNull, }, { Symbol: "usage_logs_users_usage_logs", - Columns: []*schema.Column{UsageLogsColumns[45]}, + Columns: []*schema.Column{UsageLogsColumns[38]}, RefColumns: []*schema.Column{UsersColumns[0]}, OnDelete: schema.SetNull, }, }, Indexes: []*schema.Index{ + { + Name: "usage_log_model", + Unique: false, + Columns: []*schema.Column{UsageLogsColumns[2]}, + }, { Name: "usage_log_created_at", Unique: false, - Columns: []*schema.Column{UsageLogsColumns[41]}, + Columns: []*schema.Column{UsageLogsColumns[34]}, }, { Name: "usage_log_platform_created_at", Unique: false, - Columns: []*schema.Column{UsageLogsColumns[1], UsageLogsColumns[41]}, + Columns: []*schema.Column{UsageLogsColumns[1], UsageLogsColumns[34]}, }, { Name: "usage_log_user_snapshot_created_at", Unique: false, - Columns: []*schema.Column{UsageLogsColumns[39], UsageLogsColumns[41]}, + Columns: []*schema.Column{UsageLogsColumns[32], UsageLogsColumns[34]}, }, { Name: "usage_log_user", Unique: false, - Columns: []*schema.Column{UsageLogsColumns[45]}, + Columns: []*schema.Column{UsageLogsColumns[38]}, }, { Name: "usage_log_api_key", Unique: false, - Columns: []*schema.Column{UsageLogsColumns[42]}, + Columns: []*schema.Column{UsageLogsColumns[35]}, }, { Name: "usage_log_account", Unique: false, - Columns: []*schema.Column{UsageLogsColumns[43]}, + Columns: []*schema.Column{UsageLogsColumns[36]}, }, { Name: "usage_log_group", Unique: false, - Columns: []*schema.Column{UsageLogsColumns[44]}, + Columns: []*schema.Column{UsageLogsColumns[37]}, }, }, } diff --git a/backend/ent/mutation.go b/backend/ent/mutation.go index 9498ce4a..4780d0b8 100644 --- a/backend/ent/mutation.go +++ b/backend/ent/mutation.go @@ -11,20 +11,19 @@ import ( "entgo.io/ent" "entgo.io/ent/dialect/sql" - "github.com/DouDOU-start/airgate-core/ent/account" - "github.com/DouDOU-start/airgate-core/ent/apikey" - "github.com/DouDOU-start/airgate-core/ent/balancelog" - "github.com/DouDOU-start/airgate-core/ent/group" - "github.com/DouDOU-start/airgate-core/ent/plugin" - "github.com/DouDOU-start/airgate-core/ent/pluginsource" - "github.com/DouDOU-start/airgate-core/ent/predicate" - "github.com/DouDOU-start/airgate-core/ent/proxy" - "github.com/DouDOU-start/airgate-core/ent/setting" - "github.com/DouDOU-start/airgate-core/ent/task" - "github.com/DouDOU-start/airgate-core/ent/usagelog" - "github.com/DouDOU-start/airgate-core/ent/user" - "github.com/DouDOU-start/airgate-core/ent/usersubscription" - sdk "github.com/DouDOU-start/airgate-sdk/sdkgo" + "github.com/DevilGenius/airgate-core/ent/account" + "github.com/DevilGenius/airgate-core/ent/apikey" + "github.com/DevilGenius/airgate-core/ent/balancelog" + "github.com/DevilGenius/airgate-core/ent/group" + "github.com/DevilGenius/airgate-core/ent/plugin" + "github.com/DevilGenius/airgate-core/ent/pluginsource" + "github.com/DevilGenius/airgate-core/ent/predicate" + "github.com/DevilGenius/airgate-core/ent/proxy" + "github.com/DevilGenius/airgate-core/ent/setting" + "github.com/DevilGenius/airgate-core/ent/task" + "github.com/DevilGenius/airgate-core/ent/usagelog" + "github.com/DevilGenius/airgate-core/ent/user" + "github.com/DevilGenius/airgate-core/ent/usersubscription" ) const ( @@ -10511,91 +10510,78 @@ func (m *TaskMutation) ResetEdge(name string) error { // UsageLogMutation represents an operation that mutates the UsageLog nodes in the graph. type UsageLogMutation struct { config - op Op - typ string - id *int - platform *string - model *string - input_tokens *int - addinput_tokens *int - output_tokens *int - addoutput_tokens *int - cached_input_tokens *int - addcached_input_tokens *int - cache_creation_tokens *int - addcache_creation_tokens *int - cache_creation_5m_tokens *int - addcache_creation_5m_tokens *int - cache_creation_1h_tokens *int - addcache_creation_1h_tokens *int - reasoning_output_tokens *int - addreasoning_output_tokens *int - input_price *float64 - addinput_price *float64 - output_price *float64 - addoutput_price *float64 - cached_input_price *float64 - addcached_input_price *float64 - cache_creation_price *float64 - addcache_creation_price *float64 - cache_creation_1h_price *float64 - addcache_creation_1h_price *float64 - input_cost *float64 - addinput_cost *float64 - output_cost *float64 - addoutput_cost *float64 - cached_input_cost *float64 - addcached_input_cost *float64 - cache_creation_cost *float64 - addcache_creation_cost *float64 - total_cost *float64 - addtotal_cost *float64 - actual_cost *float64 - addactual_cost *float64 - billed_cost *float64 - addbilled_cost *float64 - account_cost *float64 - addaccount_cost *float64 - rate_multiplier *float64 - addrate_multiplier *float64 - sell_rate *float64 - addsell_rate *float64 - account_rate_multiplier *float64 - addaccount_rate_multiplier *float64 - service_tier *string - image_size *string - stream *bool - duration_ms *int64 - addduration_ms *int64 - first_token_ms *int64 - addfirst_token_ms *int64 - user_agent *string - ip_address *string - endpoint *string - reasoning_effort *string - usage_attributes *[]sdk.UsageAttribute - appendusage_attributes []sdk.UsageAttribute - usage_metrics *[]sdk.UsageMetric - appendusage_metrics []sdk.UsageMetric - usage_cost_details *[]sdk.UsageCostDetail - appendusage_cost_details []sdk.UsageCostDetail - usage_metadata *map[string]string - user_id_snapshot *int - adduser_id_snapshot *int - user_email_snapshot *string - created_at *time.Time - clearedFields map[string]struct{} - user *int - cleareduser bool - api_key *int - clearedapi_key bool - account *int - clearedaccount bool - group *int - clearedgroup bool - done bool - oldValue func(context.Context) (*UsageLog, error) - predicates []predicate.UsageLog + op Op + typ string + id *int + platform *string + model *string + input_tokens *int + addinput_tokens *int + output_tokens *int + addoutput_tokens *int + cached_input_tokens *int + addcached_input_tokens *int + cache_creation_tokens *int + addcache_creation_tokens *int + reasoning_output_tokens *int + addreasoning_output_tokens *int + input_price *float64 + addinput_price *float64 + output_price *float64 + addoutput_price *float64 + cached_input_price *float64 + addcached_input_price *float64 + cache_creation_price *float64 + addcache_creation_price *float64 + input_cost *float64 + addinput_cost *float64 + output_cost *float64 + addoutput_cost *float64 + cached_input_cost *float64 + addcached_input_cost *float64 + cache_creation_cost *float64 + addcache_creation_cost *float64 + total_cost *float64 + addtotal_cost *float64 + actual_cost *float64 + addactual_cost *float64 + billed_cost *float64 + addbilled_cost *float64 + account_cost *float64 + addaccount_cost *float64 + rate_multiplier *float64 + addrate_multiplier *float64 + sell_rate *float64 + addsell_rate *float64 + account_rate_multiplier *float64 + addaccount_rate_multiplier *float64 + service_tier *string + stream *bool + duration_ms *int64 + addduration_ms *int64 + first_token_ms *int64 + addfirst_token_ms *int64 + user_agent *string + ip_address *string + endpoint *string + reasoning_effort *string + usage_metadata *map[string]string + user_id_snapshot *int + adduser_id_snapshot *int + user_email_snapshot *string + created_at *time.Time + clearedFields map[string]struct{} + user *int + cleareduser bool + api_key *int + clearedapi_key bool + account *int + clearedaccount bool + group *int + clearedgroup bool + done bool + oldValue func(context.Context) (*UsageLog, error) + predicates []predicate.UsageLog } var _ ent.Mutation = (*UsageLogMutation)(nil) @@ -10992,118 +10978,6 @@ func (m *UsageLogMutation) ResetCacheCreationTokens() { m.addcache_creation_tokens = nil } -// SetCacheCreation5mTokens sets the "cache_creation_5m_tokens" field. -func (m *UsageLogMutation) SetCacheCreation5mTokens(i int) { - m.cache_creation_5m_tokens = &i - m.addcache_creation_5m_tokens = nil -} - -// CacheCreation5mTokens returns the value of the "cache_creation_5m_tokens" field in the mutation. -func (m *UsageLogMutation) CacheCreation5mTokens() (r int, exists bool) { - v := m.cache_creation_5m_tokens - if v == nil { - return - } - return *v, true -} - -// OldCacheCreation5mTokens returns the old "cache_creation_5m_tokens" field's value of the UsageLog entity. -// If the UsageLog object wasn't provided to the builder, the object is fetched from the database. -// An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *UsageLogMutation) OldCacheCreation5mTokens(ctx context.Context) (v int, err error) { - if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldCacheCreation5mTokens is only allowed on UpdateOne operations") - } - if m.id == nil || m.oldValue == nil { - return v, errors.New("OldCacheCreation5mTokens requires an ID field in the mutation") - } - oldValue, err := m.oldValue(ctx) - if err != nil { - return v, fmt.Errorf("querying old value for OldCacheCreation5mTokens: %w", err) - } - return oldValue.CacheCreation5mTokens, nil -} - -// AddCacheCreation5mTokens adds i to the "cache_creation_5m_tokens" field. -func (m *UsageLogMutation) AddCacheCreation5mTokens(i int) { - if m.addcache_creation_5m_tokens != nil { - *m.addcache_creation_5m_tokens += i - } else { - m.addcache_creation_5m_tokens = &i - } -} - -// AddedCacheCreation5mTokens returns the value that was added to the "cache_creation_5m_tokens" field in this mutation. -func (m *UsageLogMutation) AddedCacheCreation5mTokens() (r int, exists bool) { - v := m.addcache_creation_5m_tokens - if v == nil { - return - } - return *v, true -} - -// ResetCacheCreation5mTokens resets all changes to the "cache_creation_5m_tokens" field. -func (m *UsageLogMutation) ResetCacheCreation5mTokens() { - m.cache_creation_5m_tokens = nil - m.addcache_creation_5m_tokens = nil -} - -// SetCacheCreation1hTokens sets the "cache_creation_1h_tokens" field. -func (m *UsageLogMutation) SetCacheCreation1hTokens(i int) { - m.cache_creation_1h_tokens = &i - m.addcache_creation_1h_tokens = nil -} - -// CacheCreation1hTokens returns the value of the "cache_creation_1h_tokens" field in the mutation. -func (m *UsageLogMutation) CacheCreation1hTokens() (r int, exists bool) { - v := m.cache_creation_1h_tokens - if v == nil { - return - } - return *v, true -} - -// OldCacheCreation1hTokens returns the old "cache_creation_1h_tokens" field's value of the UsageLog entity. -// If the UsageLog object wasn't provided to the builder, the object is fetched from the database. -// An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *UsageLogMutation) OldCacheCreation1hTokens(ctx context.Context) (v int, err error) { - if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldCacheCreation1hTokens is only allowed on UpdateOne operations") - } - if m.id == nil || m.oldValue == nil { - return v, errors.New("OldCacheCreation1hTokens requires an ID field in the mutation") - } - oldValue, err := m.oldValue(ctx) - if err != nil { - return v, fmt.Errorf("querying old value for OldCacheCreation1hTokens: %w", err) - } - return oldValue.CacheCreation1hTokens, nil -} - -// AddCacheCreation1hTokens adds i to the "cache_creation_1h_tokens" field. -func (m *UsageLogMutation) AddCacheCreation1hTokens(i int) { - if m.addcache_creation_1h_tokens != nil { - *m.addcache_creation_1h_tokens += i - } else { - m.addcache_creation_1h_tokens = &i - } -} - -// AddedCacheCreation1hTokens returns the value that was added to the "cache_creation_1h_tokens" field in this mutation. -func (m *UsageLogMutation) AddedCacheCreation1hTokens() (r int, exists bool) { - v := m.addcache_creation_1h_tokens - if v == nil { - return - } - return *v, true -} - -// ResetCacheCreation1hTokens resets all changes to the "cache_creation_1h_tokens" field. -func (m *UsageLogMutation) ResetCacheCreation1hTokens() { - m.cache_creation_1h_tokens = nil - m.addcache_creation_1h_tokens = nil -} - // SetReasoningOutputTokens sets the "reasoning_output_tokens" field. func (m *UsageLogMutation) SetReasoningOutputTokens(i int) { m.reasoning_output_tokens = &i @@ -11384,62 +11258,6 @@ func (m *UsageLogMutation) ResetCacheCreationPrice() { m.addcache_creation_price = nil } -// SetCacheCreation1hPrice sets the "cache_creation_1h_price" field. -func (m *UsageLogMutation) SetCacheCreation1hPrice(f float64) { - m.cache_creation_1h_price = &f - m.addcache_creation_1h_price = nil -} - -// CacheCreation1hPrice returns the value of the "cache_creation_1h_price" field in the mutation. -func (m *UsageLogMutation) CacheCreation1hPrice() (r float64, exists bool) { - v := m.cache_creation_1h_price - if v == nil { - return - } - return *v, true -} - -// OldCacheCreation1hPrice returns the old "cache_creation_1h_price" field's value of the UsageLog entity. -// If the UsageLog object wasn't provided to the builder, the object is fetched from the database. -// An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *UsageLogMutation) OldCacheCreation1hPrice(ctx context.Context) (v float64, err error) { - if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldCacheCreation1hPrice is only allowed on UpdateOne operations") - } - if m.id == nil || m.oldValue == nil { - return v, errors.New("OldCacheCreation1hPrice requires an ID field in the mutation") - } - oldValue, err := m.oldValue(ctx) - if err != nil { - return v, fmt.Errorf("querying old value for OldCacheCreation1hPrice: %w", err) - } - return oldValue.CacheCreation1hPrice, nil -} - -// AddCacheCreation1hPrice adds f to the "cache_creation_1h_price" field. -func (m *UsageLogMutation) AddCacheCreation1hPrice(f float64) { - if m.addcache_creation_1h_price != nil { - *m.addcache_creation_1h_price += f - } else { - m.addcache_creation_1h_price = &f - } -} - -// AddedCacheCreation1hPrice returns the value that was added to the "cache_creation_1h_price" field in this mutation. -func (m *UsageLogMutation) AddedCacheCreation1hPrice() (r float64, exists bool) { - v := m.addcache_creation_1h_price - if v == nil { - return - } - return *v, true -} - -// ResetCacheCreation1hPrice resets all changes to the "cache_creation_1h_price" field. -func (m *UsageLogMutation) ResetCacheCreation1hPrice() { - m.cache_creation_1h_price = nil - m.addcache_creation_1h_price = nil -} - // SetInputCost sets the "input_cost" field. func (m *UsageLogMutation) SetInputCost(f float64) { m.input_cost = &f @@ -12092,42 +11910,6 @@ func (m *UsageLogMutation) ResetServiceTier() { m.service_tier = nil } -// SetImageSize sets the "image_size" field. -func (m *UsageLogMutation) SetImageSize(s string) { - m.image_size = &s -} - -// ImageSize returns the value of the "image_size" field in the mutation. -func (m *UsageLogMutation) ImageSize() (r string, exists bool) { - v := m.image_size - if v == nil { - return - } - return *v, true -} - -// OldImageSize returns the old "image_size" field's value of the UsageLog entity. -// If the UsageLog object wasn't provided to the builder, the object is fetched from the database. -// An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *UsageLogMutation) OldImageSize(ctx context.Context) (v string, err error) { - if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldImageSize is only allowed on UpdateOne operations") - } - if m.id == nil || m.oldValue == nil { - return v, errors.New("OldImageSize requires an ID field in the mutation") - } - oldValue, err := m.oldValue(ctx) - if err != nil { - return v, fmt.Errorf("querying old value for OldImageSize: %w", err) - } - return oldValue.ImageSize, nil -} - -// ResetImageSize resets all changes to the "image_size" field. -func (m *UsageLogMutation) ResetImageSize() { - m.image_size = nil -} - // SetStream sets the "stream" field. func (m *UsageLogMutation) SetStream(b bool) { m.stream = &b @@ -12420,201 +12202,6 @@ func (m *UsageLogMutation) ResetReasoningEffort() { m.reasoning_effort = nil } -// SetUsageAttributes sets the "usage_attributes" field. -func (m *UsageLogMutation) SetUsageAttributes(sa []sdk.UsageAttribute) { - m.usage_attributes = &sa - m.appendusage_attributes = nil -} - -// UsageAttributes returns the value of the "usage_attributes" field in the mutation. -func (m *UsageLogMutation) UsageAttributes() (r []sdk.UsageAttribute, exists bool) { - v := m.usage_attributes - if v == nil { - return - } - return *v, true -} - -// OldUsageAttributes returns the old "usage_attributes" field's value of the UsageLog entity. -// If the UsageLog object wasn't provided to the builder, the object is fetched from the database. -// An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *UsageLogMutation) OldUsageAttributes(ctx context.Context) (v []sdk.UsageAttribute, err error) { - if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldUsageAttributes is only allowed on UpdateOne operations") - } - if m.id == nil || m.oldValue == nil { - return v, errors.New("OldUsageAttributes requires an ID field in the mutation") - } - oldValue, err := m.oldValue(ctx) - if err != nil { - return v, fmt.Errorf("querying old value for OldUsageAttributes: %w", err) - } - return oldValue.UsageAttributes, nil -} - -// AppendUsageAttributes adds sa to the "usage_attributes" field. -func (m *UsageLogMutation) AppendUsageAttributes(sa []sdk.UsageAttribute) { - m.appendusage_attributes = append(m.appendusage_attributes, sa...) -} - -// AppendedUsageAttributes returns the list of values that were appended to the "usage_attributes" field in this mutation. -func (m *UsageLogMutation) AppendedUsageAttributes() ([]sdk.UsageAttribute, bool) { - if len(m.appendusage_attributes) == 0 { - return nil, false - } - return m.appendusage_attributes, true -} - -// ClearUsageAttributes clears the value of the "usage_attributes" field. -func (m *UsageLogMutation) ClearUsageAttributes() { - m.usage_attributes = nil - m.appendusage_attributes = nil - m.clearedFields[usagelog.FieldUsageAttributes] = struct{}{} -} - -// UsageAttributesCleared returns if the "usage_attributes" field was cleared in this mutation. -func (m *UsageLogMutation) UsageAttributesCleared() bool { - _, ok := m.clearedFields[usagelog.FieldUsageAttributes] - return ok -} - -// ResetUsageAttributes resets all changes to the "usage_attributes" field. -func (m *UsageLogMutation) ResetUsageAttributes() { - m.usage_attributes = nil - m.appendusage_attributes = nil - delete(m.clearedFields, usagelog.FieldUsageAttributes) -} - -// SetUsageMetrics sets the "usage_metrics" field. -func (m *UsageLogMutation) SetUsageMetrics(sm []sdk.UsageMetric) { - m.usage_metrics = &sm - m.appendusage_metrics = nil -} - -// UsageMetrics returns the value of the "usage_metrics" field in the mutation. -func (m *UsageLogMutation) UsageMetrics() (r []sdk.UsageMetric, exists bool) { - v := m.usage_metrics - if v == nil { - return - } - return *v, true -} - -// OldUsageMetrics returns the old "usage_metrics" field's value of the UsageLog entity. -// If the UsageLog object wasn't provided to the builder, the object is fetched from the database. -// An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *UsageLogMutation) OldUsageMetrics(ctx context.Context) (v []sdk.UsageMetric, err error) { - if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldUsageMetrics is only allowed on UpdateOne operations") - } - if m.id == nil || m.oldValue == nil { - return v, errors.New("OldUsageMetrics requires an ID field in the mutation") - } - oldValue, err := m.oldValue(ctx) - if err != nil { - return v, fmt.Errorf("querying old value for OldUsageMetrics: %w", err) - } - return oldValue.UsageMetrics, nil -} - -// AppendUsageMetrics adds sm to the "usage_metrics" field. -func (m *UsageLogMutation) AppendUsageMetrics(sm []sdk.UsageMetric) { - m.appendusage_metrics = append(m.appendusage_metrics, sm...) -} - -// AppendedUsageMetrics returns the list of values that were appended to the "usage_metrics" field in this mutation. -func (m *UsageLogMutation) AppendedUsageMetrics() ([]sdk.UsageMetric, bool) { - if len(m.appendusage_metrics) == 0 { - return nil, false - } - return m.appendusage_metrics, true -} - -// ClearUsageMetrics clears the value of the "usage_metrics" field. -func (m *UsageLogMutation) ClearUsageMetrics() { - m.usage_metrics = nil - m.appendusage_metrics = nil - m.clearedFields[usagelog.FieldUsageMetrics] = struct{}{} -} - -// UsageMetricsCleared returns if the "usage_metrics" field was cleared in this mutation. -func (m *UsageLogMutation) UsageMetricsCleared() bool { - _, ok := m.clearedFields[usagelog.FieldUsageMetrics] - return ok -} - -// ResetUsageMetrics resets all changes to the "usage_metrics" field. -func (m *UsageLogMutation) ResetUsageMetrics() { - m.usage_metrics = nil - m.appendusage_metrics = nil - delete(m.clearedFields, usagelog.FieldUsageMetrics) -} - -// SetUsageCostDetails sets the "usage_cost_details" field. -func (m *UsageLogMutation) SetUsageCostDetails(scd []sdk.UsageCostDetail) { - m.usage_cost_details = &scd - m.appendusage_cost_details = nil -} - -// UsageCostDetails returns the value of the "usage_cost_details" field in the mutation. -func (m *UsageLogMutation) UsageCostDetails() (r []sdk.UsageCostDetail, exists bool) { - v := m.usage_cost_details - if v == nil { - return - } - return *v, true -} - -// OldUsageCostDetails returns the old "usage_cost_details" field's value of the UsageLog entity. -// If the UsageLog object wasn't provided to the builder, the object is fetched from the database. -// An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *UsageLogMutation) OldUsageCostDetails(ctx context.Context) (v []sdk.UsageCostDetail, err error) { - if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldUsageCostDetails is only allowed on UpdateOne operations") - } - if m.id == nil || m.oldValue == nil { - return v, errors.New("OldUsageCostDetails requires an ID field in the mutation") - } - oldValue, err := m.oldValue(ctx) - if err != nil { - return v, fmt.Errorf("querying old value for OldUsageCostDetails: %w", err) - } - return oldValue.UsageCostDetails, nil -} - -// AppendUsageCostDetails adds scd to the "usage_cost_details" field. -func (m *UsageLogMutation) AppendUsageCostDetails(scd []sdk.UsageCostDetail) { - m.appendusage_cost_details = append(m.appendusage_cost_details, scd...) -} - -// AppendedUsageCostDetails returns the list of values that were appended to the "usage_cost_details" field in this mutation. -func (m *UsageLogMutation) AppendedUsageCostDetails() ([]sdk.UsageCostDetail, bool) { - if len(m.appendusage_cost_details) == 0 { - return nil, false - } - return m.appendusage_cost_details, true -} - -// ClearUsageCostDetails clears the value of the "usage_cost_details" field. -func (m *UsageLogMutation) ClearUsageCostDetails() { - m.usage_cost_details = nil - m.appendusage_cost_details = nil - m.clearedFields[usagelog.FieldUsageCostDetails] = struct{}{} -} - -// UsageCostDetailsCleared returns if the "usage_cost_details" field was cleared in this mutation. -func (m *UsageLogMutation) UsageCostDetailsCleared() bool { - _, ok := m.clearedFields[usagelog.FieldUsageCostDetails] - return ok -} - -// ResetUsageCostDetails resets all changes to the "usage_cost_details" field. -func (m *UsageLogMutation) ResetUsageCostDetails() { - m.usage_cost_details = nil - m.appendusage_cost_details = nil - delete(m.clearedFields, usagelog.FieldUsageCostDetails) -} - // SetUsageMetadata sets the "usage_metadata" field. func (m *UsageLogMutation) SetUsageMetadata(value map[string]string) { m.usage_metadata = &value @@ -12982,7 +12569,7 @@ func (m *UsageLogMutation) Type() string { // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *UsageLogMutation) Fields() []string { - fields := make([]string, 0, 41) + fields := make([]string, 0, 34) if m.platform != nil { fields = append(fields, usagelog.FieldPlatform) } @@ -13001,12 +12588,6 @@ func (m *UsageLogMutation) Fields() []string { if m.cache_creation_tokens != nil { fields = append(fields, usagelog.FieldCacheCreationTokens) } - if m.cache_creation_5m_tokens != nil { - fields = append(fields, usagelog.FieldCacheCreation5mTokens) - } - if m.cache_creation_1h_tokens != nil { - fields = append(fields, usagelog.FieldCacheCreation1hTokens) - } if m.reasoning_output_tokens != nil { fields = append(fields, usagelog.FieldReasoningOutputTokens) } @@ -13022,9 +12603,6 @@ func (m *UsageLogMutation) Fields() []string { if m.cache_creation_price != nil { fields = append(fields, usagelog.FieldCacheCreationPrice) } - if m.cache_creation_1h_price != nil { - fields = append(fields, usagelog.FieldCacheCreation1hPrice) - } if m.input_cost != nil { fields = append(fields, usagelog.FieldInputCost) } @@ -13061,9 +12639,6 @@ func (m *UsageLogMutation) Fields() []string { if m.service_tier != nil { fields = append(fields, usagelog.FieldServiceTier) } - if m.image_size != nil { - fields = append(fields, usagelog.FieldImageSize) - } if m.stream != nil { fields = append(fields, usagelog.FieldStream) } @@ -13085,15 +12660,6 @@ func (m *UsageLogMutation) Fields() []string { if m.reasoning_effort != nil { fields = append(fields, usagelog.FieldReasoningEffort) } - if m.usage_attributes != nil { - fields = append(fields, usagelog.FieldUsageAttributes) - } - if m.usage_metrics != nil { - fields = append(fields, usagelog.FieldUsageMetrics) - } - if m.usage_cost_details != nil { - fields = append(fields, usagelog.FieldUsageCostDetails) - } if m.usage_metadata != nil { fields = append(fields, usagelog.FieldUsageMetadata) } @@ -13126,10 +12692,6 @@ func (m *UsageLogMutation) Field(name string) (ent.Value, bool) { return m.CachedInputTokens() case usagelog.FieldCacheCreationTokens: return m.CacheCreationTokens() - case usagelog.FieldCacheCreation5mTokens: - return m.CacheCreation5mTokens() - case usagelog.FieldCacheCreation1hTokens: - return m.CacheCreation1hTokens() case usagelog.FieldReasoningOutputTokens: return m.ReasoningOutputTokens() case usagelog.FieldInputPrice: @@ -13140,8 +12702,6 @@ func (m *UsageLogMutation) Field(name string) (ent.Value, bool) { return m.CachedInputPrice() case usagelog.FieldCacheCreationPrice: return m.CacheCreationPrice() - case usagelog.FieldCacheCreation1hPrice: - return m.CacheCreation1hPrice() case usagelog.FieldInputCost: return m.InputCost() case usagelog.FieldOutputCost: @@ -13166,8 +12726,6 @@ func (m *UsageLogMutation) Field(name string) (ent.Value, bool) { return m.AccountRateMultiplier() case usagelog.FieldServiceTier: return m.ServiceTier() - case usagelog.FieldImageSize: - return m.ImageSize() case usagelog.FieldStream: return m.Stream() case usagelog.FieldDurationMs: @@ -13182,12 +12740,6 @@ func (m *UsageLogMutation) Field(name string) (ent.Value, bool) { return m.Endpoint() case usagelog.FieldReasoningEffort: return m.ReasoningEffort() - case usagelog.FieldUsageAttributes: - return m.UsageAttributes() - case usagelog.FieldUsageMetrics: - return m.UsageMetrics() - case usagelog.FieldUsageCostDetails: - return m.UsageCostDetails() case usagelog.FieldUsageMetadata: return m.UsageMetadata() case usagelog.FieldUserIDSnapshot: @@ -13217,10 +12769,6 @@ func (m *UsageLogMutation) OldField(ctx context.Context, name string) (ent.Value return m.OldCachedInputTokens(ctx) case usagelog.FieldCacheCreationTokens: return m.OldCacheCreationTokens(ctx) - case usagelog.FieldCacheCreation5mTokens: - return m.OldCacheCreation5mTokens(ctx) - case usagelog.FieldCacheCreation1hTokens: - return m.OldCacheCreation1hTokens(ctx) case usagelog.FieldReasoningOutputTokens: return m.OldReasoningOutputTokens(ctx) case usagelog.FieldInputPrice: @@ -13231,8 +12779,6 @@ func (m *UsageLogMutation) OldField(ctx context.Context, name string) (ent.Value return m.OldCachedInputPrice(ctx) case usagelog.FieldCacheCreationPrice: return m.OldCacheCreationPrice(ctx) - case usagelog.FieldCacheCreation1hPrice: - return m.OldCacheCreation1hPrice(ctx) case usagelog.FieldInputCost: return m.OldInputCost(ctx) case usagelog.FieldOutputCost: @@ -13257,8 +12803,6 @@ func (m *UsageLogMutation) OldField(ctx context.Context, name string) (ent.Value return m.OldAccountRateMultiplier(ctx) case usagelog.FieldServiceTier: return m.OldServiceTier(ctx) - case usagelog.FieldImageSize: - return m.OldImageSize(ctx) case usagelog.FieldStream: return m.OldStream(ctx) case usagelog.FieldDurationMs: @@ -13273,12 +12817,6 @@ func (m *UsageLogMutation) OldField(ctx context.Context, name string) (ent.Value return m.OldEndpoint(ctx) case usagelog.FieldReasoningEffort: return m.OldReasoningEffort(ctx) - case usagelog.FieldUsageAttributes: - return m.OldUsageAttributes(ctx) - case usagelog.FieldUsageMetrics: - return m.OldUsageMetrics(ctx) - case usagelog.FieldUsageCostDetails: - return m.OldUsageCostDetails(ctx) case usagelog.FieldUsageMetadata: return m.OldUsageMetadata(ctx) case usagelog.FieldUserIDSnapshot: @@ -13338,20 +12876,6 @@ func (m *UsageLogMutation) SetField(name string, value ent.Value) error { } m.SetCacheCreationTokens(v) return nil - case usagelog.FieldCacheCreation5mTokens: - v, ok := value.(int) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.SetCacheCreation5mTokens(v) - return nil - case usagelog.FieldCacheCreation1hTokens: - v, ok := value.(int) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.SetCacheCreation1hTokens(v) - return nil case usagelog.FieldReasoningOutputTokens: v, ok := value.(int) if !ok { @@ -13387,13 +12911,6 @@ func (m *UsageLogMutation) SetField(name string, value ent.Value) error { } m.SetCacheCreationPrice(v) return nil - case usagelog.FieldCacheCreation1hPrice: - v, ok := value.(float64) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.SetCacheCreation1hPrice(v) - return nil case usagelog.FieldInputCost: v, ok := value.(float64) if !ok { @@ -13478,13 +12995,6 @@ func (m *UsageLogMutation) SetField(name string, value ent.Value) error { } m.SetServiceTier(v) return nil - case usagelog.FieldImageSize: - v, ok := value.(string) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.SetImageSize(v) - return nil case usagelog.FieldStream: v, ok := value.(bool) if !ok { @@ -13534,27 +13044,6 @@ func (m *UsageLogMutation) SetField(name string, value ent.Value) error { } m.SetReasoningEffort(v) return nil - case usagelog.FieldUsageAttributes: - v, ok := value.([]sdk.UsageAttribute) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.SetUsageAttributes(v) - return nil - case usagelog.FieldUsageMetrics: - v, ok := value.([]sdk.UsageMetric) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.SetUsageMetrics(v) - return nil - case usagelog.FieldUsageCostDetails: - v, ok := value.([]sdk.UsageCostDetail) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.SetUsageCostDetails(v) - return nil case usagelog.FieldUsageMetadata: v, ok := value.(map[string]string) if !ok { @@ -13603,12 +13092,6 @@ func (m *UsageLogMutation) AddedFields() []string { if m.addcache_creation_tokens != nil { fields = append(fields, usagelog.FieldCacheCreationTokens) } - if m.addcache_creation_5m_tokens != nil { - fields = append(fields, usagelog.FieldCacheCreation5mTokens) - } - if m.addcache_creation_1h_tokens != nil { - fields = append(fields, usagelog.FieldCacheCreation1hTokens) - } if m.addreasoning_output_tokens != nil { fields = append(fields, usagelog.FieldReasoningOutputTokens) } @@ -13624,9 +13107,6 @@ func (m *UsageLogMutation) AddedFields() []string { if m.addcache_creation_price != nil { fields = append(fields, usagelog.FieldCacheCreationPrice) } - if m.addcache_creation_1h_price != nil { - fields = append(fields, usagelog.FieldCacheCreation1hPrice) - } if m.addinput_cost != nil { fields = append(fields, usagelog.FieldInputCost) } @@ -13685,10 +13165,6 @@ func (m *UsageLogMutation) AddedField(name string) (ent.Value, bool) { return m.AddedCachedInputTokens() case usagelog.FieldCacheCreationTokens: return m.AddedCacheCreationTokens() - case usagelog.FieldCacheCreation5mTokens: - return m.AddedCacheCreation5mTokens() - case usagelog.FieldCacheCreation1hTokens: - return m.AddedCacheCreation1hTokens() case usagelog.FieldReasoningOutputTokens: return m.AddedReasoningOutputTokens() case usagelog.FieldInputPrice: @@ -13699,8 +13175,6 @@ func (m *UsageLogMutation) AddedField(name string) (ent.Value, bool) { return m.AddedCachedInputPrice() case usagelog.FieldCacheCreationPrice: return m.AddedCacheCreationPrice() - case usagelog.FieldCacheCreation1hPrice: - return m.AddedCacheCreation1hPrice() case usagelog.FieldInputCost: return m.AddedInputCost() case usagelog.FieldOutputCost: @@ -13766,20 +13240,6 @@ func (m *UsageLogMutation) AddField(name string, value ent.Value) error { } m.AddCacheCreationTokens(v) return nil - case usagelog.FieldCacheCreation5mTokens: - v, ok := value.(int) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.AddCacheCreation5mTokens(v) - return nil - case usagelog.FieldCacheCreation1hTokens: - v, ok := value.(int) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.AddCacheCreation1hTokens(v) - return nil case usagelog.FieldReasoningOutputTokens: v, ok := value.(int) if !ok { @@ -13815,13 +13275,6 @@ func (m *UsageLogMutation) AddField(name string, value ent.Value) error { } m.AddCacheCreationPrice(v) return nil - case usagelog.FieldCacheCreation1hPrice: - v, ok := value.(float64) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.AddCacheCreation1hPrice(v) - return nil case usagelog.FieldInputCost: v, ok := value.(float64) if !ok { @@ -13928,15 +13381,6 @@ func (m *UsageLogMutation) AddField(name string, value ent.Value) error { // mutation. func (m *UsageLogMutation) ClearedFields() []string { var fields []string - if m.FieldCleared(usagelog.FieldUsageAttributes) { - fields = append(fields, usagelog.FieldUsageAttributes) - } - if m.FieldCleared(usagelog.FieldUsageMetrics) { - fields = append(fields, usagelog.FieldUsageMetrics) - } - if m.FieldCleared(usagelog.FieldUsageCostDetails) { - fields = append(fields, usagelog.FieldUsageCostDetails) - } if m.FieldCleared(usagelog.FieldUsageMetadata) { fields = append(fields, usagelog.FieldUsageMetadata) } @@ -13954,15 +13398,6 @@ func (m *UsageLogMutation) FieldCleared(name string) bool { // error if the field is not defined in the schema. func (m *UsageLogMutation) ClearField(name string) error { switch name { - case usagelog.FieldUsageAttributes: - m.ClearUsageAttributes() - return nil - case usagelog.FieldUsageMetrics: - m.ClearUsageMetrics() - return nil - case usagelog.FieldUsageCostDetails: - m.ClearUsageCostDetails() - return nil case usagelog.FieldUsageMetadata: m.ClearUsageMetadata() return nil @@ -13992,12 +13427,6 @@ func (m *UsageLogMutation) ResetField(name string) error { case usagelog.FieldCacheCreationTokens: m.ResetCacheCreationTokens() return nil - case usagelog.FieldCacheCreation5mTokens: - m.ResetCacheCreation5mTokens() - return nil - case usagelog.FieldCacheCreation1hTokens: - m.ResetCacheCreation1hTokens() - return nil case usagelog.FieldReasoningOutputTokens: m.ResetReasoningOutputTokens() return nil @@ -14013,9 +13442,6 @@ func (m *UsageLogMutation) ResetField(name string) error { case usagelog.FieldCacheCreationPrice: m.ResetCacheCreationPrice() return nil - case usagelog.FieldCacheCreation1hPrice: - m.ResetCacheCreation1hPrice() - return nil case usagelog.FieldInputCost: m.ResetInputCost() return nil @@ -14052,9 +13478,6 @@ func (m *UsageLogMutation) ResetField(name string) error { case usagelog.FieldServiceTier: m.ResetServiceTier() return nil - case usagelog.FieldImageSize: - m.ResetImageSize() - return nil case usagelog.FieldStream: m.ResetStream() return nil @@ -14076,15 +13499,6 @@ func (m *UsageLogMutation) ResetField(name string) error { case usagelog.FieldReasoningEffort: m.ResetReasoningEffort() return nil - case usagelog.FieldUsageAttributes: - m.ResetUsageAttributes() - return nil - case usagelog.FieldUsageMetrics: - m.ResetUsageMetrics() - return nil - case usagelog.FieldUsageCostDetails: - m.ResetUsageCostDetails() - return nil case usagelog.FieldUsageMetadata: m.ResetUsageMetadata() return nil diff --git a/backend/ent/plugin.go b/backend/ent/plugin.go index 631d4e49..69aecdda 100644 --- a/backend/ent/plugin.go +++ b/backend/ent/plugin.go @@ -10,7 +10,7 @@ import ( "entgo.io/ent" "entgo.io/ent/dialect/sql" - "github.com/DouDOU-start/airgate-core/ent/plugin" + "github.com/DevilGenius/airgate-core/ent/plugin" ) // Plugin is the model entity for the Plugin schema. diff --git a/backend/ent/plugin/where.go b/backend/ent/plugin/where.go index 51ddd518..b4c01b42 100644 --- a/backend/ent/plugin/where.go +++ b/backend/ent/plugin/where.go @@ -6,7 +6,7 @@ import ( "time" "entgo.io/ent/dialect/sql" - "github.com/DouDOU-start/airgate-core/ent/predicate" + "github.com/DevilGenius/airgate-core/ent/predicate" ) // ID filters vertices based on their ID field. diff --git a/backend/ent/plugin_create.go b/backend/ent/plugin_create.go index 7ff5315c..d5247516 100644 --- a/backend/ent/plugin_create.go +++ b/backend/ent/plugin_create.go @@ -10,7 +10,7 @@ import ( "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" - "github.com/DouDOU-start/airgate-core/ent/plugin" + "github.com/DevilGenius/airgate-core/ent/plugin" ) // PluginCreate is the builder for creating a Plugin entity. diff --git a/backend/ent/plugin_delete.go b/backend/ent/plugin_delete.go index abbe07f6..2bdbdf7a 100644 --- a/backend/ent/plugin_delete.go +++ b/backend/ent/plugin_delete.go @@ -8,8 +8,8 @@ import ( "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" - "github.com/DouDOU-start/airgate-core/ent/plugin" - "github.com/DouDOU-start/airgate-core/ent/predicate" + "github.com/DevilGenius/airgate-core/ent/plugin" + "github.com/DevilGenius/airgate-core/ent/predicate" ) // PluginDelete is the builder for deleting a Plugin entity. diff --git a/backend/ent/plugin_query.go b/backend/ent/plugin_query.go index b468fa64..bcf6dd51 100644 --- a/backend/ent/plugin_query.go +++ b/backend/ent/plugin_query.go @@ -10,8 +10,8 @@ import ( "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" - "github.com/DouDOU-start/airgate-core/ent/plugin" - "github.com/DouDOU-start/airgate-core/ent/predicate" + "github.com/DevilGenius/airgate-core/ent/plugin" + "github.com/DevilGenius/airgate-core/ent/predicate" ) // PluginQuery is the builder for querying Plugin entities. diff --git a/backend/ent/plugin_update.go b/backend/ent/plugin_update.go index bb4cdf74..f88f51eb 100644 --- a/backend/ent/plugin_update.go +++ b/backend/ent/plugin_update.go @@ -11,8 +11,8 @@ import ( "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" - "github.com/DouDOU-start/airgate-core/ent/plugin" - "github.com/DouDOU-start/airgate-core/ent/predicate" + "github.com/DevilGenius/airgate-core/ent/plugin" + "github.com/DevilGenius/airgate-core/ent/predicate" ) // PluginUpdate is the builder for updating Plugin entities. diff --git a/backend/ent/pluginsource.go b/backend/ent/pluginsource.go index a5b5d0ba..2734e05e 100644 --- a/backend/ent/pluginsource.go +++ b/backend/ent/pluginsource.go @@ -9,7 +9,7 @@ import ( "entgo.io/ent" "entgo.io/ent/dialect/sql" - "github.com/DouDOU-start/airgate-core/ent/pluginsource" + "github.com/DevilGenius/airgate-core/ent/pluginsource" ) // PluginSource is the model entity for the PluginSource schema. diff --git a/backend/ent/pluginsource/where.go b/backend/ent/pluginsource/where.go index 479f64e3..efd14d9c 100644 --- a/backend/ent/pluginsource/where.go +++ b/backend/ent/pluginsource/where.go @@ -6,7 +6,7 @@ import ( "time" "entgo.io/ent/dialect/sql" - "github.com/DouDOU-start/airgate-core/ent/predicate" + "github.com/DevilGenius/airgate-core/ent/predicate" ) // ID filters vertices based on their ID field. diff --git a/backend/ent/pluginsource_create.go b/backend/ent/pluginsource_create.go index 5c4cab74..79aba6cf 100644 --- a/backend/ent/pluginsource_create.go +++ b/backend/ent/pluginsource_create.go @@ -10,7 +10,7 @@ import ( "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" - "github.com/DouDOU-start/airgate-core/ent/pluginsource" + "github.com/DevilGenius/airgate-core/ent/pluginsource" ) // PluginSourceCreate is the builder for creating a PluginSource entity. diff --git a/backend/ent/pluginsource_delete.go b/backend/ent/pluginsource_delete.go index 45e98bf1..6c80b5a0 100644 --- a/backend/ent/pluginsource_delete.go +++ b/backend/ent/pluginsource_delete.go @@ -8,8 +8,8 @@ import ( "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" - "github.com/DouDOU-start/airgate-core/ent/pluginsource" - "github.com/DouDOU-start/airgate-core/ent/predicate" + "github.com/DevilGenius/airgate-core/ent/pluginsource" + "github.com/DevilGenius/airgate-core/ent/predicate" ) // PluginSourceDelete is the builder for deleting a PluginSource entity. diff --git a/backend/ent/pluginsource_query.go b/backend/ent/pluginsource_query.go index 75a5a510..8b1fcab0 100644 --- a/backend/ent/pluginsource_query.go +++ b/backend/ent/pluginsource_query.go @@ -10,8 +10,8 @@ import ( "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" - "github.com/DouDOU-start/airgate-core/ent/pluginsource" - "github.com/DouDOU-start/airgate-core/ent/predicate" + "github.com/DevilGenius/airgate-core/ent/pluginsource" + "github.com/DevilGenius/airgate-core/ent/predicate" ) // PluginSourceQuery is the builder for querying PluginSource entities. diff --git a/backend/ent/pluginsource_update.go b/backend/ent/pluginsource_update.go index 9c61c4d7..ff3e9051 100644 --- a/backend/ent/pluginsource_update.go +++ b/backend/ent/pluginsource_update.go @@ -11,8 +11,8 @@ import ( "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" - "github.com/DouDOU-start/airgate-core/ent/pluginsource" - "github.com/DouDOU-start/airgate-core/ent/predicate" + "github.com/DevilGenius/airgate-core/ent/pluginsource" + "github.com/DevilGenius/airgate-core/ent/predicate" ) // PluginSourceUpdate is the builder for updating PluginSource entities. diff --git a/backend/ent/proxy.go b/backend/ent/proxy.go index 37bfae09..2233948e 100644 --- a/backend/ent/proxy.go +++ b/backend/ent/proxy.go @@ -9,7 +9,7 @@ import ( "entgo.io/ent" "entgo.io/ent/dialect/sql" - "github.com/DouDOU-start/airgate-core/ent/proxy" + "github.com/DevilGenius/airgate-core/ent/proxy" ) // Proxy is the model entity for the Proxy schema. diff --git a/backend/ent/proxy/where.go b/backend/ent/proxy/where.go index 1a1b9907..a829f08f 100644 --- a/backend/ent/proxy/where.go +++ b/backend/ent/proxy/where.go @@ -7,7 +7,7 @@ import ( "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" - "github.com/DouDOU-start/airgate-core/ent/predicate" + "github.com/DevilGenius/airgate-core/ent/predicate" ) // ID filters vertices based on their ID field. diff --git a/backend/ent/proxy_create.go b/backend/ent/proxy_create.go index 20d68bc0..907cbc95 100644 --- a/backend/ent/proxy_create.go +++ b/backend/ent/proxy_create.go @@ -10,8 +10,8 @@ import ( "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" - "github.com/DouDOU-start/airgate-core/ent/account" - "github.com/DouDOU-start/airgate-core/ent/proxy" + "github.com/DevilGenius/airgate-core/ent/account" + "github.com/DevilGenius/airgate-core/ent/proxy" ) // ProxyCreate is the builder for creating a Proxy entity. diff --git a/backend/ent/proxy_delete.go b/backend/ent/proxy_delete.go index af8932d6..1e1dce10 100644 --- a/backend/ent/proxy_delete.go +++ b/backend/ent/proxy_delete.go @@ -8,8 +8,8 @@ import ( "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" - "github.com/DouDOU-start/airgate-core/ent/predicate" - "github.com/DouDOU-start/airgate-core/ent/proxy" + "github.com/DevilGenius/airgate-core/ent/predicate" + "github.com/DevilGenius/airgate-core/ent/proxy" ) // ProxyDelete is the builder for deleting a Proxy entity. diff --git a/backend/ent/proxy_query.go b/backend/ent/proxy_query.go index 081b79f4..47ce8375 100644 --- a/backend/ent/proxy_query.go +++ b/backend/ent/proxy_query.go @@ -11,9 +11,9 @@ import ( "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" - "github.com/DouDOU-start/airgate-core/ent/account" - "github.com/DouDOU-start/airgate-core/ent/predicate" - "github.com/DouDOU-start/airgate-core/ent/proxy" + "github.com/DevilGenius/airgate-core/ent/account" + "github.com/DevilGenius/airgate-core/ent/predicate" + "github.com/DevilGenius/airgate-core/ent/proxy" ) // ProxyQuery is the builder for querying Proxy entities. diff --git a/backend/ent/proxy_update.go b/backend/ent/proxy_update.go index 713393e2..7366c602 100644 --- a/backend/ent/proxy_update.go +++ b/backend/ent/proxy_update.go @@ -11,9 +11,9 @@ import ( "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" - "github.com/DouDOU-start/airgate-core/ent/account" - "github.com/DouDOU-start/airgate-core/ent/predicate" - "github.com/DouDOU-start/airgate-core/ent/proxy" + "github.com/DevilGenius/airgate-core/ent/account" + "github.com/DevilGenius/airgate-core/ent/predicate" + "github.com/DevilGenius/airgate-core/ent/proxy" ) // ProxyUpdate is the builder for updating Proxy entities. diff --git a/backend/ent/runtime.go b/backend/ent/runtime.go index 1d88857f..73780356 100644 --- a/backend/ent/runtime.go +++ b/backend/ent/runtime.go @@ -5,19 +5,19 @@ package ent import ( "time" - "github.com/DouDOU-start/airgate-core/ent/account" - "github.com/DouDOU-start/airgate-core/ent/apikey" - "github.com/DouDOU-start/airgate-core/ent/balancelog" - "github.com/DouDOU-start/airgate-core/ent/group" - "github.com/DouDOU-start/airgate-core/ent/plugin" - "github.com/DouDOU-start/airgate-core/ent/pluginsource" - "github.com/DouDOU-start/airgate-core/ent/proxy" - "github.com/DouDOU-start/airgate-core/ent/schema" - "github.com/DouDOU-start/airgate-core/ent/setting" - "github.com/DouDOU-start/airgate-core/ent/task" - "github.com/DouDOU-start/airgate-core/ent/usagelog" - "github.com/DouDOU-start/airgate-core/ent/user" - "github.com/DouDOU-start/airgate-core/ent/usersubscription" + "github.com/DevilGenius/airgate-core/ent/account" + "github.com/DevilGenius/airgate-core/ent/apikey" + "github.com/DevilGenius/airgate-core/ent/balancelog" + "github.com/DevilGenius/airgate-core/ent/group" + "github.com/DevilGenius/airgate-core/ent/plugin" + "github.com/DevilGenius/airgate-core/ent/pluginsource" + "github.com/DevilGenius/airgate-core/ent/proxy" + "github.com/DevilGenius/airgate-core/ent/schema" + "github.com/DevilGenius/airgate-core/ent/setting" + "github.com/DevilGenius/airgate-core/ent/task" + "github.com/DevilGenius/airgate-core/ent/usagelog" + "github.com/DevilGenius/airgate-core/ent/user" + "github.com/DevilGenius/airgate-core/ent/usersubscription" ) // The init function reads all schema descriptors with runtime code @@ -424,128 +424,112 @@ func init() { usagelogDescCacheCreationTokens := usagelogFields[5].Descriptor() // usagelog.DefaultCacheCreationTokens holds the default value on creation for the cache_creation_tokens field. usagelog.DefaultCacheCreationTokens = usagelogDescCacheCreationTokens.Default.(int) - // usagelogDescCacheCreation5mTokens is the schema descriptor for cache_creation_5m_tokens field. - usagelogDescCacheCreation5mTokens := usagelogFields[6].Descriptor() - // usagelog.DefaultCacheCreation5mTokens holds the default value on creation for the cache_creation_5m_tokens field. - usagelog.DefaultCacheCreation5mTokens = usagelogDescCacheCreation5mTokens.Default.(int) - // usagelogDescCacheCreation1hTokens is the schema descriptor for cache_creation_1h_tokens field. - usagelogDescCacheCreation1hTokens := usagelogFields[7].Descriptor() - // usagelog.DefaultCacheCreation1hTokens holds the default value on creation for the cache_creation_1h_tokens field. - usagelog.DefaultCacheCreation1hTokens = usagelogDescCacheCreation1hTokens.Default.(int) // usagelogDescReasoningOutputTokens is the schema descriptor for reasoning_output_tokens field. - usagelogDescReasoningOutputTokens := usagelogFields[8].Descriptor() + usagelogDescReasoningOutputTokens := usagelogFields[6].Descriptor() // usagelog.DefaultReasoningOutputTokens holds the default value on creation for the reasoning_output_tokens field. usagelog.DefaultReasoningOutputTokens = usagelogDescReasoningOutputTokens.Default.(int) // usagelogDescInputPrice is the schema descriptor for input_price field. - usagelogDescInputPrice := usagelogFields[9].Descriptor() + usagelogDescInputPrice := usagelogFields[7].Descriptor() // usagelog.DefaultInputPrice holds the default value on creation for the input_price field. usagelog.DefaultInputPrice = usagelogDescInputPrice.Default.(float64) // usagelogDescOutputPrice is the schema descriptor for output_price field. - usagelogDescOutputPrice := usagelogFields[10].Descriptor() + usagelogDescOutputPrice := usagelogFields[8].Descriptor() // usagelog.DefaultOutputPrice holds the default value on creation for the output_price field. usagelog.DefaultOutputPrice = usagelogDescOutputPrice.Default.(float64) // usagelogDescCachedInputPrice is the schema descriptor for cached_input_price field. - usagelogDescCachedInputPrice := usagelogFields[11].Descriptor() + usagelogDescCachedInputPrice := usagelogFields[9].Descriptor() // usagelog.DefaultCachedInputPrice holds the default value on creation for the cached_input_price field. usagelog.DefaultCachedInputPrice = usagelogDescCachedInputPrice.Default.(float64) // usagelogDescCacheCreationPrice is the schema descriptor for cache_creation_price field. - usagelogDescCacheCreationPrice := usagelogFields[12].Descriptor() + usagelogDescCacheCreationPrice := usagelogFields[10].Descriptor() // usagelog.DefaultCacheCreationPrice holds the default value on creation for the cache_creation_price field. usagelog.DefaultCacheCreationPrice = usagelogDescCacheCreationPrice.Default.(float64) - // usagelogDescCacheCreation1hPrice is the schema descriptor for cache_creation_1h_price field. - usagelogDescCacheCreation1hPrice := usagelogFields[13].Descriptor() - // usagelog.DefaultCacheCreation1hPrice holds the default value on creation for the cache_creation_1h_price field. - usagelog.DefaultCacheCreation1hPrice = usagelogDescCacheCreation1hPrice.Default.(float64) // usagelogDescInputCost is the schema descriptor for input_cost field. - usagelogDescInputCost := usagelogFields[14].Descriptor() + usagelogDescInputCost := usagelogFields[11].Descriptor() // usagelog.DefaultInputCost holds the default value on creation for the input_cost field. usagelog.DefaultInputCost = usagelogDescInputCost.Default.(float64) // usagelogDescOutputCost is the schema descriptor for output_cost field. - usagelogDescOutputCost := usagelogFields[15].Descriptor() + usagelogDescOutputCost := usagelogFields[12].Descriptor() // usagelog.DefaultOutputCost holds the default value on creation for the output_cost field. usagelog.DefaultOutputCost = usagelogDescOutputCost.Default.(float64) // usagelogDescCachedInputCost is the schema descriptor for cached_input_cost field. - usagelogDescCachedInputCost := usagelogFields[16].Descriptor() + usagelogDescCachedInputCost := usagelogFields[13].Descriptor() // usagelog.DefaultCachedInputCost holds the default value on creation for the cached_input_cost field. usagelog.DefaultCachedInputCost = usagelogDescCachedInputCost.Default.(float64) // usagelogDescCacheCreationCost is the schema descriptor for cache_creation_cost field. - usagelogDescCacheCreationCost := usagelogFields[17].Descriptor() + usagelogDescCacheCreationCost := usagelogFields[14].Descriptor() // usagelog.DefaultCacheCreationCost holds the default value on creation for the cache_creation_cost field. usagelog.DefaultCacheCreationCost = usagelogDescCacheCreationCost.Default.(float64) // usagelogDescTotalCost is the schema descriptor for total_cost field. - usagelogDescTotalCost := usagelogFields[18].Descriptor() + usagelogDescTotalCost := usagelogFields[15].Descriptor() // usagelog.DefaultTotalCost holds the default value on creation for the total_cost field. usagelog.DefaultTotalCost = usagelogDescTotalCost.Default.(float64) // usagelogDescActualCost is the schema descriptor for actual_cost field. - usagelogDescActualCost := usagelogFields[19].Descriptor() + usagelogDescActualCost := usagelogFields[16].Descriptor() // usagelog.DefaultActualCost holds the default value on creation for the actual_cost field. usagelog.DefaultActualCost = usagelogDescActualCost.Default.(float64) // usagelogDescBilledCost is the schema descriptor for billed_cost field. - usagelogDescBilledCost := usagelogFields[20].Descriptor() + usagelogDescBilledCost := usagelogFields[17].Descriptor() // usagelog.DefaultBilledCost holds the default value on creation for the billed_cost field. usagelog.DefaultBilledCost = usagelogDescBilledCost.Default.(float64) // usagelogDescAccountCost is the schema descriptor for account_cost field. - usagelogDescAccountCost := usagelogFields[21].Descriptor() + usagelogDescAccountCost := usagelogFields[18].Descriptor() // usagelog.DefaultAccountCost holds the default value on creation for the account_cost field. usagelog.DefaultAccountCost = usagelogDescAccountCost.Default.(float64) // usagelogDescRateMultiplier is the schema descriptor for rate_multiplier field. - usagelogDescRateMultiplier := usagelogFields[22].Descriptor() + usagelogDescRateMultiplier := usagelogFields[19].Descriptor() // usagelog.DefaultRateMultiplier holds the default value on creation for the rate_multiplier field. usagelog.DefaultRateMultiplier = usagelogDescRateMultiplier.Default.(float64) // usagelogDescSellRate is the schema descriptor for sell_rate field. - usagelogDescSellRate := usagelogFields[23].Descriptor() + usagelogDescSellRate := usagelogFields[20].Descriptor() // usagelog.DefaultSellRate holds the default value on creation for the sell_rate field. usagelog.DefaultSellRate = usagelogDescSellRate.Default.(float64) // usagelogDescAccountRateMultiplier is the schema descriptor for account_rate_multiplier field. - usagelogDescAccountRateMultiplier := usagelogFields[24].Descriptor() + usagelogDescAccountRateMultiplier := usagelogFields[21].Descriptor() // usagelog.DefaultAccountRateMultiplier holds the default value on creation for the account_rate_multiplier field. usagelog.DefaultAccountRateMultiplier = usagelogDescAccountRateMultiplier.Default.(float64) // usagelogDescServiceTier is the schema descriptor for service_tier field. - usagelogDescServiceTier := usagelogFields[25].Descriptor() + usagelogDescServiceTier := usagelogFields[22].Descriptor() // usagelog.DefaultServiceTier holds the default value on creation for the service_tier field. usagelog.DefaultServiceTier = usagelogDescServiceTier.Default.(string) - // usagelogDescImageSize is the schema descriptor for image_size field. - usagelogDescImageSize := usagelogFields[26].Descriptor() - // usagelog.DefaultImageSize holds the default value on creation for the image_size field. - usagelog.DefaultImageSize = usagelogDescImageSize.Default.(string) // usagelogDescStream is the schema descriptor for stream field. - usagelogDescStream := usagelogFields[27].Descriptor() + usagelogDescStream := usagelogFields[23].Descriptor() // usagelog.DefaultStream holds the default value on creation for the stream field. usagelog.DefaultStream = usagelogDescStream.Default.(bool) // usagelogDescDurationMs is the schema descriptor for duration_ms field. - usagelogDescDurationMs := usagelogFields[28].Descriptor() + usagelogDescDurationMs := usagelogFields[24].Descriptor() // usagelog.DefaultDurationMs holds the default value on creation for the duration_ms field. usagelog.DefaultDurationMs = usagelogDescDurationMs.Default.(int64) // usagelogDescFirstTokenMs is the schema descriptor for first_token_ms field. - usagelogDescFirstTokenMs := usagelogFields[29].Descriptor() + usagelogDescFirstTokenMs := usagelogFields[25].Descriptor() // usagelog.DefaultFirstTokenMs holds the default value on creation for the first_token_ms field. usagelog.DefaultFirstTokenMs = usagelogDescFirstTokenMs.Default.(int64) // usagelogDescUserAgent is the schema descriptor for user_agent field. - usagelogDescUserAgent := usagelogFields[30].Descriptor() + usagelogDescUserAgent := usagelogFields[26].Descriptor() // usagelog.DefaultUserAgent holds the default value on creation for the user_agent field. usagelog.DefaultUserAgent = usagelogDescUserAgent.Default.(string) // usagelogDescIPAddress is the schema descriptor for ip_address field. - usagelogDescIPAddress := usagelogFields[31].Descriptor() + usagelogDescIPAddress := usagelogFields[27].Descriptor() // usagelog.DefaultIPAddress holds the default value on creation for the ip_address field. usagelog.DefaultIPAddress = usagelogDescIPAddress.Default.(string) // usagelogDescEndpoint is the schema descriptor for endpoint field. - usagelogDescEndpoint := usagelogFields[32].Descriptor() + usagelogDescEndpoint := usagelogFields[28].Descriptor() // usagelog.DefaultEndpoint holds the default value on creation for the endpoint field. usagelog.DefaultEndpoint = usagelogDescEndpoint.Default.(string) // usagelogDescReasoningEffort is the schema descriptor for reasoning_effort field. - usagelogDescReasoningEffort := usagelogFields[33].Descriptor() + usagelogDescReasoningEffort := usagelogFields[29].Descriptor() // usagelog.DefaultReasoningEffort holds the default value on creation for the reasoning_effort field. usagelog.DefaultReasoningEffort = usagelogDescReasoningEffort.Default.(string) // usagelogDescUserIDSnapshot is the schema descriptor for user_id_snapshot field. - usagelogDescUserIDSnapshot := usagelogFields[38].Descriptor() + usagelogDescUserIDSnapshot := usagelogFields[31].Descriptor() // usagelog.DefaultUserIDSnapshot holds the default value on creation for the user_id_snapshot field. usagelog.DefaultUserIDSnapshot = usagelogDescUserIDSnapshot.Default.(int) // usagelogDescUserEmailSnapshot is the schema descriptor for user_email_snapshot field. - usagelogDescUserEmailSnapshot := usagelogFields[39].Descriptor() + usagelogDescUserEmailSnapshot := usagelogFields[32].Descriptor() // usagelog.DefaultUserEmailSnapshot holds the default value on creation for the user_email_snapshot field. usagelog.DefaultUserEmailSnapshot = usagelogDescUserEmailSnapshot.Default.(string) // usagelogDescCreatedAt is the schema descriptor for created_at field. - usagelogDescCreatedAt := usagelogFields[40].Descriptor() + usagelogDescCreatedAt := usagelogFields[33].Descriptor() // usagelog.DefaultCreatedAt holds the default value on creation for the created_at field. usagelog.DefaultCreatedAt = usagelogDescCreatedAt.Default.(func() time.Time) userFields := schema.User{}.Fields() diff --git a/backend/ent/runtime/runtime.go b/backend/ent/runtime/runtime.go index a2c8005a..10413e9c 100644 --- a/backend/ent/runtime/runtime.go +++ b/backend/ent/runtime/runtime.go @@ -2,7 +2,7 @@ package runtime -// The schema-stitching logic is generated in github.com/DouDOU-start/airgate-core/ent/runtime.go +// The schema-stitching logic is generated in github.com/DevilGenius/airgate-core/ent/runtime.go const ( Version = "v0.13.1" // Version of ent codegen. diff --git a/backend/ent/schema/usagelog.go b/backend/ent/schema/usagelog.go index d8aa7e1a..882e7fdc 100644 --- a/backend/ent/schema/usagelog.go +++ b/backend/ent/schema/usagelog.go @@ -6,8 +6,6 @@ import ( "entgo.io/ent/schema/edge" "entgo.io/ent/schema/field" "entgo.io/ent/schema/index" - - sdk "github.com/DouDOU-start/airgate-sdk/sdkgo" ) // UsageLog 使用日志(只追加) @@ -23,8 +21,6 @@ func (UsageLog) Fields() []ent.Field { field.Int("output_tokens").Default(0), field.Int("cached_input_tokens").Default(0), field.Int("cache_creation_tokens").Default(0), - field.Int("cache_creation_5m_tokens").Default(0), - field.Int("cache_creation_1h_tokens").Default(0), field.Int("reasoning_output_tokens").Default(0), field.Float("input_price").Default(0). SchemaType(map[string]string{dialect.Postgres: "decimal(20,8)"}), @@ -34,8 +30,6 @@ func (UsageLog) Fields() []ent.Field { SchemaType(map[string]string{dialect.Postgres: "decimal(20,8)"}), field.Float("cache_creation_price").Default(0). SchemaType(map[string]string{dialect.Postgres: "decimal(20,8)"}), - field.Float("cache_creation_1h_price").Default(0). - SchemaType(map[string]string{dialect.Postgres: "decimal(20,8)"}), field.Float("input_cost").Default(0). SchemaType(map[string]string{dialect.Postgres: "decimal(20,8)"}), field.Float("output_cost").Default(0). @@ -62,9 +56,6 @@ func (UsageLog) Fields() []ent.Field { field.Float("account_rate_multiplier").Default(1.0). Comment("快照:本次请求生效的 account_rate"), field.String("service_tier").Default(""), - // image_size 图像生成请求实际出图尺寸("WxH")。非图像请求留空。 - // admin 后台展示用,让用户能直观看出"为什么这次图扣了 0.40"——按 1K/2K/4K 分档计费。 - field.String("image_size").Default(""), field.Bool("stream").Default(false), field.Int64("duration_ms").Default(0), field.Int64("first_token_ms").Default(0), @@ -74,10 +65,6 @@ func (UsageLog) Fields() []ent.Field { field.String("endpoint").Default(""), // 推理强度档位。 field.String("reasoning_effort").Default(""), - // SDK 原始用量明细:Core 只保存和透出,具体展示由插件前端 slot 负责。 - field.JSON("usage_attributes", []sdk.UsageAttribute{}).Optional(), - field.JSON("usage_metrics", []sdk.UsageMetric{}).Optional(), - field.JSON("usage_cost_details", []sdk.UsageCostDetail{}).Optional(), field.JSON("usage_metadata", map[string]string{}).Optional(), field.Int("user_id_snapshot").Default(0). Comment("用户 ID 快照。用户硬删除后保留历史使用记录与计费归属。"), @@ -98,6 +85,8 @@ func (UsageLog) Edges() []ent.Edge { func (UsageLog) Indexes() []ent.Index { return []ent.Index{ + index.Fields("model"). + StorageKey("usage_log_model"), index.Fields("created_at"). StorageKey("usage_log_created_at"), index.Fields("platform", "created_at"). diff --git a/backend/ent/setting.go b/backend/ent/setting.go index fd18267f..921e8a07 100644 --- a/backend/ent/setting.go +++ b/backend/ent/setting.go @@ -9,7 +9,7 @@ import ( "entgo.io/ent" "entgo.io/ent/dialect/sql" - "github.com/DouDOU-start/airgate-core/ent/setting" + "github.com/DevilGenius/airgate-core/ent/setting" ) // Setting is the model entity for the Setting schema. diff --git a/backend/ent/setting/where.go b/backend/ent/setting/where.go index 3b14e418..023a083f 100644 --- a/backend/ent/setting/where.go +++ b/backend/ent/setting/where.go @@ -6,7 +6,7 @@ import ( "time" "entgo.io/ent/dialect/sql" - "github.com/DouDOU-start/airgate-core/ent/predicate" + "github.com/DevilGenius/airgate-core/ent/predicate" ) // ID filters vertices based on their ID field. diff --git a/backend/ent/setting_create.go b/backend/ent/setting_create.go index aeab6211..b71aa861 100644 --- a/backend/ent/setting_create.go +++ b/backend/ent/setting_create.go @@ -10,7 +10,7 @@ import ( "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" - "github.com/DouDOU-start/airgate-core/ent/setting" + "github.com/DevilGenius/airgate-core/ent/setting" ) // SettingCreate is the builder for creating a Setting entity. diff --git a/backend/ent/setting_delete.go b/backend/ent/setting_delete.go index efcbd199..0d6bd649 100644 --- a/backend/ent/setting_delete.go +++ b/backend/ent/setting_delete.go @@ -8,8 +8,8 @@ import ( "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" - "github.com/DouDOU-start/airgate-core/ent/predicate" - "github.com/DouDOU-start/airgate-core/ent/setting" + "github.com/DevilGenius/airgate-core/ent/predicate" + "github.com/DevilGenius/airgate-core/ent/setting" ) // SettingDelete is the builder for deleting a Setting entity. diff --git a/backend/ent/setting_query.go b/backend/ent/setting_query.go index 74d77728..434196cb 100644 --- a/backend/ent/setting_query.go +++ b/backend/ent/setting_query.go @@ -10,8 +10,8 @@ import ( "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" - "github.com/DouDOU-start/airgate-core/ent/predicate" - "github.com/DouDOU-start/airgate-core/ent/setting" + "github.com/DevilGenius/airgate-core/ent/predicate" + "github.com/DevilGenius/airgate-core/ent/setting" ) // SettingQuery is the builder for querying Setting entities. diff --git a/backend/ent/setting_update.go b/backend/ent/setting_update.go index 33cfa277..b0f08fb3 100644 --- a/backend/ent/setting_update.go +++ b/backend/ent/setting_update.go @@ -11,8 +11,8 @@ import ( "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" - "github.com/DouDOU-start/airgate-core/ent/predicate" - "github.com/DouDOU-start/airgate-core/ent/setting" + "github.com/DevilGenius/airgate-core/ent/predicate" + "github.com/DevilGenius/airgate-core/ent/setting" ) // SettingUpdate is the builder for updating Setting entities. diff --git a/backend/ent/task.go b/backend/ent/task.go index 31164bf9..f5dc659f 100644 --- a/backend/ent/task.go +++ b/backend/ent/task.go @@ -10,7 +10,7 @@ import ( "entgo.io/ent" "entgo.io/ent/dialect/sql" - "github.com/DouDOU-start/airgate-core/ent/task" + "github.com/DevilGenius/airgate-core/ent/task" ) // Task is the model entity for the Task schema. diff --git a/backend/ent/task/where.go b/backend/ent/task/where.go index 7579df3e..21b14459 100644 --- a/backend/ent/task/where.go +++ b/backend/ent/task/where.go @@ -6,7 +6,7 @@ import ( "time" "entgo.io/ent/dialect/sql" - "github.com/DouDOU-start/airgate-core/ent/predicate" + "github.com/DevilGenius/airgate-core/ent/predicate" ) // ID filters vertices based on their ID field. diff --git a/backend/ent/task_create.go b/backend/ent/task_create.go index 60809b0a..a7b8675e 100644 --- a/backend/ent/task_create.go +++ b/backend/ent/task_create.go @@ -10,7 +10,7 @@ import ( "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" - "github.com/DouDOU-start/airgate-core/ent/task" + "github.com/DevilGenius/airgate-core/ent/task" ) // TaskCreate is the builder for creating a Task entity. diff --git a/backend/ent/task_delete.go b/backend/ent/task_delete.go index 01528815..3a79ff20 100644 --- a/backend/ent/task_delete.go +++ b/backend/ent/task_delete.go @@ -8,8 +8,8 @@ import ( "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" - "github.com/DouDOU-start/airgate-core/ent/predicate" - "github.com/DouDOU-start/airgate-core/ent/task" + "github.com/DevilGenius/airgate-core/ent/predicate" + "github.com/DevilGenius/airgate-core/ent/task" ) // TaskDelete is the builder for deleting a Task entity. diff --git a/backend/ent/task_query.go b/backend/ent/task_query.go index f635d5d7..8f9aa1a7 100644 --- a/backend/ent/task_query.go +++ b/backend/ent/task_query.go @@ -10,8 +10,8 @@ import ( "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" - "github.com/DouDOU-start/airgate-core/ent/predicate" - "github.com/DouDOU-start/airgate-core/ent/task" + "github.com/DevilGenius/airgate-core/ent/predicate" + "github.com/DevilGenius/airgate-core/ent/task" ) // TaskQuery is the builder for querying Task entities. diff --git a/backend/ent/task_update.go b/backend/ent/task_update.go index 7319a1f5..20fc5b66 100644 --- a/backend/ent/task_update.go +++ b/backend/ent/task_update.go @@ -11,8 +11,8 @@ import ( "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" - "github.com/DouDOU-start/airgate-core/ent/predicate" - "github.com/DouDOU-start/airgate-core/ent/task" + "github.com/DevilGenius/airgate-core/ent/predicate" + "github.com/DevilGenius/airgate-core/ent/task" ) // TaskUpdate is the builder for updating Task entities. diff --git a/backend/ent/usagelog.go b/backend/ent/usagelog.go index 938fb4ac..59cd5169 100644 --- a/backend/ent/usagelog.go +++ b/backend/ent/usagelog.go @@ -10,12 +10,11 @@ import ( "entgo.io/ent" "entgo.io/ent/dialect/sql" - "github.com/DouDOU-start/airgate-core/ent/account" - "github.com/DouDOU-start/airgate-core/ent/apikey" - "github.com/DouDOU-start/airgate-core/ent/group" - "github.com/DouDOU-start/airgate-core/ent/usagelog" - "github.com/DouDOU-start/airgate-core/ent/user" - sdk "github.com/DouDOU-start/airgate-sdk/sdkgo" + "github.com/DevilGenius/airgate-core/ent/account" + "github.com/DevilGenius/airgate-core/ent/apikey" + "github.com/DevilGenius/airgate-core/ent/group" + "github.com/DevilGenius/airgate-core/ent/usagelog" + "github.com/DevilGenius/airgate-core/ent/user" ) // UsageLog is the model entity for the UsageLog schema. @@ -35,10 +34,6 @@ type UsageLog struct { CachedInputTokens int `json:"cached_input_tokens,omitempty"` // CacheCreationTokens holds the value of the "cache_creation_tokens" field. CacheCreationTokens int `json:"cache_creation_tokens,omitempty"` - // CacheCreation5mTokens holds the value of the "cache_creation_5m_tokens" field. - CacheCreation5mTokens int `json:"cache_creation_5m_tokens,omitempty"` - // CacheCreation1hTokens holds the value of the "cache_creation_1h_tokens" field. - CacheCreation1hTokens int `json:"cache_creation_1h_tokens,omitempty"` // ReasoningOutputTokens holds the value of the "reasoning_output_tokens" field. ReasoningOutputTokens int `json:"reasoning_output_tokens,omitempty"` // InputPrice holds the value of the "input_price" field. @@ -49,8 +44,6 @@ type UsageLog struct { CachedInputPrice float64 `json:"cached_input_price,omitempty"` // CacheCreationPrice holds the value of the "cache_creation_price" field. CacheCreationPrice float64 `json:"cache_creation_price,omitempty"` - // CacheCreation1hPrice holds the value of the "cache_creation_1h_price" field. - CacheCreation1hPrice float64 `json:"cache_creation_1h_price,omitempty"` // InputCost holds the value of the "input_cost" field. InputCost float64 `json:"input_cost,omitempty"` // OutputCost holds the value of the "output_cost" field. @@ -75,8 +68,6 @@ type UsageLog struct { AccountRateMultiplier float64 `json:"account_rate_multiplier,omitempty"` // ServiceTier holds the value of the "service_tier" field. ServiceTier string `json:"service_tier,omitempty"` - // ImageSize holds the value of the "image_size" field. - ImageSize string `json:"image_size,omitempty"` // Stream holds the value of the "stream" field. Stream bool `json:"stream,omitempty"` // DurationMs holds the value of the "duration_ms" field. @@ -91,12 +82,6 @@ type UsageLog struct { Endpoint string `json:"endpoint,omitempty"` // ReasoningEffort holds the value of the "reasoning_effort" field. ReasoningEffort string `json:"reasoning_effort,omitempty"` - // UsageAttributes holds the value of the "usage_attributes" field. - UsageAttributes []sdk.UsageAttribute `json:"usage_attributes,omitempty"` - // UsageMetrics holds the value of the "usage_metrics" field. - UsageMetrics []sdk.UsageMetric `json:"usage_metrics,omitempty"` - // UsageCostDetails holds the value of the "usage_cost_details" field. - UsageCostDetails []sdk.UsageCostDetail `json:"usage_cost_details,omitempty"` // UsageMetadata holds the value of the "usage_metadata" field. UsageMetadata map[string]string `json:"usage_metadata,omitempty"` // 用户 ID 快照。用户硬删除后保留历史使用记录与计费归属。 @@ -179,15 +164,15 @@ func (*UsageLog) scanValues(columns []string) ([]any, error) { values := make([]any, len(columns)) for i := range columns { switch columns[i] { - case usagelog.FieldUsageAttributes, usagelog.FieldUsageMetrics, usagelog.FieldUsageCostDetails, usagelog.FieldUsageMetadata: + case usagelog.FieldUsageMetadata: values[i] = new([]byte) case usagelog.FieldStream: values[i] = new(sql.NullBool) - case usagelog.FieldInputPrice, usagelog.FieldOutputPrice, usagelog.FieldCachedInputPrice, usagelog.FieldCacheCreationPrice, usagelog.FieldCacheCreation1hPrice, usagelog.FieldInputCost, usagelog.FieldOutputCost, usagelog.FieldCachedInputCost, usagelog.FieldCacheCreationCost, usagelog.FieldTotalCost, usagelog.FieldActualCost, usagelog.FieldBilledCost, usagelog.FieldAccountCost, usagelog.FieldRateMultiplier, usagelog.FieldSellRate, usagelog.FieldAccountRateMultiplier: + case usagelog.FieldInputPrice, usagelog.FieldOutputPrice, usagelog.FieldCachedInputPrice, usagelog.FieldCacheCreationPrice, usagelog.FieldInputCost, usagelog.FieldOutputCost, usagelog.FieldCachedInputCost, usagelog.FieldCacheCreationCost, usagelog.FieldTotalCost, usagelog.FieldActualCost, usagelog.FieldBilledCost, usagelog.FieldAccountCost, usagelog.FieldRateMultiplier, usagelog.FieldSellRate, usagelog.FieldAccountRateMultiplier: values[i] = new(sql.NullFloat64) - case usagelog.FieldID, usagelog.FieldInputTokens, usagelog.FieldOutputTokens, usagelog.FieldCachedInputTokens, usagelog.FieldCacheCreationTokens, usagelog.FieldCacheCreation5mTokens, usagelog.FieldCacheCreation1hTokens, usagelog.FieldReasoningOutputTokens, usagelog.FieldDurationMs, usagelog.FieldFirstTokenMs, usagelog.FieldUserIDSnapshot: + case usagelog.FieldID, usagelog.FieldInputTokens, usagelog.FieldOutputTokens, usagelog.FieldCachedInputTokens, usagelog.FieldCacheCreationTokens, usagelog.FieldReasoningOutputTokens, usagelog.FieldDurationMs, usagelog.FieldFirstTokenMs, usagelog.FieldUserIDSnapshot: values[i] = new(sql.NullInt64) - case usagelog.FieldPlatform, usagelog.FieldModel, usagelog.FieldServiceTier, usagelog.FieldImageSize, usagelog.FieldUserAgent, usagelog.FieldIPAddress, usagelog.FieldEndpoint, usagelog.FieldReasoningEffort, usagelog.FieldUserEmailSnapshot: + case usagelog.FieldPlatform, usagelog.FieldModel, usagelog.FieldServiceTier, usagelog.FieldUserAgent, usagelog.FieldIPAddress, usagelog.FieldEndpoint, usagelog.FieldReasoningEffort, usagelog.FieldUserEmailSnapshot: values[i] = new(sql.NullString) case usagelog.FieldCreatedAt: values[i] = new(sql.NullTime) @@ -256,18 +241,6 @@ func (ul *UsageLog) assignValues(columns []string, values []any) error { } else if value.Valid { ul.CacheCreationTokens = int(value.Int64) } - case usagelog.FieldCacheCreation5mTokens: - if value, ok := values[i].(*sql.NullInt64); !ok { - return fmt.Errorf("unexpected type %T for field cache_creation_5m_tokens", values[i]) - } else if value.Valid { - ul.CacheCreation5mTokens = int(value.Int64) - } - case usagelog.FieldCacheCreation1hTokens: - if value, ok := values[i].(*sql.NullInt64); !ok { - return fmt.Errorf("unexpected type %T for field cache_creation_1h_tokens", values[i]) - } else if value.Valid { - ul.CacheCreation1hTokens = int(value.Int64) - } case usagelog.FieldReasoningOutputTokens: if value, ok := values[i].(*sql.NullInt64); !ok { return fmt.Errorf("unexpected type %T for field reasoning_output_tokens", values[i]) @@ -298,12 +271,6 @@ func (ul *UsageLog) assignValues(columns []string, values []any) error { } else if value.Valid { ul.CacheCreationPrice = value.Float64 } - case usagelog.FieldCacheCreation1hPrice: - if value, ok := values[i].(*sql.NullFloat64); !ok { - return fmt.Errorf("unexpected type %T for field cache_creation_1h_price", values[i]) - } else if value.Valid { - ul.CacheCreation1hPrice = value.Float64 - } case usagelog.FieldInputCost: if value, ok := values[i].(*sql.NullFloat64); !ok { return fmt.Errorf("unexpected type %T for field input_cost", values[i]) @@ -376,12 +343,6 @@ func (ul *UsageLog) assignValues(columns []string, values []any) error { } else if value.Valid { ul.ServiceTier = value.String } - case usagelog.FieldImageSize: - if value, ok := values[i].(*sql.NullString); !ok { - return fmt.Errorf("unexpected type %T for field image_size", values[i]) - } else if value.Valid { - ul.ImageSize = value.String - } case usagelog.FieldStream: if value, ok := values[i].(*sql.NullBool); !ok { return fmt.Errorf("unexpected type %T for field stream", values[i]) @@ -424,30 +385,6 @@ func (ul *UsageLog) assignValues(columns []string, values []any) error { } else if value.Valid { ul.ReasoningEffort = value.String } - case usagelog.FieldUsageAttributes: - if value, ok := values[i].(*[]byte); !ok { - return fmt.Errorf("unexpected type %T for field usage_attributes", values[i]) - } else if value != nil && len(*value) > 0 { - if err := json.Unmarshal(*value, &ul.UsageAttributes); err != nil { - return fmt.Errorf("unmarshal field usage_attributes: %w", err) - } - } - case usagelog.FieldUsageMetrics: - if value, ok := values[i].(*[]byte); !ok { - return fmt.Errorf("unexpected type %T for field usage_metrics", values[i]) - } else if value != nil && len(*value) > 0 { - if err := json.Unmarshal(*value, &ul.UsageMetrics); err != nil { - return fmt.Errorf("unmarshal field usage_metrics: %w", err) - } - } - case usagelog.FieldUsageCostDetails: - if value, ok := values[i].(*[]byte); !ok { - return fmt.Errorf("unexpected type %T for field usage_cost_details", values[i]) - } else if value != nil && len(*value) > 0 { - if err := json.Unmarshal(*value, &ul.UsageCostDetails); err != nil { - return fmt.Errorf("unmarshal field usage_cost_details: %w", err) - } - } case usagelog.FieldUsageMetadata: if value, ok := values[i].(*[]byte); !ok { return fmt.Errorf("unexpected type %T for field usage_metadata", values[i]) @@ -576,12 +513,6 @@ func (ul *UsageLog) String() string { builder.WriteString("cache_creation_tokens=") builder.WriteString(fmt.Sprintf("%v", ul.CacheCreationTokens)) builder.WriteString(", ") - builder.WriteString("cache_creation_5m_tokens=") - builder.WriteString(fmt.Sprintf("%v", ul.CacheCreation5mTokens)) - builder.WriteString(", ") - builder.WriteString("cache_creation_1h_tokens=") - builder.WriteString(fmt.Sprintf("%v", ul.CacheCreation1hTokens)) - builder.WriteString(", ") builder.WriteString("reasoning_output_tokens=") builder.WriteString(fmt.Sprintf("%v", ul.ReasoningOutputTokens)) builder.WriteString(", ") @@ -597,9 +528,6 @@ func (ul *UsageLog) String() string { builder.WriteString("cache_creation_price=") builder.WriteString(fmt.Sprintf("%v", ul.CacheCreationPrice)) builder.WriteString(", ") - builder.WriteString("cache_creation_1h_price=") - builder.WriteString(fmt.Sprintf("%v", ul.CacheCreation1hPrice)) - builder.WriteString(", ") builder.WriteString("input_cost=") builder.WriteString(fmt.Sprintf("%v", ul.InputCost)) builder.WriteString(", ") @@ -636,9 +564,6 @@ func (ul *UsageLog) String() string { builder.WriteString("service_tier=") builder.WriteString(ul.ServiceTier) builder.WriteString(", ") - builder.WriteString("image_size=") - builder.WriteString(ul.ImageSize) - builder.WriteString(", ") builder.WriteString("stream=") builder.WriteString(fmt.Sprintf("%v", ul.Stream)) builder.WriteString(", ") @@ -660,15 +585,6 @@ func (ul *UsageLog) String() string { builder.WriteString("reasoning_effort=") builder.WriteString(ul.ReasoningEffort) builder.WriteString(", ") - builder.WriteString("usage_attributes=") - builder.WriteString(fmt.Sprintf("%v", ul.UsageAttributes)) - builder.WriteString(", ") - builder.WriteString("usage_metrics=") - builder.WriteString(fmt.Sprintf("%v", ul.UsageMetrics)) - builder.WriteString(", ") - builder.WriteString("usage_cost_details=") - builder.WriteString(fmt.Sprintf("%v", ul.UsageCostDetails)) - builder.WriteString(", ") builder.WriteString("usage_metadata=") builder.WriteString(fmt.Sprintf("%v", ul.UsageMetadata)) builder.WriteString(", ") diff --git a/backend/ent/usagelog/usagelog.go b/backend/ent/usagelog/usagelog.go index 95791a42..4f067295 100644 --- a/backend/ent/usagelog/usagelog.go +++ b/backend/ent/usagelog/usagelog.go @@ -26,10 +26,6 @@ const ( FieldCachedInputTokens = "cached_input_tokens" // FieldCacheCreationTokens holds the string denoting the cache_creation_tokens field in the database. FieldCacheCreationTokens = "cache_creation_tokens" - // FieldCacheCreation5mTokens holds the string denoting the cache_creation_5m_tokens field in the database. - FieldCacheCreation5mTokens = "cache_creation_5m_tokens" - // FieldCacheCreation1hTokens holds the string denoting the cache_creation_1h_tokens field in the database. - FieldCacheCreation1hTokens = "cache_creation_1h_tokens" // FieldReasoningOutputTokens holds the string denoting the reasoning_output_tokens field in the database. FieldReasoningOutputTokens = "reasoning_output_tokens" // FieldInputPrice holds the string denoting the input_price field in the database. @@ -40,8 +36,6 @@ const ( FieldCachedInputPrice = "cached_input_price" // FieldCacheCreationPrice holds the string denoting the cache_creation_price field in the database. FieldCacheCreationPrice = "cache_creation_price" - // FieldCacheCreation1hPrice holds the string denoting the cache_creation_1h_price field in the database. - FieldCacheCreation1hPrice = "cache_creation_1h_price" // FieldInputCost holds the string denoting the input_cost field in the database. FieldInputCost = "input_cost" // FieldOutputCost holds the string denoting the output_cost field in the database. @@ -66,8 +60,6 @@ const ( FieldAccountRateMultiplier = "account_rate_multiplier" // FieldServiceTier holds the string denoting the service_tier field in the database. FieldServiceTier = "service_tier" - // FieldImageSize holds the string denoting the image_size field in the database. - FieldImageSize = "image_size" // FieldStream holds the string denoting the stream field in the database. FieldStream = "stream" // FieldDurationMs holds the string denoting the duration_ms field in the database. @@ -82,12 +74,6 @@ const ( FieldEndpoint = "endpoint" // FieldReasoningEffort holds the string denoting the reasoning_effort field in the database. FieldReasoningEffort = "reasoning_effort" - // FieldUsageAttributes holds the string denoting the usage_attributes field in the database. - FieldUsageAttributes = "usage_attributes" - // FieldUsageMetrics holds the string denoting the usage_metrics field in the database. - FieldUsageMetrics = "usage_metrics" - // FieldUsageCostDetails holds the string denoting the usage_cost_details field in the database. - FieldUsageCostDetails = "usage_cost_details" // FieldUsageMetadata holds the string denoting the usage_metadata field in the database. FieldUsageMetadata = "usage_metadata" // FieldUserIDSnapshot holds the string denoting the user_id_snapshot field in the database. @@ -145,14 +131,11 @@ var Columns = []string{ FieldOutputTokens, FieldCachedInputTokens, FieldCacheCreationTokens, - FieldCacheCreation5mTokens, - FieldCacheCreation1hTokens, FieldReasoningOutputTokens, FieldInputPrice, FieldOutputPrice, FieldCachedInputPrice, FieldCacheCreationPrice, - FieldCacheCreation1hPrice, FieldInputCost, FieldOutputCost, FieldCachedInputCost, @@ -165,7 +148,6 @@ var Columns = []string{ FieldSellRate, FieldAccountRateMultiplier, FieldServiceTier, - FieldImageSize, FieldStream, FieldDurationMs, FieldFirstTokenMs, @@ -173,9 +155,6 @@ var Columns = []string{ FieldIPAddress, FieldEndpoint, FieldReasoningEffort, - FieldUsageAttributes, - FieldUsageMetrics, - FieldUsageCostDetails, FieldUsageMetadata, FieldUserIDSnapshot, FieldUserEmailSnapshot, @@ -219,10 +198,6 @@ var ( DefaultCachedInputTokens int // DefaultCacheCreationTokens holds the default value on creation for the "cache_creation_tokens" field. DefaultCacheCreationTokens int - // DefaultCacheCreation5mTokens holds the default value on creation for the "cache_creation_5m_tokens" field. - DefaultCacheCreation5mTokens int - // DefaultCacheCreation1hTokens holds the default value on creation for the "cache_creation_1h_tokens" field. - DefaultCacheCreation1hTokens int // DefaultReasoningOutputTokens holds the default value on creation for the "reasoning_output_tokens" field. DefaultReasoningOutputTokens int // DefaultInputPrice holds the default value on creation for the "input_price" field. @@ -233,8 +208,6 @@ var ( DefaultCachedInputPrice float64 // DefaultCacheCreationPrice holds the default value on creation for the "cache_creation_price" field. DefaultCacheCreationPrice float64 - // DefaultCacheCreation1hPrice holds the default value on creation for the "cache_creation_1h_price" field. - DefaultCacheCreation1hPrice float64 // DefaultInputCost holds the default value on creation for the "input_cost" field. DefaultInputCost float64 // DefaultOutputCost holds the default value on creation for the "output_cost" field. @@ -259,8 +232,6 @@ var ( DefaultAccountRateMultiplier float64 // DefaultServiceTier holds the default value on creation for the "service_tier" field. DefaultServiceTier string - // DefaultImageSize holds the default value on creation for the "image_size" field. - DefaultImageSize string // DefaultStream holds the default value on creation for the "stream" field. DefaultStream bool // DefaultDurationMs holds the default value on creation for the "duration_ms" field. @@ -321,16 +292,6 @@ func ByCacheCreationTokens(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldCacheCreationTokens, opts...).ToFunc() } -// ByCacheCreation5mTokens orders the results by the cache_creation_5m_tokens field. -func ByCacheCreation5mTokens(opts ...sql.OrderTermOption) OrderOption { - return sql.OrderByField(FieldCacheCreation5mTokens, opts...).ToFunc() -} - -// ByCacheCreation1hTokens orders the results by the cache_creation_1h_tokens field. -func ByCacheCreation1hTokens(opts ...sql.OrderTermOption) OrderOption { - return sql.OrderByField(FieldCacheCreation1hTokens, opts...).ToFunc() -} - // ByReasoningOutputTokens orders the results by the reasoning_output_tokens field. func ByReasoningOutputTokens(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldReasoningOutputTokens, opts...).ToFunc() @@ -356,11 +317,6 @@ func ByCacheCreationPrice(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldCacheCreationPrice, opts...).ToFunc() } -// ByCacheCreation1hPrice orders the results by the cache_creation_1h_price field. -func ByCacheCreation1hPrice(opts ...sql.OrderTermOption) OrderOption { - return sql.OrderByField(FieldCacheCreation1hPrice, opts...).ToFunc() -} - // ByInputCost orders the results by the input_cost field. func ByInputCost(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldInputCost, opts...).ToFunc() @@ -421,11 +377,6 @@ func ByServiceTier(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldServiceTier, opts...).ToFunc() } -// ByImageSize orders the results by the image_size field. -func ByImageSize(opts ...sql.OrderTermOption) OrderOption { - return sql.OrderByField(FieldImageSize, opts...).ToFunc() -} - // ByStream orders the results by the stream field. func ByStream(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldStream, opts...).ToFunc() diff --git a/backend/ent/usagelog/where.go b/backend/ent/usagelog/where.go index c95d8710..47113d74 100644 --- a/backend/ent/usagelog/where.go +++ b/backend/ent/usagelog/where.go @@ -7,7 +7,7 @@ import ( "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" - "github.com/DouDOU-start/airgate-core/ent/predicate" + "github.com/DevilGenius/airgate-core/ent/predicate" ) // ID filters vertices based on their ID field. @@ -85,16 +85,6 @@ func CacheCreationTokens(v int) predicate.UsageLog { return predicate.UsageLog(sql.FieldEQ(FieldCacheCreationTokens, v)) } -// CacheCreation5mTokens applies equality check predicate on the "cache_creation_5m_tokens" field. It's identical to CacheCreation5mTokensEQ. -func CacheCreation5mTokens(v int) predicate.UsageLog { - return predicate.UsageLog(sql.FieldEQ(FieldCacheCreation5mTokens, v)) -} - -// CacheCreation1hTokens applies equality check predicate on the "cache_creation_1h_tokens" field. It's identical to CacheCreation1hTokensEQ. -func CacheCreation1hTokens(v int) predicate.UsageLog { - return predicate.UsageLog(sql.FieldEQ(FieldCacheCreation1hTokens, v)) -} - // ReasoningOutputTokens applies equality check predicate on the "reasoning_output_tokens" field. It's identical to ReasoningOutputTokensEQ. func ReasoningOutputTokens(v int) predicate.UsageLog { return predicate.UsageLog(sql.FieldEQ(FieldReasoningOutputTokens, v)) @@ -120,11 +110,6 @@ func CacheCreationPrice(v float64) predicate.UsageLog { return predicate.UsageLog(sql.FieldEQ(FieldCacheCreationPrice, v)) } -// CacheCreation1hPrice applies equality check predicate on the "cache_creation_1h_price" field. It's identical to CacheCreation1hPriceEQ. -func CacheCreation1hPrice(v float64) predicate.UsageLog { - return predicate.UsageLog(sql.FieldEQ(FieldCacheCreation1hPrice, v)) -} - // InputCost applies equality check predicate on the "input_cost" field. It's identical to InputCostEQ. func InputCost(v float64) predicate.UsageLog { return predicate.UsageLog(sql.FieldEQ(FieldInputCost, v)) @@ -185,11 +170,6 @@ func ServiceTier(v string) predicate.UsageLog { return predicate.UsageLog(sql.FieldEQ(FieldServiceTier, v)) } -// ImageSize applies equality check predicate on the "image_size" field. It's identical to ImageSizeEQ. -func ImageSize(v string) predicate.UsageLog { - return predicate.UsageLog(sql.FieldEQ(FieldImageSize, v)) -} - // Stream applies equality check predicate on the "stream" field. It's identical to StreamEQ. func Stream(v bool) predicate.UsageLog { return predicate.UsageLog(sql.FieldEQ(FieldStream, v)) @@ -530,86 +510,6 @@ func CacheCreationTokensLTE(v int) predicate.UsageLog { return predicate.UsageLog(sql.FieldLTE(FieldCacheCreationTokens, v)) } -// CacheCreation5mTokensEQ applies the EQ predicate on the "cache_creation_5m_tokens" field. -func CacheCreation5mTokensEQ(v int) predicate.UsageLog { - return predicate.UsageLog(sql.FieldEQ(FieldCacheCreation5mTokens, v)) -} - -// CacheCreation5mTokensNEQ applies the NEQ predicate on the "cache_creation_5m_tokens" field. -func CacheCreation5mTokensNEQ(v int) predicate.UsageLog { - return predicate.UsageLog(sql.FieldNEQ(FieldCacheCreation5mTokens, v)) -} - -// CacheCreation5mTokensIn applies the In predicate on the "cache_creation_5m_tokens" field. -func CacheCreation5mTokensIn(vs ...int) predicate.UsageLog { - return predicate.UsageLog(sql.FieldIn(FieldCacheCreation5mTokens, vs...)) -} - -// CacheCreation5mTokensNotIn applies the NotIn predicate on the "cache_creation_5m_tokens" field. -func CacheCreation5mTokensNotIn(vs ...int) predicate.UsageLog { - return predicate.UsageLog(sql.FieldNotIn(FieldCacheCreation5mTokens, vs...)) -} - -// CacheCreation5mTokensGT applies the GT predicate on the "cache_creation_5m_tokens" field. -func CacheCreation5mTokensGT(v int) predicate.UsageLog { - return predicate.UsageLog(sql.FieldGT(FieldCacheCreation5mTokens, v)) -} - -// CacheCreation5mTokensGTE applies the GTE predicate on the "cache_creation_5m_tokens" field. -func CacheCreation5mTokensGTE(v int) predicate.UsageLog { - return predicate.UsageLog(sql.FieldGTE(FieldCacheCreation5mTokens, v)) -} - -// CacheCreation5mTokensLT applies the LT predicate on the "cache_creation_5m_tokens" field. -func CacheCreation5mTokensLT(v int) predicate.UsageLog { - return predicate.UsageLog(sql.FieldLT(FieldCacheCreation5mTokens, v)) -} - -// CacheCreation5mTokensLTE applies the LTE predicate on the "cache_creation_5m_tokens" field. -func CacheCreation5mTokensLTE(v int) predicate.UsageLog { - return predicate.UsageLog(sql.FieldLTE(FieldCacheCreation5mTokens, v)) -} - -// CacheCreation1hTokensEQ applies the EQ predicate on the "cache_creation_1h_tokens" field. -func CacheCreation1hTokensEQ(v int) predicate.UsageLog { - return predicate.UsageLog(sql.FieldEQ(FieldCacheCreation1hTokens, v)) -} - -// CacheCreation1hTokensNEQ applies the NEQ predicate on the "cache_creation_1h_tokens" field. -func CacheCreation1hTokensNEQ(v int) predicate.UsageLog { - return predicate.UsageLog(sql.FieldNEQ(FieldCacheCreation1hTokens, v)) -} - -// CacheCreation1hTokensIn applies the In predicate on the "cache_creation_1h_tokens" field. -func CacheCreation1hTokensIn(vs ...int) predicate.UsageLog { - return predicate.UsageLog(sql.FieldIn(FieldCacheCreation1hTokens, vs...)) -} - -// CacheCreation1hTokensNotIn applies the NotIn predicate on the "cache_creation_1h_tokens" field. -func CacheCreation1hTokensNotIn(vs ...int) predicate.UsageLog { - return predicate.UsageLog(sql.FieldNotIn(FieldCacheCreation1hTokens, vs...)) -} - -// CacheCreation1hTokensGT applies the GT predicate on the "cache_creation_1h_tokens" field. -func CacheCreation1hTokensGT(v int) predicate.UsageLog { - return predicate.UsageLog(sql.FieldGT(FieldCacheCreation1hTokens, v)) -} - -// CacheCreation1hTokensGTE applies the GTE predicate on the "cache_creation_1h_tokens" field. -func CacheCreation1hTokensGTE(v int) predicate.UsageLog { - return predicate.UsageLog(sql.FieldGTE(FieldCacheCreation1hTokens, v)) -} - -// CacheCreation1hTokensLT applies the LT predicate on the "cache_creation_1h_tokens" field. -func CacheCreation1hTokensLT(v int) predicate.UsageLog { - return predicate.UsageLog(sql.FieldLT(FieldCacheCreation1hTokens, v)) -} - -// CacheCreation1hTokensLTE applies the LTE predicate on the "cache_creation_1h_tokens" field. -func CacheCreation1hTokensLTE(v int) predicate.UsageLog { - return predicate.UsageLog(sql.FieldLTE(FieldCacheCreation1hTokens, v)) -} - // ReasoningOutputTokensEQ applies the EQ predicate on the "reasoning_output_tokens" field. func ReasoningOutputTokensEQ(v int) predicate.UsageLog { return predicate.UsageLog(sql.FieldEQ(FieldReasoningOutputTokens, v)) @@ -810,46 +710,6 @@ func CacheCreationPriceLTE(v float64) predicate.UsageLog { return predicate.UsageLog(sql.FieldLTE(FieldCacheCreationPrice, v)) } -// CacheCreation1hPriceEQ applies the EQ predicate on the "cache_creation_1h_price" field. -func CacheCreation1hPriceEQ(v float64) predicate.UsageLog { - return predicate.UsageLog(sql.FieldEQ(FieldCacheCreation1hPrice, v)) -} - -// CacheCreation1hPriceNEQ applies the NEQ predicate on the "cache_creation_1h_price" field. -func CacheCreation1hPriceNEQ(v float64) predicate.UsageLog { - return predicate.UsageLog(sql.FieldNEQ(FieldCacheCreation1hPrice, v)) -} - -// CacheCreation1hPriceIn applies the In predicate on the "cache_creation_1h_price" field. -func CacheCreation1hPriceIn(vs ...float64) predicate.UsageLog { - return predicate.UsageLog(sql.FieldIn(FieldCacheCreation1hPrice, vs...)) -} - -// CacheCreation1hPriceNotIn applies the NotIn predicate on the "cache_creation_1h_price" field. -func CacheCreation1hPriceNotIn(vs ...float64) predicate.UsageLog { - return predicate.UsageLog(sql.FieldNotIn(FieldCacheCreation1hPrice, vs...)) -} - -// CacheCreation1hPriceGT applies the GT predicate on the "cache_creation_1h_price" field. -func CacheCreation1hPriceGT(v float64) predicate.UsageLog { - return predicate.UsageLog(sql.FieldGT(FieldCacheCreation1hPrice, v)) -} - -// CacheCreation1hPriceGTE applies the GTE predicate on the "cache_creation_1h_price" field. -func CacheCreation1hPriceGTE(v float64) predicate.UsageLog { - return predicate.UsageLog(sql.FieldGTE(FieldCacheCreation1hPrice, v)) -} - -// CacheCreation1hPriceLT applies the LT predicate on the "cache_creation_1h_price" field. -func CacheCreation1hPriceLT(v float64) predicate.UsageLog { - return predicate.UsageLog(sql.FieldLT(FieldCacheCreation1hPrice, v)) -} - -// CacheCreation1hPriceLTE applies the LTE predicate on the "cache_creation_1h_price" field. -func CacheCreation1hPriceLTE(v float64) predicate.UsageLog { - return predicate.UsageLog(sql.FieldLTE(FieldCacheCreation1hPrice, v)) -} - // InputCostEQ applies the EQ predicate on the "input_cost" field. func InputCostEQ(v float64) predicate.UsageLog { return predicate.UsageLog(sql.FieldEQ(FieldInputCost, v)) @@ -1355,71 +1215,6 @@ func ServiceTierContainsFold(v string) predicate.UsageLog { return predicate.UsageLog(sql.FieldContainsFold(FieldServiceTier, v)) } -// ImageSizeEQ applies the EQ predicate on the "image_size" field. -func ImageSizeEQ(v string) predicate.UsageLog { - return predicate.UsageLog(sql.FieldEQ(FieldImageSize, v)) -} - -// ImageSizeNEQ applies the NEQ predicate on the "image_size" field. -func ImageSizeNEQ(v string) predicate.UsageLog { - return predicate.UsageLog(sql.FieldNEQ(FieldImageSize, v)) -} - -// ImageSizeIn applies the In predicate on the "image_size" field. -func ImageSizeIn(vs ...string) predicate.UsageLog { - return predicate.UsageLog(sql.FieldIn(FieldImageSize, vs...)) -} - -// ImageSizeNotIn applies the NotIn predicate on the "image_size" field. -func ImageSizeNotIn(vs ...string) predicate.UsageLog { - return predicate.UsageLog(sql.FieldNotIn(FieldImageSize, vs...)) -} - -// ImageSizeGT applies the GT predicate on the "image_size" field. -func ImageSizeGT(v string) predicate.UsageLog { - return predicate.UsageLog(sql.FieldGT(FieldImageSize, v)) -} - -// ImageSizeGTE applies the GTE predicate on the "image_size" field. -func ImageSizeGTE(v string) predicate.UsageLog { - return predicate.UsageLog(sql.FieldGTE(FieldImageSize, v)) -} - -// ImageSizeLT applies the LT predicate on the "image_size" field. -func ImageSizeLT(v string) predicate.UsageLog { - return predicate.UsageLog(sql.FieldLT(FieldImageSize, v)) -} - -// ImageSizeLTE applies the LTE predicate on the "image_size" field. -func ImageSizeLTE(v string) predicate.UsageLog { - return predicate.UsageLog(sql.FieldLTE(FieldImageSize, v)) -} - -// ImageSizeContains applies the Contains predicate on the "image_size" field. -func ImageSizeContains(v string) predicate.UsageLog { - return predicate.UsageLog(sql.FieldContains(FieldImageSize, v)) -} - -// ImageSizeHasPrefix applies the HasPrefix predicate on the "image_size" field. -func ImageSizeHasPrefix(v string) predicate.UsageLog { - return predicate.UsageLog(sql.FieldHasPrefix(FieldImageSize, v)) -} - -// ImageSizeHasSuffix applies the HasSuffix predicate on the "image_size" field. -func ImageSizeHasSuffix(v string) predicate.UsageLog { - return predicate.UsageLog(sql.FieldHasSuffix(FieldImageSize, v)) -} - -// ImageSizeEqualFold applies the EqualFold predicate on the "image_size" field. -func ImageSizeEqualFold(v string) predicate.UsageLog { - return predicate.UsageLog(sql.FieldEqualFold(FieldImageSize, v)) -} - -// ImageSizeContainsFold applies the ContainsFold predicate on the "image_size" field. -func ImageSizeContainsFold(v string) predicate.UsageLog { - return predicate.UsageLog(sql.FieldContainsFold(FieldImageSize, v)) -} - // StreamEQ applies the EQ predicate on the "stream" field. func StreamEQ(v bool) predicate.UsageLog { return predicate.UsageLog(sql.FieldEQ(FieldStream, v)) @@ -1770,36 +1565,6 @@ func ReasoningEffortContainsFold(v string) predicate.UsageLog { return predicate.UsageLog(sql.FieldContainsFold(FieldReasoningEffort, v)) } -// UsageAttributesIsNil applies the IsNil predicate on the "usage_attributes" field. -func UsageAttributesIsNil() predicate.UsageLog { - return predicate.UsageLog(sql.FieldIsNull(FieldUsageAttributes)) -} - -// UsageAttributesNotNil applies the NotNil predicate on the "usage_attributes" field. -func UsageAttributesNotNil() predicate.UsageLog { - return predicate.UsageLog(sql.FieldNotNull(FieldUsageAttributes)) -} - -// UsageMetricsIsNil applies the IsNil predicate on the "usage_metrics" field. -func UsageMetricsIsNil() predicate.UsageLog { - return predicate.UsageLog(sql.FieldIsNull(FieldUsageMetrics)) -} - -// UsageMetricsNotNil applies the NotNil predicate on the "usage_metrics" field. -func UsageMetricsNotNil() predicate.UsageLog { - return predicate.UsageLog(sql.FieldNotNull(FieldUsageMetrics)) -} - -// UsageCostDetailsIsNil applies the IsNil predicate on the "usage_cost_details" field. -func UsageCostDetailsIsNil() predicate.UsageLog { - return predicate.UsageLog(sql.FieldIsNull(FieldUsageCostDetails)) -} - -// UsageCostDetailsNotNil applies the NotNil predicate on the "usage_cost_details" field. -func UsageCostDetailsNotNil() predicate.UsageLog { - return predicate.UsageLog(sql.FieldNotNull(FieldUsageCostDetails)) -} - // UsageMetadataIsNil applies the IsNil predicate on the "usage_metadata" field. func UsageMetadataIsNil() predicate.UsageLog { return predicate.UsageLog(sql.FieldIsNull(FieldUsageMetadata)) diff --git a/backend/ent/usagelog_create.go b/backend/ent/usagelog_create.go index 260a75e5..1cfc28f4 100644 --- a/backend/ent/usagelog_create.go +++ b/backend/ent/usagelog_create.go @@ -10,12 +10,11 @@ import ( "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" - "github.com/DouDOU-start/airgate-core/ent/account" - "github.com/DouDOU-start/airgate-core/ent/apikey" - "github.com/DouDOU-start/airgate-core/ent/group" - "github.com/DouDOU-start/airgate-core/ent/usagelog" - "github.com/DouDOU-start/airgate-core/ent/user" - sdk "github.com/DouDOU-start/airgate-sdk/sdkgo" + "github.com/DevilGenius/airgate-core/ent/account" + "github.com/DevilGenius/airgate-core/ent/apikey" + "github.com/DevilGenius/airgate-core/ent/group" + "github.com/DevilGenius/airgate-core/ent/usagelog" + "github.com/DevilGenius/airgate-core/ent/user" ) // UsageLogCreate is the builder for creating a UsageLog entity. @@ -93,34 +92,6 @@ func (ulc *UsageLogCreate) SetNillableCacheCreationTokens(i *int) *UsageLogCreat return ulc } -// SetCacheCreation5mTokens sets the "cache_creation_5m_tokens" field. -func (ulc *UsageLogCreate) SetCacheCreation5mTokens(i int) *UsageLogCreate { - ulc.mutation.SetCacheCreation5mTokens(i) - return ulc -} - -// SetNillableCacheCreation5mTokens sets the "cache_creation_5m_tokens" field if the given value is not nil. -func (ulc *UsageLogCreate) SetNillableCacheCreation5mTokens(i *int) *UsageLogCreate { - if i != nil { - ulc.SetCacheCreation5mTokens(*i) - } - return ulc -} - -// SetCacheCreation1hTokens sets the "cache_creation_1h_tokens" field. -func (ulc *UsageLogCreate) SetCacheCreation1hTokens(i int) *UsageLogCreate { - ulc.mutation.SetCacheCreation1hTokens(i) - return ulc -} - -// SetNillableCacheCreation1hTokens sets the "cache_creation_1h_tokens" field if the given value is not nil. -func (ulc *UsageLogCreate) SetNillableCacheCreation1hTokens(i *int) *UsageLogCreate { - if i != nil { - ulc.SetCacheCreation1hTokens(*i) - } - return ulc -} - // SetReasoningOutputTokens sets the "reasoning_output_tokens" field. func (ulc *UsageLogCreate) SetReasoningOutputTokens(i int) *UsageLogCreate { ulc.mutation.SetReasoningOutputTokens(i) @@ -191,20 +162,6 @@ func (ulc *UsageLogCreate) SetNillableCacheCreationPrice(f *float64) *UsageLogCr return ulc } -// SetCacheCreation1hPrice sets the "cache_creation_1h_price" field. -func (ulc *UsageLogCreate) SetCacheCreation1hPrice(f float64) *UsageLogCreate { - ulc.mutation.SetCacheCreation1hPrice(f) - return ulc -} - -// SetNillableCacheCreation1hPrice sets the "cache_creation_1h_price" field if the given value is not nil. -func (ulc *UsageLogCreate) SetNillableCacheCreation1hPrice(f *float64) *UsageLogCreate { - if f != nil { - ulc.SetCacheCreation1hPrice(*f) - } - return ulc -} - // SetInputCost sets the "input_cost" field. func (ulc *UsageLogCreate) SetInputCost(f float64) *UsageLogCreate { ulc.mutation.SetInputCost(f) @@ -373,20 +330,6 @@ func (ulc *UsageLogCreate) SetNillableServiceTier(s *string) *UsageLogCreate { return ulc } -// SetImageSize sets the "image_size" field. -func (ulc *UsageLogCreate) SetImageSize(s string) *UsageLogCreate { - ulc.mutation.SetImageSize(s) - return ulc -} - -// SetNillableImageSize sets the "image_size" field if the given value is not nil. -func (ulc *UsageLogCreate) SetNillableImageSize(s *string) *UsageLogCreate { - if s != nil { - ulc.SetImageSize(*s) - } - return ulc -} - // SetStream sets the "stream" field. func (ulc *UsageLogCreate) SetStream(b bool) *UsageLogCreate { ulc.mutation.SetStream(b) @@ -485,24 +428,6 @@ func (ulc *UsageLogCreate) SetNillableReasoningEffort(s *string) *UsageLogCreate return ulc } -// SetUsageAttributes sets the "usage_attributes" field. -func (ulc *UsageLogCreate) SetUsageAttributes(sa []sdk.UsageAttribute) *UsageLogCreate { - ulc.mutation.SetUsageAttributes(sa) - return ulc -} - -// SetUsageMetrics sets the "usage_metrics" field. -func (ulc *UsageLogCreate) SetUsageMetrics(sm []sdk.UsageMetric) *UsageLogCreate { - ulc.mutation.SetUsageMetrics(sm) - return ulc -} - -// SetUsageCostDetails sets the "usage_cost_details" field. -func (ulc *UsageLogCreate) SetUsageCostDetails(scd []sdk.UsageCostDetail) *UsageLogCreate { - ulc.mutation.SetUsageCostDetails(scd) - return ulc -} - // SetUsageMetadata sets the "usage_metadata" field. func (ulc *UsageLogCreate) SetUsageMetadata(m map[string]string) *UsageLogCreate { ulc.mutation.SetUsageMetadata(m) @@ -678,14 +603,6 @@ func (ulc *UsageLogCreate) defaults() { v := usagelog.DefaultCacheCreationTokens ulc.mutation.SetCacheCreationTokens(v) } - if _, ok := ulc.mutation.CacheCreation5mTokens(); !ok { - v := usagelog.DefaultCacheCreation5mTokens - ulc.mutation.SetCacheCreation5mTokens(v) - } - if _, ok := ulc.mutation.CacheCreation1hTokens(); !ok { - v := usagelog.DefaultCacheCreation1hTokens - ulc.mutation.SetCacheCreation1hTokens(v) - } if _, ok := ulc.mutation.ReasoningOutputTokens(); !ok { v := usagelog.DefaultReasoningOutputTokens ulc.mutation.SetReasoningOutputTokens(v) @@ -706,10 +623,6 @@ func (ulc *UsageLogCreate) defaults() { v := usagelog.DefaultCacheCreationPrice ulc.mutation.SetCacheCreationPrice(v) } - if _, ok := ulc.mutation.CacheCreation1hPrice(); !ok { - v := usagelog.DefaultCacheCreation1hPrice - ulc.mutation.SetCacheCreation1hPrice(v) - } if _, ok := ulc.mutation.InputCost(); !ok { v := usagelog.DefaultInputCost ulc.mutation.SetInputCost(v) @@ -758,10 +671,6 @@ func (ulc *UsageLogCreate) defaults() { v := usagelog.DefaultServiceTier ulc.mutation.SetServiceTier(v) } - if _, ok := ulc.mutation.ImageSize(); !ok { - v := usagelog.DefaultImageSize - ulc.mutation.SetImageSize(v) - } if _, ok := ulc.mutation.Stream(); !ok { v := usagelog.DefaultStream ulc.mutation.SetStream(v) @@ -834,12 +743,6 @@ func (ulc *UsageLogCreate) check() error { if _, ok := ulc.mutation.CacheCreationTokens(); !ok { return &ValidationError{Name: "cache_creation_tokens", err: errors.New(`ent: missing required field "UsageLog.cache_creation_tokens"`)} } - if _, ok := ulc.mutation.CacheCreation5mTokens(); !ok { - return &ValidationError{Name: "cache_creation_5m_tokens", err: errors.New(`ent: missing required field "UsageLog.cache_creation_5m_tokens"`)} - } - if _, ok := ulc.mutation.CacheCreation1hTokens(); !ok { - return &ValidationError{Name: "cache_creation_1h_tokens", err: errors.New(`ent: missing required field "UsageLog.cache_creation_1h_tokens"`)} - } if _, ok := ulc.mutation.ReasoningOutputTokens(); !ok { return &ValidationError{Name: "reasoning_output_tokens", err: errors.New(`ent: missing required field "UsageLog.reasoning_output_tokens"`)} } @@ -855,9 +758,6 @@ func (ulc *UsageLogCreate) check() error { if _, ok := ulc.mutation.CacheCreationPrice(); !ok { return &ValidationError{Name: "cache_creation_price", err: errors.New(`ent: missing required field "UsageLog.cache_creation_price"`)} } - if _, ok := ulc.mutation.CacheCreation1hPrice(); !ok { - return &ValidationError{Name: "cache_creation_1h_price", err: errors.New(`ent: missing required field "UsageLog.cache_creation_1h_price"`)} - } if _, ok := ulc.mutation.InputCost(); !ok { return &ValidationError{Name: "input_cost", err: errors.New(`ent: missing required field "UsageLog.input_cost"`)} } @@ -894,9 +794,6 @@ func (ulc *UsageLogCreate) check() error { if _, ok := ulc.mutation.ServiceTier(); !ok { return &ValidationError{Name: "service_tier", err: errors.New(`ent: missing required field "UsageLog.service_tier"`)} } - if _, ok := ulc.mutation.ImageSize(); !ok { - return &ValidationError{Name: "image_size", err: errors.New(`ent: missing required field "UsageLog.image_size"`)} - } if _, ok := ulc.mutation.Stream(); !ok { return &ValidationError{Name: "stream", err: errors.New(`ent: missing required field "UsageLog.stream"`)} } @@ -977,14 +874,6 @@ func (ulc *UsageLogCreate) createSpec() (*UsageLog, *sqlgraph.CreateSpec) { _spec.SetField(usagelog.FieldCacheCreationTokens, field.TypeInt, value) _node.CacheCreationTokens = value } - if value, ok := ulc.mutation.CacheCreation5mTokens(); ok { - _spec.SetField(usagelog.FieldCacheCreation5mTokens, field.TypeInt, value) - _node.CacheCreation5mTokens = value - } - if value, ok := ulc.mutation.CacheCreation1hTokens(); ok { - _spec.SetField(usagelog.FieldCacheCreation1hTokens, field.TypeInt, value) - _node.CacheCreation1hTokens = value - } if value, ok := ulc.mutation.ReasoningOutputTokens(); ok { _spec.SetField(usagelog.FieldReasoningOutputTokens, field.TypeInt, value) _node.ReasoningOutputTokens = value @@ -1005,10 +894,6 @@ func (ulc *UsageLogCreate) createSpec() (*UsageLog, *sqlgraph.CreateSpec) { _spec.SetField(usagelog.FieldCacheCreationPrice, field.TypeFloat64, value) _node.CacheCreationPrice = value } - if value, ok := ulc.mutation.CacheCreation1hPrice(); ok { - _spec.SetField(usagelog.FieldCacheCreation1hPrice, field.TypeFloat64, value) - _node.CacheCreation1hPrice = value - } if value, ok := ulc.mutation.InputCost(); ok { _spec.SetField(usagelog.FieldInputCost, field.TypeFloat64, value) _node.InputCost = value @@ -1057,10 +942,6 @@ func (ulc *UsageLogCreate) createSpec() (*UsageLog, *sqlgraph.CreateSpec) { _spec.SetField(usagelog.FieldServiceTier, field.TypeString, value) _node.ServiceTier = value } - if value, ok := ulc.mutation.ImageSize(); ok { - _spec.SetField(usagelog.FieldImageSize, field.TypeString, value) - _node.ImageSize = value - } if value, ok := ulc.mutation.Stream(); ok { _spec.SetField(usagelog.FieldStream, field.TypeBool, value) _node.Stream = value @@ -1089,18 +970,6 @@ func (ulc *UsageLogCreate) createSpec() (*UsageLog, *sqlgraph.CreateSpec) { _spec.SetField(usagelog.FieldReasoningEffort, field.TypeString, value) _node.ReasoningEffort = value } - if value, ok := ulc.mutation.UsageAttributes(); ok { - _spec.SetField(usagelog.FieldUsageAttributes, field.TypeJSON, value) - _node.UsageAttributes = value - } - if value, ok := ulc.mutation.UsageMetrics(); ok { - _spec.SetField(usagelog.FieldUsageMetrics, field.TypeJSON, value) - _node.UsageMetrics = value - } - if value, ok := ulc.mutation.UsageCostDetails(); ok { - _spec.SetField(usagelog.FieldUsageCostDetails, field.TypeJSON, value) - _node.UsageCostDetails = value - } if value, ok := ulc.mutation.UsageMetadata(); ok { _spec.SetField(usagelog.FieldUsageMetadata, field.TypeJSON, value) _node.UsageMetadata = value diff --git a/backend/ent/usagelog_delete.go b/backend/ent/usagelog_delete.go index c28cdfc5..55e28476 100644 --- a/backend/ent/usagelog_delete.go +++ b/backend/ent/usagelog_delete.go @@ -8,8 +8,8 @@ import ( "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" - "github.com/DouDOU-start/airgate-core/ent/predicate" - "github.com/DouDOU-start/airgate-core/ent/usagelog" + "github.com/DevilGenius/airgate-core/ent/predicate" + "github.com/DevilGenius/airgate-core/ent/usagelog" ) // UsageLogDelete is the builder for deleting a UsageLog entity. diff --git a/backend/ent/usagelog_query.go b/backend/ent/usagelog_query.go index d49168b6..3a79219d 100644 --- a/backend/ent/usagelog_query.go +++ b/backend/ent/usagelog_query.go @@ -10,12 +10,12 @@ import ( "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" - "github.com/DouDOU-start/airgate-core/ent/account" - "github.com/DouDOU-start/airgate-core/ent/apikey" - "github.com/DouDOU-start/airgate-core/ent/group" - "github.com/DouDOU-start/airgate-core/ent/predicate" - "github.com/DouDOU-start/airgate-core/ent/usagelog" - "github.com/DouDOU-start/airgate-core/ent/user" + "github.com/DevilGenius/airgate-core/ent/account" + "github.com/DevilGenius/airgate-core/ent/apikey" + "github.com/DevilGenius/airgate-core/ent/group" + "github.com/DevilGenius/airgate-core/ent/predicate" + "github.com/DevilGenius/airgate-core/ent/usagelog" + "github.com/DevilGenius/airgate-core/ent/user" ) // UsageLogQuery is the builder for querying UsageLog entities. diff --git a/backend/ent/usagelog_update.go b/backend/ent/usagelog_update.go index c20a4b01..56380479 100644 --- a/backend/ent/usagelog_update.go +++ b/backend/ent/usagelog_update.go @@ -9,15 +9,13 @@ import ( "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" - "entgo.io/ent/dialect/sql/sqljson" "entgo.io/ent/schema/field" - "github.com/DouDOU-start/airgate-core/ent/account" - "github.com/DouDOU-start/airgate-core/ent/apikey" - "github.com/DouDOU-start/airgate-core/ent/group" - "github.com/DouDOU-start/airgate-core/ent/predicate" - "github.com/DouDOU-start/airgate-core/ent/usagelog" - "github.com/DouDOU-start/airgate-core/ent/user" - sdk "github.com/DouDOU-start/airgate-sdk/sdkgo" + "github.com/DevilGenius/airgate-core/ent/account" + "github.com/DevilGenius/airgate-core/ent/apikey" + "github.com/DevilGenius/airgate-core/ent/group" + "github.com/DevilGenius/airgate-core/ent/predicate" + "github.com/DevilGenius/airgate-core/ent/usagelog" + "github.com/DevilGenius/airgate-core/ent/user" ) // UsageLogUpdate is the builder for updating UsageLog entities. @@ -145,48 +143,6 @@ func (ulu *UsageLogUpdate) AddCacheCreationTokens(i int) *UsageLogUpdate { return ulu } -// SetCacheCreation5mTokens sets the "cache_creation_5m_tokens" field. -func (ulu *UsageLogUpdate) SetCacheCreation5mTokens(i int) *UsageLogUpdate { - ulu.mutation.ResetCacheCreation5mTokens() - ulu.mutation.SetCacheCreation5mTokens(i) - return ulu -} - -// SetNillableCacheCreation5mTokens sets the "cache_creation_5m_tokens" field if the given value is not nil. -func (ulu *UsageLogUpdate) SetNillableCacheCreation5mTokens(i *int) *UsageLogUpdate { - if i != nil { - ulu.SetCacheCreation5mTokens(*i) - } - return ulu -} - -// AddCacheCreation5mTokens adds i to the "cache_creation_5m_tokens" field. -func (ulu *UsageLogUpdate) AddCacheCreation5mTokens(i int) *UsageLogUpdate { - ulu.mutation.AddCacheCreation5mTokens(i) - return ulu -} - -// SetCacheCreation1hTokens sets the "cache_creation_1h_tokens" field. -func (ulu *UsageLogUpdate) SetCacheCreation1hTokens(i int) *UsageLogUpdate { - ulu.mutation.ResetCacheCreation1hTokens() - ulu.mutation.SetCacheCreation1hTokens(i) - return ulu -} - -// SetNillableCacheCreation1hTokens sets the "cache_creation_1h_tokens" field if the given value is not nil. -func (ulu *UsageLogUpdate) SetNillableCacheCreation1hTokens(i *int) *UsageLogUpdate { - if i != nil { - ulu.SetCacheCreation1hTokens(*i) - } - return ulu -} - -// AddCacheCreation1hTokens adds i to the "cache_creation_1h_tokens" field. -func (ulu *UsageLogUpdate) AddCacheCreation1hTokens(i int) *UsageLogUpdate { - ulu.mutation.AddCacheCreation1hTokens(i) - return ulu -} - // SetReasoningOutputTokens sets the "reasoning_output_tokens" field. func (ulu *UsageLogUpdate) SetReasoningOutputTokens(i int) *UsageLogUpdate { ulu.mutation.ResetReasoningOutputTokens() @@ -292,27 +248,6 @@ func (ulu *UsageLogUpdate) AddCacheCreationPrice(f float64) *UsageLogUpdate { return ulu } -// SetCacheCreation1hPrice sets the "cache_creation_1h_price" field. -func (ulu *UsageLogUpdate) SetCacheCreation1hPrice(f float64) *UsageLogUpdate { - ulu.mutation.ResetCacheCreation1hPrice() - ulu.mutation.SetCacheCreation1hPrice(f) - return ulu -} - -// SetNillableCacheCreation1hPrice sets the "cache_creation_1h_price" field if the given value is not nil. -func (ulu *UsageLogUpdate) SetNillableCacheCreation1hPrice(f *float64) *UsageLogUpdate { - if f != nil { - ulu.SetCacheCreation1hPrice(*f) - } - return ulu -} - -// AddCacheCreation1hPrice adds f to the "cache_creation_1h_price" field. -func (ulu *UsageLogUpdate) AddCacheCreation1hPrice(f float64) *UsageLogUpdate { - ulu.mutation.AddCacheCreation1hPrice(f) - return ulu -} - // SetInputCost sets the "input_cost" field. func (ulu *UsageLogUpdate) SetInputCost(f float64) *UsageLogUpdate { ulu.mutation.ResetInputCost() @@ -558,20 +493,6 @@ func (ulu *UsageLogUpdate) SetNillableServiceTier(s *string) *UsageLogUpdate { return ulu } -// SetImageSize sets the "image_size" field. -func (ulu *UsageLogUpdate) SetImageSize(s string) *UsageLogUpdate { - ulu.mutation.SetImageSize(s) - return ulu -} - -// SetNillableImageSize sets the "image_size" field if the given value is not nil. -func (ulu *UsageLogUpdate) SetNillableImageSize(s *string) *UsageLogUpdate { - if s != nil { - ulu.SetImageSize(*s) - } - return ulu -} - // SetStream sets the "stream" field. func (ulu *UsageLogUpdate) SetStream(b bool) *UsageLogUpdate { ulu.mutation.SetStream(b) @@ -684,60 +605,6 @@ func (ulu *UsageLogUpdate) SetNillableReasoningEffort(s *string) *UsageLogUpdate return ulu } -// SetUsageAttributes sets the "usage_attributes" field. -func (ulu *UsageLogUpdate) SetUsageAttributes(sa []sdk.UsageAttribute) *UsageLogUpdate { - ulu.mutation.SetUsageAttributes(sa) - return ulu -} - -// AppendUsageAttributes appends sa to the "usage_attributes" field. -func (ulu *UsageLogUpdate) AppendUsageAttributes(sa []sdk.UsageAttribute) *UsageLogUpdate { - ulu.mutation.AppendUsageAttributes(sa) - return ulu -} - -// ClearUsageAttributes clears the value of the "usage_attributes" field. -func (ulu *UsageLogUpdate) ClearUsageAttributes() *UsageLogUpdate { - ulu.mutation.ClearUsageAttributes() - return ulu -} - -// SetUsageMetrics sets the "usage_metrics" field. -func (ulu *UsageLogUpdate) SetUsageMetrics(sm []sdk.UsageMetric) *UsageLogUpdate { - ulu.mutation.SetUsageMetrics(sm) - return ulu -} - -// AppendUsageMetrics appends sm to the "usage_metrics" field. -func (ulu *UsageLogUpdate) AppendUsageMetrics(sm []sdk.UsageMetric) *UsageLogUpdate { - ulu.mutation.AppendUsageMetrics(sm) - return ulu -} - -// ClearUsageMetrics clears the value of the "usage_metrics" field. -func (ulu *UsageLogUpdate) ClearUsageMetrics() *UsageLogUpdate { - ulu.mutation.ClearUsageMetrics() - return ulu -} - -// SetUsageCostDetails sets the "usage_cost_details" field. -func (ulu *UsageLogUpdate) SetUsageCostDetails(scd []sdk.UsageCostDetail) *UsageLogUpdate { - ulu.mutation.SetUsageCostDetails(scd) - return ulu -} - -// AppendUsageCostDetails appends scd to the "usage_cost_details" field. -func (ulu *UsageLogUpdate) AppendUsageCostDetails(scd []sdk.UsageCostDetail) *UsageLogUpdate { - ulu.mutation.AppendUsageCostDetails(scd) - return ulu -} - -// ClearUsageCostDetails clears the value of the "usage_cost_details" field. -func (ulu *UsageLogUpdate) ClearUsageCostDetails() *UsageLogUpdate { - ulu.mutation.ClearUsageCostDetails() - return ulu -} - // SetUsageMetadata sets the "usage_metadata" field. func (ulu *UsageLogUpdate) SetUsageMetadata(m map[string]string) *UsageLogUpdate { ulu.mutation.SetUsageMetadata(m) @@ -974,18 +841,6 @@ func (ulu *UsageLogUpdate) sqlSave(ctx context.Context) (n int, err error) { if value, ok := ulu.mutation.AddedCacheCreationTokens(); ok { _spec.AddField(usagelog.FieldCacheCreationTokens, field.TypeInt, value) } - if value, ok := ulu.mutation.CacheCreation5mTokens(); ok { - _spec.SetField(usagelog.FieldCacheCreation5mTokens, field.TypeInt, value) - } - if value, ok := ulu.mutation.AddedCacheCreation5mTokens(); ok { - _spec.AddField(usagelog.FieldCacheCreation5mTokens, field.TypeInt, value) - } - if value, ok := ulu.mutation.CacheCreation1hTokens(); ok { - _spec.SetField(usagelog.FieldCacheCreation1hTokens, field.TypeInt, value) - } - if value, ok := ulu.mutation.AddedCacheCreation1hTokens(); ok { - _spec.AddField(usagelog.FieldCacheCreation1hTokens, field.TypeInt, value) - } if value, ok := ulu.mutation.ReasoningOutputTokens(); ok { _spec.SetField(usagelog.FieldReasoningOutputTokens, field.TypeInt, value) } @@ -1016,12 +871,6 @@ func (ulu *UsageLogUpdate) sqlSave(ctx context.Context) (n int, err error) { if value, ok := ulu.mutation.AddedCacheCreationPrice(); ok { _spec.AddField(usagelog.FieldCacheCreationPrice, field.TypeFloat64, value) } - if value, ok := ulu.mutation.CacheCreation1hPrice(); ok { - _spec.SetField(usagelog.FieldCacheCreation1hPrice, field.TypeFloat64, value) - } - if value, ok := ulu.mutation.AddedCacheCreation1hPrice(); ok { - _spec.AddField(usagelog.FieldCacheCreation1hPrice, field.TypeFloat64, value) - } if value, ok := ulu.mutation.InputCost(); ok { _spec.SetField(usagelog.FieldInputCost, field.TypeFloat64, value) } @@ -1091,9 +940,6 @@ func (ulu *UsageLogUpdate) sqlSave(ctx context.Context) (n int, err error) { if value, ok := ulu.mutation.ServiceTier(); ok { _spec.SetField(usagelog.FieldServiceTier, field.TypeString, value) } - if value, ok := ulu.mutation.ImageSize(); ok { - _spec.SetField(usagelog.FieldImageSize, field.TypeString, value) - } if value, ok := ulu.mutation.Stream(); ok { _spec.SetField(usagelog.FieldStream, field.TypeBool, value) } @@ -1121,39 +967,6 @@ func (ulu *UsageLogUpdate) sqlSave(ctx context.Context) (n int, err error) { if value, ok := ulu.mutation.ReasoningEffort(); ok { _spec.SetField(usagelog.FieldReasoningEffort, field.TypeString, value) } - if value, ok := ulu.mutation.UsageAttributes(); ok { - _spec.SetField(usagelog.FieldUsageAttributes, field.TypeJSON, value) - } - if value, ok := ulu.mutation.AppendedUsageAttributes(); ok { - _spec.AddModifier(func(u *sql.UpdateBuilder) { - sqljson.Append(u, usagelog.FieldUsageAttributes, value) - }) - } - if ulu.mutation.UsageAttributesCleared() { - _spec.ClearField(usagelog.FieldUsageAttributes, field.TypeJSON) - } - if value, ok := ulu.mutation.UsageMetrics(); ok { - _spec.SetField(usagelog.FieldUsageMetrics, field.TypeJSON, value) - } - if value, ok := ulu.mutation.AppendedUsageMetrics(); ok { - _spec.AddModifier(func(u *sql.UpdateBuilder) { - sqljson.Append(u, usagelog.FieldUsageMetrics, value) - }) - } - if ulu.mutation.UsageMetricsCleared() { - _spec.ClearField(usagelog.FieldUsageMetrics, field.TypeJSON) - } - if value, ok := ulu.mutation.UsageCostDetails(); ok { - _spec.SetField(usagelog.FieldUsageCostDetails, field.TypeJSON, value) - } - if value, ok := ulu.mutation.AppendedUsageCostDetails(); ok { - _spec.AddModifier(func(u *sql.UpdateBuilder) { - sqljson.Append(u, usagelog.FieldUsageCostDetails, value) - }) - } - if ulu.mutation.UsageCostDetailsCleared() { - _spec.ClearField(usagelog.FieldUsageCostDetails, field.TypeJSON) - } if value, ok := ulu.mutation.UsageMetadata(); ok { _spec.SetField(usagelog.FieldUsageMetadata, field.TypeJSON, value) } @@ -1417,48 +1230,6 @@ func (uluo *UsageLogUpdateOne) AddCacheCreationTokens(i int) *UsageLogUpdateOne return uluo } -// SetCacheCreation5mTokens sets the "cache_creation_5m_tokens" field. -func (uluo *UsageLogUpdateOne) SetCacheCreation5mTokens(i int) *UsageLogUpdateOne { - uluo.mutation.ResetCacheCreation5mTokens() - uluo.mutation.SetCacheCreation5mTokens(i) - return uluo -} - -// SetNillableCacheCreation5mTokens sets the "cache_creation_5m_tokens" field if the given value is not nil. -func (uluo *UsageLogUpdateOne) SetNillableCacheCreation5mTokens(i *int) *UsageLogUpdateOne { - if i != nil { - uluo.SetCacheCreation5mTokens(*i) - } - return uluo -} - -// AddCacheCreation5mTokens adds i to the "cache_creation_5m_tokens" field. -func (uluo *UsageLogUpdateOne) AddCacheCreation5mTokens(i int) *UsageLogUpdateOne { - uluo.mutation.AddCacheCreation5mTokens(i) - return uluo -} - -// SetCacheCreation1hTokens sets the "cache_creation_1h_tokens" field. -func (uluo *UsageLogUpdateOne) SetCacheCreation1hTokens(i int) *UsageLogUpdateOne { - uluo.mutation.ResetCacheCreation1hTokens() - uluo.mutation.SetCacheCreation1hTokens(i) - return uluo -} - -// SetNillableCacheCreation1hTokens sets the "cache_creation_1h_tokens" field if the given value is not nil. -func (uluo *UsageLogUpdateOne) SetNillableCacheCreation1hTokens(i *int) *UsageLogUpdateOne { - if i != nil { - uluo.SetCacheCreation1hTokens(*i) - } - return uluo -} - -// AddCacheCreation1hTokens adds i to the "cache_creation_1h_tokens" field. -func (uluo *UsageLogUpdateOne) AddCacheCreation1hTokens(i int) *UsageLogUpdateOne { - uluo.mutation.AddCacheCreation1hTokens(i) - return uluo -} - // SetReasoningOutputTokens sets the "reasoning_output_tokens" field. func (uluo *UsageLogUpdateOne) SetReasoningOutputTokens(i int) *UsageLogUpdateOne { uluo.mutation.ResetReasoningOutputTokens() @@ -1564,27 +1335,6 @@ func (uluo *UsageLogUpdateOne) AddCacheCreationPrice(f float64) *UsageLogUpdateO return uluo } -// SetCacheCreation1hPrice sets the "cache_creation_1h_price" field. -func (uluo *UsageLogUpdateOne) SetCacheCreation1hPrice(f float64) *UsageLogUpdateOne { - uluo.mutation.ResetCacheCreation1hPrice() - uluo.mutation.SetCacheCreation1hPrice(f) - return uluo -} - -// SetNillableCacheCreation1hPrice sets the "cache_creation_1h_price" field if the given value is not nil. -func (uluo *UsageLogUpdateOne) SetNillableCacheCreation1hPrice(f *float64) *UsageLogUpdateOne { - if f != nil { - uluo.SetCacheCreation1hPrice(*f) - } - return uluo -} - -// AddCacheCreation1hPrice adds f to the "cache_creation_1h_price" field. -func (uluo *UsageLogUpdateOne) AddCacheCreation1hPrice(f float64) *UsageLogUpdateOne { - uluo.mutation.AddCacheCreation1hPrice(f) - return uluo -} - // SetInputCost sets the "input_cost" field. func (uluo *UsageLogUpdateOne) SetInputCost(f float64) *UsageLogUpdateOne { uluo.mutation.ResetInputCost() @@ -1830,20 +1580,6 @@ func (uluo *UsageLogUpdateOne) SetNillableServiceTier(s *string) *UsageLogUpdate return uluo } -// SetImageSize sets the "image_size" field. -func (uluo *UsageLogUpdateOne) SetImageSize(s string) *UsageLogUpdateOne { - uluo.mutation.SetImageSize(s) - return uluo -} - -// SetNillableImageSize sets the "image_size" field if the given value is not nil. -func (uluo *UsageLogUpdateOne) SetNillableImageSize(s *string) *UsageLogUpdateOne { - if s != nil { - uluo.SetImageSize(*s) - } - return uluo -} - // SetStream sets the "stream" field. func (uluo *UsageLogUpdateOne) SetStream(b bool) *UsageLogUpdateOne { uluo.mutation.SetStream(b) @@ -1956,60 +1692,6 @@ func (uluo *UsageLogUpdateOne) SetNillableReasoningEffort(s *string) *UsageLogUp return uluo } -// SetUsageAttributes sets the "usage_attributes" field. -func (uluo *UsageLogUpdateOne) SetUsageAttributes(sa []sdk.UsageAttribute) *UsageLogUpdateOne { - uluo.mutation.SetUsageAttributes(sa) - return uluo -} - -// AppendUsageAttributes appends sa to the "usage_attributes" field. -func (uluo *UsageLogUpdateOne) AppendUsageAttributes(sa []sdk.UsageAttribute) *UsageLogUpdateOne { - uluo.mutation.AppendUsageAttributes(sa) - return uluo -} - -// ClearUsageAttributes clears the value of the "usage_attributes" field. -func (uluo *UsageLogUpdateOne) ClearUsageAttributes() *UsageLogUpdateOne { - uluo.mutation.ClearUsageAttributes() - return uluo -} - -// SetUsageMetrics sets the "usage_metrics" field. -func (uluo *UsageLogUpdateOne) SetUsageMetrics(sm []sdk.UsageMetric) *UsageLogUpdateOne { - uluo.mutation.SetUsageMetrics(sm) - return uluo -} - -// AppendUsageMetrics appends sm to the "usage_metrics" field. -func (uluo *UsageLogUpdateOne) AppendUsageMetrics(sm []sdk.UsageMetric) *UsageLogUpdateOne { - uluo.mutation.AppendUsageMetrics(sm) - return uluo -} - -// ClearUsageMetrics clears the value of the "usage_metrics" field. -func (uluo *UsageLogUpdateOne) ClearUsageMetrics() *UsageLogUpdateOne { - uluo.mutation.ClearUsageMetrics() - return uluo -} - -// SetUsageCostDetails sets the "usage_cost_details" field. -func (uluo *UsageLogUpdateOne) SetUsageCostDetails(scd []sdk.UsageCostDetail) *UsageLogUpdateOne { - uluo.mutation.SetUsageCostDetails(scd) - return uluo -} - -// AppendUsageCostDetails appends scd to the "usage_cost_details" field. -func (uluo *UsageLogUpdateOne) AppendUsageCostDetails(scd []sdk.UsageCostDetail) *UsageLogUpdateOne { - uluo.mutation.AppendUsageCostDetails(scd) - return uluo -} - -// ClearUsageCostDetails clears the value of the "usage_cost_details" field. -func (uluo *UsageLogUpdateOne) ClearUsageCostDetails() *UsageLogUpdateOne { - uluo.mutation.ClearUsageCostDetails() - return uluo -} - // SetUsageMetadata sets the "usage_metadata" field. func (uluo *UsageLogUpdateOne) SetUsageMetadata(m map[string]string) *UsageLogUpdateOne { uluo.mutation.SetUsageMetadata(m) @@ -2276,18 +1958,6 @@ func (uluo *UsageLogUpdateOne) sqlSave(ctx context.Context) (_node *UsageLog, er if value, ok := uluo.mutation.AddedCacheCreationTokens(); ok { _spec.AddField(usagelog.FieldCacheCreationTokens, field.TypeInt, value) } - if value, ok := uluo.mutation.CacheCreation5mTokens(); ok { - _spec.SetField(usagelog.FieldCacheCreation5mTokens, field.TypeInt, value) - } - if value, ok := uluo.mutation.AddedCacheCreation5mTokens(); ok { - _spec.AddField(usagelog.FieldCacheCreation5mTokens, field.TypeInt, value) - } - if value, ok := uluo.mutation.CacheCreation1hTokens(); ok { - _spec.SetField(usagelog.FieldCacheCreation1hTokens, field.TypeInt, value) - } - if value, ok := uluo.mutation.AddedCacheCreation1hTokens(); ok { - _spec.AddField(usagelog.FieldCacheCreation1hTokens, field.TypeInt, value) - } if value, ok := uluo.mutation.ReasoningOutputTokens(); ok { _spec.SetField(usagelog.FieldReasoningOutputTokens, field.TypeInt, value) } @@ -2318,12 +1988,6 @@ func (uluo *UsageLogUpdateOne) sqlSave(ctx context.Context) (_node *UsageLog, er if value, ok := uluo.mutation.AddedCacheCreationPrice(); ok { _spec.AddField(usagelog.FieldCacheCreationPrice, field.TypeFloat64, value) } - if value, ok := uluo.mutation.CacheCreation1hPrice(); ok { - _spec.SetField(usagelog.FieldCacheCreation1hPrice, field.TypeFloat64, value) - } - if value, ok := uluo.mutation.AddedCacheCreation1hPrice(); ok { - _spec.AddField(usagelog.FieldCacheCreation1hPrice, field.TypeFloat64, value) - } if value, ok := uluo.mutation.InputCost(); ok { _spec.SetField(usagelog.FieldInputCost, field.TypeFloat64, value) } @@ -2393,9 +2057,6 @@ func (uluo *UsageLogUpdateOne) sqlSave(ctx context.Context) (_node *UsageLog, er if value, ok := uluo.mutation.ServiceTier(); ok { _spec.SetField(usagelog.FieldServiceTier, field.TypeString, value) } - if value, ok := uluo.mutation.ImageSize(); ok { - _spec.SetField(usagelog.FieldImageSize, field.TypeString, value) - } if value, ok := uluo.mutation.Stream(); ok { _spec.SetField(usagelog.FieldStream, field.TypeBool, value) } @@ -2423,39 +2084,6 @@ func (uluo *UsageLogUpdateOne) sqlSave(ctx context.Context) (_node *UsageLog, er if value, ok := uluo.mutation.ReasoningEffort(); ok { _spec.SetField(usagelog.FieldReasoningEffort, field.TypeString, value) } - if value, ok := uluo.mutation.UsageAttributes(); ok { - _spec.SetField(usagelog.FieldUsageAttributes, field.TypeJSON, value) - } - if value, ok := uluo.mutation.AppendedUsageAttributes(); ok { - _spec.AddModifier(func(u *sql.UpdateBuilder) { - sqljson.Append(u, usagelog.FieldUsageAttributes, value) - }) - } - if uluo.mutation.UsageAttributesCleared() { - _spec.ClearField(usagelog.FieldUsageAttributes, field.TypeJSON) - } - if value, ok := uluo.mutation.UsageMetrics(); ok { - _spec.SetField(usagelog.FieldUsageMetrics, field.TypeJSON, value) - } - if value, ok := uluo.mutation.AppendedUsageMetrics(); ok { - _spec.AddModifier(func(u *sql.UpdateBuilder) { - sqljson.Append(u, usagelog.FieldUsageMetrics, value) - }) - } - if uluo.mutation.UsageMetricsCleared() { - _spec.ClearField(usagelog.FieldUsageMetrics, field.TypeJSON) - } - if value, ok := uluo.mutation.UsageCostDetails(); ok { - _spec.SetField(usagelog.FieldUsageCostDetails, field.TypeJSON, value) - } - if value, ok := uluo.mutation.AppendedUsageCostDetails(); ok { - _spec.AddModifier(func(u *sql.UpdateBuilder) { - sqljson.Append(u, usagelog.FieldUsageCostDetails, value) - }) - } - if uluo.mutation.UsageCostDetailsCleared() { - _spec.ClearField(usagelog.FieldUsageCostDetails, field.TypeJSON) - } if value, ok := uluo.mutation.UsageMetadata(); ok { _spec.SetField(usagelog.FieldUsageMetadata, field.TypeJSON, value) } diff --git a/backend/ent/user.go b/backend/ent/user.go index 048a8ac2..189330ce 100644 --- a/backend/ent/user.go +++ b/backend/ent/user.go @@ -10,7 +10,7 @@ import ( "entgo.io/ent" "entgo.io/ent/dialect/sql" - "github.com/DouDOU-start/airgate-core/ent/user" + "github.com/DevilGenius/airgate-core/ent/user" ) // User is the model entity for the User schema. diff --git a/backend/ent/user/where.go b/backend/ent/user/where.go index 263f62a5..27be7cc3 100644 --- a/backend/ent/user/where.go +++ b/backend/ent/user/where.go @@ -7,7 +7,7 @@ import ( "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" - "github.com/DouDOU-start/airgate-core/ent/predicate" + "github.com/DevilGenius/airgate-core/ent/predicate" ) // ID filters vertices based on their ID field. diff --git a/backend/ent/user_create.go b/backend/ent/user_create.go index 0431a3c8..22dac2bf 100644 --- a/backend/ent/user_create.go +++ b/backend/ent/user_create.go @@ -10,12 +10,12 @@ import ( "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" - "github.com/DouDOU-start/airgate-core/ent/apikey" - "github.com/DouDOU-start/airgate-core/ent/balancelog" - "github.com/DouDOU-start/airgate-core/ent/group" - "github.com/DouDOU-start/airgate-core/ent/usagelog" - "github.com/DouDOU-start/airgate-core/ent/user" - "github.com/DouDOU-start/airgate-core/ent/usersubscription" + "github.com/DevilGenius/airgate-core/ent/apikey" + "github.com/DevilGenius/airgate-core/ent/balancelog" + "github.com/DevilGenius/airgate-core/ent/group" + "github.com/DevilGenius/airgate-core/ent/usagelog" + "github.com/DevilGenius/airgate-core/ent/user" + "github.com/DevilGenius/airgate-core/ent/usersubscription" ) // UserCreate is the builder for creating a User entity. diff --git a/backend/ent/user_delete.go b/backend/ent/user_delete.go index 56af169e..36c5aa6e 100644 --- a/backend/ent/user_delete.go +++ b/backend/ent/user_delete.go @@ -8,8 +8,8 @@ import ( "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" - "github.com/DouDOU-start/airgate-core/ent/predicate" - "github.com/DouDOU-start/airgate-core/ent/user" + "github.com/DevilGenius/airgate-core/ent/predicate" + "github.com/DevilGenius/airgate-core/ent/user" ) // UserDelete is the builder for deleting a User entity. diff --git a/backend/ent/user_query.go b/backend/ent/user_query.go index 97169416..395f8742 100644 --- a/backend/ent/user_query.go +++ b/backend/ent/user_query.go @@ -11,13 +11,13 @@ import ( "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" - "github.com/DouDOU-start/airgate-core/ent/apikey" - "github.com/DouDOU-start/airgate-core/ent/balancelog" - "github.com/DouDOU-start/airgate-core/ent/group" - "github.com/DouDOU-start/airgate-core/ent/predicate" - "github.com/DouDOU-start/airgate-core/ent/usagelog" - "github.com/DouDOU-start/airgate-core/ent/user" - "github.com/DouDOU-start/airgate-core/ent/usersubscription" + "github.com/DevilGenius/airgate-core/ent/apikey" + "github.com/DevilGenius/airgate-core/ent/balancelog" + "github.com/DevilGenius/airgate-core/ent/group" + "github.com/DevilGenius/airgate-core/ent/predicate" + "github.com/DevilGenius/airgate-core/ent/usagelog" + "github.com/DevilGenius/airgate-core/ent/user" + "github.com/DevilGenius/airgate-core/ent/usersubscription" ) // UserQuery is the builder for querying User entities. diff --git a/backend/ent/user_update.go b/backend/ent/user_update.go index ac34fd60..8c11cd2d 100644 --- a/backend/ent/user_update.go +++ b/backend/ent/user_update.go @@ -11,13 +11,13 @@ import ( "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" - "github.com/DouDOU-start/airgate-core/ent/apikey" - "github.com/DouDOU-start/airgate-core/ent/balancelog" - "github.com/DouDOU-start/airgate-core/ent/group" - "github.com/DouDOU-start/airgate-core/ent/predicate" - "github.com/DouDOU-start/airgate-core/ent/usagelog" - "github.com/DouDOU-start/airgate-core/ent/user" - "github.com/DouDOU-start/airgate-core/ent/usersubscription" + "github.com/DevilGenius/airgate-core/ent/apikey" + "github.com/DevilGenius/airgate-core/ent/balancelog" + "github.com/DevilGenius/airgate-core/ent/group" + "github.com/DevilGenius/airgate-core/ent/predicate" + "github.com/DevilGenius/airgate-core/ent/usagelog" + "github.com/DevilGenius/airgate-core/ent/user" + "github.com/DevilGenius/airgate-core/ent/usersubscription" ) // UserUpdate is the builder for updating User entities. diff --git a/backend/ent/usersubscription.go b/backend/ent/usersubscription.go index 37a213bd..1ed1c721 100644 --- a/backend/ent/usersubscription.go +++ b/backend/ent/usersubscription.go @@ -10,9 +10,9 @@ import ( "entgo.io/ent" "entgo.io/ent/dialect/sql" - "github.com/DouDOU-start/airgate-core/ent/group" - "github.com/DouDOU-start/airgate-core/ent/user" - "github.com/DouDOU-start/airgate-core/ent/usersubscription" + "github.com/DevilGenius/airgate-core/ent/group" + "github.com/DevilGenius/airgate-core/ent/user" + "github.com/DevilGenius/airgate-core/ent/usersubscription" ) // UserSubscription is the model entity for the UserSubscription schema. diff --git a/backend/ent/usersubscription/where.go b/backend/ent/usersubscription/where.go index 353377f8..60053a4e 100644 --- a/backend/ent/usersubscription/where.go +++ b/backend/ent/usersubscription/where.go @@ -7,7 +7,7 @@ import ( "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" - "github.com/DouDOU-start/airgate-core/ent/predicate" + "github.com/DevilGenius/airgate-core/ent/predicate" ) // ID filters vertices based on their ID field. diff --git a/backend/ent/usersubscription_create.go b/backend/ent/usersubscription_create.go index 3d2ac70d..38dda85d 100644 --- a/backend/ent/usersubscription_create.go +++ b/backend/ent/usersubscription_create.go @@ -10,9 +10,9 @@ import ( "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" - "github.com/DouDOU-start/airgate-core/ent/group" - "github.com/DouDOU-start/airgate-core/ent/user" - "github.com/DouDOU-start/airgate-core/ent/usersubscription" + "github.com/DevilGenius/airgate-core/ent/group" + "github.com/DevilGenius/airgate-core/ent/user" + "github.com/DevilGenius/airgate-core/ent/usersubscription" ) // UserSubscriptionCreate is the builder for creating a UserSubscription entity. diff --git a/backend/ent/usersubscription_delete.go b/backend/ent/usersubscription_delete.go index 683eb42d..cf39bd1c 100644 --- a/backend/ent/usersubscription_delete.go +++ b/backend/ent/usersubscription_delete.go @@ -8,8 +8,8 @@ import ( "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" - "github.com/DouDOU-start/airgate-core/ent/predicate" - "github.com/DouDOU-start/airgate-core/ent/usersubscription" + "github.com/DevilGenius/airgate-core/ent/predicate" + "github.com/DevilGenius/airgate-core/ent/usersubscription" ) // UserSubscriptionDelete is the builder for deleting a UserSubscription entity. diff --git a/backend/ent/usersubscription_query.go b/backend/ent/usersubscription_query.go index a82ee82c..d4080083 100644 --- a/backend/ent/usersubscription_query.go +++ b/backend/ent/usersubscription_query.go @@ -10,10 +10,10 @@ import ( "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" - "github.com/DouDOU-start/airgate-core/ent/group" - "github.com/DouDOU-start/airgate-core/ent/predicate" - "github.com/DouDOU-start/airgate-core/ent/user" - "github.com/DouDOU-start/airgate-core/ent/usersubscription" + "github.com/DevilGenius/airgate-core/ent/group" + "github.com/DevilGenius/airgate-core/ent/predicate" + "github.com/DevilGenius/airgate-core/ent/user" + "github.com/DevilGenius/airgate-core/ent/usersubscription" ) // UserSubscriptionQuery is the builder for querying UserSubscription entities. diff --git a/backend/ent/usersubscription_update.go b/backend/ent/usersubscription_update.go index 99df46e2..ad809642 100644 --- a/backend/ent/usersubscription_update.go +++ b/backend/ent/usersubscription_update.go @@ -11,10 +11,10 @@ import ( "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqlgraph" "entgo.io/ent/schema/field" - "github.com/DouDOU-start/airgate-core/ent/group" - "github.com/DouDOU-start/airgate-core/ent/predicate" - "github.com/DouDOU-start/airgate-core/ent/user" - "github.com/DouDOU-start/airgate-core/ent/usersubscription" + "github.com/DevilGenius/airgate-core/ent/group" + "github.com/DevilGenius/airgate-core/ent/predicate" + "github.com/DevilGenius/airgate-core/ent/user" + "github.com/DevilGenius/airgate-core/ent/usersubscription" ) // UserSubscriptionUpdate is the builder for updating UserSubscription entities. diff --git a/backend/go.mod b/backend/go.mod index 5e827f5b..3f477056 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -1,10 +1,10 @@ -module github.com/DouDOU-start/airgate-core +module github.com/DevilGenius/airgate-core go 1.25.7 require ( entgo.io/ent v0.13.1 - github.com/DouDOU-start/airgate-sdk v0.2.1 + github.com/DevilGenius/airgate-sdk v0.2.1 github.com/gin-gonic/gin v1.10.0 github.com/golang-jwt/jwt/v5 v5.3.1 github.com/google/uuid v1.6.0 diff --git a/backend/go.sum b/backend/go.sum index 3244318b..aa096d5a 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -4,8 +4,6 @@ entgo.io/ent v0.13.1 h1:uD8QwN1h6SNphdCCzmkMN3feSUzNnVvV/WIkHKMbzOE= entgo.io/ent v0.13.1/go.mod h1:qCEmo+biw3ccBn9OyL4ZK5dfpwg++l1Gxwac5B1206A= github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= -github.com/DouDOU-start/airgate-sdk v0.2.1 h1:MylrFMifIJy5mT1wSFdXcSvAXjIcB6itp4dw8pP4pes= -github.com/DouDOU-start/airgate-sdk v0.2.1/go.mod h1:784vC4lIfCnUdOroWuvn55Dv/b9TucVMpFwuu40gc0Q= github.com/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8= github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw= diff --git a/backend/internal/app/account/oauth_plan_filter.go b/backend/internal/app/account/oauth_plan_filter.go index 4e983562..dfdc68e4 100644 --- a/backend/internal/app/account/oauth_plan_filter.go +++ b/backend/internal/app/account/oauth_plan_filter.go @@ -4,7 +4,7 @@ import ( "encoding/json" "strings" - "github.com/DouDOU-start/airgate-core/internal/plugin" + "github.com/DevilGenius/airgate-core/internal/plugin" ) const ( diff --git a/backend/internal/app/account/service.go b/backend/internal/app/account/service.go index df016d30..5c86e1c1 100644 --- a/backend/internal/app/account/service.go +++ b/backend/internal/app/account/service.go @@ -17,10 +17,10 @@ import ( "github.com/redis/go-redis/v9" "golang.org/x/sync/singleflight" - sdk "github.com/DouDOU-start/airgate-sdk/sdkgo" + sdk "github.com/DevilGenius/airgate-sdk/sdkgo" - "github.com/DouDOU-start/airgate-core/internal/pkg/timezone" - "github.com/DouDOU-start/airgate-core/internal/plugin" + "github.com/DevilGenius/airgate-core/internal/pkg/timezone" + "github.com/DevilGenius/airgate-core/internal/plugin" ) // PluginCatalog 账号域需要的插件能力集合。 @@ -523,6 +523,11 @@ func connectivityTestErrorMessage(outcome sdk.ForwardOutcome) string { return "上游账号不可用: " + reason } return "上游账号不可用,请检查凭证或账号状态" + case sdk.OutcomeAccountUnavailable: + if reason != "" { + return "上游账号403暂不可用: " + reason + } + return "上游账号403暂不可用,请稍后重试" case sdk.OutcomeStreamAborted: return "上游响应流中断,请稍后重试或查看上游日志" case sdk.OutcomeUpstreamTransient: diff --git a/backend/internal/app/account/service_test.go b/backend/internal/app/account/service_test.go index 8a0eb395..daaeb4d8 100644 --- a/backend/internal/app/account/service_test.go +++ b/backend/internal/app/account/service_test.go @@ -7,9 +7,9 @@ import ( "testing" "time" - sdk "github.com/DouDOU-start/airgate-sdk/sdkgo" + sdk "github.com/DevilGenius/airgate-sdk/sdkgo" - "github.com/DouDOU-start/airgate-core/internal/plugin" + "github.com/DevilGenius/airgate-core/internal/plugin" ) func TestImportIgnoresEnvironmentScopedIDs(t *testing.T) { @@ -818,6 +818,14 @@ func TestConnectivityTestErrorMessage(t *testing.T) { }, want: "上游账号不可用: HTTP 401: Your authentication token has been invalidated", }, + { + name: "账号暂时不可用使用统一提示", + outcome: sdk.ForwardOutcome{ + Kind: sdk.OutcomeAccountUnavailable, + Reason: "HTTP 403: 访问被拒绝,账号可能已被禁用或无权限", + }, + want: "上游账号403暂不可用: HTTP 403: 访问被拒绝,账号可能已被禁用或无权限", + }, } for _, c := range cases { diff --git a/backend/internal/app/account/stats.go b/backend/internal/app/account/stats.go index 82cf0b34..78dc94d4 100644 --- a/backend/internal/app/account/stats.go +++ b/backend/internal/app/account/stats.go @@ -4,8 +4,8 @@ import ( "sort" "time" - "github.com/DouDOU-start/airgate-core/internal/pkg/timezone" - "github.com/DouDOU-start/airgate-core/internal/pkg/usagemodel" + "github.com/DevilGenius/airgate-core/internal/pkg/timezone" + "github.com/DevilGenius/airgate-core/internal/pkg/usagemodel" ) // isImageModel 判断 usage_log.model 是否属于生图家族。 diff --git a/backend/internal/app/apikey/service.go b/backend/internal/app/apikey/service.go index 66ff9f02..1a0f65aa 100644 --- a/backend/internal/app/apikey/service.go +++ b/backend/internal/app/apikey/service.go @@ -5,9 +5,9 @@ import ( "log/slog" "time" - "github.com/DouDOU-start/airgate-core/internal/auth" - "github.com/DouDOU-start/airgate-core/internal/pkg/timezone" - sdk "github.com/DouDOU-start/airgate-sdk/sdkgo" + "github.com/DevilGenius/airgate-core/internal/auth" + "github.com/DevilGenius/airgate-core/internal/pkg/timezone" + sdk "github.com/DevilGenius/airgate-sdk/sdkgo" ) const ( diff --git a/backend/internal/app/apikey/service_test.go b/backend/internal/app/apikey/service_test.go index 27d58cbf..4b039f10 100644 --- a/backend/internal/app/apikey/service_test.go +++ b/backend/internal/app/apikey/service_test.go @@ -7,7 +7,7 @@ import ( "testing" "time" - corauth "github.com/DouDOU-start/airgate-core/internal/auth" + corauth "github.com/DevilGenius/airgate-core/internal/auth" ) const testAPIKeySecret = "00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff" diff --git a/backend/internal/app/auth/service.go b/backend/internal/app/auth/service.go index ce75d444..e917f38f 100644 --- a/backend/internal/app/auth/service.go +++ b/backend/internal/app/auth/service.go @@ -7,8 +7,8 @@ import ( "golang.org/x/crypto/bcrypt" - corauth "github.com/DouDOU-start/airgate-core/internal/auth" - sdk "github.com/DouDOU-start/airgate-sdk/sdkgo" + corauth "github.com/DevilGenius/airgate-core/internal/auth" + sdk "github.com/DevilGenius/airgate-sdk/sdkgo" ) // Service 提供认证域用例编排。 diff --git a/backend/internal/app/auth/service_test.go b/backend/internal/app/auth/service_test.go index 6ce01425..dfe104a6 100644 --- a/backend/internal/app/auth/service_test.go +++ b/backend/internal/app/auth/service_test.go @@ -8,7 +8,7 @@ import ( "golang.org/x/crypto/bcrypt" - corauth "github.com/DouDOU-start/airgate-core/internal/auth" + corauth "github.com/DevilGenius/airgate-core/internal/auth" ) func TestRegisterRejectsDuplicateEmail(t *testing.T) { diff --git a/backend/internal/app/dashboard/service.go b/backend/internal/app/dashboard/service.go index 65d6d5f5..991e0327 100644 --- a/backend/internal/app/dashboard/service.go +++ b/backend/internal/app/dashboard/service.go @@ -10,8 +10,8 @@ import ( "github.com/google/uuid" "github.com/redis/go-redis/v9" - "github.com/DouDOU-start/airgate-core/internal/pkg/timezone" - sdk "github.com/DouDOU-start/airgate-sdk/sdkgo" + "github.com/DevilGenius/airgate-core/internal/pkg/timezone" + sdk "github.com/DevilGenius/airgate-sdk/sdkgo" ) // Service 提供仪表盘用例编排。 diff --git a/backend/internal/app/group/service.go b/backend/internal/app/group/service.go index 333a30a9..8678cdb8 100644 --- a/backend/internal/app/group/service.go +++ b/backend/internal/app/group/service.go @@ -4,8 +4,8 @@ import ( "context" "time" - "github.com/DouDOU-start/airgate-core/internal/pkg/timezone" - sdk "github.com/DouDOU-start/airgate-sdk/sdkgo" + "github.com/DevilGenius/airgate-core/internal/pkg/timezone" + sdk "github.com/DevilGenius/airgate-sdk/sdkgo" ) // Service 提供分组域用例编排。 diff --git a/backend/internal/app/openclaw/service.go b/backend/internal/app/openclaw/service.go index 94ecf9c2..9c0b382a 100644 --- a/backend/internal/app/openclaw/service.go +++ b/backend/internal/app/openclaw/service.go @@ -7,8 +7,8 @@ import ( "strings" "text/template" - appsettings "github.com/DouDOU-start/airgate-core/internal/app/settings" - sdk "github.com/DouDOU-start/airgate-sdk/sdkgo" + appsettings "github.com/DevilGenius/airgate-core/internal/app/settings" + sdk "github.com/DevilGenius/airgate-sdk/sdkgo" ) // Service 提供 OpenClaw 一键接入相关的领域用例。 diff --git a/backend/internal/app/pluginadmin/service.go b/backend/internal/app/pluginadmin/service.go index 565c97a3..60b09929 100644 --- a/backend/internal/app/pluginadmin/service.go +++ b/backend/internal/app/pluginadmin/service.go @@ -5,7 +5,7 @@ import ( "strconv" "strings" - sdk "github.com/DouDOU-start/airgate-sdk/sdkgo" + sdk "github.com/DevilGenius/airgate-sdk/sdkgo" ) // Service 提供插件管理用例编排。 @@ -101,17 +101,20 @@ func (s *Service) Upload(ctx context.Context, name string, binary []byte) error } // InstallFromGithub 从 GitHub 安装插件。 -func (s *Service) InstallFromGithub(ctx context.Context, repo string) error { +func (s *Service) InstallFromGithub(ctx context.Context, repo, version string) error { logger := sdk.LoggerFromContext(ctx) - if err := s.manager.InstallFromGithub(ctx, repo); err != nil { + version = strings.TrimSpace(version) + if err := s.manager.InstallFromGithub(ctx, repo, version); err != nil { logger.Error("plugin_admin_uploaded_failed", "repo", repo, + "version", version, "source", "github", sdk.LogFieldError, err) return err } logger.Info("plugin_admin_uploaded", "repo", repo, + "version", version, "source", "github") return nil } diff --git a/backend/internal/app/pluginadmin/service_test.go b/backend/internal/app/pluginadmin/service_test.go index 64b6cec1..975a576a 100644 --- a/backend/internal/app/pluginadmin/service_test.go +++ b/backend/internal/app/pluginadmin/service_test.go @@ -4,7 +4,7 @@ import ( "context" "testing" - "github.com/DouDOU-start/airgate-core/internal/plugin" + "github.com/DevilGenius/airgate-core/internal/plugin" ) func TestReloadRejectsNonDevPlugin(t *testing.T) { @@ -58,7 +58,7 @@ func (s pluginAdminManagerStub) GetAllPluginMeta() []plugin.PluginMeta { return append([]plugin.PluginMeta(nil), s.allMeta...) } func (s pluginAdminManagerStub) InstallFromBinary(context.Context, string, []byte) error { return nil } -func (s pluginAdminManagerStub) InstallFromGithub(context.Context, string) error { return nil } +func (s pluginAdminManagerStub) InstallFromGithub(context.Context, string, string) error { return nil } func (s pluginAdminManagerStub) Uninstall(context.Context, string) error { return nil } func (s pluginAdminManagerStub) ReloadDev(context.Context, string) error { return nil } func (s pluginAdminManagerStub) ReloadInstance(context.Context, string) error { return nil } diff --git a/backend/internal/app/pluginadmin/types.go b/backend/internal/app/pluginadmin/types.go index cf8f357f..d6747cee 100644 --- a/backend/internal/app/pluginadmin/types.go +++ b/backend/internal/app/pluginadmin/types.go @@ -4,15 +4,15 @@ import ( "context" "net/http" - "github.com/DouDOU-start/airgate-core/internal/plugin" - sdk "github.com/DouDOU-start/airgate-sdk/sdkgo" + "github.com/DevilGenius/airgate-core/internal/plugin" + sdk "github.com/DevilGenius/airgate-sdk/sdkgo" ) // Manager 定义插件管理服务所需能力。 type Manager interface { GetAllPluginMeta() []plugin.PluginMeta InstallFromBinary(context.Context, string, []byte) error - InstallFromGithub(context.Context, string) error + InstallFromGithub(context.Context, string, string) error Uninstall(context.Context, string) error ReloadDev(context.Context, string) error ReloadInstance(context.Context, string) error diff --git a/backend/internal/app/proxy/service.go b/backend/internal/app/proxy/service.go index b8a0b988..7300f394 100644 --- a/backend/internal/app/proxy/service.go +++ b/backend/internal/app/proxy/service.go @@ -15,7 +15,7 @@ import ( xproxy "golang.org/x/net/proxy" - sdk "github.com/DouDOU-start/airgate-sdk/sdkgo" + sdk "github.com/DevilGenius/airgate-sdk/sdkgo" ) // Service 提供代理域用例编排。 diff --git a/backend/internal/app/settings/service.go b/backend/internal/app/settings/service.go index 00b8c53d..5afb9839 100644 --- a/backend/internal/app/settings/service.go +++ b/backend/internal/app/settings/service.go @@ -3,7 +3,7 @@ package settings import ( "context" - sdk "github.com/DouDOU-start/airgate-sdk/sdkgo" + sdk "github.com/DevilGenius/airgate-sdk/sdkgo" ) // Service 提供设置域用例编排。 diff --git a/backend/internal/app/subscription/service.go b/backend/internal/app/subscription/service.go index 852d979b..f5986f90 100644 --- a/backend/internal/app/subscription/service.go +++ b/backend/internal/app/subscription/service.go @@ -4,7 +4,7 @@ import ( "context" "time" - sdk "github.com/DouDOU-start/airgate-sdk/sdkgo" + sdk "github.com/DevilGenius/airgate-sdk/sdkgo" ) // Service 提供订阅域用例编排。 diff --git a/backend/internal/app/usage/service.go b/backend/internal/app/usage/service.go index aad146f4..3cd71325 100644 --- a/backend/internal/app/usage/service.go +++ b/backend/internal/app/usage/service.go @@ -13,7 +13,7 @@ import ( "github.com/google/uuid" "github.com/redis/go-redis/v9" - sdk "github.com/DouDOU-start/airgate-sdk/sdkgo" + sdk "github.com/DevilGenius/airgate-sdk/sdkgo" ) // Service 使用记录用例服务。 @@ -51,10 +51,13 @@ var usageCacheLockReleaseScript = redis.NewScript(` // ListUser 查询当前用户的使用记录。 func (s *Service) ListUser(ctx context.Context, userID int64, filter ListFilter) (ListResult, error) { page, pageSize := NormalizePage(filter.Page, filter.PageSize) + if filter.BeforeID <= 0 { + page = 1 + } filter.Page = page filter.PageSize = pageSize - list, total, err := s.repo.ListUser(ctx, userID, filter) + list, hasMore, nextCursor, err := s.repo.ListUser(ctx, userID, filter) if err != nil { sdk.LoggerFromContext(ctx).Error("usage_query_failed", "scope", "user_list", @@ -64,10 +67,13 @@ func (s *Service) ListUser(ctx context.Context, userID int64, filter ListFilter) } return ListResult{ - List: list, - Total: total, - Page: page, - PageSize: pageSize, + List: list, + Total: usageListTotal(page, pageSize, len(list), hasMore), + Page: page, + PageSize: pageSize, + HasMore: hasMore, + NextCursor: nextCursor, + TotalExact: !hasMore, }, nil } @@ -117,10 +123,13 @@ func (s *Service) UserStatsWithModels(ctx context.Context, userID int64, filter // ListAdmin 查询管理员使用记录列表。 func (s *Service) ListAdmin(ctx context.Context, filter ListFilter) (ListResult, error) { page, pageSize := NormalizePage(filter.Page, filter.PageSize) + if filter.BeforeID <= 0 { + page = 1 + } filter.Page = page filter.PageSize = pageSize - list, total, err := s.repo.ListAdmin(ctx, filter) + list, hasMore, nextCursor, err := s.repo.ListAdmin(ctx, filter) if err != nil { sdk.LoggerFromContext(ctx).Error("usage_query_failed", "scope", "admin_list", @@ -129,13 +138,30 @@ func (s *Service) ListAdmin(ctx context.Context, filter ListFilter) (ListResult, } return ListResult{ - List: list, - Total: total, - Page: page, - PageSize: pageSize, + List: list, + Total: usageListTotal(page, pageSize, len(list), hasMore), + Page: page, + PageSize: pageSize, + HasMore: hasMore, + NextCursor: nextCursor, + TotalExact: !hasMore, }, nil } +func usageListTotal(page, pageSize, listLen int, hasMore bool) int64 { + if page <= 0 { + page = 1 + } + if pageSize <= 0 { + pageSize = defaultPageSize + } + total := int64((page-1)*pageSize + listLen) + if hasMore { + total++ + } + return total +} + // StatsByModel 按模型分组统计。 func (s *Service) StatsByModel(ctx context.Context, filter StatsFilter) ([]ModelStats, error) { stats, err := s.repo.StatsByModel(ctx, filter) diff --git a/backend/internal/app/usage/service_test.go b/backend/internal/app/usage/service_test.go index ebf27a08..99d92bac 100644 --- a/backend/internal/app/usage/service_test.go +++ b/backend/internal/app/usage/service_test.go @@ -68,12 +68,12 @@ type stubUsageRepository struct { trendEntriesFn func(context.Context, TrendFilter) ([]TrendEntry, error) } -func (s *stubUsageRepository) ListUser(context.Context, int64, ListFilter) ([]LogRecord, int64, error) { - return nil, 0, nil +func (s *stubUsageRepository) ListUser(context.Context, int64, ListFilter) ([]LogRecord, bool, *int64, error) { + return nil, false, nil, nil } -func (s *stubUsageRepository) ListAdmin(context.Context, ListFilter) ([]LogRecord, int64, error) { - return nil, 0, nil +func (s *stubUsageRepository) ListAdmin(context.Context, ListFilter) ([]LogRecord, bool, *int64, error) { + return nil, false, nil, nil } func (s *stubUsageRepository) SummaryUser(ctx context.Context, userID int64, filter StatsFilter) (Summary, error) { diff --git a/backend/internal/app/usage/trend.go b/backend/internal/app/usage/trend.go index eab43eac..d926f16f 100644 --- a/backend/internal/app/usage/trend.go +++ b/backend/internal/app/usage/trend.go @@ -4,7 +4,7 @@ import ( "sort" "time" - "github.com/DouDOU-start/airgate-core/internal/pkg/timezone" + "github.com/DevilGenius/airgate-core/internal/pkg/timezone" ) const ( diff --git a/backend/internal/app/usage/types.go b/backend/internal/app/usage/types.go index 86cce43b..80c20ce4 100644 --- a/backend/internal/app/usage/types.go +++ b/backend/internal/app/usage/types.go @@ -1,15 +1,12 @@ package usage -import ( - "context" - - sdk "github.com/DouDOU-start/airgate-sdk/sdkgo" -) +import "context" // ListFilter 使用记录列表筛选。 type ListFilter struct { Page int PageSize int + BeforeID int64 UserID *int64 APIKeyID *int64 AccountID *int64 @@ -64,14 +61,11 @@ type LogRecord struct { OutputTokens int CachedInputTokens int CacheCreationTokens int - CacheCreation5mTokens int - CacheCreation1hTokens int ReasoningOutputTokens int InputPrice float64 OutputPrice float64 CachedInputPrice float64 CacheCreationPrice float64 - CacheCreation1hPrice float64 InputCost float64 OutputCost float64 CachedInputCost float64 @@ -84,7 +78,6 @@ type LogRecord struct { SellRate float64 // 快照:本次生效的销售倍率(0 表示未启用 markup) AccountRateMultiplier float64 // 快照:本次生效的 account_rate ServiceTier string - ImageSize string // 图像生成请求的实际出图尺寸("WxH"),非图像请求留空 Stream bool DurationMs int64 FirstTokenMs int64 @@ -92,19 +85,19 @@ type LogRecord struct { IPAddress string Endpoint string ReasoningEffort string - UsageAttributes []sdk.UsageAttribute - UsageMetrics []sdk.UsageMetric - UsageCostDetails []sdk.UsageCostDetail UsageMetadata map[string]string CreatedAt string } // ListResult 使用记录列表结果。 type ListResult struct { - List []LogRecord - Total int64 - Page int - PageSize int + List []LogRecord + Total int64 + Page int + PageSize int + HasMore bool + NextCursor *int64 + TotalExact bool } // Summary 汇总统计。 @@ -202,8 +195,8 @@ type TrendBucket struct { // Repository 使用记录仓储接口。 type Repository interface { - ListUser(context.Context, int64, ListFilter) ([]LogRecord, int64, error) - ListAdmin(context.Context, ListFilter) ([]LogRecord, int64, error) + ListUser(context.Context, int64, ListFilter) ([]LogRecord, bool, *int64, error) + ListAdmin(context.Context, ListFilter) ([]LogRecord, bool, *int64, error) SummaryUser(context.Context, int64, StatsFilter) (Summary, error) SummaryAdmin(context.Context, StatsFilter) (Summary, error) StatsByModel(context.Context, StatsFilter) ([]ModelStats, error) diff --git a/backend/internal/app/user/service.go b/backend/internal/app/user/service.go index bac72c4d..fcfbdb9a 100644 --- a/backend/internal/app/user/service.go +++ b/backend/internal/app/user/service.go @@ -6,8 +6,8 @@ import ( "golang.org/x/crypto/bcrypt" - "github.com/DouDOU-start/airgate-core/internal/pkg/timezone" - sdk "github.com/DouDOU-start/airgate-sdk/sdkgo" + "github.com/DevilGenius/airgate-core/internal/pkg/timezone" + sdk "github.com/DevilGenius/airgate-sdk/sdkgo" ) const ( diff --git a/backend/internal/auth/apikey.go b/backend/internal/auth/apikey.go index fd34cba9..393de5c1 100644 --- a/backend/internal/auth/apikey.go +++ b/backend/internal/auth/apikey.go @@ -14,11 +14,11 @@ import ( "github.com/redis/go-redis/v9" - sdk "github.com/DouDOU-start/airgate-sdk/sdkgo" + sdk "github.com/DevilGenius/airgate-sdk/sdkgo" - "github.com/DouDOU-start/airgate-core/ent" - "github.com/DouDOU-start/airgate-core/ent/apikey" - entsetting "github.com/DouDOU-start/airgate-core/ent/setting" + "github.com/DevilGenius/airgate-core/ent" + "github.com/DevilGenius/airgate-core/ent/apikey" + entsetting "github.com/DevilGenius/airgate-core/ent/setting" ) // API Key 缓存。 diff --git a/backend/internal/auth/apikey_integration_test.go b/backend/internal/auth/apikey_integration_test.go index 7ab8d1c5..e88a8073 100644 --- a/backend/internal/auth/apikey_integration_test.go +++ b/backend/internal/auth/apikey_integration_test.go @@ -7,7 +7,7 @@ import ( "entgo.io/ent/dialect/sql/schema" _ "github.com/mattn/go-sqlite3" - "github.com/DouDOU-start/airgate-core/ent/enttest" + "github.com/DevilGenius/airgate-core/ent/enttest" ) func TestValidateAPIKeyIncludesUserEmail(t *testing.T) { diff --git a/backend/internal/auth/crypto.go b/backend/internal/auth/crypto.go index 4a12c99d..38c8af2f 100644 --- a/backend/internal/auth/crypto.go +++ b/backend/internal/auth/crypto.go @@ -10,7 +10,7 @@ import ( "io" "log/slog" - sdk "github.com/DouDOU-start/airgate-sdk/sdkgo" + sdk "github.com/DevilGenius/airgate-sdk/sdkgo" ) // deriveAESKey 从 hex 编码的 secret 中取前 32 字节作为 AES-256 密钥 diff --git a/backend/internal/billing/calculator.go b/backend/internal/billing/calculator.go index a2bc0504..2175cc64 100644 --- a/backend/internal/billing/calculator.go +++ b/backend/internal/billing/calculator.go @@ -30,10 +30,10 @@ type CalculateInput struct { // 与用户计费 (BillingRate) 完全独立,不影响 actual_cost / User.balance。 AccountRate float64 - // OutputBillingCostOverride 可覆盖 output_cost 在 actual_cost 管道里的计价结果。 - // 用于分组图片 1K/2K/4K 固定价:配置后 output 不再乘 BillingRate, - // 未配置时保持 output_cost × BillingRate 的旧行为。 - OutputBillingCostOverride *float64 + // BillingCostOverride 可覆盖 actual_cost / billed_cost 的最终计费结果。 + // 用于分组图片 1K/2K/4K 固定价:配置后整次请求按图片单张价 × 数量计费, + // 不再叠加 token × BillingRate / SellRate 的结果。 + BillingCostOverride *float64 } // CalculateResult 计算结果 @@ -75,16 +75,16 @@ func (c *Calculator) Calculate(input CalculateInput) CalculateResult { accountRate = 1.0 } - nonOutputCost := input.InputCost + input.CachedInputCost + input.CacheCreationCost - actualCost := nonOutputCost*billingRate + input.OutputCost*billingRate - if input.OutputBillingCostOverride != nil { - actualCost = nonOutputCost*billingRate + *input.OutputBillingCostOverride - } + actualCost := totalCost * billingRate billedCost := actualCost if input.SellRate > 0 { billedCost = totalCost * input.SellRate } + if input.BillingCostOverride != nil { + actualCost = *input.BillingCostOverride + billedCost = *input.BillingCostOverride + } accountCost := totalCost * accountRate diff --git a/backend/internal/billing/calculator_test.go b/backend/internal/billing/calculator_test.go index 7c39b8a2..5e14bba7 100644 --- a/backend/internal/billing/calculator_test.go +++ b/backend/internal/billing/calculator_test.go @@ -159,25 +159,26 @@ func TestCalculate_MarkupIndependentOfBillingRate(t *testing.T) { } } -func TestCalculate_OutputBillingCostOverride(t *testing.T) { +func TestCalculate_BillingCostOverride(t *testing.T) { c := NewCalculator() - outputOverride := 0.08 + override := 0.08 res := c.Calculate(CalculateInput{ - InputCost: 0.10, - OutputCost: 0.40, - BillingRate: 0.50, - OutputBillingCostOverride: &outputOverride, - AccountRate: 1.25, + InputCost: 0.10, + OutputCost: 0.40, + BillingRate: 0.50, + SellRate: 0.90, + BillingCostOverride: &override, + AccountRate: 1.25, }) if !almostEqual(res.TotalCost, 0.50) { t.Fatalf("TotalCost = %v, want 0.50", res.TotalCost) } - if !almostEqual(res.ActualCost, 0.13) { - t.Fatalf("ActualCost = %v, want 0.13", res.ActualCost) + if !almostEqual(res.ActualCost, 0.08) { + t.Fatalf("ActualCost = %v, want 0.08", res.ActualCost) } - if !almostEqual(res.BilledCost, res.ActualCost) { - t.Fatalf("BilledCost = %v, want %v", res.BilledCost, res.ActualCost) + if !almostEqual(res.BilledCost, 0.08) { + t.Fatalf("BilledCost = %v, want 0.08", res.BilledCost) } if !almostEqual(res.AccountCost, 0.625) { t.Fatalf("AccountCost = %v, want 0.625", res.AccountCost) diff --git a/backend/internal/billing/rate.go b/backend/internal/billing/rate.go index fd6493a7..9b2518fd 100644 --- a/backend/internal/billing/rate.go +++ b/backend/internal/billing/rate.go @@ -1,6 +1,6 @@ package billing -import "github.com/DouDOU-start/airgate-core/internal/auth" +import "github.com/DevilGenius/airgate-core/internal/auth" // ResolveBillingRate 决定一次请求该用什么倍率扣 reseller 的真实成本(actual_cost)。 // diff --git a/backend/internal/billing/rate_test.go b/backend/internal/billing/rate_test.go index a1503842..a5eb0cb3 100644 --- a/backend/internal/billing/rate_test.go +++ b/backend/internal/billing/rate_test.go @@ -3,7 +3,7 @@ package billing import ( "testing" - "github.com/DouDOU-start/airgate-core/internal/auth" + "github.com/DevilGenius/airgate-core/internal/auth" ) func TestResolveBillingRateForGroup_PriorityChain(t *testing.T) { diff --git a/backend/internal/billing/recorder.go b/backend/internal/billing/recorder.go index a724dfb7..993d38e7 100644 --- a/backend/internal/billing/recorder.go +++ b/backend/internal/billing/recorder.go @@ -7,8 +7,7 @@ import ( "sync" "time" - "github.com/DouDOU-start/airgate-core/ent" - sdk "github.com/DouDOU-start/airgate-sdk/sdkgo" + "github.com/DevilGenius/airgate-core/ent" ) const ( @@ -31,14 +30,11 @@ type UsageRecord struct { OutputTokens int CachedInputTokens int CacheCreationTokens int - CacheCreation5mTokens int - CacheCreation1hTokens int ReasoningOutputTokens int InputPrice float64 OutputPrice float64 CachedInputPrice float64 CacheCreationPrice float64 - CacheCreation1hPrice float64 InputCost float64 OutputCost float64 CachedInputCost float64 @@ -51,7 +47,6 @@ type UsageRecord struct { SellRate float64 // 快照:本次生效的销售倍率(0 表示未启用 markup) AccountRateMultiplier float64 // 快照:本次生效的 account_rate ServiceTier string - ImageSize string // 图像生成请求的实际出图尺寸("WxH"),非图像请求留空 Stream bool DurationMs int64 FirstTokenMs int64 @@ -59,9 +54,6 @@ type UsageRecord struct { IPAddress string Endpoint string ReasoningEffort string - UsageAttributes []sdk.UsageAttribute - UsageMetrics []sdk.UsageMetric - UsageCostDetails []sdk.UsageCostDetail UsageMetadata map[string]string } @@ -239,14 +231,11 @@ func usageLogCreate(tx *ent.Tx, rec UsageRecord) *ent.UsageLogCreate { SetOutputTokens(rec.OutputTokens). SetCachedInputTokens(rec.CachedInputTokens). SetCacheCreationTokens(rec.CacheCreationTokens). - SetCacheCreation5mTokens(rec.CacheCreation5mTokens). - SetCacheCreation1hTokens(rec.CacheCreation1hTokens). SetReasoningOutputTokens(rec.ReasoningOutputTokens). SetInputPrice(rec.InputPrice). SetOutputPrice(rec.OutputPrice). SetCachedInputPrice(rec.CachedInputPrice). SetCacheCreationPrice(rec.CacheCreationPrice). - SetCacheCreation1hPrice(rec.CacheCreation1hPrice). SetInputCost(rec.InputCost). SetOutputCost(rec.OutputCost). SetCachedInputCost(rec.CachedInputCost). @@ -259,7 +248,6 @@ func usageLogCreate(tx *ent.Tx, rec UsageRecord) *ent.UsageLogCreate { SetSellRate(rec.SellRate). SetAccountRateMultiplier(rec.AccountRateMultiplier). SetServiceTier(rec.ServiceTier). - SetImageSize(rec.ImageSize). SetStream(rec.Stream). SetDurationMs(rec.DurationMs). SetFirstTokenMs(rec.FirstTokenMs). @@ -267,10 +255,6 @@ func usageLogCreate(tx *ent.Tx, rec UsageRecord) *ent.UsageLogCreate { SetIPAddress(rec.IPAddress). SetEndpoint(rec.Endpoint). SetReasoningEffort(rec.ReasoningEffort). - SetUsageAttributes(rec.UsageAttributes). - SetUsageMetrics(rec.UsageMetrics). - SetUsageCostDetails(enrichUsageCostDetails(rec)). - SetUsageMetadata(rec.UsageMetadata). SetUserIDSnapshot(rec.UserID). SetUserEmailSnapshot(rec.UserEmail). SetUserID(rec.UserID). @@ -279,44 +263,10 @@ func usageLogCreate(tx *ent.Tx, rec UsageRecord) *ent.UsageLogCreate { if rec.APIKeyID > 0 { b.SetAPIKeyID(rec.APIKeyID) } - return b -} - -func enrichUsageCostDetails(rec UsageRecord) []sdk.UsageCostDetail { - if len(rec.UsageCostDetails) == 0 { - return rec.UsageCostDetails + if len(rec.UsageMetadata) > 0 { + b.SetUsageMetadata(rec.UsageMetadata) } - - items := make([]sdk.UsageCostDetail, len(rec.UsageCostDetails)) - copy(items, rec.UsageCostDetails) - - var accountCostSum float64 - for _, item := range items { - if item.AccountCost > 0 { - accountCostSum += item.AccountCost - } - } - - for i := range items { - accountCost := items[i].AccountCost - if accountCost <= 0 { - if rec.RateMultiplier > 0 { - items[i].BillingMultiplier = rec.RateMultiplier - } - continue - } - if accountCostSum > 0 && rec.ActualCost > 0 { - items[i].UserCost = rec.ActualCost * accountCost / accountCostSum - items[i].BillingMultiplier = items[i].UserCost / accountCost - continue - } - if rec.RateMultiplier > 0 { - items[i].BillingMultiplier = rec.RateMultiplier - items[i].UserCost = accountCost * rec.RateMultiplier - } - } - - return items + return b } func applyUsageCharges(ctx context.Context, tx *ent.Tx, batch []UsageRecord) error { diff --git a/backend/internal/billing/recorder_test.go b/backend/internal/billing/recorder_test.go index 8ca6481d..56ca87bc 100644 --- a/backend/internal/billing/recorder_test.go +++ b/backend/internal/billing/recorder_test.go @@ -7,8 +7,8 @@ import ( "entgo.io/ent/dialect/sql/schema" _ "github.com/mattn/go-sqlite3" - "github.com/DouDOU-start/airgate-core/ent" - "github.com/DouDOU-start/airgate-core/ent/enttest" + "github.com/DevilGenius/airgate-core/ent" + "github.com/DevilGenius/airgate-core/ent/enttest" ) func TestRecordSyncPersistsUserEmailSnapshot(t *testing.T) { diff --git a/backend/internal/bootstrap/http_handlers.go b/backend/internal/bootstrap/http_handlers.go index 0ccef351..92e7de37 100644 --- a/backend/internal/bootstrap/http_handlers.go +++ b/backend/internal/bootstrap/http_handlers.go @@ -9,29 +9,29 @@ import ( "github.com/redis/go-redis/v9" - sdk "github.com/DouDOU-start/airgate-sdk/sdkgo" - - "github.com/DouDOU-start/airgate-core/ent" - appaccount "github.com/DouDOU-start/airgate-core/internal/app/account" - appapikey "github.com/DouDOU-start/airgate-core/internal/app/apikey" - appauth "github.com/DouDOU-start/airgate-core/internal/app/auth" - appdashboard "github.com/DouDOU-start/airgate-core/internal/app/dashboard" - appgroup "github.com/DouDOU-start/airgate-core/internal/app/group" - appopenclaw "github.com/DouDOU-start/airgate-core/internal/app/openclaw" - apppluginadmin "github.com/DouDOU-start/airgate-core/internal/app/pluginadmin" - appproxy "github.com/DouDOU-start/airgate-core/internal/app/proxy" - appsettings "github.com/DouDOU-start/airgate-core/internal/app/settings" - appsubscription "github.com/DouDOU-start/airgate-core/internal/app/subscription" - appusage "github.com/DouDOU-start/airgate-core/internal/app/usage" - appuser "github.com/DouDOU-start/airgate-core/internal/app/user" - "github.com/DouDOU-start/airgate-core/internal/auth" - "github.com/DouDOU-start/airgate-core/internal/config" - "github.com/DouDOU-start/airgate-core/internal/infra/mailer" - "github.com/DouDOU-start/airgate-core/internal/infra/store" - "github.com/DouDOU-start/airgate-core/internal/plugin" - "github.com/DouDOU-start/airgate-core/internal/scheduler" - "github.com/DouDOU-start/airgate-core/internal/server/handler" - "github.com/DouDOU-start/airgate-core/internal/upgrade" + sdk "github.com/DevilGenius/airgate-sdk/sdkgo" + + "github.com/DevilGenius/airgate-core/ent" + appaccount "github.com/DevilGenius/airgate-core/internal/app/account" + appapikey "github.com/DevilGenius/airgate-core/internal/app/apikey" + appauth "github.com/DevilGenius/airgate-core/internal/app/auth" + appdashboard "github.com/DevilGenius/airgate-core/internal/app/dashboard" + appgroup "github.com/DevilGenius/airgate-core/internal/app/group" + appopenclaw "github.com/DevilGenius/airgate-core/internal/app/openclaw" + apppluginadmin "github.com/DevilGenius/airgate-core/internal/app/pluginadmin" + appproxy "github.com/DevilGenius/airgate-core/internal/app/proxy" + appsettings "github.com/DevilGenius/airgate-core/internal/app/settings" + appsubscription "github.com/DevilGenius/airgate-core/internal/app/subscription" + appusage "github.com/DevilGenius/airgate-core/internal/app/usage" + appuser "github.com/DevilGenius/airgate-core/internal/app/user" + "github.com/DevilGenius/airgate-core/internal/auth" + "github.com/DevilGenius/airgate-core/internal/config" + "github.com/DevilGenius/airgate-core/internal/infra/mailer" + "github.com/DevilGenius/airgate-core/internal/infra/store" + "github.com/DevilGenius/airgate-core/internal/plugin" + "github.com/DevilGenius/airgate-core/internal/scheduler" + "github.com/DevilGenius/airgate-core/internal/server/handler" + "github.com/DevilGenius/airgate-core/internal/upgrade" ) // HTTPDependencies 描述 HTTP 处理器装配所需依赖。 diff --git a/backend/internal/bootstrap/migrations/20260528075600_usage_logs_upgrade.sql b/backend/internal/bootstrap/migrations/20260528075600_usage_logs_upgrade.sql new file mode 100644 index 00000000..38c69360 --- /dev/null +++ b/backend/internal/bootstrap/migrations/20260528075600_usage_logs_upgrade.sql @@ -0,0 +1,29 @@ +-- description: Upgrade usage_logs table. + +ALTER TABLE public.usage_logs ADD COLUMN IF NOT EXISTS image_size text NOT NULL DEFAULT ''; + +UPDATE public.usage_logs +SET usage_metadata = ( + CASE + WHEN jsonb_typeof(usage_metadata) = 'object' THEN usage_metadata + ELSE '{}'::jsonb + END +) || jsonb_build_object('openai.image.size', btrim(image_size)) +WHERE image_size IS NOT NULL + AND btrim(image_size) <> '' + AND ( + usage_metadata IS NULL + OR jsonb_typeof(usage_metadata) <> 'object' + OR COALESCE(btrim(usage_metadata->>'openai.image.size'), '') = '' + ); + +ALTER TABLE public.usage_logs DROP COLUMN IF EXISTS image_size; +ALTER TABLE public.usage_logs DROP COLUMN IF EXISTS cache_creation_5m_tokens; +ALTER TABLE public.usage_logs DROP COLUMN IF EXISTS cache_creation_1h_tokens; +ALTER TABLE public.usage_logs DROP COLUMN IF EXISTS cache_creation_1h_price; +ALTER TABLE public.usage_logs DROP COLUMN IF EXISTS usage_attributes; +ALTER TABLE public.usage_logs DROP COLUMN IF EXISTS usage_metrics; +ALTER TABLE public.usage_logs DROP COLUMN IF EXISTS usage_cost_details; + +CREATE INDEX CONCURRENTLY IF NOT EXISTS usage_log_created_at ON public.usage_logs (created_at); +CREATE INDEX CONCURRENTLY IF NOT EXISTS usage_log_user_id_snapshot ON public.usage_logs (user_id_snapshot); diff --git a/backend/internal/bootstrap/startup.go b/backend/internal/bootstrap/startup.go index cc7efdee..e7530061 100644 --- a/backend/internal/bootstrap/startup.go +++ b/backend/internal/bootstrap/startup.go @@ -7,11 +7,11 @@ import ( entsql "entgo.io/ent/dialect/sql" - sdk "github.com/DouDOU-start/airgate-sdk/sdkgo" + sdk "github.com/DevilGenius/airgate-sdk/sdkgo" - "github.com/DouDOU-start/airgate-core/ent" - "github.com/DouDOU-start/airgate-core/ent/apikey" - "github.com/DouDOU-start/airgate-core/internal/auth" + "github.com/DevilGenius/airgate-core/ent" + "github.com/DevilGenius/airgate-core/ent/apikey" + "github.com/DevilGenius/airgate-core/internal/auth" ) // RunStartupTasks 运行启动阶段的整理任务。 @@ -21,6 +21,7 @@ func RunStartupTasks(db *ent.Client, drv *entsql.Driver, apiKeySecret string) { backfillResellerMarkupColumns(drv) migrateAccountState(drv) migrateUserHistoryRefs(drv) + runSystemUpgrades(drv) slog.Info("bootstrap_startup_tasks_done") } @@ -35,26 +36,46 @@ func migrateUserHistoryRefs(drv *entsql.Driver) { statements := []string{ `ALTER TABLE usage_logs ADD COLUMN IF NOT EXISTS user_id_snapshot integer NOT NULL DEFAULT 0`, `ALTER TABLE usage_logs ADD COLUMN IF NOT EXISTS user_email_snapshot text NOT NULL DEFAULT ''`, - `UPDATE usage_logs AS ul + `ALTER TABLE balance_logs ADD COLUMN IF NOT EXISTS user_id_snapshot integer NOT NULL DEFAULT 0`, + `ALTER TABLE balance_logs ADD COLUMN IF NOT EXISTS user_email_snapshot text NOT NULL DEFAULT ''`, + } + + usageFKReady, ok := historyUserRefReady(ctx, drv, "usage_logs", "user_usage_logs", "usage_logs_users_usage_logs") + if !ok { + return + } + if !usageFKReady { + statements = append(statements, + `UPDATE usage_logs AS ul SET user_id_snapshot = CASE WHEN ul.user_id_snapshot = 0 THEN u.id ELSE ul.user_id_snapshot END, user_email_snapshot = CASE WHEN ul.user_email_snapshot = '' THEN u.email ELSE ul.user_email_snapshot END FROM users AS u - WHERE ul.user_usage_logs = u.id`, - `ALTER TABLE usage_logs ALTER COLUMN user_usage_logs DROP NOT NULL`, - `ALTER TABLE usage_logs DROP CONSTRAINT IF EXISTS usage_logs_users_usage_logs`, - `ALTER TABLE usage_logs ADD CONSTRAINT usage_logs_users_usage_logs - FOREIGN KEY (user_usage_logs) REFERENCES users(id) ON DELETE SET NULL`, - `ALTER TABLE balance_logs ADD COLUMN IF NOT EXISTS user_id_snapshot integer NOT NULL DEFAULT 0`, - `ALTER TABLE balance_logs ADD COLUMN IF NOT EXISTS user_email_snapshot text NOT NULL DEFAULT ''`, - `UPDATE balance_logs AS bl + WHERE ul.user_usage_logs = u.id + AND (ul.user_id_snapshot = 0 OR ul.user_email_snapshot = '')`, + `ALTER TABLE usage_logs ALTER COLUMN user_usage_logs DROP NOT NULL`, + `ALTER TABLE usage_logs DROP CONSTRAINT IF EXISTS usage_logs_users_usage_logs`, + `ALTER TABLE usage_logs ADD CONSTRAINT usage_logs_users_usage_logs + FOREIGN KEY (user_usage_logs) REFERENCES users(id) ON DELETE SET NULL NOT VALID`, + ) + } + + balanceFKReady, ok := historyUserRefReady(ctx, drv, "balance_logs", "user_balance_logs", "balance_logs_users_balance_logs") + if !ok { + return + } + if !balanceFKReady { + statements = append(statements, + `UPDATE balance_logs AS bl SET user_id_snapshot = CASE WHEN bl.user_id_snapshot = 0 THEN u.id ELSE bl.user_id_snapshot END, user_email_snapshot = CASE WHEN bl.user_email_snapshot = '' THEN u.email ELSE bl.user_email_snapshot END FROM users AS u - WHERE bl.user_balance_logs = u.id`, - `ALTER TABLE balance_logs ALTER COLUMN user_balance_logs DROP NOT NULL`, - `ALTER TABLE balance_logs DROP CONSTRAINT IF EXISTS balance_logs_users_balance_logs`, - `ALTER TABLE balance_logs ADD CONSTRAINT balance_logs_users_balance_logs - FOREIGN KEY (user_balance_logs) REFERENCES users(id) ON DELETE SET NULL`, + WHERE bl.user_balance_logs = u.id + AND (bl.user_id_snapshot = 0 OR bl.user_email_snapshot = '')`, + `ALTER TABLE balance_logs ALTER COLUMN user_balance_logs DROP NOT NULL`, + `ALTER TABLE balance_logs DROP CONSTRAINT IF EXISTS balance_logs_users_balance_logs`, + `ALTER TABLE balance_logs ADD CONSTRAINT balance_logs_users_balance_logs + FOREIGN KEY (user_balance_logs) REFERENCES users(id) ON DELETE SET NULL NOT VALID`, + ) } for _, sql := range statements { @@ -66,6 +87,62 @@ func migrateUserHistoryRefs(drv *entsql.Driver) { } } +func historyUserRefReady(ctx context.Context, drv *entsql.Driver, table, column, constraint string) (bool, bool) { + nullable, ok := tableColumnNullable(ctx, drv, table, column) + if !ok { + return false, false + } + if !nullable { + return false, true + } + return tableForeignKeyOnDeleteSetNull(ctx, drv, table, constraint) +} + +func tableColumnNullable(ctx context.Context, drv *entsql.Driver, table, column string) (bool, bool) { + var rows entsql.Rows + const checkSQL = `SELECT is_nullable + FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = $1 + AND column_name = $2 + LIMIT 1` + if err := drv.Query(ctx, checkSQL, []any{table, column}, &rows); err != nil { + slog.Warn("bootstrap_column_nullable_check_failed", "table", table, "column", column, sdk.LogFieldError, err) + return false, false + } + defer func() { _ = rows.Close() }() + if !rows.Next() { + slog.Warn("bootstrap_column_nullable_check_missing", "table", table, "column", column) + return false, false + } + var nullable string + if err := rows.Scan(&nullable); err != nil { + slog.Warn("bootstrap_column_nullable_scan_failed", "table", table, "column", column, sdk.LogFieldError, err) + return false, false + } + return nullable == "YES", true +} + +func tableForeignKeyOnDeleteSetNull(ctx context.Context, drv *entsql.Driver, table, constraint string) (bool, bool) { + var rows entsql.Rows + const checkSQL = `SELECT 1 + FROM pg_constraint c + JOIN pg_class t ON t.oid = c.conrelid + JOIN pg_namespace n ON n.oid = t.relnamespace + WHERE n.nspname = 'public' + AND t.relname = $1 + AND c.conname = $2 + AND c.contype = 'f' + AND c.confdeltype = 'n' + LIMIT 1` + if err := drv.Query(ctx, checkSQL, []any{table, constraint}, &rows); err != nil { + slog.Warn("bootstrap_foreign_key_check_failed", "table", table, "constraint", constraint, sdk.LogFieldError, err) + return false, false + } + defer func() { _ = rows.Close() }() + return rows.Next(), true +} + // migrateAccountState 把老的 status / rate_limit_reset_at 字段一次性迁移到新的 // state / state_until,然后 DROP 旧列。幂等:首次启动或升级时有效,之后旧列已不存在时跳过。 // @@ -139,11 +216,18 @@ func migrateAccountState(drv *entsql.Driver) { } func accountColumnExists(ctx context.Context, drv *entsql.Driver, column string) (bool, bool) { + return tableColumnExists(ctx, drv, "accounts", column) +} + +func tableColumnExists(ctx context.Context, drv *entsql.Driver, table, column string) (bool, bool) { var exists entsql.Rows const checkSQL = `SELECT 1 FROM information_schema.columns - WHERE table_name='accounts' AND column_name=$1 LIMIT 1` - if err := drv.Query(ctx, checkSQL, []any{column}, &exists); err != nil { - slog.Warn("bootstrap_account_state_check_failed", "column", column, sdk.LogFieldError, err) + WHERE table_schema = 'public' + AND table_name=$1 + AND column_name=$2 + LIMIT 1` + if err := drv.Query(ctx, checkSQL, []any{table, column}, &exists); err != nil { + slog.Warn("bootstrap_column_check_failed", "table", table, "column", column, sdk.LogFieldError, err) return false, false } defer func() { _ = exists.Close() }() diff --git a/backend/internal/bootstrap/startup_test.go b/backend/internal/bootstrap/startup_test.go new file mode 100644 index 00000000..9d7b1675 --- /dev/null +++ b/backend/internal/bootstrap/startup_test.go @@ -0,0 +1,49 @@ +package bootstrap + +import "testing" + +func TestSplitSQLStatements(t *testing.T) { + sql := ` +-- comment ; stays with next statement +SELECT 'a;b'; +DO $$ +BEGIN + RAISE NOTICE 'x;y'; +END $$; +/* block ; comment */ +CREATE INDEX CONCURRENTLY idx_example ON public.example (created_at); +` + + got := splitSQLStatements(sql) + if len(got) != 3 { + t.Fatalf("len = %d, want 3: %#v", len(got), got) + } + if got[0] != "-- comment ; stays with next statement\nSELECT 'a;b'" { + t.Fatalf("stmt[0] = %q", got[0]) + } + if got[1] != "DO $$\nBEGIN\n\tRAISE NOTICE 'x;y';\nEND $$" { + t.Fatalf("stmt[1] = %q", got[1]) + } + if got[2] != "/* block ; comment */\nCREATE INDEX CONCURRENTLY idx_example ON public.example (created_at)" { + t.Fatalf("stmt[2] = %q", got[2]) + } +} + +func TestValidateSystemUpgradeFilename(t *testing.T) { + valid := "20260528143015_usage_logs_upgrade.sql" + if err := validateSystemUpgradeFilename(valid); err != nil { + t.Fatalf("valid filename rejected: %v", err) + } + + invalid := []string{ + "20260528_usage_logs_upgrade.sql", + "202605281430_usage_logs_upgrade.sql", + "20260528143015.sql", + "20261328143015_usage_logs_upgrade.sql", + } + for _, name := range invalid { + if err := validateSystemUpgradeFilename(name); err == nil { + t.Fatalf("invalid filename accepted: %s", name) + } + } +} diff --git a/backend/internal/bootstrap/system_upgrade.go b/backend/internal/bootstrap/system_upgrade.go new file mode 100644 index 00000000..10c2e25e --- /dev/null +++ b/backend/internal/bootstrap/system_upgrade.go @@ -0,0 +1,361 @@ +package bootstrap + +import ( + "context" + "crypto/sha256" + stdsql "database/sql" + "embed" + "encoding/hex" + "fmt" + "io/fs" + "log/slog" + "path/filepath" + "sort" + "strings" + "time" + + entsql "entgo.io/ent/dialect/sql" + + sdk "github.com/DevilGenius/airgate-sdk/sdkgo" +) + +const ( + systemUpgradeQualifiedTable = "public.system_upgrade" + systemUpgradeAdvisoryLockKey int64 = 2026052809517 +) + +//go:embed migrations/*.sql +var systemUpgradeFiles embed.FS + +type systemUpgrade struct { + ID string + Description string + Checksum string + SQL string +} + +func runSystemUpgrades(drv *entsql.Driver) { + if drv == nil { + return + } + upgrades := loadSystemUpgrades() + if len(upgrades) == 0 { + return + } + + ctx := context.Background() + conn, err := drv.DB().Conn(ctx) + if err != nil { + panicSystemUpgrade("open system upgrade connection", err) + } + defer func() { _ = conn.Close() }() + + if _, err := conn.ExecContext(ctx, `SELECT pg_advisory_lock($1)`, systemUpgradeAdvisoryLockKey); err != nil { + panicSystemUpgrade("lock system upgrades", err) + } + defer func() { + if _, err := conn.ExecContext(context.Background(), `SELECT pg_advisory_unlock($1)`, systemUpgradeAdvisoryLockKey); err != nil { + slog.Warn("system_upgrade_unlock_failed", sdk.LogFieldError, err) + } + }() + + if err := prepareSystemUpgradeTable(ctx, conn); err != nil { + panicSystemUpgrade("prepare system_upgrade table", err) + } + + for _, upgrade := range upgrades { + var appliedChecksum stdsql.NullString + const appliedSQL = `SELECT checksum FROM public.system_upgrade WHERE id = $1` + err := conn.QueryRowContext(ctx, appliedSQL, upgrade.ID).Scan(&appliedChecksum) + if err == nil { + if appliedChecksum.Valid && appliedChecksum.String != "" && appliedChecksum.String != upgrade.Checksum { + panicSystemUpgrade("verify system upgrade checksum "+upgrade.ID, fmt.Errorf("recorded=%s current=%s", appliedChecksum.String, upgrade.Checksum)) + } + if !appliedChecksum.Valid || appliedChecksum.String == "" { + const updateSQL = `UPDATE public.system_upgrade + SET checksum = $2, description = $3 + WHERE id = $1 AND checksum = ''` + if _, err := conn.ExecContext(ctx, updateSQL, upgrade.ID, upgrade.Checksum, upgrade.Description); err != nil { + panicSystemUpgrade("backfill system upgrade checksum "+upgrade.ID, err) + } + } + continue + } + if err != stdsql.ErrNoRows { + panicSystemUpgrade("check system upgrade "+upgrade.ID, err) + } + + start := time.Now() + slog.Info("system_upgrade_start", "id", upgrade.ID) + if err := executeSystemUpgradeSQL(ctx, conn, upgrade); err != nil { + panicSystemUpgrade("run system upgrade "+upgrade.ID, err) + } + duration := time.Since(start).Milliseconds() + const insertSQL = `INSERT INTO public.system_upgrade (id, description, checksum, duration_ms) + VALUES ($1, $2, $3, $4) + ON CONFLICT (id) DO NOTHING` + if _, err := conn.ExecContext(ctx, insertSQL, upgrade.ID, upgrade.Description, upgrade.Checksum, duration); err != nil { + panicSystemUpgrade("record system upgrade "+upgrade.ID, err) + } + slog.Info("system_upgrade_done", "id", upgrade.ID, "duration_ms", duration) + } +} + +func prepareSystemUpgradeTable(ctx context.Context, conn *stdsql.Conn) error { + const createTableSQL = `CREATE TABLE IF NOT EXISTS public.system_upgrade ( + id text PRIMARY KEY, + description text NOT NULL DEFAULT '', + checksum text NOT NULL DEFAULT '', + applied_at timestamptz NOT NULL DEFAULT now(), + duration_ms bigint NOT NULL DEFAULT 0 + )` + if _, err := conn.ExecContext(ctx, createTableSQL); err != nil { + return fmt.Errorf("create %s table: %w", systemUpgradeQualifiedTable, err) + } + if err := ensureSystemUpgradeColumns(ctx, conn, systemUpgradeQualifiedTable); err != nil { + return err + } + if err := normalizeSystemUpgradePrimaryKey(ctx, conn); err != nil { + return err + } + + return nil +} + +func ensureSystemUpgradeColumns(ctx context.Context, conn *stdsql.Conn, table string) error { + statements := []string{ + "ALTER TABLE " + table + " ADD COLUMN IF NOT EXISTS id text", + "ALTER TABLE " + table + " ADD COLUMN IF NOT EXISTS description text", + "ALTER TABLE " + table + " ADD COLUMN IF NOT EXISTS checksum text", + "ALTER TABLE " + table + " ADD COLUMN IF NOT EXISTS applied_at timestamptz", + "ALTER TABLE " + table + " ADD COLUMN IF NOT EXISTS duration_ms bigint", + "UPDATE " + table + " SET description = '' WHERE description IS NULL", + "UPDATE " + table + " SET checksum = '' WHERE checksum IS NULL", + "UPDATE " + table + " SET applied_at = now() WHERE applied_at IS NULL", + "UPDATE " + table + " SET duration_ms = 0 WHERE duration_ms IS NULL", + "ALTER TABLE " + table + " ALTER COLUMN description SET DEFAULT ''", + "ALTER TABLE " + table + " ALTER COLUMN description SET NOT NULL", + "ALTER TABLE " + table + " ALTER COLUMN checksum SET DEFAULT ''", + "ALTER TABLE " + table + " ALTER COLUMN checksum SET NOT NULL", + "ALTER TABLE " + table + " ALTER COLUMN applied_at SET DEFAULT now()", + "ALTER TABLE " + table + " ALTER COLUMN applied_at SET NOT NULL", + "ALTER TABLE " + table + " ALTER COLUMN duration_ms SET DEFAULT 0", + "ALTER TABLE " + table + " ALTER COLUMN duration_ms SET NOT NULL", + } + for _, statement := range statements { + if _, err := conn.ExecContext(ctx, statement); err != nil { + return fmt.Errorf("ensure %s columns: %w", table, err) + } + } + return nil +} + +func normalizeSystemUpgradePrimaryKey(ctx context.Context, conn *stdsql.Conn) error { + const ensurePrimaryKeySQL = `DO $$ + BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint c + JOIN pg_class t ON t.oid = c.conrelid + JOIN pg_namespace n ON n.oid = t.relnamespace + WHERE n.nspname = 'public' + AND t.relname = 'system_upgrade' + AND c.contype = 'p' + ) THEN + ALTER TABLE public.system_upgrade ADD CONSTRAINT system_upgrade_pkey PRIMARY KEY (id); + END IF; + END $$` + if _, err := conn.ExecContext(ctx, ensurePrimaryKeySQL); err != nil { + return fmt.Errorf("ensure %s primary key: %w", systemUpgradeQualifiedTable, err) + } + return nil +} + +func loadSystemUpgrades() []systemUpgrade { + entries, err := fs.ReadDir(systemUpgradeFiles, "migrations") + if err != nil { + panicSystemUpgrade("read system upgrade files", err) + } + sort.Slice(entries, func(i, j int) bool { + return entries[i].Name() < entries[j].Name() + }) + + upgrades := make([]systemUpgrade, 0, len(entries)) + for _, entry := range entries { + if entry.IsDir() || filepath.Ext(entry.Name()) != ".sql" { + continue + } + if err := validateSystemUpgradeFilename(entry.Name()); err != nil { + panicSystemUpgrade("validate system upgrade "+entry.Name(), err) + } + path := filepath.ToSlash(filepath.Join("migrations", entry.Name())) + data, err := systemUpgradeFiles.ReadFile(path) + if err != nil { + panicSystemUpgrade("read system upgrade "+entry.Name(), err) + } + hash := sha256.Sum256(data) + id := strings.TrimSuffix(entry.Name(), ".sql") + sql := string(data) + upgrades = append(upgrades, systemUpgrade{ + ID: id, + Description: systemUpgradeDescription(sql, id), + Checksum: hex.EncodeToString(hash[:]), + SQL: sql, + }) + } + return upgrades +} + +func validateSystemUpgradeFilename(name string) error { + id := strings.TrimSuffix(name, ".sql") + if len(id) < len("20060102150405_")+1 || id[14] != '_' { + return fmt.Errorf("must use YYYYMMDDHHMMSS_description.sql") + } + if _, err := time.Parse("20060102150405", id[:14]); err != nil { + return fmt.Errorf("invalid timestamp prefix: %w", err) + } + if strings.TrimSpace(id[15:]) == "" { + return fmt.Errorf("missing description after timestamp") + } + return nil +} + +func systemUpgradeDescription(sql, fallback string) string { + for _, line := range strings.Split(sql, "\n") { + line = strings.TrimSpace(line) + if strings.HasPrefix(line, "-- description:") { + return strings.TrimSpace(strings.TrimPrefix(line, "-- description:")) + } + } + return fallback +} + +func executeSystemUpgradeSQL(ctx context.Context, conn *stdsql.Conn, upgrade systemUpgrade) error { + for _, stmt := range splitSQLStatements(upgrade.SQL) { + if _, err := conn.ExecContext(ctx, stmt); err != nil { + return fmt.Errorf("execute statement in %s: %w", upgrade.ID, err) + } + } + return nil +} + +func splitSQLStatements(sql string) []string { + var statements []string + start := 0 + inSingleQuote := false + inDoubleQuote := false + inLineComment := false + inBlockComment := false + dollarTag := "" + + for i := 0; i < len(sql); i++ { + ch := sql[i] + next := byte(0) + if i+1 < len(sql) { + next = sql[i+1] + } + + switch { + case inLineComment: + if ch == '\n' { + inLineComment = false + } + continue + case inBlockComment: + if ch == '*' && next == '/' { + inBlockComment = false + i++ + } + continue + case dollarTag != "": + if strings.HasPrefix(sql[i:], dollarTag) { + i += len(dollarTag) - 1 + dollarTag = "" + } + continue + case inSingleQuote: + if ch == '\'' { + if next == '\'' { + i++ + } else { + inSingleQuote = false + } + } + continue + case inDoubleQuote: + if ch == '"' { + if next == '"' { + i++ + } else { + inDoubleQuote = false + } + } + continue + } + + if ch == '-' && next == '-' { + inLineComment = true + i++ + continue + } + if ch == '/' && next == '*' { + inBlockComment = true + i++ + continue + } + if ch == '\'' { + inSingleQuote = true + continue + } + if ch == '"' { + inDoubleQuote = true + continue + } + if ch == '$' { + if tag, ok := sqlDollarTag(sql[i:]); ok { + dollarTag = tag + i += len(tag) - 1 + continue + } + } + if ch == ';' { + stmt := strings.TrimSpace(sql[start:i]) + if stmt != "" { + statements = append(statements, stmt) + } + start = i + 1 + } + } + + tail := strings.TrimSpace(sql[start:]) + if tail != "" { + statements = append(statements, tail) + } + return statements +} + +func sqlDollarTag(sql string) (string, bool) { + if sql == "" || sql[0] != '$' { + return "", false + } + for i := 1; i < len(sql); i++ { + ch := sql[i] + if ch == '$' { + return sql[:i+1], true + } + if !((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || ch == '_') { + return "", false + } + } + return "", false +} + +func panicSystemUpgrade(action string, err error) { + if err != nil { + err = fmt.Errorf("%s: %w", action, err) + } else { + err = fmt.Errorf("%s", action) + } + slog.Error("system_upgrade_failed", sdk.LogFieldError, err) + panic(err) +} diff --git a/backend/internal/infra/mailer/mailer.go b/backend/internal/infra/mailer/mailer.go index e1ed1434..1a90a999 100644 --- a/backend/internal/infra/mailer/mailer.go +++ b/backend/internal/infra/mailer/mailer.go @@ -8,9 +8,9 @@ import ( "net/smtp" "strings" - sdk "github.com/DouDOU-start/airgate-sdk/sdkgo" + sdk "github.com/DevilGenius/airgate-sdk/sdkgo" - "github.com/DouDOU-start/airgate-core/internal/infra/store" + "github.com/DevilGenius/airgate-core/internal/infra/store" ) // Config SMTP 配置。 diff --git a/backend/internal/infra/store/account_store.go b/backend/internal/infra/store/account_store.go index 36ae5b30..2f2c79d4 100644 --- a/backend/internal/infra/store/account_store.go +++ b/backend/internal/infra/store/account_store.go @@ -7,14 +7,14 @@ import ( "entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql/sqljson" - "github.com/DouDOU-start/airgate-core/ent" - entaccount "github.com/DouDOU-start/airgate-core/ent/account" - entgroup "github.com/DouDOU-start/airgate-core/ent/group" - "github.com/DouDOU-start/airgate-core/ent/predicate" - entproxy "github.com/DouDOU-start/airgate-core/ent/proxy" - entusagelog "github.com/DouDOU-start/airgate-core/ent/usagelog" - appaccount "github.com/DouDOU-start/airgate-core/internal/app/account" - "github.com/DouDOU-start/airgate-core/internal/pkg/usagemodel" + "github.com/DevilGenius/airgate-core/ent" + entaccount "github.com/DevilGenius/airgate-core/ent/account" + entgroup "github.com/DevilGenius/airgate-core/ent/group" + "github.com/DevilGenius/airgate-core/ent/predicate" + entproxy "github.com/DevilGenius/airgate-core/ent/proxy" + entusagelog "github.com/DevilGenius/airgate-core/ent/usagelog" + appaccount "github.com/DevilGenius/airgate-core/internal/app/account" + "github.com/DevilGenius/airgate-core/internal/pkg/usagemodel" ) // AccountStore 使用 Ent 实现账号仓储。 diff --git a/backend/internal/infra/store/account_store_test.go b/backend/internal/infra/store/account_store_test.go index c713e708..268ed839 100644 --- a/backend/internal/infra/store/account_store_test.go +++ b/backend/internal/infra/store/account_store_test.go @@ -6,10 +6,10 @@ import ( _ "github.com/mattn/go-sqlite3" - "github.com/DouDOU-start/airgate-core/ent" - "github.com/DouDOU-start/airgate-core/ent/enttest" - "github.com/DouDOU-start/airgate-core/ent/migrate" - "github.com/DouDOU-start/airgate-core/internal/app/account" + "github.com/DevilGenius/airgate-core/ent" + "github.com/DevilGenius/airgate-core/ent/enttest" + "github.com/DevilGenius/airgate-core/ent/migrate" + "github.com/DevilGenius/airgate-core/internal/app/account" ) func TestAccountStoreKeywordSearchMatchesOAuthEmail(t *testing.T) { diff --git a/backend/internal/infra/store/apikey_store.go b/backend/internal/infra/store/apikey_store.go index 98868247..68ffef7c 100644 --- a/backend/internal/infra/store/apikey_store.go +++ b/backend/internal/infra/store/apikey_store.go @@ -6,13 +6,13 @@ import ( "strings" "time" - "github.com/DouDOU-start/airgate-core/ent" - entapikey "github.com/DouDOU-start/airgate-core/ent/apikey" - entgroup "github.com/DouDOU-start/airgate-core/ent/group" - "github.com/DouDOU-start/airgate-core/ent/predicate" - entusagelog "github.com/DouDOU-start/airgate-core/ent/usagelog" - entuser "github.com/DouDOU-start/airgate-core/ent/user" - appapikey "github.com/DouDOU-start/airgate-core/internal/app/apikey" + "github.com/DevilGenius/airgate-core/ent" + entapikey "github.com/DevilGenius/airgate-core/ent/apikey" + entgroup "github.com/DevilGenius/airgate-core/ent/group" + "github.com/DevilGenius/airgate-core/ent/predicate" + entusagelog "github.com/DevilGenius/airgate-core/ent/usagelog" + entuser "github.com/DevilGenius/airgate-core/ent/user" + appapikey "github.com/DevilGenius/airgate-core/internal/app/apikey" ) // APIKeyStore 使用 Ent 实现 API Key 仓储。 diff --git a/backend/internal/infra/store/apikey_store_test.go b/backend/internal/infra/store/apikey_store_test.go index c9800d5e..04b7d776 100644 --- a/backend/internal/infra/store/apikey_store_test.go +++ b/backend/internal/infra/store/apikey_store_test.go @@ -4,7 +4,7 @@ import ( "context" "testing" - appapikey "github.com/DouDOU-start/airgate-core/internal/app/apikey" + appapikey "github.com/DevilGenius/airgate-core/internal/app/apikey" ) // TestAPIKeyStoreListAdminSearchScope 验证 search_scope 控制是否按用户邮箱模糊匹配。 diff --git a/backend/internal/infra/store/apikey_usage_helper.go b/backend/internal/infra/store/apikey_usage_helper.go index bea461a2..552f8d19 100644 --- a/backend/internal/infra/store/apikey_usage_helper.go +++ b/backend/internal/infra/store/apikey_usage_helper.go @@ -4,9 +4,11 @@ import ( "context" "time" - "github.com/DouDOU-start/airgate-core/ent" - entapikey "github.com/DouDOU-start/airgate-core/ent/apikey" - entusagelog "github.com/DouDOU-start/airgate-core/ent/usagelog" + "entgo.io/ent/dialect/sql" + + "github.com/DevilGenius/airgate-core/ent" + "github.com/DevilGenius/airgate-core/ent/predicate" + entusagelog "github.com/DevilGenius/airgate-core/ent/usagelog" ) // queryAPIKeyUsage 返回每个 key 的"今日"和"近 30 天"实际成本。 @@ -28,7 +30,7 @@ func queryAPIKeyUsage(ctx context.Context, db *ent.Client, keyIDs []int, todaySt var todayRows []costRow if err := db.UsageLog.Query(). Where( - entusagelog.HasAPIKeyWith(entapikey.IDIn(keyIDs...)), + usageLogColumnIn(entusagelog.APIKeyColumn, keyIDs), entusagelog.CreatedAtGTE(todayStart), ). GroupBy(entusagelog.ForeignKeys[0]). @@ -43,7 +45,7 @@ func queryAPIKeyUsage(ctx context.Context, db *ent.Client, keyIDs []int, todaySt var thirtyDayRows []costRow if err := db.UsageLog.Query(). Where( - entusagelog.HasAPIKeyWith(entapikey.IDIn(keyIDs...)), + usageLogColumnIn(entusagelog.APIKeyColumn, keyIDs), entusagelog.CreatedAtGTE(thirtyDaysAgo), ). GroupBy(entusagelog.ForeignKeys[0]). @@ -57,3 +59,9 @@ func queryAPIKeyUsage(ctx context.Context, db *ent.Client, keyIDs []int, todaySt return todayMap, thirtyDayMap, nil } + +func usageLogColumnIn(column string, values []int) predicate.UsageLog { + return predicate.UsageLog(func(s *sql.Selector) { + s.Where(sql.InInts(s.C(column), values...)) + }) +} diff --git a/backend/internal/infra/store/auth_store.go b/backend/internal/infra/store/auth_store.go index 756676de..f404615f 100644 --- a/backend/internal/infra/store/auth_store.go +++ b/backend/internal/infra/store/auth_store.go @@ -4,10 +4,10 @@ import ( "context" "time" - "github.com/DouDOU-start/airgate-core/ent" - entapikey "github.com/DouDOU-start/airgate-core/ent/apikey" - entuser "github.com/DouDOU-start/airgate-core/ent/user" - appauth "github.com/DouDOU-start/airgate-core/internal/app/auth" + "github.com/DevilGenius/airgate-core/ent" + entapikey "github.com/DevilGenius/airgate-core/ent/apikey" + entuser "github.com/DevilGenius/airgate-core/ent/user" + appauth "github.com/DevilGenius/airgate-core/internal/app/auth" ) // AuthStore 使用 Ent 实现认证仓储。 diff --git a/backend/internal/infra/store/dashboard_store.go b/backend/internal/infra/store/dashboard_store.go index 7ac1b830..308622d2 100644 --- a/backend/internal/infra/store/dashboard_store.go +++ b/backend/internal/infra/store/dashboard_store.go @@ -11,14 +11,14 @@ import ( "github.com/google/uuid" "github.com/redis/go-redis/v9" - "github.com/DouDOU-start/airgate-core/ent" - entaccount "github.com/DouDOU-start/airgate-core/ent/account" - entapikey "github.com/DouDOU-start/airgate-core/ent/apikey" - "github.com/DouDOU-start/airgate-core/ent/predicate" - entusagelog "github.com/DouDOU-start/airgate-core/ent/usagelog" - entuser "github.com/DouDOU-start/airgate-core/ent/user" - appdashboard "github.com/DouDOU-start/airgate-core/internal/app/dashboard" - "github.com/DouDOU-start/airgate-core/internal/pkg/usagemodel" + "github.com/DevilGenius/airgate-core/ent" + entaccount "github.com/DevilGenius/airgate-core/ent/account" + entapikey "github.com/DevilGenius/airgate-core/ent/apikey" + "github.com/DevilGenius/airgate-core/ent/predicate" + entusagelog "github.com/DevilGenius/airgate-core/ent/usagelog" + entuser "github.com/DevilGenius/airgate-core/ent/user" + appdashboard "github.com/DevilGenius/airgate-core/internal/app/dashboard" + "github.com/DevilGenius/airgate-core/internal/pkg/usagemodel" ) // DashboardStore 使用 Ent 实现仪表盘仓储。 diff --git a/backend/internal/infra/store/ent_logger.go b/backend/internal/infra/store/ent_logger.go index 13c2549c..b0bfe93c 100644 --- a/backend/internal/infra/store/ent_logger.go +++ b/backend/internal/infra/store/ent_logger.go @@ -11,7 +11,7 @@ import ( "net/url" "strings" - "github.com/DouDOU-start/airgate-core/ent" + "github.com/DevilGenius/airgate-core/ent" ) // EntSlogLogger 返回一个 ent.Option,用 slog.Debug 输出 ent 自身日志。 diff --git a/backend/internal/infra/store/group_store.go b/backend/internal/infra/store/group_store.go index 8aaa1de1..c0263d0c 100644 --- a/backend/internal/infra/store/group_store.go +++ b/backend/internal/infra/store/group_store.go @@ -4,14 +4,14 @@ import ( "context" "time" - "github.com/DouDOU-start/airgate-core/ent" - entaccount "github.com/DouDOU-start/airgate-core/ent/account" - entapikey "github.com/DouDOU-start/airgate-core/ent/apikey" - entgroup "github.com/DouDOU-start/airgate-core/ent/group" - entusagelog "github.com/DouDOU-start/airgate-core/ent/usagelog" - entuser "github.com/DouDOU-start/airgate-core/ent/user" - entusersubscription "github.com/DouDOU-start/airgate-core/ent/usersubscription" - appgroup "github.com/DouDOU-start/airgate-core/internal/app/group" + "github.com/DevilGenius/airgate-core/ent" + entaccount "github.com/DevilGenius/airgate-core/ent/account" + entapikey "github.com/DevilGenius/airgate-core/ent/apikey" + entgroup "github.com/DevilGenius/airgate-core/ent/group" + entusagelog "github.com/DevilGenius/airgate-core/ent/usagelog" + entuser "github.com/DevilGenius/airgate-core/ent/user" + entusersubscription "github.com/DevilGenius/airgate-core/ent/usersubscription" + appgroup "github.com/DevilGenius/airgate-core/internal/app/group" ) // GroupStore 使用 Ent 实现分组仓储。 diff --git a/backend/internal/infra/store/proxy_store.go b/backend/internal/infra/store/proxy_store.go index 963c988c..85c78c14 100644 --- a/backend/internal/infra/store/proxy_store.go +++ b/backend/internal/infra/store/proxy_store.go @@ -3,9 +3,9 @@ package store import ( "context" - "github.com/DouDOU-start/airgate-core/ent" - entproxy "github.com/DouDOU-start/airgate-core/ent/proxy" - appproxy "github.com/DouDOU-start/airgate-core/internal/app/proxy" + "github.com/DevilGenius/airgate-core/ent" + entproxy "github.com/DevilGenius/airgate-core/ent/proxy" + appproxy "github.com/DevilGenius/airgate-core/internal/app/proxy" ) // ProxyStore 使用 Ent 实现代理仓储。 diff --git a/backend/internal/infra/store/settings_store.go b/backend/internal/infra/store/settings_store.go index 06709d9a..b8ba0e92 100644 --- a/backend/internal/infra/store/settings_store.go +++ b/backend/internal/infra/store/settings_store.go @@ -3,9 +3,9 @@ package store import ( "context" - "github.com/DouDOU-start/airgate-core/ent" - entsetting "github.com/DouDOU-start/airgate-core/ent/setting" - appsettings "github.com/DouDOU-start/airgate-core/internal/app/settings" + "github.com/DevilGenius/airgate-core/ent" + entsetting "github.com/DevilGenius/airgate-core/ent/setting" + appsettings "github.com/DevilGenius/airgate-core/internal/app/settings" ) // SettingsStore 使用 Ent 实现设置仓储。 diff --git a/backend/internal/infra/store/subscription_store.go b/backend/internal/infra/store/subscription_store.go index 1c063417..07fd0e84 100644 --- a/backend/internal/infra/store/subscription_store.go +++ b/backend/internal/infra/store/subscription_store.go @@ -3,10 +3,10 @@ package store import ( "context" - "github.com/DouDOU-start/airgate-core/ent" - entuser "github.com/DouDOU-start/airgate-core/ent/user" - entusersubscription "github.com/DouDOU-start/airgate-core/ent/usersubscription" - appsubscription "github.com/DouDOU-start/airgate-core/internal/app/subscription" + "github.com/DevilGenius/airgate-core/ent" + entuser "github.com/DevilGenius/airgate-core/ent/user" + entusersubscription "github.com/DevilGenius/airgate-core/ent/usersubscription" + appsubscription "github.com/DevilGenius/airgate-core/internal/app/subscription" ) // SubscriptionStore 使用 Ent 实现订阅仓储。 diff --git a/backend/internal/infra/store/usage_store.go b/backend/internal/infra/store/usage_store.go index 12056e4c..e8080ba7 100644 --- a/backend/internal/infra/store/usage_store.go +++ b/backend/internal/infra/store/usage_store.go @@ -5,15 +5,16 @@ import ( "sort" "time" - "github.com/DouDOU-start/airgate-core/ent" - entaccount "github.com/DouDOU-start/airgate-core/ent/account" - entapikey "github.com/DouDOU-start/airgate-core/ent/apikey" - entgroup "github.com/DouDOU-start/airgate-core/ent/group" - "github.com/DouDOU-start/airgate-core/ent/predicate" - entusagelog "github.com/DouDOU-start/airgate-core/ent/usagelog" - entuser "github.com/DouDOU-start/airgate-core/ent/user" - appusage "github.com/DouDOU-start/airgate-core/internal/app/usage" - "github.com/DouDOU-start/airgate-core/internal/pkg/timezone" + "entgo.io/ent/dialect/sql" + + "github.com/DevilGenius/airgate-core/ent" + entaccount "github.com/DevilGenius/airgate-core/ent/account" + entgroup "github.com/DevilGenius/airgate-core/ent/group" + "github.com/DevilGenius/airgate-core/ent/predicate" + entusagelog "github.com/DevilGenius/airgate-core/ent/usagelog" + entuser "github.com/DevilGenius/airgate-core/ent/user" + appusage "github.com/DevilGenius/airgate-core/internal/app/usage" + "github.com/DevilGenius/airgate-core/internal/pkg/timezone" ) // UsageStore 使用 Ent 实现使用记录仓储。 @@ -21,73 +22,122 @@ type UsageStore struct { db *ent.Client } +var usageLogListFields = []string{ + entusagelog.FieldID, + entusagelog.FieldPlatform, + entusagelog.FieldModel, + entusagelog.FieldInputTokens, + entusagelog.FieldOutputTokens, + entusagelog.FieldCachedInputTokens, + entusagelog.FieldCacheCreationTokens, + entusagelog.FieldReasoningOutputTokens, + entusagelog.FieldInputPrice, + entusagelog.FieldOutputPrice, + entusagelog.FieldCachedInputPrice, + entusagelog.FieldCacheCreationPrice, + entusagelog.FieldInputCost, + entusagelog.FieldOutputCost, + entusagelog.FieldCachedInputCost, + entusagelog.FieldCacheCreationCost, + entusagelog.FieldTotalCost, + entusagelog.FieldActualCost, + entusagelog.FieldBilledCost, + entusagelog.FieldAccountCost, + entusagelog.FieldRateMultiplier, + entusagelog.FieldSellRate, + entusagelog.FieldAccountRateMultiplier, + entusagelog.FieldServiceTier, + entusagelog.FieldStream, + entusagelog.FieldDurationMs, + entusagelog.FieldFirstTokenMs, + entusagelog.FieldUserAgent, + entusagelog.FieldIPAddress, + entusagelog.FieldEndpoint, + entusagelog.FieldReasoningEffort, + entusagelog.FieldUsageMetadata, + entusagelog.FieldUserIDSnapshot, + entusagelog.FieldUserEmailSnapshot, + entusagelog.FieldCreatedAt, + entusagelog.APIKeyColumn, + entusagelog.AccountColumn, + entusagelog.GroupColumn, + entusagelog.UserColumn, +} + // NewUsageStore 创建使用记录仓储。 func NewUsageStore(db *ent.Client) *UsageStore { return &UsageStore{db: db} } // ListUser 查询用户使用记录。 -func (s *UsageStore) ListUser(ctx context.Context, userID int64, filter appusage.ListFilter) ([]appusage.LogRecord, int64, error) { +func (s *UsageStore) ListUser(ctx context.Context, userID int64, filter appusage.ListFilter) ([]appusage.LogRecord, bool, *int64, error) { query := s.db.UsageLog.Query(). Where(usageUserPredicate(userID)) query = applyUsageListFilter(query, filter) - - total, err := query.Count(ctx) - if err != nil { - return nil, 0, err + if filter.BeforeID > 0 { + query = query.Where(entusagelog.IDLT(int(filter.BeforeID))) } logs, err := query. + Select(usageLogListFields...). WithUser(). WithAPIKey(). WithAccount(). WithGroup(). - Offset((filter.Page-1)*filter.PageSize). - Limit(filter.PageSize). - Order(ent.Desc(entusagelog.FieldCreatedAt), ent.Desc(entusagelog.FieldID)). + Limit(filter.PageSize + 1). + Order(ent.Desc(entusagelog.FieldID)). All(ctx) if err != nil { - return nil, 0, err + return nil, false, nil, err } - result := make([]appusage.LogRecord, 0, len(logs)) - for _, item := range logs { - result = append(result, mapUsageLog(item)) - } - return result, int64(total), nil + return mapUsageLogPage(logs, filter.PageSize) } // ListAdmin 查询管理员使用记录。 -func (s *UsageStore) ListAdmin(ctx context.Context, filter appusage.ListFilter) ([]appusage.LogRecord, int64, error) { +func (s *UsageStore) ListAdmin(ctx context.Context, filter appusage.ListFilter) ([]appusage.LogRecord, bool, *int64, error) { query := s.db.UsageLog.Query() if filter.UserID != nil { query = query.Where(usageUserPredicate(*filter.UserID)) } query = applyUsageListFilter(query, filter) - - total, err := query.Count(ctx) - if err != nil { - return nil, 0, err + if filter.BeforeID > 0 { + query = query.Where(entusagelog.IDLT(int(filter.BeforeID))) } logs, err := query. + Select(usageLogListFields...). WithUser(). WithAPIKey(). WithAccount(). WithGroup(). - Offset((filter.Page-1)*filter.PageSize). - Limit(filter.PageSize). - Order(ent.Desc(entusagelog.FieldCreatedAt), ent.Desc(entusagelog.FieldID)). + Limit(filter.PageSize + 1). + Order(ent.Desc(entusagelog.FieldID)). All(ctx) if err != nil { - return nil, 0, err + return nil, false, nil, err + } + + return mapUsageLogPage(logs, filter.PageSize) +} + +func mapUsageLogPage(logs []*ent.UsageLog, pageSize int) ([]appusage.LogRecord, bool, *int64, error) { + hasMore := len(logs) > pageSize + if hasMore { + logs = logs[:pageSize] } result := make([]appusage.LogRecord, 0, len(logs)) for _, item := range logs { result = append(result, mapUsageLog(item)) } - return result, int64(total), nil + + var nextCursor *int64 + if hasMore && len(result) > 0 { + cursor := result[len(result)-1].ID + nextCursor = &cursor + } + return result, hasMore, nextCursor, nil } // SummaryUser 查询用户汇总统计。 @@ -429,6 +479,12 @@ func usageUserPredicate(userID int64) predicate.UsageLog { ) } +func usageLogColumnEQ(column string, value int) predicate.UsageLog { + return predicate.UsageLog(func(s *sql.Selector) { + s.Where(sql.EQ(s.C(column), value)) + }) +} + func coalesceString(primary, fallback string) string { if primary != "" { return primary @@ -438,7 +494,7 @@ func coalesceString(primary, fallback string) string { func applyUsageListFilter(query *ent.UsageLogQuery, filter appusage.ListFilter) *ent.UsageLogQuery { if filter.APIKeyID != nil { - query = query.Where(entusagelog.HasAPIKeyWith(entapikey.IDEQ(int(*filter.APIKeyID)))) + query = query.Where(usageLogColumnEQ(entusagelog.APIKeyColumn, int(*filter.APIKeyID))) } if filter.AccountID != nil { query = query.Where(entusagelog.HasAccountWith(entaccount.IDEQ(int(*filter.AccountID)))) @@ -458,7 +514,7 @@ func applyUsageListFilter(query *ent.UsageLogQuery, filter appusage.ListFilter) func applyUsageStatsFilter(query *ent.UsageLogQuery, filter appusage.StatsFilter) *ent.UsageLogQuery { if filter.APIKeyID != nil { - query = query.Where(entusagelog.HasAPIKeyWith(entapikey.IDEQ(int(*filter.APIKeyID)))) + query = query.Where(usageLogColumnEQ(entusagelog.APIKeyColumn, int(*filter.APIKeyID))) } if filter.Platform != "" { query = query.Where(entusagelog.PlatformEQ(filter.Platform)) @@ -529,14 +585,11 @@ func mapUsageLog(item *ent.UsageLog) appusage.LogRecord { OutputTokens: item.OutputTokens, CachedInputTokens: item.CachedInputTokens, CacheCreationTokens: item.CacheCreationTokens, - CacheCreation5mTokens: item.CacheCreation5mTokens, - CacheCreation1hTokens: item.CacheCreation1hTokens, ReasoningOutputTokens: item.ReasoningOutputTokens, InputPrice: item.InputPrice, OutputPrice: item.OutputPrice, CachedInputPrice: item.CachedInputPrice, CacheCreationPrice: item.CacheCreationPrice, - CacheCreation1hPrice: item.CacheCreation1hPrice, InputCost: item.InputCost, OutputCost: item.OutputCost, CachedInputCost: item.CachedInputCost, @@ -549,7 +602,6 @@ func mapUsageLog(item *ent.UsageLog) appusage.LogRecord { SellRate: item.SellRate, AccountRateMultiplier: item.AccountRateMultiplier, ServiceTier: item.ServiceTier, - ImageSize: item.ImageSize, Stream: item.Stream, DurationMs: item.DurationMs, FirstTokenMs: item.FirstTokenMs, @@ -557,9 +609,6 @@ func mapUsageLog(item *ent.UsageLog) appusage.LogRecord { IPAddress: item.IPAddress, Endpoint: item.Endpoint, ReasoningEffort: item.ReasoningEffort, - UsageAttributes: item.UsageAttributes, - UsageMetrics: item.UsageMetrics, - UsageCostDetails: item.UsageCostDetails, UsageMetadata: item.UsageMetadata, CreatedAt: item.CreatedAt.Format(time.RFC3339), } diff --git a/backend/internal/infra/store/usage_store_test.go b/backend/internal/infra/store/usage_store_test.go index 50a691b1..fbbd3726 100644 --- a/backend/internal/infra/store/usage_store_test.go +++ b/backend/internal/infra/store/usage_store_test.go @@ -5,10 +5,10 @@ import ( "testing" "time" - appusage "github.com/DouDOU-start/airgate-core/internal/app/usage" + appusage "github.com/DevilGenius/airgate-core/internal/app/usage" ) -func TestUsageStoreListPaginationIsStableForIdenticalCreatedAt(t *testing.T) { +func TestUsageStoreListPaginationUsesStableIDOrder(t *testing.T) { db := enttestOpen(t) defer func() { if err := db.Close(); err != nil { @@ -36,41 +36,53 @@ func TestUsageStoreListPaginationIsStableForIdenticalCreatedAt(t *testing.T) { store := NewUsageStore(db) t.Run("admin list", func(t *testing.T) { - page1, total, err := store.ListAdmin(ctx, appusage.ListFilter{Page: 1, PageSize: 2}) + page1, hasMore, nextCursor, err := store.ListAdmin(ctx, appusage.ListFilter{Page: 1, PageSize: 2}) if err != nil { t.Fatalf("ListAdmin page 1 returned error: %v", err) } - if total != 3 { - t.Fatalf("ListAdmin page 1 total = %d, want 3", total) + if !hasMore { + t.Fatalf("ListAdmin page 1 hasMore = false, want true") + } + if nextCursor == nil || *nextCursor != 2 { + t.Fatalf("ListAdmin page 1 nextCursor = %v, want 2", nextCursor) } assertLogIDs(t, page1, 3, 2) - page2, total, err := store.ListAdmin(ctx, appusage.ListFilter{Page: 2, PageSize: 2}) + page2, hasMore, nextCursor, err := store.ListAdmin(ctx, appusage.ListFilter{Page: 2, PageSize: 2, BeforeID: *nextCursor}) if err != nil { t.Fatalf("ListAdmin page 2 returned error: %v", err) } - if total != 3 { - t.Fatalf("ListAdmin page 2 total = %d, want 3", total) + if hasMore { + t.Fatalf("ListAdmin page 2 hasMore = true, want false") + } + if nextCursor != nil { + t.Fatalf("ListAdmin page 2 nextCursor = %v, want nil", *nextCursor) } assertLogIDs(t, page2, 1) }) t.Run("user list", func(t *testing.T) { - page1, total, err := store.ListUser(ctx, int64(user.ID), appusage.ListFilter{Page: 1, PageSize: 2}) + page1, hasMore, nextCursor, err := store.ListUser(ctx, int64(user.ID), appusage.ListFilter{Page: 1, PageSize: 2}) if err != nil { t.Fatalf("ListUser page 1 returned error: %v", err) } - if total != 3 { - t.Fatalf("ListUser page 1 total = %d, want 3", total) + if !hasMore { + t.Fatalf("ListUser page 1 hasMore = false, want true") + } + if nextCursor == nil || *nextCursor != 2 { + t.Fatalf("ListUser page 1 nextCursor = %v, want 2", nextCursor) } assertLogIDs(t, page1, 3, 2) - page2, total, err := store.ListUser(ctx, int64(user.ID), appusage.ListFilter{Page: 2, PageSize: 2}) + page2, hasMore, nextCursor, err := store.ListUser(ctx, int64(user.ID), appusage.ListFilter{Page: 2, PageSize: 2, BeforeID: *nextCursor}) if err != nil { t.Fatalf("ListUser page 2 returned error: %v", err) } - if total != 3 { - t.Fatalf("ListUser page 2 total = %d, want 3", total) + if hasMore { + t.Fatalf("ListUser page 2 hasMore = true, want false") + } + if nextCursor != nil { + t.Fatalf("ListUser page 2 nextCursor = %v, want nil", *nextCursor) } assertLogIDs(t, page2, 1) }) diff --git a/backend/internal/infra/store/user_store.go b/backend/internal/infra/store/user_store.go index 00ca691d..0e1c0c5c 100644 --- a/backend/internal/infra/store/user_store.go +++ b/backend/internal/infra/store/user_store.go @@ -4,14 +4,14 @@ import ( "context" "time" - "github.com/DouDOU-start/airgate-core/ent" - entapikey "github.com/DouDOU-start/airgate-core/ent/apikey" - entbalancelog "github.com/DouDOU-start/airgate-core/ent/balancelog" - "github.com/DouDOU-start/airgate-core/ent/predicate" - entusagelog "github.com/DouDOU-start/airgate-core/ent/usagelog" - entuser "github.com/DouDOU-start/airgate-core/ent/user" - entusersubscription "github.com/DouDOU-start/airgate-core/ent/usersubscription" - appuser "github.com/DouDOU-start/airgate-core/internal/app/user" + "github.com/DevilGenius/airgate-core/ent" + entapikey "github.com/DevilGenius/airgate-core/ent/apikey" + entbalancelog "github.com/DevilGenius/airgate-core/ent/balancelog" + "github.com/DevilGenius/airgate-core/ent/predicate" + entusagelog "github.com/DevilGenius/airgate-core/ent/usagelog" + entuser "github.com/DevilGenius/airgate-core/ent/user" + entusersubscription "github.com/DevilGenius/airgate-core/ent/usersubscription" + appuser "github.com/DevilGenius/airgate-core/internal/app/user" ) // UserStore 使用 Ent 实现用户仓储。 diff --git a/backend/internal/infra/store/user_store_test.go b/backend/internal/infra/store/user_store_test.go index a916d792..b0ff3c7b 100644 --- a/backend/internal/infra/store/user_store_test.go +++ b/backend/internal/infra/store/user_store_test.go @@ -5,9 +5,9 @@ import ( "testing" "time" - "github.com/DouDOU-start/airgate-core/ent" - entbalancelog "github.com/DouDOU-start/airgate-core/ent/balancelog" - appusage "github.com/DouDOU-start/airgate-core/internal/app/usage" + "github.com/DevilGenius/airgate-core/ent" + entbalancelog "github.com/DevilGenius/airgate-core/ent/balancelog" + appusage "github.com/DevilGenius/airgate-core/internal/app/usage" ) func TestUserStoreDeleteKeepsUsageAndBillingHistory(t *testing.T) { diff --git a/backend/internal/plugin/asset_cleanup.go b/backend/internal/plugin/asset_cleanup.go index 4188092a..6c2737f2 100644 --- a/backend/internal/plugin/asset_cleanup.go +++ b/backend/internal/plugin/asset_cleanup.go @@ -7,9 +7,9 @@ import ( "strings" "time" - "github.com/DouDOU-start/airgate-core/ent" - "github.com/DouDOU-start/airgate-core/ent/setting" - enttask "github.com/DouDOU-start/airgate-core/ent/task" + "github.com/DevilGenius/airgate-core/ent" + "github.com/DevilGenius/airgate-core/ent/setting" + enttask "github.com/DevilGenius/airgate-core/ent/task" ) const ( diff --git a/backend/internal/plugin/asset_cleanup_test.go b/backend/internal/plugin/asset_cleanup_test.go index 0b472078..b7392f37 100644 --- a/backend/internal/plugin/asset_cleanup_test.go +++ b/backend/internal/plugin/asset_cleanup_test.go @@ -10,9 +10,9 @@ import ( "entgo.io/ent/dialect/sql/schema" _ "github.com/mattn/go-sqlite3" - "github.com/DouDOU-start/airgate-core/ent" - "github.com/DouDOU-start/airgate-core/ent/enttest" - enttask "github.com/DouDOU-start/airgate-core/ent/task" + "github.com/DevilGenius/airgate-core/ent" + "github.com/DevilGenius/airgate-core/ent/enttest" + enttask "github.com/DevilGenius/airgate-core/ent/task" ) func TestAssetStorageCleanupExpiredLocal(t *testing.T) { diff --git a/backend/internal/plugin/asset_migration.go b/backend/internal/plugin/asset_migration.go index e1f8213c..7fe54de9 100644 --- a/backend/internal/plugin/asset_migration.go +++ b/backend/internal/plugin/asset_migration.go @@ -9,7 +9,7 @@ import ( "strings" "time" - "github.com/DouDOU-start/airgate-core/ent" + "github.com/DevilGenius/airgate-core/ent" ) const ( diff --git a/backend/internal/plugin/asset_storage.go b/backend/internal/plugin/asset_storage.go index d5c42bf7..f16173b6 100644 --- a/backend/internal/plugin/asset_storage.go +++ b/backend/internal/plugin/asset_storage.go @@ -22,8 +22,8 @@ import ( "github.com/minio/minio-go/v7" "github.com/minio/minio-go/v7/pkg/credentials" - "github.com/DouDOU-start/airgate-core/ent" - "github.com/DouDOU-start/airgate-core/ent/setting" + "github.com/DevilGenius/airgate-core/ent" + "github.com/DevilGenius/airgate-core/ent/setting" ) // AssetPurpose 是 core 内部定义的资产用途枚举。 diff --git a/backend/internal/plugin/context.go b/backend/internal/plugin/context.go index 07a7a677..37c828ae 100644 --- a/backend/internal/plugin/context.go +++ b/backend/internal/plugin/context.go @@ -6,7 +6,7 @@ import ( "strconv" "time" - sdk "github.com/DouDOU-start/airgate-sdk/sdkgo" + sdk "github.com/DevilGenius/airgate-sdk/sdkgo" ) // corePluginContext 核心侧的 PluginContext 实现 diff --git a/backend/internal/plugin/extension_proxy.go b/backend/internal/plugin/extension_proxy.go index a66f8744..3f0e407f 100644 --- a/backend/internal/plugin/extension_proxy.go +++ b/backend/internal/plugin/extension_proxy.go @@ -9,10 +9,10 @@ import ( "github.com/gin-gonic/gin" - pb "github.com/DouDOU-start/airgate-sdk/protocol/proto" - sdkgrpc "github.com/DouDOU-start/airgate-sdk/runtimego/grpc" + pb "github.com/DevilGenius/airgate-sdk/protocol/proto" + sdkgrpc "github.com/DevilGenius/airgate-sdk/runtimego/grpc" - "github.com/DouDOU-start/airgate-core/internal/server/middleware" + "github.com/DevilGenius/airgate-core/internal/server/middleware" ) // 请求体大小限制(100MB) diff --git a/backend/internal/plugin/forwarder.go b/backend/internal/plugin/forwarder.go index 173a7a13..9f87d926 100644 --- a/backend/internal/plugin/forwarder.go +++ b/backend/internal/plugin/forwarder.go @@ -9,13 +9,13 @@ import ( "github.com/gin-gonic/gin" - "github.com/DouDOU-start/airgate-core/ent" - "github.com/DouDOU-start/airgate-core/internal/auth" - "github.com/DouDOU-start/airgate-core/internal/billing" - "github.com/DouDOU-start/airgate-core/internal/routing" - "github.com/DouDOU-start/airgate-core/internal/scheduler" - "github.com/DouDOU-start/airgate-core/internal/server/middleware" - sdk "github.com/DouDOU-start/airgate-sdk/sdkgo" + "github.com/DevilGenius/airgate-core/ent" + "github.com/DevilGenius/airgate-core/internal/auth" + "github.com/DevilGenius/airgate-core/internal/billing" + "github.com/DevilGenius/airgate-core/internal/routing" + "github.com/DevilGenius/airgate-core/internal/scheduler" + "github.com/DevilGenius/airgate-core/internal/server/middleware" + sdk "github.com/DevilGenius/airgate-sdk/sdkgo" ) // 访问日志富化键的本地别名,避免在大量 c.Set 处重复写出包名。 @@ -231,6 +231,9 @@ func (f *Forwarder) Forward(c *gin.Context) { releaseAccountSlot, ok := f.acquireAccountSlot(c, state) if !ok { failureSummary.recordLocalCapacityFailure() + if state.requireContinuationAffinity { + break + } softExclude = append(softExclude, accountID) continue } @@ -375,13 +378,14 @@ func hasForwardResult(execution forwardExecution) bool { } type allRoutesFailureSummary struct { - rateLimitedSeen bool - rateLimitedRetryAfter time.Duration - localCapacitySeen bool - accountUnavailable bool - accountDeadSeen bool - upstreamTimeoutSeen bool - upstreamFailureSeen bool + rateLimitedSeen bool + rateLimitedRetryAfter time.Duration + localCapacitySeen bool + continuationAffinityMissing bool + accountUnavailable bool + accountDeadSeen bool + upstreamTimeoutSeen bool + upstreamFailureSeen bool } func (s *allRoutesFailureSummary) recordExecution(execution forwardExecution) { @@ -391,6 +395,8 @@ func (s *allRoutesFailureSummary) recordExecution(execution forwardExecution) { s.recordRetryAfter(execution.outcome.RetryAfter) case sdk.OutcomeAccountDead: s.accountDeadSeen = true + case sdk.OutcomeAccountUnavailable: + s.accountUnavailable = true case sdk.OutcomeUpstreamTransient: if isTimeoutFailure(execution) { s.upstreamTimeoutSeen = true @@ -413,7 +419,15 @@ func (s *allRoutesFailureSummary) recordRetryAfter(retryAfter time.Duration) { } } -func (s *allRoutesFailureSummary) recordPickAccountError(error) { +func (s *allRoutesFailureSummary) recordPickAccountError(err error) { + if errors.Is(err, scheduler.ErrContinuationCapacityExceeded) { + s.localCapacitySeen = true + return + } + if errors.Is(err, scheduler.ErrContinuationAffinityMissing) { + s.continuationAffinityMissing = true + return + } s.accountUnavailable = true } @@ -439,6 +453,14 @@ func writeAllRoutesFailed(c *gin.Context, summary allRoutesFailureSummary) { } func selectAllRoutesFailureResponse(summary allRoutesFailureSummary) allRoutesFailureResponse { + if summary.continuationAffinityMissing { + return allRoutesFailureResponse{ + status: http.StatusBadRequest, + errType: "invalid_request_error", + code: "continuation_affinity_missing", + message: "无法定位 previous_response_id 对应的上游会话,请使用同一会话重试或提供完整上下文", + } + } if summary.rateLimitedSeen { retryAfter := summary.rateLimitedRetryAfter if retryAfter <= 0 { @@ -624,6 +646,9 @@ func (f *Forwarder) canFailover(c *gin.Context, state *forwardState, execution f if state.stream && c.Writer.Written() { return false } + if state.requireContinuationAffinity { + return false + } if execution.err != nil { return true } diff --git a/backend/internal/plugin/forwarder_result_test.go b/backend/internal/plugin/forwarder_result_test.go index c3875114..b5b7d756 100644 --- a/backend/internal/plugin/forwarder_result_test.go +++ b/backend/internal/plugin/forwarder_result_test.go @@ -10,8 +10,8 @@ import ( "github.com/gin-gonic/gin" - "github.com/DouDOU-start/airgate-core/ent" - sdk "github.com/DouDOU-start/airgate-sdk/sdkgo" + "github.com/DevilGenius/airgate-core/ent" + sdk "github.com/DevilGenius/airgate-sdk/sdkgo" ) // TestMain 在所有并行测试启动前调一次 gin.SetMode,避免 SetMode 内部变量 diff --git a/backend/internal/plugin/forwarder_test.go b/backend/internal/plugin/forwarder_test.go index 76e9defc..f120b3ca 100644 --- a/backend/internal/plugin/forwarder_test.go +++ b/backend/internal/plugin/forwarder_test.go @@ -11,10 +11,11 @@ import ( "github.com/gin-gonic/gin" - "github.com/DouDOU-start/airgate-core/ent" - "github.com/DouDOU-start/airgate-core/internal/auth" - "github.com/DouDOU-start/airgate-core/internal/routing" - sdk "github.com/DouDOU-start/airgate-sdk/sdkgo" + "github.com/DevilGenius/airgate-core/ent" + "github.com/DevilGenius/airgate-core/internal/auth" + "github.com/DevilGenius/airgate-core/internal/routing" + "github.com/DevilGenius/airgate-core/internal/scheduler" + sdk "github.com/DevilGenius/airgate-sdk/sdkgo" ) func TestParseBody(t *testing.T) { @@ -51,6 +52,44 @@ func TestParseBody_StreamTrue(t *testing.T) { } } +func TestParseBodyContinuationSignals(t *testing.T) { + t.Parallel() + + body := []byte(`{"model":"gpt-5.4","prompt_cache_key":"pcache_1","previous_response_id":"resp_1","input":[{"type":"function_call_output","call_id":"call_1","output":"ok"}]}`) + parsed := parseBody(body, "application/json") + if parsed.PromptCacheKey != "pcache_1" { + t.Fatalf("PromptCacheKey = %q, want pcache_1", parsed.PromptCacheKey) + } + if parsed.PreviousResponseID != "resp_1" { + t.Fatalf("PreviousResponseID = %q, want resp_1", parsed.PreviousResponseID) + } + if !parsed.HasToolOutput { + t.Fatalf("HasToolOutput = false, want true") + } + if parsed.HasToolCallContext { + t.Fatalf("HasToolCallContext = true, want false") + } + if !requestRequiresContinuationAffinity(parsed) { + t.Fatalf("requestRequiresContinuationAffinity = false, want true") + } +} + +func TestParseBodyContinuationSignalsWithToolCallContext(t *testing.T) { + t.Parallel() + + body := []byte(`{"model":"gpt-5.4","input":[{"type":"function_call","call_id":"call_1","name":"lookup"},{"type":"function_call_output","call_id":"call_1","output":"ok"}]}`) + parsed := parseBody(body, "application/json") + if !parsed.HasToolOutput { + t.Fatalf("HasToolOutput = false, want true") + } + if !parsed.HasToolCallContext { + t.Fatalf("HasToolCallContext = false, want true") + } + if requestRequiresContinuationAffinity(parsed) { + t.Fatalf("requestRequiresContinuationAffinity = true, want false") + } +} + func TestParseBody_MultipartIgnoresFileParts(t *testing.T) { t.Parallel() @@ -376,6 +415,14 @@ func TestSelectAllRoutesFailureResponse(t *testing.T) { wantStatus int wantCode string }{ + { + name: "continuation affinity missing", + summary: allRoutesFailureSummary{ + continuationAffinityMissing: true, + }, + wantStatus: http.StatusBadRequest, + wantCode: "continuation_affinity_missing", + }, { name: "upstream rate limited", summary: allRoutesFailureSummary{ @@ -455,3 +502,24 @@ func TestAllRoutesFailureSummaryRecordsTimeout(t *testing.T) { t.Fatalf("upstreamFailureSeen = true, want false") } } + +func TestAllRoutesFailureSummaryRecordsContinuationCapacity(t *testing.T) { + t.Parallel() + + summary := allRoutesFailureSummary{} + summary.recordPickAccountError(scheduler.ErrContinuationCapacityExceeded) + + if !summary.localCapacitySeen { + t.Fatalf("localCapacitySeen = false, want true") + } + if summary.continuationAffinityMissing { + t.Fatalf("continuationAffinityMissing = true, want false") + } + response := selectAllRoutesFailureResponse(summary) + if response.status != http.StatusTooManyRequests { + t.Fatalf("status = %d, want %d", response.status, http.StatusTooManyRequests) + } + if response.code != "all_routes_capacity_exhausted" { + t.Fatalf("code = %q, want all_routes_capacity_exhausted", response.code) + } +} diff --git a/backend/internal/plugin/host_service.go b/backend/internal/plugin/host_service.go index e4aad12c..3828c2aa 100644 --- a/backend/internal/plugin/host_service.go +++ b/backend/internal/plugin/host_service.go @@ -14,14 +14,14 @@ import ( "google.golang.org/grpc/codes" "google.golang.org/grpc/status" - "github.com/DouDOU-start/airgate-core/ent" - "github.com/DouDOU-start/airgate-core/ent/account" - "github.com/DouDOU-start/airgate-core/ent/user" - "github.com/DouDOU-start/airgate-core/internal/billing" - "github.com/DouDOU-start/airgate-core/internal/routing" - "github.com/DouDOU-start/airgate-core/internal/scheduler" - pb "github.com/DouDOU-start/airgate-sdk/protocol/proto" - sdk "github.com/DouDOU-start/airgate-sdk/sdkgo" + "github.com/DevilGenius/airgate-core/ent" + "github.com/DevilGenius/airgate-core/ent/account" + "github.com/DevilGenius/airgate-core/ent/user" + "github.com/DevilGenius/airgate-core/internal/billing" + "github.com/DevilGenius/airgate-core/internal/routing" + "github.com/DevilGenius/airgate-core/internal/scheduler" + pb "github.com/DevilGenius/airgate-sdk/protocol/proto" + sdk "github.com/DevilGenius/airgate-sdk/sdkgo" ) // HostService 是 Core 暴露给插件的反向 gRPC 能力的"底层实现"。 @@ -629,6 +629,10 @@ func (h *HostService) probeForward(ctx context.Context, req hostProbeForwardRequ resp["success"] = false resp["error_kind"] = "account_error" resp["error_msg"] = truncateProbeErr(outcome.Reason) + case sdk.OutcomeAccountUnavailable: + resp["success"] = false + resp["error_kind"] = "account_unavailable" + resp["error_msg"] = truncateProbeErr(outcome.Reason) case sdk.OutcomeUpstreamTransient, sdk.OutcomeStreamAborted: resp["success"] = false resp["error_kind"] = "upstream_5xx" @@ -1151,10 +1155,12 @@ func (h *HostService) recordHostForwardUsage( BillingRate: route.EffectiveRate, AccountRate: accFull.RateMultiplier, } - if override, ok := imageOutputBillingOverride(usage, route.GroupPluginSettings); ok { - calcInput.OutputBillingCostOverride = &override + if override, ok := imageBillingCostOverride(usage, route.GroupPluginSettings); ok { + calcInput.BillingCostOverride = &override } calc := h.calculator.Calculate(calcInput) + reasoningEffort := resolveReasoningEffort(hostForwardReasoningEffort(req), usage) + usageMetadata := usageMetadataFromSDK(usage, usageValues) h.scheduler.AddWindowCost(ctx, accountID, calc.AccountCost) @@ -1175,14 +1181,11 @@ func (h *HostService) recordHostForwardUsage( OutputTokens: usageValues.OutputTokens, CachedInputTokens: usageValues.CachedInputTokens, CacheCreationTokens: usageValues.CacheCreationTokens, - CacheCreation5mTokens: usageValues.CacheCreation5mTokens, - CacheCreation1hTokens: usageValues.CacheCreation1hTokens, ReasoningOutputTokens: usageValues.ReasoningOutputTokens, InputPrice: usageValues.InputPrice, OutputPrice: usageValues.OutputPrice, CachedInputPrice: usageValues.CachedInputPrice, CacheCreationPrice: usageValues.CacheCreationPrice, - CacheCreation1hPrice: usageValues.CacheCreation1hPrice, InputCost: calc.InputCost, OutputCost: calc.OutputCost, CachedInputCost: calc.CachedInputCost, @@ -1194,16 +1197,12 @@ func (h *HostService) recordHostForwardUsage( RateMultiplier: calc.RateMultiplier, AccountRateMultiplier: calc.AccountRateMultiplier, ServiceTier: usageValues.ServiceTier, - ImageSize: usageValues.ImageSize, Endpoint: req.Path, - ReasoningEffort: resolveReasoningEffort(hostForwardReasoningEffort(req), usage), + ReasoningEffort: reasoningEffort, Stream: req.Stream, DurationMs: duration.Milliseconds(), FirstTokenMs: usageValues.FirstTokenMs, - UsageAttributes: usage.Attributes, - UsageMetrics: usage.Metrics, - UsageCostDetails: usage.CostDetails, - UsageMetadata: usage.Metadata, + UsageMetadata: usageMetadata, } if h.recorder == nil { return 0, nil diff --git a/backend/internal/plugin/host_service_test.go b/backend/internal/plugin/host_service_test.go index 5b5edf9c..574ffa88 100644 --- a/backend/internal/plugin/host_service_test.go +++ b/backend/internal/plugin/host_service_test.go @@ -12,8 +12,8 @@ import ( "google.golang.org/grpc/codes" "google.golang.org/grpc/status" - "github.com/DouDOU-start/airgate-core/ent/enttest" - sdk "github.com/DouDOU-start/airgate-sdk/sdkgo" + "github.com/DevilGenius/airgate-core/ent/enttest" + sdk "github.com/DevilGenius/airgate-sdk/sdkgo" ) func TestHostForwardTimeout(t *testing.T) { diff --git a/backend/internal/plugin/image_pricing.go b/backend/internal/plugin/image_pricing.go index a73896a5..32097246 100644 --- a/backend/internal/plugin/image_pricing.go +++ b/backend/internal/plugin/image_pricing.go @@ -1,11 +1,10 @@ package plugin import ( - "math" "strconv" "strings" - sdk "github.com/DouDOU-start/airgate-sdk/sdkgo" + sdk "github.com/DevilGenius/airgate-sdk/sdkgo" ) const ( @@ -16,24 +15,20 @@ const ( imagePrice4KKey = "image_price_4k" ) -func imageOutputBillingOverride(usage *sdk.Usage, settings map[string]map[string]string) (float64, bool) { +func imageBillingCostOverride(usage *sdk.Usage, settings map[string]map[string]string) (float64, bool) { snap := usageSnapshotFromSDK(usage) - if strings.TrimSpace(snap.ImageSize) == "" || snap.OutputCost <= 0 { + if strings.TrimSpace(snap.ImageSize) == "" || snap.ImageCount <= 0 { return 0, false } - tier, basePrice, ok := imageTierForSize(snap.ImageSize) - if !ok || basePrice <= 0 { + tier, _, ok := imageTierForSize(snap.ImageSize) + if !ok { return 0, false } price, ok := imageTierPriceFromSettings(settings, tier) if !ok { return 0, false } - imageCount := int(math.Round(snap.OutputCost / basePrice)) - if imageCount < 1 { - imageCount = 1 - } - return float64(imageCount) * price, true + return float64(snap.ImageCount) * price, true } func shouldForwardPluginSetting(plugin, key string) bool { diff --git a/backend/internal/plugin/image_pricing_test.go b/backend/internal/plugin/image_pricing_test.go index ac94cdc7..bb617b79 100644 --- a/backend/internal/plugin/image_pricing_test.go +++ b/backend/internal/plugin/image_pricing_test.go @@ -4,16 +4,15 @@ import ( "math" "testing" - sdk "github.com/DouDOU-start/airgate-sdk/sdkgo" + sdk "github.com/DevilGenius/airgate-sdk/sdkgo" ) -func TestImageOutputBillingOverride_UsesConfiguredTier(t *testing.T) { +func TestImageBillingCostOverride_UsesConfiguredTier(t *testing.T) { usage := &sdk.Usage{ - Attributes: []sdk.UsageAttribute{ - {Key: "image_size", Value: "1672x941"}, - }, - CostDetails: []sdk.UsageCostDetail{ - {Key: "images", AccountCost: 0.40}, + OutputCost: 0.40, + Metadata: map[string]string{ + "openai.image.size": "1672x941", + "openai.image.count": "2", }, } settings := map[string]map[string]string{ @@ -22,7 +21,7 @@ func TestImageOutputBillingOverride_UsesConfiguredTier(t *testing.T) { }, } - got, ok := imageOutputBillingOverride(usage, settings) + got, ok := imageBillingCostOverride(usage, settings) if !ok { t.Fatal("expected override") } @@ -31,13 +30,12 @@ func TestImageOutputBillingOverride_UsesConfiguredTier(t *testing.T) { } } -func TestImageOutputBillingOverride_FallsBackWhenTierUnset(t *testing.T) { +func TestImageBillingCostOverride_FallsBackWhenTierUnset(t *testing.T) { usage := &sdk.Usage{ - Attributes: []sdk.UsageAttribute{ - {Key: "image_size", Value: "3840x2160"}, - }, - CostDetails: []sdk.UsageCostDetail{ - {Key: "images", AccountCost: 0.40}, + OutputCost: 0.40, + Metadata: map[string]string{ + "openai.image.size": "3840x2160", + "openai.image.count": "2", }, } settings := map[string]map[string]string{ @@ -46,11 +44,64 @@ func TestImageOutputBillingOverride_FallsBackWhenTierUnset(t *testing.T) { }, } - if got, ok := imageOutputBillingOverride(usage, settings); ok { + if got, ok := imageBillingCostOverride(usage, settings); ok { t.Fatalf("override = %v, want fallback", got) } } +func TestUsageSnapshotFromSDKReadsPluginMetadata(t *testing.T) { + usage := &sdk.Usage{ + InputTokens: 10, + OutputCost: 0.40, + ReasoningEffort: "high", + Metadata: map[string]string{ + "service_tier": "priority", + "openai.image.size": "1672x941", + "openai.image.input_text_tokens": "3", + "openai.image.input_image_tokens": "7", + "openai.image.count": "2", + "openai.image.unit_price": "0.2", + "openai.image.unit": "USD/image", + }, + } + + snap := usageSnapshotFromSDK(usage) + if snap.ServiceTier != "priority" || snap.ImageSize != "1672x941" { + t.Fatalf("snapshot metadata strings = (%q, %q)", snap.ServiceTier, snap.ImageSize) + } + if snap.TextInputTokens != 3 || snap.ImageInputTokens != 7 || snap.ImageCount != 2 { + t.Fatalf("snapshot metadata ints = (%d, %d, %d)", snap.TextInputTokens, snap.ImageInputTokens, snap.ImageCount) + } + if snap.ImageUnitPrice != 0.2 || snap.ImageUnit != "USD/image" { + t.Fatalf("snapshot image price = (%v, %q)", snap.ImageUnitPrice, snap.ImageUnit) + } + if got := resolveReasoningEffort("", usage); got != "high" { + t.Fatalf("resolveReasoningEffort = %q, want high", got) + } +} + +func TestUsageMetadataFromSDKPreservesPluginMetadata(t *testing.T) { + usage := &sdk.Usage{ + Metadata: map[string]string{ + "custom.plugin.value": "kept", + "openai.image.size": "1672x941", + "openai.image.input_text_tokens": "3", + "claude.cache_creation_1h_tokens": "4", + }, + } + + meta := usageMetadataFromSDK(usage, usageSnapshotFromSDK(usage)) + if meta["custom.plugin.value"] != "kept" { + t.Fatalf("custom plugin metadata = %q, want kept", meta["custom.plugin.value"]) + } + if meta["openai.image.size"] != "1672x941" || meta["openai.image.input_text_tokens"] != "3" { + t.Fatalf("openai image metadata not preserved: %+v", meta) + } + if meta["claude.cache_creation_1h_tokens"] != "4" { + t.Fatalf("claude metadata = %q, want 4", meta["claude.cache_creation_1h_tokens"]) + } +} + func TestImageTierForSize(t *testing.T) { tests := []struct { size string diff --git a/backend/internal/plugin/manager_background.go b/backend/internal/plugin/manager_background.go index 7f52743a..697c930f 100644 --- a/backend/internal/plugin/manager_background.go +++ b/backend/internal/plugin/manager_background.go @@ -5,7 +5,7 @@ import ( "log/slog" "time" - sdkgrpc "github.com/DouDOU-start/airgate-sdk/runtimego/grpc" + sdkgrpc "github.com/DevilGenius/airgate-sdk/runtimego/grpc" ) // minBackgroundInterval 兜底最小间隔,避免插件声明 0 / 极小间隔时把 Core 打爆。 diff --git a/backend/internal/plugin/manager_catalog.go b/backend/internal/plugin/manager_catalog.go index a9396ef1..12cf02c1 100644 --- a/backend/internal/plugin/manager_catalog.go +++ b/backend/internal/plugin/manager_catalog.go @@ -8,11 +8,11 @@ import ( "path/filepath" "strings" - sdkgrpc "github.com/DouDOU-start/airgate-sdk/runtimego/grpc" - sdk "github.com/DouDOU-start/airgate-sdk/sdkgo" + sdkgrpc "github.com/DevilGenius/airgate-sdk/runtimego/grpc" + sdk "github.com/DevilGenius/airgate-sdk/sdkgo" - "github.com/DouDOU-start/airgate-core/ent" - pluginent "github.com/DouDOU-start/airgate-core/ent/plugin" + "github.com/DevilGenius/airgate-core/ent" + pluginent "github.com/DevilGenius/airgate-core/ent/plugin" ) // GetExtensionByName 根据插件名查找 extension 类型插件。 diff --git a/backend/internal/plugin/manager_install.go b/backend/internal/plugin/manager_install.go index d5772782..ade59329 100644 --- a/backend/internal/plugin/manager_install.go +++ b/backend/internal/plugin/manager_install.go @@ -7,6 +7,7 @@ import ( "io" "log/slog" "net/http" + "net/url" "os" "os/exec" "path/filepath" @@ -15,8 +16,8 @@ import ( goplugin "github.com/hashicorp/go-plugin" - sdkgrpc "github.com/DouDOU-start/airgate-sdk/runtimego/grpc" - sdk "github.com/DouDOU-start/airgate-sdk/sdkgo" + sdkgrpc "github.com/DevilGenius/airgate-sdk/runtimego/grpc" + sdk "github.com/DevilGenius/airgate-sdk/sdkgo" ) // Uninstall 卸载插件。 @@ -53,19 +54,27 @@ func (m *Manager) InstallFromBinary(ctx context.Context, name string, binary []b realName = name } + targetDir := filepath.Join(m.pluginDir, realName) + binaryPath := filepath.Join(targetDir, realName) + previousBinary, previousErr := os.ReadFile(binaryPath) + m.stopPlugin(realName) - targetDir := filepath.Join(m.pluginDir, realName) if err := os.MkdirAll(targetDir, 0755); err != nil { return fmt.Errorf("创建插件目录失败: %w", err) } - binaryPath := filepath.Join(targetDir, realName) if err := os.WriteFile(binaryPath, binary, 0755); err != nil { return fmt.Errorf("写入插件二进制失败: %w", err) } canonicalName, err := m.startPlugin(ctx, realName, exec.Command(binaryPath), realName) if err != nil { + if previousErr == nil { + if restoreErr := m.restorePreviousBinary(ctx, realName, binaryPath, previousBinary); restoreErr != nil { + return fmt.Errorf("启动插件失败: %w;恢复旧版本也失败: %v", err, restoreErr) + } + slog.Warn("新插件启动失败,已恢复旧版本", "name", realName, "error", err) + } return fmt.Errorf("启动插件失败: %w", err) } @@ -73,6 +82,16 @@ func (m *Manager) InstallFromBinary(ctx context.Context, name string, binary []b return nil } +func (m *Manager) restorePreviousBinary(ctx context.Context, name, binaryPath string, previousBinary []byte) error { + if err := os.WriteFile(binaryPath, previousBinary, 0755); err != nil { + return fmt.Errorf("写回旧插件二进制失败: %w", err) + } + if _, err := m.startPlugin(ctx, name, exec.Command(binaryPath), name); err != nil { + return fmt.Errorf("重启旧插件失败: %w", err) + } + return nil +} + func (m *Manager) probePluginName(fallbackName string, binary []byte) (string, error) { tmpDir, err := os.MkdirTemp("", "airgate-probe-*") if err != nil { @@ -128,36 +147,16 @@ func (m *Manager) probePluginName(fallbackName string, binary []byte) (string, e } // InstallFromGithub 从 GitHub Release 下载并安装插件。 -func (m *Manager) InstallFromGithub(ctx context.Context, repo string) error { +// version 为空时安装 latest release;非空时按 release tag 安装,可用于回滚到旧版本。 +func (m *Manager) InstallFromGithub(ctx context.Context, repo, version string) error { owner, repoName, err := parseGithubRepo(repo) if err != nil { return err } - apiURL := fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/latest", owner, repoName) - req, _ := http.NewRequestWithContext(ctx, http.MethodGet, apiURL, nil) - req.Header.Set("Accept", "application/vnd.github.v3+json") - - resp, err := http.DefaultClient.Do(req) + release, err := fetchGithubReleaseForInstall(ctx, owner, repoName, version) if err != nil { - return fmt.Errorf("请求 GitHub API 失败: %w", err) - } - defer func() { - if err := resp.Body.Close(); err != nil { - slog.Warn("关闭 GitHub API 响应失败", "repo", repo, "error", err) - } - }() - - if resp.StatusCode == http.StatusNotFound { - return fmt.Errorf("仓库 %s/%s 不存在或没有 Release", owner, repoName) - } - if resp.StatusCode != http.StatusOK { - return fmt.Errorf("GitHub API 返回状态码 %d", resp.StatusCode) - } - - var release githubRelease - if err := json.NewDecoder(resp.Body).Decode(&release); err != nil { - return fmt.Errorf("解析 Release 数据失败: %w", err) + return err } targetOS := runtime.GOOS @@ -197,6 +196,84 @@ func (m *Manager) InstallFromGithub(ctx context.Context, repo string) error { return m.InstallFromBinary(ctx, repoName, binary) } +func fetchGithubReleaseForInstall(ctx context.Context, owner, repoName, version string) (githubRelease, error) { + var lastStatus int + for _, apiURL := range githubReleaseAPIURLs(owner, repoName, version) { + release, status, err := fetchGithubReleaseByURL(ctx, apiURL) + if err == nil { + return release, nil + } + lastStatus = status + if status != http.StatusNotFound { + return githubRelease{}, err + } + } + + if strings.TrimSpace(version) == "" { + return githubRelease{}, fmt.Errorf("仓库 %s/%s 不存在或没有 Release", owner, repoName) + } + if lastStatus == http.StatusNotFound { + return githubRelease{}, fmt.Errorf("仓库 %s/%s 不存在或没有 Release %s", owner, repoName, strings.TrimSpace(version)) + } + return githubRelease{}, fmt.Errorf("无法获取仓库 %s/%s 的 Release %s", owner, repoName, strings.TrimSpace(version)) +} + +func fetchGithubReleaseByURL(ctx context.Context, apiURL string) (githubRelease, int, error) { + req, _ := http.NewRequestWithContext(ctx, http.MethodGet, apiURL, nil) + req.Header.Set("Accept", "application/vnd.github.v3+json") + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return githubRelease{}, 0, fmt.Errorf("请求 GitHub API 失败: %w", err) + } + defer func() { + if err := resp.Body.Close(); err != nil { + slog.Warn("关闭 GitHub API 响应失败", "url", apiURL, "error", err) + } + }() + + if resp.StatusCode == http.StatusNotFound { + return githubRelease{}, resp.StatusCode, fmt.Errorf("GitHub Release 不存在") + } + if resp.StatusCode != http.StatusOK { + return githubRelease{}, resp.StatusCode, fmt.Errorf("GitHub API 返回状态码 %d", resp.StatusCode) + } + + var release githubRelease + if err := json.NewDecoder(resp.Body).Decode(&release); err != nil { + return githubRelease{}, resp.StatusCode, fmt.Errorf("解析 Release 数据失败: %w", err) + } + return release, resp.StatusCode, nil +} + +func githubReleaseAPIURLs(owner, repoName, version string) []string { + baseURL := fmt.Sprintf("https://api.github.com/repos/%s/%s/releases", owner, repoName) + version = strings.TrimSpace(version) + if version == "" { + return []string{baseURL + "/latest"} + } + + tags := []string{version} + if strings.HasPrefix(version, "v") { + if trimmed := strings.TrimPrefix(version, "v"); trimmed != "" { + tags = append(tags, trimmed) + } + } else { + tags = append(tags, "v"+version) + } + + urls := make([]string, 0, len(tags)) + seen := make(map[string]struct{}, len(tags)) + for _, tag := range tags { + if _, ok := seen[tag]; ok { + continue + } + seen[tag] = struct{}{} + urls = append(urls, baseURL+"/tags/"+url.PathEscape(tag)) + } + return urls +} + type githubRelease struct { TagName string `json:"tag_name"` Assets []githubAsset `json:"assets"` diff --git a/backend/internal/plugin/manager_plugin_db.go b/backend/internal/plugin/manager_plugin_db.go index 321effce..67f4523e 100644 --- a/backend/internal/plugin/manager_plugin_db.go +++ b/backend/internal/plugin/manager_plugin_db.go @@ -11,8 +11,8 @@ import ( _ "github.com/lib/pq" - "github.com/DouDOU-start/airgate-core/ent" - settingent "github.com/DouDOU-start/airgate-core/ent/setting" + "github.com/DevilGenius/airgate-core/ent" + settingent "github.com/DevilGenius/airgate-core/ent/setting" ) // manager_plugin_db.go:为每个插件 provision 独立 schema + 受限 postgres role diff --git a/backend/internal/plugin/manager_runtime.go b/backend/internal/plugin/manager_runtime.go index f400f3a9..d61b0788 100644 --- a/backend/internal/plugin/manager_runtime.go +++ b/backend/internal/plugin/manager_runtime.go @@ -15,13 +15,13 @@ import ( "google.golang.org/grpc/codes" "google.golang.org/grpc/status" - pb "github.com/DouDOU-start/airgate-sdk/protocol/proto" - sdkgrpc "github.com/DouDOU-start/airgate-sdk/runtimego/grpc" - sdk "github.com/DouDOU-start/airgate-sdk/sdkgo" + pb "github.com/DevilGenius/airgate-sdk/protocol/proto" + sdkgrpc "github.com/DevilGenius/airgate-sdk/runtimego/grpc" + sdk "github.com/DevilGenius/airgate-sdk/sdkgo" - "github.com/DouDOU-start/airgate-core/ent" - pluginent "github.com/DouDOU-start/airgate-core/ent/plugin" - settingent "github.com/DouDOU-start/airgate-core/ent/setting" + "github.com/DevilGenius/airgate-core/ent" + pluginent "github.com/DevilGenius/airgate-core/ent/plugin" + settingent "github.com/DevilGenius/airgate-core/ent/setting" ) // pluginGRPCMaxMessageBytes 是与插件之间 gRPC 单条消息的最大字节数(收/发同值)。 diff --git a/backend/internal/plugin/manager_tasks.go b/backend/internal/plugin/manager_tasks.go index e95d1f67..8f620f58 100644 --- a/backend/internal/plugin/manager_tasks.go +++ b/backend/internal/plugin/manager_tasks.go @@ -8,11 +8,11 @@ import ( "sync" "time" - "github.com/DouDOU-start/airgate-core/ent" - enttask "github.com/DouDOU-start/airgate-core/ent/task" - pb "github.com/DouDOU-start/airgate-sdk/protocol/proto" - sdkgrpc "github.com/DouDOU-start/airgate-sdk/runtimego/grpc" - sdk "github.com/DouDOU-start/airgate-sdk/sdkgo" + "github.com/DevilGenius/airgate-core/ent" + enttask "github.com/DevilGenius/airgate-core/ent/task" + pb "github.com/DevilGenius/airgate-sdk/protocol/proto" + sdkgrpc "github.com/DevilGenius/airgate-sdk/runtimego/grpc" + sdk "github.com/DevilGenius/airgate-sdk/sdkgo" ) const ( diff --git a/backend/internal/plugin/manager_test.go b/backend/internal/plugin/manager_test.go index 667bdf33..f2e585dd 100644 --- a/backend/internal/plugin/manager_test.go +++ b/backend/internal/plugin/manager_test.go @@ -4,7 +4,7 @@ import ( "os/exec" "testing" - sdk "github.com/DouDOU-start/airgate-sdk/sdkgo" + sdk "github.com/DevilGenius/airgate-sdk/sdkgo" ) func TestMatchPluginByPlatformAndPath(t *testing.T) { @@ -59,6 +59,52 @@ func TestParseGithubRepo(t *testing.T) { } } +func TestGithubReleaseAPIURLs(t *testing.T) { + tests := []struct { + name string + version string + want []string + }{ + { + name: "latest", + version: "", + want: []string{ + "https://api.github.com/repos/acme/airgate-plugin/releases/latest", + }, + }, + { + name: "plain version tries v-prefixed fallback", + version: "1.2.3", + want: []string{ + "https://api.github.com/repos/acme/airgate-plugin/releases/tags/1.2.3", + "https://api.github.com/repos/acme/airgate-plugin/releases/tags/v1.2.3", + }, + }, + { + name: "v-prefixed version tries plain fallback", + version: "v1.2.3", + want: []string{ + "https://api.github.com/repos/acme/airgate-plugin/releases/tags/v1.2.3", + "https://api.github.com/repos/acme/airgate-plugin/releases/tags/1.2.3", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := githubReleaseAPIURLs("acme", "airgate-plugin", tt.version) + if len(got) != len(tt.want) { + t.Fatalf("len(got) = %d, want %d: %#v", len(got), len(tt.want), got) + } + for i := range tt.want { + if got[i] != tt.want[i] { + t.Fatalf("got[%d] = %q, want %q", i, got[i], tt.want[i]) + } + } + }) + } +} + func TestGetModelsReturnsClone(t *testing.T) { mgr := &Manager{ modelCache: map[string][]sdk.ModelInfo{ diff --git a/backend/internal/plugin/manager_types.go b/backend/internal/plugin/manager_types.go index 781f521c..9edda6ed 100644 --- a/backend/internal/plugin/manager_types.go +++ b/backend/internal/plugin/manager_types.go @@ -9,10 +9,10 @@ import ( goplugin "github.com/hashicorp/go-plugin" - sdkgrpc "github.com/DouDOU-start/airgate-sdk/runtimego/grpc" - sdk "github.com/DouDOU-start/airgate-sdk/sdkgo" + sdkgrpc "github.com/DevilGenius/airgate-sdk/runtimego/grpc" + sdk "github.com/DevilGenius/airgate-sdk/sdkgo" - "github.com/DouDOU-start/airgate-core/ent" + "github.com/DevilGenius/airgate-core/ent" ) // PluginInstance 运行中的插件实例。 diff --git a/backend/internal/plugin/marketplace.go b/backend/internal/plugin/marketplace.go index 693ace10..fd324b70 100644 --- a/backend/internal/plugin/marketplace.go +++ b/backend/internal/plugin/marketplace.go @@ -109,7 +109,7 @@ var officialPlugins = []MarketplacePlugin{ Description: "OpenAI API 网关插件", Author: "AirGate", Type: "gateway", - GithubRepo: "DouDOU-start/airgate-openai", + GithubRepo: "DevilGenius/airgate-openai", }, { Name: "payment-epay", @@ -117,7 +117,7 @@ var officialPlugins = []MarketplacePlugin{ Description: "多渠道支付插件:易支付 / 支付宝官方 / 微信支付官方", Author: "AirGate", Type: "extension", - GithubRepo: "DouDOU-start/airgate-epay", + GithubRepo: "DevilGenius/airgate-epay", }, { Name: "airgate-health", @@ -125,7 +125,7 @@ var officialPlugins = []MarketplacePlugin{ Description: "AI 提供商健康监控:主动探测、可用率/延迟聚合、对外公开状态页", Author: "AirGate", Type: "extension", - GithubRepo: "DouDOU-start/airgate-health", + GithubRepo: "DevilGenius/airgate-health", }, { Name: "airgate-playground", @@ -133,7 +133,7 @@ var officialPlugins = []MarketplacePlugin{ Description: "AI 对话插件:网页聊天、多模型切换、会话管理", Author: "AirGate", Type: "extension", - GithubRepo: "DouDOU-start/airgate-playground", + GithubRepo: "DevilGenius/airgate-playground", }, { Name: "gateway-claude", @@ -141,7 +141,7 @@ var officialPlugins = []MarketplacePlugin{ Description: "Claude Messages API 网关插件:OAuth 授权、TLS 指纹、用量监控", Author: "AirGate", Type: "gateway", - GithubRepo: "DouDOU-start/airgate-claude", + GithubRepo: "DevilGenius/airgate-claude", }, { Name: "gateway-kiro", @@ -149,7 +149,7 @@ var officialPlugins = []MarketplacePlugin{ Description: "Kiro (AWS CodeWhisperer) 反代网关,兼容 Anthropic Messages API", Author: "AirGate", Type: "gateway", - GithubRepo: "DouDOU-start/airgate-kiro", + GithubRepo: "DevilGenius/airgate-kiro", }, { Name: "airgate-studio", @@ -157,7 +157,7 @@ var officialPlugins = []MarketplacePlugin{ Description: "面向图片、视频、音频等多模态内容生成的统一创作中心", Author: "AirGate", Type: "extension", - GithubRepo: "DouDOU-start/airgate-studio", + GithubRepo: "DevilGenius/airgate-studio", }, } diff --git a/backend/internal/plugin/marketplace_test.go b/backend/internal/plugin/marketplace_test.go index c2e081a2..9edd5cd9 100644 --- a/backend/internal/plugin/marketplace_test.go +++ b/backend/internal/plugin/marketplace_test.go @@ -4,13 +4,13 @@ import "testing" func TestOfficialPluginsIncludeCorePlugins(t *testing.T) { want := map[string]string{ - "gateway-openai": "DouDOU-start/airgate-openai", - "gateway-claude": "DouDOU-start/airgate-claude", - "gateway-kiro": "DouDOU-start/airgate-kiro", - "airgate-playground": "DouDOU-start/airgate-playground", - "airgate-studio": "DouDOU-start/airgate-studio", - "airgate-health": "DouDOU-start/airgate-health", - "payment-epay": "DouDOU-start/airgate-epay", + "gateway-openai": "DevilGenius/airgate-openai", + "gateway-claude": "DevilGenius/airgate-claude", + "gateway-kiro": "DevilGenius/airgate-kiro", + "airgate-playground": "DevilGenius/airgate-playground", + "airgate-studio": "DevilGenius/airgate-studio", + "airgate-health": "DevilGenius/airgate-health", + "payment-epay": "DevilGenius/airgate-epay", } got := make(map[string]MarketplacePlugin, len(officialPlugins)) diff --git a/backend/internal/plugin/middleware.go b/backend/internal/plugin/middleware.go index 79fb57a6..a37b7d3f 100644 --- a/backend/internal/plugin/middleware.go +++ b/backend/internal/plugin/middleware.go @@ -8,7 +8,7 @@ import ( "github.com/gin-gonic/gin" - sdk "github.com/DouDOU-start/airgate-sdk/sdkgo" + sdk "github.com/DevilGenius/airgate-sdk/sdkgo" ) // middleware.go:Core 与 middleware 类型插件的对接层。 diff --git a/backend/internal/plugin/outcome.go b/backend/internal/plugin/outcome.go index b819717a..5dcbfedb 100644 --- a/backend/internal/plugin/outcome.go +++ b/backend/internal/plugin/outcome.go @@ -11,10 +11,10 @@ import ( "github.com/gin-gonic/gin" - "github.com/DouDOU-start/airgate-core/ent/account" - "github.com/DouDOU-start/airgate-core/internal/billing" - "github.com/DouDOU-start/airgate-core/internal/scheduler" - sdk "github.com/DouDOU-start/airgate-sdk/sdkgo" + "github.com/DevilGenius/airgate-core/ent/account" + "github.com/DevilGenius/airgate-core/internal/billing" + "github.com/DevilGenius/airgate-core/internal/scheduler" + sdk "github.com/DevilGenius/airgate-sdk/sdkgo" ) // openAIError 以 OpenAI 兼容格式返回错误,保证 Claude Code 等客户端能识别。 @@ -69,6 +69,7 @@ func (f *Forwarder) writeResult(c *gin.Context, state *forwardState, execution f switch execution.outcome.Kind { case sdk.OutcomeSuccess: + f.bindResponseAffinity(ctx, state, execution) f.recordUsage(c, state, execution) if !state.stream { writeUpstream(c, execution.outcome.Upstream) @@ -91,6 +92,62 @@ func (f *Forwarder) writeResult(c *gin.Context, state *forwardState, execution f } } +const responseIDUsageMetadataKey = "openai.response_id" + +func (f *Forwarder) bindResponseAffinity(ctx context.Context, state *forwardState, execution forwardExecution) { + if f == nil || f.scheduler == nil || state == nil || state.account == nil || state.keyInfo == nil { + return + } + for _, responseID := range responseIDsFromOutcome(execution.outcome) { + f.scheduler.BindResponseAccount(ctx, state.keyInfo.GroupID, state.requestedPlatform, responseID, state.account.ID) + } +} + +func responseIDsFromOutcome(outcome sdk.ForwardOutcome) []string { + seen := map[string]struct{}{} + var ids []string + add := func(id string) { + id = strings.TrimSpace(id) + if id == "" || !strings.HasPrefix(id, "resp_") { + return + } + if _, ok := seen[id]; ok { + return + } + seen[id] = struct{}{} + ids = append(ids, id) + } + if outcome.Usage != nil && outcome.Usage.Metadata != nil { + add(outcome.Usage.Metadata[responseIDUsageMetadataKey]) + for _, id := range strings.Split(outcome.Usage.Metadata["openai.response_ids"], ",") { + add(id) + } + } + if len(outcome.Upstream.Body) > 0 { + for _, id := range responseIDsFromBody(outcome.Upstream.Body) { + add(id) + } + } + return ids +} + +func responseIDsFromBody(body []byte) []string { + var payload map[string]any + if err := json.Unmarshal(body, &payload); err != nil { + return nil + } + var ids []string + if id, ok := payload["id"].(string); ok { + ids = append(ids, id) + } + if response, ok := payload["response"].(map[string]any); ok { + if id, ok := response["id"].(string); ok { + ids = append(ids, id) + } + } + return ids +} + const ( defaultClientErrorMessage = "请求无法完成,请检查输入后重试" imageTooLargeMessage = "图片过大,请压缩后重试" @@ -192,6 +249,8 @@ func sanitizedMessage(kind sdk.OutcomeKind) string { return "上游账号当前被限流,请稍后重试" case sdk.OutcomeAccountDead: return "上游账号不可用,请联系管理员" + case sdk.OutcomeAccountUnavailable: + return "上游账号403暂不可用,请稍后重试" case sdk.OutcomeStreamAborted: return "响应流中断" case sdk.OutcomeUpstreamTransient: @@ -280,10 +339,12 @@ func (f *Forwarder) recordUsage(c *gin.Context, state *forwardState, execution f SellRate: state.keyInfo.SellRate, AccountRate: state.account.RateMultiplier, } - if override, ok := imageOutputBillingOverride(usage, state.keyInfo.GroupPluginSettings); ok { - calcInput.OutputBillingCostOverride = &override + if override, ok := imageBillingCostOverride(usage, state.keyInfo.GroupPluginSettings); ok { + calcInput.BillingCostOverride = &override } calc := f.calculator.Calculate(calcInput) + reasoningEffort := resolveReasoningEffort(state.reasoningEffort, usage) + usageMetadata := usageMetadataFromSDK(usage, usageValues) // 窗口费用沿用 account_cost(= total × account_rate),与用户账单解耦。 f.scheduler.AddWindowCost(ctx, state.account.ID, calc.AccountCost) @@ -300,14 +361,11 @@ func (f *Forwarder) recordUsage(c *gin.Context, state *forwardState, execution f OutputTokens: usageValues.OutputTokens, CachedInputTokens: usageValues.CachedInputTokens, CacheCreationTokens: usageValues.CacheCreationTokens, - CacheCreation5mTokens: usageValues.CacheCreation5mTokens, - CacheCreation1hTokens: usageValues.CacheCreation1hTokens, ReasoningOutputTokens: usageValues.ReasoningOutputTokens, InputPrice: usageValues.InputPrice, OutputPrice: usageValues.OutputPrice, CachedInputPrice: usageValues.CachedInputPrice, CacheCreationPrice: usageValues.CacheCreationPrice, - CacheCreation1hPrice: usageValues.CacheCreation1hPrice, InputCost: calc.InputCost, OutputCost: calc.OutputCost, CachedInputCost: calc.CachedInputCost, @@ -320,26 +378,20 @@ func (f *Forwarder) recordUsage(c *gin.Context, state *forwardState, execution f SellRate: calc.SellRate, AccountRateMultiplier: calc.AccountRateMultiplier, ServiceTier: usageValues.ServiceTier, - ImageSize: usageValues.ImageSize, Stream: state.stream, DurationMs: execution.duration.Milliseconds(), FirstTokenMs: usageValues.FirstTokenMs, UserAgent: c.Request.UserAgent(), IPAddress: c.ClientIP(), Endpoint: state.requestPath, - ReasoningEffort: resolveReasoningEffort(state.reasoningEffort, usage), - UsageAttributes: usage.Attributes, - UsageMetrics: usage.Metrics, - UsageCostDetails: usage.CostDetails, - UsageMetadata: usage.Metadata, + ReasoningEffort: reasoningEffort, + UsageMetadata: usageMetadata, }) } func resolveReasoningEffort(fromRequest string, usage *sdk.Usage) string { - if usage != nil && usage.Metadata != nil { - if effort := normalizeReasoningEffort(usage.Metadata["reasoning_effort"]); effort != "" { - return effort - } + if usage != nil && usage.ReasoningEffort != "" { + return normalizeReasoningEffort(usage.ReasoningEffort) } if fromRequest != "" { return fromRequest diff --git a/backend/internal/plugin/quota.go b/backend/internal/plugin/quota.go index e3e2d9cf..454657b4 100644 --- a/backend/internal/plugin/quota.go +++ b/backend/internal/plugin/quota.go @@ -10,8 +10,8 @@ import ( "github.com/gin-gonic/gin" "github.com/google/uuid" - "github.com/DouDOU-start/airgate-core/internal/scheduler" - sdk "github.com/DouDOU-start/airgate-sdk/sdkgo" + "github.com/DevilGenius/airgate-core/internal/scheduler" + sdk "github.com/DevilGenius/airgate-sdk/sdkgo" ) // checkBalance 执行请求进入业务逻辑前的最后一道准入:余额预检。 @@ -95,13 +95,17 @@ func (f *Forwarder) acquireClientQuota(c *gin.Context, state *forwardState) func func (f *Forwarder) pickAccount(c *gin.Context, state *forwardState, excludeIDs ...int) error { var lastErr error for _, model := range state.schedulingModelCandidates() { - account, err := f.scheduler.SelectAccount( + account, err := f.scheduler.SelectAccountWithOptions( c.Request.Context(), state.requestedPlatform, model, state.keyInfo.UserID, state.keyInfo.GroupID, state.sessionID, + scheduler.AccountSelectionOptions{ + PreviousResponseID: state.previousResponseID, + RequireContinuationAffinity: state.requireContinuationAffinity, + }, excludeIDs..., ) if err == nil { diff --git a/backend/internal/plugin/request.go b/backend/internal/plugin/request.go index 0afd9e8b..86c9966a 100644 --- a/backend/internal/plugin/request.go +++ b/backend/internal/plugin/request.go @@ -16,10 +16,10 @@ import ( "github.com/gin-gonic/gin" - "github.com/DouDOU-start/airgate-core/ent" - "github.com/DouDOU-start/airgate-core/internal/auth" - "github.com/DouDOU-start/airgate-core/internal/server/middleware" - sdk "github.com/DouDOU-start/airgate-sdk/sdkgo" + "github.com/DevilGenius/airgate-core/ent" + "github.com/DevilGenius/airgate-core/internal/auth" + "github.com/DevilGenius/airgate-core/internal/server/middleware" + sdk "github.com/DevilGenius/airgate-sdk/sdkgo" ) // parseRequest 从 HTTP 请求构造 forwardState。认证 / body 读取 / 插件匹配失败时 @@ -45,6 +45,8 @@ func (f *Forwarder) parseRequest(c *gin.Context) (*forwardState, bool) { path := requestPath(c) parsed := parseBody(body, c.GetHeader("Content-Type")) + parsed.PreviousResponseID = firstNonEmpty(parsed.PreviousResponseID, previousResponseIDFromHeaders(c.Request.Header)) + parsed.SessionID = resolveRequestSessionID(c.Request.Header, parsed) requestedPlatform := requestedPlatform(c, keyInfo) inst := f.matchPlugin(c, keyInfo, requestedPlatform, path) if inst == nil { @@ -57,19 +59,21 @@ func (f *Forwarder) parseRequest(c *gin.Context) (*forwardState, bool) { } return &forwardState{ - startedAt: startedAt, - requestPath: path, - body: body, - model: parsed.Model, - schedulingModels: schedulingModels, - schedulingModel: schedulingModel, - stream: parsed.Stream, - realtime: parsed.Stream, - sessionID: parsed.SessionID, - reasoningEffort: parsed.ReasoningEffort, - requestedPlatform: requestedPlatform, - keyInfo: keyInfo, - plugin: inst, + startedAt: startedAt, + requestPath: path, + body: body, + model: parsed.Model, + schedulingModels: schedulingModels, + schedulingModel: schedulingModel, + stream: parsed.Stream, + realtime: parsed.Stream, + sessionID: parsed.SessionID, + previousResponseID: parsed.PreviousResponseID, + requireContinuationAffinity: requestRequiresContinuationAffinity(parsed), + reasoningEffort: parsed.ReasoningEffort, + requestedPlatform: requestedPlatform, + keyInfo: keyInfo, + plugin: inst, }, true } @@ -115,11 +119,16 @@ func parseBody(body []byte, contentType string) parsedRequest { var fields requestFields if json.Unmarshal(body, &fields) == nil { effort := extractAndNormalizeReasoningEffort(fields) + signals := analyzeContinuationSignals(fields) return parsedRequest{ - Model: fields.Model, - Stream: fields.Stream, - SessionID: fields.Metadata.UserID, - ReasoningEffort: effort, + Model: fields.Model, + Stream: fields.Stream, + SessionID: strings.TrimSpace(fields.Metadata.UserID), + PromptCacheKey: strings.TrimSpace(fields.PromptCacheKey), + PreviousResponseID: strings.TrimSpace(fields.PreviousResponseID), + HasToolOutput: signals.hasToolOutput, + HasToolCallContext: signals.hasToolCallContext, + ReasoningEffort: effort, } } if strings.HasPrefix(contentType, "multipart/") { @@ -128,6 +137,170 @@ func parseBody(body []byte, contentType string) parsedRequest { return parsedRequest{} } +func resolveRequestSessionID(headers http.Header, parsed parsedRequest) string { + if parsed.SessionID != "" { + return parsed.SessionID + } + if headers != nil { + if v := firstNonEmpty(headers.Get("session_id"), headers.Get("Session_ID")); v != "" { + return v + } + if v := firstNonEmpty(headers.Get("conversation_id"), headers.Get("Conversation_ID")); v != "" { + return "conversation:" + v + } + } + if parsed.PromptCacheKey != "" { + return "prompt_cache:" + parsed.PromptCacheKey + } + return "" +} + +func previousResponseIDFromHeaders(headers http.Header) string { + if headers == nil { + return "" + } + return firstNonEmpty( + headers.Get("x-openai-previous-response-id"), + headers.Get("OpenAI-Previous-Response-ID"), + headers.Get("previous_response_id"), + ) +} + +func firstNonEmpty(values ...string) string { + for _, value := range values { + if trimmed := strings.TrimSpace(value); trimmed != "" { + return trimmed + } + } + return "" +} + +func requestRequiresContinuationAffinity(parsed parsedRequest) bool { + return strings.TrimSpace(parsed.PreviousResponseID) != "" || + (parsed.HasToolOutput && !parsed.HasToolCallContext) +} + +type continuationSignals struct { + hasToolOutput bool + hasToolCallContext bool +} + +func analyzeContinuationSignals(fields requestFields) continuationSignals { + signals := continuationSignals{} + mergeSignals(&signals, analyzeResponsesInputSignals(fields.Input)) + mergeSignals(&signals, analyzeMessagesSignals(fields.Messages)) + return signals +} + +func mergeSignals(dst *continuationSignals, src continuationSignals) { + if dst == nil { + return + } + dst.hasToolOutput = dst.hasToolOutput || src.hasToolOutput + dst.hasToolCallContext = dst.hasToolCallContext || src.hasToolCallContext +} + +func analyzeResponsesInputSignals(raw json.RawMessage) continuationSignals { + var signals continuationSignals + if len(raw) == 0 { + return signals + } + var items []map[string]any + if err := json.Unmarshal(raw, &items); err != nil { + return signals + } + for _, item := range items { + itemType, _ := item["type"].(string) + switch { + case isToolOutputItemType(itemType): + signals.hasToolOutput = true + case isToolCallContextItemType(itemType): + if strings.TrimSpace(asString(item["call_id"])) != "" { + signals.hasToolCallContext = true + } + } + } + return signals +} + +func analyzeMessagesSignals(raw json.RawMessage) continuationSignals { + var signals continuationSignals + if len(raw) == 0 { + return signals + } + var messages []map[string]any + if err := json.Unmarshal(raw, &messages); err != nil { + return signals + } + for _, msg := range messages { + role := strings.ToLower(strings.TrimSpace(asString(msg["role"]))) + if role == "tool" { + signals.hasToolOutput = true + } + if role == "assistant" { + if _, ok := msg["tool_calls"]; ok { + signals.hasToolCallContext = true + } + if _, ok := msg["function_call"]; ok { + signals.hasToolCallContext = true + } + } + analyzeMessageContentSignals(msg["content"], &signals) + } + return signals +} + +func analyzeMessageContentSignals(content any, signals *continuationSignals) { + if signals == nil { + return + } + items, ok := content.([]any) + if !ok { + return + } + for _, item := range items { + itemMap, ok := item.(map[string]any) + if !ok { + continue + } + itemType := strings.TrimSpace(asString(itemMap["type"])) + switch itemType { + case "tool_result": + signals.hasToolOutput = true + case "tool_use": + signals.hasToolCallContext = true + } + } +} + +func isToolCallContextItemType(itemType string) bool { + switch strings.TrimSpace(itemType) { + case "tool_call", "function_call", "local_shell_call", "tool_search_call", "custom_tool_call", "mcp_tool_call": + return true + default: + return false + } +} + +func isToolOutputItemType(itemType string) bool { + switch strings.TrimSpace(itemType) { + case "function_call_output", "tool_search_output", "custom_tool_call_output", "mcp_tool_call_output": + return true + default: + return false + } +} + +func asString(value any) string { + if value == nil { + return "" + } + if s, ok := value.(string); ok { + return s + } + return fmt.Sprint(value) +} + // extractAndNormalizeReasoningEffort 提取并归一化推理强度档位。 func extractAndNormalizeReasoningEffort(fields requestFields) string { effort := fields.ReasoningEffort diff --git a/backend/internal/plugin/task_asset_cleanup.go b/backend/internal/plugin/task_asset_cleanup.go index 7d7d38e4..ef2e2fd9 100644 --- a/backend/internal/plugin/task_asset_cleanup.go +++ b/backend/internal/plugin/task_asset_cleanup.go @@ -6,7 +6,7 @@ import ( "sort" "strings" - "github.com/DouDOU-start/airgate-core/ent" + "github.com/DevilGenius/airgate-core/ent" ) const ( diff --git a/backend/internal/plugin/task_service.go b/backend/internal/plugin/task_service.go index 922dfb72..7ca06077 100644 --- a/backend/internal/plugin/task_service.go +++ b/backend/internal/plugin/task_service.go @@ -8,9 +8,9 @@ import ( "google.golang.org/grpc/codes" "google.golang.org/grpc/status" - "github.com/DouDOU-start/airgate-core/ent" - enttask "github.com/DouDOU-start/airgate-core/ent/task" - sdk "github.com/DouDOU-start/airgate-sdk/sdkgo" + "github.com/DevilGenius/airgate-core/ent" + enttask "github.com/DevilGenius/airgate-core/ent/task" + sdk "github.com/DevilGenius/airgate-sdk/sdkgo" ) // task_service.go 收敛 Core 内部任务状态机的持久化入口。 diff --git a/backend/internal/plugin/task_service_test.go b/backend/internal/plugin/task_service_test.go index c481edb8..87f147f1 100644 --- a/backend/internal/plugin/task_service_test.go +++ b/backend/internal/plugin/task_service_test.go @@ -6,7 +6,7 @@ import ( "google.golang.org/grpc/codes" "google.golang.org/grpc/status" - enttask "github.com/DouDOU-start/airgate-core/ent/task" + enttask "github.com/DevilGenius/airgate-core/ent/task" ) func TestValidateTaskTransition(t *testing.T) { diff --git a/backend/internal/plugin/types.go b/backend/internal/plugin/types.go index 766f8acb..01c9efc9 100644 --- a/backend/internal/plugin/types.go +++ b/backend/internal/plugin/types.go @@ -1,12 +1,13 @@ package plugin import ( + "encoding/json" "time" - "github.com/DouDOU-start/airgate-core/ent" - "github.com/DouDOU-start/airgate-core/internal/auth" - "github.com/DouDOU-start/airgate-core/internal/routing" - sdk "github.com/DouDOU-start/airgate-sdk/sdkgo" + "github.com/DevilGenius/airgate-core/ent" + "github.com/DevilGenius/airgate-core/internal/auth" + "github.com/DevilGenius/airgate-core/internal/routing" + sdk "github.com/DevilGenius/airgate-sdk/sdkgo" ) // forwardState 一次转发请求在 Core 内的上下文。 @@ -21,11 +22,13 @@ type forwardState struct { // schedulingModels 是调度层使用的模型候选。协议翻译入口里,客户端传入的 // model 可能不是上游真实模型,例如 OpenAI 插件的 /v1/messages 会把 // claude-* 映射到 GPT 模型后再调用上游。 - schedulingModels []string - schedulingModel string - stream bool - realtime bool - sessionID string + schedulingModels []string + schedulingModel string + stream bool + realtime bool + sessionID string + previousResponseID string + requireContinuationAffinity bool // 推理强度档位快照。 reasoningEffort string @@ -48,10 +51,14 @@ type forwardExecution struct { // parsedRequest 从 JSON body 提取的请求元信息。 type parsedRequest struct { - Model string - Stream bool - SessionID string - ReasoningEffort string // 推理强度档位 + Model string + Stream bool + SessionID string + PromptCacheKey string + PreviousResponseID string + HasToolOutput bool + HasToolCallContext bool + ReasoningEffort string // 推理强度档位 } // requestFields 一次性 Unmarshal 的 JSON 字段结构。 @@ -61,8 +68,12 @@ type requestFields struct { Metadata struct { UserID string `json:"user_id"` } `json:"metadata"` - ReasoningEffort string `json:"reasoning_effort"` - Reasoning *struct { + PromptCacheKey string `json:"prompt_cache_key"` + PreviousResponseID string `json:"previous_response_id"` + Input json.RawMessage `json:"input"` + Messages json.RawMessage `json:"messages"` + ReasoningEffort string `json:"reasoning_effort"` + Reasoning *struct { Effort string `json:"effort"` } `json:"reasoning"` OutputConfig *struct { diff --git a/backend/internal/plugin/usage_adapter.go b/backend/internal/plugin/usage_adapter.go index 37bb0c48..b6074643 100644 --- a/backend/internal/plugin/usage_adapter.go +++ b/backend/internal/plugin/usage_adapter.go @@ -4,7 +4,7 @@ import ( "strconv" "strings" - sdk "github.com/DouDOU-start/airgate-sdk/sdkgo" + sdk "github.com/DevilGenius/airgate-sdk/sdkgo" ) type usageSnapshot struct { @@ -12,15 +12,17 @@ type usageSnapshot struct { OutputTokens int CachedInputTokens int CacheCreationTokens int - CacheCreation5mTokens int - CacheCreation1hTokens int ReasoningOutputTokens int + TextInputTokens int + ImageInputTokens int + ImageCount int - InputPrice float64 - OutputPrice float64 - CachedInputPrice float64 - CacheCreationPrice float64 - CacheCreation1hPrice float64 + InputPrice float64 + OutputPrice float64 + CachedInputPrice float64 + CacheCreationPrice float64 + ImageUnitPrice float64 + ImageUnit string InputCost float64 OutputCost float64 @@ -36,124 +38,110 @@ func usageSnapshotFromSDK(usage *sdk.Usage) usageSnapshot { if usage == nil { return usageSnapshot{} } - snap := usageSnapshot{FirstTokenMs: usage.FirstTokenMs} - - for _, metric := range usage.Metrics { - key := normalizedUsageKey(metric.Key, metric.Kind, metric.Label) - switch key { - case "input_tokens", "input_token", "prompt_tokens", "prompt_token": - snap.InputTokens += int(metric.Value) - case "output_tokens", "output_token", "completion_tokens", "completion_token": - snap.OutputTokens += int(metric.Value) - case "cached_input_tokens", "cached_input_token", "cache_read_tokens", "cache_read_token": - snap.CachedInputTokens += int(metric.Value) - case "cache_creation_tokens", "cache_creation_token": - snap.CacheCreationTokens += int(metric.Value) - case "cache_creation_5m_tokens", "cache_creation_5m_token": - snap.CacheCreation5mTokens += int(metric.Value) - case "cache_creation_1h_tokens", "cache_creation_1h_token": - snap.CacheCreation1hTokens += int(metric.Value) - case "reasoning_output_tokens", "reasoning_tokens", "reasoning_token": - snap.ReasoningOutputTokens += int(metric.Value) - } + snap := usageSnapshot{ + InputTokens: usage.InputTokens, + OutputTokens: usage.OutputTokens, + CachedInputTokens: usage.CachedInputTokens, + CacheCreationTokens: usage.CacheCreationTokens, + ReasoningOutputTokens: usage.ReasoningOutputTokens, + InputPrice: usage.InputPrice, + OutputPrice: usage.OutputPrice, + CachedInputPrice: usage.CachedInputPrice, + CacheCreationPrice: usage.CacheCreationPrice, + InputCost: usage.InputCost, + OutputCost: usage.OutputCost, + CachedInputCost: usage.CachedInputCost, + CacheCreationCost: usage.CacheCreationCost, + FirstTokenMs: usage.FirstTokenMs, } - for _, detail := range usage.CostDetails { - key := normalizedUsageKey(detail.Key, "", detail.Label) - applyUsageCost(&snap, key, detail.AccountCost) - applyUsagePrice(&snap, key, detail.Metadata) + meta := usage.Metadata + snap.TextInputTokens = metadataInt(meta, "openai.image.input_text_tokens") + snap.ImageInputTokens = metadataInt(meta, "openai.image.input_image_tokens") + snap.ImageCount = metadataInt(meta, "openai.image.count") + snap.ImageUnitPrice = metadataFloat(meta, "openai.image.unit_price") + snap.ImageUnit = metadataText(meta, "openai.image.unit") + snap.ServiceTier = metadataText(meta, "service_tier", "tier") + snap.ImageSize = metadataText(meta, "openai.image.size") + + return snap +} + +func usageMetadataFromSDK(usage *sdk.Usage, snap usageSnapshot) map[string]string { + meta := map[string]string{} + if usage == nil { + return meta } - if snap.InputCost+snap.OutputCost+snap.CachedInputCost+snap.CacheCreationCost <= 0 { - accountCost := usage.AccountCost - if accountCost <= 0 { - for _, metric := range usage.Metrics { - accountCost += metric.AccountCost - } - for _, detail := range usage.CostDetails { - accountCost += detail.AccountCost - } - } - snap.InputCost = accountCost + + for key, value := range usage.Metadata { + putMetadata(meta, key, value) } + putMetadata(meta, "openai.image.size", snap.ImageSize) + putMetadataInt(meta, "openai.image.input_text_tokens", snap.TextInputTokens) + putMetadataInt(meta, "openai.image.input_image_tokens", snap.ImageInputTokens) + putMetadataInt(meta, "openai.image.count", snap.ImageCount) + putMetadataFloat(meta, "openai.image.unit_price", snap.ImageUnitPrice) + putMetadata(meta, "openai.image.unit", snap.ImageUnit) + return meta +} - for _, attr := range usage.Attributes { - key := normalizedUsageKey(attr.Key, attr.Kind, attr.Label) - switch key { - case "service_tier", "tier": - if snap.ServiceTier == "" { - snap.ServiceTier = attr.Value - } - case "image_size", "resolution", "size": - if snap.ImageSize == "" { - snap.ImageSize = attr.Value - } +func metadataText(meta map[string]string, keys ...string) string { + for _, key := range keys { + value := strings.TrimSpace(meta[key]) + if value != "" { + return value } } + return "" +} - if usage.Metadata != nil { - if snap.ServiceTier == "" { - snap.ServiceTier = usage.Metadata["service_tier"] +func metadataInt(meta map[string]string, keys ...string) int { + for _, key := range keys { + raw := strings.TrimSpace(meta[key]) + if raw == "" { + continue + } + if value, err := strconv.Atoi(raw); err == nil { + return value } - if snap.ImageSize == "" { - snap.ImageSize = usage.Metadata["image_size"] + if value, err := strconv.ParseFloat(raw, 64); err == nil { + return int(value) } } - - return snap + return 0 } -func applyUsageCost(snap *usageSnapshot, key string, cost float64) { - if snap == nil || cost <= 0 { - return - } - switch key { - case "input", "input_tokens", "input_token", "prompt_tokens", "prompt_token": - snap.InputCost += cost - case "output", "output_tokens", "output_token", "completion_tokens", "completion_token", - "image", "images", "image_generation", "image_tool": - snap.OutputCost += cost - case "cached_input", "cached_input_tokens", "cached_input_token", "cache_read_tokens", "cache_read_token": - snap.CachedInputCost += cost - case "cache_creation", "cache_creation_tokens", "cache_creation_token", - "cache_creation_5m", "cache_creation_5m_tokens", "cache_creation_5m_token", - "cache_creation_1h", "cache_creation_1h_tokens", "cache_creation_1h_token": - snap.CacheCreationCost += cost +func metadataFloat(meta map[string]string, keys ...string) float64 { + for _, key := range keys { + raw := strings.TrimSpace(meta[key]) + if raw == "" { + continue + } + if value, err := strconv.ParseFloat(raw, 64); err == nil { + return value + } } + return 0 } -func applyUsagePrice(snap *usageSnapshot, key string, metadata map[string]string) { - if snap == nil || len(metadata) == 0 { +func putMetadata(meta map[string]string, key, value string) { + value = strings.TrimSpace(value) + if value == "" { return } - price, err := strconv.ParseFloat(strings.TrimSpace(metadata["unit_price"]), 64) - if err != nil || price <= 0 { + meta[key] = value +} + +func putMetadataInt(meta map[string]string, key string, value int) { + if value <= 0 { return } - switch key { - case "input", "input_tokens", "input_token", "prompt_tokens", "prompt_token": - snap.InputPrice = price - case "output", "output_tokens", "output_token", "completion_tokens", "completion_token", - "image", "images", "image_generation", "image_tool": - snap.OutputPrice = price - case "cached_input", "cached_input_tokens", "cached_input_token", "cache_read_tokens", "cache_read_token": - snap.CachedInputPrice = price - case "cache_creation", "cache_creation_tokens", "cache_creation_token", - "cache_creation_5m", "cache_creation_5m_tokens", "cache_creation_5m_token": - snap.CacheCreationPrice = price - case "cache_creation_1h", "cache_creation_1h_tokens", "cache_creation_1h_token": - snap.CacheCreation1hPrice = price - } + meta[key] = strconv.Itoa(value) } -func normalizedUsageKey(parts ...string) string { - for _, part := range parts { - part = strings.TrimSpace(strings.ToLower(part)) - if part == "" { - continue - } - part = strings.ReplaceAll(part, "-", "_") - part = strings.ReplaceAll(part, " ", "_") - return part +func putMetadataFloat(meta map[string]string, key string, value float64) { + if value <= 0 { + return } - return "" + meta[key] = strconv.FormatFloat(value, 'f', -1, 64) } diff --git a/backend/internal/routing/selector.go b/backend/internal/routing/selector.go index 58bdb1da..4fb8e3b7 100644 --- a/backend/internal/routing/selector.go +++ b/backend/internal/routing/selector.go @@ -6,12 +6,12 @@ import ( "sort" "strings" - sdk "github.com/DouDOU-start/airgate-sdk/sdkgo" + sdk "github.com/DevilGenius/airgate-sdk/sdkgo" - "github.com/DouDOU-start/airgate-core/ent" - "github.com/DouDOU-start/airgate-core/ent/group" - "github.com/DouDOU-start/airgate-core/ent/user" - "github.com/DouDOU-start/airgate-core/internal/billing" + "github.com/DevilGenius/airgate-core/ent" + "github.com/DevilGenius/airgate-core/ent/group" + "github.com/DevilGenius/airgate-core/ent/user" + "github.com/DevilGenius/airgate-core/internal/billing" ) type Requirements struct { diff --git a/backend/internal/routing/selector_test.go b/backend/internal/routing/selector_test.go index ef432ef8..defd2761 100644 --- a/backend/internal/routing/selector_test.go +++ b/backend/internal/routing/selector_test.go @@ -6,8 +6,8 @@ import ( _ "github.com/mattn/go-sqlite3" - "github.com/DouDOU-start/airgate-core/ent/enttest" - "github.com/DouDOU-start/airgate-core/ent/migrate" + "github.com/DevilGenius/airgate-core/ent/enttest" + "github.com/DevilGenius/airgate-core/ent/migrate" ) func TestListEligibleGroups(t *testing.T) { diff --git a/backend/internal/scheduler/admin.go b/backend/internal/scheduler/admin.go index fdc9faf3..0e2e5191 100644 --- a/backend/internal/scheduler/admin.go +++ b/backend/internal/scheduler/admin.go @@ -4,7 +4,7 @@ import ( "context" "time" - "github.com/DouDOU-start/airgate-core/ent/account" + "github.com/DevilGenius/airgate-core/ent/account" ) // 管理员 / 配额巡检的状态写入口。这些调用不经过 Apply —— 它们是"外部已知事实" @@ -14,11 +14,20 @@ import ( func (s *Scheduler) ManualRecover(ctx context.Context, accountID int) error { dbCtx, cancel := context.WithTimeout(ctx, dbTimeout) defer cancel() - err := s.db.Account.UpdateOneID(accountID). + + upd := s.db.Account.UpdateOneID(accountID). SetState(account.StateActive). ClearStateUntil(). - SetErrorMsg(""). - Exec(dbCtx) + SetErrorMsg("") + if existing, getErr := s.db.Account.Get(dbCtx, accountID); getErr == nil { + if extraInt(existing.Extra, accountUnavailableCountExtraKey) > 0 { + extra := cloneExtra(existing.Extra) + delete(extra, accountUnavailableCountExtraKey) + upd = upd.SetExtra(extra) + } + } + + err := upd.Exec(dbCtx) if err == nil { s.routeCache.InvalidateAll() } diff --git a/backend/internal/scheduler/response_affinity.go b/backend/internal/scheduler/response_affinity.go new file mode 100644 index 00000000..4e7a3beb --- /dev/null +++ b/backend/internal/scheduler/response_affinity.go @@ -0,0 +1,150 @@ +package scheduler + +import ( + "context" + "fmt" + "strconv" + "strings" + "sync" + "time" + + "github.com/redis/go-redis/v9" +) + +const ( + responseAffinityTTL = time.Hour + responseAffinityMemoryMaxEntries = 65536 + responseAffinityCleanupInterval = time.Minute +) + +type responseAffinityBinding struct { + accountID int + expiresAt time.Time +} + +// ResponseAffinity 保存 OpenAI Responses response_id 到上游账号的绑定。 +// Redis 存在时用于跨进程粘连;内存缓存作为热缓存和单实例兜底。 +type ResponseAffinity struct { + rdb *redis.Client + ttl time.Duration + mu sync.RWMutex + items map[string]responseAffinityBinding + lastCleanupTime time.Time +} + +func NewResponseAffinity(rdb *redis.Client) *ResponseAffinity { + return &ResponseAffinity{ + rdb: rdb, + ttl: responseAffinityTTL, + items: make(map[string]responseAffinityBinding, 256), + lastCleanupTime: time.Now(), + } +} + +func responseAffinityKey(groupID int, platform, responseID string) string { + return fmt.Sprintf("response_affinity:%d:%s:%s", groupID, strings.TrimSpace(platform), strings.TrimSpace(responseID)) +} + +func (a *ResponseAffinity) Bind(ctx context.Context, groupID int, platform, responseID string, accountID int) { + if a == nil || groupID <= 0 || strings.TrimSpace(platform) == "" || strings.TrimSpace(responseID) == "" || accountID <= 0 { + return + } + key := responseAffinityKey(groupID, platform, responseID) + a.setMemory(key, accountID) + if a.rdb != nil { + a.rdb.Set(ctx, key, strconv.Itoa(accountID), a.ttl) + } +} + +func (a *ResponseAffinity) Get(ctx context.Context, groupID int, platform, responseID string) (int, bool) { + if a == nil || groupID <= 0 || strings.TrimSpace(platform) == "" || strings.TrimSpace(responseID) == "" { + return 0, false + } + key := responseAffinityKey(groupID, platform, responseID) + if accountID, ok := a.getMemory(key); ok { + return accountID, true + } + if a.rdb == nil { + return 0, false + } + val, err := a.rdb.Get(ctx, key).Result() + if err != nil { + return 0, false + } + accountID, err := strconv.Atoi(val) + if err != nil || accountID <= 0 { + return 0, false + } + a.setMemory(key, accountID) + return accountID, true +} + +func (a *ResponseAffinity) Refresh(ctx context.Context, groupID int, platform, responseID string, accountID int) { + a.Bind(ctx, groupID, platform, responseID, accountID) +} + +func (a *ResponseAffinity) getMemory(key string) (int, bool) { + now := time.Now() + a.mu.RLock() + binding, ok := a.items[key] + a.mu.RUnlock() + if !ok || now.After(binding.expiresAt) || binding.accountID <= 0 { + if ok { + a.mu.Lock() + delete(a.items, key) + a.mu.Unlock() + } + return 0, false + } + return binding.accountID, true +} + +func (a *ResponseAffinity) setMemory(key string, accountID int) { + if key == "" || accountID <= 0 { + return + } + now := time.Now() + a.cleanupMemory(now) + a.mu.Lock() + if len(a.items) >= responseAffinityMemoryMaxEntries { + deleteOneExpiredOrArbitraryResponseAffinity(a.items, now) + } + a.items[key] = responseAffinityBinding{ + accountID: accountID, + expiresAt: now.Add(a.ttl), + } + a.mu.Unlock() +} + +func (a *ResponseAffinity) cleanupMemory(now time.Time) { + a.mu.RLock() + shouldCleanup := now.Sub(a.lastCleanupTime) >= responseAffinityCleanupInterval + a.mu.RUnlock() + if !shouldCleanup { + return + } + a.mu.Lock() + defer a.mu.Unlock() + if now.Sub(a.lastCleanupTime) < responseAffinityCleanupInterval { + return + } + for key, binding := range a.items { + if now.After(binding.expiresAt) { + delete(a.items, key) + } + } + a.lastCleanupTime = now +} + +func deleteOneExpiredOrArbitraryResponseAffinity(items map[string]responseAffinityBinding, now time.Time) { + for key, binding := range items { + if now.After(binding.expiresAt) { + delete(items, key) + return + } + } + for key := range items { + delete(items, key) + return + } +} diff --git a/backend/internal/scheduler/response_affinity_test.go b/backend/internal/scheduler/response_affinity_test.go new file mode 100644 index 00000000..d9acbf4b --- /dev/null +++ b/backend/internal/scheduler/response_affinity_test.go @@ -0,0 +1,50 @@ +package scheduler + +import ( + "context" + "testing" + "time" +) + +func TestResponseAffinityMemoryFallback(t *testing.T) { + t.Parallel() + + store := NewResponseAffinity(nil) + store.Bind(context.Background(), 7, "openai", "resp_123", 42) + + accountID, ok := store.Get(context.Background(), 7, "openai", "resp_123") + if !ok { + t.Fatal("expected memory response affinity hit") + } + if accountID != 42 { + t.Fatalf("accountID = %d, want 42", accountID) + } +} + +func TestResponseAffinityExpiresMemoryEntries(t *testing.T) { + t.Parallel() + + store := NewResponseAffinity(nil) + store.ttl = time.Millisecond + store.Bind(context.Background(), 7, "openai", "resp_expired", 42) + time.Sleep(2 * time.Millisecond) + + if accountID, ok := store.Get(context.Background(), 7, "openai", "resp_expired"); ok { + t.Fatalf("expected expired miss, got accountID %d", accountID) + } +} + +func TestStickySessionMemoryFallback(t *testing.T) { + t.Parallel() + + sticky := NewStickySession(nil) + sticky.Set(context.Background(), 9, "openai", "session-1", 11) + + accountID, ok := sticky.Get(context.Background(), 9, "openai", "session-1") + if !ok { + t.Fatal("expected memory sticky hit") + } + if accountID != 11 { + t.Fatalf("accountID = %d, want 11", accountID) + } +} diff --git a/backend/internal/scheduler/routecache.go b/backend/internal/scheduler/routecache.go index 31af6040..9751ae9c 100644 --- a/backend/internal/scheduler/routecache.go +++ b/backend/internal/scheduler/routecache.go @@ -4,7 +4,7 @@ import ( "sync" "time" - "github.com/DouDOU-start/airgate-core/ent" + "github.com/DevilGenius/airgate-core/ent" ) // Route 结果缓存。 diff --git a/backend/internal/scheduler/routecache_test.go b/backend/internal/scheduler/routecache_test.go index 269b3f0c..3af32d83 100644 --- a/backend/internal/scheduler/routecache_test.go +++ b/backend/internal/scheduler/routecache_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - "github.com/DouDOU-start/airgate-core/ent" + "github.com/DevilGenius/airgate-core/ent" ) // TestRouteCache_HitMiss 基础命中 / 未命中行为。 diff --git a/backend/internal/scheduler/scheduler.go b/backend/internal/scheduler/scheduler.go index e219a56d..d5d80e24 100644 --- a/backend/internal/scheduler/scheduler.go +++ b/backend/internal/scheduler/scheduler.go @@ -9,12 +9,14 @@ import ( "github.com/redis/go-redis/v9" - "github.com/DouDOU-start/airgate-core/ent" + "github.com/DevilGenius/airgate-core/ent" ) var ( - ErrNoAvailableAccount = errors.New("无可用账户") - ErrGroupNotFound = errors.New("分组不存在") + ErrNoAvailableAccount = errors.New("无可用账户") + ErrGroupNotFound = errors.New("分组不存在") + ErrContinuationAffinityMissing = errors.New("续链请求无法定位原上游账号") + ErrContinuationCapacityExceeded = errors.New("续链请求原上游账号容量不足") ) // dbTimeout 后台 DB 操作超时,防止 goroutine 泄漏。 @@ -36,15 +38,16 @@ type Scheduler struct { db *ent.Client rdb *redis.Client - sticky *StickySession - windowCost *WindowCostChecker - rpm *RPMCounter - session *SessionManager - msgQueue *MessageQueue - state *StateMachine - familyCooldown *FamilyCooldown - routeCache *routeCache - accountFilters map[string]AccountFilterFunc + sticky *StickySession + windowCost *WindowCostChecker + rpm *RPMCounter + session *SessionManager + msgQueue *MessageQueue + state *StateMachine + familyCooldown *FamilyCooldown + routeCache *routeCache + responseAffinity *ResponseAffinity + accountFilters map[string]AccountFilterFunc } // SetAccountFilter 注册平台级账号过滤函数。 @@ -62,21 +65,30 @@ func NewScheduler(db *ent.Client, rdb *redis.Client) *Scheduler { rc := newRouteCache(routeCacheTTL) fc := NewFamilyCooldown(rdb) s := &Scheduler{ - db: db, - rdb: rdb, - sticky: NewStickySession(rdb), - windowCost: NewWindowCostChecker(db, rdb), - rpm: rpm, - session: NewSessionManager(rdb), - msgQueue: NewMessageQueue(rdb, rpm), - state: NewStateMachine(db, rdb, fc), - familyCooldown: fc, - routeCache: rc, + db: db, + rdb: rdb, + sticky: NewStickySession(rdb), + windowCost: NewWindowCostChecker(db, rdb), + rpm: rpm, + session: NewSessionManager(rdb), + msgQueue: NewMessageQueue(rdb, rpm), + state: NewStateMachine(db, rdb, fc), + familyCooldown: fc, + routeCache: rc, + responseAffinity: NewResponseAffinity(rdb), } s.state.onCriticalTransition = rc.InvalidateAll return s } +// BindResponseAccount 记录 Responses response_id 所在账号,用于后续 previous_response_id 续链路由。 +func (s *Scheduler) BindResponseAccount(ctx context.Context, groupID int, platform, responseID string, accountID int) { + if s == nil || s.responseAffinity == nil { + return + } + s.responseAffinity.Bind(ctx, groupID, platform, responseID, accountID) +} + // InvalidateRouteCache 清除指定分组的 route 缓存。admin 改分组 / 增删账号时调用。 // groupID <= 0 时清空所有缓存。 func (s *Scheduler) InvalidateRouteCache(groupID int) { diff --git a/backend/internal/scheduler/selection.go b/backend/internal/scheduler/selection.go index 2614f346..19e50711 100644 --- a/backend/internal/scheduler/selection.go +++ b/backend/internal/scheduler/selection.go @@ -9,21 +9,34 @@ import ( "path/filepath" "sort" "strconv" + "strings" "time" - sdk "github.com/DouDOU-start/airgate-sdk/sdkgo" + sdk "github.com/DevilGenius/airgate-sdk/sdkgo" - "github.com/DouDOU-start/airgate-core/ent" - "github.com/DouDOU-start/airgate-core/ent/account" + "github.com/DevilGenius/airgate-core/ent" + "github.com/DevilGenius/airgate-core/ent/account" ) // SelectAccount 选一个可用账户。流程: // // 模型路由 → 状态过滤 → 软约束过滤(RPM / window / session)→ -// 粘性会话 → 负载均衡。 +// 硬续链亲和 → 同优先级软粘连 → 负载均衡。 // // excludeIDs 为 failover 时已尝试过的账户。 func (s *Scheduler) SelectAccount(ctx context.Context, platform, model string, userID, groupID int, sessionID string, excludeIDs ...int) (*ent.Account, error) { + return s.SelectAccountWithOptions(ctx, platform, model, userID, groupID, sessionID, AccountSelectionOptions{}, excludeIDs...) +} + +type AccountSelectionOptions struct { + PreviousResponseID string + RequireContinuationAffinity bool +} + +// SelectAccountWithOptions 在常规调度前优先按 previous_response_id 命中原账号。 +// RequireContinuationAffinity=true 时,请求不是自包含重放,previous_response_id 或 session sticky +// 都是硬亲和,不能被 priority 覆盖。普通 session sticky 只在当前可用最高优先级层内生效。 +func (s *Scheduler) SelectAccountWithOptions(ctx context.Context, platform, model string, userID, groupID int, sessionID string, opts AccountSelectionOptions, excludeIDs ...int) (*ent.Account, error) { candidates, err := s.routeAccounts(ctx, platform, model, groupID) if err != nil { return nil, err @@ -52,17 +65,40 @@ func (s *Scheduler) SelectAccount(ctx context.Context, platform, model string, u } } - // 粘性会话优先(可命中 StickyOnly + Normal) + if previousResponseID := strings.TrimSpace(opts.PreviousResponseID); previousResponseID != "" && s.responseAffinity != nil { + if accountID, found := s.responseAffinity.Get(ctx, groupID, platform, previousResponseID); found { + if acc := findAccountByID(stickyCandidates, accountID); acc != nil { + s.responseAffinity.Refresh(ctx, groupID, platform, previousResponseID, accountID) + if sessionID != "" { + s.sticky.Set(ctx, userID, platform, sessionID, accountID) + } + return acc, nil + } + if opts.RequireContinuationAffinity { + return nil, continuationBlockedError(candidates, accountID) + } + } + } + + // 续链请求的 session sticky 是硬亲和;普通 session sticky 只是软粘连, + // 低优先级旧账号不能抢过当前可用最高优先级账号。 if sessionID != "" { if accountID, found := s.sticky.Get(ctx, userID, platform, sessionID); found { - for _, acc := range stickyCandidates { - if acc.ID == accountID { + if opts.RequireContinuationAffinity { + if acc := findAccountByID(stickyCandidates, accountID); acc != nil { s.sticky.Set(ctx, userID, platform, sessionID, accountID) return acc, nil } + return nil, continuationBlockedError(candidates, accountID) + } else if acc := selectSoftStickyAccount(softStickyCandidates(normalCandidates, stickyCandidates), accountID); acc != nil { + s.sticky.Set(ctx, userID, platform, sessionID, accountID) + return acc, nil } } } + if opts.RequireContinuationAffinity { + return nil, ErrContinuationAffinityMissing + } if len(normalCandidates) == 0 { // 没有 Normal 但可能有 StickyOnly 兜底(如 degraded 账号) @@ -88,6 +124,49 @@ func (s *Scheduler) SelectAccount(ctx context.Context, platform, model string, u return s.maybeRegisterSession(ctx, selected, userID, platform, sessionID, normalCandidates, now) } +func continuationBlockedError(candidates []*ent.Account, accountID int) error { + if findAccountByID(candidates, accountID) != nil { + return ErrContinuationCapacityExceeded + } + return ErrContinuationAffinityMissing +} + +func findAccountByID(candidates []*ent.Account, accountID int) *ent.Account { + if accountID <= 0 { + return nil + } + for _, acc := range candidates { + if acc != nil && acc.ID == accountID { + return acc + } + } + return nil +} + +func softStickyCandidates(normalCandidates, stickyCandidates []*ent.Account) []*ent.Account { + if len(normalCandidates) > 0 { + return normalCandidates + } + return stickyCandidates +} + +func selectSoftStickyAccount(candidates []*ent.Account, accountID int) *ent.Account { + acc := findAccountByID(candidates, accountID) + if acc == nil { + return nil + } + maxPriority := acc.Priority + for _, candidate := range candidates { + if candidate != nil && candidate.Priority > maxPriority { + maxPriority = candidate.Priority + } + } + if acc.Priority != maxPriority { + return nil + } + return acc +} + // excludeAccounts 过滤掉 excludeIDs 中的账号(failover 已尝试过的)。 func excludeAccounts(candidates []*ent.Account, excludeIDs []int) []*ent.Account { if len(excludeIDs) == 0 { diff --git a/backend/internal/scheduler/selection_test.go b/backend/internal/scheduler/selection_test.go index 254d0b46..e460ce4c 100644 --- a/backend/internal/scheduler/selection_test.go +++ b/backend/internal/scheduler/selection_test.go @@ -5,7 +5,7 @@ import ( "errors" "testing" - "github.com/DouDOU-start/airgate-core/ent" + "github.com/DevilGenius/airgate-core/ent" ) func TestExcludeAccountsDoesNotMutateCandidates(t *testing.T) { @@ -22,6 +22,53 @@ func TestExcludeAccountsDoesNotMutateCandidates(t *testing.T) { } } +func TestSelectSoftStickyAccountHonorsHighestPriority(t *testing.T) { + t.Parallel() + + candidates := []*ent.Account{ + {ID: 1, Priority: 10}, + {ID: 2, Priority: 20}, + {ID: 3, Priority: 20}, + } + + if got := selectSoftStickyAccount(candidates, 1); got != nil { + t.Fatalf("low priority sticky account selected: %+v", got) + } + if got := selectSoftStickyAccount(candidates, 2); got == nil || got.ID != 2 { + t.Fatalf("top priority sticky account = %+v, want account 2", got) + } +} + +func TestSoftStickyPrefersNormalPriorityPool(t *testing.T) { + t.Parallel() + + normalCandidates := []*ent.Account{{ID: 2, Priority: 20}} + stickyCandidates := []*ent.Account{ + {ID: 1, Priority: 30}, + {ID: 2, Priority: 20}, + } + + pool := softStickyCandidates(normalCandidates, stickyCandidates) + if got := selectSoftStickyAccount(pool, 1); got != nil { + t.Fatalf("sticky-only account selected while normal candidates exist: %+v", got) + } + if got := selectSoftStickyAccount(pool, 2); got == nil || got.ID != 2 { + t.Fatalf("normal top priority sticky account = %+v, want account 2", got) + } +} + +func TestContinuationBlockedErrorDistinguishesCapacityFromMissingAffinity(t *testing.T) { + t.Parallel() + + candidates := []*ent.Account{{ID: 1}} + if err := continuationBlockedError(candidates, 1); !errors.Is(err, ErrContinuationCapacityExceeded) { + t.Fatalf("continuationBlockedError(existing) = %v, want ErrContinuationCapacityExceeded", err) + } + if err := continuationBlockedError(candidates, 2); !errors.Is(err, ErrContinuationAffinityMissing) { + t.Fatalf("continuationBlockedError(missing) = %v, want ErrContinuationAffinityMissing", err) + } +} + func TestNormalizeGroupLookupErrorPreservesCancellation(t *testing.T) { t.Parallel() diff --git a/backend/internal/scheduler/state.go b/backend/internal/scheduler/state.go index 357c2c41..10274026 100644 --- a/backend/internal/scheduler/state.go +++ b/backend/internal/scheduler/state.go @@ -7,9 +7,9 @@ import ( "github.com/redis/go-redis/v9" - "github.com/DouDOU-start/airgate-core/ent" - "github.com/DouDOU-start/airgate-core/ent/account" - sdk "github.com/DouDOU-start/airgate-sdk/sdkgo" + "github.com/DevilGenius/airgate-core/ent" + "github.com/DevilGenius/airgate-core/ent/account" + sdk "github.com/DevilGenius/airgate-sdk/sdkgo" ) // 状态机使用的默认窗口。 @@ -26,6 +26,10 @@ const ( degradedDefault = 60 * time.Second // degradedMax 池账号最长降级窗口。 degradedMax = 10 * time.Minute + // accountUnavailableThreshold 账号短暂 403 连续达到该次数后升级为 disabled。 + accountUnavailableThreshold = 3 + + accountUnavailableCountExtraKey = "_airgate_account_unavailable_count" ) // Judgment forwarder 对一次调用的判决,交给状态机做状态转移。 @@ -78,6 +82,7 @@ func (sm *StateMachine) notifyCritical() { // Success → state=active,清 state_until,last_used_at=now // AccountRateLimited → state=rate_limited,state_until=now+RetryAfter // AccountDead → state=disabled(凭证失效,需人工介入) +// AccountUnavailable → 非池账号 state=degraded,累计 3 次后升级 disabled // UpstreamTransient → 非池:**不动状态**(上游抖动不扣账号分,靠 failover 切走就行);池:state=degraded // ClientError / StreamAborted / Unknown → 不改状态(账号无辜) func (sm *StateMachine) Apply(ctx context.Context, accountID int, j Judgment) { @@ -120,6 +125,9 @@ func (sm *StateMachine) Apply(ctx context.Context, accountID int, j Judgment) { } sm.transition(ctx, accountID, account.StateDisabled, nil, j.Reason) + case sdk.OutcomeAccountUnavailable: + sm.applyAccountUnavailable(ctx, accountID, j.Reason) + case sdk.OutcomeUpstreamTransient: // 按定义,UpstreamTransient 是"上游侧瞬时故障"(SSE 提前断流、网络抖动、上游 5xx 等), // 账号本身没问题——不动 state,让 failover 切到下一账号就够了。 @@ -145,6 +153,88 @@ func (sm *StateMachine) applyDegraded(ctx context.Context, accountID int, reason sm.transition(ctx, accountID, account.StateDegraded, &until, reason) } +func (sm *StateMachine) applyAccountUnavailable(ctx context.Context, accountID int, reason string) { + dbCtx, cancel := context.WithTimeout(context.Background(), dbTimeout) + defer cancel() + + existing, err := sm.db.Account.Get(dbCtx, accountID) + if err != nil { + slog.Warn("scheduler_account_unavailable_load_failed", + sdk.LogFieldAccountID, accountID, sdk.LogFieldError, err) + return + } + + if !shouldTrackAccountUnavailable(existing) { + slog.Warn("scheduler_account_unavailable_ignored", + sdk.LogFieldAccountID, accountID, + "account_type", existing.Type, + "is_pool", existing.UpstreamIsPool, + sdk.LogFieldReason, reason, + ) + return + } + + extra := cloneExtra(existing.Extra) + now := time.Now() + if existing.State == account.StateDegraded && existing.StateUntil != nil && existing.StateUntil.After(now) && extraInt(extra, accountUnavailableCountExtraKey) > 0 { + slog.Warn("scheduler_account_unavailable_degraded_skip_count", + sdk.LogFieldAccountID, accountID, + "count", extraInt(extra, accountUnavailableCountExtraKey), + "until", existing.StateUntil, + sdk.LogFieldReason, reason, + ) + return + } + + count := extraInt(extra, accountUnavailableCountExtraKey) + 1 + extra[accountUnavailableCountExtraKey] = count + + if count >= accountUnavailableThreshold { + delete(extra, accountUnavailableCountExtraKey) + err = sm.db.Account.UpdateOneID(accountID). + SetState(account.StateDisabled). + ClearStateUntil(). + SetErrorMsg(truncateReason(reason)). + SetExtra(extra). + Exec(dbCtx) + if err != nil { + slog.Error("scheduler_account_unavailable_disable_failed", + sdk.LogFieldAccountID, accountID, sdk.LogFieldError, err) + return + } + slog.Warn("scheduler_account_unavailable_escalated", + sdk.LogFieldAccountID, accountID, + "count", count, + sdk.LogFieldReason, reason, + ) + sm.notifyCritical() + return + } + + until := now.Add(degradedDefault) + err = sm.db.Account.UpdateOneID(accountID). + SetState(account.StateDegraded). + SetStateUntil(until). + SetErrorMsg(truncateReason(reason)). + SetExtra(extra). + Exec(dbCtx) + if err != nil { + slog.Error("scheduler_account_unavailable_degrade_failed", + sdk.LogFieldAccountID, accountID, sdk.LogFieldError, err) + return + } + slog.Warn("scheduler_account_unavailable_degraded", + sdk.LogFieldAccountID, accountID, + "count", count, + "until", until, + sdk.LogFieldReason, reason, + ) +} + +func shouldTrackAccountUnavailable(acc *ent.Account) bool { + return acc != nil && !acc.UpstreamIsPool +} + // transitionActive 成功时回到 active:清 state_until、清 reason、清失败计数、更新 last_used_at。 // // disabled 状态受保护:只有管理员操作(ManualRecover / ToggleScheduling)才能解除, @@ -155,7 +245,8 @@ func (sm *StateMachine) transitionActive(ctx context.Context, accountID int) { defer cancel() prevState := account.StateActive - if existing, err := sm.db.Account.Get(dbCtx, accountID); err == nil { + existing, getErr := sm.db.Account.Get(dbCtx, accountID) + if getErr == nil { prevState = existing.State } @@ -166,12 +257,20 @@ func (sm *StateMachine) transitionActive(ctx context.Context, accountID int) { return } - err := sm.db.Account.UpdateOneID(accountID). + upd := sm.db.Account.UpdateOneID(accountID). SetState(account.StateActive). ClearStateUntil(). SetErrorMsg(""). - SetLastUsedAt(now). - Exec(dbCtx) + SetLastUsedAt(now) + if getErr == nil { + if extraInt(existing.Extra, accountUnavailableCountExtraKey) > 0 { + extra := cloneExtra(existing.Extra) + delete(extra, accountUnavailableCountExtraKey) + upd = upd.SetExtra(extra) + } + } + + err := upd.Exec(dbCtx) if err != nil { slog.Warn("scheduler_state_active_failed", sdk.LogFieldAccountID, accountID, sdk.LogFieldError, err) @@ -252,3 +351,31 @@ func truncateReason(s string) string { } return s } + +func cloneExtra(input map[string]interface{}) map[string]interface{} { + if len(input) == 0 { + return map[string]interface{}{} + } + out := make(map[string]interface{}, len(input)) + for k, v := range input { + out[k] = v + } + return out +} + +func extraInt(extra map[string]interface{}, key string) int { + switch v := extra[key].(type) { + case int: + return v + case int32: + return int(v) + case int64: + return int(v) + case float64: + return int(v) + case float32: + return int(v) + default: + return 0 + } +} diff --git a/backend/internal/scheduler/state_test.go b/backend/internal/scheduler/state_test.go new file mode 100644 index 00000000..1af8423d --- /dev/null +++ b/backend/internal/scheduler/state_test.go @@ -0,0 +1,253 @@ +package scheduler + +import ( + "context" + "testing" + "time" + + _ "github.com/mattn/go-sqlite3" + + "github.com/DevilGenius/airgate-core/ent" + "github.com/DevilGenius/airgate-core/ent/account" + "github.com/DevilGenius/airgate-core/ent/enttest" + "github.com/DevilGenius/airgate-core/ent/migrate" + sdk "github.com/DevilGenius/airgate-sdk/sdkgo" +) + +func TestStateMachineAccountUnavailableEscalatesAfterThreshold(t *testing.T) { + ctx := context.Background() + db := openStateMachineTestDB(t, "scheduler_account_unavailable_threshold") + sm := NewStateMachine(db, nil, nil) + criticalTransitions := 0 + sm.onCriticalTransition = func() { criticalTransitions++ } + + acc := db.Account.Create(). + SetName("temporary 403"). + SetPlatform("openai"). + SetType("oauth"). + SetCredentials(map[string]string{}). + SaveX(ctx) + + sm.Apply(ctx, acc.ID, Judgment{Kind: sdk.OutcomeAccountUnavailable, Reason: "HTTP 403"}) + fresh := db.Account.GetX(ctx, acc.ID) + if fresh.State != account.StateDegraded { + t.Fatalf("state after first unavailable = %s, want degraded", fresh.State) + } + if fresh.StateUntil == nil { + t.Fatalf("state_until should be set after first unavailable") + } + if got := extraInt(fresh.Extra, accountUnavailableCountExtraKey); got != 1 { + t.Fatalf("unavailable count after first unavailable = %d, want 1", got) + } + + sm.Apply(ctx, acc.ID, Judgment{Kind: sdk.OutcomeAccountUnavailable, Reason: "HTTP 403"}) + fresh = db.Account.GetX(ctx, acc.ID) + if got := extraInt(fresh.Extra, accountUnavailableCountExtraKey); got != 1 { + t.Fatalf("unavailable count during degraded window = %d, want 1", got) + } + + expireAccountDegradedWindow(ctx, db, acc.ID) + + sm.Apply(ctx, acc.ID, Judgment{Kind: sdk.OutcomeAccountUnavailable, Reason: "HTTP 403"}) + fresh = db.Account.GetX(ctx, acc.ID) + if fresh.State != account.StateDegraded { + t.Fatalf("state after second unavailable = %s, want degraded", fresh.State) + } + if got := extraInt(fresh.Extra, accountUnavailableCountExtraKey); got != 2 { + t.Fatalf("unavailable count after second unavailable = %d, want 2", got) + } + + expireAccountDegradedWindow(ctx, db, acc.ID) + + sm.Apply(ctx, acc.ID, Judgment{Kind: sdk.OutcomeAccountUnavailable, Reason: "HTTP 403"}) + fresh = db.Account.GetX(ctx, acc.ID) + if fresh.State != account.StateDisabled { + t.Fatalf("state after third unavailable = %s, want disabled", fresh.State) + } + if fresh.StateUntil != nil { + t.Fatalf("state_until should be cleared after escalation") + } + if got := extraInt(fresh.Extra, accountUnavailableCountExtraKey); got != 0 { + t.Fatalf("unavailable count after escalation = %d, want cleared", got) + } + if criticalTransitions != 1 { + t.Fatalf("critical transitions = %d, want 1", criticalTransitions) + } +} + +func TestStateMachineSuccessClearsAccountUnavailableCount(t *testing.T) { + ctx := context.Background() + db := openStateMachineTestDB(t, "scheduler_account_unavailable_success") + sm := NewStateMachine(db, nil, nil) + + acc := db.Account.Create(). + SetName("temporary 403"). + SetPlatform("openai"). + SetType("oauth"). + SetCredentials(map[string]string{}). + SetExtra(map[string]interface{}{"keep": "value"}). + SaveX(ctx) + + sm.Apply(ctx, acc.ID, Judgment{Kind: sdk.OutcomeAccountUnavailable, Reason: "HTTP 403"}) + sm.Apply(ctx, acc.ID, Judgment{Kind: sdk.OutcomeSuccess}) + + fresh := db.Account.GetX(ctx, acc.ID) + if fresh.State != account.StateActive { + t.Fatalf("state after success = %s, want active", fresh.State) + } + if fresh.StateUntil != nil { + t.Fatalf("state_until should be cleared after success") + } + if fresh.ErrorMsg != "" { + t.Fatalf("error_msg after success = %q, want empty", fresh.ErrorMsg) + } + if got := extraInt(fresh.Extra, accountUnavailableCountExtraKey); got != 0 { + t.Fatalf("unavailable count after success = %d, want cleared", got) + } + if fresh.Extra["keep"] != "value" { + t.Fatalf("unrelated extra value was not preserved: %+v", fresh.Extra) + } +} + +func TestShouldTrackAccountUnavailableOnlyNonPool(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + acc *ent.Account + want bool + }{ + { + name: "oauth account", + acc: &ent.Account{Type: "oauth"}, + want: true, + }, + { + name: "oauth pool account", + acc: &ent.Account{Type: "oauth", UpstreamIsPool: true}, + want: false, + }, + { + name: "api key account", + acc: &ent.Account{Type: "apikey"}, + want: true, + }, + { + name: "api key pool account", + acc: &ent.Account{Type: "apikey", UpstreamIsPool: true}, + want: false, + }, + { + name: "nil account", + acc: nil, + want: false, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if got := shouldTrackAccountUnavailable(tt.acc); got != tt.want { + t.Fatalf("shouldTrackAccountUnavailable() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestStateMachineAccountUnavailableTracksNormalAPIKey(t *testing.T) { + ctx := context.Background() + db := openStateMachineTestDB(t, "scheduler_account_unavailable_api_key") + sm := NewStateMachine(db, nil, nil) + + acc := db.Account.Create(). + SetName("api key"). + SetPlatform("openai"). + SetType("apikey"). + SetCredentials(map[string]string{}). + SaveX(ctx) + + sm.Apply(ctx, acc.ID, Judgment{Kind: sdk.OutcomeAccountUnavailable, Reason: "HTTP 403"}) + + fresh := db.Account.GetX(ctx, acc.ID) + if fresh.State != account.StateDegraded { + t.Fatalf("state after api key unavailable = %s, want degraded", fresh.State) + } + if fresh.StateUntil == nil { + t.Fatalf("state_until should be set for normal api key account") + } + if got := extraInt(fresh.Extra, accountUnavailableCountExtraKey); got != 1 { + t.Fatalf("unavailable count for normal api key account = %d, want 1", got) + } +} + +func TestStateMachineAccountUnavailableIgnoredForAPIPool(t *testing.T) { + ctx := context.Background() + db := openStateMachineTestDB(t, "scheduler_account_unavailable_api_pool") + sm := NewStateMachine(db, nil, nil) + + acc := db.Account.Create(). + SetName("api pool"). + SetPlatform("openai"). + SetType("apikey"). + SetUpstreamIsPool(true). + SetCredentials(map[string]string{}). + SaveX(ctx) + + sm.Apply(ctx, acc.ID, Judgment{Kind: sdk.OutcomeAccountUnavailable, Reason: "HTTP 403"}) + + fresh := db.Account.GetX(ctx, acc.ID) + if fresh.State != account.StateActive { + t.Fatalf("state after api pool unavailable = %s, want active", fresh.State) + } + if fresh.StateUntil != nil { + t.Fatalf("state_until should remain empty for api pool account") + } + if got := extraInt(fresh.Extra, accountUnavailableCountExtraKey); got != 0 { + t.Fatalf("unavailable count for api pool account = %d, want 0", got) + } +} + +func TestStateMachineAccountUnavailableIgnoredForOAuthPool(t *testing.T) { + ctx := context.Background() + db := openStateMachineTestDB(t, "scheduler_account_unavailable_oauth_pool") + sm := NewStateMachine(db, nil, nil) + + acc := db.Account.Create(). + SetName("oauth pool"). + SetPlatform("openai"). + SetType("oauth"). + SetUpstreamIsPool(true). + SetCredentials(map[string]string{}). + SaveX(ctx) + + sm.Apply(ctx, acc.ID, Judgment{Kind: sdk.OutcomeAccountUnavailable, Reason: "HTTP 403"}) + + fresh := db.Account.GetX(ctx, acc.ID) + if fresh.State != account.StateActive { + t.Fatalf("state after oauth pool unavailable = %s, want active", fresh.State) + } + if fresh.StateUntil != nil { + t.Fatalf("state_until should remain empty for oauth pool account") + } + if got := extraInt(fresh.Extra, accountUnavailableCountExtraKey); got != 0 { + t.Fatalf("unavailable count for oauth pool account = %d, want 0", got) + } +} + +func openStateMachineTestDB(t *testing.T, name string) *ent.Client { + t.Helper() + db := enttest.Open(t, "sqlite3", "file:"+name+"?mode=memory&cache=shared&_fk=1", enttest.WithMigrateOptions(migrate.WithGlobalUniqueID(false))) + t.Cleanup(func() { + if err := db.Close(); err != nil { + t.Fatalf("close db: %v", err) + } + }) + return db +} + +func expireAccountDegradedWindow(ctx context.Context, db *ent.Client, accountID int) { + db.Account.UpdateOneID(accountID). + SetStateUntil(time.Now().Add(-time.Second)). + ExecX(ctx) +} diff --git a/backend/internal/scheduler/sticky.go b/backend/internal/scheduler/sticky.go index b92d2e38..d6235375 100644 --- a/backend/internal/scheduler/sticky.go +++ b/backend/internal/scheduler/sticky.go @@ -4,28 +4,42 @@ import ( "context" "fmt" "strconv" + "sync" "time" "github.com/redis/go-redis/v9" ) const ( - // stickyTTL 粘性会话默认过期时间 - stickyTTL = 30 * time.Minute + // stickyTTL 粘性会话默认过期时间,和 response_id 亲和缓存保持一致。 + stickyTTL = time.Hour + stickyMemoryMaxEntries = 65536 + stickyCleanupInterval = time.Minute ) +type stickyBinding struct { + accountID int + expiresAt time.Time +} + // StickySession 粘性会话管理 -// 通过 Redis 缓存 session → account 映射,实现对话上下文连续性 +// 通过 Redis + 进程内缓存保存 session → account 映射,实现对话上下文连续性。 +// Redis 不可用或未配置时,进程内缓存仍可提供单实例粘连。 type StickySession struct { - rdb *redis.Client - ttl time.Duration + rdb *redis.Client + ttl time.Duration + mu sync.RWMutex + items map[string]stickyBinding + lastCleanupTime time.Time } // NewStickySession 创建粘性会话管理器 func NewStickySession(rdb *redis.Client) *StickySession { return &StickySession{ - rdb: rdb, - ttl: stickyTTL, + rdb: rdb, + ttl: stickyTTL, + items: make(map[string]stickyBinding, 256), + lastCleanupTime: time.Now(), } } @@ -37,11 +51,17 @@ func stickyKey(userID int, platform, sessionID string) string { // Get 获取粘性会话绑定的账户 ID func (s *StickySession) Get(ctx context.Context, userID int, platform, sessionID string) (accountID int, found bool) { - if s.rdb == nil { + if s == nil || sessionID == "" { return 0, false } - key := stickyKey(userID, platform, sessionID) + if accountID, found := s.getMemory(key); found { + return accountID, true + } + + if s.rdb == nil { + return 0, false + } val, err := s.rdb.Get(ctx, key).Result() if err != nil { return 0, false @@ -51,15 +71,86 @@ func (s *StickySession) Get(ctx context.Context, userID int, platform, sessionID if err != nil { return 0, false } + s.setMemory(key, id) return id, true } // Set 设置粘性会话绑定(同时续期 TTL) func (s *StickySession) Set(ctx context.Context, userID int, platform, sessionID string, accountID int) { - if s.rdb == nil { + if s == nil || sessionID == "" || accountID <= 0 { return } - key := stickyKey(userID, platform, sessionID) - s.rdb.Set(ctx, key, strconv.Itoa(accountID), s.ttl) + s.setMemory(key, accountID) + if s.rdb != nil { + s.rdb.Set(ctx, key, strconv.Itoa(accountID), s.ttl) + } +} + +func (s *StickySession) getMemory(key string) (int, bool) { + if key == "" { + return 0, false + } + now := time.Now() + s.mu.RLock() + binding, ok := s.items[key] + s.mu.RUnlock() + if !ok || now.After(binding.expiresAt) || binding.accountID <= 0 { + if ok { + s.mu.Lock() + delete(s.items, key) + s.mu.Unlock() + } + return 0, false + } + return binding.accountID, true +} + +func (s *StickySession) setMemory(key string, accountID int) { + if key == "" || accountID <= 0 { + return + } + s.cleanupMemory(time.Now()) + s.mu.Lock() + if len(s.items) >= stickyMemoryMaxEntries { + deleteOneExpiredOrArbitrarySticky(s.items, time.Now()) + } + s.items[key] = stickyBinding{ + accountID: accountID, + expiresAt: time.Now().Add(s.ttl), + } + s.mu.Unlock() +} + +func (s *StickySession) cleanupMemory(now time.Time) { + s.mu.RLock() + shouldCleanup := now.Sub(s.lastCleanupTime) >= stickyCleanupInterval + s.mu.RUnlock() + if !shouldCleanup { + return + } + s.mu.Lock() + defer s.mu.Unlock() + if now.Sub(s.lastCleanupTime) < stickyCleanupInterval { + return + } + for key, binding := range s.items { + if now.After(binding.expiresAt) { + delete(s.items, key) + } + } + s.lastCleanupTime = now +} + +func deleteOneExpiredOrArbitrarySticky(items map[string]stickyBinding, now time.Time) { + for key, binding := range items { + if now.After(binding.expiresAt) { + delete(items, key) + return + } + } + for key := range items { + delete(items, key) + return + } } diff --git a/backend/internal/scheduler/windowcost.go b/backend/internal/scheduler/windowcost.go index 91faca88..af15c866 100644 --- a/backend/internal/scheduler/windowcost.go +++ b/backend/internal/scheduler/windowcost.go @@ -9,9 +9,9 @@ import ( "github.com/redis/go-redis/v9" - "github.com/DouDOU-start/airgate-core/ent" - "github.com/DouDOU-start/airgate-core/ent/account" - "github.com/DouDOU-start/airgate-core/ent/usagelog" + "github.com/DevilGenius/airgate-core/ent" + "github.com/DevilGenius/airgate-core/ent/account" + "github.com/DevilGenius/airgate-core/ent/usagelog" ) const ( diff --git a/backend/internal/server/cc_compat.go b/backend/internal/server/cc_compat.go index 9448253b..a433cbe0 100644 --- a/backend/internal/server/cc_compat.go +++ b/backend/internal/server/cc_compat.go @@ -7,8 +7,8 @@ import ( "github.com/gin-gonic/gin" - "github.com/DouDOU-start/airgate-core/ent/apikey" - "github.com/DouDOU-start/airgate-core/internal/auth" + "github.com/DevilGenius/airgate-core/ent/apikey" + "github.com/DevilGenius/airgate-core/internal/auth" ) // cc-switch 通用模板兼容端点 diff --git a/backend/internal/server/dto/plugin.go b/backend/internal/server/dto/plugin.go index dc3c44ed..aa30974f 100644 --- a/backend/internal/server/dto/plugin.go +++ b/backend/internal/server/dto/plugin.go @@ -50,7 +50,8 @@ type FrontendPageResp struct { // InstallGithubReq 从 GitHub 安装插件请求 type InstallGithubReq struct { - Repo string `json:"repo" binding:"required"` // owner/repo 或完整 GitHub URL + Repo string `json:"repo" binding:"required"` // owner/repo 或完整 GitHub URL + Version string `json:"version,omitempty"` // 可选 release tag,留空安装 latest } // PluginOAuthStartResp 插件 OAuth 授权开始响应 diff --git a/backend/internal/server/dto/usage.go b/backend/internal/server/dto/usage.go index a6c5b38a..53792e69 100644 --- a/backend/internal/server/dto/usage.go +++ b/backend/internal/server/dto/usage.go @@ -1,60 +1,51 @@ package dto -import sdk "github.com/DouDOU-start/airgate-sdk/sdkgo" - // UsageLogResp 使用记录响应(reseller / admin scope,包含完整的成本字段) type UsageLogResp struct { - ID int64 `json:"id"` - UserID int64 `json:"user_id"` - UserEmail string `json:"user_email,omitempty"` - UserDeleted bool `json:"user_deleted,omitempty"` - APIKeyID int64 `json:"api_key_id"` - APIKeyName string `json:"api_key_name,omitempty"` - APIKeyHint string `json:"api_key_hint,omitempty"` - APIKeyDeleted bool `json:"api_key_deleted"` - AccountID int64 `json:"account_id"` - AccountName string `json:"account_name,omitempty"` - AccountEmail string `json:"account_email,omitempty"` - GroupID int64 `json:"group_id"` - Platform string `json:"platform"` - Model string `json:"model"` - InputTokens int `json:"input_tokens"` - OutputTokens int `json:"output_tokens"` - CachedInputTokens int `json:"cached_input_tokens"` - CacheCreationTokens int `json:"cache_creation_tokens"` - CacheCreation5mTokens int `json:"cache_creation_5m_tokens"` - CacheCreation1hTokens int `json:"cache_creation_1h_tokens"` - ReasoningOutputTokens int `json:"reasoning_output_tokens"` - InputPrice float64 `json:"input_price"` - OutputPrice float64 `json:"output_price"` - CachedInputPrice float64 `json:"cached_input_price"` - CacheCreationPrice float64 `json:"cache_creation_price"` - CacheCreation1hPrice float64 `json:"cache_creation_1h_price"` - InputCost float64 `json:"input_cost"` - OutputCost float64 `json:"output_cost"` - CachedInputCost float64 `json:"cached_input_cost"` - CacheCreationCost float64 `json:"cache_creation_cost"` - TotalCost float64 `json:"total_cost"` - ActualCost float64 `json:"actual_cost"` // 平台真实成本/用户扣费 - BilledCost float64 `json:"billed_cost"` // 客户账面消耗(reseller markup 后的金额) - AccountCost float64 `json:"account_cost"` // 账号实际成本 = total × account_rate - RateMultiplier float64 `json:"rate_multiplier"` // 平台计费倍率快照 - SellRate float64 `json:"sell_rate"` // 销售倍率快照 - AccountRateMultiplier float64 `json:"account_rate_multiplier"` // 账号倍率快照 - ServiceTier string `json:"service_tier,omitempty"` - ImageSize string `json:"image_size,omitempty"` // 图像生成实际出图尺寸("WxH"),非图像请求空 - Stream bool `json:"stream"` - DurationMs int64 `json:"duration_ms"` - FirstTokenMs int64 `json:"first_token_ms"` - UserAgent string `json:"user_agent,omitempty"` - IPAddress string `json:"ip_address,omitempty"` - Endpoint string `json:"endpoint,omitempty"` - ReasoningEffort string `json:"reasoning_effort,omitempty"` // 推理强度档位 - UsageAttributes []sdk.UsageAttribute `json:"usage_attributes,omitempty"` - UsageMetrics []sdk.UsageMetric `json:"usage_metrics,omitempty"` - UsageCostDetails []sdk.UsageCostDetail `json:"usage_cost_details,omitempty"` - UsageMetadata map[string]string `json:"usage_metadata,omitempty"` - CreatedAt string `json:"created_at"` + ID int64 `json:"id"` + UserID int64 `json:"user_id"` + UserEmail string `json:"user_email,omitempty"` + UserDeleted bool `json:"user_deleted,omitempty"` + APIKeyID int64 `json:"api_key_id"` + APIKeyName string `json:"api_key_name,omitempty"` + APIKeyHint string `json:"api_key_hint,omitempty"` + APIKeyDeleted bool `json:"api_key_deleted"` + AccountID int64 `json:"account_id"` + AccountName string `json:"account_name,omitempty"` + AccountEmail string `json:"account_email,omitempty"` + GroupID int64 `json:"group_id"` + Platform string `json:"platform"` + Model string `json:"model"` + InputTokens int `json:"input_tokens"` + OutputTokens int `json:"output_tokens"` + CachedInputTokens int `json:"cached_input_tokens"` + CacheCreationTokens int `json:"cache_creation_tokens"` + ReasoningOutputTokens int `json:"reasoning_output_tokens"` + InputPrice float64 `json:"input_price"` + OutputPrice float64 `json:"output_price"` + CachedInputPrice float64 `json:"cached_input_price"` + CacheCreationPrice float64 `json:"cache_creation_price"` + InputCost float64 `json:"input_cost"` + OutputCost float64 `json:"output_cost"` + CachedInputCost float64 `json:"cached_input_cost"` + CacheCreationCost float64 `json:"cache_creation_cost"` + TotalCost float64 `json:"total_cost"` + ActualCost float64 `json:"actual_cost"` // 平台真实成本/用户扣费 + BilledCost float64 `json:"billed_cost"` // 客户账面消耗(reseller markup 后的金额) + AccountCost float64 `json:"account_cost"` // 账号实际成本 = total × account_rate + RateMultiplier float64 `json:"rate_multiplier"` // 平台计费倍率快照 + SellRate float64 `json:"sell_rate"` // 销售倍率快照 + AccountRateMultiplier float64 `json:"account_rate_multiplier"` // 账号倍率快照 + ServiceTier string `json:"service_tier,omitempty"` + Stream bool `json:"stream"` + DurationMs int64 `json:"duration_ms"` + FirstTokenMs int64 `json:"first_token_ms"` + UserAgent string `json:"user_agent,omitempty"` + IPAddress string `json:"ip_address,omitempty"` + Endpoint string `json:"endpoint,omitempty"` + ReasoningEffort string `json:"reasoning_effort,omitempty"` // 推理强度档位 + UsageMetadata map[string]string `json:"usage_metadata,omitempty"` + CreatedAt string `json:"created_at"` } // CustomerUsageLogResp 使用记录响应(end customer scope,剥离所有平台真实成本字段) @@ -62,34 +53,30 @@ type UsageLogResp struct { // 当请求来自 end customer(通过 API key 登录拿到的 scoped JWT)时返回此结构, // 不暴露 actual_cost / total_cost / 单价 / rate_multiplier 等会泄漏 reseller 毛利的字段。 type CustomerUsageLogResp struct { - ID int64 `json:"id"` - APIKeyID int64 `json:"api_key_id"` - Platform string `json:"platform"` - Model string `json:"model"` - InputTokens int `json:"input_tokens"` - OutputTokens int `json:"output_tokens"` - CachedInputTokens int `json:"cached_input_tokens"` - CacheCreationTokens int `json:"cache_creation_tokens"` - CacheCreation5mTokens int `json:"cache_creation_5m_tokens"` - CacheCreation1hTokens int `json:"cache_creation_1h_tokens"` - ReasoningOutputTokens int `json:"reasoning_output_tokens"` - BilledCost float64 `json:"cost"` // 客户视角:"本次消耗 = X 美元" - ServiceTier string `json:"service_tier,omitempty"` - ImageSize string `json:"image_size,omitempty"` // 图像生成实际出图尺寸("WxH"),非图像请求空 - Stream bool `json:"stream"` - DurationMs int64 `json:"duration_ms"` - FirstTokenMs int64 `json:"first_token_ms"` - Endpoint string `json:"endpoint,omitempty"` - ReasoningEffort string `json:"reasoning_effort,omitempty"` // 推理强度档位 - UsageAttributes []sdk.UsageAttribute `json:"usage_attributes,omitempty"` - UsageMetrics []sdk.UsageMetric `json:"usage_metrics,omitempty"` - UsageMetadata map[string]string `json:"usage_metadata,omitempty"` - CreatedAt string `json:"created_at"` + ID int64 `json:"id"` + APIKeyID int64 `json:"api_key_id"` + Platform string `json:"platform"` + Model string `json:"model"` + InputTokens int `json:"input_tokens"` + OutputTokens int `json:"output_tokens"` + CachedInputTokens int `json:"cached_input_tokens"` + CacheCreationTokens int `json:"cache_creation_tokens"` + ReasoningOutputTokens int `json:"reasoning_output_tokens"` + BilledCost float64 `json:"cost"` // 客户视角:"本次消耗 = X 美元" + ServiceTier string `json:"service_tier,omitempty"` + Stream bool `json:"stream"` + DurationMs int64 `json:"duration_ms"` + FirstTokenMs int64 `json:"first_token_ms"` + Endpoint string `json:"endpoint,omitempty"` + ReasoningEffort string `json:"reasoning_effort,omitempty"` // 推理强度档位 + UsageMetadata map[string]string `json:"usage_metadata,omitempty"` + CreatedAt string `json:"created_at"` } // UsageQuery 使用记录查询参数 type UsageQuery struct { PageReq + BeforeID *int64 `form:"before_id"` UserID *int64 `form:"user_id"` APIKeyID *int64 `form:"api_key_id"` AccountID *int64 `form:"account_id"` diff --git a/backend/internal/server/dynamic_router.go b/backend/internal/server/dynamic_router.go index ab19d378..1dbbf4bc 100644 --- a/backend/internal/server/dynamic_router.go +++ b/backend/internal/server/dynamic_router.go @@ -6,7 +6,7 @@ import ( "github.com/gin-gonic/gin" - "github.com/DouDOU-start/airgate-core/internal/plugin" + "github.com/DevilGenius/airgate-core/internal/plugin" ) // DynamicRouter 动态路由表 diff --git a/backend/internal/server/handler/account_handler.go b/backend/internal/server/handler/account_handler.go index 1eef3c1f..d866067f 100644 --- a/backend/internal/server/handler/account_handler.go +++ b/backend/internal/server/handler/account_handler.go @@ -8,9 +8,9 @@ import ( "strings" "time" - appaccount "github.com/DouDOU-start/airgate-core/internal/app/account" - "github.com/DouDOU-start/airgate-core/internal/scheduler" - "github.com/DouDOU-start/airgate-core/internal/server/dto" + appaccount "github.com/DevilGenius/airgate-core/internal/app/account" + "github.com/DevilGenius/airgate-core/internal/scheduler" + "github.com/DevilGenius/airgate-core/internal/server/dto" ) // AccountHandler 上游账号管理 Handler。 diff --git a/backend/internal/server/handler/account_handler_mapper.go b/backend/internal/server/handler/account_handler_mapper.go index 33505733..ffd26653 100644 --- a/backend/internal/server/handler/account_handler_mapper.go +++ b/backend/internal/server/handler/account_handler_mapper.go @@ -1,8 +1,8 @@ package handler import ( - appaccount "github.com/DouDOU-start/airgate-core/internal/app/account" - "github.com/DouDOU-start/airgate-core/internal/server/dto" + appaccount "github.com/DevilGenius/airgate-core/internal/app/account" + "github.com/DevilGenius/airgate-core/internal/server/dto" ) func toAccountResp(account appaccount.Account) dto.AccountResp { diff --git a/backend/internal/server/handler/account_handler_mapper_test.go b/backend/internal/server/handler/account_handler_mapper_test.go index bac3edfc..5bd34b6d 100644 --- a/backend/internal/server/handler/account_handler_mapper_test.go +++ b/backend/internal/server/handler/account_handler_mapper_test.go @@ -6,7 +6,7 @@ import ( "testing" "time" - appaccount "github.com/DouDOU-start/airgate-core/internal/app/account" + appaccount "github.com/DevilGenius/airgate-core/internal/app/account" ) func TestToAccountExportItemOmitsEnvironmentScopedIDs(t *testing.T) { diff --git a/backend/internal/server/handler/account_handler_routes.go b/backend/internal/server/handler/account_handler_routes.go index 3c0de2dd..d52f3c1a 100644 --- a/backend/internal/server/handler/account_handler_routes.go +++ b/backend/internal/server/handler/account_handler_routes.go @@ -10,9 +10,9 @@ import ( "github.com/gin-gonic/gin" "github.com/gin-gonic/gin/binding" - appaccount "github.com/DouDOU-start/airgate-core/internal/app/account" - "github.com/DouDOU-start/airgate-core/internal/server/dto" - "github.com/DouDOU-start/airgate-core/internal/server/response" + appaccount "github.com/DevilGenius/airgate-core/internal/app/account" + "github.com/DevilGenius/airgate-core/internal/server/dto" + "github.com/DevilGenius/airgate-core/internal/server/response" ) // ListAccounts 查询账号列表。 diff --git a/backend/internal/server/handler/apikey_handler.go b/backend/internal/server/handler/apikey_handler.go index bce7d2c1..d8f1861d 100644 --- a/backend/internal/server/handler/apikey_handler.go +++ b/backend/internal/server/handler/apikey_handler.go @@ -5,7 +5,7 @@ import ( "log/slog" "strconv" - appapikey "github.com/DouDOU-start/airgate-core/internal/app/apikey" + appapikey "github.com/DevilGenius/airgate-core/internal/app/apikey" ) // APIKeyHandler API 密钥管理 Handler。 diff --git a/backend/internal/server/handler/apikey_handler_mapper.go b/backend/internal/server/handler/apikey_handler_mapper.go index a81f69af..51a030d8 100644 --- a/backend/internal/server/handler/apikey_handler_mapper.go +++ b/backend/internal/server/handler/apikey_handler_mapper.go @@ -1,8 +1,8 @@ package handler import ( - appapikey "github.com/DouDOU-start/airgate-core/internal/app/apikey" - "github.com/DouDOU-start/airgate-core/internal/server/dto" + appapikey "github.com/DevilGenius/airgate-core/internal/app/apikey" + "github.com/DevilGenius/airgate-core/internal/server/dto" ) func toAPIKeyResp(item appapikey.Key) dto.APIKeyResp { diff --git a/backend/internal/server/handler/apikey_handler_routes.go b/backend/internal/server/handler/apikey_handler_routes.go index 81c216d6..b914bdef 100644 --- a/backend/internal/server/handler/apikey_handler_routes.go +++ b/backend/internal/server/handler/apikey_handler_routes.go @@ -3,9 +3,9 @@ package handler import ( "github.com/gin-gonic/gin" - appapikey "github.com/DouDOU-start/airgate-core/internal/app/apikey" - "github.com/DouDOU-start/airgate-core/internal/server/dto" - "github.com/DouDOU-start/airgate-core/internal/server/response" + appapikey "github.com/DevilGenius/airgate-core/internal/app/apikey" + "github.com/DevilGenius/airgate-core/internal/server/dto" + "github.com/DevilGenius/airgate-core/internal/server/response" ) // ListKeys 查询当前用户的 API 密钥列表。 diff --git a/backend/internal/server/handler/auth_handler.go b/backend/internal/server/handler/auth_handler.go index af32c1d5..dd1b002a 100644 --- a/backend/internal/server/handler/auth_handler.go +++ b/backend/internal/server/handler/auth_handler.go @@ -5,12 +5,12 @@ import ( "errors" "log/slog" - "github.com/DouDOU-start/airgate-core/ent" - appauth "github.com/DouDOU-start/airgate-core/internal/app/auth" - appsettings "github.com/DouDOU-start/airgate-core/internal/app/settings" - appuser "github.com/DouDOU-start/airgate-core/internal/app/user" - "github.com/DouDOU-start/airgate-core/internal/auth" - "github.com/DouDOU-start/airgate-core/internal/infra/mailer" + "github.com/DevilGenius/airgate-core/ent" + appauth "github.com/DevilGenius/airgate-core/internal/app/auth" + appsettings "github.com/DevilGenius/airgate-core/internal/app/settings" + appuser "github.com/DevilGenius/airgate-core/internal/app/user" + "github.com/DevilGenius/airgate-core/internal/auth" + "github.com/DevilGenius/airgate-core/internal/infra/mailer" ) // AuthHandler 认证相关 Handler。 diff --git a/backend/internal/server/handler/auth_handler_mapper.go b/backend/internal/server/handler/auth_handler_mapper.go index dea6a742..5b01fafb 100644 --- a/backend/internal/server/handler/auth_handler_mapper.go +++ b/backend/internal/server/handler/auth_handler_mapper.go @@ -1,8 +1,8 @@ package handler import ( - appauth "github.com/DouDOU-start/airgate-core/internal/app/auth" - "github.com/DouDOU-start/airgate-core/internal/server/dto" + appauth "github.com/DevilGenius/airgate-core/internal/app/auth" + "github.com/DevilGenius/airgate-core/internal/server/dto" ) // userToResp 将认证域 User 转换为 DTO 响应。 diff --git a/backend/internal/server/handler/auth_handler_routes.go b/backend/internal/server/handler/auth_handler_routes.go index f1a06f2e..a577eae9 100644 --- a/backend/internal/server/handler/auth_handler_routes.go +++ b/backend/internal/server/handler/auth_handler_routes.go @@ -10,11 +10,11 @@ import ( "github.com/gin-gonic/gin" - appauth "github.com/DouDOU-start/airgate-core/internal/app/auth" - "github.com/DouDOU-start/airgate-core/internal/auth" - "github.com/DouDOU-start/airgate-core/internal/infra/mailer" - "github.com/DouDOU-start/airgate-core/internal/server/dto" - "github.com/DouDOU-start/airgate-core/internal/server/response" + appauth "github.com/DevilGenius/airgate-core/internal/app/auth" + "github.com/DevilGenius/airgate-core/internal/auth" + "github.com/DevilGenius/airgate-core/internal/infra/mailer" + "github.com/DevilGenius/airgate-core/internal/server/dto" + "github.com/DevilGenius/airgate-core/internal/server/response" ) // Login 用户登录。 diff --git a/backend/internal/server/handler/auth_handler_routes_test.go b/backend/internal/server/handler/auth_handler_routes_test.go index cc27ed96..80a9ba4f 100644 --- a/backend/internal/server/handler/auth_handler_routes_test.go +++ b/backend/internal/server/handler/auth_handler_routes_test.go @@ -8,7 +8,7 @@ import ( "github.com/gin-gonic/gin" - "github.com/DouDOU-start/airgate-core/internal/infra/mailer" + "github.com/DevilGenius/airgate-core/internal/infra/mailer" ) func TestVerifyCodeRejectsInvalidCode(t *testing.T) { diff --git a/backend/internal/server/handler/dashboard_handler.go b/backend/internal/server/handler/dashboard_handler.go index 30cc1717..2756b632 100644 --- a/backend/internal/server/handler/dashboard_handler.go +++ b/backend/internal/server/handler/dashboard_handler.go @@ -5,7 +5,7 @@ import ( "github.com/gin-gonic/gin" - appdashboard "github.com/DouDOU-start/airgate-core/internal/app/dashboard" + appdashboard "github.com/DevilGenius/airgate-core/internal/app/dashboard" ) // DashboardHandler 仪表盘 Handler。 diff --git a/backend/internal/server/handler/dashboard_handler_mapper.go b/backend/internal/server/handler/dashboard_handler_mapper.go index b7514e2e..96060973 100644 --- a/backend/internal/server/handler/dashboard_handler_mapper.go +++ b/backend/internal/server/handler/dashboard_handler_mapper.go @@ -1,8 +1,8 @@ package handler import ( - appdashboard "github.com/DouDOU-start/airgate-core/internal/app/dashboard" - "github.com/DouDOU-start/airgate-core/internal/server/dto" + appdashboard "github.com/DevilGenius/airgate-core/internal/app/dashboard" + "github.com/DevilGenius/airgate-core/internal/server/dto" ) func toDashboardStatsResp(item appdashboard.Stats) dto.DashboardStatsResp { diff --git a/backend/internal/server/handler/dashboard_handler_routes.go b/backend/internal/server/handler/dashboard_handler_routes.go index 17c861d5..12c91498 100644 --- a/backend/internal/server/handler/dashboard_handler_routes.go +++ b/backend/internal/server/handler/dashboard_handler_routes.go @@ -3,9 +3,9 @@ package handler import ( "github.com/gin-gonic/gin" - appdashboard "github.com/DouDOU-start/airgate-core/internal/app/dashboard" - "github.com/DouDOU-start/airgate-core/internal/server/dto" - "github.com/DouDOU-start/airgate-core/internal/server/response" + appdashboard "github.com/DevilGenius/airgate-core/internal/app/dashboard" + "github.com/DevilGenius/airgate-core/internal/server/dto" + "github.com/DevilGenius/airgate-core/internal/server/response" ) // Stats 返回仪表盘统计数据。 diff --git a/backend/internal/server/handler/group_handler.go b/backend/internal/server/handler/group_handler.go index 7ec135c5..1d7e1c09 100644 --- a/backend/internal/server/handler/group_handler.go +++ b/backend/internal/server/handler/group_handler.go @@ -5,7 +5,7 @@ import ( "log/slog" "strconv" - appgroup "github.com/DouDOU-start/airgate-core/internal/app/group" + appgroup "github.com/DevilGenius/airgate-core/internal/app/group" ) // GroupHandler 分组管理 Handler。 diff --git a/backend/internal/server/handler/group_handler_mapper.go b/backend/internal/server/handler/group_handler_mapper.go index bee04a37..65ad387d 100644 --- a/backend/internal/server/handler/group_handler_mapper.go +++ b/backend/internal/server/handler/group_handler_mapper.go @@ -1,8 +1,8 @@ package handler import ( - appgroup "github.com/DouDOU-start/airgate-core/internal/app/group" - "github.com/DouDOU-start/airgate-core/internal/server/dto" + appgroup "github.com/DevilGenius/airgate-core/internal/app/group" + "github.com/DevilGenius/airgate-core/internal/server/dto" ) func toGroupRespFromDomain(item appgroup.Group) dto.GroupResp { diff --git a/backend/internal/server/handler/group_handler_routes.go b/backend/internal/server/handler/group_handler_routes.go index 53996541..e8b100bb 100644 --- a/backend/internal/server/handler/group_handler_routes.go +++ b/backend/internal/server/handler/group_handler_routes.go @@ -3,9 +3,9 @@ package handler import ( "github.com/gin-gonic/gin" - appgroup "github.com/DouDOU-start/airgate-core/internal/app/group" - "github.com/DouDOU-start/airgate-core/internal/server/dto" - "github.com/DouDOU-start/airgate-core/internal/server/response" + appgroup "github.com/DevilGenius/airgate-core/internal/app/group" + "github.com/DevilGenius/airgate-core/internal/server/dto" + "github.com/DevilGenius/airgate-core/internal/server/response" ) // ListGroups 查询分组列表。 diff --git a/backend/internal/server/handler/mapper_conversion_test.go b/backend/internal/server/handler/mapper_conversion_test.go index 4b965744..a8be37f5 100644 --- a/backend/internal/server/handler/mapper_conversion_test.go +++ b/backend/internal/server/handler/mapper_conversion_test.go @@ -4,16 +4,16 @@ import ( "testing" "time" - appauth "github.com/DouDOU-start/airgate-core/internal/app/auth" - appdashboard "github.com/DouDOU-start/airgate-core/internal/app/dashboard" - appgroup "github.com/DouDOU-start/airgate-core/internal/app/group" - apppluginadmin "github.com/DouDOU-start/airgate-core/internal/app/pluginadmin" - appproxy "github.com/DouDOU-start/airgate-core/internal/app/proxy" - appsettings "github.com/DouDOU-start/airgate-core/internal/app/settings" - appsubscription "github.com/DouDOU-start/airgate-core/internal/app/subscription" - appusage "github.com/DouDOU-start/airgate-core/internal/app/usage" - appuser "github.com/DouDOU-start/airgate-core/internal/app/user" - sdk "github.com/DouDOU-start/airgate-sdk/sdkgo" + appauth "github.com/DevilGenius/airgate-core/internal/app/auth" + appdashboard "github.com/DevilGenius/airgate-core/internal/app/dashboard" + appgroup "github.com/DevilGenius/airgate-core/internal/app/group" + apppluginadmin "github.com/DevilGenius/airgate-core/internal/app/pluginadmin" + appproxy "github.com/DevilGenius/airgate-core/internal/app/proxy" + appsettings "github.com/DevilGenius/airgate-core/internal/app/settings" + appsubscription "github.com/DevilGenius/airgate-core/internal/app/subscription" + appusage "github.com/DevilGenius/airgate-core/internal/app/usage" + appuser "github.com/DevilGenius/airgate-core/internal/app/user" + sdk "github.com/DevilGenius/airgate-sdk/sdkgo" ) func TestUserToRespClonesAllowedGroupIDs(t *testing.T) { diff --git a/backend/internal/server/handler/openclaw_handler.go b/backend/internal/server/handler/openclaw_handler.go index 0641ea64..1cbcca70 100644 --- a/backend/internal/server/handler/openclaw_handler.go +++ b/backend/internal/server/handler/openclaw_handler.go @@ -1,7 +1,7 @@ package handler import ( - appopenclaw "github.com/DouDOU-start/airgate-core/internal/app/openclaw" + appopenclaw "github.com/DevilGenius/airgate-core/internal/app/openclaw" ) // OpenClawHandler 负责 /openclaw/* 一键接入相关的公共路由。 diff --git a/backend/internal/server/handler/openclaw_handler_routes.go b/backend/internal/server/handler/openclaw_handler_routes.go index ba835c44..3c52e0d2 100644 --- a/backend/internal/server/handler/openclaw_handler_routes.go +++ b/backend/internal/server/handler/openclaw_handler_routes.go @@ -9,8 +9,8 @@ import ( "github.com/gin-gonic/gin" - appopenclaw "github.com/DouDOU-start/airgate-core/internal/app/openclaw" - "github.com/DouDOU-start/airgate-core/internal/server/response" + appopenclaw "github.com/DevilGenius/airgate-core/internal/app/openclaw" + "github.com/DevilGenius/airgate-core/internal/server/response" ) // loadConfig 读取 openclaw 配置并在 BaseURL 为空时根据当前请求 Host 推导一个兜底值。 diff --git a/backend/internal/server/handler/plugin_handler.go b/backend/internal/server/handler/plugin_handler.go index 50d58cfd..a4ef5a88 100644 --- a/backend/internal/server/handler/plugin_handler.go +++ b/backend/internal/server/handler/plugin_handler.go @@ -2,7 +2,7 @@ package handler import ( - apppluginadmin "github.com/DouDOU-start/airgate-core/internal/app/pluginadmin" + apppluginadmin "github.com/DevilGenius/airgate-core/internal/app/pluginadmin" ) // PluginHandler 插件管理 API。 diff --git a/backend/internal/server/handler/plugin_handler_mapper.go b/backend/internal/server/handler/plugin_handler_mapper.go index abd77ced..9bb1d28f 100644 --- a/backend/internal/server/handler/plugin_handler_mapper.go +++ b/backend/internal/server/handler/plugin_handler_mapper.go @@ -1,8 +1,8 @@ package handler import ( - apppluginadmin "github.com/DouDOU-start/airgate-core/internal/app/pluginadmin" - "github.com/DouDOU-start/airgate-core/internal/server/dto" + apppluginadmin "github.com/DevilGenius/airgate-core/internal/app/pluginadmin" + "github.com/DevilGenius/airgate-core/internal/server/dto" ) func toPluginResp(item apppluginadmin.PluginMeta) dto.PluginResp { diff --git a/backend/internal/server/handler/plugin_handler_routes.go b/backend/internal/server/handler/plugin_handler_routes.go index 7c67867e..1c6af9a4 100644 --- a/backend/internal/server/handler/plugin_handler_routes.go +++ b/backend/internal/server/handler/plugin_handler_routes.go @@ -8,9 +8,9 @@ import ( "github.com/gin-gonic/gin" - apppluginadmin "github.com/DouDOU-start/airgate-core/internal/app/pluginadmin" - "github.com/DouDOU-start/airgate-core/internal/server/dto" - "github.com/DouDOU-start/airgate-core/internal/server/response" + apppluginadmin "github.com/DevilGenius/airgate-core/internal/app/pluginadmin" + "github.com/DevilGenius/airgate-core/internal/server/dto" + "github.com/DevilGenius/airgate-core/internal/server/response" ) // ListPlugins 获取已加载的插件列表。 @@ -130,7 +130,7 @@ func (h *PluginHandler) InstallFromGithub(c *gin.Context) { return } - if err := h.service.InstallFromGithub(c.Request.Context(), req.Repo); err != nil { + if err := h.service.InstallFromGithub(c.Request.Context(), req.Repo, req.Version); err != nil { response.InternalError(c, "从 GitHub 安装失败: "+err.Error()) return } diff --git a/backend/internal/server/handler/proxy_handler.go b/backend/internal/server/handler/proxy_handler.go index 31d2b56a..a098b716 100644 --- a/backend/internal/server/handler/proxy_handler.go +++ b/backend/internal/server/handler/proxy_handler.go @@ -5,7 +5,7 @@ import ( "log/slog" "strconv" - appproxy "github.com/DouDOU-start/airgate-core/internal/app/proxy" + appproxy "github.com/DevilGenius/airgate-core/internal/app/proxy" ) // ProxyHandler 代理管理 Handler。 diff --git a/backend/internal/server/handler/proxy_handler_mapper.go b/backend/internal/server/handler/proxy_handler_mapper.go index a8a3a070..46b182fb 100644 --- a/backend/internal/server/handler/proxy_handler_mapper.go +++ b/backend/internal/server/handler/proxy_handler_mapper.go @@ -1,8 +1,8 @@ package handler import ( - appproxy "github.com/DouDOU-start/airgate-core/internal/app/proxy" - "github.com/DouDOU-start/airgate-core/internal/server/dto" + appproxy "github.com/DevilGenius/airgate-core/internal/app/proxy" + "github.com/DevilGenius/airgate-core/internal/server/dto" ) func toProxyRespFromDomain(item appproxy.Proxy) dto.ProxyResp { diff --git a/backend/internal/server/handler/proxy_handler_routes.go b/backend/internal/server/handler/proxy_handler_routes.go index ef3aa6f2..02f5990f 100644 --- a/backend/internal/server/handler/proxy_handler_routes.go +++ b/backend/internal/server/handler/proxy_handler_routes.go @@ -3,9 +3,9 @@ package handler import ( "github.com/gin-gonic/gin" - appproxy "github.com/DouDOU-start/airgate-core/internal/app/proxy" - "github.com/DouDOU-start/airgate-core/internal/server/dto" - "github.com/DouDOU-start/airgate-core/internal/server/response" + appproxy "github.com/DevilGenius/airgate-core/internal/app/proxy" + "github.com/DevilGenius/airgate-core/internal/server/dto" + "github.com/DevilGenius/airgate-core/internal/server/response" ) // ListProxies 分页列表代理。 diff --git a/backend/internal/server/handler/settings_handler.go b/backend/internal/server/handler/settings_handler.go index 747c00e5..1ec8f684 100644 --- a/backend/internal/server/handler/settings_handler.go +++ b/backend/internal/server/handler/settings_handler.go @@ -1,7 +1,7 @@ package handler import ( - appsettings "github.com/DouDOU-start/airgate-core/internal/app/settings" + appsettings "github.com/DevilGenius/airgate-core/internal/app/settings" ) // SettingsHandler 系统设置 Handler。 diff --git a/backend/internal/server/handler/settings_handler_mapper.go b/backend/internal/server/handler/settings_handler_mapper.go index a5656416..649fb7b7 100644 --- a/backend/internal/server/handler/settings_handler_mapper.go +++ b/backend/internal/server/handler/settings_handler_mapper.go @@ -1,8 +1,8 @@ package handler import ( - appsettings "github.com/DouDOU-start/airgate-core/internal/app/settings" - "github.com/DouDOU-start/airgate-core/internal/server/dto" + appsettings "github.com/DevilGenius/airgate-core/internal/app/settings" + "github.com/DevilGenius/airgate-core/internal/server/dto" ) func toSettingResp(item appsettings.Setting) dto.SettingResp { diff --git a/backend/internal/server/handler/settings_handler_routes.go b/backend/internal/server/handler/settings_handler_routes.go index 5d123942..e7644856 100644 --- a/backend/internal/server/handler/settings_handler_routes.go +++ b/backend/internal/server/handler/settings_handler_routes.go @@ -13,10 +13,10 @@ import ( "github.com/gin-gonic/gin" - appsettings "github.com/DouDOU-start/airgate-core/internal/app/settings" - "github.com/DouDOU-start/airgate-core/internal/auth" - "github.com/DouDOU-start/airgate-core/internal/server/dto" - "github.com/DouDOU-start/airgate-core/internal/server/response" + appsettings "github.com/DevilGenius/airgate-core/internal/app/settings" + "github.com/DevilGenius/airgate-core/internal/auth" + "github.com/DevilGenius/airgate-core/internal/server/dto" + "github.com/DevilGenius/airgate-core/internal/server/response" ) // publicGroups 允许公开访问的设置分组。 diff --git a/backend/internal/server/handler/subscription_handler.go b/backend/internal/server/handler/subscription_handler.go index 2c42dfec..ef0f6fc8 100644 --- a/backend/internal/server/handler/subscription_handler.go +++ b/backend/internal/server/handler/subscription_handler.go @@ -5,7 +5,7 @@ import ( "log/slog" "strconv" - appsubscription "github.com/DouDOU-start/airgate-core/internal/app/subscription" + appsubscription "github.com/DevilGenius/airgate-core/internal/app/subscription" ) // SubscriptionHandler 订阅管理 Handler。 diff --git a/backend/internal/server/handler/subscription_handler_mapper.go b/backend/internal/server/handler/subscription_handler_mapper.go index 861fe9a5..c94dace1 100644 --- a/backend/internal/server/handler/subscription_handler_mapper.go +++ b/backend/internal/server/handler/subscription_handler_mapper.go @@ -3,8 +3,8 @@ package handler import ( "time" - appsubscription "github.com/DouDOU-start/airgate-core/internal/app/subscription" - "github.com/DouDOU-start/airgate-core/internal/server/dto" + appsubscription "github.com/DevilGenius/airgate-core/internal/app/subscription" + "github.com/DevilGenius/airgate-core/internal/server/dto" ) func toSubscriptionRespFromDomain(item appsubscription.Subscription) dto.SubscriptionResp { diff --git a/backend/internal/server/handler/subscription_handler_routes.go b/backend/internal/server/handler/subscription_handler_routes.go index fdc0c750..76f4453b 100644 --- a/backend/internal/server/handler/subscription_handler_routes.go +++ b/backend/internal/server/handler/subscription_handler_routes.go @@ -3,9 +3,9 @@ package handler import ( "github.com/gin-gonic/gin" - appsubscription "github.com/DouDOU-start/airgate-core/internal/app/subscription" - "github.com/DouDOU-start/airgate-core/internal/server/dto" - "github.com/DouDOU-start/airgate-core/internal/server/response" + appsubscription "github.com/DevilGenius/airgate-core/internal/app/subscription" + "github.com/DevilGenius/airgate-core/internal/server/dto" + "github.com/DevilGenius/airgate-core/internal/server/response" ) // UserSubscriptions 用户查看自己的订阅列表。 diff --git a/backend/internal/server/handler/upgrade_handler.go b/backend/internal/server/handler/upgrade_handler.go index 0709dc12..99a966a1 100644 --- a/backend/internal/server/handler/upgrade_handler.go +++ b/backend/internal/server/handler/upgrade_handler.go @@ -5,8 +5,8 @@ import ( "github.com/gin-gonic/gin" - "github.com/DouDOU-start/airgate-core/internal/server/response" - "github.com/DouDOU-start/airgate-core/internal/upgrade" + "github.com/DevilGenius/airgate-core/internal/server/response" + "github.com/DevilGenius/airgate-core/internal/upgrade" ) // UpgradeHandler 系统更新 Handler。 diff --git a/backend/internal/server/handler/usage_handler.go b/backend/internal/server/handler/usage_handler.go index 4ec6b814..875de504 100644 --- a/backend/internal/server/handler/usage_handler.go +++ b/backend/internal/server/handler/usage_handler.go @@ -3,7 +3,7 @@ package handler import ( "log/slog" - appusage "github.com/DouDOU-start/airgate-core/internal/app/usage" + appusage "github.com/DevilGenius/airgate-core/internal/app/usage" ) // UsageHandler 使用记录 Handler。 diff --git a/backend/internal/server/handler/usage_handler_mapper.go b/backend/internal/server/handler/usage_handler_mapper.go index 4021944b..e38b6c86 100644 --- a/backend/internal/server/handler/usage_handler_mapper.go +++ b/backend/internal/server/handler/usage_handler_mapper.go @@ -1,8 +1,8 @@ package handler import ( - appusage "github.com/DouDOU-start/airgate-core/internal/app/usage" - "github.com/DouDOU-start/airgate-core/internal/server/dto" + appusage "github.com/DevilGenius/airgate-core/internal/app/usage" + "github.com/DevilGenius/airgate-core/internal/server/dto" ) // toUsageLogResp 转换为 reseller / admin 视角的完整响应(包含 actual_cost、billed_cost 等所有字段)。 @@ -26,14 +26,11 @@ func toUsageLogResp(record appusage.LogRecord) dto.UsageLogResp { OutputTokens: record.OutputTokens, CachedInputTokens: record.CachedInputTokens, CacheCreationTokens: record.CacheCreationTokens, - CacheCreation5mTokens: record.CacheCreation5mTokens, - CacheCreation1hTokens: record.CacheCreation1hTokens, ReasoningOutputTokens: record.ReasoningOutputTokens, InputPrice: record.InputPrice, OutputPrice: record.OutputPrice, CachedInputPrice: record.CachedInputPrice, CacheCreationPrice: record.CacheCreationPrice, - CacheCreation1hPrice: record.CacheCreation1hPrice, InputCost: record.InputCost, OutputCost: record.OutputCost, CachedInputCost: record.CachedInputCost, @@ -46,7 +43,6 @@ func toUsageLogResp(record appusage.LogRecord) dto.UsageLogResp { SellRate: record.SellRate, AccountRateMultiplier: record.AccountRateMultiplier, ServiceTier: record.ServiceTier, - ImageSize: record.ImageSize, Stream: record.Stream, DurationMs: record.DurationMs, FirstTokenMs: record.FirstTokenMs, @@ -54,9 +50,6 @@ func toUsageLogResp(record appusage.LogRecord) dto.UsageLogResp { IPAddress: record.IPAddress, Endpoint: record.Endpoint, ReasoningEffort: record.ReasoningEffort, - UsageAttributes: record.UsageAttributes, - UsageMetrics: record.UsageMetrics, - UsageCostDetails: record.UsageCostDetails, UsageMetadata: record.UsageMetadata, CreatedAt: record.CreatedAt, } @@ -75,19 +68,14 @@ func toCustomerUsageLogResp(record appusage.LogRecord) dto.CustomerUsageLogResp OutputTokens: record.OutputTokens, CachedInputTokens: record.CachedInputTokens, CacheCreationTokens: record.CacheCreationTokens, - CacheCreation5mTokens: record.CacheCreation5mTokens, - CacheCreation1hTokens: record.CacheCreation1hTokens, ReasoningOutputTokens: record.ReasoningOutputTokens, BilledCost: record.BilledCost, ServiceTier: record.ServiceTier, - ImageSize: record.ImageSize, Stream: record.Stream, DurationMs: record.DurationMs, FirstTokenMs: record.FirstTokenMs, Endpoint: record.Endpoint, ReasoningEffort: record.ReasoningEffort, - UsageAttributes: record.UsageAttributes, - UsageMetrics: record.UsageMetrics, UsageMetadata: record.UsageMetadata, CreatedAt: record.CreatedAt, } diff --git a/backend/internal/server/handler/usage_handler_routes.go b/backend/internal/server/handler/usage_handler_routes.go index adbff626..a887ebb1 100644 --- a/backend/internal/server/handler/usage_handler_routes.go +++ b/backend/internal/server/handler/usage_handler_routes.go @@ -3,10 +3,10 @@ package handler import ( "github.com/gin-gonic/gin" - appusage "github.com/DouDOU-start/airgate-core/internal/app/usage" - "github.com/DouDOU-start/airgate-core/internal/server/dto" - "github.com/DouDOU-start/airgate-core/internal/server/middleware" - "github.com/DouDOU-start/airgate-core/internal/server/response" + appusage "github.com/DevilGenius/airgate-core/internal/app/usage" + "github.com/DevilGenius/airgate-core/internal/server/dto" + "github.com/DevilGenius/airgate-core/internal/server/middleware" + "github.com/DevilGenius/airgate-core/internal/server/response" ) // UserUsage 用户查看自己的使用记录。 @@ -34,6 +34,7 @@ func (h *UsageHandler) UserUsage(c *gin.Context) { result, err := h.service.ListUser(c.Request.Context(), int64(userID), appusage.ListFilter{ Page: query.Page, PageSize: query.PageSize, + BeforeID: ptrInt64Value(query.BeforeID), APIKeyID: apiKeyFilter, AccountID: query.AccountID, GroupID: query.GroupID, @@ -56,7 +57,7 @@ func (h *UsageHandler) UserUsage(c *gin.Context) { for _, item := range result.List { list = append(list, toCustomerUsageLogResp(item)) } - response.Success(c, response.PagedData(list, result.Total, result.Page, result.PageSize)) + response.Success(c, response.CursorPagedData(list, result.Total, result.Page, result.PageSize, result.HasMore, result.NextCursor, result.TotalExact)) return } @@ -69,7 +70,7 @@ func (h *UsageHandler) UserUsage(c *gin.Context) { resp.AccountRateMultiplier = 0 list = append(list, resp) } - response.Success(c, response.PagedData(list, result.Total, result.Page, result.PageSize)) + response.Success(c, response.CursorPagedData(list, result.Total, result.Page, result.PageSize, result.HasMore, result.NextCursor, result.TotalExact)) } // UserUsageStats 用户聚合统计。 @@ -225,6 +226,7 @@ func (h *UsageHandler) AdminUsage(c *gin.Context) { result, err := h.service.ListAdmin(c.Request.Context(), appusage.ListFilter{ Page: query.Page, PageSize: query.PageSize, + BeforeID: ptrInt64Value(query.BeforeID), UserID: query.UserID, APIKeyID: query.APIKeyID, AccountID: query.AccountID, @@ -245,7 +247,14 @@ func (h *UsageHandler) AdminUsage(c *gin.Context) { for _, item := range result.List { list = append(list, toUsageLogResp(item)) } - response.Success(c, response.PagedData(list, result.Total, result.Page, result.PageSize)) + response.Success(c, response.CursorPagedData(list, result.Total, result.Page, result.PageSize, result.HasMore, result.NextCursor, result.TotalExact)) +} + +func ptrInt64Value(value *int64) int64 { + if value == nil { + return 0 + } + return *value } // AdminUsageStats 管理员聚合统计。 diff --git a/backend/internal/server/handler/user_defaults.go b/backend/internal/server/handler/user_defaults.go index e419198e..d6bf529f 100644 --- a/backend/internal/server/handler/user_defaults.go +++ b/backend/internal/server/handler/user_defaults.go @@ -5,7 +5,7 @@ import ( "strconv" "strings" - appsettings "github.com/DouDOU-start/airgate-core/internal/app/settings" + appsettings "github.com/DevilGenius/airgate-core/internal/app/settings" ) const fallbackDefaultUserMaxConcurrency = 5 diff --git a/backend/internal/server/handler/user_handler.go b/backend/internal/server/handler/user_handler.go index af77fe47..7594cc3c 100644 --- a/backend/internal/server/handler/user_handler.go +++ b/backend/internal/server/handler/user_handler.go @@ -5,8 +5,8 @@ import ( "log/slog" "strconv" - appsettings "github.com/DouDOU-start/airgate-core/internal/app/settings" - appuser "github.com/DouDOU-start/airgate-core/internal/app/user" + appsettings "github.com/DevilGenius/airgate-core/internal/app/settings" + appuser "github.com/DevilGenius/airgate-core/internal/app/user" ) // UserHandler 用户管理 Handler。 diff --git a/backend/internal/server/handler/user_handler_group_rates.go b/backend/internal/server/handler/user_handler_group_rates.go index a042a09b..74f787ce 100644 --- a/backend/internal/server/handler/user_handler_group_rates.go +++ b/backend/internal/server/handler/user_handler_group_rates.go @@ -5,8 +5,8 @@ import ( "github.com/gin-gonic/gin" - "github.com/DouDOU-start/airgate-core/internal/server/dto" - "github.com/DouDOU-start/airgate-core/internal/server/response" + "github.com/DevilGenius/airgate-core/internal/server/dto" + "github.com/DevilGenius/airgate-core/internal/server/response" ) // ListGroupRateOverrides 列出某个分组下所有设置了专属倍率的用户。 diff --git a/backend/internal/server/handler/user_handler_mapper.go b/backend/internal/server/handler/user_handler_mapper.go index 5c785fa0..208158c5 100644 --- a/backend/internal/server/handler/user_handler_mapper.go +++ b/backend/internal/server/handler/user_handler_mapper.go @@ -1,9 +1,9 @@ package handler import ( - appapikey "github.com/DouDOU-start/airgate-core/internal/app/apikey" - appuser "github.com/DouDOU-start/airgate-core/internal/app/user" - "github.com/DouDOU-start/airgate-core/internal/server/dto" + appapikey "github.com/DevilGenius/airgate-core/internal/app/apikey" + appuser "github.com/DevilGenius/airgate-core/internal/app/user" + "github.com/DevilGenius/airgate-core/internal/server/dto" ) func toUserRespFromDomain(item appuser.User) dto.UserResp { diff --git a/backend/internal/server/handler/user_handler_routes.go b/backend/internal/server/handler/user_handler_routes.go index 33d95c7b..4374284e 100644 --- a/backend/internal/server/handler/user_handler_routes.go +++ b/backend/internal/server/handler/user_handler_routes.go @@ -5,11 +5,11 @@ import ( "github.com/gin-gonic/gin" - appuser "github.com/DouDOU-start/airgate-core/internal/app/user" - corauth "github.com/DouDOU-start/airgate-core/internal/auth" - "github.com/DouDOU-start/airgate-core/internal/server/dto" - "github.com/DouDOU-start/airgate-core/internal/server/middleware" - "github.com/DouDOU-start/airgate-core/internal/server/response" + appuser "github.com/DevilGenius/airgate-core/internal/app/user" + corauth "github.com/DevilGenius/airgate-core/internal/auth" + "github.com/DevilGenius/airgate-core/internal/server/dto" + "github.com/DevilGenius/airgate-core/internal/server/middleware" + "github.com/DevilGenius/airgate-core/internal/server/response" ) // GetMe 获取当前登录用户信息。 diff --git a/backend/internal/server/handler/version_handler.go b/backend/internal/server/handler/version_handler.go index 4f177ffc..64ba0ff0 100644 --- a/backend/internal/server/handler/version_handler.go +++ b/backend/internal/server/handler/version_handler.go @@ -5,8 +5,8 @@ import ( "github.com/gin-gonic/gin" - "github.com/DouDOU-start/airgate-core/internal/server/response" - "github.com/DouDOU-start/airgate-core/internal/version" + "github.com/DevilGenius/airgate-core/internal/server/response" + "github.com/DevilGenius/airgate-core/internal/version" ) // VersionHandler 暴露 core 版本信息(管理员可见)。 diff --git a/backend/internal/server/middleware/auth.go b/backend/internal/server/middleware/auth.go index e7e6d28b..9370a2a3 100644 --- a/backend/internal/server/middleware/auth.go +++ b/backend/internal/server/middleware/auth.go @@ -8,11 +8,11 @@ import ( "github.com/gin-gonic/gin" - sdk "github.com/DouDOU-start/airgate-sdk/sdkgo" + sdk "github.com/DevilGenius/airgate-sdk/sdkgo" - "github.com/DouDOU-start/airgate-core/ent" - "github.com/DouDOU-start/airgate-core/internal/auth" - "github.com/DouDOU-start/airgate-core/internal/server/response" + "github.com/DevilGenius/airgate-core/ent" + "github.com/DevilGenius/airgate-core/internal/auth" + "github.com/DevilGenius/airgate-core/internal/server/response" ) // Context Key 常量 diff --git a/backend/internal/server/middleware/recovery.go b/backend/internal/server/middleware/recovery.go index 9562b27e..65f31368 100644 --- a/backend/internal/server/middleware/recovery.go +++ b/backend/internal/server/middleware/recovery.go @@ -7,7 +7,7 @@ import ( "github.com/gin-gonic/gin" - sdk "github.com/DouDOU-start/airgate-sdk/sdkgo" + sdk "github.com/DevilGenius/airgate-sdk/sdkgo" ) // Recovery 拦截 panic 并写入 500 JSON,替代 gin.Recovery() 以便接入结构化日志。 diff --git a/backend/internal/server/middleware/recovery_test.go b/backend/internal/server/middleware/recovery_test.go index 02b1ed6d..65c63fec 100644 --- a/backend/internal/server/middleware/recovery_test.go +++ b/backend/internal/server/middleware/recovery_test.go @@ -8,7 +8,7 @@ import ( "github.com/gin-gonic/gin" - sdk "github.com/DouDOU-start/airgate-sdk/sdkgo" + sdk "github.com/DevilGenius/airgate-sdk/sdkgo" ) func TestRequestLoggerPropagatesRequestID(t *testing.T) { diff --git a/backend/internal/server/middleware/request_logger.go b/backend/internal/server/middleware/request_logger.go index 9afd0de6..0ef92014 100644 --- a/backend/internal/server/middleware/request_logger.go +++ b/backend/internal/server/middleware/request_logger.go @@ -6,7 +6,7 @@ import ( "github.com/gin-gonic/gin" - sdk "github.com/DouDOU-start/airgate-sdk/sdkgo" + sdk "github.com/DevilGenius/airgate-sdk/sdkgo" ) // CtxKeyRequestID 在 gin.Context 中存放 request_id 的键名。 diff --git a/backend/internal/server/response/response.go b/backend/internal/server/response/response.go index c653553f..0919f406 100644 --- a/backend/internal/server/response/response.go +++ b/backend/internal/server/response/response.go @@ -72,3 +72,14 @@ func PagedData(list interface{}, total int64, page, pageSize int) map[string]int "page_size": pageSize, } } + +// CursorPagedData 构建基于游标的分页响应数据。 +func CursorPagedData(list interface{}, total int64, page, pageSize int, hasMore bool, nextCursor *int64, totalExact bool) map[string]interface{} { + data := PagedData(list, total, page, pageSize) + data["has_more"] = hasMore + data["total_exact"] = totalExact + if nextCursor != nil { + data["next_cursor"] = *nextCursor + } + return data +} diff --git a/backend/internal/server/router.go b/backend/internal/server/router.go index 7c63556d..9e078bb3 100644 --- a/backend/internal/server/router.go +++ b/backend/internal/server/router.go @@ -11,10 +11,10 @@ import ( "github.com/gin-gonic/gin" - "github.com/DouDOU-start/airgate-core/internal/plugin" - "github.com/DouDOU-start/airgate-core/internal/server/middleware" - "github.com/DouDOU-start/airgate-core/internal/setup" - webfs "github.com/DouDOU-start/airgate-core/internal/web" + "github.com/DevilGenius/airgate-core/internal/plugin" + "github.com/DevilGenius/airgate-core/internal/server/middleware" + "github.com/DevilGenius/airgate-core/internal/setup" + webfs "github.com/DevilGenius/airgate-core/internal/web" ) // registerRoutes 注册所有 API 路由 diff --git a/backend/internal/server/server.go b/backend/internal/server/server.go index dbd735d5..87c0a9d2 100644 --- a/backend/internal/server/server.go +++ b/backend/internal/server/server.go @@ -10,13 +10,13 @@ import ( "github.com/gin-gonic/gin" "github.com/redis/go-redis/v9" - "github.com/DouDOU-start/airgate-core/ent" - "github.com/DouDOU-start/airgate-core/internal/auth" - "github.com/DouDOU-start/airgate-core/internal/billing" - "github.com/DouDOU-start/airgate-core/internal/bootstrap" - "github.com/DouDOU-start/airgate-core/internal/config" - "github.com/DouDOU-start/airgate-core/internal/plugin" - "github.com/DouDOU-start/airgate-core/internal/scheduler" + "github.com/DevilGenius/airgate-core/ent" + "github.com/DevilGenius/airgate-core/internal/auth" + "github.com/DevilGenius/airgate-core/internal/billing" + "github.com/DevilGenius/airgate-core/internal/bootstrap" + "github.com/DevilGenius/airgate-core/internal/config" + "github.com/DevilGenius/airgate-core/internal/plugin" + "github.com/DevilGenius/airgate-core/internal/scheduler" ) // Server HTTP 服务器 diff --git a/backend/internal/server/server_test.go b/backend/internal/server/server_test.go index 52632370..d8f04755 100644 --- a/backend/internal/server/server_test.go +++ b/backend/internal/server/server_test.go @@ -3,7 +3,7 @@ package server import ( "testing" - "github.com/DouDOU-start/airgate-core/internal/config" + "github.com/DevilGenius/airgate-core/internal/config" ) func TestContentTypeFromExt(t *testing.T) { diff --git a/backend/internal/setup/handler.go b/backend/internal/setup/handler.go index 9d06b89b..0f555216 100644 --- a/backend/internal/setup/handler.go +++ b/backend/internal/setup/handler.go @@ -6,9 +6,9 @@ import ( "github.com/gin-gonic/gin" - "github.com/DouDOU-start/airgate-core/internal/config" - "github.com/DouDOU-start/airgate-core/internal/server/dto" - "github.com/DouDOU-start/airgate-core/internal/server/response" + "github.com/DevilGenius/airgate-core/internal/config" + "github.com/DevilGenius/airgate-core/internal/server/dto" + "github.com/DevilGenius/airgate-core/internal/server/response" ) // 安装完成回调 diff --git a/backend/internal/setup/setup.go b/backend/internal/setup/setup.go index 5ffe4ae5..ea147137 100644 --- a/backend/internal/setup/setup.go +++ b/backend/internal/setup/setup.go @@ -21,12 +21,12 @@ import ( "golang.org/x/crypto/bcrypt" "gopkg.in/yaml.v3" - sdk "github.com/DouDOU-start/airgate-sdk/sdkgo" + sdk "github.com/DevilGenius/airgate-sdk/sdkgo" - "github.com/DouDOU-start/airgate-core/ent" - "github.com/DouDOU-start/airgate-core/ent/migrate" - "github.com/DouDOU-start/airgate-core/internal/config" - "github.com/DouDOU-start/airgate-core/internal/infra/store" + "github.com/DevilGenius/airgate-core/ent" + "github.com/DevilGenius/airgate-core/ent/migrate" + "github.com/DevilGenius/airgate-core/internal/config" + "github.com/DevilGenius/airgate-core/internal/infra/store" ) var installMu sync.Mutex @@ -120,16 +120,17 @@ func NeedsSetup() bool { if err := db.PingContext(ctx); err != nil { slog.Warn("db_ping_failed", "stage", "needs_setup", sdk.LogFieldError, err) - return true + return isSetupBootstrapError(err) } // 查询 users 表是否存在管理员记录 var count int err = db.QueryRowContext(ctx, "SELECT COUNT(*) FROM users WHERE role = 'admin'").Scan(&count) if err != nil { - // 表不存在或查询失败,视为未安装 + // 表不存在等初始化状态才视为未安装;权限/连接类异常应让主服务失败退出, + // 避免配置已存在的部署误进入安装向导并把 API 请求 fallback 成 HTML 200。 slog.Warn("setup_admin_query_failed", "stage", "needs_setup", sdk.LogFieldError, err) - return true + return isSetupBootstrapError(err) } if count == 0 { @@ -197,6 +198,24 @@ func isDatabaseNotExistError(err error) bool { return strings.Contains(err.Error(), "does not exist") } +func isSetupBootstrapError(err error) bool { + if err == nil { + return false + } + if pqErr, ok := err.(*pq.Error); ok { + switch pqErr.Code { + case "3D000", // invalid_catalog_name: database does not exist + "42P01", // undefined_table: users table does not exist + "42703": // undefined_column: users.role does not exist + return true + default: + return false + } + } + msg := strings.ToLower(err.Error()) + return strings.Contains(msg, "does not exist") || strings.Contains(msg, "no such table") +} + // createDatabase 连到 PostgreSQL 系统库 `postgres` 执行 CREATE DATABASE。 // 通过 quoteIdentifier 防止用户在数据库名里塞 SQL 注入(虽然安装向导通常没人这么干, // 但 CREATE DATABASE 不支持参数占位符,必须自己拼字符串,所以这一步是必须的)。 diff --git a/backend/internal/setup/setup_test.go b/backend/internal/setup/setup_test.go index bb7ca05d..721977ba 100644 --- a/backend/internal/setup/setup_test.go +++ b/backend/internal/setup/setup_test.go @@ -73,6 +73,29 @@ func TestDatabaseNotExistDetection(t *testing.T) { } } +func TestSetupBootstrapErrorDetection(t *testing.T) { + tests := []struct { + name string + err error + want bool + }{ + {name: "database missing", err: &pq.Error{Code: "3D000"}, want: true}, + {name: "users table missing", err: &pq.Error{Code: "42P01"}, want: true}, + {name: "users role missing", err: &pq.Error{Code: "42703"}, want: true}, + {name: "permission denied", err: &pq.Error{Code: "42501"}, want: false}, + {name: "network error", err: errors.New("connection refused"), want: false}, + {name: "sqlite-style missing table", err: errors.New("no such table: users"), want: true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := isSetupBootstrapError(tt.err); got != tt.want { + t.Fatalf("isSetupBootstrapError() = %v, want %v", got, tt.want) + } + }) + } +} + func TestQuoteIdentifierEscapesDoubleQuotes(t *testing.T) { got := quoteIdentifier(`air"gate`) want := `"air""gate"` diff --git a/backend/internal/upgrade/github.go b/backend/internal/upgrade/github.go index 2c8b6f9b..ad2c348e 100644 --- a/backend/internal/upgrade/github.go +++ b/backend/internal/upgrade/github.go @@ -10,7 +10,7 @@ import ( ) // GithubRepo 仓库标识。 -const GithubRepo = "DouDOU-start/airgate-core" +const GithubRepo = "DevilGenius/airgate-core" // githubClient 带 ETag 缓存和短时间内存缓存的 GitHub release 客户端。 type githubClient struct { diff --git a/backend/internal/upgrade/service.go b/backend/internal/upgrade/service.go index eec07f6e..b99b8533 100644 --- a/backend/internal/upgrade/service.go +++ b/backend/internal/upgrade/service.go @@ -18,9 +18,9 @@ import ( "github.com/redis/go-redis/v9" - sdk "github.com/DouDOU-start/airgate-sdk/sdkgo" + sdk "github.com/DevilGenius/airgate-sdk/sdkgo" - "github.com/DouDOU-start/airgate-core/internal/version" + "github.com/DevilGenius/airgate-core/internal/version" ) // redisLockKey 全局升级互斥锁。 diff --git a/backend/internal/version/version.go b/backend/internal/version/version.go index f39c395f..3f0cdc5d 100644 --- a/backend/internal/version/version.go +++ b/backend/internal/version/version.go @@ -1,7 +1,7 @@ // Package version 暴露 core 的版本号。 // // 默认值 "dev" 仅用于本地 go build / go run。正式发版由 Makefile(或 release -// workflow)通过 -ldflags "-X github.com/DouDOU-start/airgate-core/internal/version.Version=$tag" +// workflow)通过 -ldflags "-X github.com/DevilGenius/airgate-core/internal/version.Version=$tag" // 注入 git tag。 package version diff --git a/deploy/.env.example b/deploy/.env.example index f2728764..872b6fe3 100644 --- a/deploy/.env.example +++ b/deploy/.env.example @@ -5,7 +5,7 @@ # ---- 镜像 ---- # 镜像名(默认走 GitHub Container Registry,自建镜像时改成自己的) -AIRGATE_IMAGE=ghcr.io/doudou-start/airgate-core +AIRGATE_IMAGE=ghcr.io/devilgenius/airgate-core # 镜像版本:latest = 最新发布版本;可固定到 v0.x.y 防止意外升级 AIRGATE_IMAGE_TAG=latest diff --git a/deploy/Dockerfile b/deploy/Dockerfile index 7e2d249d..bee5e0a1 100644 --- a/deploy/Dockerfile +++ b/deploy/Dockerfile @@ -19,9 +19,9 @@ RUN cd airgate-core/web && pnpm build FROM golang:1.25-alpine AS backend RUN apk add --no-cache git WORKDIR /build -ENV GOPRIVATE=github.com/DouDOU-start/airgate-sdk \ - GONOPROXY=github.com/DouDOU-start/airgate-sdk \ - GONOSUMDB=github.com/DouDOU-start/airgate-sdk +ENV GOPRIVATE=github.com/DevilGenius/airgate-sdk \ + GONOPROXY=github.com/DevilGenius/airgate-sdk \ + GONOSUMDB=github.com/DevilGenius/airgate-sdk COPY airgate-core/backend/go.mod airgate-core/backend/go.sum ./airgate-core/backend/ RUN cd airgate-core/backend && go mod download -x @@ -36,7 +36,7 @@ COPY --from=frontend /build/airgate-core/web/dist/. airgate-core/backend/interna # 镜像内的 Version 由 build-arg 注入,与 release workflow 的 git tag 一致 ARG VERSION=docker RUN cd airgate-core/backend && CGO_ENABLED=0 go build -buildvcs=false -trimpath \ - -ldflags "-X github.com/DouDOU-start/airgate-core/internal/version.Version=${VERSION}" \ + -ldflags "-X github.com/DevilGenius/airgate-core/internal/version.Version=${VERSION}" \ -o /server ./cmd/server # ---- 阶段 3: 运行时 ---- diff --git a/deploy/airgate-core.service b/deploy/airgate-core.service index 9a0b8394..4385ba2b 100644 --- a/deploy/airgate-core.service +++ b/deploy/airgate-core.service @@ -1,6 +1,6 @@ [Unit] Description=AirGate Core - Pluggable AI Gateway Runtime -Documentation=https://github.com/DouDOU-start/airgate-core +Documentation=https://github.com/DevilGenius/airgate-core After=network.target postgresql.service redis.service redis-server.service Wants=postgresql.service redis.service diff --git a/deploy/dev.ps1 b/deploy/dev.ps1 index 80ed13fc..fa589b84 100644 --- a/deploy/dev.ps1 +++ b/deploy/dev.ps1 @@ -159,7 +159,7 @@ function Invoke-InDir([string]$Directory, [string]$Command) { } function Get-GoEnvCommand([string]$Command) { - "`$env:GOTOOLCHAIN = 'local'; `$env:GOPRIVATE = 'github.com/DouDOU-start/airgate-sdk'; `$env:GONOPROXY = 'github.com/DouDOU-start/airgate-sdk'; `$env:GONOSUMDB = 'github.com/DouDOU-start/airgate-sdk'; $Command" + "`$env:GOTOOLCHAIN = 'local'; `$env:GOPRIVATE = 'github.com/DevilGenius/airgate-sdk'; `$env:GONOPROXY = 'github.com/DevilGenius/airgate-sdk'; `$env:GONOSUMDB = 'github.com/DevilGenius/airgate-sdk'; $Command" } function Assert-Command([string]$Name) { @@ -253,7 +253,7 @@ go 1.25.7 use . -replace github.com/DouDOU-start/airgate-sdk => ../../airgate-sdk +replace github.com/DevilGenius/airgate-sdk => ../../airgate-sdk "@ if (-not (Test-Path $goWork) -or ((Get-Content -Raw $goWork) -ne $desired)) { @@ -371,7 +371,7 @@ function Sync-PluginWebdist($Plugin) { function Build-Plugin($Plugin) { Ensure-PluginGoWork $Plugin - $themeTypes = Join-Path $Plugin.WebDir "node_modules\@doudou-start\airgate-theme\dist\index.d.ts" + $themeTypes = Join-Path $Plugin.WebDir "node_modules\@devilgenius\airgate-theme\dist\index.d.ts" if (-not (Test-Path $themeTypes)) { Invoke-PnpmInstall $Plugin.WebDir -Force } @@ -387,7 +387,7 @@ function Build-All { Assert-Command "pnpm" Invoke-InDir $SdkTheme "pnpm build" - $themeTypes = Join-Path $WebDir "node_modules\@doudou-start\airgate-theme\dist\index.d.ts" + $themeTypes = Join-Path $WebDir "node_modules\@devilgenius\airgate-theme\dist\index.d.ts" if (-not (Test-Path $themeTypes)) { Invoke-PnpmInstall $WebDir -Force } diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml index 4b8d71db..564b897a 100644 --- a/deploy/docker-compose.yml +++ b/deploy/docker-compose.yml @@ -2,7 +2,7 @@ # AirGate Core 生产部署 # # 推荐用法(一行安装): -# curl -fsSL https://raw.githubusercontent.com/DouDOU-start/airgate-core/master/deploy/install.sh | bash +# curl -fsSL https://raw.githubusercontent.com/DevilGenius/airgate-core/master/deploy/install.sh | bash # # 手动用法: # 1. 在本目录(或任意空目录)准备 docker-compose.yml + .env: @@ -78,7 +78,7 @@ services: - airgate-net core: - image: ${AIRGATE_IMAGE:-ghcr.io/doudou-start/airgate-core}:${AIRGATE_IMAGE_TAG:-latest} + image: ${AIRGATE_IMAGE:-ghcr.io/devilgenius/airgate-core}:${AIRGATE_IMAGE_TAG:-latest} restart: unless-stopped depends_on: postgres: diff --git a/deploy/docker-deploy.sh b/deploy/docker-deploy.sh index 48c70489..35513c90 100644 --- a/deploy/docker-deploy.sh +++ b/deploy/docker-deploy.sh @@ -16,7 +16,7 @@ # # 用法: # mkdir airgate && cd airgate -# curl -sSL https://raw.githubusercontent.com/DouDOU-start/airgate-core/master/deploy/docker-deploy.sh | bash +# curl -sSL https://raw.githubusercontent.com/DevilGenius/airgate-core/master/deploy/docker-deploy.sh | bash # # 或带参数: # curl -sSL .../docker-deploy.sh -o docker-deploy.sh @@ -69,7 +69,7 @@ AIRGATE_BRANCH="${AIRGATE_BRANCH:-master}" AIRGATE_TAG="${AIRGATE_TAG:-latest}" NON_INTERACTIVE="${NON_INTERACTIVE:-0}" -REPO_RAW_URL="https://raw.githubusercontent.com/DouDOU-start/airgate-core/${AIRGATE_BRANCH}/deploy" +REPO_RAW_URL="https://raw.githubusercontent.com/DevilGenius/airgate-core/${AIRGATE_BRANCH}/deploy" # ---- 依赖检查 ---- section "环境检查" @@ -164,7 +164,7 @@ else # 任何修改需要 docker compose up -d 重启生效 # 数据库 / Redis 密码相关说明见 README.md -AIRGATE_IMAGE=ghcr.io/doudou-start/airgate-core +AIRGATE_IMAGE=ghcr.io/devilgenius/airgate-core AIRGATE_IMAGE_TAG=${AIRGATE_TAG} PORT=${AIRGATE_PORT} @@ -206,6 +206,6 @@ ${C_BOLD}常用命令${C_RESET} $COMPOSE down # 停止 $COMPOSE pull && $COMPOSE up -d # 升级镜像 -${C_DIM}文档:https://github.com/DouDOU-start/airgate-core${C_RESET} +${C_DIM}文档:https://github.com/DevilGenius/airgate-core${C_RESET} DONE diff --git a/deploy/install.sh b/deploy/install.sh index 83eb7527..33e48f73 100644 --- a/deploy/install.sh +++ b/deploy/install.sh @@ -3,7 +3,7 @@ # AirGate Core - 裸金属安装脚本(systemd) # # 用法: -# curl -sSL https://raw.githubusercontent.com/DouDOU-start/airgate-core/master/deploy/install.sh | sudo bash +# curl -sSL https://raw.githubusercontent.com/DevilGenius/airgate-core/master/deploy/install.sh | sudo bash # # 或带子命令: # sudo bash install.sh install # 安装最新版本(默认动作) @@ -38,7 +38,8 @@ set -e # ---- Constants ---- -GITHUB_REPO="DouDOU-start/airgate-core" +GITHUB_REPO="${GITHUB_REPO:-DevilGenius/airgate-core}" +AIRGATE_BRANCH="${AIRGATE_BRANCH:-master}" INSTALL_DIR="/opt/airgate-core" CONFIG_DIR="/etc/airgate-core" DATA_DIR="/var/lib/airgate-core" @@ -237,7 +238,7 @@ setup_directories() { # ---- Install systemd unit ---- install_service() { - local unit_url="https://raw.githubusercontent.com/${GITHUB_REPO}/master/deploy/airgate-core.service" + local unit_url="https://raw.githubusercontent.com/${GITHUB_REPO}/${AIRGATE_BRANCH}/deploy/airgate-core.service" print_info "下载并安装 systemd unit ..." if ! curl -fsSL "$unit_url" -o "/etc/systemd/system/${SERVICE_NAME}.service"; then print_error "下载 systemd unit 失败" @@ -294,7 +295,7 @@ ${BLUE}常用命令${NC} ${BLUE}卸载${NC} - curl -sSL https://raw.githubusercontent.com/${GITHUB_REPO}/master/deploy/install.sh | sudo bash -s -- uninstall + curl -sSL https://raw.githubusercontent.com/${GITHUB_REPO}/${AIRGATE_BRANCH}/deploy/install.sh | sudo bash -s -- uninstall DONE } diff --git a/docs/architecture/ecosystem-v2.md b/docs/architecture/ecosystem-v2.md index 0150ca21..d575238a 100644 --- a/docs/architecture/ecosystem-v2.md +++ b/docs/architecture/ecosystem-v2.md @@ -234,7 +234,7 @@ Core 不应负责: - account/OAuth/import action bridge 类型 - React helper -`@doudou-start/airgate-theme` 负责设计 token 和 theme/CSS helper。 +`@devilgenius/airgate-theme` 负责设计 token 和 theme/CSS helper。 ### 网关插件 @@ -588,7 +588,7 @@ Core task 只保存规范化 task metadata、状态、权限、进度、asset re | --- | --- | --- | | Phase 0 | 停止边界继续扩张 | 不再新增 broad HostService;新 Host API 必须 versioned capability;task API 强制 ownership;Core 不新增 provider image response 解析 | | Phase 1 | 用本文评审新增工作 | 每个变更能回答:属于 Core/Gateway/Provider/SDK/UI 哪一层;capability 是否声明;Host API 是否版本化;ownership 是否由 Core 执行;协议兼容是否保持 | -| Phase 2 | 拆分 SDK 概念 | 逻辑上区分 `airgate-protocol`、`airgate-sdk-go`、`airgate-plugin-runtime-go`、`airgate-devkit-go`、`@airgate/plugin-ui`、`@doudou-start/airgate-theme` | +| Phase 2 | 拆分 SDK 概念 | 逻辑上区分 `airgate-protocol`、`airgate-sdk-go`、`airgate-plugin-runtime-go`、`airgate-devkit-go`、`@airgate/plugin-ui`、`@devilgenius/airgate-theme` | | Phase 3 | 引入 Core service interface | plugin runtime、registry、asset、model catalog、routing、task、asset service、metering/rating/ledger、frontend shell 有清晰内部边界 | | Phase 4 | 以 OpenAI 作为样板 | gateway 不知道 ChatGPT OAuth;provider 不拥有 `/v1/...` route;task lifecycle 由 Core 负责;OpenAI-compatible image sync 语义不回退 | | Phase 5 | 迁移 Claude、Kiro、Playground | Claude/Kiro 成为 provider adapter 或复用 gateway;Playground 转为 UI-only 产品插件 | diff --git a/web/package.json b/web/package.json index 33aac3e0..592707ae 100644 --- a/web/package.json +++ b/web/package.json @@ -12,7 +12,7 @@ "type-check": "tsc -b --noEmit" }, "dependencies": { - "@doudou-start/airgate-theme": "https://github.com/DouDOU-start/airgate-sdk/releases/download/v0.2.1/airgate-theme-v0.2.1.tgz", + "@devilgenius/airgate-theme": "https://github.com/DevilGenius/airgate-sdk/releases/download/v0.2.1/airgate-theme-v0.2.1.tgz", "@fontsource/geist-mono": "^5.2.7", "@fontsource/geist-sans": "^5.2.5", "@heroui/react": "^3.0.3", diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index fc6d7768..32439100 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -8,9 +8,9 @@ importers: .: dependencies: - '@doudou-start/airgate-theme': - specifier: https://github.com/DouDOU-start/airgate-sdk/releases/download/v0.2.1/airgate-theme-v0.2.1.tgz - version: https://github.com/DouDOU-start/airgate-sdk/releases/download/v0.2.1/airgate-theme-v0.2.1.tgz(react@19.2.5) + '@devilgenius/airgate-theme': + specifier: https://github.com/DevilGenius/airgate-sdk/releases/download/v0.2.1/airgate-theme-v0.2.1.tgz + version: https://github.com/DevilGenius/airgate-sdk/releases/download/v0.2.1/airgate-theme-v0.2.1.tgz(react@19.2.5) '@fontsource/geist-mono': specifier: ^5.2.7 version: 5.2.7 @@ -198,8 +198,8 @@ packages: resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} - '@doudou-start/airgate-theme@https://github.com/DouDOU-start/airgate-sdk/releases/download/v0.2.1/airgate-theme-v0.2.1.tgz': - resolution: {integrity: sha512-itYvgvVIcT477Bqa1izpzuQ1TP2iAYjkXQNpOtc4YD0b9NqPYIpuop4lws8bETppqlTzejglLSr2syoWCD7LKA==, tarball: https://github.com/DouDOU-start/airgate-sdk/releases/download/v0.2.1/airgate-theme-v0.2.1.tgz} + '@devilgenius/airgate-theme@https://github.com/DevilGenius/airgate-sdk/releases/download/v0.2.1/airgate-theme-v0.2.1.tgz': + resolution: {integrity: sha512-itYvgvVIcT477Bqa1izpzuQ1TP2iAYjkXQNpOtc4YD0b9NqPYIpuop4lws8bETppqlTzejglLSr2syoWCD7LKA==, tarball: https://github.com/DevilGenius/airgate-sdk/releases/download/v0.2.1/airgate-theme-v0.2.1.tgz} version: 0.2.1 peerDependencies: react: ^19.0.0 @@ -2273,7 +2273,7 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 - '@doudou-start/airgate-theme@https://github.com/DouDOU-start/airgate-sdk/releases/download/v0.2.1/airgate-theme-v0.2.1.tgz(react@19.2.5)': + '@devilgenius/airgate-theme@https://github.com/DevilGenius/airgate-sdk/releases/download/v0.2.1/airgate-theme-v0.2.1.tgz(react@19.2.5)': dependencies: react: 19.2.5 diff --git a/web/src/app/layout/AppShell.tsx b/web/src/app/layout/AppShell.tsx index ac510f70..7bf48265 100644 --- a/web/src/app/layout/AppShell.tsx +++ b/web/src/app/layout/AppShell.tsx @@ -440,7 +440,7 @@ export function AppShell({ children }: AppShellProps) { )} {/* GitHub */} void; diff --git a/web/src/app/plugin-loader.ts b/web/src/app/plugin-loader.ts index 7c866df5..a6fb15fa 100644 --- a/web/src/app/plugin-loader.ts +++ b/web/src/app/plugin-loader.ts @@ -1,5 +1,5 @@ import { createElement, type ComponentType } from 'react'; -import type { PluginFrontendModule } from '@doudou-start/airgate-theme/plugin'; +import type { PluginFrontendModule } from '@devilgenius/airgate-theme/plugin'; function wrapPluginComponent( Component: ComponentType, @@ -53,7 +53,7 @@ const SHARED_MODULES = [ 'react-dom', 'react/jsx-runtime', 'react-i18next', - '@doudou-start/airgate-core/plugin-ui', + '@devilgenius/airgate-core/plugin-ui', ]; const pluginFrontendCache = new Map>(); const pluginFrontendCacheListeners = new Set<(pluginId?: string) => void>(); diff --git a/web/src/app/providers/ThemeProvider.tsx b/web/src/app/providers/ThemeProvider.tsx index 79449152..4350e691 100644 --- a/web/src/app/providers/ThemeProvider.tsx +++ b/web/src/app/providers/ThemeProvider.tsx @@ -1,5 +1,5 @@ import { createContext, useCallback, useContext, useEffect, useMemo, useState, type ReactNode } from 'react'; -import { injectThemeStyle, setTheme, getStoredTheme, type ThemeName } from '@doudou-start/airgate-theme'; +import { injectThemeStyle, setTheme, getStoredTheme, type ThemeName } from '@devilgenius/airgate-theme'; interface ThemeContextValue { theme: ThemeName; diff --git a/web/src/i18n/en.json b/web/src/i18n/en.json index 90e1e04f..69550ae3 100644 --- a/web/src/i18n/en.json +++ b/web/src/i18n/en.json @@ -739,6 +739,10 @@ "plugin_name_hint": "Leave empty to use filename", "github_repo": "Repository", "github_repo_placeholder": "owner/repo", + "github_version": "Release Version", + "github_version_placeholder": "Leave empty for latest, e.g. v1.2.3", + "github_version_hint": "Use an older release tag to roll back", + "version_install": "Install Specific Version", "github_hint": "Download pre-built binary from GitHub Release for the current platform", "upload_success": "Plugin uploaded and installed", "github_success": "Plugin installed from GitHub", diff --git a/web/src/i18n/zh.json b/web/src/i18n/zh.json index b7113e52..84ac8642 100644 --- a/web/src/i18n/zh.json +++ b/web/src/i18n/zh.json @@ -742,6 +742,10 @@ "plugin_name_hint": "留空则使用文件名", "github_repo": "仓库地址", "github_repo_placeholder": "owner/repo", + "github_version": "Release 版本", + "github_version_placeholder": "留空安装最新版,例如 v1.2.3", + "github_version_hint": "填写旧版本 tag 可回滚到指定版本", + "version_install": "安装指定版本", "github_hint": "从 GitHub Release 下载适配当前系统的预编译二进制文件", "upload_success": "插件上传安装成功", "github_success": "插件从 GitHub 安装成功", diff --git a/web/src/main.tsx b/web/src/main.tsx index a56acedf..deea5fae 100644 --- a/web/src/main.tsx +++ b/web/src/main.tsx @@ -24,7 +24,7 @@ import './index.css'; 'react-dom': ReactDOM, 'react/jsx-runtime': ReactJSXRuntime, 'react-i18next': ReactI18next, - '@doudou-start/airgate-core/plugin-ui': AirGatePluginUI, + '@devilgenius/airgate-core/plugin-ui': AirGatePluginUI, }; // PluginAPIBridge 把 core 内的运行时能力暴露到 window.airgate,供插件前端调用。 diff --git a/web/src/pages/DashboardPage.tsx b/web/src/pages/DashboardPage.tsx index 9a34c8c1..16101e61 100644 --- a/web/src/pages/DashboardPage.tsx +++ b/web/src/pages/DashboardPage.tsx @@ -28,7 +28,7 @@ import { Users, Zap, } from 'lucide-react'; -import { decorativePalette } from '@doudou-start/airgate-theme'; +import { decorativePalette } from '@devilgenius/airgate-theme'; import { dashboardApi } from '../shared/api/dashboard'; import { usersApi } from '../shared/api/users'; import { queryKeys } from '../shared/queryKeys'; diff --git a/web/src/pages/PluginPage.tsx b/web/src/pages/PluginPage.tsx index 3c3f3820..87e947b9 100644 --- a/web/src/pages/PluginPage.tsx +++ b/web/src/pages/PluginPage.tsx @@ -1,6 +1,6 @@ import { useMemo, useState, useEffect } from 'react'; import { useParams } from '@tanstack/react-router'; -import type { PluginFrontendModule } from '@doudou-start/airgate-theme/plugin'; +import type { PluginFrontendModule } from '@devilgenius/airgate-theme/plugin'; import { loadPluginFrontend } from '../app/plugin-loader'; import { ChatPageLoading, PageLoading } from '../shared/components/PageLoading'; diff --git a/web/src/pages/admin/PluginsPage.tsx b/web/src/pages/admin/PluginsPage.tsx index 8a1f4d0b..c0a13b4f 100644 --- a/web/src/pages/admin/PluginsPage.tsx +++ b/web/src/pages/admin/PluginsPage.tsx @@ -11,11 +11,16 @@ import { AlertDialog, Button, Card, Checkbox, Chip, Description, EmptyState, Inp import { DialogTriggerShim } from '../../shared/components/DialogTriggerShim'; import { Trash2, Download, Loader2, RefreshCw, - Package, User, Tag, Plus, Upload, Github, Settings, Store, + Package, User, Tag, Plus, Upload, Github, Settings, Store, History, } from 'lucide-react'; import { CommonTable } from '../../shared/components/CommonTable'; import type { PluginResp, MarketplacePluginResp } from '../../shared/types'; +type InstallPrefill = { + repo: string; + version?: string; +} | null; + // 插件类型 Badge 颜色 const typeVariant: Record = { gateway: 'accent', @@ -31,6 +36,7 @@ export default function PluginsPage() { const [activeTab, setActiveTab] = useState<'installed' | 'marketplace'>('installed'); const [uninstallTarget, setUninstallTarget] = useState(null); const [installOpen, setInstallOpen] = useState(false); + const [installPrefill, setInstallPrefill] = useState(null); const [configTarget, setConfigTarget] = useState(null); // 已安装插件列表 @@ -50,7 +56,7 @@ export default function PluginsPage() { const [installingRepo, setInstallingRepo] = useState(null); const [isUpdating, setIsUpdating] = useState(false); const marketInstallMutation = useMutation({ - mutationFn: (repo: string) => pluginsApi.installGithub(repo), + mutationFn: ({ repo, version }: { repo: string; version?: string }) => pluginsApi.installGithub(repo, version), onSuccess: () => { toast('success', t(isUpdating ? 'plugins.update_success' : 'plugins.github_success')); // 插件前端模块需要整页重载才能生效 @@ -63,10 +69,15 @@ export default function PluginsPage() { }, }); - function handleMarketInstall(repo: string, update = false) { + function handleMarketInstall(repo: string, update = false, version?: string) { setInstallingRepo(repo); setIsUpdating(update); - marketInstallMutation.mutate(repo); + marketInstallMutation.mutate({ repo, version }); + } + + function openVersionInstall(repo: string, version = '') { + setInstallPrefill({ repo, version }); + setInstallOpen(true); } // 强制从 GitHub 同步市场列表(点击右上角刷新按钮时触发) @@ -152,7 +163,10 @@ export default function PluginsPage() { ) : ( - {t('plugins.already_installed')} +
+ {t('plugins.already_installed')} + +
) ) : ( - +
+ + +
)} diff --git a/web/src/pages/admin/UsagePage.tsx b/web/src/pages/admin/UsagePage.tsx index a7223083..040c26ac 100644 --- a/web/src/pages/admin/UsagePage.tsx +++ b/web/src/pages/admin/UsagePage.tsx @@ -5,7 +5,7 @@ import { Card, ComboBox, Input, ListBox, Select, Tabs } from '@heroui/react'; import { usageApi } from '../../shared/api/usage'; import { usersApi } from '../../shared/api/users'; import { apikeysApi } from '../../shared/api/apikeys'; -import { usePagination } from '../../shared/hooks/usePagination'; +import { useCursorPagination } from '../../shared/hooks/useCursorPagination'; import { usePlatforms } from '../../shared/hooks/usePlatforms'; import { useDebouncedValue } from '../../shared/hooks/useDebouncedValue'; import { useDeferredActivation } from '../../shared/hooks/useDeferredActivation'; @@ -388,7 +388,7 @@ function TokenTrendCard({ export default function UsagePage() { const { t } = useTranslation(); - const { page, setPage, pageSize, setPageSize } = usePagination(20, 'admin.usage'); + const { beforeId, page, setPage, pageSize, setPageSize, resetCursorPagination } = useCursorPagination(20, 'admin.usage'); const [filters, setFilters] = useState>({}); const [statsGroupBy, setStatsGroupBy] = useState('model'); const [granularity, setGranularity] = useState('hour'); @@ -401,9 +401,9 @@ export default function UsagePage() { const handleModelChange = useCallback((model: string) => { const nextModel = model || undefined; - setPage(1); + resetCursorPagination(); setFilters((prev) => (prev.model === nextModel ? prev : { ...prev, model: nextModel })); - }, [setPage]); + }, [resetCursorPagination]); // 用户搜索 const [userKeyword, setUserKeyword] = useState(''); @@ -475,8 +475,9 @@ export default function UsagePage() { const queryParams = useMemo(() => ({ page, page_size: pageSize, + before_id: beforeId, ...filters, - }), [filters, page, pageSize]); + }), [beforeId, filters, page, pageSize]); // 使用记录列表 const { @@ -555,7 +556,7 @@ export default function UsagePage() { ? (value ? Number(value) : undefined) : value || undefined; setFilters((prev) => ({ ...prev, [key]: nextValue })); - setPage(1); + resetCursorPagination(); } const activeStats = pageActive ? stats : undefined; @@ -686,6 +687,7 @@ export default function UsagePage() { ] as UsageColumnConfig[]; }, [sharedColumns, t]); const total = data?.total ?? 0; + const canUseCursor = pageActive && !isPlaceholderData; return (
@@ -758,7 +760,7 @@ export default function UsagePage() { label={t('usage.time_range')} startDate={filters.start_date} onChange={(startDate, endDate) => { - setPage(1); + resetCursorPagination(); setFilters((prev) => ({ ...prev, start_date: startDate, end_date: endDate })); }} /> @@ -936,14 +938,16 @@ export default function UsagePage() { emptyTitle={t('common.no_data')} highlightNewRows={pageActive && autoRefreshEnabled && page === 1} highlightResetKey={JSON.stringify({ ...filters, page, pageSize })} + hasMore={canUseCursor ? data?.has_more : false} isLoading={!pageActive || isLoading} page={page} pageSize={pageSize} rows={pageActive ? data?.list ?? [] : []} - setPage={setPage} + setPage={(nextPage) => setPage(nextPage, canUseCursor ? data?.next_cursor : undefined)} setPageSize={setPageSize} suppressHighlight={!pageActive || isPlaceholderData} total={pageActive ? total : 0} + totalExact={canUseCursor ? data?.total_exact : true} />
); diff --git a/web/src/pages/admin/accounts/CreateAccountModal.tsx b/web/src/pages/admin/accounts/CreateAccountModal.tsx index 5b4c9c2e..7f6de396 100644 --- a/web/src/pages/admin/accounts/CreateAccountModal.tsx +++ b/web/src/pages/admin/accounts/CreateAccountModal.tsx @@ -6,7 +6,7 @@ import { IdCard, Hash, Gauge } from 'lucide-react'; import type { PluginBatchAccountInput, PluginBatchImportResult, -} from '@doudou-start/airgate-theme/plugin'; +} from '@devilgenius/airgate-theme/plugin'; import { accountsApi } from '../../../shared/api/accounts'; import { groupsApi } from '../../../shared/api/groups'; import { proxiesApi } from '../../../shared/api/proxies'; diff --git a/web/src/pages/admin/accounts/accountUtils.ts b/web/src/pages/admin/accounts/accountUtils.ts index 9c4be96c..43ef3b57 100644 --- a/web/src/pages/admin/accounts/accountUtils.ts +++ b/web/src/pages/admin/accounts/accountUtils.ts @@ -4,7 +4,7 @@ import type { PluginOAuthBridge, PluginOAuthBatchExchangeResult, PluginOAuthExchangeResult, -} from '@doudou-start/airgate-theme/plugin'; +} from '@devilgenius/airgate-theme/plugin'; /** Session 导入能力尚未沉淀进 SDK;Core 在 bridge 上扩展两个可选字段, * 插件 widget 用相同的 intersection 形态拿到这两个 method。 */ diff --git a/web/src/pages/user/UserUsageContent.tsx b/web/src/pages/user/UserUsageContent.tsx index 23b0ba7a..9ed82c27 100644 --- a/web/src/pages/user/UserUsageContent.tsx +++ b/web/src/pages/user/UserUsageContent.tsx @@ -5,7 +5,7 @@ import { Button, Card, ListBox, Meter, Select } from '@heroui/react'; import { usageApi } from '../../shared/api/usage'; import { apikeysApi } from '../../shared/api/apikeys'; import { queryKeys } from '../../shared/queryKeys'; -import { usePagination } from '../../shared/hooks/usePagination'; +import { useCursorPagination } from '../../shared/hooks/useCursorPagination'; import { usePlatforms } from '../../shared/hooks/usePlatforms'; import { useAuth } from '../../app/providers/AuthProvider'; import { useToast } from '../../shared/ui'; @@ -185,7 +185,7 @@ export default function UserUsageContent() { const { t } = useTranslation(); const { user } = useAuth(); const customerScope = !!user?.api_key_id; - const { page, setPage, pageSize, setPageSize } = usePagination(20, 'user.usage'); + const { beforeId, page, setPage, pageSize, setPageSize, resetCursorPagination } = useCursorPagination(20, 'user.usage'); const [filters, setFilters] = useState>({}); const [autoRefresh, setAutoRefresh] = usePersistentAutoRefresh(USER_USAGE_AUTO_UPDATE_STORAGE_KEY, 0, USER_AUTO_REFRESH_OPTIONS); const autoRefreshEnabled = autoRefresh > 0; @@ -194,15 +194,16 @@ export default function UserUsageContent() { const handleModelChange = useCallback((model: string) => { const nextModel = model || undefined; - setPage(1); + resetCursorPagination(); setFilters((prev) => (prev.model === nextModel ? prev : { ...prev, model: nextModel })); - }, [setPage]); + }, [resetCursorPagination]); const queryParams = useMemo(() => ({ page, page_size: pageSize, + before_id: beforeId, ...filters, - }), [filters, page, pageSize]); + }), [beforeId, filters, page, pageSize]); const { platforms, platformName } = usePlatforms(); const platformOptions = [ @@ -262,11 +263,12 @@ export default function UserUsageContent() { function updateFilter(key: string, value: string) { const nextValue = key === 'api_key_id' && value ? Number(value) : value || undefined; setFilters((prev) => ({ ...prev, [key]: nextValue })); - setPage(1); + resetCursorPagination(); } const list = data?.list ?? []; const total = data?.total ?? 0; + const canUseCursor = !isPlaceholderData; const visibleActualCost = customerScope ? (stats?.total_billed_cost ?? 0) : (stats?.total_actual_cost ?? 0); const sharedColumns = useUsageColumns({ customerScope, adminView: false }); @@ -369,7 +371,7 @@ export default function UserUsageContent() { label={t('usage.time_range')} startDate={filters.start_date} onChange={(startDate, endDate) => { - setPage(1); + resetCursorPagination(); setFilters((prev) => ({ ...prev, start_date: startDate, end_date: endDate })); }} /> @@ -460,14 +462,16 @@ export default function UserUsageContent() { emptyTitle={t('common.no_data')} highlightNewRows={autoRefreshEnabled && page === 1} highlightResetKey={JSON.stringify({ ...filters, page, pageSize })} + hasMore={canUseCursor ? data?.has_more : false} isLoading={isLoading} page={page} pageSize={pageSize} rows={list} - setPage={setPage} + setPage={(nextPage) => setPage(nextPage, canUseCursor ? data?.next_cursor : undefined)} setPageSize={setPageSize} suppressHighlight={isPlaceholderData} total={total} + totalExact={canUseCursor ? data?.total_exact : true} /> ); diff --git a/web/src/shared/api/plugins.ts b/web/src/shared/api/plugins.ts index 9b54717a..35bb3746 100644 --- a/web/src/shared/api/plugins.ts +++ b/web/src/shared/api/plugins.ts @@ -22,8 +22,8 @@ export const pluginsApi = { return upload('/api/v1/admin/plugins/upload', fd); }, // 从 GitHub Release 安装 - installGithub: (repo: string) => - post('/api/v1/admin/plugins/install-github', { repo }), + installGithub: (repo: string, version?: string) => + post('/api/v1/admin/plugins/install-github', { repo, version }), // 通用插件 RPC 调用,action 由插件自行定义 rpc: (name: string, action: string, body?: unknown) => post(`/api/v1/admin/plugins/${name}/rpc/${action}`, body), diff --git a/web/src/shared/columns/usageColumns.tsx b/web/src/shared/columns/usageColumns.tsx index f8cd56ab..0a438bb4 100644 --- a/web/src/shared/columns/usageColumns.tsx +++ b/web/src/shared/columns/usageColumns.tsx @@ -14,7 +14,7 @@ import { subscribeUsageMetricDetailChange, subscribeUsageModelMetaChange, } from '../../app/plugin-frontend-registry'; -import type { UsageLogResp, CustomerUsageLogResp, UsageAttribute, UsageMetric } from '../types'; +import type { UsageLogResp, CustomerUsageLogResp } from '../types'; import { USAGE_TOKEN_COLORS } from '../constants'; import { CostValue } from '../components/CostValue'; @@ -245,24 +245,10 @@ function normalizeUsageKey(value?: string): string { return (value || '').trim().toLowerCase().replace(/[\s-]+/g, '_'); } -function normalizeMetricKey(metric: Pick): string { - return normalizeUsageKey(metric.key || metric.kind || metric.label); -} - function metricNumber(value: unknown): number { return typeof value === 'number' && Number.isFinite(value) ? value : 0; } -function metricMatches(metric: UsageMetric, keys: string[]) { - const key = normalizeMetricKey(metric); - return keys.includes(key); -} - -function metricValue(metrics: UsageMetric[], keys: string[]): number | undefined { - const item = metrics.find((metric) => metricMatches(metric, keys)); - return item ? metricNumber(item.value) : undefined; -} - function firstText(...values: unknown[]): string | undefined { for (const value of values) { if (typeof value !== 'string') continue; @@ -272,14 +258,6 @@ function firstText(...values: unknown[]): string | undefined { return undefined; } -function usageAttributeValue(attributes: UsageAttribute[], keys: string[]): string | undefined { - const normalizedKeys = new Set(keys.map(normalizeUsageKey)); - const item = attributes.find((attr) => ( - normalizedKeys.has(normalizeUsageKey(attr.key || attr.kind || attr.label)) - )); - return firstText(item?.value); -} - function usageMetadataValue(metadata: Record, keys: string[]): string | undefined { const normalizedKeys = new Set(keys.map(normalizeUsageKey)); for (const [key, value] of Object.entries(metadata)) { @@ -290,20 +268,54 @@ function usageMetadataValue(metadata: Record, keys: string[]): s return undefined; } -function isTotalMetric(metric: UsageMetric) { - return metricMatches(metric, ['total_tokens', 'total_token', 'total']); +function usageMetadataNumber(metadata: Record, keys: string[]): number { + const value = usageMetadataValue(metadata, keys); + if (!value) return 0; + const parsed = Number(value); + return Number.isFinite(parsed) ? parsed : 0; } -function formatMetricValue(metric: UsageMetric): string { - const value = metricNumber(metric.value); - const formatted = Number.isInteger(value) +interface MetricDisplay { + key: string; + label: string; + kind: 'token' | 'image'; + unit: string; + value: number; +} + +function isTotalMetric(metric: MetricDisplay) { + return metric.key === 'total_tokens'; +} + +function isReasoningMetric(metric: MetricDisplay) { + return metric.key === 'reasoning_output_tokens'; +} + +function isOutputMetric(metric: MetricDisplay) { + return metric.key === 'output_tokens'; +} + +function isTokenUnit(unit?: string) { + const normalized = normalizeUsageKey(unit); + return normalized === 'token' || normalized === 'tokens'; +} + +function formatMetricNumber(value: number): string { + return Number.isInteger(value) ? value.toLocaleString() : value.toLocaleString(undefined, { maximumFractionDigits: 4 }); - return metric.unit ? `${formatted} ${metric.unit}` : formatted; } -function metricColor(metric: UsageMetric, index: number): string | undefined { - const key = normalizeMetricKey(metric); +function formatMetricValue(metric: MetricDisplay): string { + const value = metricNumber(metric.value); + const formatted = formatMetricNumber(value); + const unit = metric.unit?.trim(); + if (!unit || isTokenUnit(unit)) return formatted; + return `${formatted} ${unit}`; +} + +function metricColor(metric: MetricDisplay, index: number): string | undefined { + const key = metric.key; if (key.includes('input') && !key.includes('cached')) return USAGE_TOKEN_COLORS.input; if (key.includes('output')) return USAGE_TOKEN_COLORS.output; if (key.includes('cache_read') || key.includes('cached_input')) return USAGE_TOKEN_COLORS.cacheRead; @@ -312,64 +324,35 @@ function metricColor(metric: UsageMetric, index: number): string | undefined { return [USAGE_TOKEN_COLORS.input, USAGE_TOKEN_COLORS.output, USAGE_TOKEN_COLORS.cacheRead, USAGE_TOKEN_COLORS.cacheCreation][index % 4]; } -function legacyMetrics(row: UsageRow): UsageMetric[] { +function rowMetrics(row: UsageRow): MetricDisplay[] { const cacheCreation = (row as UsageLogResp).cache_creation_tokens ?? 0; - return [ + const metrics: MetricDisplay[] = [ { key: 'input_tokens', label: '输入 Token', kind: 'token', unit: 'token', value: row.input_tokens }, { key: 'output_tokens', label: '输出 Token', kind: 'token', unit: 'token', value: row.output_tokens }, { key: 'cached_input_tokens', label: '缓存读取 Token', kind: 'token', unit: 'token', value: row.cached_input_tokens }, { key: 'cache_creation_tokens', label: '缓存写入 Token', kind: 'token', unit: 'token', value: cacheCreation }, - ].filter((metric) => metric.value > 0 || metric.key === 'input_tokens' || metric.key === 'output_tokens'); -} - -function rowMetrics(row: UsageRow): UsageMetric[] { - const metrics = row.usage_metrics ?? []; - if (metrics.length > 0) return metrics; - return legacyMetrics(row); + ]; + const imageCount = usageMetadataNumber(row.usage_metadata ?? {}, ['openai.image.count']); + if (imageCount > 0) { + metrics.push({ key: 'images', label: '图片数量', kind: 'image', unit: 'image', value: imageCount }); + } + return metrics.filter((metric) => metric.value > 0 || metric.key === 'input_tokens' || metric.key === 'output_tokens'); } function buildUsageRecordContext(row: UsageRow, customerScope: boolean) { - const usageCostDetails = !customerScope && 'usage_cost_details' in row - ? (row.usage_cost_details ?? []) - : []; - const usageAttributes = row.usage_attributes ?? []; - const usageMetrics = row.usage_metrics ?? []; const usageMetadata = row.usage_metadata ?? {}; - const imageSize = firstText( - row.image_size, - usageAttributeValue(usageAttributes, ['image_size', 'resolution', 'size']), - usageMetadataValue(usageMetadata, ['image_size', 'resolution', 'size']), - ); - const serviceTier = firstText( - row.service_tier, - usageAttributeValue(usageAttributes, ['service_tier', 'tier']), - usageMetadataValue(usageMetadata, ['service_tier', 'tier']), - ); - const reasoningEffort = firstText( - (row as Partial).reasoning_effort, - usageAttributeValue(usageAttributes, ['reasoning_effort', 'reasoning']), - usageMetadataValue(usageMetadata, ['reasoning_effort', 'reasoning']), - ); - const reasoningTokens = - (row as Partial).reasoning_output_tokens - ?? metricValue(usageMetrics, ['reasoning_output_tokens', 'reasoning_tokens', 'reasoning_token']); + const serviceTier = firstText(row.service_tier); + const reasoningEffort = firstText((row as Partial).reasoning_effort); + const reasoningTokens = (row as Partial).reasoning_output_tokens; const ctx: Record = { record: row, customerScope, - usageAttributes, - usageMetrics, - usageCostDetails, - usageMetadata, - usage_attributes: usageAttributes, - usage_metrics: usageMetrics, - usage_cost_details: usageCostDetails, usage_metadata: usageMetadata, // 常用的行级别字段做扁平化,方便插件扩展渲染器直接取值。 model: row.model, platform: row.platform, service_tier: serviceTier, - image_size: imageSize, endpoint: row.endpoint, stream: row.stream, created_at: row.created_at, @@ -391,15 +374,15 @@ function buildCostDetailContext(row: UsageLogResp, adminView: boolean) { function GenericMetricDetail({ row, t }: { row: UsageRow; t: TFunction }) { const allMetrics = rowMetrics(row); - const hasSDKMetrics = (row.usage_metrics?.length ?? 0) > 0; + const reasoningTokens = (row as Partial).reasoning_output_tokens ?? 0; const metrics = allMetrics.filter((metric) => ( - !isTotalMetric(metric) && (metricNumber(metric.value) > 0 || !hasSDKMetrics) + !isTotalMetric(metric) + && !isReasoningMetric(metric) + && (metricNumber(metric.value) > 0 || metric.key === 'input_tokens' || metric.key === 'output_tokens' || (isOutputMetric(metric) && reasoningTokens > 0)) )); - const totalMetric = allMetrics.find(isTotalMetric); const tokenTotal = - totalMetric?.value - ?? row.input_tokens + row.output_tokens + row.cached_input_tokens + ((row as UsageLogResp).cache_creation_tokens ?? 0); - const shouldShowTokenTotal = !!totalMetric || tokenTotal > 0 || metrics.some((metric) => metric.kind === 'token'); + row.input_tokens + row.output_tokens + row.cached_input_tokens + ((row as UsageLogResp).cache_creation_tokens ?? 0); + const shouldShowTokenTotal = tokenTotal > 0 || metrics.some((metric) => metric.kind === 'token'); return ( @@ -407,7 +390,12 @@ function GenericMetricDetail({ row, t }: { row: UsageRow; t: TFunction }) { 0 ? ( + + (推理 {formatMetricNumber(reasoningTokens)}) + {formatMetricValue(metric)} + + ) : formatMetricValue(metric)} color={metricColor(metric, index)} /> ))} @@ -449,6 +437,9 @@ function buildResellerCostColumn(t: TFunction, adminView: boolean): UsageColumnC {row.output_price > 0 && ( )} + {row.cache_creation_price > 0 && ( + + )} {row.cached_input_cost > 0 && ( )} @@ -573,7 +564,7 @@ export function useUsageColumns(opts?: { customerScope?: boolean; adminView?: bo const metaContext = buildUsageRecordContext(row, customerScope); const fallbackMeta = (() => { if (PluginUsageModelMeta) return null; - const imageSize = typeof metaContext.image_size === 'string' ? metaContext.image_size : ''; + const imageSize = usageMetadataValue(row.usage_metadata ?? {}, ['openai.image.size']) ?? ''; if (imageSize) { return ( { - const metrics = rowMetrics(row); const PluginUsageMetricDetail = getPluginUsageMetricDetail(row.platform); - const inputTokens = metricValue(metrics, ['input_tokens', 'input_token', 'prompt_tokens', 'prompt_token']) ?? row.input_tokens; - const outputTokens = metricValue(metrics, ['output_tokens', 'output_token', 'completion_tokens', 'completion_token']) ?? row.output_tokens; - const cacheReadTokens = metricValue(metrics, ['cached_input_tokens', 'cached_input_token', 'cache_read_tokens', 'cache_read_token']) ?? row.cached_input_tokens; - const cacheCreationTokens = metricValue(metrics, ['cache_creation_tokens', 'cache_creation_token']) ?? ((row as UsageLogResp).cache_creation_tokens ?? 0); - const total = - metricValue(metrics, ['total_tokens', 'total_token']) - ?? inputTokens + outputTokens + cacheReadTokens + cacheCreationTokens; + const metrics = rowMetrics(row); + const inputTokens = row.input_tokens; + const outputTokens = row.output_tokens; + const cacheReadTokens = row.cached_input_tokens; + const cacheCreationTokens = (row as UsageLogResp).cache_creation_tokens ?? 0; + const total = inputTokens + outputTokens + cacheReadTokens + cacheCreationTokens; const hasCacheRead = cacheReadTokens > 0; const hasCacheWrite = cacheCreationTokens > 0; const tokenSummaryVisible = inputTokens > 0 || outputTokens > 0 || hasCacheRead || hasCacheWrite || total > 0; diff --git a/web/src/shared/components/TablePaginationFooter.tsx b/web/src/shared/components/TablePaginationFooter.tsx index 5d05360a..ec653c51 100644 --- a/web/src/shared/components/TablePaginationFooter.tsx +++ b/web/src/shared/components/TablePaginationFooter.tsx @@ -2,25 +2,32 @@ import { ListBox, Pagination, Select } from '@heroui/react'; import { DEFAULT_PAGINATION_PAGE_SIZE_OPTIONS, getPaginationItems } from '../utils/pagination'; interface TablePaginationFooterProps { + hasMore?: boolean; page: number; pageSize?: number; pageSizeOptions?: readonly number[]; setPage: (page: number) => void; setPageSize?: (pageSize: number) => void; total: number; + totalExact?: boolean; totalPages: number; } export function TablePaginationFooter({ + hasMore, page, pageSize, pageSizeOptions = DEFAULT_PAGINATION_PAGE_SIZE_OPTIONS, setPage, setPageSize, total, + totalExact = true, totalPages, }: TablePaginationFooterProps) { - const safeTotalPages = Math.max(totalPages, 1); + const safeTotalPages = totalExact + ? Math.max(totalPages, 1) + : Math.max(totalPages, page + (hasMore ? 1 : 0), 1); + const canGoNext = totalExact ? page < safeTotalPages : !!hasMore; const showPageSize = pageSize != null && setPageSize != null; const selectedPageSize = pageSize == null ? '' : String(pageSize); const pageSizeItems = pageSizeOptions.map((size) => ({ id: String(size), label: String(size) })); @@ -28,7 +35,7 @@ export function TablePaginationFooter({ return ( - + {totalExact ? '共' : '至少'} {total.toLocaleString()}