5 Commits
Author SHA1 Message Date
wangdefa b2252678dd 发布 v0.7.0
CI / test (push) Successful in 21s
Release / release (push) Successful in 59s
2026-07-16 12:59:52 +08:00
wangdefa 7002e42c06 美化项目与AI网关文档
CI / test (push) Successful in 21s
2026-07-16 12:47:57 +08:00
wangdefa da7b29d2e3 设置页AI Tab:网关设置入库、保险丝改维度与grok工具注入
CI / test (push) Successful in 33s
2026-07-16 12:31:36 +08:00
wangdefa 99b551401e 实例地址恒以主网卡为准,修复多网卡覆写随机 2026-07-16 12:31:36 +08:00
wangdefa 4e2bab3032 升级 oci-go-sdk 到 v65.121.0
CI / test (push) Successful in 44s
2026-07-15 14:23:01 +08:00
18 changed files with 858 additions and 239 deletions
+18
View File
@@ -2,6 +2,24 @@
格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/)(版本段不记日期),版本号遵循语义化版本。 格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/)(版本段不记日期),版本号遵循语义化版本。
## [0.7.0]
### Added
- AI 网关运行时设置扩展(`GET` / `PUT /api/v1/ai-settings`,持久化、即时生效):
- Responses 流式保险丝:开关 + 阈值(KB,1..1024),超阈值的流式请求预防性改非流式上游并合成最小 SSE
- grok 服务端搜索工具默认注入:`xai.` 前缀模型的 Responses 请求按开关默认注入 `web_search` / `x_search`,请求 `tools` 已含同名工具(任意参数形态)时不覆盖,注入动作记服务端日志
- 聚合模型目录端点 `GET /api/v1/ai-model-catalog`:启用渠道去重、含能力字段(空能力归一 CHAT),与模型列表同口径(随「过滤弃用」开关),供设置页黑名单添加弹窗使用
### Changed
- Responses 流式保险丝触发口径由「完整请求体 > 76KB(环境变量 `AI_RESP_STREAM_UPGRADE_KB`)」改为「`instructions` + `tools` 原始字节合计超阈值(默认开、60KB,设置页可调)」:复测证实约 82KB 的纯体积断流已由上游修复,而 `instructions`+`tools` 合计 >≈64.5KB 的流式静默断流仍存在(`input` 不计入);环境变量随之废弃
- 升级 oci-go-sdk 到 v65.121.0
### Fixed
- 修复多网卡实例的列表 / 详情地址字段随机:并发填充改以主网卡为准(主卡未返回前允许先到网卡兜底,主卡到达后覆盖并锁定),公网 IP / 私网 IP / IPv6 / 子网恒为主网卡数据
## [0.6.1] ## [0.6.1]
### Added ### Added
+1 -1
View File
@@ -1 +1 @@
v0.6.0 v0.7.0
+81 -43
View File
@@ -6,12 +6,14 @@
**自托管的 OCI 多租户管理面板与 GenAI 兼容网关** **自托管的 OCI 多租户管理面板与 GenAI 兼容网关**
![Release](https://img.shields.io/github/v/release/wangdefaa/oci-portal?display_name=tag) [![Release](https://img.shields.io/github/v/release/wangdefaa/oci-portal?display_name=tag)](https://github.com/wangdefaa/oci-portal/releases/latest)
![Go](https://img.shields.io/badge/Go-1.26.5-00ADD8?logo=go&logoColor=white) [![Go](https://img.shields.io/badge/Go-1.26.5-00ADD8?logo=go&logoColor=white)](go.mod)
![Docker](https://img.shields.io/badge/Docker-amd64%20%7C%20arm64-2496ED?logo=docker&logoColor=white) [![Docker](https://img.shields.io/badge/Docker-amd64%20%7C%20arm64-2496ED?logo=docker&logoColor=white)](docker-compose.yml)
![License](https://img.shields.io/badge/License-MIT-blue) [![License](https://img.shields.io/badge/License-MIT-blue)](LICENSE)
[快速开始](#快速开始) · [核心能力](#核心能力) · [生产部署](#生产部署) · [AI 网关](#ai-网关) · [开发](#开发) [界面预览](#界面预览) · [核心能力](#核心能力) · [快速开始](#快速开始) · [生产部署](#生产部署) · [AI 网关](#ai-网关) · [开发](#开发)
[AI 网关文档](docs/AI网关.md) · [OpenAPI](docs/swagger.yaml) · [更新日志](CHANGELOG.md) · [前端仓库](https://github.com/wangdefaa/oci-portal-dash)
</div> </div>
@@ -21,27 +23,36 @@ OCI Portal 将多份 OCI API Key、云资源、自动化任务、审计事件和
## 界面预览 ## 界面预览
| 总览 | 登录 | <p align="center">
| --- | --- | <img src="docs/assets/screenshot-overview.png" width="960" alt="OCI Portal 总览">
| ![总览](docs/assets/screenshot-overview.png) | ![登录](docs/assets/screenshot-signin.png) | </p>
| 租户 | 任务 | <details>
| --- | --- | <summary><strong>展开更多界面截图</strong></summary>
| ![租户](docs/assets/screenshot-tenants.png) | ![任务](docs/assets/screenshot-tasks.png) |
| AI 网关 | 通知设置 | | 登录 | 租户 |
| --- | --- | | --- | --- |
| ![AI 网关](docs/assets/screenshot-ai-gateway.png) | ![通知设置](docs/assets/screenshot-settings-notify.png) | | ![登录](docs/assets/screenshot-signin.png) | ![租户](docs/assets/screenshot-tenants.png) |
| 任务 | AI 网关 |
| --- | --- |
| ![任务](docs/assets/screenshot-tasks.png) | ![AI 网关](docs/assets/screenshot-ai-gateway.png) |
| 通知设置 |
| --- |
| ![通知设置](docs/assets/screenshot-settings-notify.png) |
</details>
## 核心能力 ## 核心能力
- **租户与区域**:集中管理多份 OCI API Key,支持分组、批量测活、账户画像、订阅区域缓存与区域切换;私钥和口令使用 AES-256-GCM 加密落库 | 能力域 | 覆盖范围 |
- **计算、网络与存储**:实例创建与电源操作、公网 IP、IPv6、VNIC、串行控制台连接,VCN / 子网 / 安全列表,引导卷与块存储挂载,限额和成本查询 | --- | --- |
- **自动化任务**:抢机、租户测活、成本同步、AI 渠道探测四类 cron 任务,提供执行日志、重叠执行防护、熔断与结果通知 | **租户与云资源** | 多 OCI API Key、分组、批量测活、账户画像、订阅区域缓存与切换;实例创建与电源操作、VNIC、公网 IP、IPv6、VCN、安全列表、引导卷、块存储挂载、限额与成本查询 |
- **网页控制台**:浏览器内使用 xterm 串行终端noVNC,通过 OCI 控制台连接建立两跳 SSH 隧道 | **自动化与控制台** | 抢机、租户测活、成本同步、AI 渠道探测;执行日志、重叠防护、熔断与结果通知;xterm 串行终端noVNC OCI 控制台连接两跳 SSH 隧道 |
- **身份与审计**IAM 用户、MFA、API Key、密码策略、SAML 身份提供商、通知收件人多 Identity Domain 管理;OCI Audit 事件可经 Service Connector Hub 与 Notifications 回传,关键事件按类别通过「云端事件」通知推送 | **身份与审计** | IAM 用户、MFA、API Key、密码策略、SAML、通知收件人多 Identity Domain;通过 Service Connector Hub 与 Notifications 接收并分类推送 OCI Audit 事件 |
- **通知与安全**Telegram、Webhook、ntfy、Bark、SMTP 五类渠道JWT、bcrypt、TOTP、OIDC / GitHub 登录、登录锁定、IP 限速、会话撤销和系统操作审计 | **通知与安全** | Telegram、Webhook、ntfy、Bark、SMTPAES-256-GCM、JWT、bcrypt、TOTP、OIDC / GitHub 登录、登录锁定、IP 限速、会话撤销和操作审计 |
- **AI 网关**:提供 OpenAI Responses、Chat Completions、Embeddings 与 Anthropic Messages 兼容接口,支持渠道分组、加权路由、熔断探测、模型黑白名单、密钥管理和调用日志 | **AI 网关** | OpenAI Responses、Chat Completions、Embeddings 与 Anthropic Messages渠道分组、加权路由、熔断探测、模型治理、密钥管理和调用日志 |
## 运行形态 ## 运行形态
@@ -57,7 +68,9 @@ OCI Portal 将多份 OCI API Key、云资源、自动化任务、审计事件和
└─────────────────────────────────────────────────────────────────┘ └─────────────────────────────────────────────────────────────────┘
``` ```
默认推荐 SQLite 单实例部署。MySQL 和 PostgreSQL 适配仍属 experimental,不应据此推断服务支持多副本并发运行。 > [!NOTE]
> 默认推荐 SQLite 单实例部署。MySQL 和 PostgreSQL 适配仍属 experimental
> 不应据此推断服务支持多副本并发运行。
## 快速开始 ## 快速开始
@@ -65,6 +78,10 @@ OCI Portal 将多份 OCI API Key、云资源、自动化任务、审计事件和
前置条件:Docker、Docker Compose v2、OpenSSL。 前置条件:Docker、Docker Compose v2、OpenSSL。
> [!CAUTION]
> `.env` 中的 `DATA_KEY` 用于解密 OCI 私钥、口令和渠道凭据。首次生成后必须长期
> 保存;升级或重装时不要覆盖,否则已有密文将无法恢复。
1. 克隆仓库并生成一份需要长期保存的 `.env` 1. 克隆仓库并生成一份需要长期保存的 `.env`
```bash ```bash
@@ -79,19 +96,27 @@ OCI Portal 将多份 OCI API Key、云资源、自动化任务、审计事件和
chmod 600 .env chmod 600 .env
``` ```
2. 准备数据目录并启动 2. 准备数据目录:
```bash ```bash
mkdir -p data mkdir -p data
```
# Linux bind mount 需要让镜像内的 nonroot 用户(uid 65532)可写 Linux 使用 bind mount 时,需要让镜像内的 nonroot 用户(uid `65532`)可写
Docker Desktop 用户通常不需要执行:
```bash
sudo chown 65532:65532 data sudo chown 65532:65532 data
```
3. 启动并检查状态:
```bash
docker compose up -d docker compose up -d
docker compose ps docker compose ps
``` ```
3. 查看初始管理员密码并登录: 4. 查看初始管理员密码并登录:
```bash ```bash
grep '^ADMIN_PASSWORD=' .env grep '^ADMIN_PASSWORD=' .env
@@ -99,7 +124,14 @@ OCI Portal 将多份 OCI API Key、云资源、自动化任务、审计事件和
访问 `http://127.0.0.1:18888`,默认用户名为 `admin`。 访问 `http://127.0.0.1:18888`,默认用户名为 `admin`。
> `DATA_KEY` 用于解密数据库中的 OCI 私钥、口令和渠道凭据。它只能生成一次并持续复用;丢失或更换后,已有密文无法恢复。请将 `.env` 与 `data/oci-portal.db` 一起备份。 启动异常时查看最近日志:
```bash
docker compose logs --tail=100 oci-portal
```
> [!TIP]
> 请将 `.env` 与 `data/oci-portal.db` 成对备份;恢复时两者必须匹配。
### 二进制运行 ### 二进制运行
@@ -107,6 +139,8 @@ Release 提供 Linux amd64 / arm64 二进制。以下以 amd64 为例;arm64
```bash ```bash
curl -fLO https://github.com/wangdefaa/oci-portal/releases/latest/download/oci-portal-server-linux-amd64 curl -fLO https://github.com/wangdefaa/oci-portal/releases/latest/download/oci-portal-server-linux-amd64
curl -fLO https://github.com/wangdefaa/oci-portal/releases/latest/download/SHA256SUMS
grep 'oci-portal-server-linux-amd64$' SHA256SUMS | sha256sum -c -
chmod +x oci-portal-server-linux-amd64 chmod +x oci-portal-server-linux-amd64
# 复用上文生成并妥善保存的 .env。 # 复用上文生成并妥善保存的 .env。
@@ -142,7 +176,9 @@ CGO_ENABLED=0 go build -trimpath -o bin/oci-portal-server ./cmd/server
## 生产部署 ## 生产部署
服务本身只提供 HTTP。除本机试用外,应保持服务仅监听回环地址或容器内部网络,并由 TLS 反向代理提供 HTTPS;否则管理员密码、JWT、AI 密钥和租户凭据会经明文连接传输。 > [!WARNING]
> 服务本身只提供 HTTP。除本机试用外,应仅监听回环地址或容器内部网络,并由
> TLS 反向代理提供 HTTPS;否则管理员密码、JWT、AI 密钥和租户凭据会经明文传输。
### Caddy ### Caddy
@@ -229,29 +265,31 @@ OpenAPI 文件随仓库维护:[`docs/swagger.yaml`](docs/swagger.yaml) · [`do
### 环境变量 ### 环境变量
| 变量 | 必填 | 默认值 | 说明 | | 变量 | 使用条件 | 默认值 | 说明 |
| --- | :---: | --- | --- | | --- | :---: | --- | --- |
| `DATA_KEY` | | — | 敏感字段加密主密钥;必须持久保存,不能随意轮换 | | `DATA_KEY` | 必填 | — | 敏感字段加密主密钥;必须持久保存,不能随意轮换 |
| `JWT_SECRET` | | — | JWT 签名密钥;更换会使已有登录令牌失效 | | `JWT_SECRET` | 必填 | — | JWT 签名密钥;更换会使已有登录令牌失效 |
| `ADMIN_USERNAME` | | `admin` | 初始管理员用户名 | | `ADMIN_USERNAME` | 可选 | `admin` | 初始管理员用户名 |
| `ADMIN_PASSWORD` | 首次启动 | — | 仅在数据库无用户时创建管理员,不会重置已有密码 | | `ADMIN_PASSWORD` | 首次启动 | — | 仅在数据库无用户时创建管理员,不会重置已有密码 |
| `ADDR` | | `:8080` | HTTP 监听地址 | | `ADDR` | 可选 | `:8080` | HTTP 监听地址 |
| `DB_DRIVER` | | `sqlite` | `sqlite` / `mysql` / `postgres`;后两者为 experimental | | `DB_DRIVER` | 可选 | `sqlite` | `sqlite` / `mysql` / `postgres`;后两者为 experimental |
| `DB_PATH` | SQLite | `oci-portal.db` | SQLite 文件路径 | | `DB_PATH` | SQLite | `oci-portal.db` | SQLite 文件路径 |
| `DB_DSN` | 外部数据库 | — | MySQL 需 `parseTime=True`;不要在日志或文档中暴露凭据 | | `DB_DSN` | MySQL / PostgreSQL | — | MySQL 需 `parseTime=True`;不要在日志或文档中暴露凭据 |
| `PUBLIC_URL` | | — | 面板公网基址,作为 OAuth 回调和日志回传引导的回退值 | | `PUBLIC_URL` | 可选 | — | 面板公网基址,作为 OAuth 回调和日志回传引导的回退值 |
| `TZ` | | 系统时区 | cron 表达式的解释时区;容器示例使用 `Asia/Shanghai` | | `TZ` | 可选 | 系统时区 | cron 表达式的解释时区;容器示例使用 `Asia/Shanghai` |
| `HTTP_PROXY` / `HTTPS_PROXY` / `NO_PROXY` | | — | Go 标准出站代理变量;面板内显式代理配置优先用于对应业务 | | `HTTP_PROXY` / `HTTPS_PROXY` / `NO_PROXY` | 可选 | — | Go 标准出站代理变量;面板内显式代理配置优先用于对应业务 |
| `SWAGGER` | | 关闭 | 设为 `1` 时开放 Swagger UI | | `SWAGGER` | 可选 | 关闭 | 设为 `1` 时开放 Swagger UI |
| `GIN_MODE` | | `release` | `debug` / `release` | | `GIN_MODE` | 可选 | `release` | `debug` / `release` |
## 升级与备份 ## 升级与备份
- 升级前同时备份 `.env` 和数据库;SQLite Compose 部署的数据文件为 `data/oci-portal.db` 1. 阅读 [CHANGELOG](CHANGELOG.md),确认目标版本的行为变化。
- 保持 `DATA_KEY` 不变;只恢复数据库而没有原密钥,敏感字段无法解密 2. 备份 `.env` 和数据库。SQLite Compose 部署建议先停止服务,再复制
- 服务启动时会自动执行数据库迁移;跨版本升级前先阅读 [CHANGELOG](CHANGELOG.md) `data/oci-portal.db`,避免在线复制产生不一致快照。
- Compose 部署使用 `docker compose pull && docker compose up -d` 更新镜像 3. 保持原 `DATA_KEY` 不变;只有数据库而没有对应密钥时,敏感字段无法解密。
- OCI API Key 应遵循最小权限原则;生产环境保持 Swagger 关闭并限制管理面访问来源 4. Compose 部署执行 `docker compose pull`,再执行 `docker compose up -d`;服务启动时
会自动完成数据库迁移。
5. 生产环境保持 Swagger 关闭、限制管理面访问来源,并为 OCI API Key 配置最小权限。
## 开发 ## 开发
+116 -56
View File
@@ -1,23 +1,33 @@
<a id="top"></a>
<div align="center">
<img src="assets/logo.svg" width="88" alt="OCI Portal logo">
# AI 网关 # AI 网关
**将多路 OCI Generative AI 统一为 OpenAI、Anthropic 与 xAI 兼容接口**
![OpenAI](https://img.shields.io/badge/OpenAI-Responses%20%7C%20Chat-412991?logo=openai&logoColor=white)
![Anthropic](https://img.shields.io/badge/Anthropic-Messages-D97757?logo=anthropic&logoColor=white)
![xAI](https://img.shields.io/badge/xAI-TTS%20%7C%20Tools-111111?logo=x&logoColor=white)
![Streaming](https://img.shields.io/badge/Streaming-SSE-0F9D58)
[快速接入](#quick-start) · [端点一览](#endpoints) · [路由机制](#routing) · [Codex 接入](#codex) · [已知限制](#limitations) · [兼容矩阵](#compatibility)
</div>
> [!NOTE] > [!NOTE]
> OCI Portal AI 网关将多个 OCI GenAI 渠道统一为 OpenAI、Anthropic 与 xAI > 网关集中处理密钥鉴权、模型访问控制、渠道调度与协议适配。路径、参数和
> 兼容接口,并集中处理鉴权、模型访问控制、渠道调度和协议适配。 > 响应结构以 [Swagger YAML](swagger.yaml) 或运行时 Swagger UI 为准;协议差异、
> > 兼容改写和实测边界以本文为准。
> 路径、参数与响应结构以 [Swagger YAML](swagger.yaml) 或运行时 Swagger UI
> 为准;协议差异、兼容改写和实测边界以本文为准。
**兼容快照:2026-07-15** | 文档属性 | 当前值 |
| --- | --- |
## 快速导航 | 兼容快照 | **2026-07-16** |
| API 基址 | `/ai/v1` |
- [快速接入](#quick-start) | 首选对话协议 | OpenAI Responses |
- [端点一览](#endpoints) | 会话模式 | 无状态,客户端携带完整上下文 |
- [路由与全局行为](#routing)
- [Codex 接入](#codex)
- [已知限制](#limitations)
- [字段兼容矩阵](#compatibility)
- [实现索引](#implementation)
<a id="quick-start"></a> <a id="quick-start"></a>
@@ -25,19 +35,13 @@
### 基础地址与鉴权 ### 基础地址与鉴权
```text 网关密钥在管理面板中创建。连接信息如下:
Base URL: https://<网关地址>/ai/v1
```
网关密钥在管理面板中创建,支持以下任一请求头: | 项目 | 配置 |
| --- | --- |
```http | Base URL | `https://<网关地址>/ai/v1` |
Authorization: Bearer sk-... | Bearer 鉴权 | `Authorization: Bearer sk-...` |
``` | API Key 鉴权 | `x-api-key: sk-...` |
```http
x-api-key: sk-...
```
密钥可绑定渠道分组和模型白名单。全局模型黑名单会同时作用于模型列表、 密钥可绑定渠道分组和模型白名单。全局模型黑名单会同时作用于模型列表、
请求路由和探测候选;开启「过滤弃用模型」后,OCI 已宣布弃用的模型也会从 请求路由和探测候选;开启「过滤弃用模型」后,OCI 已宣布弃用的模型也会从
@@ -50,6 +54,15 @@ curl "https://<网关地址>/ai/v1/models" \
-H "Authorization: Bearer $OCI_PORTAL_KEY" -H "Authorization: Bearer $OCI_PORTAL_KEY"
``` ```
再发起一条最小 Responses 请求;请将示例模型替换为模型列表中的可见模型:
```bash
curl "https://<网关地址>/ai/v1/responses" \
-H "Authorization: Bearer $OCI_PORTAL_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"xai.grok-4.3","input":"你好,请用一句话介绍自己。"}'
```
<a id="endpoints"></a> <a id="endpoints"></a>
## 端点一览 ## 端点一览
@@ -66,12 +79,14 @@ curl "https://<网关地址>/ai/v1/models" \
| 安全 | `POST /ai/v1/moderations` | OpenAI 外壳映射 OCI Guardrails | — | | 安全 | `POST /ai/v1/moderations` | OpenAI 外壳映射 OCI Guardrails | — |
| 发现 | `GET /ai/v1/models` | 当前密钥可见模型列表 | — | | 发现 | `GET /ai/v1/models` | 当前密钥可见模型列表 | — |
选择建议: ### 如何选择协议
- 新客户端优先使用 **Responses** | 使用场景 | 推荐接口 |
- Anthropic SDK 或 Claude 生态客户端使用 **Messages** | --- | --- |
- 仅支持旧 OpenAI 对话协议的客户端使用 **Chat Completions** | 新客户端、推理模型、服务端工具 | **Responses** |
- Embeddings、TTS、Rerank 与 Moderations 使用各自专用端点 | Anthropic SDK、Claude 生态客户端 | **Messages** |
| 仅支持旧 OpenAI 对话协议的客户端 | **Chat Completions** |
| 向量、语音、重排与安全审核 | 对应专用端点 |
<a id="routing"></a> <a id="routing"></a>
@@ -106,6 +121,14 @@ flowchart LR
| 文件输入 | `input_file` 会被 OCI ZDR 形态拒绝,详见[已知限制](#limitations) | | 文件输入 | `input_file` 会被 OCI ZDR 形态拒绝,详见[已知限制](#limitations) |
| Chat 定位 | Chat Completions 只承担协议转换与兼容修复;新能力优先落在 Responses 与 Messages | | Chat 定位 | Chat Completions 只承担协议转换与兼容修复;新能力优先落在 Responses 与 Messages |
### 可配置运行策略
| 策略 | 默认行为 | 配置入口 |
| --- | --- | --- |
| Responses 流式保险丝 | 开启;阈值 `60 KB`,按 `instructions``tools` 两个字段的原始 JSON 值计算 | **设置 → AI → 流式保险丝** |
| Grok 服务端搜索 | 对 `xai.` 模型默认注入 `web_search``x_search`;同名工具不重复覆盖 | **设置 → AI → Grok 服务端搜索工具** |
| 弃用模型过滤 | 开启后从模型列表、请求路由与探测候选中统一排除 | **设置 → AI → 模型治理** |
<a id="codex"></a> <a id="codex"></a>
## Codex 接入 ## Codex 接入
@@ -143,7 +166,7 @@ model = "xai.grok-4.3"
``` ```
> [!IMPORTANT] > [!IMPORTANT]
> `codex-cli 0.144.1` 已实测主会话和 multi-agent 子代理全链路可用。 > `Codex CLI 0.144.1` 已实测主会话和 multi-agent 子代理全链路可用。
> 内置 worker 可能先尝试 `gpt-5.6-luna` 或 `gpt-5.4`;网关没有对应渠道时 > 内置 worker 可能先尝试 `gpt-5.6-luna` 或 `gpt-5.4`;网关没有对应渠道时
> 会出现少量 404,随后由 Codex 回落到可用模型。自定义 agent 的模型覆盖是否 > 会出现少量 404,随后由 Codex 回落到可用模型。自定义 agent 的模型覆盖是否
> 直接生效取决于 Codex 版本;0.144.1 的实测主要依赖自动回落。 > 直接生效取决于 Codex 版本;0.144.1 的实测主要依赖自动回落。
@@ -165,22 +188,35 @@ Codex 工具兼容现状:
## 已知限制 ## 已知限制
### 超大流式请求 ### `instructions` / `tools` 大体量流式断流
> [!WARNING] > [!WARNING]
> 2026-07 实测:OCI xAI 兼容面对**完整请求体超过约 82 KB** 的流式请求 > `instructions` 与 `tools` 两个字段的原始 JSON 值合计超过约 **64.5 KB**
> 可能在推理阶段提前断流;该现象由请求体积触发,与工具构成或字符集无关, > 上游流式请求可能在推理阶段静默断连:连接直接 EOF,不发送 `error` 或终态事件。
> 同一请求改为非流式可正常完成。 > 同一请求改为非流式实测可正常完成;单独扩大 `input` 未触发该限制
网关按协议采取不同保护 该问题于 2026-07-13 通过字节级二分定位,2026-07-16 在 Chicago 复测仍存在
`70.4 KB` 断流、`59.7 KB` 正常,API Key 与签名鉴权表现一致。本文及设置页中的
`KB` 均按 `1024 B` 计算。
- **Responses**:兼容改写后的请求体超过 **76 KB** 时,预防性改为非流式上游 | 协议 | 网关保护 | 客户端表现 |
请求,再合成最小 SSE 序列(`response.created` | --- | --- | --- |
`response.output_item.done``response.completed`);结果语义保留,但没有增量输出 | Responses | 保险丝默认开启;超过 `60 KB` 时改走非流式上游,并合成最小 SSE 序列 | 结果语义保留,但不再增量输出 |
- **Chat Completions / Messages**:若上游在客户端收到任何内容断流,自动 | Chat Completions / Messages | 客户端尚未收到内容断流,自动改用非流式重做 | 合成对应 chunk / event 序列 |
非流式重做,并合成对应 chunk / event 序列 | 已开始输出的流 | 无法透明重试;调用日志记录提前终止 | 客户端可能只收到部分事件 |
- **已开始输出的流**:不能透明重试;调用日志会记录提前终止,客户端可能只拿到
部分事件 Responses 合成的最小事件序列为:`response.created`
`response.output_item.done``response.completed`。保险丝可在
**设置 → AI → 流式保险丝** 调整或关闭。
<details>
<summary><strong>历史问题:完整请求体体积断流(已由上游修复)</strong></summary>
2026-07-15 曾在完整请求体约 `82 KB`(含 `input`)时观测到纯体积流式断流。
2026-07-16 复核 `83 KB`、真实 Codex 形态 `104.5 KB``200 KB``400 KB`
请求均正常完成;Chicago 与 Phoenix、签名与 API Key 两条路径结果一致。
</details>
### ZDR 与文件输入 ### ZDR 与文件输入
@@ -192,16 +228,20 @@ File content is currently unsupported for ZDR customers
网关强制 `store:false`,属于 ZDR 请求形态,因此当前不能通过该端点上传或引用文件。 网关强制 `store:false`,属于 ZDR 请求形态,因此当前不能通过该端点上传或引用文件。
### 规格与实测边界
本项目提供的是兼容接口,而不是 OpenAI、Anthropic 或 xAI 协议的完整实现。
部分 OCI OpenAI 兼容行为来自实测,未见 Oracle 文档合同,可能随上游调整。
<a id="compatibility"></a> <a id="compatibility"></a>
## 字段兼容矩阵 ## 字段兼容矩阵
矩阵基线: > [!IMPORTANT]
> 本项目提供兼容接口,而不是 OpenAI、Anthropic 或 xAI 协议的完整实现。部分 OCI
> OpenAI 兼容行为来自实测,未见 Oracle 文档合同,可能随上游调整。
### 矩阵索引
[Responses](#compat-responses) · [Chat Completions](#compat-chat) · [Messages](#compat-messages) · [Embeddings](#compat-embeddings) · [语音生成](#compat-audio) · [Rerank](#compat-rerank) · [Moderations](#compat-moderations) · [Models](#compat-models)
<details>
<summary><strong>展开参考规格</strong></summary>
- [OpenAI Responses](https://developers.openai.com/api/reference/resources/responses/methods/create) - [OpenAI Responses](https://developers.openai.com/api/reference/resources/responses/methods/create)
- [OpenAI Chat Completions](https://developers.openai.com/api/reference/resources/chat/subresources/completions/methods/create) - [OpenAI Chat Completions](https://developers.openai.com/api/reference/resources/chat/subresources/completions/methods/create)
@@ -211,6 +251,8 @@ File content is currently unsupported for ZDR customers
- [Anthropic Messages](https://platform.claude.com/docs/en/api/messages/create) - [Anthropic Messages](https://platform.claude.com/docs/en/api/messages/create)
- [Cohere Rerank](https://docs.cohere.com/reference/rerank) - [Cohere Rerank](https://docs.cohere.com/reference/rerank)
</details>
| 标记 | 含义 | | 标记 | 含义 |
| :---: | --- | | :---: | --- |
| ✅ | 网关直接支持 | | ✅ | 网关直接支持 |
@@ -218,6 +260,8 @@ File content is currently unsupported for ZDR customers
| 🔄 | 网关执行字段或协议转换后支持 | | 🔄 | 网关执行字段或协议转换后支持 |
| ◐ | 部分支持、存在前置条件或语义降级 | | ◐ | 部分支持、存在前置条件或语义降级 |
<a id="compat-responses"></a>
### OpenAI Responses ### OpenAI Responses
**`POST /ai/v1/responses` · 无状态主接口** **`POST /ai/v1/responses` · 无状态主接口**
@@ -239,12 +283,12 @@ Codex 工具兼容改写。请求会重新编码,不承诺字节级原样转
| `reasoning` | ➡️ | 整个对象保留;`effort` 不校验档位 | | `reasoning` | ➡️ | 整个对象保留;`effort` 不校验档位 |
| `tool_choice` | ◐ | 普通形态保留;namespace 对象会重限定,全部工具被剥离时删除 | | `tool_choice` | ◐ | 普通形态保留;namespace 对象会重限定,全部工具被剥离时删除 |
| `store` | 🔄 | 无论客户端传什么,上游请求都强制改写为 `false` | | `store` | 🔄 | 无论客户端传什么,上游请求都强制改写为 `false` |
| `stream` | ◐ | 支持 SSE请求体超过 76 KB 时改为非流式上游并合成 SSE | | `stream` | ◐ | 支持 SSE`instructions``tools` 的原始 JSON 值合计超过保险丝阈值时,预防性改走非流式上游(默认开启、`60 KB`,见[已知限制](#limitations) |
| 其余标准与未知顶层字段 | ➡️ | `context_management``include``metadata``prompt``prompt_cache_key``service_tier``truncation``user` 等均保留,由 OCI 决定是否接受 | | 其余标准与未知顶层字段 | ➡️ | `context_management``include``metadata``prompt``prompt_cache_key``service_tier``truncation``user` 等均保留,由 OCI 决定是否接受 |
</details> </details>
<details open> <details>
<summary><strong>工具兼容</strong></summary> <summary><strong>工具兼容</strong></summary>
| 工具或参数 | 状态 | 网关行为 | | 工具或参数 | 状态 | 网关行为 |
@@ -254,7 +298,7 @@ Codex 工具兼容改写。请求会重新编码,不承诺字节级原样转
| `tools[].type=mcp` | ➡️ | 远程 MCP 由上游直连,`server_url``require_approval``authorization` 等保留 | | `tools[].type=mcp` | ➡️ | 远程 MCP 由上游直连,`server_url``require_approval``authorization` 等保留 |
| `tools[].type=namespace` | 🔄 | 子 `function` 上提并限定命名;响应、历史调用和 `tool_choice` 会反向还原;组内非 `function` 子工具被剥离 | | `tools[].type=namespace` | 🔄 | 子 `function` 上提并限定命名;响应、历史调用和 `tool_choice` 会反向还原;组内非 `function` 子工具被剥离 |
| `tools[].type=custom` | ◐ | 顶层非 `apply_patch` 工具转为带 `input` schema 的 `function`;响应与多轮历史双向回转;`format` 会删除 | | `tools[].type=custom` | ◐ | 顶层非 `apply_patch` 工具转为带 `input` schema 的 `function`;响应与多轮历史双向回转;`format` 会删除 |
| `custom:apply_patch` | ◐ | 整体剥离;grok 系未针对 Codex 补丁格式训练,模型应回落其他编辑方式 | | `custom:apply_patch` | ◐ | 整体剥离;Grok 系未针对 Codex 补丁格式训练,模型应回落其他编辑方式 |
| `tools[].type=tool_search` | ◐ | 请求可被接受,但工具本身直接剥离 | | `tools[].type=tool_search` | ◐ | 请求可被接受,但工具本身直接剥离 |
| `web_search.external_web_access=true` | 🔄 | 删除上游不识别的字段,保留 `web_search` | | `web_search.external_web_access=true` | 🔄 | 删除上游不识别的字段,保留 `web_search` |
| `web_search.external_web_access=false` | ◐ | 上游没有“仅缓存检索”对应能力,按不越权原则剥离整个工具 | | `web_search.external_web_access=false` | ◐ | 上游没有“仅缓存检索”对应能力,按不越权原则剥离整个工具 |
@@ -277,6 +321,8 @@ Codex 工具兼容改写。请求会重新编码,不承诺字节级原样转
- 未知模型返回 404,无可用渠道返回 503 - 未知模型返回 404,无可用渠道返回 503
- 上游错误使用 OpenAI 风格错误外壳,但不保证字段与标准 OpenAI 完全一致 - 上游错误使用 OpenAI 风格错误外壳,但不保证字段与标准 OpenAI 完全一致
<a id="compat-chat"></a>
### OpenAI Chat Completions ### OpenAI Chat Completions
**`POST /ai/v1/chat/completions` · 存量客户端兼容层** **`POST /ai/v1/chat/completions` · 存量客户端兼容层**
@@ -332,6 +378,8 @@ Codex 工具兼容改写。请求会重新编码,不承诺字节级原样转
`prompt_tokens_details.cached_tokens` `prompt_tokens_details.cached_tokens`
- 已开始输出的流中断不会转换成标准 SSE 错误事件 - 已开始输出的流中断不会转换成标准 SSE 错误事件
<a id="compat-messages"></a>
### Anthropic Messages ### Anthropic Messages
**`POST /ai/v1/messages` · Anthropic 协议转换层** **`POST /ai/v1/messages` · Anthropic 协议转换层**
@@ -385,6 +433,8 @@ Codex 工具兼容改写。请求会重新编码,不承诺字节级原样转
- usage 只保留 `input_tokens``output_tokens``cache_read_input_tokens` - usage 只保留 `input_tokens``output_tokens``cache_read_input_tokens`
- 流式上游错误与无终态断流会转成 Anthropic `error` 事件 - 流式上游错误与无终态断流会转成 Anthropic `error` 事件
<a id="compat-embeddings"></a>
### OpenAI Embeddings ### OpenAI Embeddings
**`POST /ai/v1/embeddings` · 向量化专用端点** **`POST /ai/v1/embeddings` · 向量化专用端点**
@@ -402,6 +452,8 @@ Codex 工具兼容改写。请求会重新编码,不承诺字节级原样转
响应使用标准 `object:"list"` 外壳,向量为 `float32` 数组,不支持流式。 响应使用标准 `object:"list"` 外壳,向量为 `float32` 数组,不支持流式。
<a id="compat-audio"></a>
### 语音生成 ### 语音生成
两个端点最终使用同一 OCI xAI TTS 上游与渠道调度: 两个端点最终使用同一 OCI xAI TTS 上游与渠道调度:
@@ -442,6 +494,8 @@ Codex 工具兼容改写。请求会重新编码,不承诺字节级原样转
- 不提供 HTTP 流式音频或 WebSocket 代理 - 不提供 HTTP 流式音频或 WebSocket 代理
- 无 token 用量口径,调用日志只记时延与渠道 - 无 token 用量口径,调用日志只记时延与渠道
<a id="compat-rerank"></a>
### Rerank ### Rerank
**`POST /ai/v1/rerank` · Cohere / Jina 风格协议** **`POST /ai/v1/rerank` · Cohere / Jina 风格协议**
@@ -459,6 +513,8 @@ Codex 工具兼容改写。请求会重新编码,不承诺字节级原样转
`max_tokens_per_doc` 与未知字段静默忽略。响应按相关度降序, `max_tokens_per_doc` 与未知字段静默忽略。响应按相关度降序,
`results[].index` 指向输入下标,`relevance_score` 为 0~1 浮点;无 token 用量口径。 `results[].index` 指向输入下标,`relevance_score` 为 0~1 浮点;无 token 用量口径。
<a id="compat-moderations"></a>
### Moderations ### Moderations
**`POST /ai/v1/moderations` · OpenAI 外壳映射 OCI Guardrails** **`POST /ai/v1/moderations` · OpenAI 外壳映射 OCI Guardrails**
@@ -480,6 +536,8 @@ Codex 工具兼容改写。请求会重新编码,不承诺字节级原样转
- 实测中文人名与手机号识别较弱,英文 PII 识别正常 - 实测中文人名与手机号识别较弱,英文 PII 识别正常
- 响应 `model` 恒为 `oci-guardrails` - 响应 `model` 恒为 `oci-guardrails`
<a id="compat-models"></a>
### Models ### Models
**`GET /ai/v1/models` · 当前密钥可见模型列表** **`GET /ai/v1/models` · 当前密钥可见模型列表**
@@ -496,7 +554,7 @@ Codex 工具兼容改写。请求会重新编码,不承诺字节级原样转
<a id="implementation"></a> <a id="implementation"></a>
## 实现索引 ## 附录:实现索引
| 端点 / 能力 | 主要实现 | | 端点 / 能力 | 主要实现 |
| --- | --- | | --- | --- |
@@ -510,3 +568,5 @@ Codex 工具兼容改写。请求会重新编码,不承诺字节级原样转
本文是兼容性快照,不替代 Swagger。标准接口、Codex 客户端与 OCI 上游均可能 本文是兼容性快照,不替代 Swagger。标准接口、Codex 客户端与 OCI 上游均可能
变化,最终行为以当前版本代码、运行时 Swagger 和实测结果为准。 变化,最终行为以当前版本代码、运行时 Swagger 和实测结果为准。
[返回顶部](#top)
+68 -1
View File
@@ -786,6 +786,27 @@ const docTemplate = `{
} }
} }
}, },
"/api/v1/ai-model-catalog": {
"get": {
"security": [
{
"BearerAuth": []
}
],
"tags": [
"AI 管理"
],
"summary": "聚合模型目录",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/internal_api.itemsResponse-oci-portal_internal_service_AggregatedModel"
}
}
}
}
},
"/api/v1/ai-models": { "/api/v1/ai-models": {
"get": { "get": {
"security": [ "security": [
@@ -839,7 +860,7 @@ const docTemplate = `{
"summary": "更新 AI 网关全局设置", "summary": "更新 AI 网关全局设置",
"parameters": [ "parameters": [
{ {
"description": "开启后已宣布弃用(即使未退役)的模型从列表与路由中排除", "description": "全量提交;保险丝阈值限 1..1024 KB",
"name": "body", "name": "body",
"in": "body", "in": "body",
"required": true, "required": true,
@@ -854,6 +875,15 @@ const docTemplate = `{
"schema": { "schema": {
"$ref": "#/definitions/internal_api.aiSettingsResponse" "$ref": "#/definitions/internal_api.aiSettingsResponse"
} }
},
"400": {
"description": "Bad Request",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
} }
} }
} }
@@ -6050,7 +6080,22 @@ const docTemplate = `{
"type": "object", "type": "object",
"properties": { "properties": {
"filterDeprecated": { "filterDeprecated": {
"description": "FilterDeprecated 开启后已宣布弃用(即使未退役)的模型从列表与路由中排除",
"type": "boolean" "type": "boolean"
},
"grokWebSearch": {
"description": "GrokWebSearch / GrokXSearch 是 xai. 模型服务端搜索工具默认注入开关;\n请求 tools 已包含同名工具时不覆盖",
"type": "boolean"
},
"grokXSearch": {
"type": "boolean"
},
"streamGuardEnabled": {
"description": "StreamGuardEnabled / StreamGuardKB 是 Responses 流式保险丝:\ninstructions+tools 合计超阈值(KB)的流式请求改非流式上游并合成 SSE",
"type": "boolean"
},
"streamGuardKB": {
"type": "integer"
} }
} }
}, },
@@ -6621,6 +6666,17 @@ const docTemplate = `{
} }
} }
}, },
"internal_api.itemsResponse-oci-portal_internal_service_AggregatedModel": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/oci-portal_internal_service.AggregatedModel"
}
}
}
},
"internal_api.itemsResponse-oci-portal_internal_service_NotifyChannelView": { "internal_api.itemsResponse-oci-portal_internal_service_NotifyChannelView": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -9358,6 +9414,17 @@ const docTemplate = `{
} }
} }
}, },
"oci-portal_internal_service.AggregatedModel": {
"type": "object",
"properties": {
"capability": {
"type": "string"
},
"name": {
"type": "string"
}
}
},
"oci-portal_internal_service.AuditEventsView": { "oci-portal_internal_service.AuditEventsView": {
"type": "object", "type": "object",
"properties": { "properties": {
+68 -1
View File
@@ -779,6 +779,27 @@
} }
} }
}, },
"/api/v1/ai-model-catalog": {
"get": {
"security": [
{
"BearerAuth": []
}
],
"tags": [
"AI 管理"
],
"summary": "聚合模型目录",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/internal_api.itemsResponse-oci-portal_internal_service_AggregatedModel"
}
}
}
}
},
"/api/v1/ai-models": { "/api/v1/ai-models": {
"get": { "get": {
"security": [ "security": [
@@ -832,7 +853,7 @@
"summary": "更新 AI 网关全局设置", "summary": "更新 AI 网关全局设置",
"parameters": [ "parameters": [
{ {
"description": "开启后已宣布弃用(即使未退役)的模型从列表与路由中排除", "description": "全量提交;保险丝阈值限 1..1024 KB",
"name": "body", "name": "body",
"in": "body", "in": "body",
"required": true, "required": true,
@@ -847,6 +868,15 @@
"schema": { "schema": {
"$ref": "#/definitions/internal_api.aiSettingsResponse" "$ref": "#/definitions/internal_api.aiSettingsResponse"
} }
},
"400": {
"description": "Bad Request",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
} }
} }
} }
@@ -6043,7 +6073,22 @@
"type": "object", "type": "object",
"properties": { "properties": {
"filterDeprecated": { "filterDeprecated": {
"description": "FilterDeprecated 开启后已宣布弃用(即使未退役)的模型从列表与路由中排除",
"type": "boolean" "type": "boolean"
},
"grokWebSearch": {
"description": "GrokWebSearch / GrokXSearch 是 xai. 模型服务端搜索工具默认注入开关;\n请求 tools 已包含同名工具时不覆盖",
"type": "boolean"
},
"grokXSearch": {
"type": "boolean"
},
"streamGuardEnabled": {
"description": "StreamGuardEnabled / StreamGuardKB 是 Responses 流式保险丝:\ninstructions+tools 合计超阈值(KB)的流式请求改非流式上游并合成 SSE",
"type": "boolean"
},
"streamGuardKB": {
"type": "integer"
} }
} }
}, },
@@ -6614,6 +6659,17 @@
} }
} }
}, },
"internal_api.itemsResponse-oci-portal_internal_service_AggregatedModel": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/oci-portal_internal_service.AggregatedModel"
}
}
}
},
"internal_api.itemsResponse-oci-portal_internal_service_NotifyChannelView": { "internal_api.itemsResponse-oci-portal_internal_service_NotifyChannelView": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -9351,6 +9407,17 @@
} }
} }
}, },
"oci-portal_internal_service.AggregatedModel": {
"type": "object",
"properties": {
"capability": {
"type": "string"
},
"name": {
"type": "string"
}
}
},
"oci-portal_internal_service.AuditEventsView": { "oci-portal_internal_service.AuditEventsView": {
"type": "object", "type": "object",
"properties": { "properties": {
+48 -1
View File
@@ -49,7 +49,22 @@ definitions:
internal_api.aiSettingsResponse: internal_api.aiSettingsResponse:
properties: properties:
filterDeprecated: filterDeprecated:
description: FilterDeprecated 开启后已宣布弃用(即使未退役)的模型从列表与路由中排除
type: boolean type: boolean
grokWebSearch:
description: |-
GrokWebSearch / GrokXSearch 是 xai. 模型服务端搜索工具默认注入开关;
请求 tools 已包含同名工具时不覆盖
type: boolean
grokXSearch:
type: boolean
streamGuardEnabled:
description: |-
StreamGuardEnabled / StreamGuardKB 是 Responses 流式保险丝:
instructions+tools 合计超阈值(KB)的流式请求改非流式上游并合成 SSE
type: boolean
streamGuardKB:
type: integer
type: object type: object
internal_api.attachBootVolumeRequest: internal_api.attachBootVolumeRequest:
properties: properties:
@@ -427,6 +442,13 @@ definitions:
$ref: '#/definitions/oci-portal_internal_model.UserIdentity' $ref: '#/definitions/oci-portal_internal_model.UserIdentity'
type: array type: array
type: object type: object
internal_api.itemsResponse-oci-portal_internal_service_AggregatedModel:
properties:
items:
items:
$ref: '#/definitions/oci-portal_internal_service.AggregatedModel'
type: array
type: object
internal_api.itemsResponse-oci-portal_internal_service_NotifyChannelView: internal_api.itemsResponse-oci-portal_internal_service_NotifyChannelView:
properties: properties:
items: items:
@@ -2232,6 +2254,13 @@ definitions:
description: 卷本身的名称(attachment 名是自动生成的) description: 卷本身的名称(attachment 名是自动生成的)
type: string type: string
type: object type: object
oci-portal_internal_service.AggregatedModel:
properties:
capability:
type: string
name:
type: string
type: object
oci-portal_internal_service.AuditEventsView: oci-portal_internal_service.AuditEventsView:
properties: properties:
cursor: cursor:
@@ -3135,6 +3164,18 @@ paths:
summary: AI 调用日志列表 summary: AI 调用日志列表
tags: tags:
- AI 管理 - AI 管理
/api/v1/ai-model-catalog:
get:
responses:
"200":
description: OK
schema:
$ref: '#/definitions/internal_api.itemsResponse-oci-portal_internal_service_AggregatedModel'
security:
- BearerAuth: []
summary: 聚合模型目录
tags:
- AI 管理
/api/v1/ai-models: /api/v1/ai-models:
get: get:
responses: responses:
@@ -3161,7 +3202,7 @@ paths:
- AI 管理 - AI 管理
put: put:
parameters: parameters:
- description: 开启后已宣布弃用(即使未退役)的模型从列表与路由中排除 - description: 全量提交;保险丝阈值限 1..1024 KB
in: body in: body
name: body name: body
required: true required: true
@@ -3172,6 +3213,12 @@ paths:
description: OK description: OK
schema: schema:
$ref: '#/definitions/internal_api.aiSettingsResponse' $ref: '#/definitions/internal_api.aiSettingsResponse'
"400":
description: Bad Request
schema:
additionalProperties:
type: string
type: object
security: security:
- BearerAuth: [] - BearerAuth: []
summary: 更新 AI 网关全局设置 summary: 更新 AI 网关全局设置
+1 -1
View File
@@ -8,7 +8,7 @@ require (
github.com/glebarez/sqlite v1.11.0 github.com/glebarez/sqlite v1.11.0
github.com/golang-jwt/jwt/v5 v5.3.1 github.com/golang-jwt/jwt/v5 v5.3.1
github.com/gorilla/websocket v1.5.3 github.com/gorilla/websocket v1.5.3
github.com/oracle/oci-go-sdk/v65 v65.120.0 github.com/oracle/oci-go-sdk/v65 v65.121.0
github.com/pquerna/otp v1.5.0 github.com/pquerna/otp v1.5.0
github.com/robfig/cron/v3 v3.0.1 github.com/robfig/cron/v3 v3.0.1
github.com/swaggo/files v1.0.1 github.com/swaggo/files v1.0.1
+2 -102
View File
@@ -1,40 +1,24 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/purell v1.2.2 h1:irlAloIzZaJ5RP/+UcaT1Nw0H9on2HKWdRehCsbJWJw=
github.com/PuerkitoBio/purell v1.2.2/go.mod h1:ZwHcC/82TOaovDi//J/804umJFFmbOHPngi8iYYv/Eo=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI= github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/boombuler/barcode v1.1.0 h1:ChaYjBR63fr4LFyGn8E8nt7dBSt3MiU3zMOZqFvVkHo=
github.com/boombuler/barcode v1.1.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
github.com/bytedance/gopkg v0.1.4 h1:oZnQwnX82KAIWb7033bEwtxvTqXcYMxDBaQxo5JJHWM=
github.com/bytedance/gopkg v0.1.4/go.mod h1:v1zWfPm21Fb+OsyXN2VAHdL6TBb2L88anLQgdyje6R4=
github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE= github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE=
github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k= github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k=
github.com/bytedance/sonic v1.15.2 h1:90H+rcF/FwLXwfB1cudOLq/je83n683Utf4Cbp0xHCo=
github.com/bytedance/sonic v1.15.2/go.mod h1:mT2NbXunuaEbnZ+mRIX/vYqKISmgEuHFDI4UzmKx2SA=
github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE= github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE=
github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
github.com/bytedance/sonic/loader v0.5.1 h1:Ygpfa9zwRCCKSlrp5bBP/b/Xzc3VxsAW+5NIYXrOOpI=
github.com/bytedance/sonic/loader v0.5.1/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
github.com/cloudwego/base64x v0.1.7 h1:NppS+Fgzg5ovhn4NkUXaDT3x9jldgH5ToMCqzBSi2zI=
github.com/cloudwego/base64x v0.1.7/go.mod h1:Cu1PV9zfrSf7ET2tIbWbbEy7jO7HHJ13q4X2SQ8aWYg=
github.com/coreos/go-oidc/v3 v3.19.0 h1:F/xyOi3x1UnG1U27YVnM1N6bHiL1K2upi6U/0qr8r+I= github.com/coreos/go-oidc/v3 v3.19.0 h1:F/xyOi3x1UnG1U27YVnM1N6bHiL1K2upi6U/0qr8r+I=
github.com/coreos/go-oidc/v3 v3.19.0/go.mod h1:DYCf24+ncYi+XkIH97GY1+dqoRlbaSI26KVTCI9SrY4= github.com/coreos/go-oidc/v3 v3.19.0/go.mod h1:DYCf24+ncYi+XkIH97GY1+dqoRlbaSI26KVTCI9SrY4=
github.com/coreos/go-oidc/v3 v3.20.0 h1:EtE0WIBHk03N+DqGkY4+UONzzZHk7amKt6IyNd7OsZE=
github.com/coreos/go-oidc/v3 v3.20.0/go.mod h1:DYCf24+ncYi+XkIH97GY1+dqoRlbaSI26KVTCI9SrY4=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
@@ -45,20 +29,14 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw= github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw=
github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM=
github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4= github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk= github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
github.com/gin-contrib/sse v1.1.1 h1:uGYpNwTacv5R68bSGMapo62iLTRa9l5zxGCps4hK6ko=
github.com/gin-contrib/sse v1.1.1/go.mod h1:QXzuVkA0YO7o/gun03UI1Q+FTI8ZV/n5t03kIQAI89s=
github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8= github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8=
github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc= github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc=
github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo= github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k= github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ=
github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc=
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw= github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ= github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA= github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
@@ -66,35 +44,13 @@ github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v1.0.0 h1:kR9tHqY0CtZaOPVFm622dPVNhrvYpwr4uCxgL3h1H8s=
github.com/go-openapi/jsonpointer v1.0.0/go.mod h1:Z3rw7dWu1p9IgitXCFamSlA5lmDiklEB6vkaxcNZW5Y=
github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs= github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs=
github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=
github.com/go-openapi/jsonreference v1.0.0 h1:jlmTr6torcd1YgDQvSfNmRtKzYDO4FGBkrAdlAVWnpY=
github.com/go-openapi/jsonreference v1.0.0/go.mod h1:jtwdyGbJk0Xhe5Y+rwtglQP6Sb1WZST4rT32LWB+sv0=
github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M= github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M=
github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I= github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I=
github.com/go-openapi/spec v0.22.6 h1:Tyy1pLaNCM8GBCFLoGYLonjJi6zykqyLCjXLc19ZPic=
github.com/go-openapi/spec v0.22.6/go.mod h1:HZvTHat+iH0PALQRWhrqIHtU/PEqxqd89fu0MxGlMeM=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-openapi/swag v0.27.0 h1:8ecSuZlh4NXc3GsmAOqECIYqDTApCWaMe3gO4gjJNEE=
github.com/go-openapi/swag v0.27.0/go.mod h1:Kkgz9Ht0+ul9/aVdFmc9xSyPzUwf/aFF5KiFPBXfSY0=
github.com/go-openapi/swag/conv v0.27.0 h1:EKOH4feXrvdo8DbSsXSAqRT8fz1epEnS5O2IfXUOzE8=
github.com/go-openapi/swag/conv v0.27.0/go.mod h1:pfiv0uKQTbaGApk8Zs/lZV3uSjmSpa2FO1y183YngN8=
github.com/go-openapi/swag/jsonname v0.27.0 h1:4QVB//CKOdE8IOiBg19JNY2wfDS48MhesIquYBy2rUE=
github.com/go-openapi/swag/jsonname v0.27.0/go.mod h1:I1YsyvvhBuZsFXSW6I7ODfdyq13p7hDil//1T9/pFFk=
github.com/go-openapi/swag/jsonutils v0.27.0 h1:VYtd9jEQYeU4j8q5vdn5KWotF4vKywhGdMBrALtAsfE=
github.com/go-openapi/swag/jsonutils v0.27.0/go.mod h1:U7pb8AGuwhok3RDicHeHwSG4L3PXSq6PAL98Aon632g=
github.com/go-openapi/swag/loading v0.27.0 h1:s8DA9aPEdFH6OluHUYUn3DnIuoTdyWs9RwffXBUfyeI=
github.com/go-openapi/swag/loading v0.27.0/go.mod h1:VOz+Jg6UGGywcmRvYsI4fvtp+bd7NfioseGEPleYdA4=
github.com/go-openapi/swag/stringutils v0.27.0 h1:Of7w/HljWsNZvuxsUAnw3n+hCOyI6HLJOxW2kQRAxio=
github.com/go-openapi/swag/stringutils v0.27.0/go.mod h1:lzRN95CxXmA03XcDWHLOb6nOMcxCqR5rGY0lOgsfRoM=
github.com/go-openapi/swag/typeutils v0.27.0 h1:aCf4MSGo8NLwZP8Q6t32DWLJSvl/WwNqgmEG+xJ6v2o=
github.com/go-openapi/swag/typeutils v0.27.0/go.mod h1:Srm0xFNRZ1Y+vCxJclo5qzx8aj+1pAKda/YfFPrG0dQ=
github.com/go-openapi/swag/yamlutils v0.27.0 h1:bQ6eAMil5X9tdcf7dMn4t15alzG6jddnrKPuKa/zxKM=
github.com/go-openapi/swag/yamlutils v0.27.0/go.mod h1:yRfIo7qqVkmJRQjX8exjA3AfcI8rH1KDNPsTparoCv4=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
@@ -103,22 +59,14 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w= github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w=
github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM= github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM=
github.com/go-playground/validator/v10 v10.30.3 h1:4MU6YkEwx7GbcPJOZxrtbu+QfF3pJLJuaYTeAH0DYy8=
github.com/go-playground/validator/v10 v10.30.3/go.mod h1:4Axh7oCNGcoGkqLoE4YWt6n20mcEIsPRlB7vPk3lpyc=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/go-sql-driver/mysql v1.10.0 h1:Q+1LV8DkHJvSYAdR83XzuhDaTykuDx0l6fkXxoWCWfw=
github.com/go-sql-driver/mysql v1.10.0/go.mod h1:M+cqaI7+xxXGG9swrdeUIoPG3Y3KCkF0pZej+SK+nWk=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU=
github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/gofrs/flock v0.10.0 h1:SHMXenfaB03KbroETaCMtbBg3Yn29v4w1r+tgy4ff4k= github.com/gofrs/flock v0.10.0 h1:SHMXenfaB03KbroETaCMtbBg3Yn29v4w1r+tgy4ff4k=
github.com/gofrs/flock v0.10.0/go.mod h1:FirDy1Ing0mI2+kB6wk+vyyAH+e6xiE+EYA0jnzV9jc= github.com/gofrs/flock v0.10.0/go.mod h1:FirDy1Ing0mI2+kB6wk+vyyAH+e6xiE+EYA0jnzV9jc=
github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw=
github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0=
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
@@ -126,11 +74,8 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
@@ -139,8 +84,6 @@ github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7Ulw
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.9.2 h1:3ZhOzMWnR4yJ+RW1XImIPsD1aNSz4T4fyP7zlQb56hw= github.com/jackc/pgx/v5 v5.9.2 h1:3ZhOzMWnR4yJ+RW1XImIPsD1aNSz4T4fyP7zlQb56hw=
github.com/jackc/pgx/v5 v5.9.2/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= github.com/jackc/pgx/v5 v5.9.2/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
github.com/jackc/pgx/v5 v5.10.0 h1:VhSvgU2jSli8o3AqIEOTJr7rZwAEUVo4E4XhR94Zfr0=
github.com/jackc/pgx/v5 v5.10.0/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
@@ -153,8 +96,6 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/klauspost/cpuid/v2 v2.4.0 h1:S6Hrbc7+ywsr0r+RLapfGBHfyefhCTwEh3A0tV913Dw=
github.com/klauspost/cpuid/v2 v2.4.0/go.mod h1:19jmZ9mjzoF//ddRSUsv0zfBTJWh3QJh9FNxZTMrGxU=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
@@ -168,12 +109,8 @@ github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mailru/easyjson v0.9.2 h1:dX8U45hQsZpxd80nLvDGihsQ/OxlvTkVUXH2r/8cb2M=
github.com/mailru/easyjson v0.9.2/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4=
github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -181,15 +118,11 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/oracle/oci-go-sdk/v65 v65.120.0 h1:qpGdts2Yleg6TdmtxXkL8MAnsASO+SOG+iG/Nd8wUGk= github.com/oracle/oci-go-sdk/v65 v65.121.0 h1:1J+5ARgrodrx8kzFy/hxznaoUzz43jr0EestCzEaOHw=
github.com/oracle/oci-go-sdk/v65 v65.120.0/go.mod h1:Pzy+BpgkDesvGZXEHgslwhIYobHCPHg6wRta1mWnlqQ= github.com/oracle/oci-go-sdk/v65 v65.121.0/go.mod h1:Pzy+BpgkDesvGZXEHgslwhIYobHCPHg6wRta1mWnlqQ=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pelletier/go-toml/v2 v2.4.3 h1:GTRvJQutkOSftxIFD5xw9aepkYNuPWmVJpffdDPYVpY=
github.com/pelletier/go-toml/v2 v2.4.3/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs= github.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs=
@@ -198,8 +131,6 @@ github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
github.com/quic-go/quic-go v0.59.1 h1:0Gmua0HW1Tv7ANR7hUYwRyD0MG5OJfgvYSZasGZzBic= github.com/quic-go/quic-go v0.59.1 h1:0Gmua0HW1Tv7ANR7hUYwRyD0MG5OJfgvYSZasGZzBic=
github.com/quic-go/quic-go v0.59.1/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU= github.com/quic-go/quic-go v0.59.1/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
github.com/quic-go/quic-go v0.60.0 h1:xcQioE8OM66UQLeUMHltK1CCcOu3JbVB4JAQdDQSB+0=
github.com/quic-go/quic-go v0.60.0/go.mod h1:wpKpjmPpftl30sL6pFh7REVpjbcCVy4zt2vDyK1TuJk=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
@@ -244,27 +175,17 @@ github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfS
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE= go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE=
go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0= go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
go.mongodb.org/mongo-driver/v2 v2.8.0 h1:CxWDGQYY8QQwNjAl/aq2sfWakdnWZynnqJ9F4DhHbP8=
go.mongodb.org/mongo-driver/v2 v2.8.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI= golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI=
golang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= golang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
golang.org/x/arch v0.29.0 h1:8sSET5wB0+exBm0FGmOtdHMqjlRdV2DRD3/IV6OZgho=
golang.org/x/arch v0.29.0/go.mod h1:0X+GdSIP+kL5wPmpK7sdkEVTt2XoYP0cSjQSbZBwOi8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio= golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw=
golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4= golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4=
golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ= golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ=
golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk=
golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
@@ -272,16 +193,12 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE=
golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU=
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -292,14 +209,11 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc= golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc=
golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y= golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y=
golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@@ -307,8 +221,6 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs=
golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY=
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -316,13 +228,9 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8= golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8=
golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0= golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0=
golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE=
golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -346,19 +254,11 @@ gorm.io/gorm v1.31.2 h1:3o8FXNo9v9S858gil+3LlZA1LkCOzgb4g5BL64FgaCo=
gorm.io/gorm v1.31.2/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= gorm.io/gorm v1.31.2/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE= modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE=
modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY= modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY=
modernc.org/libc v1.74.1 h1:bdR4VTKFMC4966QSNZ05XLGI/VwzVa2kTUX51Dm0riQ=
modernc.org/libc v1.74.1/go.mod h1:uH4t5bOx3G3g9Xcmj10YKlTcVISlRDwv8VoQJG9n8Os=
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds= modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
modernc.org/sqlite v1.23.1 h1:nrSBg4aRQQwq59JpvGEQ15tNxoO5pX/kUjcRNwSAGQM= modernc.org/sqlite v1.23.1 h1:nrSBg4aRQQwq59JpvGEQ15tNxoO5pX/kUjcRNwSAGQM=
modernc.org/sqlite v1.23.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk= modernc.org/sqlite v1.23.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk=
modernc.org/sqlite v1.53.0 h1:20WG8N9q4ji/dEqGk4uiI0c6OPjSeLTNYGFCc3+7c1M=
modernc.org/sqlite v1.53.0/go.mod h1:xoEpOIpGrgT48H5iiyt/YXPCZPEzlfmfFwtk8Lklw8s=
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
+57 -5
View File
@@ -422,9 +422,47 @@ func aiPathID(c *gin.Context) (uint, bool) {
return uint(id), true return uint(id), true
} }
// modelCatalog 返回启用渠道聚合去重后的模型目录(含能力),黑名单添加弹窗用。
//
// @Summary 聚合模型目录
// @Tags AI 管理
// @Success 200 {object} itemsResponse[service.AggregatedModel]
// @Security BearerAuth
// @Router /api/v1/ai-model-catalog [get]
func (h *aiAdminHandler) modelCatalog(c *gin.Context) {
items, err := h.gw.AggregatedModels(c.Request.Context())
if err != nil {
respondError(c, err)
return
}
c.JSON(http.StatusOK, gin.H{"items": items})
}
// aiSettingsResponse 是 AI 网关全局设置(文档与响应共用)。 // aiSettingsResponse 是 AI 网关全局设置(文档与响应共用)。
type aiSettingsResponse struct { type aiSettingsResponse struct {
// FilterDeprecated 开启后已宣布弃用(即使未退役)的模型从列表与路由中排除
FilterDeprecated bool `json:"filterDeprecated"` FilterDeprecated bool `json:"filterDeprecated"`
// StreamGuardEnabled / StreamGuardKB 是 Responses 流式保险丝:
// instructions+tools 合计超阈值(KB)的流式请求改非流式上游并合成 SSE
StreamGuardEnabled bool `json:"streamGuardEnabled"`
StreamGuardKB int `json:"streamGuardKB"`
// GrokWebSearch / GrokXSearch 是 xai. 模型服务端搜索工具默认注入开关;
// 请求 tools 已包含同名工具时不覆盖
GrokWebSearch bool `json:"grokWebSearch"`
GrokXSearch bool `json:"grokXSearch"`
}
// currentAiSettings 汇总网关运行时设置为响应体。
func (h *aiAdminHandler) currentAiSettings() aiSettingsResponse {
guardOn, guardKB := h.gw.StreamGuard()
web, x := h.gw.GrokSearch()
return aiSettingsResponse{
FilterDeprecated: h.gw.FilterDeprecated(),
StreamGuardEnabled: guardOn,
StreamGuardKB: guardKB,
GrokWebSearch: web,
GrokXSearch: x,
}
} }
// aiSettings 返回 AI 网关全局设置。 // aiSettings 返回 AI 网关全局设置。
@@ -435,15 +473,16 @@ type aiSettingsResponse struct {
// @Security BearerAuth // @Security BearerAuth
// @Router /api/v1/ai-settings [get] // @Router /api/v1/ai-settings [get]
func (h *aiAdminHandler) aiSettings(c *gin.Context) { func (h *aiAdminHandler) aiSettings(c *gin.Context) {
c.JSON(http.StatusOK, aiSettingsResponse{FilterDeprecated: h.gw.FilterDeprecated()}) c.JSON(http.StatusOK, h.currentAiSettings())
} }
// updateAiSettings 更新 AI 网关全局设置(当前仅「过滤弃用模型」开关)。 // updateAiSettings 更新 AI 网关全局设置(过滤弃用/流式保险丝/grok 搜索工具默认注入)。
// //
// @Summary 更新 AI 网关全局设置 // @Summary 更新 AI 网关全局设置
// @Tags AI 管理 // @Tags AI 管理
// @Param body body aiSettingsResponse true "开启后已宣布弃用(即使未退役)的模型从列表与路由中排除" // @Param body body aiSettingsResponse true "全量提交;保险丝阈值限 1..1024 KB"
// @Success 200 {object} aiSettingsResponse // @Success 200 {object} aiSettingsResponse
// @Failure 400 {object} map[string]string
// @Security BearerAuth // @Security BearerAuth
// @Router /api/v1/ai-settings [put] // @Router /api/v1/ai-settings [put]
func (h *aiAdminHandler) updateAiSettings(c *gin.Context) { func (h *aiAdminHandler) updateAiSettings(c *gin.Context) {
@@ -452,9 +491,22 @@ func (h *aiAdminHandler) updateAiSettings(c *gin.Context) {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return return
} }
if err := h.gw.SetFilterDeprecated(c.Request.Context(), req.FilterDeprecated); err != nil { if req.StreamGuardKB < 1 || req.StreamGuardKB > 1024 {
c.JSON(http.StatusBadRequest, gin.H{"error": "streamGuardKB 须在 1..1024"})
return
}
ctx := c.Request.Context()
if err := h.gw.SetFilterDeprecated(ctx, req.FilterDeprecated); err != nil {
respondError(c, err) respondError(c, err)
return return
} }
c.JSON(http.StatusOK, aiSettingsResponse{FilterDeprecated: h.gw.FilterDeprecated()}) if err := h.gw.SetStreamGuard(ctx, req.StreamGuardEnabled, req.StreamGuardKB); err != nil {
respondError(c, err)
return
}
if err := h.gw.SetGrokSearch(ctx, req.GrokWebSearch, req.GrokXSearch); err != nil {
respondError(c, err)
return
}
c.JSON(http.StatusOK, h.currentAiSettings())
} }
+11 -5
View File
@@ -587,8 +587,13 @@ func (h *aiGatewayHandler) responsesPassthrough(c *gin.Context, raw []byte, req
return return
} }
logRespCompat(req.Model, compat) logRespCompat(req.Model, compat)
web, x := h.gw.GrokSearch()
if injectedBody, injected := service.RespInjectGrokTools(body, req.Model, web, x); len(injected) > 0 {
body = injectedBody
log.Printf("responses 直通(model=%s): 默认注入 %s", req.Model, strings.Join(injected, ", "))
}
if req.Stream { if req.Stream {
if len(body) > service.RespStreamUpgradeLimit { if on, kb := h.gw.StreamGuard(); on && service.RespGuardBytes(body) > kb*1024 {
h.responsesStreamUpgrade(c, body, req, compat) h.responsesStreamUpgrade(c, body, req, compat)
return return
} }
@@ -630,9 +635,9 @@ func logRespCompat(model string, compat service.RespCompat) {
log.Printf("responses 直通(model=%s): %s", model, strings.Join(parts, "; ")) log.Printf("responses 直通(model=%s): %s", model, strings.Join(parts, "; "))
} }
// responsesStreamUpgrade 流式升级回退:上游对超大流式请求会在推理途中掐断 // responsesStreamUpgrade 流式升级回退:instructions+tools 合计超过保险丝阈值
// (实测 >~82KB,见 RespStreamUpgradeLimit),超限时改调非流式上游拿完整响应, // (设置页 AI Tab 配置,默认开 60KB;上游对 >≈64.5KB 会静默断流)时改调非流式
// 本地合成最小 SSE 事件序列回给客户端;丢失增量输出,换会话不中断。 // 上游拿完整响应,本地合成最小 SSE 事件序列回给客户端;丢失增量输出,换会话不中断。
func (h *aiGatewayHandler) responsesStreamUpgrade(c *gin.Context, body []byte, req aiwire.RespRequest, compat service.RespCompat) { func (h *aiGatewayHandler) responsesStreamUpgrade(c *gin.Context, body []byte, req aiwire.RespRequest, compat service.RespCompat) {
start := time.Now() start := time.Now()
nsBody, err := service.RespDisableStream(body) nsBody, err := service.RespDisableStream(body)
@@ -640,7 +645,8 @@ func (h *aiGatewayHandler) responsesStreamUpgrade(c *gin.Context, body []byte, r
aiError(c, http.StatusBadRequest, "invalid_request_error", err.Error()) aiError(c, http.StatusBadRequest, "invalid_request_error", err.Error())
return return
} }
log.Printf("responses 直通(model=%s): 请求体 %dKB 超流式安全上限,改走非流式合成 SSE", req.Model, len(body)/1024) log.Printf("responses 直通(model=%s): instructions+tools %dKB 超保险丝阈值,改走非流式合成 SSE",
req.Model, service.RespGuardBytes(body)/1024)
payload, meta, err := h.gw.RespPassthrough(c.Request.Context(), nsBody, req.Model, keyGroup(c)) payload, meta, err := h.gw.RespPassthrough(c.Request.Context(), nsBody, req.Model, keyGroup(c))
entry := h.logEntry(c, "responses", req.Model, true, meta, start) entry := h.logEntry(c, "responses", req.Model, true, meta, start)
if err != nil { if err != nil {
+1
View File
@@ -39,6 +39,7 @@ func registerAiAdmin(secured *gin.RouterGroup, aiGateway *service.AiGatewayServi
secured.GET("/ai-channels/:id/models", aiadmin.listChannelModels) secured.GET("/ai-channels/:id/models", aiadmin.listChannelModels)
secured.POST("/ai-channels/:id/test-model", aiadmin.testChannelModel) secured.POST("/ai-channels/:id/test-model", aiadmin.testChannelModel)
secured.GET("/ai-models", aiadmin.gatewayModels) secured.GET("/ai-models", aiadmin.gatewayModels)
secured.GET("/ai-model-catalog", aiadmin.modelCatalog)
secured.GET("/ai-settings", aiadmin.aiSettings) secured.GET("/ai-settings", aiadmin.aiSettings)
secured.PUT("/ai-settings", aiadmin.updateAiSettings) secured.PUT("/ai-settings", aiadmin.updateAiSettings)
secured.GET("/ai-blacklist", aiadmin.listBlacklist) secured.GET("/ai-blacklist", aiadmin.listBlacklist)
+19 -4
View File
@@ -400,6 +400,7 @@ func fillInstanceIPs(ctx context.Context, cc core.ComputeClient, vn core.Virtual
wg sync.WaitGroup wg sync.WaitGroup
mu sync.Mutex mu sync.Mutex
sem = make(chan struct{}, 8) sem = make(chan struct{}, 8)
primarySeen = make(map[*Instance]bool)
) )
for _, att := range attResp.Items { for _, att := range attResp.Items {
inst, ok := active[deref(att.InstanceId)] inst, ok := active[deref(att.InstanceId)]
@@ -416,16 +417,30 @@ func fillInstanceIPs(ctx context.Context, cc core.ComputeClient, vn core.Virtual
return return
} }
mu.Lock() mu.Lock()
inst.SubnetID = deref(vnicResp.SubnetId) applyVnicAddrs(inst, vnicResp.Vnic, primarySeen)
inst.PrivateIP = deref(vnicResp.PrivateIp)
inst.PublicIP = deref(vnicResp.PublicIp)
inst.Ipv6Addresses = vnicResp.Ipv6Addresses
mu.Unlock() mu.Unlock()
}(att.VnicId, inst) }(att.VnicId, inst)
} }
wg.Wait() wg.Wait()
} }
// applyVnicAddrs 将 VNIC 地址写入实例。多网卡实例以主网卡为准:
// 主网卡返回前先用先到的网卡兜底,主网卡到达后覆盖并锁定,避免并发
// 完成顺序决定展示结果。调用方需持有保护 inst 与 primarySeen 的锁。
func applyVnicAddrs(inst *Instance, v core.Vnic, primarySeen map[*Instance]bool) {
isPrimary := v.IsPrimary != nil && *v.IsPrimary
if !isPrimary && (primarySeen[inst] || inst.SubnetID != "") {
return
}
inst.SubnetID = deref(v.SubnetId)
inst.PrivateIP = deref(v.PrivateIp)
inst.PublicIP = deref(v.PublicIp)
inst.Ipv6Addresses = v.Ipv6Addresses
if isPrimary {
primarySeen[inst] = true
}
}
func toInstance(inst core.Instance) Instance { func toInstance(inst core.Instance) Instance {
out := Instance{ out := Instance{
ID: deref(inst.Id), ID: deref(inst.Id),
+42
View File
@@ -5,9 +5,51 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/oracle/oci-go-sdk/v65/common"
"github.com/oracle/oci-go-sdk/v65/core" "github.com/oracle/oci-go-sdk/v65/core"
) )
func TestApplyVnicAddrs(t *testing.T) {
primary := core.Vnic{
IsPrimary: common.Bool(true),
SubnetId: common.String("sub-a"),
PrivateIp: common.String("10.0.0.2"),
PublicIp: common.String("1.1.1.1"),
}
secondary := core.Vnic{
IsPrimary: common.Bool(false),
SubnetId: common.String("sub-b"),
PrivateIp: common.String("10.0.0.9"),
}
secondary2 := core.Vnic{
SubnetId: common.String("sub-c"),
PrivateIp: common.String("10.0.0.7"),
}
tests := []struct {
name string
order []core.Vnic
wantPrivate string
wantSubnet string
}{
{name: "主卡先到不被次卡覆盖", order: []core.Vnic{primary, secondary}, wantPrivate: "10.0.0.2", wantSubnet: "sub-a"},
{name: "次卡先兜底主卡后覆盖", order: []core.Vnic{secondary, primary}, wantPrivate: "10.0.0.2", wantSubnet: "sub-a"},
{name: "无主卡时先到者保留", order: []core.Vnic{secondary, secondary2}, wantPrivate: "10.0.0.9", wantSubnet: "sub-b"},
{name: "仅次卡也能兜底", order: []core.Vnic{secondary2}, wantPrivate: "10.0.0.7", wantSubnet: "sub-c"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
inst := &Instance{}
seen := make(map[*Instance]bool)
for _, v := range tt.order {
applyVnicAddrs(inst, v, seen)
}
if inst.PrivateIP != tt.wantPrivate || inst.SubnetID != tt.wantSubnet {
t.Errorf("got (%q, %q), want (%q, %q)", inst.PrivateIP, inst.SubnetID, tt.wantPrivate, tt.wantSubnet)
}
})
}
}
func TestShellSingleQuote(t *testing.T) { func TestShellSingleQuote(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
+135 -12
View File
@@ -10,6 +10,7 @@ import (
"fmt" "fmt"
"log" "log"
"sort" "sort"
"strconv"
"strings" "strings"
"sync" "sync"
"sync/atomic" "sync/atomic"
@@ -61,20 +62,72 @@ type AiGatewayService struct {
onChannelsChanged func(context.Context) onChannelsChanged func(context.Context)
// filterDeprecated 是「过滤弃用模型」开关(内存镜像,持久化在 settings 表) // filterDeprecated 是「过滤弃用模型」开关(内存镜像,持久化在 settings 表)
filterDeprecated atomic.Bool filterDeprecated atomic.Bool
// streamGuard* 是 Responses 流式保险丝(instructions+tools 合计超阈值改非流式)
streamGuardEnabled atomic.Bool
streamGuardKB atomic.Int64
// grokWebSearch / grokXSearch 是 xai. 模型服务端搜索工具默认注入开关
grokWebSearch atomic.Bool
grokXSearch atomic.Bool
} }
// NewAiGatewayService 组装依赖;调用 StartCleanup 后开始调用日志周期清理。 // NewAiGatewayService 组装依赖;调用 StartCleanup 后开始调用日志周期清理。
func NewAiGatewayService(db *gorm.DB, configs *OciConfigService, client oci.Client) *AiGatewayService { func NewAiGatewayService(db *gorm.DB, configs *OciConfigService, client oci.Client) *AiGatewayService {
s := &AiGatewayService{db: db, configs: configs, client: client, lastTouch: map[uint]time.Time{}} s := &AiGatewayService{db: db, configs: configs, client: client, lastTouch: map[uint]time.Time{}}
var row model.Setting s.filterDeprecated.Store(loadBoolSetting(db, settingAiFilterDeprecated, false))
if err := db.Where("key = ?", settingAiFilterDeprecated).First(&row).Error; err == nil { s.streamGuardEnabled.Store(loadBoolSetting(db, settingAiStreamGuardEnabled, true))
s.filterDeprecated.Store(row.Value == "1") s.streamGuardKB.Store(int64(loadIntSetting(db, settingAiStreamGuardKB, defaultStreamGuardKB)))
} s.grokWebSearch.Store(loadBoolSetting(db, settingAiGrokWebSearch, true))
s.grokXSearch.Store(loadBoolSetting(db, settingAiGrokXSearch, true))
return s return s
} }
// settingAiFilterDeprecated 是「过滤弃用模型」开关的配置键,值 "1"/"0",缺省关闭 // AI 网关运行时设置的配置键;bool 值存 "1"/"0"。
const settingAiFilterDeprecated = "ai_filter_deprecated" const (
// settingAiFilterDeprecated 是「过滤弃用模型」开关,缺省关闭。
settingAiFilterDeprecated = "ai_filter_deprecated"
// settingAiStreamGuardEnabled / settingAiStreamGuardKB 是流式保险丝开关与
// 阈值(KB),缺省开、60(上游对 instructions+tools >≈64.5KB 流式静默断流)。
settingAiStreamGuardEnabled = "ai_stream_guard_enabled"
settingAiStreamGuardKB = "ai_stream_guard_kb"
// settingAiGrokWebSearch / settingAiGrokXSearch 是 grok 搜索工具默认注入
// 开关,缺省开。
settingAiGrokWebSearch = "ai_grok_web_search"
settingAiGrokXSearch = "ai_grok_x_search"
)
// defaultStreamGuardKB 是保险丝阈值缺省值,低于实测断流边界留余量。
const defaultStreamGuardKB = 60
// loadBoolSetting 读 settings 表布尔键,无行或值非法时返回缺省。
func loadBoolSetting(db *gorm.DB, key string, def bool) bool {
var row model.Setting
if err := db.Where("key = ?", key).First(&row).Error; err != nil {
return def
}
return row.Value == "1"
}
// loadIntSetting 读 settings 表整数键,无行或解析失败时返回缺省。
func loadIntSetting(db *gorm.DB, key string, def int) int {
var row model.Setting
if err := db.Where("key = ?", key).First(&row).Error; err != nil {
return def
}
n, err := strconv.Atoi(row.Value)
if err != nil {
return def
}
return n
}
// saveBoolSetting 持久化布尔键。
func (s *AiGatewayService) saveBoolSetting(ctx context.Context, key string, on bool) error {
value := "0"
if on {
value = "1"
}
return s.db.WithContext(ctx).Save(&model.Setting{Key: key, Value: value}).Error
}
// FilterDeprecated 返回「过滤弃用模型」开关状态。 // FilterDeprecated 返回「过滤弃用模型」开关状态。
func (s *AiGatewayService) FilterDeprecated() bool { return s.filterDeprecated.Load() } func (s *AiGatewayService) FilterDeprecated() bool { return s.filterDeprecated.Load() }
@@ -82,18 +135,54 @@ func (s *AiGatewayService) FilterDeprecated() bool { return s.filterDeprecated.L
// SetFilterDeprecated 持久化并即时生效开关:开启后已宣布弃用 // SetFilterDeprecated 持久化并即时生效开关:开启后已宣布弃用
// (deprecated_at 非空,即使未退役)的模型从列表与路由中排除。 // (deprecated_at 非空,即使未退役)的模型从列表与路由中排除。
func (s *AiGatewayService) SetFilterDeprecated(ctx context.Context, on bool) error { func (s *AiGatewayService) SetFilterDeprecated(ctx context.Context, on bool) error {
value := "0" if err := s.saveBoolSetting(ctx, settingAiFilterDeprecated, on); err != nil {
if on {
value = "1"
}
err := s.db.WithContext(ctx).Save(&model.Setting{Key: settingAiFilterDeprecated, Value: value}).Error
if err != nil {
return fmt.Errorf("保存过滤弃用模型开关: %w", err) return fmt.Errorf("保存过滤弃用模型开关: %w", err)
} }
s.filterDeprecated.Store(on) s.filterDeprecated.Store(on)
return nil return nil
} }
// StreamGuard 返回流式保险丝开关与阈值(KB)。
func (s *AiGatewayService) StreamGuard() (bool, int) {
return s.streamGuardEnabled.Load(), int(s.streamGuardKB.Load())
}
// SetStreamGuard 持久化并即时生效流式保险丝;kb 限定 1..1024。
func (s *AiGatewayService) SetStreamGuard(ctx context.Context, on bool, kb int) error {
if kb < 1 || kb > 1024 {
return fmt.Errorf("流式保险丝阈值须在 1..1024 KB, 收到 %d", kb)
}
if err := s.saveBoolSetting(ctx, settingAiStreamGuardEnabled, on); err != nil {
return fmt.Errorf("保存流式保险丝开关: %w", err)
}
err := s.db.WithContext(ctx).
Save(&model.Setting{Key: settingAiStreamGuardKB, Value: strconv.Itoa(kb)}).Error
if err != nil {
return fmt.Errorf("保存流式保险丝阈值: %w", err)
}
s.streamGuardEnabled.Store(on)
s.streamGuardKB.Store(int64(kb))
return nil
}
// GrokSearch 返回 grok 服务端搜索工具默认注入开关(web_search, x_search)。
func (s *AiGatewayService) GrokSearch() (bool, bool) {
return s.grokWebSearch.Load(), s.grokXSearch.Load()
}
// SetGrokSearch 持久化并即时生效 grok 搜索工具默认注入开关。
func (s *AiGatewayService) SetGrokSearch(ctx context.Context, web, x bool) error {
if err := s.saveBoolSetting(ctx, settingAiGrokWebSearch, web); err != nil {
return fmt.Errorf("保存 grok web_search 开关: %w", err)
}
if err := s.saveBoolSetting(ctx, settingAiGrokXSearch, x); err != nil {
return fmt.Errorf("保存 grok x_search 开关: %w", err)
}
s.grokWebSearch.Store(web)
s.grokXSearch.Store(x)
return nil
}
// SetOnChannelsChanged 注册渠道数量变化钩子(渠道创建/删除成功后调用)。 // SetOnChannelsChanged 注册渠道数量变化钩子(渠道创建/删除成功后调用)。
func (s *AiGatewayService) SetOnChannelsChanged(fn func(context.Context)) { func (s *AiGatewayService) SetOnChannelsChanged(fn func(context.Context)) {
s.onChannelsChanged = fn s.onChannelsChanged = fn
@@ -661,6 +750,40 @@ func (s *AiGatewayService) GatewayModels(ctx context.Context, group string) (aiw
return list, nil return list, nil
} }
// AggregatedModel 是聚合模型目录条目(设置页黑名单添加弹窗用)。
type AggregatedModel struct {
Name string `json:"name"`
Capability string `json:"capability"`
}
// AggregatedModels 返回启用渠道去重后的模型目录(含能力);空能力归一为 CHAT。
// 与模型列表口径一致:「过滤弃用」开启时弃用模型不出现在目录中。
func (s *AiGatewayService) AggregatedModels(ctx context.Context) ([]AggregatedModel, error) {
q := s.db.WithContext(ctx).
Joins("JOIN ai_channels ON ai_channels.id = ai_model_caches.channel_id AND ai_channels.enabled = ?", true)
if s.FilterDeprecated() {
q = q.Where("ai_model_caches.deprecated_at IS NULL")
}
var rows []model.AiModelCache
if err := q.Order("ai_model_caches.name ASC").Find(&rows).Error; err != nil {
return nil, fmt.Errorf("聚合模型目录: %w", err)
}
seen := map[string]bool{}
out := []AggregatedModel{}
for _, r := range rows {
if seen[r.Name] {
continue
}
seen[r.Name] = true
cap := r.Capability
if cap == "" {
cap = "CHAT"
}
out = append(out, AggregatedModel{Name: r.Name, Capability: cap})
}
return out, nil
}
// DeprecatingModels 返回 within 窗口内即将退役或即将弃用的在池模型(按名称去重): // DeprecatingModels 返回 within 窗口内即将退役或即将弃用的在池模型(按名称去重):
// 退役(TimeOnDemandRetired)才导致不可调用,单独标注;已过弃用日但未到退役日的 // 退役(TimeOnDemandRetired)才导致不可调用,单独标注;已过弃用日但未到退役日的
// 模型仍可正常调用,不再反复告警;已过退役日的在同步层剔除,不会出现在池中。 // 模型仍可正常调用,不再反复告警;已过退役日的在同步层剔除,不会出现在池中。
+64
View File
@@ -1060,3 +1060,67 @@ func TestRespPassthroughStreamSwitchesChannel(t *testing.T) {
t.Errorf("流内容未透传: %s", payload) t.Errorf("流内容未透传: %s", payload)
} }
} }
// TestAiRuntimeSettings 断言流式保险丝与 grok 注入开关的缺省值、往返与持久化。
func TestAiRuntimeSettings(t *testing.T) {
gw, svc := newTestGateway(t, &gatewayStubClient{fakeClient: &fakeClient{tenancy: oci.TenancyInfo{Name: "t"}}})
ctx := context.Background()
if on, kb := gw.StreamGuard(); !on || kb != 60 {
t.Fatalf("保险丝缺省应为 开/60, got %v/%d", on, kb)
}
if web, x := gw.GrokSearch(); !web || !x {
t.Fatalf("grok 注入缺省应双开, got %v/%v", web, x)
}
for _, bad := range []int{0, -1, 1025} {
if err := gw.SetStreamGuard(ctx, true, bad); err == nil {
t.Errorf("阈值 %d 应报错", bad)
}
}
if err := gw.SetStreamGuard(ctx, false, 80); err != nil {
t.Fatalf("SetStreamGuard: %v", err)
}
if err := gw.SetGrokSearch(ctx, false, true); err != nil {
t.Fatalf("SetGrokSearch: %v", err)
}
gw2 := NewAiGatewayService(gw.db, svc, &gatewayStubClient{})
if on, kb := gw2.StreamGuard(); on || kb != 80 {
t.Errorf("重建后保险丝应为 关/80, got %v/%d", on, kb)
}
if web, x := gw2.GrokSearch(); web || !x {
t.Errorf("重建后 grok 注入应为 关/开, got %v/%v", web, x)
}
}
// TestAggregatedModelsFilterDeprecated 断言聚合目录与模型列表口径一致:
// 「过滤弃用」开启时弃用模型不出现,关闭时出现;空能力归一为 CHAT。
func TestAggregatedModelsFilterDeprecated(t *testing.T) {
gw, svc := newTestGateway(t, &gatewayStubClient{fakeClient: &fakeClient{tenancy: oci.TenancyInfo{Name: "t"}}})
cfg := importAliveConfig(t, svc)
ch := seedChannel(t, gw, cfg.ID, "eu-frankfurt-1", 1, 1)
dep := time.Now().Add(-24 * time.Hour)
old := &model.AiModelCache{ChannelID: ch.ID, ModelOcid: "ocid1..dep", Name: "meta.llama-old",
Vendor: "meta", SyncedAt: time.Now(), DeprecatedAt: &dep}
if err := gw.db.Create(old).Error; err != nil {
t.Fatalf("seed deprecated cache: %v", err)
}
ctx := context.Background()
items, err := gw.AggregatedModels(ctx)
if err != nil {
t.Fatalf("AggregatedModels: %v", err)
}
if len(items) != 2 || items[0].Capability == "" {
t.Fatalf("开关关:应含弃用模型且能力归一, got %+v", items)
}
if err := gw.SetFilterDeprecated(ctx, true); err != nil {
t.Fatalf("SetFilterDeprecated: %v", err)
}
items, err = gw.AggregatedModels(ctx)
if err != nil {
t.Fatalf("AggregatedModels(过滤): %v", err)
}
if len(items) != 1 || items[0].Name == "meta.llama-old" {
t.Fatalf("开关开:弃用模型应被过滤, got %+v", items)
}
}
+57 -4
View File
@@ -320,10 +320,63 @@ func respToolDrop(tool map[string]any) (string, bool) {
return "", false return "", false
} }
// RespStreamUpgradeLimit 是流式直通的请求体安全上限。实测(2026-07,xai 面): // RespGuardBytes 返回请求体中 instructions 与 tools 两字段的原始字节数之和,
// 超过 ~82KB 的流式请求上游会在推理阶段掐断流(非流式不受影响,纯体积触发, // 流式保险丝据此判定。上游对二者合计 >≈64.5KB 的流式请求会在推理阶段静默断流
// 与工具构成无关),预留余量取 76KB;超限时网关改走非流式上游并合成 SSE // (纯 EOF,input 正文不计入;2026-07-16 实测仍存在),阈值由设置页 AI Tab 配置
const RespStreamUpgradeLimit = 76 * 1024 // 解析失败返回 0(放行,交由上游正常报错)。
func RespGuardBytes(body []byte) int {
var probe struct {
Instructions json.RawMessage `json:"instructions"`
Tools json.RawMessage `json:"tools"`
}
if err := json.Unmarshal(body, &probe); err != nil {
return 0
}
return len(probe.Instructions) + len(probe.Tools)
}
// RespInjectGrokTools 为 xai. 前缀模型默认注入服务端搜索工具:开关开启且请求
// tools 中不存在同名工具时追加 {"type":"web_search"} / {"type":"x_search"};
// 已存在(含任意参数形态)不覆盖。返回改写后 body 与注入清单(观测日志用);
// 模型不匹配、两开关全关或解析失败时原样返回。
func RespInjectGrokTools(body []byte, model string, web, x bool) ([]byte, []string) {
if !strings.HasPrefix(model, "xai.") || (!web && !x) {
return body, nil
}
dec := json.NewDecoder(strings.NewReader(string(body)))
dec.UseNumber()
var m map[string]any
if err := dec.Decode(&m); err != nil {
return body, nil
}
tools, _ := m["tools"].([]any)
missing := map[string]bool{"web_search": web, "x_search": x}
for _, t := range tools {
tool, ok := t.(map[string]any)
if !ok {
continue
}
if typ, _ := tool["type"].(string); missing[typ] {
missing[typ] = false
}
}
var injected []string
for _, typ := range []string{"web_search", "x_search"} {
if missing[typ] {
tools = append(tools, map[string]any{"type": typ})
injected = append(injected, typ)
}
}
if len(injected) == 0 {
return body, nil
}
m["tools"] = tools
out, err := json.Marshal(m)
if err != nil {
return body, nil
}
return out, injected
}
// RespDisableStream 把请求体的 stream 改为 false(流式升级回退用),其余字段 // RespDisableStream 把请求体的 stream 改为 false(流式升级回退用),其余字段
// 原样保留。 // 原样保留。
+66
View File
@@ -2,6 +2,7 @@ package service
import ( import (
"encoding/json" "encoding/json"
"fmt"
"slices" "slices"
"strings" "strings"
"testing" "testing"
@@ -329,6 +330,71 @@ func TestRespStreamCompletedUsage(t *testing.T) {
} }
} }
// TestRespGuardBytes 断言保险丝计量:instructions+tools 原始字节和,缺字段计 0,
// 解析失败返回 0 放行。
func TestRespGuardBytes(t *testing.T) {
for _, tc := range []struct {
name, body string
want int
}{
{"双字段", `{"instructions":"abcd","tools":[{"type":"web_search"}],"input":"xxxxxxxx"}`,
len(`"abcd"`) + len(`[{"type":"web_search"}]`)},
{"仅 instructions", `{"instructions":"abcd"}`, len(`"abcd"`)},
{"均缺失 input 不计", `{"input":"xxxxxxxxxxxxxxxx"}`, 0},
{"解析失败放行", `not-json`, 0},
} {
t.Run(tc.name, func(t *testing.T) {
if got := RespGuardBytes([]byte(tc.body)); got != tc.want {
t.Fatalf("RespGuardBytes() = %d, want %d", got, tc.want)
}
})
}
}
// TestRespInjectGrokTools 断言 grok 搜索工具默认注入:仅 xai. 模型、开关可控、
// 已带同名工具不覆盖、注入清单正确。
func TestRespInjectGrokTools(t *testing.T) {
for _, tc := range []struct {
name, body, model string
web, x bool
wantInjected []string
wantContains []string
}{
{"非 xai 不注入", `{"tools":[]}`, "meta.llama-3.3", true, true, nil, nil},
{"双开无 tools 字段注入两个", `{"model":"xai.grok-4.3"}`, "xai.grok-4.3", true, true,
[]string{"web_search", "x_search"}, []string{`"web_search"`, `"x_search"`}},
{"已带 web_search 只注入 x_search", `{"tools":[{"type":"web_search","filters":{"x":1}}]}`,
"xai.grok-4.3", true, true, []string{"x_search"}, []string{`"filters"`}},
{"开关全关不注入", `{"tools":[]}`, "xai.grok-4.3", false, false, nil, nil},
{"仅开 x_search", `{"tools":[]}`, "xai.grok-4.3", false, true, []string{"x_search"}, nil},
{"解析失败原样", `not-json`, "xai.grok-4.3", true, true, nil, nil},
} {
t.Run(tc.name, func(t *testing.T) {
out, injected := RespInjectGrokTools([]byte(tc.body), tc.model, tc.web, tc.x)
if fmt.Sprint(injected) != fmt.Sprint(tc.wantInjected) {
t.Fatalf("injected = %v, want %v", injected, tc.wantInjected)
}
if len(injected) == 0 && string(out) != tc.body {
t.Fatalf("未注入时应原样返回: %s", out)
}
for _, sub := range append(tc.wantContains, toolTypes(injected)...) {
if !strings.Contains(string(out), sub) {
t.Fatalf("输出缺少 %s: %s", sub, out)
}
}
})
}
}
// toolTypes 把注入清单转为输出应包含的片段断言。
func toolTypes(injected []string) []string {
var out []string
for _, typ := range injected {
out = append(out, `{"type":"`+typ+`"}`)
}
return out
}
// TestRespDisableStream 断言流式升级回退把 stream 置 false 且其余字段保留。 // TestRespDisableStream 断言流式升级回退把 stream 置 false 且其余字段保留。
func TestRespDisableStream(t *testing.T) { func TestRespDisableStream(t *testing.T) {
out, err := RespDisableStream([]byte(`{"model":"m","stream":true,"max_output_tokens":64}`)) out, err := RespDisableStream([]byte(`{"model":"m","stream":true,"max_output_tokens":64}`))