diff --git a/.env.example b/.env.example index 437f1f4bb..522c007f5 100644 --- a/.env.example +++ b/.env.example @@ -57,6 +57,17 @@ API_TEST_TIMEOUT_MS=15000 # 警告:若设置为 true 且使用远程 HTTP 访问,浏览器将拒绝设置 Cookie 导致无法登录 ENABLE_SECURE_COOKIES=true +# Management REST API / legacy actions API +# - ENABLE_LEGACY_ACTIONS_API=false returns 410 Gone for legacy /api/actions execution. +# - LEGACY_ACTIONS_DOCS_MODE=hidden returns 410 for legacy docs even when execution stays enabled. +# - ENABLE_API_KEY_ADMIN_ACCESS=true allows admin-role DB API keys to call admin /api/v1 routes. +# - CSRF_SECRET signs cookie-authenticated management mutations; set the same value on all replicas. +ENABLE_LEGACY_ACTIONS_API=true +LEGACY_ACTIONS_DOCS_MODE=deprecated +LEGACY_ACTIONS_SUNSET_DATE=2026-12-31 +ENABLE_API_KEY_ADMIN_ACCESS=false +CSRF_SECRET= + # Redis 配置(用于限流和 Session 追踪) # 功能说明: # - 限流功能:金额限制(5小时/周/月)+ Session 并发限制 @@ -74,7 +85,9 @@ API_KEY_AUTH_CACHE_TTL_SECONDS="60" # 鉴权缓存 TTL(秒,默认 60, ENABLE_API_KEY_REDIS_CACHE="true" # 是否启用 API Key Redis 缓存(默认:true) # Session 配置 -SESSION_TTL=300 # Session 过期时间(秒,默认 300 = 5 分钟) +# 降低该值会按签发时间收紧已签发 ADMIN_TOKEN 签名 cookie 的剩余寿命,且不会延长其原始 exp。 +AUTH_SESSION_TTL_SECONDS=604800 # Web UI 登录态过期时间(秒,默认 604800 = 7 天,范围 60-31536000) +SESSION_TTL=300 # 代理请求上下文缓存时间(秒,默认 300 = 5 分钟;不控制 Web UI 登录态) STORE_SESSION_MESSAGES=false # 会话消息存储模式(默认:false) # - false:存储请求/响应体但对 message 内容脱敏 [REDACTED] # - true:原样存储 message 内容(注意隐私和存储空间影响) diff --git a/.github/workflows/claude-ci-autofix.yml b/.github/workflows/claude-ci-autofix.yml index c37a8de7a..7a1f24cda 100644 --- a/.github/workflows/claude-ci-autofix.yml +++ b/.github/workflows/claude-ci-autofix.yml @@ -60,7 +60,7 @@ jobs: echo "Branch validation passed: syncing $SOURCE -> $TARGET" - name: Checkout code - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: ref: ${{ github.event.inputs.target_branch }} fetch-depth: 0 @@ -237,14 +237,14 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: ref: ${{ github.event.workflow_run.head_branch }} fetch-depth: 0 - name: Get CI failure details id: failure_details - uses: actions/github-script@v7 + uses: actions/github-script@v9 with: script: | const run = await github.rest.actions.getWorkflowRun({ diff --git a/.github/workflows/claude-issue-auto-response.yml b/.github/workflows/claude-issue-auto-response.yml index 10fd934d9..60d2adb1d 100644 --- a/.github/workflows/claude-issue-auto-response.yml +++ b/.github/workflows/claude-issue-auto-response.yml @@ -26,7 +26,7 @@ jobs: - name: Check if Codex workflow succeeded for this issue if: steps.perm_check.outputs.EXTERNAL_USER == 'false' id: check - uses: actions/github-script@v7 + uses: actions/github-script@v9 with: script: | // Wait for Codex workflow to complete (max 5 minutes) @@ -91,7 +91,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 diff --git a/.github/workflows/claude-issue-duplicate-check.yml b/.github/workflows/claude-issue-duplicate-check.yml index d27b74271..704e7877c 100644 --- a/.github/workflows/claude-issue-duplicate-check.yml +++ b/.github/workflows/claude-issue-duplicate-check.yml @@ -13,7 +13,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 diff --git a/.github/workflows/claude-issue-oncall-triage.yml b/.github/workflows/claude-issue-oncall-triage.yml index 667df0d14..e7787e43b 100644 --- a/.github/workflows/claude-issue-oncall-triage.yml +++ b/.github/workflows/claude-issue-oncall-triage.yml @@ -15,7 +15,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Run Claude Code for Oncall Triage uses: anthropics/claude-code-action@v1 diff --git a/.github/workflows/claude-issue-stale-cleanup.yml b/.github/workflows/claude-issue-stale-cleanup.yml index 793e4652f..4c78f4f46 100644 --- a/.github/workflows/claude-issue-stale-cleanup.yml +++ b/.github/workflows/claude-issue-stale-cleanup.yml @@ -15,7 +15,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Run Claude Code for Stale Issue Cleanup uses: anthropics/claude-code-action@v1 diff --git a/.github/workflows/claude-issue-triage.yml b/.github/workflows/claude-issue-triage.yml index 4dd425056..6f562b17f 100644 --- a/.github/workflows/claude-issue-triage.yml +++ b/.github/workflows/claude-issue-triage.yml @@ -13,7 +13,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 diff --git a/.github/workflows/claude-mention-responder.yml b/.github/workflows/claude-mention-responder.yml index 3b51b56e5..8344b938f 100644 --- a/.github/workflows/claude-mention-responder.yml +++ b/.github/workflows/claude-mention-responder.yml @@ -20,7 +20,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 diff --git a/.github/workflows/claude-pr-description.yml b/.github/workflows/claude-pr-description.yml index c4331bd8a..dd024fc1a 100644 --- a/.github/workflows/claude-pr-description.yml +++ b/.github/workflows/claude-pr-description.yml @@ -18,7 +18,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 diff --git a/.github/workflows/claude-pr-label.yml b/.github/workflows/claude-pr-label.yml index 11bb01caf..363de044f 100644 --- a/.github/workflows/claude-pr-label.yml +++ b/.github/workflows/claude-pr-label.yml @@ -18,7 +18,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 1 diff --git a/.github/workflows/claude-pr-review.yml b/.github/workflows/claude-pr-review.yml index 8c7ec5f32..25d24f282 100644 --- a/.github/workflows/claude-pr-review.yml +++ b/.github/workflows/claude-pr-review.yml @@ -30,7 +30,7 @@ jobs: - name: Check if Codex workflow succeeded for this PR if: steps.perm_check.outputs.EXTERNAL_USER == 'false' id: check - uses: actions/github-script@v7 + uses: actions/github-script@v9 with: script: | // Wait for Codex workflow to complete (max 10 minutes for PR review) @@ -99,7 +99,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 1 diff --git a/.github/workflows/claude-review-responder.yml b/.github/workflows/claude-review-responder.yml index d68d6826b..5eac8b686 100644 --- a/.github/workflows/claude-review-responder.yml +++ b/.github/workflows/claude-review-responder.yml @@ -22,7 +22,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 ref: ${{ github.event.pull_request.head.sha }} diff --git a/.github/workflows/claude-unified-docs.yml b/.github/workflows/claude-unified-docs.yml index 7c9db7d45..389ee26a2 100644 --- a/.github/workflows/claude-unified-docs.yml +++ b/.github/workflows/claude-unified-docs.yml @@ -26,7 +26,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 token: ${{ secrets.GITHUB_TOKEN || secrets.GH_PAT }} diff --git a/.github/workflows/codex-issue-auto-response.yml b/.github/workflows/codex-issue-auto-response.yml index db3cfcb08..419aa368e 100644 --- a/.github/workflows/codex-issue-auto-response.yml +++ b/.github/workflows/codex-issue-auto-response.yml @@ -18,7 +18,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 diff --git a/.github/workflows/codex-pr-review.yml b/.github/workflows/codex-pr-review.yml index cf68cdd81..4fda4b490 100644 --- a/.github/workflows/codex-pr-review.yml +++ b/.github/workflows/codex-pr-review.yml @@ -26,7 +26,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: ref: refs/pull/${{ github.event.pull_request.number }}/merge fetch-depth: 0 @@ -60,7 +60,7 @@ jobs: pull-requests: write steps: - name: Post Review Comment - uses: actions/github-script@v7 + uses: actions/github-script@v9 env: REVIEW_RESULT: ${{ needs.pr-review.outputs.review_result }} with: diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index cf36c4f9d..f158d8cb6 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -18,7 +18,7 @@ jobs: if: github.event.pusher.name != 'github-actions[bot]' && !contains(github.event.head_commit.message, '[skip ci]') steps: - name: Checkout code - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 token: ${{ secrets.GITHUB_TOKEN }} @@ -56,7 +56,7 @@ jobs: echo "Versioned tag: $VERSIONED_TAG" - name: Setup Node.js for formatting - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version: "20" @@ -66,7 +66,7 @@ jobs: bun-version-file: .bun-version - name: Cache Bun package cache - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: ~/.bun/install/cache # This repo intentionally does not track Bun lockfiles; rotate cache on package/runtime inputs. @@ -112,20 +112,20 @@ jobs: # Docker构建步骤 - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + uses: docker/setup-qemu-action@v4 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@v4 - name: Log in to GitHub Container Registry - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push Docker image - uses: docker/build-push-action@v6 + uses: docker/build-push-action@v7 with: context: . file: ./deploy/Dockerfile diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index b9ae96a87..c68e73ac3 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -21,7 +21,7 @@ jobs: steps: - name: 📥 Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: 📦 Setup Bun uses: oven-sh/setup-bun@v2 @@ -29,12 +29,12 @@ jobs: bun-version-file: .bun-version - name: 🟢 Setup Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version: '20' - name: Cache Bun package cache - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: ~/.bun/install/cache # This repo intentionally does not track Bun lockfiles; rotate cache on package/runtime inputs. @@ -71,14 +71,14 @@ jobs: steps: - name: 📥 Checkout repository - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: 🔧 Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@v4 - name: 📋 Extract metadata id: meta - uses: docker/metadata-action@v5 + uses: docker/metadata-action@v6 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} tags: | @@ -86,7 +86,7 @@ jobs: type=sha,prefix=pr- - name: 🏗️ Build Docker image (Test Only - No Push) - uses: docker/build-push-action@v6 + uses: docker/build-push-action@v7 with: context: . file: ./deploy/Dockerfile diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5f86dbf09..7ae380a03 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -37,7 +37,7 @@ jobs: (github.event.pusher.name != 'github-actions[bot]' && !contains(github.event.head_commit.message, '[skip ci]')) steps: - name: Checkout code - uses: actions/checkout@v5 + uses: actions/checkout@v6 with: fetch-depth: 0 token: ${{ secrets.GITHUB_TOKEN }} @@ -191,7 +191,7 @@ jobs: - name: Setup Node.js for formatting if: steps.check.outputs.needs_bump == 'true' || github.event_name == 'workflow_dispatch' - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version: "20" @@ -203,7 +203,7 @@ jobs: - name: Cache Bun package cache if: steps.check.outputs.needs_bump == 'true' || github.event_name == 'workflow_dispatch' - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: ~/.bun/install/cache # This repo intentionally does not track Bun lockfiles; rotate cache on package/runtime inputs. @@ -258,7 +258,7 @@ jobs: - name: Create GitHub Release if: (steps.check.outputs.needs_bump == 'true' || github.event_name == 'workflow_dispatch') && success() - uses: softprops/action-gh-release@v2 + uses: softprops/action-gh-release@v3 with: tag_name: ${{ steps.next_version.outputs.new_tag }} name: Release ${{ steps.next_version.outputs.new_version }} @@ -381,15 +381,15 @@ jobs: # Docker构建步骤 - name: Set up QEMU if: (steps.check.outputs.needs_bump == 'true' || github.event_name == 'workflow_dispatch') && success() - uses: docker/setup-qemu-action@v3 + uses: docker/setup-qemu-action@v4 - name: Set up Docker Buildx if: (steps.check.outputs.needs_bump == 'true' || github.event_name == 'workflow_dispatch') && success() - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@v4 - name: Log in to GitHub Container Registry if: (steps.check.outputs.needs_bump == 'true' || github.event_name == 'workflow_dispatch') && success() - uses: docker/login-action@v3 + uses: docker/login-action@v4 with: registry: ghcr.io username: ${{ github.repository_owner }} @@ -397,7 +397,7 @@ jobs: - name: Build and push Docker image if: (steps.check.outputs.needs_bump == 'true' || github.event_name == 'workflow_dispatch') && success() - uses: docker/build-push-action@v6 + uses: docker/build-push-action@v7 with: context: . file: ./deploy/Dockerfile diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 67f9f199b..ecff5eed4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -20,7 +20,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup Bun uses: oven-sh/setup-bun@v2 @@ -28,7 +28,7 @@ jobs: bun-version-file: .bun-version - name: Cache Bun package cache - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: ~/.bun/install/cache # This repo intentionally does not track Bun lockfiles; rotate cache on package/runtime inputs. @@ -47,6 +47,11 @@ jobs: - name: Run type checking run: bun run typecheck + - name: Check v1 OpenAPI contract + run: | + bun run openapi:check + bun run openapi:lint + - name: Check formatting run: bun run format:check @@ -58,7 +63,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup Bun uses: oven-sh/setup-bun@v2 @@ -66,7 +71,7 @@ jobs: bun-version-file: .bun-version - name: Cache Bun package cache - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: ~/.bun/install/cache # This repo intentionally does not track Bun lockfiles; rotate cache on package/runtime inputs. @@ -82,6 +87,9 @@ jobs: - name: Run unit tests run: bun run test -- tests/unit/ --passWithNoTests + - name: Run v1 management API tests + run: bun run test:v1 + # ==================== 集成测试(需要数据库)==================== integration-tests: name: 🔗 Integration Tests @@ -119,13 +127,14 @@ jobs: ADMIN_TOKEN: test-admin-token-for-ci AUTO_MIGRATE: true ENABLE_RATE_LIMIT: true + AUTH_SESSION_TTL_SECONDS: 604800 SESSION_TTL: 300 VITEST_STATEFUL_MAX_WORKERS: 2 VITEST_STATEFUL_MAX_CONCURRENCY: 3 steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup Bun uses: oven-sh/setup-bun@v2 @@ -133,7 +142,7 @@ jobs: bun-version-file: .bun-version - name: Cache Bun package cache - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: ~/.bun/install/cache # This repo intentionally does not track Bun lockfiles; rotate cache on package/runtime inputs. @@ -193,13 +202,14 @@ jobs: AUTO_MIGRATE: true PORT: 13500 ENABLE_RATE_LIMIT: true + AUTH_SESSION_TTL_SECONDS: 604800 SESSION_TTL: 300 VITEST_STATEFUL_MAX_WORKERS: 1 VITEST_STATEFUL_MAX_CONCURRENCY: 3 steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup Bun uses: oven-sh/setup-bun@v2 @@ -207,7 +217,7 @@ jobs: bun-version-file: .bun-version - name: Cache Bun package cache - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: ~/.bun/install/cache # This repo intentionally does not track Bun lockfiles; rotate cache on package/runtime inputs. @@ -224,7 +234,7 @@ jobs: run: bun run db:migrate - name: Cache Next.js build cache - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: ${{ github.workspace }}/.next/cache key: ${{ runner.os }}-nextjs-${{ hashFiles('package.json', '.bun-version') }}-${{ hashFiles('src/**/*.js', 'src/**/*.jsx', 'src/**/*.ts', 'src/**/*.tsx', 'next.config.*', 'tsconfig.json', 'postcss.config.*') }} @@ -243,12 +253,13 @@ jobs: - name: Wait for server ready run: | - timeout 60 bash -c 'until curl -f http://localhost:13500/api/actions/health; do sleep 2; done' + timeout 60 bash -c 'until curl -f http://localhost:13500/api/actions/health && curl -f http://localhost:13500/api/v1/health; do sleep 2; done' - name: Run E2E API tests run: bun run test:e2e env: API_BASE_URL: http://localhost:13500/api/actions + API_E2E_BASE_URL: http://localhost:13500/api/v1 TEST_ADMIN_TOKEN: test-admin-token-for-ci AUTO_CLEANUP_TEST_DATA: true @@ -281,7 +292,7 @@ jobs: - name: Create summary if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository - uses: actions/github-script@v7 + uses: actions/github-script@v9 with: script: | const summary = `## 🧪 测试结果 diff --git a/CLAUDE.md b/CLAUDE.md index 15049fc97..4c628b6a1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -81,13 +81,15 @@ bun run db:studio # Open Drizzle Studio src/ ├── app/ │ ├── [locale]/dashboard/ # Dashboard UI pages -│ ├── api/ # Internal API routes +│ ├── api/ +│ │ ├── v1/ # REST management API + OpenAPI docs +│ │ └── actions/ # Legacy Server Action adapter (deprecated) │ └── v1/ # Proxy API (Claude/OpenAI compatible) │ └── _lib/ │ ├── proxy/ # Core proxy pipeline │ ├── converters/ # Format converters (claude/openai/codex/gemini) │ └── codex/ # Codex CLI adapter -├── actions/ # Server Actions (exposed via /api/actions) +├── actions/ # Server Actions reused by REST handlers ├── lib/ # Core business logic │ ├── session-manager.ts # Session & context caching │ ├── circuit-breaker.ts # Provider health management @@ -117,8 +119,10 @@ Key components: ### API Layer - **Proxy endpoints**: `/v1/messages`, `/v1/chat/completions`, `/v1/responses` -- **Management API**: `/api/actions/{module}/{action}` - Auto-generated OpenAPI docs -- **Docs**: `/api/actions/scalar` (Scalar UI), `/api/actions/docs` (Swagger) +- **Management API**: `/api/v1/*` - RESTful management surface documented by OpenAPI +- **Legacy Management API**: `/api/actions/{module}/{action}` - Deprecated Server Action adapter, retained behind `ENABLE_LEGACY_ACTIONS_API` +- **Docs**: `/api/v1/scalar` (Scalar UI), `/api/v1/docs` (Swagger), `/api/v1/openapi.json` +- **OpenAPI checks**: `bun run test:v1`, `bun run openapi:check`, `bun run openapi:lint` ## Code Conventions diff --git a/Dockerfile b/Dockerfile index f356cccc2..06dc8b4b1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -25,4 +25,11 @@ COPY --from=builder /app/.next/static ./.next/static COPY --from=builder /app/drizzle ./drizzle COPY --from=builder /app/VERSION ./VERSION -CMD ["node", "server.js"] +# Node 诊断报告输出目录(issue #1147) +# 容器外通过 docker-compose volume 挂载到 ./data/reports 持久化 +RUN mkdir -p /app/reports + +# --report-on-fatalerror / --report-uncaught-exception:在 native 段错误或 +# 未捕获异常时写出 JSON 诊断报告(包含原生堆栈、libuv 句柄、JS 堆等) +# --report-directory:指向 /app/reports 以便挂卷持久化 +CMD ["node", "--report-on-fatalerror", "--report-uncaught-exception", "--report-directory=/app/reports", "server.js"] diff --git a/README.md b/README.md index 833a1e318..e72b82e46 100644 --- a/README.md +++ b/README.md @@ -370,7 +370,8 @@ cch doctor # 诊断集群与部署状态 | `ENABLE_API_KEY_VACUUM_FILTER` | `true` | 是否启用 API Key 真空过滤器(仅负向短路无效 key;可设为 `false/0` 关闭用于排查/节省内存)。 | | `ENABLE_API_KEY_REDIS_CACHE` | `true` | 是否启用 API Key 鉴权 Redis 缓存(需 Redis 可用;异常自动回落到 DB)。 | | `API_KEY_AUTH_CACHE_TTL_SECONDS` | `60` | API Key 鉴权缓存 TTL(秒,默认 60,最大 3600)。 | -| `SESSION_TTL` | `300` | Session 缓存时间(秒),影响供应商复用策略。 | +| `AUTH_SESSION_TTL_SECONDS` | `604800` | Web UI 登录态 TTL(秒,默认 7 天);`ADMIN_TOKEN` opaque 登录的签名 cookie 也使用该值。降低该值会按签发时间收紧已签发 admin 签名 cookie 的剩余寿命,且不会延长其原始 `exp`。 | +| `SESSION_TTL` | `300` | 代理请求上下文缓存时间(秒),影响供应商复用策略;不控制 Web UI 登录态。 | | `ENABLE_SECURE_COOKIES` | `true` | 仅 HTTPS 场景能设置 Secure Cookie;HTTP 访问(非 localhost)需改为 `false`。 | | `ENABLE_CIRCUIT_BREAKER_ON_NETWORK_ERRORS` | `false` | 是否将网络错误计入熔断器;开启后能更激进地阻断异常线路。 | | `APP_PORT` | `23000` | 生产端口,可被容器或进程管理器覆盖。 | diff --git a/VERSION b/VERSION index 0a1ffad4b..a3df0a695 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.7.4 +0.8.0 diff --git a/biome.json b/biome.json index 649f42251..fec605641 100644 --- a/biome.json +++ b/biome.json @@ -1,5 +1,5 @@ { - "$schema": "https://biomejs.dev/schemas/2.4.12/schema.json", + "$schema": "https://biomejs.dev/schemas/2.4.15/schema.json", "vcs": { "enabled": true, "clientKind": "git", @@ -113,7 +113,8 @@ "!**/.next", "!**/dist", "!**/drizzle", - "!**/docs-site" + "!**/docs-site", + "!src/lib/api-client/v1/openapi-types.gen.ts" ] } } diff --git a/bunfig.toml b/bunfig.toml new file mode 100644 index 000000000..2d2b3eccb --- /dev/null +++ b/bunfig.toml @@ -0,0 +1,5 @@ +[install] +# Use Yarn registry (Cloudflare CDN-backed) — faster and more reliable globally than +# the default npmjs.org. Picked after npmmirror (Taobao) proved slow from US-region +# runtimes and the default registry showed intermittent slowness. +registry = "https://registry.yarnpkg.com/" diff --git a/deploy/.env.example b/deploy/.env.example index 2d560e8e6..9766b2729 100644 --- a/deploy/.env.example +++ b/deploy/.env.example @@ -23,5 +23,6 @@ ENABLE_RATE_LIMIT=true # 是否启用限流功能(默认:tr REDIS_URL=redis://redis:6379 # Redis 连接地址(Docker Compose 内网络) # Session 配置 -SESSION_TTL=300 # Session 过期时间(秒,默认 300 = 5 分钟) - +# 降低该值会按签发时间收紧已签发 ADMIN_TOKEN 签名 cookie 的剩余寿命,且不会延长其原始 exp。 +AUTH_SESSION_TTL_SECONDS=604800 # Web UI 登录态过期时间(秒,默认 604800 = 7 天,范围 60-31536000) +SESSION_TTL=300 # 代理请求上下文缓存时间(秒,默认 300 = 5 分钟;不控制 Web UI 登录态) diff --git a/deploy/k8s/app/deployment.yaml b/deploy/k8s/app/deployment.yaml index 86258f2ff..b6c02c6c7 100644 --- a/deploy/k8s/app/deployment.yaml +++ b/deploy/k8s/app/deployment.yaml @@ -59,10 +59,10 @@ spec: value: "10" - name: FETCH_CONNECT_TIMEOUT value: "30" - - name: FETCH_HEADERS_TIMEOUT - value: "600" - - name: FETCH_BODY_TIMEOUT - value: "600" + #- name: FETCH_HEADERS_TIMEOUT + # value: "600" + #- name: FETCH_BODY_TIMEOUT + # value: "600" - name: ENABLE_RATE_LIMIT value: "true" - name: ENABLE_API_KEY_REDIS_CACHE @@ -73,6 +73,8 @@ spec: value: "true" - name: ENABLE_PROVIDER_CACHE value: "true" + - name: AUTH_SESSION_TTL_SECONDS + value: "604800" - name: SESSION_TTL value: "300" - name: MESSAGE_REQUEST_WRITE_MODE diff --git a/deploy/k8s/ingress/ingress.yaml b/deploy/k8s/ingress/ingress.yaml index 89d2630a4..61b37ec26 100644 --- a/deploy/k8s/ingress/ingress.yaml +++ b/deploy/k8s/ingress/ingress.yaml @@ -5,8 +5,8 @@ metadata: namespace: {{NAMESPACE}} annotations: # 流式响应需要的长超时和禁用缓冲 (nginx-ingress) - nginx.ingress.kubernetes.io/proxy-read-timeout: "600" - nginx.ingress.kubernetes.io/proxy-send-timeout: "600" + nginx.ingress.kubernetes.io/proxy-read-timeout: "3700" + nginx.ingress.kubernetes.io/proxy-send-timeout: "3700" nginx.ingress.kubernetes.io/proxy-buffering: "off" nginx.ingress.kubernetes.io/proxy-request-buffering: "off" # 客户端 IP 透传依赖 ingress controller 级 forwarded-header / real-ip 配置。 diff --git a/dev/README.md b/dev/README.md index 0599ae9a4..15f9c1a7f 100644 --- a/dev/README.md +++ b/dev/README.md @@ -47,3 +47,4 @@ - `POSTGRES_PORT=35432 REDIS_PORT=36379 make db` - `APP_PORT=24000 make app` - `DB_PASSWORD=postgres make dev` +- `AUTH_SESSION_TTL_SECONDS=86400 make app`(覆盖 Web UI 登录态 TTL;`SESSION_TTL` 只影响代理请求上下文缓存) diff --git a/dev/docker-compose.yaml b/dev/docker-compose.yaml index 7162c413f..49f83cd64 100644 --- a/dev/docker-compose.yaml +++ b/dev/docker-compose.yaml @@ -56,6 +56,7 @@ services: REDIS_URL: redis://redis:6379 AUTO_MIGRATE: ${AUTO_MIGRATE:-true} ENABLE_RATE_LIMIT: ${ENABLE_RATE_LIMIT:-true} + AUTH_SESSION_TTL_SECONDS: ${AUTH_SESSION_TTL_SECONDS:-604800} SESSION_TTL: ${SESSION_TTL:-300} ADMIN_TOKEN: ${ADMIN_TOKEN:-cch-dev-admin} TZ: Asia/Shanghai @@ -67,4 +68,3 @@ services: timeout: 5s retries: 20 start_period: 30s - diff --git a/docker-compose.yaml b/docker-compose.yaml index a1fff2abe..8bf18867d 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -70,6 +70,10 @@ services: TZ: Asia/Shanghai ports: - "${APP_PORT:-23000}:3000" + volumes: + # Node 诊断报告(report.*.json)持久化到宿主机,便于事后排查 SIGSEGV 等 + # native 崩溃。配合 Dockerfile 中 --report-* 启动参数生效(issue #1147) + - ./data/reports:/app/reports restart: unless-stopped healthcheck: test: diff --git a/docs/api-authentication-guide.md b/docs/api-authentication-guide.md index 3e6fd080e..4372f5328 100644 --- a/docs/api-authentication-guide.md +++ b/docs/api-authentication-guide.md @@ -1,295 +1,197 @@ # API 认证使用指南 -## 📋 概述 +## 概述 -Claude Code Hub 的管理 API 端点通过 **HTTP Cookie** 进行认证,Cookie 名称为 `auth-token`。 +Claude Code Hub 的新版管理 API 位于 `/api/v1/*`。它和代理 API `/v1/*` +互相独立,也和已弃用的 Server Action 适配层 `/api/actions/*` 独立。 -公开状态接口 `GET /api/public-status` 和 `GET /api/public-site-meta` 无需认证。详细契约、过滤参数和示例见 [Public Status API](public-status-api.md)。 +新版管理 API 支持三种凭据传递方式: -## 🔐 认证方式 +- Cookie session:`Cookie: auth-token=` +- Bearer token:`Authorization: Bearer ` +- 用户 API Key:`X-Api-Key: ` -### 方法 1:通过 Web UI 登录(推荐) +访问权限按路由分为三层: -这是最简单的认证方式,适合在浏览器中测试 API。 +- `public`:无需认证,例如 `GET /api/v1/public/status`。 +- `read`:接受有效 session、`ADMIN_TOKEN` 或任意有效用户 API Key。 +- `admin`:默认接受有效 session Cookie、opaque session bearer token 和 + `ADMIN_TOKEN`。用户 API Key 仅在 `ENABLE_API_KEY_ADMIN_ACCESS=true` 且属于 + admin 用户时可调用 admin 路由。 -**步骤:** +Cookie 认证的写操作需要 CSRF 保护:先调用 `GET /api/v1/auth/csrf`,再在 +`POST`、`PUT`、`PATCH`、`DELETE` 请求中携带 `X-CCH-CSRF`。Bearer 和 +`X-Api-Key` 请求不需要 CSRF header。 -1. 访问 Claude Code Hub 登录页面(通常是 `http://localhost:23000` 或您部署的域名) -2. 使用您的 API Key 或管理员令牌(ADMIN_TOKEN)登录 -3. 登录成功后,浏览器会自动设置 `auth-token` Cookie(有效期 7 天) -4. 在同一浏览器中访问 API 文档页面即可直接测试(Cookie 自动携带) +生产环境建议显式配置 `CSRF_SECRET`。多副本部署必须让所有实例使用同一个 +`CSRF_SECRET`,否则一个实例签发的 cookie 写操作 token 可能无法被另一个实例验证。 -**优点:** -- ✅ 无需手动处理 Cookie -- ✅ 可以直接在 Scalar/Swagger UI 中测试 API -- ✅ 浏览器自动管理 Cookie 的生命周期 +旧版 `/api/actions/*` 仍可用但已弃用,响应会带标准 `Deprecation`、`Sunset` +与指向 `/api/v1/openapi.json` 的 successor `Link`。设置 +`ENABLE_LEGACY_ACTIONS_API=false` 后,旧 action 执行接口返回 +`410 application/problem+json`。 -### 方法 2:手动获取 Cookie(用于脚本或编程调用) +## Cookie Session -如果需要在脚本、自动化工具或编程环境中调用 API,需要手动获取并设置 Cookie。 +适合浏览器内测试和 Scalar/Swagger UI。 -**步骤:** +1. 访问 Claude Code Hub 登录页面。 +2. 使用 `ADMIN_TOKEN` 或允许 Web UI 登录的用户 API Key 登录。 +3. 浏览器会设置 `auth-token` Cookie。 +4. 在同一浏览器访问 `/api/v1/scalar` 或 `/api/v1/docs`,文档页会自动携带 + Cookie。 -1. 先通过浏览器登录 Claude Code Hub -2. 打开浏览器开发者工具(按 F12 键) -3. 切换到以下标签页之一: - - Chrome/Edge: `Application` → `Cookies` - - Firefox: `Storage` → `Cookies` - - Safari: `Storage` → `Cookies` -4. 在 Cookie 列表中找到 `auth-token` -5. 复制该 Cookie 的值(例如:`cch_1234567890abcdef...`) -6. 在 API 调用中通过 HTTP Header 携带该 Cookie - -**优点:** -- ✅ 适合自动化脚本和后台服务 -- ✅ 可以在任何支持 HTTP 请求的环境中使用 -- ✅ 便于集成到 CI/CD 流程 - -## 💻 使用示例 - -### curl 示例 +Cookie 写操作示例: ```bash -# 基本用法:通过 Cookie Header 认证 -curl -X POST 'http://localhost:23000/api/actions/users/getUsers' \ - -H 'Content-Type: application/json' \ - -H 'Cookie: auth-token=your-token-here' \ - -d '{}' - -# 使用 -b 参数(curl 的 Cookie 简写) -curl -X POST 'http://localhost:23000/api/actions/users/getUsers' \ - -H 'Content-Type: application/json' \ - -b 'auth-token=your-token-here' \ - -d '{}' +csrf_token="$(curl -s 'http://localhost:13500/api/v1/auth/csrf' \ + -b 'auth-token=your-session-token' | jq -r '.csrfToken')" -# 从文件读取 Cookie -curl -X POST 'http://localhost:23000/api/actions/users/getUsers' \ +curl -X PATCH 'http://localhost:13500/api/v1/users/1' \ -H 'Content-Type: application/json' \ - -b cookies.txt \ - -d '{}' -``` - -### JavaScript (fetch) 示例 - -#### 浏览器环境(推荐) - -```javascript -// Cookie 自动携带,无需手动设置 -fetch('/api/actions/users/getUsers', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - credentials: 'include', // 重要:告诉浏览器携带 Cookie - body: JSON.stringify({}), -}) - .then(res => res.json()) - .then(data => { - if (data.ok) { - console.log('成功:', data.data); - } else { - console.error('失败:', data.error); - } - }); + -H "X-CCH-CSRF: ${csrf_token}" \ + -b 'auth-token=your-session-token' \ + -d '{"note":"updated by REST API"}' ``` -#### Node.js 环境 +浏览器 fetch 示例: ```javascript -const fetch = require('node-fetch'); +const csrf = await fetch("/api/v1/auth/csrf", { + credentials: "include", +}).then((res) => res.json()); -// 手动设置 Cookie -fetch('http://localhost:23000/api/actions/users/getUsers', { - method: 'POST', +const user = await fetch("/api/v1/users/1", { + method: "PATCH", + credentials: "include", headers: { - 'Content-Type': 'application/json', - 'Cookie': 'auth-token=your-token-here', + "Content-Type": "application/json", + "X-CCH-CSRF": csrf.csrfToken, }, - body: JSON.stringify({}), -}) - .then(res => res.json()) - .then(data => { - if (data.ok) { - console.log('成功:', data.data); - } else { - console.error('失败:', data.error); - } - }); -``` + body: JSON.stringify({ note: "updated by REST API" }), +}).then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); +}); -### Python 示例 +console.log(user); +``` -#### 使用 requests 库 +## Bearer Token -```python -import requests +适合脚本、CLI 和服务端 SDK。 -# 方式 1:使用 Session(推荐,自动管理 Cookie) -session = requests.Session() -session.cookies.set('auth-token', 'your-token-here') +```bash +curl 'http://localhost:13500/api/v1/users?limit=20' \ + -H 'Authorization: Bearer your-session-or-admin-token' +``` -response = session.post( - 'http://localhost:23000/api/actions/users/getUsers', - json={}, -) +Node.js 示例: -if response.json()['ok']: - print('成功:', response.json()['data']) -else: - print('失败:', response.json()['error']) +```javascript +const response = await fetch("http://localhost:13500/api/v1/users?limit=20", { + headers: { + Authorization: `Bearer ${process.env.CCH_TOKEN}`, + }, +}); -# 方式 2:直接在 headers 中设置 Cookie -response = requests.post( - 'http://localhost:23000/api/actions/users/getUsers', - json={}, - headers={ - 'Content-Type': 'application/json', - 'Cookie': 'auth-token=your-token-here' - } -) -``` +if (!response.ok) { + const problem = await response.json(); + throw new Error(`${problem.errorCode}: ${problem.detail}`); +} -#### 使用 httpx 库(异步支持) - -```python -import httpx - -async def get_users(): - async with httpx.AsyncClient() as client: - response = await client.post( - 'http://localhost:23000/api/actions/users/getUsers', - json={}, - headers={ - 'Cookie': 'auth-token=your-token-here' - } - ) - return response.json() - -# 使用示例 -import asyncio -result = asyncio.run(get_users()) +const page = await response.json(); +console.log(page.users ?? page.items ?? page); ``` -### Go 示例 +## X-Api-Key -```go -package main +适合第三方工具读取自身范围内的数据。 -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "net/http" -) - -func main() { - url := "http://localhost:23000/api/actions/users/getUsers" +```bash +curl 'http://localhost:13500/api/v1/me/quota' \ + -H 'X-Api-Key: your-user-api-key' +``` - // 创建请求体 - body := bytes.NewBuffer([]byte("{}")) +admin 路由默认不接受用户 API Key。确需允许第三方管理工具通过 admin 用户 +API Key 调用管理端接口时,必须显式设置: - // 创建请求 - req, err := http.NewRequest("POST", url, body) - if err != nil { - panic(err) - } +```bash +ENABLE_API_KEY_ADMIN_ACCESS=true +``` - // 设置 Headers - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Cookie", "auth-token=your-token-here") +开启后仍要求该 API Key 对应的用户角色为 `admin`。普通用户 API Key 不能调 +admin 路由。 - // 发送请求 - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - panic(err) - } - defer resp.Body.Close() +## 响应格式 - // 解析响应 - respBody, _ := io.ReadAll(resp.Body) - var result map[string]interface{} - json.Unmarshal(respBody, &result) +成功响应直接返回资源或列表对象,不再使用 legacy `{ ok, data }` 包装。 - if result["ok"].(bool) { - fmt.Println("成功:", result["data"]) - } else { - fmt.Println("失败:", result["error"]) +```json +{ + "users": [ + { + "id": 1, + "name": "admin", + "role": "admin" } + ], + "nextCursor": null, + "hasMore": false } ``` -## ⚠️ 常见问题 - -### 1. 401 Unauthorized - "未认证" - -**原因:** 缺少 `auth-token` Cookie - -**解决方法:** -- 确认请求中包含了 `Cookie: auth-token=...` Header -- 检查 Cookie 值是否正确(不要包含额外的空格或换行符) -- 在浏览器环境确保设置了 `credentials: 'include'` - -### 2. 401 Unauthorized - "认证无效或已过期" - -**原因:** Cookie 无效、已过期或已被撤销 - -**解决方法:** -- 重新登录获取新的 `auth-token` -- 检查用户账号是否被禁用 -- 确认 API Key 是否设置了 `canLoginWebUi` 权限 - -### 3. 403 Forbidden - "权限不足" - -**原因:** 当前用户没有访问该端点的权限 +失败响应使用 `application/problem+json`: + +```json +{ + "type": "urn:claude-code-hub:problem:auth.forbidden", + "title": "Forbidden", + "status": 403, + "detail": "Admin access is required.", + "instance": "/api/v1/providers", + "errorCode": "auth.forbidden", + "errorParams": {} +} +``` -**解决方法:** -- 检查端点是否需要管理员权限(标记为 `[管理员]`) -- 使用管理员账号登录(使用 `ADMIN_TOKEN` 或具有 admin 角色的用户) +前端和第三方客户端应优先使用 `errorCode` 和 `errorParams` 做错误分支与本地化, +不要依赖 `detail` 的展示语言。 -### 4. 浏览器环境 Cookie 未自动携带 +## 常见问题 -**原因:** 未设置 `credentials: 'include'` +### 401 Unauthorized -**解决方法:** -```javascript -fetch('/api/actions/users/getUsers', { - credentials: 'include', // 添加这一行 - // ... 其他配置 -}) -``` +请求没有携带凭据,或凭据无效、过期、已撤销。 -### 5. 跨域请求 Cookie 问题 +处理方式: -**原因:** CORS 策略限制 +- Cookie 模式确认请求携带 `Cookie: auth-token=...`。 +- 浏览器 fetch 确认设置 `credentials: "include"`。 +- Bearer 模式确认使用 `Authorization: Bearer `。 +- `X-Api-Key` 模式确认 key 未被禁用、未过期。 -**解决方法:** -- 确保 API 服务器配置了正确的 CORS 策略 -- 在前端请求中设置 `credentials: 'include'` -- 使用相同域名或配置服务器允许跨域 Cookie +### 403 Forbidden -## 🔒 安全最佳实践 +当前身份没有访问该路由的权限。 -1. **不要在公共场合分享 Cookie 值** - - `auth-token` 相当于您的登录凭证 - - 泄露后他人可以冒充您的身份操作系统 +处理方式: -2. **定期更换 API Key** - - Cookie 有效期为 7 天 - - 到期后需要重新登录 +- admin 路由使用管理员 session 或 `ADMIN_TOKEN`。 +- 用户 API Key 调 admin 路由前确认 `ENABLE_API_KEY_ADMIN_ACCESS=true`,且 key + 所属用户为 admin。 +- Cookie 写操作确认已携带当前 session 对应的 `X-CCH-CSRF`。 -3. **使用 HTTPS** - - 生产环境务必启用 HTTPS - - 确保 `ENABLE_SECURE_COOKIES=true`(默认值) +### 404 Not Found -4. **环境变量管理** - - 将 Cookie 值存储在环境变量中 - - 不要硬编码在代码仓库中 +资源不存在,或该资源属于已隐藏/已弃用类型。新版 `/api/v1` 不暴露 +`claude-auth` 与 `gemini-cli` provider 类型。 -## 📚 相关资源 +## 相关资源 -- [OpenAPI 文档](/api/actions/docs) - Swagger UI -- [Scalar API 文档](/api/actions/scalar) - 现代化 API 文档界面 -- [Public Status API](public-status-api.md) - 公开状态接口与响应示例 -- [GitHub 仓库](https://github.com/ding113/claude-code-hub) - 查看源码和更多文档 +- OpenAPI JSON:`/api/v1/openapi.json` +- Swagger UI:`/api/v1/docs` +- Scalar UI:`/api/v1/scalar` +- Public Status API:`public-status-api.md` +- API Key admin access 安全说明:`security/api-key-admin-access.md` +- GitHub 仓库:`https://github.com/ding113/claude-code-hub` diff --git a/docs/api/v1/README.md b/docs/api/v1/README.md new file mode 100644 index 000000000..ee5d467b7 --- /dev/null +++ b/docs/api/v1/README.md @@ -0,0 +1,64 @@ +# Management REST API v1 + +`/api/v1/*` is the REST management API for Claude Code Hub. Its HTTP surface is +mounted separately from: + +- `/v1/*`: Claude/OpenAI-compatible proxy endpoints. +- `/api/actions/*`: legacy Server Action adapter, now deprecated. + +During this migration, v1 handlers intentionally delegate to the existing +server-side business actions so REST, OpenAPI, audit logging, and frontend +traffic can converge without reimplementing business rules. + +## Documentation + +- OpenAPI JSON: `/api/v1/openapi.json` +- Scalar UI: `/api/v1/scalar` +- Swagger UI: `/api/v1/docs` + +Every response includes `X-API-Version: 1.0.0`. + +## Authentication + +The API accepts three credential transports: + +- Browser session cookie: `auth-token=`. +- Bearer token: `Authorization: Bearer `. +- API key header: `X-Api-Key: `. + +Access tiers: + +- `public`: no authentication required. Example: `GET /api/v1/public/status`. +- `read`: accepts a valid session, `ADMIN_TOKEN`, or any valid user API key. +- `admin`: accepts a valid session cookie, opaque session bearer token, and `ADMIN_TOKEN` by default. User API keys are rejected unless `ENABLE_API_KEY_ADMIN_ACCESS=true` and the key belongs to an admin user. + +Cookie-authenticated mutations must first call `GET /api/v1/auth/csrf` and send the returned token in `X-CCH-CSRF`. +Set `CSRF_SECRET` in production, and use the same value on every replica. + +## Error Format + +Failures use RFC 9457-style `application/problem+json`: + +```json +{ + "type": "urn:claude-code-hub:problem:auth.forbidden", + "title": "Forbidden", + "status": 403, + "detail": "Admin access is required.", + "instance": "/api/v1/providers", + "errorCode": "auth.forbidden", + "errorParams": {} +} +``` + +Frontend code should localize by `errorCode` and `errorParams`, not display `detail` directly. + +## Legacy Actions API + +`/api/actions/*` remains available by default with deprecation headers: + +- `Deprecation: @1777420800` style structured-field date for April 29, 2026 +- `Sunset: Thu, 31 Dec 2026 00:00:00 GMT` style HTTP date unless overridden by `LEGACY_ACTIONS_SUNSET_DATE` +- `Link: ; rel="successor-version"` + +Set `ENABLE_LEGACY_ACTIONS_API=false` to return `410 Gone` for legacy action execution. Legacy docs stay visible unless `LEGACY_ACTIONS_DOCS_MODE=hidden`. diff --git a/docs/api/v1/migration-guide.md b/docs/api/v1/migration-guide.md new file mode 100644 index 000000000..f0db4c6cd --- /dev/null +++ b/docs/api/v1/migration-guide.md @@ -0,0 +1,73 @@ +# Management API v1 Migration Guide + +This guide maps the deprecated `/api/actions/*` management surface to the new REST +management API under `/api/v1/*`. + +Closes #1123 by covering provider search and true provider key reveal through documented REST +endpoints. + +## Boundaries + +- `/api/v1/*` is the management REST API. +- `/v1/*` remains the Claude/OpenAI-compatible proxy API. +- `/api/actions/*` remains available by default, but is deprecated and guarded by + `ENABLE_LEGACY_ACTIONS_API`. + +## Authentication Changes + +- Public endpoints, such as `GET /api/v1/public/status`, require no credentials. +- Read endpoints accept a valid session, `ADMIN_TOKEN`, or user API key. +- Admin endpoints accept sessions and `ADMIN_TOKEN` by default. +- Admin user API keys can call admin endpoints only when `ENABLE_API_KEY_ADMIN_ACCESS=true`. +- Cookie-authenticated mutations require `X-CCH-CSRF` from `GET /api/v1/auth/csrf`. + +## Endpoint Mapping + +```text +/api/actions/users/getUsersBatchCore -> GET /api/v1/users +/api/actions/users/addUser -> POST /api/v1/users +/api/actions/users/editUser -> PATCH /api/v1/users/{id} +/api/actions/users/removeUser -> DELETE /api/v1/users/{id} +/api/actions/users/searchUsersForFilter -> GET /api/v1/users:filter-search +/api/actions/keys/getKeys -> GET /api/v1/users/{userId}/keys +/api/actions/keys/addKey -> POST /api/v1/users/{userId}/keys +/api/actions/keys/editKey -> PATCH /api/v1/keys/{keyId} +/api/actions/providers/getProviders -> GET /api/v1/providers +/api/actions/providers/addProvider -> POST /api/v1/providers +/api/actions/providers/editProvider -> PATCH /api/v1/providers/{providerId} +/api/actions/providers/removeProvider -> DELETE /api/v1/providers/{providerId} +/api/actions/providers/getUnmaskedProviderKey -> GET /api/v1/providers/{providerId}/key:reveal +/api/actions/provider-endpoints/getProviderVendors -> GET /api/v1/provider-vendors +/api/actions/model-prices/getModelPricesPaginated -> GET /api/v1/model-prices +/api/actions/usage-logs/getUsageLogsBatch -> GET /api/v1/usage-logs +/api/actions/audit-logs/getAuditLogsBatch -> GET /api/v1/audit-logs +/api/actions/active-sessions/getActiveSessions -> GET /api/v1/sessions +/api/actions/my-usage/getMyQuota -> GET /api/v1/me/quota +/api/actions/system-config/fetchSystemSettings -> GET /api/v1/system/settings +/api/actions/public-status/getPublicStatusSettings -> GET /api/v1/public/status +``` + +The complete action coverage inventory is enforced by +`src/lib/api/v1/action-migration-matrix.ts` and +`tests/unit/api/v1/action-migration-matrix.test.ts`. + +## Provider Search And Key Reveal + +Issue #1123 asked for management API support for provider search and true key lookup. + +- Provider search/listing: `GET /api/v1/providers?q=...` +- True key lookup: `GET /api/v1/providers/{providerId}/key:reveal` + +`key:reveal` is admin-tier, emits audit context through the shared action bridge, and returns +`Cache-Control: no-store` so clients do not persist revealed secrets by accident. + +## Legacy Deprecation + +Legacy action routes include: + +- `Deprecation` +- `Sunset` +- `Link: ; rel="successor-version"` + +Set `ENABLE_LEGACY_ACTIONS_API=false` to return `410 Gone` problem responses for legacy action +execution while keeping the new `/api/v1/*` API active. diff --git a/docs/k8s-deployment.md b/docs/k8s-deployment.md index b94acf195..84826a4ea 100644 --- a/docs/k8s-deployment.md +++ b/docs/k8s-deployment.md @@ -213,6 +213,65 @@ bash scripts/deploy-k8s.sh --replicas 3 --hpa-min 3 --hpa-max 10 -y 默认模板保留 `replicas=2`,但 `AUTO_MIGRATE` 入口 `src/instrumentation.ts` 会先获取 PostgreSQL advisory lock, 因此首次多副本启动时迁移会串行执行。如果你更关心首启速度,也可以先用 `--replicas 1` 部署,确认健康后再扩容。 +### Codex `/v1/responses` WebSocket 反代 + +`/v1/responses` 端点对 Codex 客户端会走 **WebSocket 升级**(其余路径仍是 HTTP)。 +同一条连接会连续承载多个 `response.create`,并依赖上游连接本地缓存支持 +`store=false` + `previous_response_id` 续接。反代必须显式放行 Upgrade/Connection +头并放宽 idle timeout,否则会出现 +`stream disconnected before completion: WebSocket protocol error: Connection reset +without closing handshake`(直连 CCH 正常,经反代失败时多半是这一项)。 + +**Nginx** + +建议在 `http {}` 中先定义连接头映射,避免普通 HTTP 请求也被写死为 +`Connection: upgrade`: + +```nginx +map $http_upgrade $connection_upgrade { + default upgrade; + '' close; +} +``` + +```nginx +location /v1/responses { + proxy_pass http://cch_upstream; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_read_timeout 3700s; # OpenAI Responses WS 连接上限约 60 分钟 + proxy_send_timeout 3700s; + proxy_connect_timeout 300s; + proxy_buffering off; # 关闭缓冲,SSE / WS 才能实时 + proxy_request_buffering off; + client_max_body_size 100m; # 避免 Codex 大上下文在反代层被截断 +} +``` + +**ingress-nginx (Kubernetes)** + +`deploy/k8s/ingress/ingress.yaml` 已经默认带这两条注解;若你自定义 Ingress +要保留: + +```yaml +metadata: + annotations: + nginx.ingress.kubernetes.io/proxy-read-timeout: "3700" + nginx.ingress.kubernetes.io/proxy-send-timeout: "3700" + nginx.ingress.kubernetes.io/proxy-buffering: "off" + nginx.ingress.kubernetes.io/proxy-request-buffering: "off" +``` + +**Cloudflare / 其它 CDN** + +WebSocket 必须在 CDN 控制台显式开启;同时 `cache-everything` 一类规则会绕过 +WS,务必把 `/v1/responses` 排除在缓存规则之外。 + ### 客户端 IP 透传 - 默认 k8s 部署保持 `main -> ghcr.io/ding113/claude-code-hub:latest`;只有显式传 diff --git a/docs/public-status-api.md b/docs/public-status-api.md index 9988aee92..964af5fec 100644 --- a/docs/public-status-api.md +++ b/docs/public-status-api.md @@ -13,7 +13,7 @@ OpenAPI 与在线文档入口: - 原始 OpenAPI JSON:`/api/actions/openapi.json` - Swagger UI:`/api/actions/docs` -- Scalar UI:`/api/actions/scalar` +- Scalar UI:`/api/v1/scalar` ## `GET /api/public-status` diff --git a/docs/security/api-key-admin-access.md b/docs/security/api-key-admin-access.md new file mode 100644 index 000000000..982a91b56 --- /dev/null +++ b/docs/security/api-key-admin-access.md @@ -0,0 +1,36 @@ +# API Key Admin Access + +`ENABLE_API_KEY_ADMIN_ACCESS` controls whether user-issued API keys can call admin-tier `/api/v1/*` routes. + +Default: + +```bash +ENABLE_API_KEY_ADMIN_ACCESS=false +``` + +With the default, admin-tier routes accept: + +- Browser session cookies. +- Opaque session bearer tokens. +- `ADMIN_TOKEN`. + +They reject user API keys, even when the key belongs to an admin user and is sent with `Authorization: Bearer`. + +When enabled: + +```bash +ENABLE_API_KEY_ADMIN_ACCESS=true +``` + +Admin-tier routes also accept user API keys whose owner has `role=admin`. + +## Security Tradeoff + +Enabling this flag makes third-party automation easier, but it also widens the blast radius of any leaked admin API key. Treat admin user keys as production secrets: + +- Use separate keys for automation. +- Rotate keys regularly. +- Avoid sharing keys between tools. +- Keep `provider key reveal` responses out of logs and caches. + +Sensitive reveal endpoints set `Cache-Control: no-store, no-cache, must-revalidate` and `Pragma: no-cache`, but clients should still avoid persisting returned secrets. diff --git a/drizzle/0099_nervous_squadron_sinister.sql b/drizzle/0099_nervous_squadron_sinister.sql new file mode 100644 index 000000000..cc18ecf2d --- /dev/null +++ b/drizzle/0099_nervous_squadron_sinister.sql @@ -0,0 +1 @@ +ALTER TABLE "providers" ADD COLUMN "custom_headers" jsonb; \ No newline at end of file diff --git a/drizzle/0100_equal_expediter.sql b/drizzle/0100_equal_expediter.sql new file mode 100644 index 000000000..f3d55d810 --- /dev/null +++ b/drizzle/0100_equal_expediter.sql @@ -0,0 +1 @@ +ALTER TABLE "system_settings" ADD COLUMN "fake_streaming_whitelist" jsonb; \ No newline at end of file diff --git a/drizzle/0101_worthless_gauntlet.sql b/drizzle/0101_worthless_gauntlet.sql new file mode 100644 index 000000000..cc8752e2a --- /dev/null +++ b/drizzle/0101_worthless_gauntlet.sql @@ -0,0 +1 @@ +ALTER TABLE "system_settings" ADD COLUMN IF NOT EXISTS "enable_openai_responses_websocket" boolean DEFAULT true NOT NULL; diff --git a/drizzle/0102_useful_lionheart.sql b/drizzle/0102_useful_lionheart.sql new file mode 100644 index 000000000..314f98869 --- /dev/null +++ b/drizzle/0102_useful_lionheart.sql @@ -0,0 +1 @@ +ALTER TABLE "system_settings" ADD COLUMN IF NOT EXISTS "bill_non_successful_requests" boolean DEFAULT false NOT NULL; \ No newline at end of file diff --git a/drizzle/0103_small_master_mold.sql b/drizzle/0103_small_master_mold.sql new file mode 100644 index 000000000..149e34008 --- /dev/null +++ b/drizzle/0103_small_master_mold.sql @@ -0,0 +1,42 @@ +-- Add `endpoint` to the three usage_ledger SUM(cost_usd) covering indexes so the +-- LEDGER_BILLING_CONDITION non-billing-endpoint filter stays index-only (regression from #1091). +-- +-- usage_ledger is a high-write table. A plain CREATE INDEX holds a SHARE lock that +-- blocks writes for the whole rebuild, and Drizzle's migrator runs inside a +-- transaction so CREATE INDEX CONCURRENTLY cannot be inlined here. +-- +-- To rebuild without write-blocking on a large / busy database, run the following +-- BEFORE this migration (psql, outside a transaction), once per index -- example +-- for idx_usage_ledger_user_cost_cover: +-- DROP INDEX CONCURRENTLY IF EXISTS "idx_usage_ledger_user_cost_cover"; +-- CREATE INDEX CONCURRENTLY "idx_usage_ledger_user_cost_cover" +-- ON "usage_ledger" ("user_id","created_at","cost_usd","endpoint") +-- WHERE "blocked_by" IS NULL; +-- The guarded blocks below detect the already-fixed shape via pg_indexes and +-- become a no-op. +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM pg_indexes + WHERE indexname = 'idx_usage_ledger_key_cost' AND indexdef LIKE '%endpoint%' + ) THEN + DROP INDEX IF EXISTS "idx_usage_ledger_key_cost"; + CREATE INDEX IF NOT EXISTS "idx_usage_ledger_key_cost" ON "usage_ledger" USING btree ("key","created_at","cost_usd","endpoint") WHERE "usage_ledger"."blocked_by" IS NULL; + END IF; + + IF NOT EXISTS ( + SELECT 1 FROM pg_indexes + WHERE indexname = 'idx_usage_ledger_user_cost_cover' AND indexdef LIKE '%endpoint%' + ) THEN + DROP INDEX IF EXISTS "idx_usage_ledger_user_cost_cover"; + CREATE INDEX IF NOT EXISTS "idx_usage_ledger_user_cost_cover" ON "usage_ledger" USING btree ("user_id","created_at","cost_usd","endpoint") WHERE "usage_ledger"."blocked_by" IS NULL; + END IF; + + IF NOT EXISTS ( + SELECT 1 FROM pg_indexes + WHERE indexname = 'idx_usage_ledger_provider_cost_cover' AND indexdef LIKE '%endpoint%' + ) THEN + DROP INDEX IF EXISTS "idx_usage_ledger_provider_cost_cover"; + CREATE INDEX IF NOT EXISTS "idx_usage_ledger_provider_cost_cover" ON "usage_ledger" USING btree ("final_provider_id","created_at","cost_usd","endpoint") WHERE "usage_ledger"."blocked_by" IS NULL; + END IF; +END $$; diff --git a/drizzle/meta/0099_snapshot.json b/drizzle/meta/0099_snapshot.json new file mode 100644 index 000000000..1a312fc42 --- /dev/null +++ b/drizzle/meta/0099_snapshot.json @@ -0,0 +1,4477 @@ +{ + "id": "4c0c61bd-9bd1-4468-8211-311faa7379fa", + "prevId": "6014bb32-638d-4ca1-bb4b-16d9f3fe0e01", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.audit_log": { + "name": "audit_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "action_category": { + "name": "action_category", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "action_type": { + "name": "action_type", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false + }, + "target_id": { + "name": "target_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "target_name": { + "name": "target_name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "before_value": { + "name": "before_value", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "after_value": { + "name": "after_value", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "operator_user_id": { + "name": "operator_user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "operator_user_name": { + "name": "operator_user_name", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "operator_key_id": { + "name": "operator_key_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "operator_key_name": { + "name": "operator_key_name", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "operator_ip": { + "name": "operator_ip", + "type": "varchar(45)", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "success": { + "name": "success", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_audit_log_category_created_at": { + "name": "idx_audit_log_category_created_at", + "columns": [ + { + "expression": "action_category", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_log_operator_user_created_at": { + "name": "idx_audit_log_operator_user_created_at", + "columns": [ + { + "expression": "operator_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"audit_log\".\"operator_user_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_log_operator_ip_created_at": { + "name": "idx_audit_log_operator_ip_created_at", + "columns": [ + { + "expression": "operator_ip", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"audit_log\".\"operator_ip\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_log_target": { + "name": "idx_audit_log_target", + "columns": [ + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"audit_log\".\"target_type\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_log_created_at_id": { + "name": "idx_audit_log_created_at_id", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.error_rules": { + "name": "error_rules", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pattern": { + "name": "pattern", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "match_type": { + "name": "match_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'regex'" + }, + "category": { + "name": "category", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "override_response": { + "name": "override_response", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "override_status_code": { + "name": "override_status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_error_rules_enabled": { + "name": "idx_error_rules_enabled", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "unique_pattern": { + "name": "unique_pattern", + "columns": [ + { + "expression": "pattern", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_category": { + "name": "idx_category", + "columns": [ + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_match_type": { + "name": "idx_match_type", + "columns": [ + { + "expression": "match_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.keys": { + "name": "keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "can_login_web_ui": { + "name": "can_login_web_ui", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "limit_5h_usd": { + "name": "limit_5h_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_5h_reset_mode": { + "name": "limit_5h_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'rolling'" + }, + "limit_daily_usd": { + "name": "limit_daily_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "daily_reset_mode": { + "name": "daily_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'fixed'" + }, + "daily_reset_time": { + "name": "daily_reset_time", + "type": "varchar(5)", + "primaryKey": false, + "notNull": true, + "default": "'00:00'" + }, + "limit_weekly_usd": { + "name": "limit_weekly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_monthly_usd": { + "name": "limit_monthly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_total_usd": { + "name": "limit_total_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "cost_reset_at": { + "name": "cost_reset_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "limit_concurrent_sessions": { + "name": "limit_concurrent_sessions", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "provider_group": { + "name": "provider_group", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false, + "default": "'default'" + }, + "cache_ttl_preference": { + "name": "cache_ttl_preference", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_keys_user_id": { + "name": "idx_keys_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_keys_key": { + "name": "idx_keys_key", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_keys_created_at": { + "name": "idx_keys_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_keys_deleted_at": { + "name": "idx_keys_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.message_request": { + "name": "message_request", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cost_usd": { + "name": "cost_usd", + "type": "numeric(21, 15)", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "cost_multiplier": { + "name": "cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": false + }, + "group_cost_multiplier": { + "name": "group_cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": false + }, + "cost_breakdown": { + "name": "cost_breakdown", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "request_sequence": { + "name": "request_sequence", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 1 + }, + "provider_chain": { + "name": "provider_chain", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status_code": { + "name": "status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "api_type": { + "name": "api_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "endpoint": { + "name": "endpoint", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "original_model": { + "name": "original_model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "actual_response_model": { + "name": "actual_response_model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "input_tokens": { + "name": "input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "output_tokens": { + "name": "output_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "ttfb_ms": { + "name": "ttfb_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cache_creation_input_tokens": { + "name": "cache_creation_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_read_input_tokens": { + "name": "cache_read_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_creation_5m_input_tokens": { + "name": "cache_creation_5m_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_creation_1h_input_tokens": { + "name": "cache_creation_1h_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_ttl_applied": { + "name": "cache_ttl_applied", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "context_1m_applied": { + "name": "context_1m_applied", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "swap_cache_ttl_applied": { + "name": "swap_cache_ttl_applied", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "special_settings": { + "name": "special_settings", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_stack": { + "name": "error_stack", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_cause": { + "name": "error_cause", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "blocked_by": { + "name": "blocked_by", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "blocked_reason": { + "name": "blocked_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "client_ip": { + "name": "client_ip", + "type": "varchar(45)", + "primaryKey": false, + "notNull": false + }, + "messages_count": { + "name": "messages_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_message_request_user_date_cost": { + "name": "idx_message_request_user_date_cost", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_user_created_at_cost_stats": { + "name": "idx_message_request_user_created_at_cost_stats", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_user_query": { + "name": "idx_message_request_user_query", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_provider_created_at_active": { + "name": "idx_message_request_provider_created_at_active", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_provider_created_at_finalized_active": { + "name": "idx_message_request_provider_created_at_finalized_active", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"status_code\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_session_id": { + "name": "idx_message_request_session_id", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_session_id_prefix": { + "name": "idx_message_request_session_id_prefix", + "columns": [ + { + "expression": "\"session_id\" varchar_pattern_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_session_seq": { + "name": "idx_message_request_session_seq", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_sequence", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_endpoint": { + "name": "idx_message_request_endpoint", + "columns": [ + { + "expression": "endpoint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_blocked_by": { + "name": "idx_message_request_blocked_by", + "columns": [ + { + "expression": "blocked_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_provider_id": { + "name": "idx_message_request_provider_id", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_user_id": { + "name": "idx_message_request_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_key": { + "name": "idx_message_request_key", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_key_created_at_id": { + "name": "idx_message_request_key_created_at_id", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_key_model_active": { + "name": "idx_message_request_key_model_active", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"model\" IS NOT NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_key_endpoint_active": { + "name": "idx_message_request_key_endpoint_active", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "endpoint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"endpoint\" IS NOT NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_created_at_id_active": { + "name": "idx_message_request_created_at_id_active", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_model_active": { + "name": "idx_message_request_model_active", + "columns": [ + { + "expression": "model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"model\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_status_code_active": { + "name": "idx_message_request_status_code_active", + "columns": [ + { + "expression": "status_code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"status_code\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_created_at": { + "name": "idx_message_request_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_deleted_at": { + "name": "idx_message_request_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_key_last_active": { + "name": "idx_message_request_key_last_active", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_key_cost_active": { + "name": "idx_message_request_key_cost_active", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_session_user_info": { + "name": "idx_message_request_session_user_info", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_client_ip_created_at": { + "name": "idx_message_request_client_ip_created_at", + "columns": [ + { + "expression": "client_ip", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"client_ip\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.model_prices": { + "name": "model_prices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "model_name": { + "name": "model_name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "price_data": { + "name": "price_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'litellm'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_model_prices_latest": { + "name": "idx_model_prices_latest", + "columns": [ + { + "expression": "model_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_model_prices_model_name": { + "name": "idx_model_prices_model_name", + "columns": [ + { + "expression": "model_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_model_prices_created_at": { + "name": "idx_model_prices_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_model_prices_source": { + "name": "idx_model_prices_source", + "columns": [ + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.notification_settings": { + "name": "notification_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "use_legacy_mode": { + "name": "use_legacy_mode", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "circuit_breaker_enabled": { + "name": "circuit_breaker_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "circuit_breaker_webhook": { + "name": "circuit_breaker_webhook", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "daily_leaderboard_enabled": { + "name": "daily_leaderboard_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "daily_leaderboard_webhook": { + "name": "daily_leaderboard_webhook", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "daily_leaderboard_time": { + "name": "daily_leaderboard_time", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false, + "default": "'09:00'" + }, + "daily_leaderboard_top_n": { + "name": "daily_leaderboard_top_n", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 5 + }, + "cost_alert_enabled": { + "name": "cost_alert_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "cost_alert_webhook": { + "name": "cost_alert_webhook", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "cost_alert_threshold": { + "name": "cost_alert_threshold", + "type": "numeric(5, 2)", + "primaryKey": false, + "notNull": false, + "default": "'0.80'" + }, + "cost_alert_check_interval": { + "name": "cost_alert_check_interval", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 60 + }, + "cache_hit_rate_alert_enabled": { + "name": "cache_hit_rate_alert_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "cache_hit_rate_alert_webhook": { + "name": "cache_hit_rate_alert_webhook", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "cache_hit_rate_alert_window_mode": { + "name": "cache_hit_rate_alert_window_mode", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false, + "default": "'auto'" + }, + "cache_hit_rate_alert_check_interval": { + "name": "cache_hit_rate_alert_check_interval", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 5 + }, + "cache_hit_rate_alert_historical_lookback_days": { + "name": "cache_hit_rate_alert_historical_lookback_days", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 7 + }, + "cache_hit_rate_alert_min_eligible_requests": { + "name": "cache_hit_rate_alert_min_eligible_requests", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 20 + }, + "cache_hit_rate_alert_min_eligible_tokens": { + "name": "cache_hit_rate_alert_min_eligible_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "cache_hit_rate_alert_abs_min": { + "name": "cache_hit_rate_alert_abs_min", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "cache_hit_rate_alert_drop_rel": { + "name": "cache_hit_rate_alert_drop_rel", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.3'" + }, + "cache_hit_rate_alert_drop_abs": { + "name": "cache_hit_rate_alert_drop_abs", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.1'" + }, + "cache_hit_rate_alert_cooldown_minutes": { + "name": "cache_hit_rate_alert_cooldown_minutes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 30 + }, + "cache_hit_rate_alert_top_n": { + "name": "cache_hit_rate_alert_top_n", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 10 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.notification_target_bindings": { + "name": "notification_target_bindings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "notification_type": { + "name": "notification_type", + "type": "notification_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "schedule_cron": { + "name": "schedule_cron", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "schedule_timezone": { + "name": "schedule_timezone", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "template_override": { + "name": "template_override", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "unique_notification_target_binding": { + "name": "unique_notification_target_binding", + "columns": [ + { + "expression": "notification_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_notification_bindings_type": { + "name": "idx_notification_bindings_type", + "columns": [ + { + "expression": "notification_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_notification_bindings_target": { + "name": "idx_notification_bindings_target", + "columns": [ + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "notification_target_bindings_target_id_webhook_targets_id_fk": { + "name": "notification_target_bindings_target_id_webhook_targets_id_fk", + "tableFrom": "notification_target_bindings", + "tableTo": "webhook_targets", + "columnsFrom": [ + "target_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_endpoint_probe_logs": { + "name": "provider_endpoint_probe_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'scheduled'" + }, + "ok": { + "name": "ok", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "status_code": { + "name": "status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "latency_ms": { + "name": "latency_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "error_type": { + "name": "error_type", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_provider_endpoint_probe_logs_endpoint_created_at": { + "name": "idx_provider_endpoint_probe_logs_endpoint_created_at", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoint_probe_logs_created_at": { + "name": "idx_provider_endpoint_probe_logs_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "provider_endpoint_probe_logs_endpoint_id_provider_endpoints_id_fk": { + "name": "provider_endpoint_probe_logs_endpoint_id_provider_endpoints_id_fk", + "tableFrom": "provider_endpoint_probe_logs", + "tableTo": "provider_endpoints", + "columnsFrom": [ + "endpoint_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_endpoints": { + "name": "provider_endpoints", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "vendor_id": { + "name": "vendor_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "provider_type": { + "name": "provider_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'claude'" + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_probed_at": { + "name": "last_probed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_probe_ok": { + "name": "last_probe_ok", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "last_probe_status_code": { + "name": "last_probe_status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_probe_latency_ms": { + "name": "last_probe_latency_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_probe_error_type": { + "name": "last_probe_error_type", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "last_probe_error_message": { + "name": "last_probe_error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "uniq_provider_endpoints_vendor_type_url": { + "name": "uniq_provider_endpoints_vendor_type_url", + "columns": [ + { + "expression": "vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "url", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"provider_endpoints\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoints_vendor_type": { + "name": "idx_provider_endpoints_vendor_type", + "columns": [ + { + "expression": "vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"provider_endpoints\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoints_enabled": { + "name": "idx_provider_endpoints_enabled", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"provider_endpoints\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoints_pick_enabled": { + "name": "idx_provider_endpoints_pick_enabled", + "columns": [ + { + "expression": "vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sort_order", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"provider_endpoints\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoints_created_at": { + "name": "idx_provider_endpoints_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoints_deleted_at": { + "name": "idx_provider_endpoints_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "provider_endpoints_vendor_id_provider_vendors_id_fk": { + "name": "provider_endpoints_vendor_id_provider_vendors_id_fk", + "tableFrom": "provider_endpoints", + "tableTo": "provider_vendors", + "columnsFrom": [ + "vendor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_groups": { + "name": "provider_groups", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "cost_multiplier": { + "name": "cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": true, + "default": "'1.0'" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "provider_groups_name_unique": { + "name": "provider_groups_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_vendors": { + "name": "provider_vendors", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "website_domain": { + "name": "website_domain", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false + }, + "website_url": { + "name": "website_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "favicon_url": { + "name": "favicon_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "uniq_provider_vendors_website_domain": { + "name": "uniq_provider_vendors_website_domain", + "columns": [ + { + "expression": "website_domain", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_vendors_created_at": { + "name": "idx_provider_vendors_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.providers": { + "name": "providers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "provider_vendor_id": { + "name": "provider_vendor_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "weight": { + "name": "weight", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "group_priorities": { + "name": "group_priorities", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'null'::jsonb" + }, + "cost_multiplier": { + "name": "cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": false, + "default": "'1.0'" + }, + "group_tag": { + "name": "group_tag", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "provider_type": { + "name": "provider_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'claude'" + }, + "preserve_client_ip": { + "name": "preserve_client_ip", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "disable_session_reuse": { + "name": "disable_session_reuse", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "model_redirects": { + "name": "model_redirects", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "allowed_models": { + "name": "allowed_models", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'null'::jsonb" + }, + "allowed_clients": { + "name": "allowed_clients", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "blocked_clients": { + "name": "blocked_clients", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "active_time_start": { + "name": "active_time_start", + "type": "varchar(5)", + "primaryKey": false, + "notNull": false + }, + "active_time_end": { + "name": "active_time_end", + "type": "varchar(5)", + "primaryKey": false, + "notNull": false + }, + "codex_instructions_strategy": { + "name": "codex_instructions_strategy", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false, + "default": "'auto'" + }, + "mcp_passthrough_type": { + "name": "mcp_passthrough_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "mcp_passthrough_url": { + "name": "mcp_passthrough_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "limit_5h_usd": { + "name": "limit_5h_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_5h_reset_mode": { + "name": "limit_5h_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'rolling'" + }, + "limit_daily_usd": { + "name": "limit_daily_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "daily_reset_mode": { + "name": "daily_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'fixed'" + }, + "daily_reset_time": { + "name": "daily_reset_time", + "type": "varchar(5)", + "primaryKey": false, + "notNull": true, + "default": "'00:00'" + }, + "limit_weekly_usd": { + "name": "limit_weekly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_monthly_usd": { + "name": "limit_monthly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_total_usd": { + "name": "limit_total_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "total_cost_reset_at": { + "name": "total_cost_reset_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "limit_concurrent_sessions": { + "name": "limit_concurrent_sessions", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "max_retry_attempts": { + "name": "max_retry_attempts", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "circuit_breaker_failure_threshold": { + "name": "circuit_breaker_failure_threshold", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 5 + }, + "circuit_breaker_open_duration": { + "name": "circuit_breaker_open_duration", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 1800000 + }, + "circuit_breaker_half_open_success_threshold": { + "name": "circuit_breaker_half_open_success_threshold", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 2 + }, + "proxy_url": { + "name": "proxy_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "proxy_fallback_to_direct": { + "name": "proxy_fallback_to_direct", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "custom_headers": { + "name": "custom_headers", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "first_byte_timeout_streaming_ms": { + "name": "first_byte_timeout_streaming_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "streaming_idle_timeout_ms": { + "name": "streaming_idle_timeout_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "request_timeout_non_streaming_ms": { + "name": "request_timeout_non_streaming_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "website_url": { + "name": "website_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "favicon_url": { + "name": "favicon_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cache_ttl_preference": { + "name": "cache_ttl_preference", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "swap_cache_ttl_billing": { + "name": "swap_cache_ttl_billing", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "context_1m_preference": { + "name": "context_1m_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "codex_reasoning_effort_preference": { + "name": "codex_reasoning_effort_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "codex_reasoning_summary_preference": { + "name": "codex_reasoning_summary_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "codex_text_verbosity_preference": { + "name": "codex_text_verbosity_preference", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "codex_parallel_tool_calls_preference": { + "name": "codex_parallel_tool_calls_preference", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "codex_service_tier_preference": { + "name": "codex_service_tier_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "anthropic_max_tokens_preference": { + "name": "anthropic_max_tokens_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "anthropic_thinking_budget_preference": { + "name": "anthropic_thinking_budget_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "anthropic_adaptive_thinking": { + "name": "anthropic_adaptive_thinking", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'null'::jsonb" + }, + "gemini_google_search_preference": { + "name": "gemini_google_search_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "tpm": { + "name": "tpm", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "rpm": { + "name": "rpm", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "rpd": { + "name": "rpd", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "cc": { + "name": "cc", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_providers_enabled_priority": { + "name": "idx_providers_enabled_priority", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "weight", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"providers\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_group": { + "name": "idx_providers_group", + "columns": [ + { + "expression": "group_tag", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"providers\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_vendor_type_url_active": { + "name": "idx_providers_vendor_type_url_active", + "columns": [ + { + "expression": "provider_vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "url", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"providers\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_created_at": { + "name": "idx_providers_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_deleted_at": { + "name": "idx_providers_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_vendor_type": { + "name": "idx_providers_vendor_type", + "columns": [ + { + "expression": "provider_vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"providers\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_enabled_vendor_type": { + "name": "idx_providers_enabled_vendor_type", + "columns": [ + { + "expression": "provider_vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"providers\".\"deleted_at\" IS NULL AND \"providers\".\"is_enabled\" = true AND \"providers\".\"provider_vendor_id\" IS NOT NULL AND \"providers\".\"provider_vendor_id\" > 0", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "providers_provider_vendor_id_provider_vendors_id_fk": { + "name": "providers_provider_vendor_id_provider_vendors_id_fk", + "tableFrom": "providers", + "tableTo": "provider_vendors", + "columnsFrom": [ + "provider_vendor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.request_filters": { + "name": "request_filters", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true + }, + "match_type": { + "name": "match_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "target": { + "name": "target", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "replacement": { + "name": "replacement", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "binding_type": { + "name": "binding_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'global'" + }, + "provider_ids": { + "name": "provider_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "group_tags": { + "name": "group_tags", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "rule_mode": { + "name": "rule_mode", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'simple'" + }, + "execution_phase": { + "name": "execution_phase", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'guard'" + }, + "operations": { + "name": "operations", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_request_filters_enabled": { + "name": "idx_request_filters_enabled", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_request_filters_scope": { + "name": "idx_request_filters_scope", + "columns": [ + { + "expression": "scope", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_request_filters_action": { + "name": "idx_request_filters_action", + "columns": [ + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_request_filters_binding": { + "name": "idx_request_filters_binding", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "binding_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_request_filters_phase": { + "name": "idx_request_filters_phase", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_phase", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sensitive_words": { + "name": "sensitive_words", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "word": { + "name": "word", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "match_type": { + "name": "match_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'contains'" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_sensitive_words_enabled": { + "name": "idx_sensitive_words_enabled", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "match_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_sensitive_words_created_at": { + "name": "idx_sensitive_words_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.system_settings": { + "name": "system_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "site_title": { + "name": "site_title", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true, + "default": "'Claude Code Hub'" + }, + "allow_global_usage_view": { + "name": "allow_global_usage_view", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "currency_display": { + "name": "currency_display", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "default": "'USD'" + }, + "billing_model_source": { + "name": "billing_model_source", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'original'" + }, + "codex_priority_billing_source": { + "name": "codex_priority_billing_source", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'requested'" + }, + "timezone": { + "name": "timezone", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "enable_auto_cleanup": { + "name": "enable_auto_cleanup", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "cleanup_retention_days": { + "name": "cleanup_retention_days", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 30 + }, + "cleanup_schedule": { + "name": "cleanup_schedule", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false, + "default": "'0 2 * * *'" + }, + "cleanup_batch_size": { + "name": "cleanup_batch_size", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 10000 + }, + "enable_client_version_check": { + "name": "enable_client_version_check", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "verbose_provider_error": { + "name": "verbose_provider_error", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "pass_through_upstream_error_message": { + "name": "pass_through_upstream_error_message", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_http2": { + "name": "enable_http2", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "enable_high_concurrency_mode": { + "name": "enable_high_concurrency_mode", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "intercept_anthropic_warmup_requests": { + "name": "intercept_anthropic_warmup_requests", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "enable_thinking_signature_rectifier": { + "name": "enable_thinking_signature_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_thinking_budget_rectifier": { + "name": "enable_thinking_budget_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_billing_header_rectifier": { + "name": "enable_billing_header_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_response_input_rectifier": { + "name": "enable_response_input_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "allow_non_conversation_endpoint_provider_fallback": { + "name": "allow_non_conversation_endpoint_provider_fallback", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_codex_session_id_completion": { + "name": "enable_codex_session_id_completion", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_claude_metadata_user_id_injection": { + "name": "enable_claude_metadata_user_id_injection", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_response_fixer": { + "name": "enable_response_fixer", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "response_fixer_config": { + "name": "response_fixer_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{\"fixTruncatedJson\":true,\"fixSseFormat\":true,\"fixEncoding\":true,\"maxJsonDepth\":200,\"maxFixSize\":1048576}'::jsonb" + }, + "quota_db_refresh_interval_seconds": { + "name": "quota_db_refresh_interval_seconds", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 10 + }, + "quota_lease_percent_5h": { + "name": "quota_lease_percent_5h", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "quota_lease_percent_daily": { + "name": "quota_lease_percent_daily", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "quota_lease_percent_weekly": { + "name": "quota_lease_percent_weekly", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "quota_lease_percent_monthly": { + "name": "quota_lease_percent_monthly", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "quota_lease_cap_usd": { + "name": "quota_lease_cap_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "ip_extraction_config": { + "name": "ip_extraction_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "ip_geo_lookup_enabled": { + "name": "ip_geo_lookup_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "public_status_window_hours": { + "name": "public_status_window_hours", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 24 + }, + "public_status_aggregation_interval_minutes": { + "name": "public_status_aggregation_interval_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 5 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.usage_ledger": { + "name": "usage_ledger", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "request_id": { + "name": "request_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "final_provider_id": { + "name": "final_provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "original_model": { + "name": "original_model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "actual_response_model": { + "name": "actual_response_model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "endpoint": { + "name": "endpoint", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "api_type": { + "name": "api_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "status_code": { + "name": "status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "is_success": { + "name": "is_success", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "success_rate_outcome": { + "name": "success_rate_outcome", + "type": "varchar(16)", + "primaryKey": false, + "notNull": false + }, + "blocked_by": { + "name": "blocked_by", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "cost_usd": { + "name": "cost_usd", + "type": "numeric(21, 15)", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "cost_multiplier": { + "name": "cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": false + }, + "group_cost_multiplier": { + "name": "group_cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": false + }, + "input_tokens": { + "name": "input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "output_tokens": { + "name": "output_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_creation_input_tokens": { + "name": "cache_creation_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_read_input_tokens": { + "name": "cache_read_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_creation_5m_input_tokens": { + "name": "cache_creation_5m_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_creation_1h_input_tokens": { + "name": "cache_creation_1h_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_ttl_applied": { + "name": "cache_ttl_applied", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "context_1m_applied": { + "name": "context_1m_applied", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "swap_cache_ttl_applied": { + "name": "swap_cache_ttl_applied", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "ttfb_ms": { + "name": "ttfb_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "client_ip": { + "name": "client_ip", + "type": "varchar(45)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_usage_ledger_request_id": { + "name": "idx_usage_ledger_request_id", + "columns": [ + { + "expression": "request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_user_created_at": { + "name": "idx_usage_ledger_user_created_at", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_key_created_at": { + "name": "idx_usage_ledger_key_created_at", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_provider_created_at": { + "name": "idx_usage_ledger_provider_created_at", + "columns": [ + { + "expression": "final_provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_created_at_minute": { + "name": "idx_usage_ledger_created_at_minute", + "columns": [ + { + "expression": "date_trunc('minute', \"created_at\" AT TIME ZONE 'UTC')", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_created_at_desc_id": { + "name": "idx_usage_ledger_created_at_desc_id", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_session_id": { + "name": "idx_usage_ledger_session_id", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"session_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_model": { + "name": "idx_usage_ledger_model", + "columns": [ + { + "expression": "model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"model\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_key_cost": { + "name": "idx_usage_ledger_key_cost", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_user_cost_cover": { + "name": "idx_usage_ledger_user_cost_cover", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_provider_cost_cover": { + "name": "idx_usage_ledger_provider_cost_cover", + "columns": [ + { + "expression": "final_provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_key_created_at_desc_cover": { + "name": "idx_usage_ledger_key_created_at_desc_cover", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"created_at\" DESC NULLS LAST", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "final_provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'user'" + }, + "rpm_limit": { + "name": "rpm_limit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "daily_limit_usd": { + "name": "daily_limit_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "provider_group": { + "name": "provider_group", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false, + "default": "'default'" + }, + "tags": { + "name": "tags", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "limit_5h_usd": { + "name": "limit_5h_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_5h_reset_mode": { + "name": "limit_5h_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'rolling'" + }, + "limit_weekly_usd": { + "name": "limit_weekly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_monthly_usd": { + "name": "limit_monthly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_total_usd": { + "name": "limit_total_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "cost_reset_at": { + "name": "cost_reset_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "limit_5h_cost_reset_at": { + "name": "limit_5h_cost_reset_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "limit_concurrent_sessions": { + "name": "limit_concurrent_sessions", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "daily_reset_mode": { + "name": "daily_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'fixed'" + }, + "daily_reset_time": { + "name": "daily_reset_time", + "type": "varchar(5)", + "primaryKey": false, + "notNull": true, + "default": "'00:00'" + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "allowed_clients": { + "name": "allowed_clients", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "allowed_models": { + "name": "allowed_models", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "blocked_clients": { + "name": "blocked_clients", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_users_active_role_sort": { + "name": "idx_users_active_role_sort", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"users\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_users_enabled_expires_at": { + "name": "idx_users_enabled_expires_at", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"users\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_users_tags_gin": { + "name": "idx_users_tags_gin", + "columns": [ + { + "expression": "tags", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"users\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "gin", + "with": {} + }, + "idx_users_created_at": { + "name": "idx_users_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_users_deleted_at": { + "name": "idx_users_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.webhook_targets": { + "name": "webhook_targets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "provider_type": { + "name": "provider_type", + "type": "webhook_provider_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "webhook_url": { + "name": "webhook_url", + "type": "varchar(1024)", + "primaryKey": false, + "notNull": false + }, + "telegram_bot_token": { + "name": "telegram_bot_token", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "telegram_chat_id": { + "name": "telegram_chat_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "dingtalk_secret": { + "name": "dingtalk_secret", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "custom_template": { + "name": "custom_template", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "custom_headers": { + "name": "custom_headers", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "proxy_url": { + "name": "proxy_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "proxy_fallback_to_direct": { + "name": "proxy_fallback_to_direct", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_test_at": { + "name": "last_test_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_test_result": { + "name": "last_test_result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.daily_reset_mode": { + "name": "daily_reset_mode", + "schema": "public", + "values": [ + "fixed", + "rolling" + ] + }, + "public.notification_type": { + "name": "notification_type", + "schema": "public", + "values": [ + "circuit_breaker", + "daily_leaderboard", + "cost_alert", + "cache_hit_rate_alert" + ] + }, + "public.webhook_provider_type": { + "name": "webhook_provider_type", + "schema": "public", + "values": [ + "wechat", + "feishu", + "dingtalk", + "telegram", + "custom" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/0100_snapshot.json b/drizzle/meta/0100_snapshot.json new file mode 100644 index 000000000..e08f3895b --- /dev/null +++ b/drizzle/meta/0100_snapshot.json @@ -0,0 +1,4483 @@ +{ + "id": "0121f242-24bd-4fb5-b4c4-1889677099c5", + "prevId": "4c0c61bd-9bd1-4468-8211-311faa7379fa", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.audit_log": { + "name": "audit_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "action_category": { + "name": "action_category", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "action_type": { + "name": "action_type", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false + }, + "target_id": { + "name": "target_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "target_name": { + "name": "target_name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "before_value": { + "name": "before_value", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "after_value": { + "name": "after_value", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "operator_user_id": { + "name": "operator_user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "operator_user_name": { + "name": "operator_user_name", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "operator_key_id": { + "name": "operator_key_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "operator_key_name": { + "name": "operator_key_name", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "operator_ip": { + "name": "operator_ip", + "type": "varchar(45)", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "success": { + "name": "success", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_audit_log_category_created_at": { + "name": "idx_audit_log_category_created_at", + "columns": [ + { + "expression": "action_category", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_log_operator_user_created_at": { + "name": "idx_audit_log_operator_user_created_at", + "columns": [ + { + "expression": "operator_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"audit_log\".\"operator_user_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_log_operator_ip_created_at": { + "name": "idx_audit_log_operator_ip_created_at", + "columns": [ + { + "expression": "operator_ip", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"audit_log\".\"operator_ip\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_log_target": { + "name": "idx_audit_log_target", + "columns": [ + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"audit_log\".\"target_type\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_log_created_at_id": { + "name": "idx_audit_log_created_at_id", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.error_rules": { + "name": "error_rules", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pattern": { + "name": "pattern", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "match_type": { + "name": "match_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'regex'" + }, + "category": { + "name": "category", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "override_response": { + "name": "override_response", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "override_status_code": { + "name": "override_status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_error_rules_enabled": { + "name": "idx_error_rules_enabled", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "unique_pattern": { + "name": "unique_pattern", + "columns": [ + { + "expression": "pattern", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_category": { + "name": "idx_category", + "columns": [ + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_match_type": { + "name": "idx_match_type", + "columns": [ + { + "expression": "match_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.keys": { + "name": "keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "can_login_web_ui": { + "name": "can_login_web_ui", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "limit_5h_usd": { + "name": "limit_5h_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_5h_reset_mode": { + "name": "limit_5h_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'rolling'" + }, + "limit_daily_usd": { + "name": "limit_daily_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "daily_reset_mode": { + "name": "daily_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'fixed'" + }, + "daily_reset_time": { + "name": "daily_reset_time", + "type": "varchar(5)", + "primaryKey": false, + "notNull": true, + "default": "'00:00'" + }, + "limit_weekly_usd": { + "name": "limit_weekly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_monthly_usd": { + "name": "limit_monthly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_total_usd": { + "name": "limit_total_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "cost_reset_at": { + "name": "cost_reset_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "limit_concurrent_sessions": { + "name": "limit_concurrent_sessions", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "provider_group": { + "name": "provider_group", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false, + "default": "'default'" + }, + "cache_ttl_preference": { + "name": "cache_ttl_preference", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_keys_user_id": { + "name": "idx_keys_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_keys_key": { + "name": "idx_keys_key", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_keys_created_at": { + "name": "idx_keys_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_keys_deleted_at": { + "name": "idx_keys_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.message_request": { + "name": "message_request", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cost_usd": { + "name": "cost_usd", + "type": "numeric(21, 15)", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "cost_multiplier": { + "name": "cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": false + }, + "group_cost_multiplier": { + "name": "group_cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": false + }, + "cost_breakdown": { + "name": "cost_breakdown", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "request_sequence": { + "name": "request_sequence", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 1 + }, + "provider_chain": { + "name": "provider_chain", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status_code": { + "name": "status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "api_type": { + "name": "api_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "endpoint": { + "name": "endpoint", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "original_model": { + "name": "original_model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "actual_response_model": { + "name": "actual_response_model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "input_tokens": { + "name": "input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "output_tokens": { + "name": "output_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "ttfb_ms": { + "name": "ttfb_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cache_creation_input_tokens": { + "name": "cache_creation_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_read_input_tokens": { + "name": "cache_read_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_creation_5m_input_tokens": { + "name": "cache_creation_5m_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_creation_1h_input_tokens": { + "name": "cache_creation_1h_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_ttl_applied": { + "name": "cache_ttl_applied", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "context_1m_applied": { + "name": "context_1m_applied", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "swap_cache_ttl_applied": { + "name": "swap_cache_ttl_applied", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "special_settings": { + "name": "special_settings", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_stack": { + "name": "error_stack", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_cause": { + "name": "error_cause", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "blocked_by": { + "name": "blocked_by", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "blocked_reason": { + "name": "blocked_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "client_ip": { + "name": "client_ip", + "type": "varchar(45)", + "primaryKey": false, + "notNull": false + }, + "messages_count": { + "name": "messages_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_message_request_user_date_cost": { + "name": "idx_message_request_user_date_cost", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_user_created_at_cost_stats": { + "name": "idx_message_request_user_created_at_cost_stats", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_user_query": { + "name": "idx_message_request_user_query", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_provider_created_at_active": { + "name": "idx_message_request_provider_created_at_active", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_provider_created_at_finalized_active": { + "name": "idx_message_request_provider_created_at_finalized_active", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"status_code\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_session_id": { + "name": "idx_message_request_session_id", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_session_id_prefix": { + "name": "idx_message_request_session_id_prefix", + "columns": [ + { + "expression": "\"session_id\" varchar_pattern_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_session_seq": { + "name": "idx_message_request_session_seq", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_sequence", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_endpoint": { + "name": "idx_message_request_endpoint", + "columns": [ + { + "expression": "endpoint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_blocked_by": { + "name": "idx_message_request_blocked_by", + "columns": [ + { + "expression": "blocked_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_provider_id": { + "name": "idx_message_request_provider_id", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_user_id": { + "name": "idx_message_request_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_key": { + "name": "idx_message_request_key", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_key_created_at_id": { + "name": "idx_message_request_key_created_at_id", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_key_model_active": { + "name": "idx_message_request_key_model_active", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"model\" IS NOT NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_key_endpoint_active": { + "name": "idx_message_request_key_endpoint_active", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "endpoint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"endpoint\" IS NOT NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_created_at_id_active": { + "name": "idx_message_request_created_at_id_active", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_model_active": { + "name": "idx_message_request_model_active", + "columns": [ + { + "expression": "model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"model\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_status_code_active": { + "name": "idx_message_request_status_code_active", + "columns": [ + { + "expression": "status_code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"status_code\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_created_at": { + "name": "idx_message_request_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_deleted_at": { + "name": "idx_message_request_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_key_last_active": { + "name": "idx_message_request_key_last_active", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_key_cost_active": { + "name": "idx_message_request_key_cost_active", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_session_user_info": { + "name": "idx_message_request_session_user_info", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_client_ip_created_at": { + "name": "idx_message_request_client_ip_created_at", + "columns": [ + { + "expression": "client_ip", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"client_ip\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.model_prices": { + "name": "model_prices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "model_name": { + "name": "model_name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "price_data": { + "name": "price_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'litellm'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_model_prices_latest": { + "name": "idx_model_prices_latest", + "columns": [ + { + "expression": "model_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_model_prices_model_name": { + "name": "idx_model_prices_model_name", + "columns": [ + { + "expression": "model_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_model_prices_created_at": { + "name": "idx_model_prices_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_model_prices_source": { + "name": "idx_model_prices_source", + "columns": [ + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.notification_settings": { + "name": "notification_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "use_legacy_mode": { + "name": "use_legacy_mode", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "circuit_breaker_enabled": { + "name": "circuit_breaker_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "circuit_breaker_webhook": { + "name": "circuit_breaker_webhook", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "daily_leaderboard_enabled": { + "name": "daily_leaderboard_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "daily_leaderboard_webhook": { + "name": "daily_leaderboard_webhook", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "daily_leaderboard_time": { + "name": "daily_leaderboard_time", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false, + "default": "'09:00'" + }, + "daily_leaderboard_top_n": { + "name": "daily_leaderboard_top_n", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 5 + }, + "cost_alert_enabled": { + "name": "cost_alert_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "cost_alert_webhook": { + "name": "cost_alert_webhook", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "cost_alert_threshold": { + "name": "cost_alert_threshold", + "type": "numeric(5, 2)", + "primaryKey": false, + "notNull": false, + "default": "'0.80'" + }, + "cost_alert_check_interval": { + "name": "cost_alert_check_interval", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 60 + }, + "cache_hit_rate_alert_enabled": { + "name": "cache_hit_rate_alert_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "cache_hit_rate_alert_webhook": { + "name": "cache_hit_rate_alert_webhook", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "cache_hit_rate_alert_window_mode": { + "name": "cache_hit_rate_alert_window_mode", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false, + "default": "'auto'" + }, + "cache_hit_rate_alert_check_interval": { + "name": "cache_hit_rate_alert_check_interval", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 5 + }, + "cache_hit_rate_alert_historical_lookback_days": { + "name": "cache_hit_rate_alert_historical_lookback_days", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 7 + }, + "cache_hit_rate_alert_min_eligible_requests": { + "name": "cache_hit_rate_alert_min_eligible_requests", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 20 + }, + "cache_hit_rate_alert_min_eligible_tokens": { + "name": "cache_hit_rate_alert_min_eligible_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "cache_hit_rate_alert_abs_min": { + "name": "cache_hit_rate_alert_abs_min", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "cache_hit_rate_alert_drop_rel": { + "name": "cache_hit_rate_alert_drop_rel", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.3'" + }, + "cache_hit_rate_alert_drop_abs": { + "name": "cache_hit_rate_alert_drop_abs", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.1'" + }, + "cache_hit_rate_alert_cooldown_minutes": { + "name": "cache_hit_rate_alert_cooldown_minutes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 30 + }, + "cache_hit_rate_alert_top_n": { + "name": "cache_hit_rate_alert_top_n", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 10 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.notification_target_bindings": { + "name": "notification_target_bindings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "notification_type": { + "name": "notification_type", + "type": "notification_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "schedule_cron": { + "name": "schedule_cron", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "schedule_timezone": { + "name": "schedule_timezone", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "template_override": { + "name": "template_override", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "unique_notification_target_binding": { + "name": "unique_notification_target_binding", + "columns": [ + { + "expression": "notification_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_notification_bindings_type": { + "name": "idx_notification_bindings_type", + "columns": [ + { + "expression": "notification_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_notification_bindings_target": { + "name": "idx_notification_bindings_target", + "columns": [ + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "notification_target_bindings_target_id_webhook_targets_id_fk": { + "name": "notification_target_bindings_target_id_webhook_targets_id_fk", + "tableFrom": "notification_target_bindings", + "tableTo": "webhook_targets", + "columnsFrom": [ + "target_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_endpoint_probe_logs": { + "name": "provider_endpoint_probe_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'scheduled'" + }, + "ok": { + "name": "ok", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "status_code": { + "name": "status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "latency_ms": { + "name": "latency_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "error_type": { + "name": "error_type", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_provider_endpoint_probe_logs_endpoint_created_at": { + "name": "idx_provider_endpoint_probe_logs_endpoint_created_at", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoint_probe_logs_created_at": { + "name": "idx_provider_endpoint_probe_logs_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "provider_endpoint_probe_logs_endpoint_id_provider_endpoints_id_fk": { + "name": "provider_endpoint_probe_logs_endpoint_id_provider_endpoints_id_fk", + "tableFrom": "provider_endpoint_probe_logs", + "tableTo": "provider_endpoints", + "columnsFrom": [ + "endpoint_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_endpoints": { + "name": "provider_endpoints", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "vendor_id": { + "name": "vendor_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "provider_type": { + "name": "provider_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'claude'" + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_probed_at": { + "name": "last_probed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_probe_ok": { + "name": "last_probe_ok", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "last_probe_status_code": { + "name": "last_probe_status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_probe_latency_ms": { + "name": "last_probe_latency_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_probe_error_type": { + "name": "last_probe_error_type", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "last_probe_error_message": { + "name": "last_probe_error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "uniq_provider_endpoints_vendor_type_url": { + "name": "uniq_provider_endpoints_vendor_type_url", + "columns": [ + { + "expression": "vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "url", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"provider_endpoints\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoints_vendor_type": { + "name": "idx_provider_endpoints_vendor_type", + "columns": [ + { + "expression": "vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"provider_endpoints\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoints_enabled": { + "name": "idx_provider_endpoints_enabled", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"provider_endpoints\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoints_pick_enabled": { + "name": "idx_provider_endpoints_pick_enabled", + "columns": [ + { + "expression": "vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sort_order", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"provider_endpoints\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoints_created_at": { + "name": "idx_provider_endpoints_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoints_deleted_at": { + "name": "idx_provider_endpoints_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "provider_endpoints_vendor_id_provider_vendors_id_fk": { + "name": "provider_endpoints_vendor_id_provider_vendors_id_fk", + "tableFrom": "provider_endpoints", + "tableTo": "provider_vendors", + "columnsFrom": [ + "vendor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_groups": { + "name": "provider_groups", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "cost_multiplier": { + "name": "cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": true, + "default": "'1.0'" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "provider_groups_name_unique": { + "name": "provider_groups_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_vendors": { + "name": "provider_vendors", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "website_domain": { + "name": "website_domain", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false + }, + "website_url": { + "name": "website_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "favicon_url": { + "name": "favicon_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "uniq_provider_vendors_website_domain": { + "name": "uniq_provider_vendors_website_domain", + "columns": [ + { + "expression": "website_domain", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_vendors_created_at": { + "name": "idx_provider_vendors_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.providers": { + "name": "providers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "provider_vendor_id": { + "name": "provider_vendor_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "weight": { + "name": "weight", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "group_priorities": { + "name": "group_priorities", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'null'::jsonb" + }, + "cost_multiplier": { + "name": "cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": false, + "default": "'1.0'" + }, + "group_tag": { + "name": "group_tag", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "provider_type": { + "name": "provider_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'claude'" + }, + "preserve_client_ip": { + "name": "preserve_client_ip", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "disable_session_reuse": { + "name": "disable_session_reuse", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "model_redirects": { + "name": "model_redirects", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "allowed_models": { + "name": "allowed_models", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'null'::jsonb" + }, + "allowed_clients": { + "name": "allowed_clients", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "blocked_clients": { + "name": "blocked_clients", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "active_time_start": { + "name": "active_time_start", + "type": "varchar(5)", + "primaryKey": false, + "notNull": false + }, + "active_time_end": { + "name": "active_time_end", + "type": "varchar(5)", + "primaryKey": false, + "notNull": false + }, + "codex_instructions_strategy": { + "name": "codex_instructions_strategy", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false, + "default": "'auto'" + }, + "mcp_passthrough_type": { + "name": "mcp_passthrough_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "mcp_passthrough_url": { + "name": "mcp_passthrough_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "limit_5h_usd": { + "name": "limit_5h_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_5h_reset_mode": { + "name": "limit_5h_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'rolling'" + }, + "limit_daily_usd": { + "name": "limit_daily_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "daily_reset_mode": { + "name": "daily_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'fixed'" + }, + "daily_reset_time": { + "name": "daily_reset_time", + "type": "varchar(5)", + "primaryKey": false, + "notNull": true, + "default": "'00:00'" + }, + "limit_weekly_usd": { + "name": "limit_weekly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_monthly_usd": { + "name": "limit_monthly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_total_usd": { + "name": "limit_total_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "total_cost_reset_at": { + "name": "total_cost_reset_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "limit_concurrent_sessions": { + "name": "limit_concurrent_sessions", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "max_retry_attempts": { + "name": "max_retry_attempts", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "circuit_breaker_failure_threshold": { + "name": "circuit_breaker_failure_threshold", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 5 + }, + "circuit_breaker_open_duration": { + "name": "circuit_breaker_open_duration", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 1800000 + }, + "circuit_breaker_half_open_success_threshold": { + "name": "circuit_breaker_half_open_success_threshold", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 2 + }, + "proxy_url": { + "name": "proxy_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "proxy_fallback_to_direct": { + "name": "proxy_fallback_to_direct", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "custom_headers": { + "name": "custom_headers", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "first_byte_timeout_streaming_ms": { + "name": "first_byte_timeout_streaming_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "streaming_idle_timeout_ms": { + "name": "streaming_idle_timeout_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "request_timeout_non_streaming_ms": { + "name": "request_timeout_non_streaming_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "website_url": { + "name": "website_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "favicon_url": { + "name": "favicon_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cache_ttl_preference": { + "name": "cache_ttl_preference", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "swap_cache_ttl_billing": { + "name": "swap_cache_ttl_billing", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "context_1m_preference": { + "name": "context_1m_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "codex_reasoning_effort_preference": { + "name": "codex_reasoning_effort_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "codex_reasoning_summary_preference": { + "name": "codex_reasoning_summary_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "codex_text_verbosity_preference": { + "name": "codex_text_verbosity_preference", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "codex_parallel_tool_calls_preference": { + "name": "codex_parallel_tool_calls_preference", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "codex_service_tier_preference": { + "name": "codex_service_tier_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "anthropic_max_tokens_preference": { + "name": "anthropic_max_tokens_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "anthropic_thinking_budget_preference": { + "name": "anthropic_thinking_budget_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "anthropic_adaptive_thinking": { + "name": "anthropic_adaptive_thinking", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'null'::jsonb" + }, + "gemini_google_search_preference": { + "name": "gemini_google_search_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "tpm": { + "name": "tpm", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "rpm": { + "name": "rpm", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "rpd": { + "name": "rpd", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "cc": { + "name": "cc", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_providers_enabled_priority": { + "name": "idx_providers_enabled_priority", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "weight", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"providers\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_group": { + "name": "idx_providers_group", + "columns": [ + { + "expression": "group_tag", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"providers\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_vendor_type_url_active": { + "name": "idx_providers_vendor_type_url_active", + "columns": [ + { + "expression": "provider_vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "url", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"providers\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_created_at": { + "name": "idx_providers_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_deleted_at": { + "name": "idx_providers_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_vendor_type": { + "name": "idx_providers_vendor_type", + "columns": [ + { + "expression": "provider_vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"providers\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_enabled_vendor_type": { + "name": "idx_providers_enabled_vendor_type", + "columns": [ + { + "expression": "provider_vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"providers\".\"deleted_at\" IS NULL AND \"providers\".\"is_enabled\" = true AND \"providers\".\"provider_vendor_id\" IS NOT NULL AND \"providers\".\"provider_vendor_id\" > 0", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "providers_provider_vendor_id_provider_vendors_id_fk": { + "name": "providers_provider_vendor_id_provider_vendors_id_fk", + "tableFrom": "providers", + "tableTo": "provider_vendors", + "columnsFrom": [ + "provider_vendor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.request_filters": { + "name": "request_filters", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true + }, + "match_type": { + "name": "match_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "target": { + "name": "target", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "replacement": { + "name": "replacement", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "binding_type": { + "name": "binding_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'global'" + }, + "provider_ids": { + "name": "provider_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "group_tags": { + "name": "group_tags", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "rule_mode": { + "name": "rule_mode", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'simple'" + }, + "execution_phase": { + "name": "execution_phase", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'guard'" + }, + "operations": { + "name": "operations", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_request_filters_enabled": { + "name": "idx_request_filters_enabled", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_request_filters_scope": { + "name": "idx_request_filters_scope", + "columns": [ + { + "expression": "scope", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_request_filters_action": { + "name": "idx_request_filters_action", + "columns": [ + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_request_filters_binding": { + "name": "idx_request_filters_binding", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "binding_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_request_filters_phase": { + "name": "idx_request_filters_phase", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_phase", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sensitive_words": { + "name": "sensitive_words", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "word": { + "name": "word", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "match_type": { + "name": "match_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'contains'" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_sensitive_words_enabled": { + "name": "idx_sensitive_words_enabled", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "match_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_sensitive_words_created_at": { + "name": "idx_sensitive_words_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.system_settings": { + "name": "system_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "site_title": { + "name": "site_title", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true, + "default": "'Claude Code Hub'" + }, + "allow_global_usage_view": { + "name": "allow_global_usage_view", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "currency_display": { + "name": "currency_display", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "default": "'USD'" + }, + "billing_model_source": { + "name": "billing_model_source", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'original'" + }, + "codex_priority_billing_source": { + "name": "codex_priority_billing_source", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'requested'" + }, + "timezone": { + "name": "timezone", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "enable_auto_cleanup": { + "name": "enable_auto_cleanup", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "cleanup_retention_days": { + "name": "cleanup_retention_days", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 30 + }, + "cleanup_schedule": { + "name": "cleanup_schedule", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false, + "default": "'0 2 * * *'" + }, + "cleanup_batch_size": { + "name": "cleanup_batch_size", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 10000 + }, + "enable_client_version_check": { + "name": "enable_client_version_check", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "verbose_provider_error": { + "name": "verbose_provider_error", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "pass_through_upstream_error_message": { + "name": "pass_through_upstream_error_message", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_http2": { + "name": "enable_http2", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "enable_high_concurrency_mode": { + "name": "enable_high_concurrency_mode", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "intercept_anthropic_warmup_requests": { + "name": "intercept_anthropic_warmup_requests", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "enable_thinking_signature_rectifier": { + "name": "enable_thinking_signature_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_thinking_budget_rectifier": { + "name": "enable_thinking_budget_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_billing_header_rectifier": { + "name": "enable_billing_header_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_response_input_rectifier": { + "name": "enable_response_input_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "allow_non_conversation_endpoint_provider_fallback": { + "name": "allow_non_conversation_endpoint_provider_fallback", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "fake_streaming_whitelist": { + "name": "fake_streaming_whitelist", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "enable_codex_session_id_completion": { + "name": "enable_codex_session_id_completion", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_claude_metadata_user_id_injection": { + "name": "enable_claude_metadata_user_id_injection", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_response_fixer": { + "name": "enable_response_fixer", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "response_fixer_config": { + "name": "response_fixer_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{\"fixTruncatedJson\":true,\"fixSseFormat\":true,\"fixEncoding\":true,\"maxJsonDepth\":200,\"maxFixSize\":1048576}'::jsonb" + }, + "quota_db_refresh_interval_seconds": { + "name": "quota_db_refresh_interval_seconds", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 10 + }, + "quota_lease_percent_5h": { + "name": "quota_lease_percent_5h", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "quota_lease_percent_daily": { + "name": "quota_lease_percent_daily", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "quota_lease_percent_weekly": { + "name": "quota_lease_percent_weekly", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "quota_lease_percent_monthly": { + "name": "quota_lease_percent_monthly", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "quota_lease_cap_usd": { + "name": "quota_lease_cap_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "ip_extraction_config": { + "name": "ip_extraction_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "ip_geo_lookup_enabled": { + "name": "ip_geo_lookup_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "public_status_window_hours": { + "name": "public_status_window_hours", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 24 + }, + "public_status_aggregation_interval_minutes": { + "name": "public_status_aggregation_interval_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 5 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.usage_ledger": { + "name": "usage_ledger", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "request_id": { + "name": "request_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "final_provider_id": { + "name": "final_provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "original_model": { + "name": "original_model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "actual_response_model": { + "name": "actual_response_model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "endpoint": { + "name": "endpoint", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "api_type": { + "name": "api_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "status_code": { + "name": "status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "is_success": { + "name": "is_success", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "success_rate_outcome": { + "name": "success_rate_outcome", + "type": "varchar(16)", + "primaryKey": false, + "notNull": false + }, + "blocked_by": { + "name": "blocked_by", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "cost_usd": { + "name": "cost_usd", + "type": "numeric(21, 15)", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "cost_multiplier": { + "name": "cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": false + }, + "group_cost_multiplier": { + "name": "group_cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": false + }, + "input_tokens": { + "name": "input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "output_tokens": { + "name": "output_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_creation_input_tokens": { + "name": "cache_creation_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_read_input_tokens": { + "name": "cache_read_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_creation_5m_input_tokens": { + "name": "cache_creation_5m_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_creation_1h_input_tokens": { + "name": "cache_creation_1h_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_ttl_applied": { + "name": "cache_ttl_applied", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "context_1m_applied": { + "name": "context_1m_applied", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "swap_cache_ttl_applied": { + "name": "swap_cache_ttl_applied", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "ttfb_ms": { + "name": "ttfb_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "client_ip": { + "name": "client_ip", + "type": "varchar(45)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_usage_ledger_request_id": { + "name": "idx_usage_ledger_request_id", + "columns": [ + { + "expression": "request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_user_created_at": { + "name": "idx_usage_ledger_user_created_at", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_key_created_at": { + "name": "idx_usage_ledger_key_created_at", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_provider_created_at": { + "name": "idx_usage_ledger_provider_created_at", + "columns": [ + { + "expression": "final_provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_created_at_minute": { + "name": "idx_usage_ledger_created_at_minute", + "columns": [ + { + "expression": "date_trunc('minute', \"created_at\" AT TIME ZONE 'UTC')", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_created_at_desc_id": { + "name": "idx_usage_ledger_created_at_desc_id", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_session_id": { + "name": "idx_usage_ledger_session_id", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"session_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_model": { + "name": "idx_usage_ledger_model", + "columns": [ + { + "expression": "model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"model\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_key_cost": { + "name": "idx_usage_ledger_key_cost", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_user_cost_cover": { + "name": "idx_usage_ledger_user_cost_cover", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_provider_cost_cover": { + "name": "idx_usage_ledger_provider_cost_cover", + "columns": [ + { + "expression": "final_provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_key_created_at_desc_cover": { + "name": "idx_usage_ledger_key_created_at_desc_cover", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"created_at\" DESC NULLS LAST", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "final_provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'user'" + }, + "rpm_limit": { + "name": "rpm_limit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "daily_limit_usd": { + "name": "daily_limit_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "provider_group": { + "name": "provider_group", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false, + "default": "'default'" + }, + "tags": { + "name": "tags", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "limit_5h_usd": { + "name": "limit_5h_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_5h_reset_mode": { + "name": "limit_5h_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'rolling'" + }, + "limit_weekly_usd": { + "name": "limit_weekly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_monthly_usd": { + "name": "limit_monthly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_total_usd": { + "name": "limit_total_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "cost_reset_at": { + "name": "cost_reset_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "limit_5h_cost_reset_at": { + "name": "limit_5h_cost_reset_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "limit_concurrent_sessions": { + "name": "limit_concurrent_sessions", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "daily_reset_mode": { + "name": "daily_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'fixed'" + }, + "daily_reset_time": { + "name": "daily_reset_time", + "type": "varchar(5)", + "primaryKey": false, + "notNull": true, + "default": "'00:00'" + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "allowed_clients": { + "name": "allowed_clients", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "allowed_models": { + "name": "allowed_models", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "blocked_clients": { + "name": "blocked_clients", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_users_active_role_sort": { + "name": "idx_users_active_role_sort", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"users\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_users_enabled_expires_at": { + "name": "idx_users_enabled_expires_at", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"users\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_users_tags_gin": { + "name": "idx_users_tags_gin", + "columns": [ + { + "expression": "tags", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"users\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "gin", + "with": {} + }, + "idx_users_created_at": { + "name": "idx_users_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_users_deleted_at": { + "name": "idx_users_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.webhook_targets": { + "name": "webhook_targets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "provider_type": { + "name": "provider_type", + "type": "webhook_provider_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "webhook_url": { + "name": "webhook_url", + "type": "varchar(1024)", + "primaryKey": false, + "notNull": false + }, + "telegram_bot_token": { + "name": "telegram_bot_token", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "telegram_chat_id": { + "name": "telegram_chat_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "dingtalk_secret": { + "name": "dingtalk_secret", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "custom_template": { + "name": "custom_template", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "custom_headers": { + "name": "custom_headers", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "proxy_url": { + "name": "proxy_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "proxy_fallback_to_direct": { + "name": "proxy_fallback_to_direct", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_test_at": { + "name": "last_test_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_test_result": { + "name": "last_test_result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.daily_reset_mode": { + "name": "daily_reset_mode", + "schema": "public", + "values": [ + "fixed", + "rolling" + ] + }, + "public.notification_type": { + "name": "notification_type", + "schema": "public", + "values": [ + "circuit_breaker", + "daily_leaderboard", + "cost_alert", + "cache_hit_rate_alert" + ] + }, + "public.webhook_provider_type": { + "name": "webhook_provider_type", + "schema": "public", + "values": [ + "wechat", + "feishu", + "dingtalk", + "telegram", + "custom" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/0101_snapshot.json b/drizzle/meta/0101_snapshot.json new file mode 100644 index 000000000..8b18e9dd6 --- /dev/null +++ b/drizzle/meta/0101_snapshot.json @@ -0,0 +1,4490 @@ +{ + "id": "f475eec8-aa3e-4c45-8b70-85400b73a776", + "prevId": "0121f242-24bd-4fb5-b4c4-1889677099c5", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.audit_log": { + "name": "audit_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "action_category": { + "name": "action_category", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "action_type": { + "name": "action_type", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false + }, + "target_id": { + "name": "target_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "target_name": { + "name": "target_name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "before_value": { + "name": "before_value", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "after_value": { + "name": "after_value", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "operator_user_id": { + "name": "operator_user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "operator_user_name": { + "name": "operator_user_name", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "operator_key_id": { + "name": "operator_key_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "operator_key_name": { + "name": "operator_key_name", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "operator_ip": { + "name": "operator_ip", + "type": "varchar(45)", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "success": { + "name": "success", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_audit_log_category_created_at": { + "name": "idx_audit_log_category_created_at", + "columns": [ + { + "expression": "action_category", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_log_operator_user_created_at": { + "name": "idx_audit_log_operator_user_created_at", + "columns": [ + { + "expression": "operator_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"audit_log\".\"operator_user_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_log_operator_ip_created_at": { + "name": "idx_audit_log_operator_ip_created_at", + "columns": [ + { + "expression": "operator_ip", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"audit_log\".\"operator_ip\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_log_target": { + "name": "idx_audit_log_target", + "columns": [ + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"audit_log\".\"target_type\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_log_created_at_id": { + "name": "idx_audit_log_created_at_id", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.error_rules": { + "name": "error_rules", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pattern": { + "name": "pattern", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "match_type": { + "name": "match_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'regex'" + }, + "category": { + "name": "category", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "override_response": { + "name": "override_response", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "override_status_code": { + "name": "override_status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_error_rules_enabled": { + "name": "idx_error_rules_enabled", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "unique_pattern": { + "name": "unique_pattern", + "columns": [ + { + "expression": "pattern", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_category": { + "name": "idx_category", + "columns": [ + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_match_type": { + "name": "idx_match_type", + "columns": [ + { + "expression": "match_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.keys": { + "name": "keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "can_login_web_ui": { + "name": "can_login_web_ui", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "limit_5h_usd": { + "name": "limit_5h_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_5h_reset_mode": { + "name": "limit_5h_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'rolling'" + }, + "limit_daily_usd": { + "name": "limit_daily_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "daily_reset_mode": { + "name": "daily_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'fixed'" + }, + "daily_reset_time": { + "name": "daily_reset_time", + "type": "varchar(5)", + "primaryKey": false, + "notNull": true, + "default": "'00:00'" + }, + "limit_weekly_usd": { + "name": "limit_weekly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_monthly_usd": { + "name": "limit_monthly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_total_usd": { + "name": "limit_total_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "cost_reset_at": { + "name": "cost_reset_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "limit_concurrent_sessions": { + "name": "limit_concurrent_sessions", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "provider_group": { + "name": "provider_group", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false, + "default": "'default'" + }, + "cache_ttl_preference": { + "name": "cache_ttl_preference", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_keys_user_id": { + "name": "idx_keys_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_keys_key": { + "name": "idx_keys_key", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_keys_created_at": { + "name": "idx_keys_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_keys_deleted_at": { + "name": "idx_keys_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.message_request": { + "name": "message_request", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cost_usd": { + "name": "cost_usd", + "type": "numeric(21, 15)", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "cost_multiplier": { + "name": "cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": false + }, + "group_cost_multiplier": { + "name": "group_cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": false + }, + "cost_breakdown": { + "name": "cost_breakdown", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "request_sequence": { + "name": "request_sequence", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 1 + }, + "provider_chain": { + "name": "provider_chain", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status_code": { + "name": "status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "api_type": { + "name": "api_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "endpoint": { + "name": "endpoint", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "original_model": { + "name": "original_model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "actual_response_model": { + "name": "actual_response_model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "input_tokens": { + "name": "input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "output_tokens": { + "name": "output_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "ttfb_ms": { + "name": "ttfb_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cache_creation_input_tokens": { + "name": "cache_creation_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_read_input_tokens": { + "name": "cache_read_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_creation_5m_input_tokens": { + "name": "cache_creation_5m_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_creation_1h_input_tokens": { + "name": "cache_creation_1h_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_ttl_applied": { + "name": "cache_ttl_applied", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "context_1m_applied": { + "name": "context_1m_applied", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "swap_cache_ttl_applied": { + "name": "swap_cache_ttl_applied", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "special_settings": { + "name": "special_settings", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_stack": { + "name": "error_stack", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_cause": { + "name": "error_cause", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "blocked_by": { + "name": "blocked_by", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "blocked_reason": { + "name": "blocked_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "client_ip": { + "name": "client_ip", + "type": "varchar(45)", + "primaryKey": false, + "notNull": false + }, + "messages_count": { + "name": "messages_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_message_request_user_date_cost": { + "name": "idx_message_request_user_date_cost", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_user_created_at_cost_stats": { + "name": "idx_message_request_user_created_at_cost_stats", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_user_query": { + "name": "idx_message_request_user_query", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_provider_created_at_active": { + "name": "idx_message_request_provider_created_at_active", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_provider_created_at_finalized_active": { + "name": "idx_message_request_provider_created_at_finalized_active", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"status_code\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_session_id": { + "name": "idx_message_request_session_id", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_session_id_prefix": { + "name": "idx_message_request_session_id_prefix", + "columns": [ + { + "expression": "\"session_id\" varchar_pattern_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_session_seq": { + "name": "idx_message_request_session_seq", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_sequence", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_endpoint": { + "name": "idx_message_request_endpoint", + "columns": [ + { + "expression": "endpoint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_blocked_by": { + "name": "idx_message_request_blocked_by", + "columns": [ + { + "expression": "blocked_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_provider_id": { + "name": "idx_message_request_provider_id", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_user_id": { + "name": "idx_message_request_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_key": { + "name": "idx_message_request_key", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_key_created_at_id": { + "name": "idx_message_request_key_created_at_id", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_key_model_active": { + "name": "idx_message_request_key_model_active", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"model\" IS NOT NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_key_endpoint_active": { + "name": "idx_message_request_key_endpoint_active", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "endpoint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"endpoint\" IS NOT NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_created_at_id_active": { + "name": "idx_message_request_created_at_id_active", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_model_active": { + "name": "idx_message_request_model_active", + "columns": [ + { + "expression": "model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"model\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_status_code_active": { + "name": "idx_message_request_status_code_active", + "columns": [ + { + "expression": "status_code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"status_code\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_created_at": { + "name": "idx_message_request_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_deleted_at": { + "name": "idx_message_request_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_key_last_active": { + "name": "idx_message_request_key_last_active", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_key_cost_active": { + "name": "idx_message_request_key_cost_active", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_session_user_info": { + "name": "idx_message_request_session_user_info", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_client_ip_created_at": { + "name": "idx_message_request_client_ip_created_at", + "columns": [ + { + "expression": "client_ip", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"client_ip\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.model_prices": { + "name": "model_prices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "model_name": { + "name": "model_name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "price_data": { + "name": "price_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'litellm'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_model_prices_latest": { + "name": "idx_model_prices_latest", + "columns": [ + { + "expression": "model_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_model_prices_model_name": { + "name": "idx_model_prices_model_name", + "columns": [ + { + "expression": "model_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_model_prices_created_at": { + "name": "idx_model_prices_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_model_prices_source": { + "name": "idx_model_prices_source", + "columns": [ + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.notification_settings": { + "name": "notification_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "use_legacy_mode": { + "name": "use_legacy_mode", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "circuit_breaker_enabled": { + "name": "circuit_breaker_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "circuit_breaker_webhook": { + "name": "circuit_breaker_webhook", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "daily_leaderboard_enabled": { + "name": "daily_leaderboard_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "daily_leaderboard_webhook": { + "name": "daily_leaderboard_webhook", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "daily_leaderboard_time": { + "name": "daily_leaderboard_time", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false, + "default": "'09:00'" + }, + "daily_leaderboard_top_n": { + "name": "daily_leaderboard_top_n", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 5 + }, + "cost_alert_enabled": { + "name": "cost_alert_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "cost_alert_webhook": { + "name": "cost_alert_webhook", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "cost_alert_threshold": { + "name": "cost_alert_threshold", + "type": "numeric(5, 2)", + "primaryKey": false, + "notNull": false, + "default": "'0.80'" + }, + "cost_alert_check_interval": { + "name": "cost_alert_check_interval", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 60 + }, + "cache_hit_rate_alert_enabled": { + "name": "cache_hit_rate_alert_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "cache_hit_rate_alert_webhook": { + "name": "cache_hit_rate_alert_webhook", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "cache_hit_rate_alert_window_mode": { + "name": "cache_hit_rate_alert_window_mode", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false, + "default": "'auto'" + }, + "cache_hit_rate_alert_check_interval": { + "name": "cache_hit_rate_alert_check_interval", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 5 + }, + "cache_hit_rate_alert_historical_lookback_days": { + "name": "cache_hit_rate_alert_historical_lookback_days", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 7 + }, + "cache_hit_rate_alert_min_eligible_requests": { + "name": "cache_hit_rate_alert_min_eligible_requests", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 20 + }, + "cache_hit_rate_alert_min_eligible_tokens": { + "name": "cache_hit_rate_alert_min_eligible_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "cache_hit_rate_alert_abs_min": { + "name": "cache_hit_rate_alert_abs_min", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "cache_hit_rate_alert_drop_rel": { + "name": "cache_hit_rate_alert_drop_rel", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.3'" + }, + "cache_hit_rate_alert_drop_abs": { + "name": "cache_hit_rate_alert_drop_abs", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.1'" + }, + "cache_hit_rate_alert_cooldown_minutes": { + "name": "cache_hit_rate_alert_cooldown_minutes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 30 + }, + "cache_hit_rate_alert_top_n": { + "name": "cache_hit_rate_alert_top_n", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 10 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.notification_target_bindings": { + "name": "notification_target_bindings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "notification_type": { + "name": "notification_type", + "type": "notification_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "schedule_cron": { + "name": "schedule_cron", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "schedule_timezone": { + "name": "schedule_timezone", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "template_override": { + "name": "template_override", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "unique_notification_target_binding": { + "name": "unique_notification_target_binding", + "columns": [ + { + "expression": "notification_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_notification_bindings_type": { + "name": "idx_notification_bindings_type", + "columns": [ + { + "expression": "notification_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_notification_bindings_target": { + "name": "idx_notification_bindings_target", + "columns": [ + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "notification_target_bindings_target_id_webhook_targets_id_fk": { + "name": "notification_target_bindings_target_id_webhook_targets_id_fk", + "tableFrom": "notification_target_bindings", + "tableTo": "webhook_targets", + "columnsFrom": [ + "target_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_endpoint_probe_logs": { + "name": "provider_endpoint_probe_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'scheduled'" + }, + "ok": { + "name": "ok", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "status_code": { + "name": "status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "latency_ms": { + "name": "latency_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "error_type": { + "name": "error_type", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_provider_endpoint_probe_logs_endpoint_created_at": { + "name": "idx_provider_endpoint_probe_logs_endpoint_created_at", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoint_probe_logs_created_at": { + "name": "idx_provider_endpoint_probe_logs_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "provider_endpoint_probe_logs_endpoint_id_provider_endpoints_id_fk": { + "name": "provider_endpoint_probe_logs_endpoint_id_provider_endpoints_id_fk", + "tableFrom": "provider_endpoint_probe_logs", + "tableTo": "provider_endpoints", + "columnsFrom": [ + "endpoint_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_endpoints": { + "name": "provider_endpoints", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "vendor_id": { + "name": "vendor_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "provider_type": { + "name": "provider_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'claude'" + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_probed_at": { + "name": "last_probed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_probe_ok": { + "name": "last_probe_ok", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "last_probe_status_code": { + "name": "last_probe_status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_probe_latency_ms": { + "name": "last_probe_latency_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_probe_error_type": { + "name": "last_probe_error_type", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "last_probe_error_message": { + "name": "last_probe_error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "uniq_provider_endpoints_vendor_type_url": { + "name": "uniq_provider_endpoints_vendor_type_url", + "columns": [ + { + "expression": "vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "url", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"provider_endpoints\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoints_vendor_type": { + "name": "idx_provider_endpoints_vendor_type", + "columns": [ + { + "expression": "vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"provider_endpoints\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoints_enabled": { + "name": "idx_provider_endpoints_enabled", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"provider_endpoints\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoints_pick_enabled": { + "name": "idx_provider_endpoints_pick_enabled", + "columns": [ + { + "expression": "vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sort_order", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"provider_endpoints\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoints_created_at": { + "name": "idx_provider_endpoints_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoints_deleted_at": { + "name": "idx_provider_endpoints_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "provider_endpoints_vendor_id_provider_vendors_id_fk": { + "name": "provider_endpoints_vendor_id_provider_vendors_id_fk", + "tableFrom": "provider_endpoints", + "tableTo": "provider_vendors", + "columnsFrom": [ + "vendor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_groups": { + "name": "provider_groups", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "cost_multiplier": { + "name": "cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": true, + "default": "'1.0'" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "provider_groups_name_unique": { + "name": "provider_groups_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_vendors": { + "name": "provider_vendors", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "website_domain": { + "name": "website_domain", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false + }, + "website_url": { + "name": "website_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "favicon_url": { + "name": "favicon_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "uniq_provider_vendors_website_domain": { + "name": "uniq_provider_vendors_website_domain", + "columns": [ + { + "expression": "website_domain", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_vendors_created_at": { + "name": "idx_provider_vendors_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.providers": { + "name": "providers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "provider_vendor_id": { + "name": "provider_vendor_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "weight": { + "name": "weight", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "group_priorities": { + "name": "group_priorities", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'null'::jsonb" + }, + "cost_multiplier": { + "name": "cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": false, + "default": "'1.0'" + }, + "group_tag": { + "name": "group_tag", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "provider_type": { + "name": "provider_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'claude'" + }, + "preserve_client_ip": { + "name": "preserve_client_ip", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "disable_session_reuse": { + "name": "disable_session_reuse", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "model_redirects": { + "name": "model_redirects", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "allowed_models": { + "name": "allowed_models", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'null'::jsonb" + }, + "allowed_clients": { + "name": "allowed_clients", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "blocked_clients": { + "name": "blocked_clients", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "active_time_start": { + "name": "active_time_start", + "type": "varchar(5)", + "primaryKey": false, + "notNull": false + }, + "active_time_end": { + "name": "active_time_end", + "type": "varchar(5)", + "primaryKey": false, + "notNull": false + }, + "codex_instructions_strategy": { + "name": "codex_instructions_strategy", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false, + "default": "'auto'" + }, + "mcp_passthrough_type": { + "name": "mcp_passthrough_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "mcp_passthrough_url": { + "name": "mcp_passthrough_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "limit_5h_usd": { + "name": "limit_5h_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_5h_reset_mode": { + "name": "limit_5h_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'rolling'" + }, + "limit_daily_usd": { + "name": "limit_daily_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "daily_reset_mode": { + "name": "daily_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'fixed'" + }, + "daily_reset_time": { + "name": "daily_reset_time", + "type": "varchar(5)", + "primaryKey": false, + "notNull": true, + "default": "'00:00'" + }, + "limit_weekly_usd": { + "name": "limit_weekly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_monthly_usd": { + "name": "limit_monthly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_total_usd": { + "name": "limit_total_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "total_cost_reset_at": { + "name": "total_cost_reset_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "limit_concurrent_sessions": { + "name": "limit_concurrent_sessions", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "max_retry_attempts": { + "name": "max_retry_attempts", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "circuit_breaker_failure_threshold": { + "name": "circuit_breaker_failure_threshold", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 5 + }, + "circuit_breaker_open_duration": { + "name": "circuit_breaker_open_duration", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 1800000 + }, + "circuit_breaker_half_open_success_threshold": { + "name": "circuit_breaker_half_open_success_threshold", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 2 + }, + "proxy_url": { + "name": "proxy_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "proxy_fallback_to_direct": { + "name": "proxy_fallback_to_direct", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "custom_headers": { + "name": "custom_headers", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "first_byte_timeout_streaming_ms": { + "name": "first_byte_timeout_streaming_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "streaming_idle_timeout_ms": { + "name": "streaming_idle_timeout_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "request_timeout_non_streaming_ms": { + "name": "request_timeout_non_streaming_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "website_url": { + "name": "website_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "favicon_url": { + "name": "favicon_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cache_ttl_preference": { + "name": "cache_ttl_preference", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "swap_cache_ttl_billing": { + "name": "swap_cache_ttl_billing", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "context_1m_preference": { + "name": "context_1m_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "codex_reasoning_effort_preference": { + "name": "codex_reasoning_effort_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "codex_reasoning_summary_preference": { + "name": "codex_reasoning_summary_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "codex_text_verbosity_preference": { + "name": "codex_text_verbosity_preference", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "codex_parallel_tool_calls_preference": { + "name": "codex_parallel_tool_calls_preference", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "codex_service_tier_preference": { + "name": "codex_service_tier_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "anthropic_max_tokens_preference": { + "name": "anthropic_max_tokens_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "anthropic_thinking_budget_preference": { + "name": "anthropic_thinking_budget_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "anthropic_adaptive_thinking": { + "name": "anthropic_adaptive_thinking", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'null'::jsonb" + }, + "gemini_google_search_preference": { + "name": "gemini_google_search_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "tpm": { + "name": "tpm", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "rpm": { + "name": "rpm", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "rpd": { + "name": "rpd", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "cc": { + "name": "cc", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_providers_enabled_priority": { + "name": "idx_providers_enabled_priority", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "weight", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"providers\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_group": { + "name": "idx_providers_group", + "columns": [ + { + "expression": "group_tag", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"providers\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_vendor_type_url_active": { + "name": "idx_providers_vendor_type_url_active", + "columns": [ + { + "expression": "provider_vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "url", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"providers\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_created_at": { + "name": "idx_providers_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_deleted_at": { + "name": "idx_providers_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_vendor_type": { + "name": "idx_providers_vendor_type", + "columns": [ + { + "expression": "provider_vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"providers\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_enabled_vendor_type": { + "name": "idx_providers_enabled_vendor_type", + "columns": [ + { + "expression": "provider_vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"providers\".\"deleted_at\" IS NULL AND \"providers\".\"is_enabled\" = true AND \"providers\".\"provider_vendor_id\" IS NOT NULL AND \"providers\".\"provider_vendor_id\" > 0", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "providers_provider_vendor_id_provider_vendors_id_fk": { + "name": "providers_provider_vendor_id_provider_vendors_id_fk", + "tableFrom": "providers", + "tableTo": "provider_vendors", + "columnsFrom": [ + "provider_vendor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.request_filters": { + "name": "request_filters", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true + }, + "match_type": { + "name": "match_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "target": { + "name": "target", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "replacement": { + "name": "replacement", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "binding_type": { + "name": "binding_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'global'" + }, + "provider_ids": { + "name": "provider_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "group_tags": { + "name": "group_tags", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "rule_mode": { + "name": "rule_mode", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'simple'" + }, + "execution_phase": { + "name": "execution_phase", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'guard'" + }, + "operations": { + "name": "operations", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_request_filters_enabled": { + "name": "idx_request_filters_enabled", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_request_filters_scope": { + "name": "idx_request_filters_scope", + "columns": [ + { + "expression": "scope", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_request_filters_action": { + "name": "idx_request_filters_action", + "columns": [ + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_request_filters_binding": { + "name": "idx_request_filters_binding", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "binding_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_request_filters_phase": { + "name": "idx_request_filters_phase", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_phase", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sensitive_words": { + "name": "sensitive_words", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "word": { + "name": "word", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "match_type": { + "name": "match_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'contains'" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_sensitive_words_enabled": { + "name": "idx_sensitive_words_enabled", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "match_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_sensitive_words_created_at": { + "name": "idx_sensitive_words_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.system_settings": { + "name": "system_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "site_title": { + "name": "site_title", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true, + "default": "'Claude Code Hub'" + }, + "allow_global_usage_view": { + "name": "allow_global_usage_view", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "currency_display": { + "name": "currency_display", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "default": "'USD'" + }, + "billing_model_source": { + "name": "billing_model_source", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'original'" + }, + "codex_priority_billing_source": { + "name": "codex_priority_billing_source", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'requested'" + }, + "timezone": { + "name": "timezone", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "enable_auto_cleanup": { + "name": "enable_auto_cleanup", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "cleanup_retention_days": { + "name": "cleanup_retention_days", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 30 + }, + "cleanup_schedule": { + "name": "cleanup_schedule", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false, + "default": "'0 2 * * *'" + }, + "cleanup_batch_size": { + "name": "cleanup_batch_size", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 10000 + }, + "enable_client_version_check": { + "name": "enable_client_version_check", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "verbose_provider_error": { + "name": "verbose_provider_error", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "pass_through_upstream_error_message": { + "name": "pass_through_upstream_error_message", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_http2": { + "name": "enable_http2", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "enable_openai_responses_websocket": { + "name": "enable_openai_responses_websocket", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_high_concurrency_mode": { + "name": "enable_high_concurrency_mode", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "intercept_anthropic_warmup_requests": { + "name": "intercept_anthropic_warmup_requests", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "enable_thinking_signature_rectifier": { + "name": "enable_thinking_signature_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_thinking_budget_rectifier": { + "name": "enable_thinking_budget_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_billing_header_rectifier": { + "name": "enable_billing_header_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_response_input_rectifier": { + "name": "enable_response_input_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "allow_non_conversation_endpoint_provider_fallback": { + "name": "allow_non_conversation_endpoint_provider_fallback", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "fake_streaming_whitelist": { + "name": "fake_streaming_whitelist", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "enable_codex_session_id_completion": { + "name": "enable_codex_session_id_completion", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_claude_metadata_user_id_injection": { + "name": "enable_claude_metadata_user_id_injection", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_response_fixer": { + "name": "enable_response_fixer", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "response_fixer_config": { + "name": "response_fixer_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{\"fixTruncatedJson\":true,\"fixSseFormat\":true,\"fixEncoding\":true,\"maxJsonDepth\":200,\"maxFixSize\":1048576}'::jsonb" + }, + "quota_db_refresh_interval_seconds": { + "name": "quota_db_refresh_interval_seconds", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 10 + }, + "quota_lease_percent_5h": { + "name": "quota_lease_percent_5h", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "quota_lease_percent_daily": { + "name": "quota_lease_percent_daily", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "quota_lease_percent_weekly": { + "name": "quota_lease_percent_weekly", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "quota_lease_percent_monthly": { + "name": "quota_lease_percent_monthly", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "quota_lease_cap_usd": { + "name": "quota_lease_cap_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "ip_extraction_config": { + "name": "ip_extraction_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "ip_geo_lookup_enabled": { + "name": "ip_geo_lookup_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "public_status_window_hours": { + "name": "public_status_window_hours", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 24 + }, + "public_status_aggregation_interval_minutes": { + "name": "public_status_aggregation_interval_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 5 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.usage_ledger": { + "name": "usage_ledger", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "request_id": { + "name": "request_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "final_provider_id": { + "name": "final_provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "original_model": { + "name": "original_model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "actual_response_model": { + "name": "actual_response_model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "endpoint": { + "name": "endpoint", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "api_type": { + "name": "api_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "status_code": { + "name": "status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "is_success": { + "name": "is_success", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "success_rate_outcome": { + "name": "success_rate_outcome", + "type": "varchar(16)", + "primaryKey": false, + "notNull": false + }, + "blocked_by": { + "name": "blocked_by", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "cost_usd": { + "name": "cost_usd", + "type": "numeric(21, 15)", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "cost_multiplier": { + "name": "cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": false + }, + "group_cost_multiplier": { + "name": "group_cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": false + }, + "input_tokens": { + "name": "input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "output_tokens": { + "name": "output_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_creation_input_tokens": { + "name": "cache_creation_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_read_input_tokens": { + "name": "cache_read_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_creation_5m_input_tokens": { + "name": "cache_creation_5m_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_creation_1h_input_tokens": { + "name": "cache_creation_1h_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_ttl_applied": { + "name": "cache_ttl_applied", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "context_1m_applied": { + "name": "context_1m_applied", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "swap_cache_ttl_applied": { + "name": "swap_cache_ttl_applied", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "ttfb_ms": { + "name": "ttfb_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "client_ip": { + "name": "client_ip", + "type": "varchar(45)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_usage_ledger_request_id": { + "name": "idx_usage_ledger_request_id", + "columns": [ + { + "expression": "request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_user_created_at": { + "name": "idx_usage_ledger_user_created_at", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_key_created_at": { + "name": "idx_usage_ledger_key_created_at", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_provider_created_at": { + "name": "idx_usage_ledger_provider_created_at", + "columns": [ + { + "expression": "final_provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_created_at_minute": { + "name": "idx_usage_ledger_created_at_minute", + "columns": [ + { + "expression": "date_trunc('minute', \"created_at\" AT TIME ZONE 'UTC')", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_created_at_desc_id": { + "name": "idx_usage_ledger_created_at_desc_id", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_session_id": { + "name": "idx_usage_ledger_session_id", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"session_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_model": { + "name": "idx_usage_ledger_model", + "columns": [ + { + "expression": "model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"model\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_key_cost": { + "name": "idx_usage_ledger_key_cost", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_user_cost_cover": { + "name": "idx_usage_ledger_user_cost_cover", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_provider_cost_cover": { + "name": "idx_usage_ledger_provider_cost_cover", + "columns": [ + { + "expression": "final_provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_key_created_at_desc_cover": { + "name": "idx_usage_ledger_key_created_at_desc_cover", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"created_at\" DESC NULLS LAST", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "final_provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'user'" + }, + "rpm_limit": { + "name": "rpm_limit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "daily_limit_usd": { + "name": "daily_limit_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "provider_group": { + "name": "provider_group", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false, + "default": "'default'" + }, + "tags": { + "name": "tags", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "limit_5h_usd": { + "name": "limit_5h_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_5h_reset_mode": { + "name": "limit_5h_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'rolling'" + }, + "limit_weekly_usd": { + "name": "limit_weekly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_monthly_usd": { + "name": "limit_monthly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_total_usd": { + "name": "limit_total_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "cost_reset_at": { + "name": "cost_reset_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "limit_5h_cost_reset_at": { + "name": "limit_5h_cost_reset_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "limit_concurrent_sessions": { + "name": "limit_concurrent_sessions", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "daily_reset_mode": { + "name": "daily_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'fixed'" + }, + "daily_reset_time": { + "name": "daily_reset_time", + "type": "varchar(5)", + "primaryKey": false, + "notNull": true, + "default": "'00:00'" + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "allowed_clients": { + "name": "allowed_clients", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "allowed_models": { + "name": "allowed_models", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "blocked_clients": { + "name": "blocked_clients", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_users_active_role_sort": { + "name": "idx_users_active_role_sort", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"users\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_users_enabled_expires_at": { + "name": "idx_users_enabled_expires_at", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"users\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_users_tags_gin": { + "name": "idx_users_tags_gin", + "columns": [ + { + "expression": "tags", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"users\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "gin", + "with": {} + }, + "idx_users_created_at": { + "name": "idx_users_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_users_deleted_at": { + "name": "idx_users_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.webhook_targets": { + "name": "webhook_targets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "provider_type": { + "name": "provider_type", + "type": "webhook_provider_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "webhook_url": { + "name": "webhook_url", + "type": "varchar(1024)", + "primaryKey": false, + "notNull": false + }, + "telegram_bot_token": { + "name": "telegram_bot_token", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "telegram_chat_id": { + "name": "telegram_chat_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "dingtalk_secret": { + "name": "dingtalk_secret", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "custom_template": { + "name": "custom_template", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "custom_headers": { + "name": "custom_headers", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "proxy_url": { + "name": "proxy_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "proxy_fallback_to_direct": { + "name": "proxy_fallback_to_direct", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_test_at": { + "name": "last_test_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_test_result": { + "name": "last_test_result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.daily_reset_mode": { + "name": "daily_reset_mode", + "schema": "public", + "values": [ + "fixed", + "rolling" + ] + }, + "public.notification_type": { + "name": "notification_type", + "schema": "public", + "values": [ + "circuit_breaker", + "daily_leaderboard", + "cost_alert", + "cache_hit_rate_alert" + ] + }, + "public.webhook_provider_type": { + "name": "webhook_provider_type", + "schema": "public", + "values": [ + "wechat", + "feishu", + "dingtalk", + "telegram", + "custom" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/0102_snapshot.json b/drizzle/meta/0102_snapshot.json new file mode 100644 index 000000000..91be4a5ab --- /dev/null +++ b/drizzle/meta/0102_snapshot.json @@ -0,0 +1,4497 @@ +{ + "id": "278b6561-15fa-4eac-804d-228f8480e173", + "prevId": "f475eec8-aa3e-4c45-8b70-85400b73a776", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.audit_log": { + "name": "audit_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "action_category": { + "name": "action_category", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "action_type": { + "name": "action_type", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false + }, + "target_id": { + "name": "target_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "target_name": { + "name": "target_name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "before_value": { + "name": "before_value", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "after_value": { + "name": "after_value", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "operator_user_id": { + "name": "operator_user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "operator_user_name": { + "name": "operator_user_name", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "operator_key_id": { + "name": "operator_key_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "operator_key_name": { + "name": "operator_key_name", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "operator_ip": { + "name": "operator_ip", + "type": "varchar(45)", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "success": { + "name": "success", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_audit_log_category_created_at": { + "name": "idx_audit_log_category_created_at", + "columns": [ + { + "expression": "action_category", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_log_operator_user_created_at": { + "name": "idx_audit_log_operator_user_created_at", + "columns": [ + { + "expression": "operator_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"audit_log\".\"operator_user_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_log_operator_ip_created_at": { + "name": "idx_audit_log_operator_ip_created_at", + "columns": [ + { + "expression": "operator_ip", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"audit_log\".\"operator_ip\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_log_target": { + "name": "idx_audit_log_target", + "columns": [ + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"audit_log\".\"target_type\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_log_created_at_id": { + "name": "idx_audit_log_created_at_id", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.error_rules": { + "name": "error_rules", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pattern": { + "name": "pattern", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "match_type": { + "name": "match_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'regex'" + }, + "category": { + "name": "category", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "override_response": { + "name": "override_response", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "override_status_code": { + "name": "override_status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_error_rules_enabled": { + "name": "idx_error_rules_enabled", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "unique_pattern": { + "name": "unique_pattern", + "columns": [ + { + "expression": "pattern", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_category": { + "name": "idx_category", + "columns": [ + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_match_type": { + "name": "idx_match_type", + "columns": [ + { + "expression": "match_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.keys": { + "name": "keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "can_login_web_ui": { + "name": "can_login_web_ui", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "limit_5h_usd": { + "name": "limit_5h_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_5h_reset_mode": { + "name": "limit_5h_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'rolling'" + }, + "limit_daily_usd": { + "name": "limit_daily_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "daily_reset_mode": { + "name": "daily_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'fixed'" + }, + "daily_reset_time": { + "name": "daily_reset_time", + "type": "varchar(5)", + "primaryKey": false, + "notNull": true, + "default": "'00:00'" + }, + "limit_weekly_usd": { + "name": "limit_weekly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_monthly_usd": { + "name": "limit_monthly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_total_usd": { + "name": "limit_total_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "cost_reset_at": { + "name": "cost_reset_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "limit_concurrent_sessions": { + "name": "limit_concurrent_sessions", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "provider_group": { + "name": "provider_group", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false, + "default": "'default'" + }, + "cache_ttl_preference": { + "name": "cache_ttl_preference", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_keys_user_id": { + "name": "idx_keys_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_keys_key": { + "name": "idx_keys_key", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_keys_created_at": { + "name": "idx_keys_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_keys_deleted_at": { + "name": "idx_keys_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.message_request": { + "name": "message_request", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cost_usd": { + "name": "cost_usd", + "type": "numeric(21, 15)", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "cost_multiplier": { + "name": "cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": false + }, + "group_cost_multiplier": { + "name": "group_cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": false + }, + "cost_breakdown": { + "name": "cost_breakdown", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "request_sequence": { + "name": "request_sequence", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 1 + }, + "provider_chain": { + "name": "provider_chain", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status_code": { + "name": "status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "api_type": { + "name": "api_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "endpoint": { + "name": "endpoint", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "original_model": { + "name": "original_model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "actual_response_model": { + "name": "actual_response_model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "input_tokens": { + "name": "input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "output_tokens": { + "name": "output_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "ttfb_ms": { + "name": "ttfb_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cache_creation_input_tokens": { + "name": "cache_creation_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_read_input_tokens": { + "name": "cache_read_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_creation_5m_input_tokens": { + "name": "cache_creation_5m_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_creation_1h_input_tokens": { + "name": "cache_creation_1h_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_ttl_applied": { + "name": "cache_ttl_applied", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "context_1m_applied": { + "name": "context_1m_applied", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "swap_cache_ttl_applied": { + "name": "swap_cache_ttl_applied", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "special_settings": { + "name": "special_settings", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_stack": { + "name": "error_stack", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_cause": { + "name": "error_cause", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "blocked_by": { + "name": "blocked_by", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "blocked_reason": { + "name": "blocked_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "client_ip": { + "name": "client_ip", + "type": "varchar(45)", + "primaryKey": false, + "notNull": false + }, + "messages_count": { + "name": "messages_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_message_request_user_date_cost": { + "name": "idx_message_request_user_date_cost", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_user_created_at_cost_stats": { + "name": "idx_message_request_user_created_at_cost_stats", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_user_query": { + "name": "idx_message_request_user_query", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_provider_created_at_active": { + "name": "idx_message_request_provider_created_at_active", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_provider_created_at_finalized_active": { + "name": "idx_message_request_provider_created_at_finalized_active", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"status_code\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_session_id": { + "name": "idx_message_request_session_id", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_session_id_prefix": { + "name": "idx_message_request_session_id_prefix", + "columns": [ + { + "expression": "\"session_id\" varchar_pattern_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_session_seq": { + "name": "idx_message_request_session_seq", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_sequence", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_endpoint": { + "name": "idx_message_request_endpoint", + "columns": [ + { + "expression": "endpoint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_blocked_by": { + "name": "idx_message_request_blocked_by", + "columns": [ + { + "expression": "blocked_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_provider_id": { + "name": "idx_message_request_provider_id", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_user_id": { + "name": "idx_message_request_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_key": { + "name": "idx_message_request_key", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_key_created_at_id": { + "name": "idx_message_request_key_created_at_id", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_key_model_active": { + "name": "idx_message_request_key_model_active", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"model\" IS NOT NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_key_endpoint_active": { + "name": "idx_message_request_key_endpoint_active", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "endpoint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"endpoint\" IS NOT NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_created_at_id_active": { + "name": "idx_message_request_created_at_id_active", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_model_active": { + "name": "idx_message_request_model_active", + "columns": [ + { + "expression": "model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"model\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_status_code_active": { + "name": "idx_message_request_status_code_active", + "columns": [ + { + "expression": "status_code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"status_code\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_created_at": { + "name": "idx_message_request_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_deleted_at": { + "name": "idx_message_request_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_key_last_active": { + "name": "idx_message_request_key_last_active", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_key_cost_active": { + "name": "idx_message_request_key_cost_active", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_session_user_info": { + "name": "idx_message_request_session_user_info", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_client_ip_created_at": { + "name": "idx_message_request_client_ip_created_at", + "columns": [ + { + "expression": "client_ip", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"client_ip\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.model_prices": { + "name": "model_prices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "model_name": { + "name": "model_name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "price_data": { + "name": "price_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'litellm'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_model_prices_latest": { + "name": "idx_model_prices_latest", + "columns": [ + { + "expression": "model_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_model_prices_model_name": { + "name": "idx_model_prices_model_name", + "columns": [ + { + "expression": "model_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_model_prices_created_at": { + "name": "idx_model_prices_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_model_prices_source": { + "name": "idx_model_prices_source", + "columns": [ + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.notification_settings": { + "name": "notification_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "use_legacy_mode": { + "name": "use_legacy_mode", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "circuit_breaker_enabled": { + "name": "circuit_breaker_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "circuit_breaker_webhook": { + "name": "circuit_breaker_webhook", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "daily_leaderboard_enabled": { + "name": "daily_leaderboard_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "daily_leaderboard_webhook": { + "name": "daily_leaderboard_webhook", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "daily_leaderboard_time": { + "name": "daily_leaderboard_time", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false, + "default": "'09:00'" + }, + "daily_leaderboard_top_n": { + "name": "daily_leaderboard_top_n", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 5 + }, + "cost_alert_enabled": { + "name": "cost_alert_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "cost_alert_webhook": { + "name": "cost_alert_webhook", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "cost_alert_threshold": { + "name": "cost_alert_threshold", + "type": "numeric(5, 2)", + "primaryKey": false, + "notNull": false, + "default": "'0.80'" + }, + "cost_alert_check_interval": { + "name": "cost_alert_check_interval", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 60 + }, + "cache_hit_rate_alert_enabled": { + "name": "cache_hit_rate_alert_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "cache_hit_rate_alert_webhook": { + "name": "cache_hit_rate_alert_webhook", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "cache_hit_rate_alert_window_mode": { + "name": "cache_hit_rate_alert_window_mode", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false, + "default": "'auto'" + }, + "cache_hit_rate_alert_check_interval": { + "name": "cache_hit_rate_alert_check_interval", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 5 + }, + "cache_hit_rate_alert_historical_lookback_days": { + "name": "cache_hit_rate_alert_historical_lookback_days", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 7 + }, + "cache_hit_rate_alert_min_eligible_requests": { + "name": "cache_hit_rate_alert_min_eligible_requests", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 20 + }, + "cache_hit_rate_alert_min_eligible_tokens": { + "name": "cache_hit_rate_alert_min_eligible_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "cache_hit_rate_alert_abs_min": { + "name": "cache_hit_rate_alert_abs_min", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "cache_hit_rate_alert_drop_rel": { + "name": "cache_hit_rate_alert_drop_rel", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.3'" + }, + "cache_hit_rate_alert_drop_abs": { + "name": "cache_hit_rate_alert_drop_abs", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.1'" + }, + "cache_hit_rate_alert_cooldown_minutes": { + "name": "cache_hit_rate_alert_cooldown_minutes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 30 + }, + "cache_hit_rate_alert_top_n": { + "name": "cache_hit_rate_alert_top_n", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 10 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.notification_target_bindings": { + "name": "notification_target_bindings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "notification_type": { + "name": "notification_type", + "type": "notification_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "schedule_cron": { + "name": "schedule_cron", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "schedule_timezone": { + "name": "schedule_timezone", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "template_override": { + "name": "template_override", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "unique_notification_target_binding": { + "name": "unique_notification_target_binding", + "columns": [ + { + "expression": "notification_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_notification_bindings_type": { + "name": "idx_notification_bindings_type", + "columns": [ + { + "expression": "notification_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_notification_bindings_target": { + "name": "idx_notification_bindings_target", + "columns": [ + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "notification_target_bindings_target_id_webhook_targets_id_fk": { + "name": "notification_target_bindings_target_id_webhook_targets_id_fk", + "tableFrom": "notification_target_bindings", + "tableTo": "webhook_targets", + "columnsFrom": [ + "target_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_endpoint_probe_logs": { + "name": "provider_endpoint_probe_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'scheduled'" + }, + "ok": { + "name": "ok", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "status_code": { + "name": "status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "latency_ms": { + "name": "latency_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "error_type": { + "name": "error_type", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_provider_endpoint_probe_logs_endpoint_created_at": { + "name": "idx_provider_endpoint_probe_logs_endpoint_created_at", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoint_probe_logs_created_at": { + "name": "idx_provider_endpoint_probe_logs_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "provider_endpoint_probe_logs_endpoint_id_provider_endpoints_id_fk": { + "name": "provider_endpoint_probe_logs_endpoint_id_provider_endpoints_id_fk", + "tableFrom": "provider_endpoint_probe_logs", + "tableTo": "provider_endpoints", + "columnsFrom": [ + "endpoint_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_endpoints": { + "name": "provider_endpoints", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "vendor_id": { + "name": "vendor_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "provider_type": { + "name": "provider_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'claude'" + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_probed_at": { + "name": "last_probed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_probe_ok": { + "name": "last_probe_ok", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "last_probe_status_code": { + "name": "last_probe_status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_probe_latency_ms": { + "name": "last_probe_latency_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_probe_error_type": { + "name": "last_probe_error_type", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "last_probe_error_message": { + "name": "last_probe_error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "uniq_provider_endpoints_vendor_type_url": { + "name": "uniq_provider_endpoints_vendor_type_url", + "columns": [ + { + "expression": "vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "url", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"provider_endpoints\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoints_vendor_type": { + "name": "idx_provider_endpoints_vendor_type", + "columns": [ + { + "expression": "vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"provider_endpoints\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoints_enabled": { + "name": "idx_provider_endpoints_enabled", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"provider_endpoints\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoints_pick_enabled": { + "name": "idx_provider_endpoints_pick_enabled", + "columns": [ + { + "expression": "vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sort_order", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"provider_endpoints\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoints_created_at": { + "name": "idx_provider_endpoints_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoints_deleted_at": { + "name": "idx_provider_endpoints_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "provider_endpoints_vendor_id_provider_vendors_id_fk": { + "name": "provider_endpoints_vendor_id_provider_vendors_id_fk", + "tableFrom": "provider_endpoints", + "tableTo": "provider_vendors", + "columnsFrom": [ + "vendor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_groups": { + "name": "provider_groups", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "cost_multiplier": { + "name": "cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": true, + "default": "'1.0'" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "provider_groups_name_unique": { + "name": "provider_groups_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_vendors": { + "name": "provider_vendors", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "website_domain": { + "name": "website_domain", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false + }, + "website_url": { + "name": "website_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "favicon_url": { + "name": "favicon_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "uniq_provider_vendors_website_domain": { + "name": "uniq_provider_vendors_website_domain", + "columns": [ + { + "expression": "website_domain", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_vendors_created_at": { + "name": "idx_provider_vendors_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.providers": { + "name": "providers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "provider_vendor_id": { + "name": "provider_vendor_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "weight": { + "name": "weight", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "group_priorities": { + "name": "group_priorities", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'null'::jsonb" + }, + "cost_multiplier": { + "name": "cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": false, + "default": "'1.0'" + }, + "group_tag": { + "name": "group_tag", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "provider_type": { + "name": "provider_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'claude'" + }, + "preserve_client_ip": { + "name": "preserve_client_ip", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "disable_session_reuse": { + "name": "disable_session_reuse", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "model_redirects": { + "name": "model_redirects", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "allowed_models": { + "name": "allowed_models", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'null'::jsonb" + }, + "allowed_clients": { + "name": "allowed_clients", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "blocked_clients": { + "name": "blocked_clients", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "active_time_start": { + "name": "active_time_start", + "type": "varchar(5)", + "primaryKey": false, + "notNull": false + }, + "active_time_end": { + "name": "active_time_end", + "type": "varchar(5)", + "primaryKey": false, + "notNull": false + }, + "codex_instructions_strategy": { + "name": "codex_instructions_strategy", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false, + "default": "'auto'" + }, + "mcp_passthrough_type": { + "name": "mcp_passthrough_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "mcp_passthrough_url": { + "name": "mcp_passthrough_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "limit_5h_usd": { + "name": "limit_5h_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_5h_reset_mode": { + "name": "limit_5h_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'rolling'" + }, + "limit_daily_usd": { + "name": "limit_daily_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "daily_reset_mode": { + "name": "daily_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'fixed'" + }, + "daily_reset_time": { + "name": "daily_reset_time", + "type": "varchar(5)", + "primaryKey": false, + "notNull": true, + "default": "'00:00'" + }, + "limit_weekly_usd": { + "name": "limit_weekly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_monthly_usd": { + "name": "limit_monthly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_total_usd": { + "name": "limit_total_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "total_cost_reset_at": { + "name": "total_cost_reset_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "limit_concurrent_sessions": { + "name": "limit_concurrent_sessions", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "max_retry_attempts": { + "name": "max_retry_attempts", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "circuit_breaker_failure_threshold": { + "name": "circuit_breaker_failure_threshold", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 5 + }, + "circuit_breaker_open_duration": { + "name": "circuit_breaker_open_duration", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 1800000 + }, + "circuit_breaker_half_open_success_threshold": { + "name": "circuit_breaker_half_open_success_threshold", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 2 + }, + "proxy_url": { + "name": "proxy_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "proxy_fallback_to_direct": { + "name": "proxy_fallback_to_direct", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "custom_headers": { + "name": "custom_headers", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "first_byte_timeout_streaming_ms": { + "name": "first_byte_timeout_streaming_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "streaming_idle_timeout_ms": { + "name": "streaming_idle_timeout_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "request_timeout_non_streaming_ms": { + "name": "request_timeout_non_streaming_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "website_url": { + "name": "website_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "favicon_url": { + "name": "favicon_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cache_ttl_preference": { + "name": "cache_ttl_preference", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "swap_cache_ttl_billing": { + "name": "swap_cache_ttl_billing", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "context_1m_preference": { + "name": "context_1m_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "codex_reasoning_effort_preference": { + "name": "codex_reasoning_effort_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "codex_reasoning_summary_preference": { + "name": "codex_reasoning_summary_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "codex_text_verbosity_preference": { + "name": "codex_text_verbosity_preference", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "codex_parallel_tool_calls_preference": { + "name": "codex_parallel_tool_calls_preference", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "codex_service_tier_preference": { + "name": "codex_service_tier_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "anthropic_max_tokens_preference": { + "name": "anthropic_max_tokens_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "anthropic_thinking_budget_preference": { + "name": "anthropic_thinking_budget_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "anthropic_adaptive_thinking": { + "name": "anthropic_adaptive_thinking", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'null'::jsonb" + }, + "gemini_google_search_preference": { + "name": "gemini_google_search_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "tpm": { + "name": "tpm", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "rpm": { + "name": "rpm", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "rpd": { + "name": "rpd", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "cc": { + "name": "cc", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_providers_enabled_priority": { + "name": "idx_providers_enabled_priority", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "weight", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"providers\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_group": { + "name": "idx_providers_group", + "columns": [ + { + "expression": "group_tag", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"providers\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_vendor_type_url_active": { + "name": "idx_providers_vendor_type_url_active", + "columns": [ + { + "expression": "provider_vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "url", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"providers\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_created_at": { + "name": "idx_providers_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_deleted_at": { + "name": "idx_providers_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_vendor_type": { + "name": "idx_providers_vendor_type", + "columns": [ + { + "expression": "provider_vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"providers\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_enabled_vendor_type": { + "name": "idx_providers_enabled_vendor_type", + "columns": [ + { + "expression": "provider_vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"providers\".\"deleted_at\" IS NULL AND \"providers\".\"is_enabled\" = true AND \"providers\".\"provider_vendor_id\" IS NOT NULL AND \"providers\".\"provider_vendor_id\" > 0", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "providers_provider_vendor_id_provider_vendors_id_fk": { + "name": "providers_provider_vendor_id_provider_vendors_id_fk", + "tableFrom": "providers", + "tableTo": "provider_vendors", + "columnsFrom": [ + "provider_vendor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.request_filters": { + "name": "request_filters", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true + }, + "match_type": { + "name": "match_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "target": { + "name": "target", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "replacement": { + "name": "replacement", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "binding_type": { + "name": "binding_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'global'" + }, + "provider_ids": { + "name": "provider_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "group_tags": { + "name": "group_tags", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "rule_mode": { + "name": "rule_mode", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'simple'" + }, + "execution_phase": { + "name": "execution_phase", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'guard'" + }, + "operations": { + "name": "operations", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_request_filters_enabled": { + "name": "idx_request_filters_enabled", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_request_filters_scope": { + "name": "idx_request_filters_scope", + "columns": [ + { + "expression": "scope", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_request_filters_action": { + "name": "idx_request_filters_action", + "columns": [ + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_request_filters_binding": { + "name": "idx_request_filters_binding", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "binding_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_request_filters_phase": { + "name": "idx_request_filters_phase", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_phase", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sensitive_words": { + "name": "sensitive_words", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "word": { + "name": "word", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "match_type": { + "name": "match_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'contains'" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_sensitive_words_enabled": { + "name": "idx_sensitive_words_enabled", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "match_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_sensitive_words_created_at": { + "name": "idx_sensitive_words_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.system_settings": { + "name": "system_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "site_title": { + "name": "site_title", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true, + "default": "'Claude Code Hub'" + }, + "allow_global_usage_view": { + "name": "allow_global_usage_view", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "currency_display": { + "name": "currency_display", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "default": "'USD'" + }, + "billing_model_source": { + "name": "billing_model_source", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'original'" + }, + "codex_priority_billing_source": { + "name": "codex_priority_billing_source", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'requested'" + }, + "bill_non_successful_requests": { + "name": "bill_non_successful_requests", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "timezone": { + "name": "timezone", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "enable_auto_cleanup": { + "name": "enable_auto_cleanup", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "cleanup_retention_days": { + "name": "cleanup_retention_days", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 30 + }, + "cleanup_schedule": { + "name": "cleanup_schedule", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false, + "default": "'0 2 * * *'" + }, + "cleanup_batch_size": { + "name": "cleanup_batch_size", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 10000 + }, + "enable_client_version_check": { + "name": "enable_client_version_check", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "verbose_provider_error": { + "name": "verbose_provider_error", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "pass_through_upstream_error_message": { + "name": "pass_through_upstream_error_message", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_http2": { + "name": "enable_http2", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "enable_openai_responses_websocket": { + "name": "enable_openai_responses_websocket", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_high_concurrency_mode": { + "name": "enable_high_concurrency_mode", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "intercept_anthropic_warmup_requests": { + "name": "intercept_anthropic_warmup_requests", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "enable_thinking_signature_rectifier": { + "name": "enable_thinking_signature_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_thinking_budget_rectifier": { + "name": "enable_thinking_budget_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_billing_header_rectifier": { + "name": "enable_billing_header_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_response_input_rectifier": { + "name": "enable_response_input_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "allow_non_conversation_endpoint_provider_fallback": { + "name": "allow_non_conversation_endpoint_provider_fallback", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "fake_streaming_whitelist": { + "name": "fake_streaming_whitelist", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "enable_codex_session_id_completion": { + "name": "enable_codex_session_id_completion", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_claude_metadata_user_id_injection": { + "name": "enable_claude_metadata_user_id_injection", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_response_fixer": { + "name": "enable_response_fixer", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "response_fixer_config": { + "name": "response_fixer_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{\"fixTruncatedJson\":true,\"fixSseFormat\":true,\"fixEncoding\":true,\"maxJsonDepth\":200,\"maxFixSize\":1048576}'::jsonb" + }, + "quota_db_refresh_interval_seconds": { + "name": "quota_db_refresh_interval_seconds", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 10 + }, + "quota_lease_percent_5h": { + "name": "quota_lease_percent_5h", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "quota_lease_percent_daily": { + "name": "quota_lease_percent_daily", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "quota_lease_percent_weekly": { + "name": "quota_lease_percent_weekly", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "quota_lease_percent_monthly": { + "name": "quota_lease_percent_monthly", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "quota_lease_cap_usd": { + "name": "quota_lease_cap_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "ip_extraction_config": { + "name": "ip_extraction_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "ip_geo_lookup_enabled": { + "name": "ip_geo_lookup_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "public_status_window_hours": { + "name": "public_status_window_hours", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 24 + }, + "public_status_aggregation_interval_minutes": { + "name": "public_status_aggregation_interval_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 5 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.usage_ledger": { + "name": "usage_ledger", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "request_id": { + "name": "request_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "final_provider_id": { + "name": "final_provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "original_model": { + "name": "original_model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "actual_response_model": { + "name": "actual_response_model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "endpoint": { + "name": "endpoint", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "api_type": { + "name": "api_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "status_code": { + "name": "status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "is_success": { + "name": "is_success", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "success_rate_outcome": { + "name": "success_rate_outcome", + "type": "varchar(16)", + "primaryKey": false, + "notNull": false + }, + "blocked_by": { + "name": "blocked_by", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "cost_usd": { + "name": "cost_usd", + "type": "numeric(21, 15)", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "cost_multiplier": { + "name": "cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": false + }, + "group_cost_multiplier": { + "name": "group_cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": false + }, + "input_tokens": { + "name": "input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "output_tokens": { + "name": "output_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_creation_input_tokens": { + "name": "cache_creation_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_read_input_tokens": { + "name": "cache_read_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_creation_5m_input_tokens": { + "name": "cache_creation_5m_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_creation_1h_input_tokens": { + "name": "cache_creation_1h_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_ttl_applied": { + "name": "cache_ttl_applied", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "context_1m_applied": { + "name": "context_1m_applied", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "swap_cache_ttl_applied": { + "name": "swap_cache_ttl_applied", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "ttfb_ms": { + "name": "ttfb_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "client_ip": { + "name": "client_ip", + "type": "varchar(45)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_usage_ledger_request_id": { + "name": "idx_usage_ledger_request_id", + "columns": [ + { + "expression": "request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_user_created_at": { + "name": "idx_usage_ledger_user_created_at", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_key_created_at": { + "name": "idx_usage_ledger_key_created_at", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_provider_created_at": { + "name": "idx_usage_ledger_provider_created_at", + "columns": [ + { + "expression": "final_provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_created_at_minute": { + "name": "idx_usage_ledger_created_at_minute", + "columns": [ + { + "expression": "date_trunc('minute', \"created_at\" AT TIME ZONE 'UTC')", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_created_at_desc_id": { + "name": "idx_usage_ledger_created_at_desc_id", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_session_id": { + "name": "idx_usage_ledger_session_id", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"session_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_model": { + "name": "idx_usage_ledger_model", + "columns": [ + { + "expression": "model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"model\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_key_cost": { + "name": "idx_usage_ledger_key_cost", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_user_cost_cover": { + "name": "idx_usage_ledger_user_cost_cover", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_provider_cost_cover": { + "name": "idx_usage_ledger_provider_cost_cover", + "columns": [ + { + "expression": "final_provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_key_created_at_desc_cover": { + "name": "idx_usage_ledger_key_created_at_desc_cover", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"created_at\" DESC NULLS LAST", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "final_provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'user'" + }, + "rpm_limit": { + "name": "rpm_limit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "daily_limit_usd": { + "name": "daily_limit_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "provider_group": { + "name": "provider_group", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false, + "default": "'default'" + }, + "tags": { + "name": "tags", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "limit_5h_usd": { + "name": "limit_5h_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_5h_reset_mode": { + "name": "limit_5h_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'rolling'" + }, + "limit_weekly_usd": { + "name": "limit_weekly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_monthly_usd": { + "name": "limit_monthly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_total_usd": { + "name": "limit_total_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "cost_reset_at": { + "name": "cost_reset_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "limit_5h_cost_reset_at": { + "name": "limit_5h_cost_reset_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "limit_concurrent_sessions": { + "name": "limit_concurrent_sessions", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "daily_reset_mode": { + "name": "daily_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'fixed'" + }, + "daily_reset_time": { + "name": "daily_reset_time", + "type": "varchar(5)", + "primaryKey": false, + "notNull": true, + "default": "'00:00'" + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "allowed_clients": { + "name": "allowed_clients", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "allowed_models": { + "name": "allowed_models", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "blocked_clients": { + "name": "blocked_clients", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_users_active_role_sort": { + "name": "idx_users_active_role_sort", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"users\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_users_enabled_expires_at": { + "name": "idx_users_enabled_expires_at", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"users\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_users_tags_gin": { + "name": "idx_users_tags_gin", + "columns": [ + { + "expression": "tags", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"users\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "gin", + "with": {} + }, + "idx_users_created_at": { + "name": "idx_users_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_users_deleted_at": { + "name": "idx_users_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.webhook_targets": { + "name": "webhook_targets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "provider_type": { + "name": "provider_type", + "type": "webhook_provider_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "webhook_url": { + "name": "webhook_url", + "type": "varchar(1024)", + "primaryKey": false, + "notNull": false + }, + "telegram_bot_token": { + "name": "telegram_bot_token", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "telegram_chat_id": { + "name": "telegram_chat_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "dingtalk_secret": { + "name": "dingtalk_secret", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "custom_template": { + "name": "custom_template", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "custom_headers": { + "name": "custom_headers", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "proxy_url": { + "name": "proxy_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "proxy_fallback_to_direct": { + "name": "proxy_fallback_to_direct", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_test_at": { + "name": "last_test_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_test_result": { + "name": "last_test_result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.daily_reset_mode": { + "name": "daily_reset_mode", + "schema": "public", + "values": [ + "fixed", + "rolling" + ] + }, + "public.notification_type": { + "name": "notification_type", + "schema": "public", + "values": [ + "circuit_breaker", + "daily_leaderboard", + "cost_alert", + "cache_hit_rate_alert" + ] + }, + "public.webhook_provider_type": { + "name": "webhook_provider_type", + "schema": "public", + "values": [ + "wechat", + "feishu", + "dingtalk", + "telegram", + "custom" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/0103_snapshot.json b/drizzle/meta/0103_snapshot.json new file mode 100644 index 000000000..ac1ecc93b --- /dev/null +++ b/drizzle/meta/0103_snapshot.json @@ -0,0 +1,4515 @@ +{ + "id": "c506654b-2e29-44f0-88c8-2376b07eea17", + "prevId": "278b6561-15fa-4eac-804d-228f8480e173", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.audit_log": { + "name": "audit_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "action_category": { + "name": "action_category", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "action_type": { + "name": "action_type", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false + }, + "target_id": { + "name": "target_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "target_name": { + "name": "target_name", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "before_value": { + "name": "before_value", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "after_value": { + "name": "after_value", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "operator_user_id": { + "name": "operator_user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "operator_user_name": { + "name": "operator_user_name", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "operator_key_id": { + "name": "operator_key_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "operator_key_name": { + "name": "operator_key_name", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "operator_ip": { + "name": "operator_ip", + "type": "varchar(45)", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "success": { + "name": "success", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_audit_log_category_created_at": { + "name": "idx_audit_log_category_created_at", + "columns": [ + { + "expression": "action_category", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_log_operator_user_created_at": { + "name": "idx_audit_log_operator_user_created_at", + "columns": [ + { + "expression": "operator_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"audit_log\".\"operator_user_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_log_operator_ip_created_at": { + "name": "idx_audit_log_operator_ip_created_at", + "columns": [ + { + "expression": "operator_ip", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"audit_log\".\"operator_ip\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_log_target": { + "name": "idx_audit_log_target", + "columns": [ + { + "expression": "target_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"audit_log\".\"target_type\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_audit_log_created_at_id": { + "name": "idx_audit_log_created_at_id", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.error_rules": { + "name": "error_rules", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "pattern": { + "name": "pattern", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "match_type": { + "name": "match_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'regex'" + }, + "category": { + "name": "category", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "override_response": { + "name": "override_response", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "override_status_code": { + "name": "override_status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_error_rules_enabled": { + "name": "idx_error_rules_enabled", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "unique_pattern": { + "name": "unique_pattern", + "columns": [ + { + "expression": "pattern", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_category": { + "name": "idx_category", + "columns": [ + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_match_type": { + "name": "idx_match_type", + "columns": [ + { + "expression": "match_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.keys": { + "name": "keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "can_login_web_ui": { + "name": "can_login_web_ui", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "limit_5h_usd": { + "name": "limit_5h_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_5h_reset_mode": { + "name": "limit_5h_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'rolling'" + }, + "limit_daily_usd": { + "name": "limit_daily_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "daily_reset_mode": { + "name": "daily_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'fixed'" + }, + "daily_reset_time": { + "name": "daily_reset_time", + "type": "varchar(5)", + "primaryKey": false, + "notNull": true, + "default": "'00:00'" + }, + "limit_weekly_usd": { + "name": "limit_weekly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_monthly_usd": { + "name": "limit_monthly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_total_usd": { + "name": "limit_total_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "cost_reset_at": { + "name": "cost_reset_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "limit_concurrent_sessions": { + "name": "limit_concurrent_sessions", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "provider_group": { + "name": "provider_group", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false, + "default": "'default'" + }, + "cache_ttl_preference": { + "name": "cache_ttl_preference", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_keys_user_id": { + "name": "idx_keys_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_keys_key": { + "name": "idx_keys_key", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_keys_created_at": { + "name": "idx_keys_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_keys_deleted_at": { + "name": "idx_keys_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.message_request": { + "name": "message_request", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cost_usd": { + "name": "cost_usd", + "type": "numeric(21, 15)", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "cost_multiplier": { + "name": "cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": false + }, + "group_cost_multiplier": { + "name": "group_cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": false + }, + "cost_breakdown": { + "name": "cost_breakdown", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "request_sequence": { + "name": "request_sequence", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 1 + }, + "provider_chain": { + "name": "provider_chain", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status_code": { + "name": "status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "api_type": { + "name": "api_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "endpoint": { + "name": "endpoint", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "original_model": { + "name": "original_model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "actual_response_model": { + "name": "actual_response_model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "input_tokens": { + "name": "input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "output_tokens": { + "name": "output_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "ttfb_ms": { + "name": "ttfb_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "cache_creation_input_tokens": { + "name": "cache_creation_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_read_input_tokens": { + "name": "cache_read_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_creation_5m_input_tokens": { + "name": "cache_creation_5m_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_creation_1h_input_tokens": { + "name": "cache_creation_1h_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_ttl_applied": { + "name": "cache_ttl_applied", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "context_1m_applied": { + "name": "context_1m_applied", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "swap_cache_ttl_applied": { + "name": "swap_cache_ttl_applied", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "special_settings": { + "name": "special_settings", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_stack": { + "name": "error_stack", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_cause": { + "name": "error_cause", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "blocked_by": { + "name": "blocked_by", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "blocked_reason": { + "name": "blocked_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "client_ip": { + "name": "client_ip", + "type": "varchar(45)", + "primaryKey": false, + "notNull": false + }, + "messages_count": { + "name": "messages_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_message_request_user_date_cost": { + "name": "idx_message_request_user_date_cost", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_user_created_at_cost_stats": { + "name": "idx_message_request_user_created_at_cost_stats", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_user_query": { + "name": "idx_message_request_user_query", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_provider_created_at_active": { + "name": "idx_message_request_provider_created_at_active", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_provider_created_at_finalized_active": { + "name": "idx_message_request_provider_created_at_finalized_active", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"status_code\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_session_id": { + "name": "idx_message_request_session_id", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_session_id_prefix": { + "name": "idx_message_request_session_id_prefix", + "columns": [ + { + "expression": "\"session_id\" varchar_pattern_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_session_seq": { + "name": "idx_message_request_session_seq", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "request_sequence", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_endpoint": { + "name": "idx_message_request_endpoint", + "columns": [ + { + "expression": "endpoint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_blocked_by": { + "name": "idx_message_request_blocked_by", + "columns": [ + { + "expression": "blocked_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_provider_id": { + "name": "idx_message_request_provider_id", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_user_id": { + "name": "idx_message_request_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_key": { + "name": "idx_message_request_key", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_key_created_at_id": { + "name": "idx_message_request_key_created_at_id", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_key_model_active": { + "name": "idx_message_request_key_model_active", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"model\" IS NOT NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_key_endpoint_active": { + "name": "idx_message_request_key_endpoint_active", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "endpoint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"endpoint\" IS NOT NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_created_at_id_active": { + "name": "idx_message_request_created_at_id_active", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_model_active": { + "name": "idx_message_request_model_active", + "columns": [ + { + "expression": "model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"model\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_status_code_active": { + "name": "idx_message_request_status_code_active", + "columns": [ + { + "expression": "status_code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"status_code\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_created_at": { + "name": "idx_message_request_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_deleted_at": { + "name": "idx_message_request_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_key_last_active": { + "name": "idx_message_request_key_last_active", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_key_cost_active": { + "name": "idx_message_request_key_cost_active", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_session_user_info": { + "name": "idx_message_request_session_user_info", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_client_ip_created_at": { + "name": "idx_message_request_client_ip_created_at", + "columns": [ + { + "expression": "client_ip", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"client_ip\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.model_prices": { + "name": "model_prices", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "model_name": { + "name": "model_name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "price_data": { + "name": "price_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'litellm'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_model_prices_latest": { + "name": "idx_model_prices_latest", + "columns": [ + { + "expression": "model_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_model_prices_model_name": { + "name": "idx_model_prices_model_name", + "columns": [ + { + "expression": "model_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_model_prices_created_at": { + "name": "idx_model_prices_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_model_prices_source": { + "name": "idx_model_prices_source", + "columns": [ + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.notification_settings": { + "name": "notification_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "use_legacy_mode": { + "name": "use_legacy_mode", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "circuit_breaker_enabled": { + "name": "circuit_breaker_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "circuit_breaker_webhook": { + "name": "circuit_breaker_webhook", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "daily_leaderboard_enabled": { + "name": "daily_leaderboard_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "daily_leaderboard_webhook": { + "name": "daily_leaderboard_webhook", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "daily_leaderboard_time": { + "name": "daily_leaderboard_time", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false, + "default": "'09:00'" + }, + "daily_leaderboard_top_n": { + "name": "daily_leaderboard_top_n", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 5 + }, + "cost_alert_enabled": { + "name": "cost_alert_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "cost_alert_webhook": { + "name": "cost_alert_webhook", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "cost_alert_threshold": { + "name": "cost_alert_threshold", + "type": "numeric(5, 2)", + "primaryKey": false, + "notNull": false, + "default": "'0.80'" + }, + "cost_alert_check_interval": { + "name": "cost_alert_check_interval", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 60 + }, + "cache_hit_rate_alert_enabled": { + "name": "cache_hit_rate_alert_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "cache_hit_rate_alert_webhook": { + "name": "cache_hit_rate_alert_webhook", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "cache_hit_rate_alert_window_mode": { + "name": "cache_hit_rate_alert_window_mode", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false, + "default": "'auto'" + }, + "cache_hit_rate_alert_check_interval": { + "name": "cache_hit_rate_alert_check_interval", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 5 + }, + "cache_hit_rate_alert_historical_lookback_days": { + "name": "cache_hit_rate_alert_historical_lookback_days", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 7 + }, + "cache_hit_rate_alert_min_eligible_requests": { + "name": "cache_hit_rate_alert_min_eligible_requests", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 20 + }, + "cache_hit_rate_alert_min_eligible_tokens": { + "name": "cache_hit_rate_alert_min_eligible_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "cache_hit_rate_alert_abs_min": { + "name": "cache_hit_rate_alert_abs_min", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "cache_hit_rate_alert_drop_rel": { + "name": "cache_hit_rate_alert_drop_rel", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.3'" + }, + "cache_hit_rate_alert_drop_abs": { + "name": "cache_hit_rate_alert_drop_abs", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.1'" + }, + "cache_hit_rate_alert_cooldown_minutes": { + "name": "cache_hit_rate_alert_cooldown_minutes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 30 + }, + "cache_hit_rate_alert_top_n": { + "name": "cache_hit_rate_alert_top_n", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 10 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.notification_target_bindings": { + "name": "notification_target_bindings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "notification_type": { + "name": "notification_type", + "type": "notification_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "schedule_cron": { + "name": "schedule_cron", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false + }, + "schedule_timezone": { + "name": "schedule_timezone", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "template_override": { + "name": "template_override", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "unique_notification_target_binding": { + "name": "unique_notification_target_binding", + "columns": [ + { + "expression": "notification_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_notification_bindings_type": { + "name": "idx_notification_bindings_type", + "columns": [ + { + "expression": "notification_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_notification_bindings_target": { + "name": "idx_notification_bindings_target", + "columns": [ + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "notification_target_bindings_target_id_webhook_targets_id_fk": { + "name": "notification_target_bindings_target_id_webhook_targets_id_fk", + "tableFrom": "notification_target_bindings", + "tableTo": "webhook_targets", + "columnsFrom": [ + "target_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_endpoint_probe_logs": { + "name": "provider_endpoint_probe_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "endpoint_id": { + "name": "endpoint_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'scheduled'" + }, + "ok": { + "name": "ok", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "status_code": { + "name": "status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "latency_ms": { + "name": "latency_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "error_type": { + "name": "error_type", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_provider_endpoint_probe_logs_endpoint_created_at": { + "name": "idx_provider_endpoint_probe_logs_endpoint_created_at", + "columns": [ + { + "expression": "endpoint_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoint_probe_logs_created_at": { + "name": "idx_provider_endpoint_probe_logs_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "provider_endpoint_probe_logs_endpoint_id_provider_endpoints_id_fk": { + "name": "provider_endpoint_probe_logs_endpoint_id_provider_endpoints_id_fk", + "tableFrom": "provider_endpoint_probe_logs", + "tableTo": "provider_endpoints", + "columnsFrom": [ + "endpoint_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_endpoints": { + "name": "provider_endpoints", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "vendor_id": { + "name": "vendor_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "provider_type": { + "name": "provider_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'claude'" + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_probed_at": { + "name": "last_probed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_probe_ok": { + "name": "last_probe_ok", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "last_probe_status_code": { + "name": "last_probe_status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_probe_latency_ms": { + "name": "last_probe_latency_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_probe_error_type": { + "name": "last_probe_error_type", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "last_probe_error_message": { + "name": "last_probe_error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "uniq_provider_endpoints_vendor_type_url": { + "name": "uniq_provider_endpoints_vendor_type_url", + "columns": [ + { + "expression": "vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "url", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"provider_endpoints\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoints_vendor_type": { + "name": "idx_provider_endpoints_vendor_type", + "columns": [ + { + "expression": "vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"provider_endpoints\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoints_enabled": { + "name": "idx_provider_endpoints_enabled", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"provider_endpoints\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoints_pick_enabled": { + "name": "idx_provider_endpoints_pick_enabled", + "columns": [ + { + "expression": "vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sort_order", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"provider_endpoints\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoints_created_at": { + "name": "idx_provider_endpoints_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_endpoints_deleted_at": { + "name": "idx_provider_endpoints_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "provider_endpoints_vendor_id_provider_vendors_id_fk": { + "name": "provider_endpoints_vendor_id_provider_vendors_id_fk", + "tableFrom": "provider_endpoints", + "tableTo": "provider_vendors", + "columnsFrom": [ + "vendor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_groups": { + "name": "provider_groups", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(200)", + "primaryKey": false, + "notNull": true + }, + "cost_multiplier": { + "name": "cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": true, + "default": "'1.0'" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "provider_groups_name_unique": { + "name": "provider_groups_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_vendors": { + "name": "provider_vendors", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "website_domain": { + "name": "website_domain", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false + }, + "website_url": { + "name": "website_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "favicon_url": { + "name": "favicon_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "uniq_provider_vendors_website_domain": { + "name": "uniq_provider_vendors_website_domain", + "columns": [ + { + "expression": "website_domain", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_vendors_created_at": { + "name": "idx_provider_vendors_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.providers": { + "name": "providers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "url": { + "name": "url", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "provider_vendor_id": { + "name": "provider_vendor_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "weight": { + "name": "weight", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "group_priorities": { + "name": "group_priorities", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'null'::jsonb" + }, + "cost_multiplier": { + "name": "cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": false, + "default": "'1.0'" + }, + "group_tag": { + "name": "group_tag", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "provider_type": { + "name": "provider_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'claude'" + }, + "preserve_client_ip": { + "name": "preserve_client_ip", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "disable_session_reuse": { + "name": "disable_session_reuse", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "model_redirects": { + "name": "model_redirects", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "allowed_models": { + "name": "allowed_models", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'null'::jsonb" + }, + "allowed_clients": { + "name": "allowed_clients", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "blocked_clients": { + "name": "blocked_clients", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "active_time_start": { + "name": "active_time_start", + "type": "varchar(5)", + "primaryKey": false, + "notNull": false + }, + "active_time_end": { + "name": "active_time_end", + "type": "varchar(5)", + "primaryKey": false, + "notNull": false + }, + "codex_instructions_strategy": { + "name": "codex_instructions_strategy", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false, + "default": "'auto'" + }, + "mcp_passthrough_type": { + "name": "mcp_passthrough_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "mcp_passthrough_url": { + "name": "mcp_passthrough_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "limit_5h_usd": { + "name": "limit_5h_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_5h_reset_mode": { + "name": "limit_5h_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'rolling'" + }, + "limit_daily_usd": { + "name": "limit_daily_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "daily_reset_mode": { + "name": "daily_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'fixed'" + }, + "daily_reset_time": { + "name": "daily_reset_time", + "type": "varchar(5)", + "primaryKey": false, + "notNull": true, + "default": "'00:00'" + }, + "limit_weekly_usd": { + "name": "limit_weekly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_monthly_usd": { + "name": "limit_monthly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_total_usd": { + "name": "limit_total_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "total_cost_reset_at": { + "name": "total_cost_reset_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "limit_concurrent_sessions": { + "name": "limit_concurrent_sessions", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "max_retry_attempts": { + "name": "max_retry_attempts", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "circuit_breaker_failure_threshold": { + "name": "circuit_breaker_failure_threshold", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 5 + }, + "circuit_breaker_open_duration": { + "name": "circuit_breaker_open_duration", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 1800000 + }, + "circuit_breaker_half_open_success_threshold": { + "name": "circuit_breaker_half_open_success_threshold", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 2 + }, + "proxy_url": { + "name": "proxy_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "proxy_fallback_to_direct": { + "name": "proxy_fallback_to_direct", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "custom_headers": { + "name": "custom_headers", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "first_byte_timeout_streaming_ms": { + "name": "first_byte_timeout_streaming_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "streaming_idle_timeout_ms": { + "name": "streaming_idle_timeout_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "request_timeout_non_streaming_ms": { + "name": "request_timeout_non_streaming_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "website_url": { + "name": "website_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "favicon_url": { + "name": "favicon_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cache_ttl_preference": { + "name": "cache_ttl_preference", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "swap_cache_ttl_billing": { + "name": "swap_cache_ttl_billing", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "context_1m_preference": { + "name": "context_1m_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "codex_reasoning_effort_preference": { + "name": "codex_reasoning_effort_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "codex_reasoning_summary_preference": { + "name": "codex_reasoning_summary_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "codex_text_verbosity_preference": { + "name": "codex_text_verbosity_preference", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "codex_parallel_tool_calls_preference": { + "name": "codex_parallel_tool_calls_preference", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "codex_service_tier_preference": { + "name": "codex_service_tier_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "anthropic_max_tokens_preference": { + "name": "anthropic_max_tokens_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "anthropic_thinking_budget_preference": { + "name": "anthropic_thinking_budget_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "anthropic_adaptive_thinking": { + "name": "anthropic_adaptive_thinking", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'null'::jsonb" + }, + "gemini_google_search_preference": { + "name": "gemini_google_search_preference", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "tpm": { + "name": "tpm", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "rpm": { + "name": "rpm", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "rpd": { + "name": "rpd", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "cc": { + "name": "cc", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_providers_enabled_priority": { + "name": "idx_providers_enabled_priority", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "weight", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"providers\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_group": { + "name": "idx_providers_group", + "columns": [ + { + "expression": "group_tag", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"providers\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_vendor_type_url_active": { + "name": "idx_providers_vendor_type_url_active", + "columns": [ + { + "expression": "provider_vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "url", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"providers\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_created_at": { + "name": "idx_providers_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_deleted_at": { + "name": "idx_providers_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_vendor_type": { + "name": "idx_providers_vendor_type", + "columns": [ + { + "expression": "provider_vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"providers\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_providers_enabled_vendor_type": { + "name": "idx_providers_enabled_vendor_type", + "columns": [ + { + "expression": "provider_vendor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"providers\".\"deleted_at\" IS NULL AND \"providers\".\"is_enabled\" = true AND \"providers\".\"provider_vendor_id\" IS NOT NULL AND \"providers\".\"provider_vendor_id\" > 0", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "providers_provider_vendor_id_provider_vendors_id_fk": { + "name": "providers_provider_vendor_id_provider_vendors_id_fk", + "tableFrom": "providers", + "tableTo": "provider_vendors", + "columnsFrom": [ + "provider_vendor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.request_filters": { + "name": "request_filters", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "varchar(30)", + "primaryKey": false, + "notNull": true + }, + "match_type": { + "name": "match_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "target": { + "name": "target", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "replacement": { + "name": "replacement", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "binding_type": { + "name": "binding_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'global'" + }, + "provider_ids": { + "name": "provider_ids", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "group_tags": { + "name": "group_tags", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "rule_mode": { + "name": "rule_mode", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'simple'" + }, + "execution_phase": { + "name": "execution_phase", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'guard'" + }, + "operations": { + "name": "operations", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_request_filters_enabled": { + "name": "idx_request_filters_enabled", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_request_filters_scope": { + "name": "idx_request_filters_scope", + "columns": [ + { + "expression": "scope", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_request_filters_action": { + "name": "idx_request_filters_action", + "columns": [ + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_request_filters_binding": { + "name": "idx_request_filters_binding", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "binding_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_request_filters_phase": { + "name": "idx_request_filters_phase", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "execution_phase", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sensitive_words": { + "name": "sensitive_words", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "word": { + "name": "word", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "match_type": { + "name": "match_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'contains'" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_sensitive_words_enabled": { + "name": "idx_sensitive_words_enabled", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "match_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_sensitive_words_created_at": { + "name": "idx_sensitive_words_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.system_settings": { + "name": "system_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "site_title": { + "name": "site_title", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true, + "default": "'Claude Code Hub'" + }, + "allow_global_usage_view": { + "name": "allow_global_usage_view", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "currency_display": { + "name": "currency_display", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "default": "'USD'" + }, + "billing_model_source": { + "name": "billing_model_source", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'original'" + }, + "codex_priority_billing_source": { + "name": "codex_priority_billing_source", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'requested'" + }, + "bill_non_successful_requests": { + "name": "bill_non_successful_requests", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "timezone": { + "name": "timezone", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "enable_auto_cleanup": { + "name": "enable_auto_cleanup", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "cleanup_retention_days": { + "name": "cleanup_retention_days", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 30 + }, + "cleanup_schedule": { + "name": "cleanup_schedule", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false, + "default": "'0 2 * * *'" + }, + "cleanup_batch_size": { + "name": "cleanup_batch_size", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 10000 + }, + "enable_client_version_check": { + "name": "enable_client_version_check", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "verbose_provider_error": { + "name": "verbose_provider_error", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "pass_through_upstream_error_message": { + "name": "pass_through_upstream_error_message", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_http2": { + "name": "enable_http2", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "enable_openai_responses_websocket": { + "name": "enable_openai_responses_websocket", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_high_concurrency_mode": { + "name": "enable_high_concurrency_mode", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "intercept_anthropic_warmup_requests": { + "name": "intercept_anthropic_warmup_requests", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "enable_thinking_signature_rectifier": { + "name": "enable_thinking_signature_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_thinking_budget_rectifier": { + "name": "enable_thinking_budget_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_billing_header_rectifier": { + "name": "enable_billing_header_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_response_input_rectifier": { + "name": "enable_response_input_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "allow_non_conversation_endpoint_provider_fallback": { + "name": "allow_non_conversation_endpoint_provider_fallback", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "fake_streaming_whitelist": { + "name": "fake_streaming_whitelist", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "enable_codex_session_id_completion": { + "name": "enable_codex_session_id_completion", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_claude_metadata_user_id_injection": { + "name": "enable_claude_metadata_user_id_injection", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_response_fixer": { + "name": "enable_response_fixer", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "response_fixer_config": { + "name": "response_fixer_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{\"fixTruncatedJson\":true,\"fixSseFormat\":true,\"fixEncoding\":true,\"maxJsonDepth\":200,\"maxFixSize\":1048576}'::jsonb" + }, + "quota_db_refresh_interval_seconds": { + "name": "quota_db_refresh_interval_seconds", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 10 + }, + "quota_lease_percent_5h": { + "name": "quota_lease_percent_5h", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "quota_lease_percent_daily": { + "name": "quota_lease_percent_daily", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "quota_lease_percent_weekly": { + "name": "quota_lease_percent_weekly", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "quota_lease_percent_monthly": { + "name": "quota_lease_percent_monthly", + "type": "numeric(5, 4)", + "primaryKey": false, + "notNull": false, + "default": "'0.05'" + }, + "quota_lease_cap_usd": { + "name": "quota_lease_cap_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "ip_extraction_config": { + "name": "ip_extraction_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "ip_geo_lookup_enabled": { + "name": "ip_geo_lookup_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "public_status_window_hours": { + "name": "public_status_window_hours", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 24 + }, + "public_status_aggregation_interval_minutes": { + "name": "public_status_aggregation_interval_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 5 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.usage_ledger": { + "name": "usage_ledger", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "request_id": { + "name": "request_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "final_provider_id": { + "name": "final_provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "original_model": { + "name": "original_model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "actual_response_model": { + "name": "actual_response_model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "endpoint": { + "name": "endpoint", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "api_type": { + "name": "api_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "status_code": { + "name": "status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "is_success": { + "name": "is_success", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "success_rate_outcome": { + "name": "success_rate_outcome", + "type": "varchar(16)", + "primaryKey": false, + "notNull": false + }, + "blocked_by": { + "name": "blocked_by", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "cost_usd": { + "name": "cost_usd", + "type": "numeric(21, 15)", + "primaryKey": false, + "notNull": false, + "default": "'0'" + }, + "cost_multiplier": { + "name": "cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": false + }, + "group_cost_multiplier": { + "name": "group_cost_multiplier", + "type": "numeric(10, 4)", + "primaryKey": false, + "notNull": false + }, + "input_tokens": { + "name": "input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "output_tokens": { + "name": "output_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_creation_input_tokens": { + "name": "cache_creation_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_read_input_tokens": { + "name": "cache_read_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_creation_5m_input_tokens": { + "name": "cache_creation_5m_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_creation_1h_input_tokens": { + "name": "cache_creation_1h_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_ttl_applied": { + "name": "cache_ttl_applied", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "context_1m_applied": { + "name": "context_1m_applied", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "swap_cache_ttl_applied": { + "name": "swap_cache_ttl_applied", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "ttfb_ms": { + "name": "ttfb_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "client_ip": { + "name": "client_ip", + "type": "varchar(45)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_usage_ledger_request_id": { + "name": "idx_usage_ledger_request_id", + "columns": [ + { + "expression": "request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_user_created_at": { + "name": "idx_usage_ledger_user_created_at", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_key_created_at": { + "name": "idx_usage_ledger_key_created_at", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_provider_created_at": { + "name": "idx_usage_ledger_provider_created_at", + "columns": [ + { + "expression": "final_provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_created_at_minute": { + "name": "idx_usage_ledger_created_at_minute", + "columns": [ + { + "expression": "date_trunc('minute', \"created_at\" AT TIME ZONE 'UTC')", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_created_at_desc_id": { + "name": "idx_usage_ledger_created_at_desc_id", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_session_id": { + "name": "idx_usage_ledger_session_id", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"session_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_model": { + "name": "idx_usage_ledger_model", + "columns": [ + { + "expression": "model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"model\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_key_cost": { + "name": "idx_usage_ledger_key_cost", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "endpoint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_user_cost_cover": { + "name": "idx_usage_ledger_user_cost_cover", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "endpoint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_provider_cost_cover": { + "name": "idx_usage_ledger_provider_cost_cover", + "columns": [ + { + "expression": "final_provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cost_usd", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "endpoint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_key_created_at_desc_cover": { + "name": "idx_usage_ledger_key_created_at_desc_cover", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"created_at\" DESC NULLS LAST", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "final_provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "varchar", + "primaryKey": false, + "notNull": false, + "default": "'user'" + }, + "rpm_limit": { + "name": "rpm_limit", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "daily_limit_usd": { + "name": "daily_limit_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "provider_group": { + "name": "provider_group", + "type": "varchar(200)", + "primaryKey": false, + "notNull": false, + "default": "'default'" + }, + "tags": { + "name": "tags", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "limit_5h_usd": { + "name": "limit_5h_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_5h_reset_mode": { + "name": "limit_5h_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'rolling'" + }, + "limit_weekly_usd": { + "name": "limit_weekly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_monthly_usd": { + "name": "limit_monthly_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "limit_total_usd": { + "name": "limit_total_usd", + "type": "numeric(10, 2)", + "primaryKey": false, + "notNull": false + }, + "cost_reset_at": { + "name": "cost_reset_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "limit_5h_cost_reset_at": { + "name": "limit_5h_cost_reset_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "limit_concurrent_sessions": { + "name": "limit_concurrent_sessions", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "daily_reset_mode": { + "name": "daily_reset_mode", + "type": "daily_reset_mode", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'fixed'" + }, + "daily_reset_time": { + "name": "daily_reset_time", + "type": "varchar(5)", + "primaryKey": false, + "notNull": true, + "default": "'00:00'" + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "allowed_clients": { + "name": "allowed_clients", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "allowed_models": { + "name": "allowed_models", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'[]'::jsonb" + }, + "blocked_clients": { + "name": "blocked_clients", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_users_active_role_sort": { + "name": "idx_users_active_role_sort", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "role", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"users\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_users_enabled_expires_at": { + "name": "idx_users_enabled_expires_at", + "columns": [ + { + "expression": "is_enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"users\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_users_tags_gin": { + "name": "idx_users_tags_gin", + "columns": [ + { + "expression": "tags", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"users\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "gin", + "with": {} + }, + "idx_users_created_at": { + "name": "idx_users_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_users_deleted_at": { + "name": "idx_users_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.webhook_targets": { + "name": "webhook_targets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true + }, + "provider_type": { + "name": "provider_type", + "type": "webhook_provider_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "webhook_url": { + "name": "webhook_url", + "type": "varchar(1024)", + "primaryKey": false, + "notNull": false + }, + "telegram_bot_token": { + "name": "telegram_bot_token", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "telegram_chat_id": { + "name": "telegram_chat_id", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "dingtalk_secret": { + "name": "dingtalk_secret", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "custom_template": { + "name": "custom_template", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "custom_headers": { + "name": "custom_headers", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "proxy_url": { + "name": "proxy_url", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "proxy_fallback_to_direct": { + "name": "proxy_fallback_to_direct", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_test_at": { + "name": "last_test_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_test_result": { + "name": "last_test_result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.daily_reset_mode": { + "name": "daily_reset_mode", + "schema": "public", + "values": [ + "fixed", + "rolling" + ] + }, + "public.notification_type": { + "name": "notification_type", + "schema": "public", + "values": [ + "circuit_breaker", + "daily_leaderboard", + "cost_alert", + "cache_hit_rate_alert" + ] + }, + "public.webhook_provider_type": { + "name": "webhook_provider_type", + "schema": "public", + "values": [ + "wechat", + "feishu", + "dingtalk", + "telegram", + "custom" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index 579a42619..c27e52755 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -694,6 +694,41 @@ "when": 1776965161943, "tag": "0098_equal_selene", "breakpoints": true + }, + { + "idx": 99, + "version": "7", + "when": 1777361216354, + "tag": "0099_nervous_squadron_sinister", + "breakpoints": true + }, + { + "idx": 100, + "version": "7", + "when": 1777362451734, + "tag": "0100_equal_expediter", + "breakpoints": true + }, + { + "idx": 101, + "version": "7", + "when": 1777362451735, + "tag": "0101_worthless_gauntlet", + "breakpoints": true + }, + { + "idx": 102, + "version": "7", + "when": 1777739658761, + "tag": "0102_useful_lionheart", + "breakpoints": true + }, + { + "idx": 103, + "version": "7", + "when": 1779003941480, + "tag": "0103_small_master_mold", + "breakpoints": true } ] -} +} \ No newline at end of file diff --git a/messages/en/auditLogs.json b/messages/en/auditLogs.json index 862bc13dd..4bb77e766 100644 --- a/messages/en/auditLogs.json +++ b/messages/en/auditLogs.json @@ -45,7 +45,8 @@ "provider": { "create": "Create provider", "update": "Update provider", - "delete": "Delete provider" + "delete": "Delete provider", + "key_reveal": "Reveal provider key" }, "provider_group": { "create": "Create provider group", @@ -58,7 +59,8 @@ "key": { "create": "Create key", "update": "Update key", - "delete": "Delete key" + "delete": "Delete key", + "key_reveal": "Reveal user key" }, "notification": { "update": "Update notification" diff --git a/messages/en/dashboard.json b/messages/en/dashboard.json index 21db4cc9d..eddce290d 100644 --- a/messages/en/dashboard.json +++ b/messages/en/dashboard.json @@ -718,6 +718,7 @@ "noRequests": "No requests found", "fetchFailed": "Failed to fetch requests", "unknownError": "Unknown error occurred", + "searchPlaceholder": "Search requests...", "prev": "Prev", "next": "Next", "orderAsc": "Switch to ascending order (oldest first)", diff --git a/messages/en/errors.json b/messages/en/errors.json index da374c2b3..0e9d3a4ac 100644 --- a/messages/en/errors.json +++ b/messages/en/errors.json @@ -30,6 +30,9 @@ "PERMISSION_DENIED": "Permission denied", "TOKEN_REQUIRED": "Authentication token required", "INVALID_TOKEN": "Invalid authentication token", + "PROXY_INVALID_API_KEY": "Invalid API key. The provided key does not exist or has been deleted.", + "PROXY_API_KEY_DISABLED": "This API key has been disabled. Please contact your administrator to re-enable it, or use a different key.", + "PROXY_API_KEY_EXPIRED": "This API key has expired. Please contact your administrator to renew it or rotate to a new key.", "INTERNAL_ERROR": "Internal server error, please try again later", "DATABASE_ERROR": "Database error", diff --git a/messages/en/settings/config.json b/messages/en/settings/config.json index 0a102133b..17faa0378 100644 --- a/messages/en/settings/config.json +++ b/messages/en/settings/config.json @@ -56,6 +56,8 @@ "enableAutoCleanupDesc": "Automatically clean up historical log data on schedule", "enableHttp2": "Enable HTTP/2", "enableHttp2Desc": "When enabled, proxy requests will prefer HTTP/2 protocol. Automatically falls back to HTTP/1.1 on failure.", + "enableOpenaiResponsesWebsocket": "Enable OpenAI Responses WebSocket", + "enableOpenaiResponsesWebsocketDesc": "When enabled, if a client opens a WebSocket connection to /v1/responses and the selected provider is a Codex type, CCH will attempt a sibling WebSocket to the upstream. If the upstream does not support WebSocket or the handshake fails, CCH gracefully falls back to standard HTTP Responses while keeping the client WebSocket open; the fallback is not counted toward circuit breakers. Non-WebSocket clients and non-Codex providers are unaffected.", "enableHighConcurrencyMode": "Enable High-Concurrency Mode", "enableHighConcurrencyModeDesc": "When enabled, CCH disables part of the Redis debug snapshots and real-time session observability writes to reduce CPU and IO pressure under high RPM. Forwarding, rectifiers, fake-200 detection, billing, and quota enforcement remain unchanged, but Sessions debugging details may be reduced or delayed.", "enableResponseFixer": "Enable Response Fixer", @@ -70,6 +72,17 @@ "enableResponseInputRectifierDesc": "Automatically normalizes non-array input (string shortcut or single message object) in /v1/responses requests to standard array format before processing (enabled by default).", "allowNonConversationEndpointProviderFallback": "Allow cross-provider fallback for non-conversation endpoints", "allowNonConversationEndpointProviderFallbackDesc": "Controls whether /v1/messages/count_tokens and /v1/responses/compact may reuse the existing decision chain to switch to a compatible provider after the current provider fails. Enabled by default while preserving raw passthrough and non-billing semantics.", + "fakeStreaming": { + "title": "Fake streaming whitelist", + "description": "For long-running synchronous generations (image / video) that exceed Cloudflare's 120s no-body timeout, CCH keeps the SSE connection alive with safe heartbeat pings while it serially calls upstream providers internally and only emits a final, non-empty result. Listed models are auto-enabled; an empty list disables fake streaming entirely.", + "modelLabel": "Model", + "modelPlaceholder": "exact client-requested model name", + "groupsLabel": "Groups", + "allGroupsHint": "No groups selected — applies to all provider groups.", + "addModel": "Add model", + "remove": "Remove", + "emptyState": "No models configured. Fake streaming is disabled — use \"Add model\" to enable it for specific models." + }, "enableCodexSessionIdCompletion": "Enable Codex Session ID Completion", "enableCodexSessionIdCompletionDesc": "When Codex requests provide only one of session_id (header) or prompt_cache_key (body), automatically completes the other. If both are missing, generates a UUID v7 session id and reuses it stably within the same conversation.", "enableClaudeMetadataUserIdInjection": "Enable Claude metadata.user_id Injection", @@ -95,6 +108,9 @@ "siteTitleRequired": "Site title cannot be empty", "ipLoggingInvalidJson": "IP extraction config is not valid JSON: {message}", "ipLoggingInvalidShape": "IP extraction config must be an object with a `headers` array.", + "billNonSuccessfulRequests": "Bill Non-2xx Requests by Token Usage", + "billNonSuccessfulRequestsDesc": "When enabled, requests with non-success status (e.g., 499 client cancellation) are billed by token usage if upstream returned positive usage data. Default off.", + "billNonSuccessfulRequestsTooltip": "Useful when an upstream provider counts tokens regardless of the final status (e.g., aborted streaming responses). Fake-200 upstream errors remain unbilled.", "verboseProviderError": "Verbose Provider Error", "verboseProviderErrorDesc": "When enabled, CCH may return detailed diagnostic information for some upstream failure types in `error.details` (for example provider availability diagnostics or sanitized upstream snippets).", "verboseProviderErrorTooltip": "May expose provider names, internal routing clues, upstream failure reasons, and other diagnostic details. Enable only if clients are allowed to see low-level troubleshooting context.", diff --git a/messages/en/settings/prices.json b/messages/en/settings/prices.json index 534410124..cb206b446 100644 --- a/messages/en/settings/prices.json +++ b/messages/en/settings/prices.json @@ -172,10 +172,10 @@ "deleteConfirm": "Are you sure you want to delete model {name}? This action cannot be undone.", "form": { "modelName": "Model ID", - "modelNamePlaceholder": "e.g., gpt-5.3-codex", + "modelNamePlaceholder": "e.g., gpt-5.4", "modelNameRequired": "Model ID is required", "displayName": "Display Name (Optional)", - "displayNamePlaceholder": "e.g., GPT-5.3 Codex", + "displayNamePlaceholder": "e.g., GPT-5.4 Codex", "type": "Type", "provider": "Provider", "providerPlaceholder": "e.g., openai", diff --git a/messages/en/settings/providers/form/apiTest.json b/messages/en/settings/providers/form/apiTest.json index fbd10d75c..802aa75be 100644 --- a/messages/en/settings/providers/form/apiTest.json +++ b/messages/en/settings/providers/form/apiTest.json @@ -13,6 +13,21 @@ "copyResult": "Copy Result", "copySuccess": "Copied to clipboard", "customConfig": "Custom", + "customHeaders": { + "label": "Custom request headers (JSON)", + "desc": "Static headers merged into the outbound test request. Will not override authentication.", + "geminiNotSupported": "Custom headers are not supported for the Gemini test path", + "errors": { + "invalidJson": "Custom headers must be valid JSON", + "notObject": "Custom headers must be a JSON object", + "invalidName": "Header name contains characters that are not allowed", + "duplicateName": "Header name appears more than once (case-insensitive)", + "protectedName": "Authentication headers cannot be set via custom headers", + "invalidValue": "Header values must be strings", + "emptyName": "Header name cannot be empty", + "crlf": "Header name and value cannot contain line breaks" + } + }, "customPayloadDesc": "Enter custom JSON payload to override default request body", "customPayloadPlaceholder": "{\"model\": \"...\", \"messages\": [...]}", "disclaimer": { diff --git a/messages/en/settings/providers/form/modelSelect.json b/messages/en/settings/providers/form/modelSelect.json index 9a34815a7..23f77b387 100644 --- a/messages/en/settings/providers/form/modelSelect.json +++ b/messages/en/settings/providers/form/modelSelect.json @@ -10,7 +10,7 @@ "loading": "Loading...", "manualAdd": "Manually Add Model", "manualDesc": "Support adding any model name (not limited to price table)", - "manualPlaceholder": "Enter model name (e.g. gpt-5-turbo)", + "manualPlaceholder": "Enter model name (e.g. gpt-5.4)", "notFound": "Model not found", "openai": "OpenAI", "providerFilterAll": "All Providers", diff --git a/messages/en/settings/providers/form/sections.json b/messages/en/settings/providers/form/sections.json index e6fa34f68..af9272aa2 100644 --- a/messages/en/settings/providers/form/sections.json +++ b/messages/en/settings/providers/form/sections.json @@ -190,6 +190,20 @@ "label": "Swap Cache TTL Billing", "desc": "Invert cache TTL for incoming data: 1h tokens treated as 5min and vice versa. Affects badge, cost, and all stored metrics." }, + "customHeaders": { + "label": "Custom request headers (JSON)", + "desc": "Static headers merged into outbound requests. Cannot override authentication or final request filters.", + "errors": { + "invalidJson": "Custom headers must be valid JSON", + "notObject": "Custom headers must be a JSON object", + "invalidName": "Header name contains characters that are not allowed", + "duplicateName": "Header name appears more than once (case-insensitive)", + "protectedName": "Authentication headers cannot be set via custom headers", + "invalidValue": "Header values must be strings", + "emptyName": "Header name cannot be empty", + "crlf": "Header name and value cannot contain line breaks" + } + }, "codexOverrides": { "title": "Codex Parameter Overrides", "desc": "Override Codex (Responses API) request parameters at the provider level", diff --git a/messages/en/usage.json b/messages/en/usage.json index c022cc62c..419669bc0 100644 --- a/messages/en/usage.json +++ b/messages/en/usage.json @@ -544,7 +544,7 @@ "importantPoints": [ "Create an API key in the cch console and set the CCH_API_KEY environment variable", "cchClaude/openai use ${resolvedOrigin}/v1; cchGemini uses ${resolvedOrigin}/v1beta", - "When selecting models, use provider_id/model_id (e.g. openai/gpt-5.2 or cchClaude/claude-sonnet-4-5-20250929)" + "When selecting models, use provider_id/model_id (e.g. openai/gpt-5.4 or cchClaude/claude-sonnet-4-5-20250929)" ] }, @@ -622,7 +622,7 @@ "steps": [ "Restart Droid", "Enter the /model command", - "Select GPT-5-Codex [cch] or Sonnet 4.5 [cch]", + "Select GPT-5.4 [cch] or Sonnet 4.5 [cch]", "Start using!" ] } diff --git a/messages/ja/auditLogs.json b/messages/ja/auditLogs.json index 9b66ab7fe..6e21cdf4e 100644 --- a/messages/ja/auditLogs.json +++ b/messages/ja/auditLogs.json @@ -45,7 +45,8 @@ "provider": { "create": "プロバイダー作成", "update": "プロバイダー更新", - "delete": "プロバイダー削除" + "delete": "プロバイダー削除", + "key_reveal": "プロバイダーキー表示" }, "provider_group": { "create": "プロバイダーグループ作成", @@ -58,7 +59,8 @@ "key": { "create": "キー作成", "update": "キー更新", - "delete": "キー削除" + "delete": "キー削除", + "key_reveal": "ユーザーキーを表示" }, "notification": { "update": "通知更新" diff --git a/messages/ja/dashboard.json b/messages/ja/dashboard.json index f3b3a9561..6fec7c32a 100644 --- a/messages/ja/dashboard.json +++ b/messages/ja/dashboard.json @@ -718,6 +718,7 @@ "noRequests": "リクエストがありません", "fetchFailed": "リクエスト一覧の取得に失敗しました", "unknownError": "不明なエラー", + "searchPlaceholder": "リクエストを検索...", "prev": "前へ", "next": "次へ", "orderAsc": "昇順に切り替え(古い順)", diff --git a/messages/ja/errors.json b/messages/ja/errors.json index 5758e6fc5..b951cd5bb 100644 --- a/messages/ja/errors.json +++ b/messages/ja/errors.json @@ -30,6 +30,9 @@ "PERMISSION_DENIED": "アクセス権限がありません", "TOKEN_REQUIRED": "認証トークンが必要です", "INVALID_TOKEN": "無効な認証トークン", + "PROXY_INVALID_API_KEY": "API キーが無効です。指定されたキーは存在しないか、削除されています。", + "PROXY_API_KEY_DISABLED": "この API キーは無効化されています。管理者に再有効化を依頼するか、別のキーをご使用ください。", + "PROXY_API_KEY_EXPIRED": "この API キーは期限切れです。管理者に更新を依頼するか、新しいキーへ切り替えてください。", "INTERNAL_ERROR": "内部サーバーエラー、後でもう一度お試しください", "DATABASE_ERROR": "データベースエラー", diff --git a/messages/ja/settings/config.json b/messages/ja/settings/config.json index bc47d2580..2da14986a 100644 --- a/messages/ja/settings/config.json +++ b/messages/ja/settings/config.json @@ -56,6 +56,8 @@ "enableAutoCleanupDesc": "スケジュールに従って履歴ログを自動的にクリーンアップします", "enableHttp2": "HTTP/2 を有効にする", "enableHttp2Desc": "有効にすると、プロキシ要求は優先的に HTTP/2 を使用します。HTTP/2 が失敗した場合は自動的に HTTP/1.1 にフォールバックします。", + "enableOpenaiResponsesWebsocket": "OpenAI Responses WebSocket を有効化", + "enableOpenaiResponsesWebsocketDesc": "有効にすると、クライアントが /v1/responses に WebSocket 接続し、かつ Codex タイプのプロバイダーが選択された場合、CCH は上流にも WebSocket 接続を試みます。上流が WebSocket をサポートしない、またはハンドシェイクに失敗した場合は、クライアント WebSocket を開いたまま通常の HTTP Responses に優雅にフォールバックします。このフォールバックはサーキットブレーカーにカウントされません。非 WebSocket クライアントと非 Codex プロバイダーの動作は変わりません。", "enableHighConcurrencyMode": "高並行モードを有効化", "enableHighConcurrencyModeDesc": "有効にすると、高 RPM 時の CPU / IO 負荷を下げるため、Redis の一部デバッグスナップショットとリアルタイム Session 観測書き込みを停止します。転送、整流、fake 200 検知、課金、制限処理は維持されますが、Sessions のデバッグ詳細は減少または遅延する場合があります。", "enableResponseFixer": "レスポンス整流を有効化", @@ -70,6 +72,17 @@ "enableResponseInputRectifierDesc": "/v1/responses リクエストの非配列 input(文字列ショートカットまたは role/type 付き単一メッセージオブジェクト)を処理前に標準の配列形式に自動正規化します(既定で有効)。", "allowNonConversationEndpointProviderFallback": "非会話エンドポイントのクロスプロバイダ fallback を許可", "allowNonConversationEndpointProviderFallbackDesc": "/v1/messages/count_tokens と /v1/responses/compact で現在のプロバイダが失敗した際に、既存の決定チェーンを使って互換プロバイダへ切り替えて再試行するかを制御します。既定で有効で、raw passthrough と非課金の意味論は維持されます。", + "fakeStreaming": { + "title": "Fake ストリーミング出力ホワイトリスト", + "description": "画像 / 動画など Cloudflare の 120 秒ノーボディタイムアウトを超えやすい長時間同期生成では、CCH が SSE ハートビートで接続を維持しつつ内部で上流プロバイダを直列に呼び出し、最終的な非空結果のみをクライアントへ返します。リスト掲載モデルは自動的に有効化、空リストは完全に無効化を意味します。", + "modelLabel": "モデル", + "modelPlaceholder": "クライアント要求の正確なモデル名", + "groupsLabel": "プロバイダグループ", + "allGroupsHint": "グループ未選択 → すべてのプロバイダグループに適用されます。", + "addModel": "モデル追加", + "remove": "削除", + "emptyState": "モデル未設定 — fake streaming は無効です。「モデル追加」で対象モデルを設定すると有効化されます。" + }, "enableCodexSessionIdCompletion": "Codex セッションID補完を有効化", "enableCodexSessionIdCompletionDesc": "Codex リクエストで session_id(ヘッダー)または prompt_cache_key(ボディ)のどちらか一方しか提供されない場合に、欠けている方を自動補完します。両方ない場合は UUID v7 のセッションIDを生成し、同一対話内で安定して再利用します。", "enableClaudeMetadataUserIdInjection": "Claude metadata.user_id 注入を有効化", @@ -97,6 +110,9 @@ "siteTitleRequired": "サイトタイトルは空にできません", "ipLoggingInvalidJson": "IP 抽出設定が有効な JSON ではありません:{message}", "ipLoggingInvalidShape": "IP 抽出設定は `headers` 配列を持つオブジェクトである必要があります。", + "billNonSuccessfulRequests": "非成功リクエストを Token 使用量で課金", + "billNonSuccessfulRequestsDesc": "有効にすると、非 2xx ステータス(例: クライアント中断による 499)のリクエストでも、上流が正の token 使用量を返した場合は使用量に応じて課金されます。既定はオフ。", + "billNonSuccessfulRequestsTooltip": "上流プロバイダーがレスポンス失敗時にも token をカウントするケース(ストリーム中断でも token を計上する等)に有用です。fake-200 の偽成功エラー応答は引き続き課金されません。", "verboseProviderError": "詳細なプロバイダーエラー", "verboseProviderErrorDesc": "有効にすると、一部の上流障害タイプで `error.details` により詳細な診断情報(プロバイダー可用性の診断やサニタイズ済み上流断片など)を含める場合があります。", "verboseProviderErrorTooltip": "この設定を有効にすると、プロバイダー名、内部ルーティングの手掛かり、上流障害の理由などの診断情報が露出する可能性があります。クライアントに低レベルのトラブルシュート文脈を見せてもよい場合にのみ有効化してください。", diff --git a/messages/ja/settings/prices.json b/messages/ja/settings/prices.json index 5265e9518..82a9a94d9 100644 --- a/messages/ja/settings/prices.json +++ b/messages/ja/settings/prices.json @@ -172,10 +172,10 @@ "deleteConfirm": "モデル {name} を削除してもよろしいですか?この操作は元に戻せません。", "form": { "modelName": "モデルID", - "modelNamePlaceholder": "例: gpt-5.3-codex", + "modelNamePlaceholder": "例: gpt-5.4", "modelNameRequired": "モデルIDは必須です", "displayName": "表示名 (任意)", - "displayNamePlaceholder": "例: GPT-5.3 Codex", + "displayNamePlaceholder": "例: GPT-5.4 Codex", "type": "タイプ", "provider": "プロバイダー", "providerPlaceholder": "例: openai", diff --git a/messages/ja/settings/providers/form/apiTest.json b/messages/ja/settings/providers/form/apiTest.json index 271173685..c9b6ae7fc 100644 --- a/messages/ja/settings/providers/form/apiTest.json +++ b/messages/ja/settings/providers/form/apiTest.json @@ -13,6 +13,21 @@ "copyResult": "結果をコピー", "copySuccess": "クリップボードにコピーしました", "customConfig": "カスタム", + "customHeaders": { + "label": "カスタムリクエストヘッダー(JSON)", + "desc": "静的なヘッダーがテストリクエストにマージされます。認証ヘッダーは上書きされません。", + "geminiNotSupported": "Gemini テスト経路ではカスタムヘッダーをサポートしていません", + "errors": { + "invalidJson": "カスタムヘッダーは有効な JSON である必要があります", + "notObject": "カスタムヘッダーは JSON オブジェクトである必要があります", + "invalidName": "ヘッダー名に許可されていない文字が含まれています", + "duplicateName": "ヘッダー名が重複しています(大文字小文字を区別しません)", + "protectedName": "認証ヘッダーはカスタムヘッダーで設定できません", + "invalidValue": "ヘッダー値は文字列である必要があります", + "emptyName": "ヘッダー名は空にできません", + "crlf": "ヘッダー名と値に改行を含めることはできません" + } + }, "customPayloadDesc": "カスタムJSONペイロードを入力してデフォルトのリクエストボディを上書き", "customPayloadPlaceholder": "{\"model\": \"...\", \"messages\": [...]}", "disclaimer": { diff --git a/messages/ja/settings/providers/form/modelSelect.json b/messages/ja/settings/providers/form/modelSelect.json index 9bc1993dc..801a9e6a0 100644 --- a/messages/ja/settings/providers/form/modelSelect.json +++ b/messages/ja/settings/providers/form/modelSelect.json @@ -10,7 +10,7 @@ "loading": "読み込み中...", "manualAdd": "手動でモデルを追加", "manualDesc": "任意のモデル名を追加できます(価格表のモデルに限定されません)", - "manualPlaceholder": "モデル名を入力(例:gpt-5-turbo)", + "manualPlaceholder": "モデル名を入力(例:gpt-5.4)", "notFound": "モデルが見つかりません", "openai": "OpenAI", "providerFilterAll": "すべてのプロバイダー", diff --git a/messages/ja/settings/providers/form/sections.json b/messages/ja/settings/providers/form/sections.json index f9e2ee2ac..f6417e415 100644 --- a/messages/ja/settings/providers/form/sections.json +++ b/messages/ja/settings/providers/form/sections.json @@ -190,6 +190,20 @@ "label": "キャッシュTTL課金スワップ", "desc": "受信データのキャッシュTTLを反転:1hトークンを5分として扱い、その逆も同様。バッジ、コスト、保存メトリクスすべてに影響します。" }, + "customHeaders": { + "label": "カスタムリクエストヘッダー(JSON)", + "desc": "静的なヘッダーが送信リクエストにマージされます。認証ヘッダーや最終リクエストフィルターを上書きしません。", + "errors": { + "invalidJson": "カスタムヘッダーは有効な JSON である必要があります", + "notObject": "カスタムヘッダーは JSON オブジェクトである必要があります", + "invalidName": "ヘッダー名に許可されていない文字が含まれています", + "duplicateName": "ヘッダー名が重複しています(大文字小文字を区別しません)", + "protectedName": "認証ヘッダーはカスタムヘッダーで設定できません", + "invalidValue": "ヘッダー値は文字列である必要があります", + "emptyName": "ヘッダー名は空にできません", + "crlf": "ヘッダー名と値に改行を含めることはできません" + } + }, "codexOverrides": { "title": "Codex パラメータオーバーライド", "desc": "プロバイダーレベルで Codex (Responses API) リクエストパラメータをオーバーライド", diff --git a/messages/ja/settings/providers/form/strings.json b/messages/ja/settings/providers/form/strings.json index 63267fc76..a5b92cd23 100644 --- a/messages/ja/settings/providers/form/strings.json +++ b/messages/ja/settings/providers/form/strings.json @@ -82,7 +82,7 @@ "modelWhitelistLoading": "読み込み中...", "modelWhitelistManualAdd": "モデルを手動追加", "modelWhitelistManualDesc": "価格表に限定せず、任意のモデル名を追加できます", - "modelWhitelistManualPlaceholder": "モデル名を入力 (例: gpt-5-turbo)", + "modelWhitelistManualPlaceholder": "モデル名を入力 (例: gpt-5.4)", "modelWhitelistNotFound": "モデルが見つかりません", "modelWhitelistSearchPlaceholder": "モデル名を検索...", "modelWhitelistSelectAll": "すべて選択 ({count})", diff --git a/messages/ja/usage.json b/messages/ja/usage.json index e72c8dffb..871f163e5 100644 --- a/messages/ja/usage.json +++ b/messages/ja/usage.json @@ -544,7 +544,7 @@ "importantPoints": [ "cch の管理画面で API Key を作成し、環境変数 CCH_API_KEY を設定してください", "cchClaude/openai は ${resolvedOrigin}/v1、cchGemini は ${resolvedOrigin}/v1beta を baseURL に使用します", - "モデル選択は provider_id/model_id 形式(例:openai/gpt-5.2 または cchClaude/claude-sonnet-4-5-20250929)" + "モデル選択は provider_id/model_id 形式(例:openai/gpt-5.4 または cchClaude/claude-sonnet-4-5-20250929)" ] }, @@ -622,7 +622,7 @@ "steps": [ "Droid を再起動", "/model コマンドを入力", - "GPT-5-Codex [cch] または Sonnet 4.5 [cch] を選択", + "GPT-5.4 [cch] または Sonnet 4.5 [cch] を選択", "使用開始!" ] } diff --git a/messages/ru/auditLogs.json b/messages/ru/auditLogs.json index 62b77ca3a..e05922d7c 100644 --- a/messages/ru/auditLogs.json +++ b/messages/ru/auditLogs.json @@ -45,7 +45,8 @@ "provider": { "create": "Создание провайдера", "update": "Обновление провайдера", - "delete": "Удаление провайдера" + "delete": "Удаление провайдера", + "key_reveal": "Просмотр ключа провайдера" }, "provider_group": { "create": "Создание группы провайдеров", @@ -58,7 +59,8 @@ "key": { "create": "Создание ключа", "update": "Обновление ключа", - "delete": "Удаление ключа" + "delete": "Удаление ключа", + "key_reveal": "Просмотр ключа пользователя" }, "notification": { "update": "Обновление уведомления" diff --git a/messages/ru/dashboard.json b/messages/ru/dashboard.json index 8543647fc..a8da0de62 100644 --- a/messages/ru/dashboard.json +++ b/messages/ru/dashboard.json @@ -718,6 +718,7 @@ "noRequests": "Запросы не найдены", "fetchFailed": "Не удалось получить список запросов", "unknownError": "Неизвестная ошибка", + "searchPlaceholder": "Искать запросы...", "prev": "Назад", "next": "Вперед", "orderAsc": "Переключить на прямой порядок (старые первыми)", diff --git a/messages/ru/errors.json b/messages/ru/errors.json index 7e4f2502a..87562a3ef 100644 --- a/messages/ru/errors.json +++ b/messages/ru/errors.json @@ -30,6 +30,9 @@ "PERMISSION_DENIED": "Доступ запрещен", "TOKEN_REQUIRED": "Требуется токен аутентификации", "INVALID_TOKEN": "Недействительный токен аутентификации", + "PROXY_INVALID_API_KEY": "Неверный API-ключ. Указанный ключ не существует или был удалён.", + "PROXY_API_KEY_DISABLED": "Этот API-ключ отключён. Обратитесь к администратору, чтобы повторно включить его, или используйте другой ключ.", + "PROXY_API_KEY_EXPIRED": "Срок действия этого API-ключа истёк. Обратитесь к администратору, чтобы продлить срок, или замените ключ.", "INTERNAL_ERROR": "Внутренняя ошибка сервера, попробуйте позже", "DATABASE_ERROR": "Ошибка базы данных", diff --git a/messages/ru/settings/config.json b/messages/ru/settings/config.json index d6920235a..509d5fe7e 100644 --- a/messages/ru/settings/config.json +++ b/messages/ru/settings/config.json @@ -56,6 +56,8 @@ "enableAutoCleanupDesc": "Автоматически очищать исторические логи по расписанию", "enableHttp2": "Включить HTTP/2", "enableHttp2Desc": "При включении прокси-запросы будут отдавать приоритет HTTP/2. Если HTTP/2 не удастся, произойдёт автоматическое понижение до HTTP/1.1.", + "enableOpenaiResponsesWebsocket": "Включить OpenAI Responses WebSocket", + "enableOpenaiResponsesWebsocketDesc": "Если включено, то когда клиент открывает WebSocket-соединение с /v1/responses и выбирается провайдер типа Codex, CCH попытается установить WebSocket-соединение с вышестоящим сервером. Если сервер не поддерживает WebSocket или рукопожатие не удастся, CCH плавно переключится на обычный HTTP Responses, сохраняя WebSocket клиента открытым; этот fallback не учитывается в circuit breaker. Клиенты без WebSocket и провайдеры, отличные от Codex, работают без изменений.", "enableHighConcurrencyMode": "Включить режим высокой нагрузки", "enableHighConcurrencyModeDesc": "Если включено, CCH отключит часть Redis-снимков для отладки и записи real-time Session-наблюдения, чтобы снизить нагрузку на CPU и IO при высоком RPM. Пересылка, rectifier-логика, обнаружение fake 200, биллинг и лимиты сохраняются, но детализация отладки в Sessions может уменьшиться или запаздывать.", "enableResponseFixer": "Включить исправление ответов", @@ -70,6 +72,17 @@ "enableResponseInputRectifierDesc": "Автоматически нормализует не-массивные input (строковые сокращения или одиночные объекты сообщений с role/type) в запросах /v1/responses в стандартный формат массива перед обработкой (включено по умолчанию).", "allowNonConversationEndpointProviderFallback": "Разрешить cross-provider fallback для не-диалоговых endpoint", "allowNonConversationEndpointProviderFallbackDesc": "Управляет тем, могут ли /v1/messages/count_tokens и /v1/responses/compact при ошибке текущего провайдера использовать существующую decision chain для переключения на совместимый провайдер. По умолчанию включено и сохраняет raw passthrough и non-billing семантику.", + "fakeStreaming": { + "title": "Список «фейковой» потоковой выдачи", + "description": "Для длительных синхронных генераций (изображения / видео), которые могут превысить 120-секундный no-body таймаут Cloudflare, CCH удерживает SSE-соединение безопасными heartbeat-пингами, последовательно опрашивает upstream-провайдеров во внутреннем цикле и отправляет клиенту только финальный непустой результат. Модели в списке включаются автоматически; пустой список полностью отключает функцию.", + "modelLabel": "Модель", + "modelPlaceholder": "точное имя модели из запроса клиента", + "groupsLabel": "Группы провайдеров", + "allGroupsHint": "Группы не выбраны — применяется ко всем группам провайдеров.", + "addModel": "Добавить модель", + "remove": "Удалить", + "emptyState": "Модели не настроены — fake streaming отключён. Нажмите «Добавить модель», чтобы включить его для конкретных моделей." + }, "enableCodexSessionIdCompletion": "Включить дополнение Session ID для Codex", "enableCodexSessionIdCompletionDesc": "Если в Codex-запросе присутствует только session_id (в заголовках) или prompt_cache_key (в теле), автоматически дополняет отсутствующее поле. Если оба отсутствуют, генерирует UUID v7 и стабильно переиспользует его в рамках одного диалога.", "enableClaudeMetadataUserIdInjection": "Включить инъекцию Claude metadata.user_id", @@ -97,6 +110,9 @@ "siteTitleRequired": "Название сайта не может быть пустым", "ipLoggingInvalidJson": "Конфигурация извлечения IP не является валидным JSON: {message}", "ipLoggingInvalidShape": "Конфигурация извлечения IP должна быть объектом с массивом `headers`.", + "billNonSuccessfulRequests": "Тарифицировать неуспешные запросы по токенам", + "billNonSuccessfulRequestsDesc": "Если включено, запросы с не-2xx статусом (например, 499 при отмене клиентом) будут тарифицироваться по фактическому количеству токенов, если апстрим вернул положительные данные об использовании. По умолчанию выключено.", + "billNonSuccessfulRequestsTooltip": "Полезно, когда апстрим считает токены даже при неудачном статусе (например, прерванные стриминговые ответы). Поддельные ответы 200 (fake-200) по-прежнему не тарифицируются.", "verboseProviderError": "Подробные ошибки провайдеров", "verboseProviderErrorDesc": "При включении CCH может добавлять более подробную диагностику некоторых типов сбоев апстрима в `error.details` (например, диагностику доступности провайдеров или очищенные фрагменты ответа апстрима).", "verboseProviderErrorTooltip": "Может раскрывать названия провайдеров, внутренние подсказки маршрутизации, причины сбоев апстрима и другие диагностические детали. Включайте только если клиентам допустимо видеть низкоуровневый контекст отладки.", diff --git a/messages/ru/settings/prices.json b/messages/ru/settings/prices.json index 94e00aa80..cd9c9ef55 100644 --- a/messages/ru/settings/prices.json +++ b/messages/ru/settings/prices.json @@ -172,10 +172,10 @@ "deleteConfirm": "Удалить модель {name}? Это действие необратимо.", "form": { "modelName": "ID модели", - "modelNamePlaceholder": "например: gpt-5.3-codex", + "modelNamePlaceholder": "например: gpt-5.4", "modelNameRequired": "ID модели обязателен", "displayName": "Отображаемое имя (необязательно)", - "displayNamePlaceholder": "например: GPT-5.3 Codex", + "displayNamePlaceholder": "например: GPT-5.4 Codex", "type": "Тип", "provider": "Поставщик", "providerPlaceholder": "например: openai", diff --git a/messages/ru/settings/providers/form/apiTest.json b/messages/ru/settings/providers/form/apiTest.json index f983a7802..7119064df 100644 --- a/messages/ru/settings/providers/form/apiTest.json +++ b/messages/ru/settings/providers/form/apiTest.json @@ -13,6 +13,21 @@ "copyResult": "Копировать результат", "copySuccess": "Скопировано в буфер обмена", "customConfig": "Пользовательский", + "customHeaders": { + "label": "Пользовательские заголовки запроса (JSON)", + "desc": "Статические заголовки добавляются к тестовому запросу. Не перекрывают заголовки аутентификации.", + "geminiNotSupported": "Тестовый канал Gemini не поддерживает пользовательские заголовки", + "errors": { + "invalidJson": "Пользовательские заголовки должны быть корректным JSON", + "notObject": "Пользовательские заголовки должны быть JSON-объектом", + "invalidName": "Имя заголовка содержит недопустимые символы", + "duplicateName": "Имя заголовка повторяется (без учёта регистра)", + "protectedName": "Заголовки аутентификации нельзя задать через пользовательские заголовки", + "invalidValue": "Значения заголовков должны быть строками", + "emptyName": "Имя заголовка не может быть пустым", + "crlf": "Имя и значение заголовка не могут содержать переводы строки" + } + }, "customPayloadDesc": "Введите пользовательский JSON payload для замены тела запроса по умолчанию", "customPayloadPlaceholder": "{\"model\": \"...\", \"messages\": [...]}", "disclaimer": { diff --git a/messages/ru/settings/providers/form/modelSelect.json b/messages/ru/settings/providers/form/modelSelect.json index fd2763cdf..3bb7e58fe 100644 --- a/messages/ru/settings/providers/form/modelSelect.json +++ b/messages/ru/settings/providers/form/modelSelect.json @@ -10,7 +10,7 @@ "loading": "Загрузка...", "manualAdd": "Добавить модель вручную", "manualDesc": "Поддержка добавления любого названия модели (не ограничено прайс-листом)", - "manualPlaceholder": "Введите название модели (например, gpt-5-turbo)", + "manualPlaceholder": "Введите название модели (например, gpt-5.4)", "notFound": "Модели не найдены", "openai": "OpenAI", "providerFilterAll": "Все провайдеры", diff --git a/messages/ru/settings/providers/form/sections.json b/messages/ru/settings/providers/form/sections.json index 3541dd0ce..69ed6f9cf 100644 --- a/messages/ru/settings/providers/form/sections.json +++ b/messages/ru/settings/providers/form/sections.json @@ -190,6 +190,20 @@ "label": "Инверсия тарификации Cache TTL", "desc": "Инвертировать TTL кэша на входе: токены 1h обрабатываются как 5 мин и наоборот. Влияет на бейдж, стоимость и все сохраняемые метрики." }, + "customHeaders": { + "label": "Пользовательские заголовки запроса (JSON)", + "desc": "Статические заголовки добавляются к исходящим запросам. Не перекрывают заголовки аутентификации и финальные request filter.", + "errors": { + "invalidJson": "Пользовательские заголовки должны быть корректным JSON", + "notObject": "Пользовательские заголовки должны быть JSON-объектом", + "invalidName": "Имя заголовка содержит недопустимые символы", + "duplicateName": "Имя заголовка повторяется (без учёта регистра)", + "protectedName": "Заголовки аутентификации нельзя задать через пользовательские заголовки", + "invalidValue": "Значения заголовков должны быть строками", + "emptyName": "Имя заголовка не может быть пустым", + "crlf": "Имя и значение заголовка не могут содержать переводы строки" + } + }, "codexOverrides": { "title": "Переопределение параметров Codex", "desc": "Переопределение параметров запросов Codex (Responses API) на уровне провайдера", diff --git a/messages/ru/settings/providers/form/strings.json b/messages/ru/settings/providers/form/strings.json index b1191c1cb..7f0c9a113 100644 --- a/messages/ru/settings/providers/form/strings.json +++ b/messages/ru/settings/providers/form/strings.json @@ -82,7 +82,7 @@ "modelWhitelistLoading": "Загрузка...", "modelWhitelistManualAdd": "Добавить модель вручную", "modelWhitelistManualDesc": "Поддерживает добавление любого имени модели (не ограничено прайс-листом)", - "modelWhitelistManualPlaceholder": "Введите имя модели (например, gpt-5-turbo)", + "modelWhitelistManualPlaceholder": "Введите имя модели (например, gpt-5.4)", "modelWhitelistNotFound": "Модели не найдены", "modelWhitelistSearchPlaceholder": "Поиск по имени модели...", "modelWhitelistSelectAll": "Выбрать все ({count})", diff --git a/messages/ru/usage.json b/messages/ru/usage.json index babe42cc5..9e6971559 100644 --- a/messages/ru/usage.json +++ b/messages/ru/usage.json @@ -544,7 +544,7 @@ "importantPoints": [ "Создайте API key в панели cch и задайте переменную окружения CCH_API_KEY", "cchClaude/openai используют ${resolvedOrigin}/v1; cchGemini использует ${resolvedOrigin}/v1beta", - "При выборе модели используйте provider_id/model_id (например, openai/gpt-5.2 или cchClaude/claude-sonnet-4-5-20250929)" + "При выборе модели используйте provider_id/model_id (например, openai/gpt-5.4 или cchClaude/claude-sonnet-4-5-20250929)" ] }, @@ -622,7 +622,7 @@ "steps": [ "Перезагрузите Droid", "Введите команду /model", - "Выберите GPT-5-Codex [cch] или Sonnet 4.5 [cch]", + "Выберите GPT-5.4 [cch] или Sonnet 4.5 [cch]", "Начните использовать!" ] } diff --git a/messages/zh-CN/auditLogs.json b/messages/zh-CN/auditLogs.json index 932f000fb..8fad548e4 100644 --- a/messages/zh-CN/auditLogs.json +++ b/messages/zh-CN/auditLogs.json @@ -45,7 +45,8 @@ "provider": { "create": "创建供应商", "update": "更新供应商", - "delete": "删除供应商" + "delete": "删除供应商", + "key_reveal": "查看供应商密钥" }, "provider_group": { "create": "创建供应商分组", @@ -58,7 +59,8 @@ "key": { "create": "创建密钥", "update": "更新密钥", - "delete": "删除密钥" + "delete": "删除密钥", + "key_reveal": "查看用户密钥" }, "notification": { "update": "更新通知" diff --git a/messages/zh-CN/dashboard.json b/messages/zh-CN/dashboard.json index 347b01908..245a15335 100644 --- a/messages/zh-CN/dashboard.json +++ b/messages/zh-CN/dashboard.json @@ -718,6 +718,7 @@ "noRequests": "暂无请求记录", "fetchFailed": "获取请求列表失败", "unknownError": "未知错误", + "searchPlaceholder": "搜索请求...", "prev": "上一页", "next": "下一页", "orderAsc": "切换为正序(最早的在前)", diff --git a/messages/zh-CN/errors.json b/messages/zh-CN/errors.json index 42dc25433..b8d037487 100644 --- a/messages/zh-CN/errors.json +++ b/messages/zh-CN/errors.json @@ -30,6 +30,9 @@ "PERMISSION_DENIED": "权限不足", "TOKEN_REQUIRED": "需要提供认证令牌", "INVALID_TOKEN": "无效的认证令牌", + "PROXY_INVALID_API_KEY": "API 密钥无效。提供的密钥不存在或已被删除。", + "PROXY_API_KEY_DISABLED": "API 密钥已被禁用。请联系管理员重新启用,或使用其他可用密钥。", + "PROXY_API_KEY_EXPIRED": "API 密钥已过期。请联系管理员续期或更换密钥。", "INTERNAL_ERROR": "系统内部错误,请稍后重试", "DATABASE_ERROR": "数据库错误", diff --git a/messages/zh-CN/settings/config.json b/messages/zh-CN/settings/config.json index 8b95c5bdf..a6085902b 100644 --- a/messages/zh-CN/settings/config.json +++ b/messages/zh-CN/settings/config.json @@ -41,6 +41,9 @@ }, "allowGlobalView": "允许查看全站使用量", "allowGlobalViewDesc": "关闭后,普通用户在仪表盘仅能查看自己密钥的使用统计。", + "billNonSuccessfulRequests": "非成功请求按 Token 用量计费", + "billNonSuccessfulRequestsDesc": "开启后,对于响应非 2xx 状态码(例如客户端中断的 499)的请求,只要上游返回了正向 token 用量,仍按用量计费。默认关闭。", + "billNonSuccessfulRequestsTooltip": "适用于上游供应商即使响应失败也按 token 计费的场景(如流式中断时仍计入 token)。fake-200 假成功错误响应仍不会计费。", "verboseProviderError": "详细供应商错误信息", "verboseProviderErrorDesc": "开启后,CCH 会在某些上游失败类型下于 `error.details` 返回更详细的诊断信息(例如供应商可用性诊断或脱敏后的上游片段)。", "verboseProviderErrorTooltip": "该选项可能暴露供应商名称、内部路由线索、上游失败原因等诊断信息。仅建议在客户端可以查看底层排障上下文时开启。", @@ -48,6 +51,8 @@ "passThroughUpstreamErrorMessageDesc": "开启后,CCH 会在能提取出安全脱敏后的上游错误消息时替换 `error.message`;否则 `error.message` 回退到通用代理错误。它不会关闭“详细供应商错误信息”控制的 `error.details` 诊断。", "enableHttp2": "启用 HTTP/2", "enableHttp2Desc": "启用后,代理请求将优先使用 HTTP/2 协议。如果 HTTP/2 失败,将自动降级到 HTTP/1.1。", + "enableOpenaiResponsesWebsocket": "启用 OpenAI Responses WebSocket", + "enableOpenaiResponsesWebsocketDesc": "启用后,当客户端以 WebSocket 连接 /v1/responses 且选中 Codex 类型供应商时,CCH 会尝试与上游建立 WebSocket。若上游不支持或握手失败,将优雅降级到普通 HTTP Responses,客户端 WebSocket 保持打开;降级不计入熔断。非 WebSocket 客户端与非 Codex 供应商行为不变。", "enableHighConcurrencyMode": "启用高并发模式", "enableHighConcurrencyModeDesc": "开启后,将关闭部分 Redis 调试快照与实时会话观测写入,以降低高并发下的 CPU 与 IO 开销。不会影响转发、整流、fake 200 检测、计费与限额,但 Sessions 调试详情会减少或延后。", "interceptAnthropicWarmupRequests": "拦截 Warmup 请求(Anthropic)", @@ -62,6 +67,17 @@ "enableResponseInputRectifierDesc": "自动将 /v1/responses 请求中的非数组 input(字符串简写或带 role/type 的单消息对象)规范化为标准数组格式后再处理(默认开启)。", "allowNonConversationEndpointProviderFallback": "允许非对话端点跨供应商 fallback", "allowNonConversationEndpointProviderFallbackDesc": "控制 /v1/messages/count_tokens 与 /v1/responses/compact 在当前供应商失败时,是否沿用现有决策链切换到兼容供应商重试。默认开启,并继续保持 raw passthrough 与非计费语义。", + "fakeStreaming": { + "title": "Fake 流式输出白名单", + "description": "针对图像 / 视频等长耗时同步生成场景(容易超过 Cloudflare 120 秒无响应体超时),CCH 会先返回 SSE 心跳保活,并在内部串行调用上游供应商,仅在最终拿到非空结果时回写最终响应。命中白名单的模型会启用 fake streaming;列表为空表示完全禁用。", + "modelLabel": "模型", + "modelPlaceholder": "客户端请求中的精确模型名", + "groupsLabel": "供应商分组", + "allGroupsHint": "未选择分组 → 适用于所有供应商分组。", + "addModel": "添加模型", + "remove": "删除", + "emptyState": "尚未配置任何模型,fake streaming 处于关闭状态。点击 \"添加模型\" 即可为指定模型启用。" + }, "enableCodexSessionIdCompletion": "启用 Codex Session ID 补全", "enableCodexSessionIdCompletionDesc": "当 Codex 请求仅提供 session_id(请求头)或 prompt_cache_key(请求体)之一时,自动补全另一个;若两者均缺失,则生成 UUID v7 会话 ID,并在同一对话内稳定复用。", "enableClaudeMetadataUserIdInjection": "启用 Claude metadata.user_id 注入", diff --git a/messages/zh-CN/settings/prices.json b/messages/zh-CN/settings/prices.json index eb8cbac0b..4af7bbf8d 100644 --- a/messages/zh-CN/settings/prices.json +++ b/messages/zh-CN/settings/prices.json @@ -172,10 +172,10 @@ "deleteConfirm": "确定要删除模型 {name} 吗?此操作不可撤销。", "form": { "modelName": "模型 ID", - "modelNamePlaceholder": "例如: gpt-5.3-codex", + "modelNamePlaceholder": "例如: gpt-5.4", "modelNameRequired": "模型 ID 不能为空", "displayName": "展示名称(可选)", - "displayNamePlaceholder": "例如: GPT-5.3 Codex", + "displayNamePlaceholder": "例如: GPT-5.4 Codex", "type": "类型", "provider": "供应商", "providerPlaceholder": "例如: openai", diff --git a/messages/zh-CN/settings/providers/form/apiTest.json b/messages/zh-CN/settings/providers/form/apiTest.json index 03aec7df7..f9a90e48b 100644 --- a/messages/zh-CN/settings/providers/form/apiTest.json +++ b/messages/zh-CN/settings/providers/form/apiTest.json @@ -19,6 +19,21 @@ "requestConfig": "请求配置", "presetConfig": "预置配置", "customConfig": "自定义", + "customHeaders": { + "label": "自定义请求头(JSON)", + "desc": "静态请求头会合并到本次测试请求;不会覆盖鉴权头。", + "geminiNotSupported": "Gemini 测试通道暂不支持自定义请求头", + "errors": { + "invalidJson": "自定义请求头必须是合法 JSON", + "notObject": "自定义请求头必须是 JSON 对象", + "invalidName": "请求头名称包含不允许的字符", + "duplicateName": "请求头名称重复(不区分大小写)", + "protectedName": "鉴权请求头不能通过自定义请求头设置", + "invalidValue": "请求头值必须为字符串", + "emptyName": "请求头名称不能为空", + "crlf": "请求头名称和值不能包含换行符" + } + }, "selectPreset": "选择预置模板", "presetDesc": "预置模板包含真实 CLI 请求特征,用于通过中转服务验证", "customPayloadPlaceholder": "{\"model\": \"...\", \"messages\": [...]}", diff --git a/messages/zh-CN/settings/providers/form/modelSelect.json b/messages/zh-CN/settings/providers/form/modelSelect.json index 60ac497fb..c58aa6ea6 100644 --- a/messages/zh-CN/settings/providers/form/modelSelect.json +++ b/messages/zh-CN/settings/providers/form/modelSelect.json @@ -13,7 +13,7 @@ "exactMatchHint": "这里选中的模型会作为精确匹配规则加入白名单;前缀、后缀、关键词和正则规则仍在下方高级编辑区维护。", "fallbackNotice": "当前无法获取上游模型列表,已自动切换到本地价格表目录。", "manualAdd": "手动添加模型", - "manualPlaceholder": "输入模型名称(如 gpt-5-turbo)", + "manualPlaceholder": "输入模型名称(如 gpt-5.4)", "manualDesc": "支持添加任意模型名称(不限于价格表中的模型)", "claude": "Claude", "openai": "OpenAI", diff --git a/messages/zh-CN/settings/providers/form/sections.json b/messages/zh-CN/settings/providers/form/sections.json index 9db688457..83bd723b1 100644 --- a/messages/zh-CN/settings/providers/form/sections.json +++ b/messages/zh-CN/settings/providers/form/sections.json @@ -146,6 +146,20 @@ "label": "Cache TTL 计费互换", "desc": "反转传入数据的缓存 TTL:1h 令牌视为 5 分钟,反之亦然。影响标记、成本及所有存储指标。" }, + "customHeaders": { + "label": "自定义请求头(JSON)", + "desc": "静态请求头会合并到出站请求;不会覆盖鉴权头或最终 request filter。", + "errors": { + "invalidJson": "自定义请求头必须是合法 JSON", + "notObject": "自定义请求头必须是 JSON 对象", + "invalidName": "请求头名称包含不允许的字符", + "duplicateName": "请求头名称重复(不区分大小写)", + "protectedName": "鉴权请求头不能通过自定义请求头设置", + "invalidValue": "请求头值必须为字符串", + "emptyName": "请求头名称不能为空", + "crlf": "请求头名称和值不能包含换行符" + } + }, "codexOverrides": { "title": "Codex 参数覆写", "desc": "在供应商级别覆写 Codex (Responses API) 请求参数", diff --git a/messages/zh-CN/settings/providers/form/strings.json b/messages/zh-CN/settings/providers/form/strings.json index f1e2e54b2..154f1ae09 100644 --- a/messages/zh-CN/settings/providers/form/strings.json +++ b/messages/zh-CN/settings/providers/form/strings.json @@ -105,7 +105,7 @@ "modelWhitelistSelectAll": "全选 ({count})", "modelWhitelistClear": "清空", "modelWhitelistManualAdd": "手动添加模型", - "modelWhitelistManualPlaceholder": "输入模型名称(如 gpt-5-turbo)", + "modelWhitelistManualPlaceholder": "输入模型名称(如 gpt-5.4)", "modelWhitelistManualDesc": "支持添加任意模型名称(不限于价格表中的模型)", "modelWhitelistAllowAll": "允许所有 {type} 模型", "modelWhitelistAllowAllClause": "允许所有 Claude 模型", diff --git a/messages/zh-CN/usage.json b/messages/zh-CN/usage.json index 4bd2c86ff..6bdb3829f 100644 --- a/messages/zh-CN/usage.json +++ b/messages/zh-CN/usage.json @@ -540,7 +540,7 @@ "importantPoints": [ "请先在 cch 后台创建 API Key,并设置环境变量 CCH_API_KEY", "cchClaude/openai 使用 ${resolvedOrigin}/v1,cchGemini 使用 ${resolvedOrigin}/v1beta", - "模型选择时使用 provider_id/model_id 格式(例如 openai/gpt-5.2 或 cchClaude/claude-sonnet-4-5-20250929)" + "模型选择时使用 provider_id/model_id 格式(例如 openai/gpt-5.4 或 cchClaude/claude-sonnet-4-5-20250929)" ] }, @@ -618,7 +618,7 @@ "steps": [ "重启 Droid", "输入 /model 命令", - "选择 GPT-5-Codex [cch] 或 Sonnet 4.5 [cch]", + "选择 GPT-5.4 [cch] 或 Sonnet 4.5 [cch]", "开始使用!" ] } diff --git a/messages/zh-TW/auditLogs.json b/messages/zh-TW/auditLogs.json index fe2201d7a..c63323140 100644 --- a/messages/zh-TW/auditLogs.json +++ b/messages/zh-TW/auditLogs.json @@ -45,7 +45,8 @@ "provider": { "create": "建立供應商", "update": "更新供應商", - "delete": "刪除供應商" + "delete": "刪除供應商", + "key_reveal": "查看供應商金鑰" }, "provider_group": { "create": "建立供應商分組", @@ -58,7 +59,8 @@ "key": { "create": "建立金鑰", "update": "更新金鑰", - "delete": "刪除金鑰" + "delete": "刪除金鑰", + "key_reveal": "查看用戶金鑰" }, "notification": { "update": "更新通知" diff --git a/messages/zh-TW/dashboard.json b/messages/zh-TW/dashboard.json index b2a41eebb..f1a3b63fb 100644 --- a/messages/zh-TW/dashboard.json +++ b/messages/zh-TW/dashboard.json @@ -718,6 +718,7 @@ "noRequests": "暫無請求記錄", "fetchFailed": "取得請求列表失敗", "unknownError": "未知錯誤", + "searchPlaceholder": "搜尋請求...", "prev": "上一頁", "next": "下一頁", "orderAsc": "切換為正序(最早的在前)", diff --git a/messages/zh-TW/errors.json b/messages/zh-TW/errors.json index 538e56655..07d104c72 100644 --- a/messages/zh-TW/errors.json +++ b/messages/zh-TW/errors.json @@ -30,6 +30,9 @@ "PERMISSION_DENIED": "權限不足", "TOKEN_REQUIRED": "需要提供認證令牌", "INVALID_TOKEN": "無效的認證令牌", + "PROXY_INVALID_API_KEY": "API 金鑰無效。提供的金鑰不存在或已被刪除。", + "PROXY_API_KEY_DISABLED": "API 金鑰已被停用。請聯絡管理員重新啟用,或使用其他可用金鑰。", + "PROXY_API_KEY_EXPIRED": "API 金鑰已過期。請聯絡管理員續期或更換金鑰。", "INTERNAL_ERROR": "系統內部錯誤,請稍後重試", "DATABASE_ERROR": "資料庫錯誤", diff --git a/messages/zh-TW/settings/config.json b/messages/zh-TW/settings/config.json index cfeb43fd2..4f99a9021 100644 --- a/messages/zh-TW/settings/config.json +++ b/messages/zh-TW/settings/config.json @@ -56,6 +56,8 @@ "enableAutoCleanupDesc": "定時自動清理歷史日誌資料", "enableHttp2": "啟用 HTTP/2", "enableHttp2Desc": "啟用後,代理請求將優先使用 HTTP/2 協定;若 HTTP/2 失敗,將自動降級為 HTTP/1.1。", + "enableOpenaiResponsesWebsocket": "啟用 OpenAI Responses WebSocket", + "enableOpenaiResponsesWebsocketDesc": "啟用後,當客戶端以 WebSocket 連線 /v1/responses 且命中 Codex 類型供應商時,CCH 會嘗試與上游建立 WebSocket 連線。若上游不支援或握手失敗,將優雅降級為一般 HTTP Responses,客戶端 WebSocket 保持開啟;降級不計入熔斷。非 WebSocket 客戶端與非 Codex 供應商行為不變。", "enableHighConcurrencyMode": "啟用高並發模式", "enableHighConcurrencyModeDesc": "開啟後,將關閉部分 Redis 除錯快照與即時 Session 觀測寫入,以降低高並發下的 CPU 與 IO 開銷。轉發、整流、fake 200 偵測、計費與限額不受影響,但 Sessions 除錯詳情會減少或延後。", "enableResponseFixer": "啟用回應整流", @@ -70,6 +72,17 @@ "enableResponseInputRectifierDesc": "自動將 /v1/responses 請求中的非陣列 input(字串簡寫或帶 role/type 的單訊息物件)規範化為標準陣列格式後再處理(預設開啟)。", "allowNonConversationEndpointProviderFallback": "允許非對話端點跨供應商 fallback", "allowNonConversationEndpointProviderFallbackDesc": "控制 /v1/messages/count_tokens 與 /v1/responses/compact 在目前供應商失敗時,是否沿用既有決策鏈切換到相容供應商重試。預設開啟,並持續保持 raw passthrough 與非計費語義。", + "fakeStreaming": { + "title": "Fake 串流輸出白名單", + "description": "針對圖像 / 影片等長耗時同步生成場景(容易超過 Cloudflare 120 秒無回應主體逾時),CCH 會先回傳 SSE 心跳保持連線,在內部串行呼叫上游供應商,僅在最終取得非空結果時才回寫最終回應。命中白名單的模型會啟用 fake streaming;列表為空表示完全停用。", + "modelLabel": "模型", + "modelPlaceholder": "客戶端請求中的精確模型名稱", + "groupsLabel": "供應商分組", + "allGroupsHint": "未選擇分組 → 套用於所有供應商分組。", + "addModel": "新增模型", + "remove": "刪除", + "emptyState": "尚未設定任何模型,fake streaming 已關閉。點擊「新增模型」即可為指定模型啟用。" + }, "enableCodexSessionIdCompletion": "啟用 Codex Session ID 補全", "enableCodexSessionIdCompletionDesc": "當 Codex 請求僅提供 session_id(請求頭)或 prompt_cache_key(請求體)之一時,自動補全另一個;若兩者皆缺失,則產生 UUID v7 會話 ID,並在同一對話內穩定複用。", "enableClaudeMetadataUserIdInjection": "啟用 Claude metadata.user_id 注入", @@ -97,6 +110,9 @@ "siteTitleRequired": "站台標題不能為空", "ipLoggingInvalidJson": "IP 擷取設定不是合法的 JSON:{message}", "ipLoggingInvalidShape": "IP 擷取設定必須是包含 `headers` 陣列的物件。", + "billNonSuccessfulRequests": "非成功請求按 Token 用量計費", + "billNonSuccessfulRequestsDesc": "開啟後,對於回應非 2xx 狀態碼(例如客戶端中斷的 499)的請求,只要上游回報了正向 token 用量,仍會按用量計費。預設關閉。", + "billNonSuccessfulRequestsTooltip": "適用於上游供應商即使回應失敗也按 token 計費的情境(例如串流中斷時仍記入 token)。fake-200 偽成功錯誤響應仍不會計費。", "verboseProviderError": "詳細供應商錯誤資訊", "verboseProviderErrorDesc": "開啟後,CCH 會在某些上游失敗類型下於 `error.details` 返回較詳細的診斷資訊(例如供應商可用性診斷或脫敏後的上游片段)。", "verboseProviderErrorTooltip": "此選項可能暴露供應商名稱、內部路由線索、上游失敗原因等診斷資訊。僅建議在客戶端可以查看底層排障上下文時開啟。", diff --git a/messages/zh-TW/settings/prices.json b/messages/zh-TW/settings/prices.json index d37f5d5be..68022a36d 100644 --- a/messages/zh-TW/settings/prices.json +++ b/messages/zh-TW/settings/prices.json @@ -172,10 +172,10 @@ "deleteConfirm": "確定要刪除模型 {name} 嗎?此操作無法復原。", "form": { "modelName": "模型識別碼", - "modelNamePlaceholder": "例如:gpt-5.3-codex", + "modelNamePlaceholder": "例如:gpt-5.4", "modelNameRequired": "模型 ID 為必填", "displayName": "顯示名稱(選填)", - "displayNamePlaceholder": "例如:GPT-5.3 Codex", + "displayNamePlaceholder": "例如:GPT-5.4 Codex", "type": "類型", "provider": "供應商", "providerPlaceholder": "例如:openai", diff --git a/messages/zh-TW/settings/providers/form/apiTest.json b/messages/zh-TW/settings/providers/form/apiTest.json index 191a03051..5cac1e496 100644 --- a/messages/zh-TW/settings/providers/form/apiTest.json +++ b/messages/zh-TW/settings/providers/form/apiTest.json @@ -13,6 +13,21 @@ "copyResult": "複製結果", "copySuccess": "已複製到剪貼簿", "customConfig": "自訂", + "customHeaders": { + "label": "自訂請求標頭(JSON)", + "desc": "靜態請求標頭會合併到本次測試請求;不會覆蓋鑑權標頭。", + "geminiNotSupported": "Gemini 測試通道暫不支援自訂請求標頭", + "errors": { + "invalidJson": "自訂請求標頭必須為合法 JSON", + "notObject": "自訂請求標頭必須為 JSON 物件", + "invalidName": "請求標頭名稱包含不允許的字元", + "duplicateName": "請求標頭名稱重複(不區分大小寫)", + "protectedName": "鑑權請求標頭不能透過自訂請求標頭設定", + "invalidValue": "請求標頭值必須為字串", + "emptyName": "請求標頭名稱不能為空", + "crlf": "請求標頭名稱和值不能包含換行符" + } + }, "customPayloadDesc": "輸入自訂 JSON payload,將覆蓋預設請求主體", "customPayloadPlaceholder": "{\"model\": \"...\", \"messages\": [...]}", "disclaimer": { diff --git a/messages/zh-TW/settings/providers/form/modelSelect.json b/messages/zh-TW/settings/providers/form/modelSelect.json index 0525b31d5..cfb705451 100644 --- a/messages/zh-TW/settings/providers/form/modelSelect.json +++ b/messages/zh-TW/settings/providers/form/modelSelect.json @@ -10,7 +10,7 @@ "loading": "載入中...", "manualAdd": "手動新增模型", "manualDesc": "支援新增任意模型名稱(不限於價格表中的模型)", - "manualPlaceholder": "輸入模型名稱(例如 gpt-5-turbo)", + "manualPlaceholder": "輸入模型名稱(例如 gpt-5.4)", "notFound": "找不到模型", "openai": "OpenAI", "providerFilterAll": "全部供應商", diff --git a/messages/zh-TW/settings/providers/form/sections.json b/messages/zh-TW/settings/providers/form/sections.json index 7974b7c0c..892078e2e 100644 --- a/messages/zh-TW/settings/providers/form/sections.json +++ b/messages/zh-TW/settings/providers/form/sections.json @@ -190,6 +190,20 @@ "label": "Cache TTL 計費互換", "desc": "反轉傳入資料的快取 TTL:1h 令牌視為 5 分鐘,反之亦然。影響標記、成本及所有儲存指標。" }, + "customHeaders": { + "label": "自訂請求標頭(JSON)", + "desc": "靜態請求標頭會合併到出站請求;不會覆蓋鑑權標頭或最終 request filter。", + "errors": { + "invalidJson": "自訂請求標頭必須為合法 JSON", + "notObject": "自訂請求標頭必須為 JSON 物件", + "invalidName": "請求標頭名稱包含不允許的字元", + "duplicateName": "請求標頭名稱重複(不區分大小寫)", + "protectedName": "鑑權請求標頭不能透過自訂請求標頭設定", + "invalidValue": "請求標頭值必須為字串", + "emptyName": "請求標頭名稱不能為空", + "crlf": "請求標頭名稱和值不能包含換行符" + } + }, "codexOverrides": { "title": "Codex 參數覆寫", "desc": "在供應商級別覆寫 Codex (Responses API) 請求參數", diff --git a/messages/zh-TW/settings/providers/form/strings.json b/messages/zh-TW/settings/providers/form/strings.json index 71bd1f47a..3b6f56a8f 100644 --- a/messages/zh-TW/settings/providers/form/strings.json +++ b/messages/zh-TW/settings/providers/form/strings.json @@ -82,7 +82,7 @@ "modelWhitelistLoading": "載入中...", "modelWhitelistManualAdd": "手動新增模型", "modelWhitelistManualDesc": "支援新增任意模型名稱(不限於價格表中的模型)", - "modelWhitelistManualPlaceholder": "輸入模型名稱(例如 gpt-5-turbo)", + "modelWhitelistManualPlaceholder": "輸入模型名稱(例如 gpt-5.4)", "modelWhitelistNotFound": "未找到模型", "modelWhitelistSearchPlaceholder": "搜尋模型名稱...", "modelWhitelistSelectAll": "全選({count})", diff --git a/messages/zh-TW/usage.json b/messages/zh-TW/usage.json index ec5c9afc5..e2ea4ea04 100644 --- a/messages/zh-TW/usage.json +++ b/messages/zh-TW/usage.json @@ -540,7 +540,7 @@ "importantPoints": [ "請先在 cch 後台創建 API Key,並設置環境變量 CCH_API_KEY", "cchClaude/openai 使用 ${resolvedOrigin}/v1,cchGemini 使用 ${resolvedOrigin}/v1beta", - "模型選擇時使用 provider_id/model_id 格式(例如 openai/gpt-5.2 或 cchClaude/claude-sonnet-4-5-20250929)" + "模型選擇時使用 provider_id/model_id 格式(例如 openai/gpt-5.4 或 cchClaude/claude-sonnet-4-5-20250929)" ] }, @@ -618,7 +618,7 @@ "steps": [ "重啟 Droid", "輸入 /model 命令", - "選擇 GPT-5-Codex [cch] 或 Sonnet 4.5 [cch]", + "選擇 GPT-5.4 [cch] 或 Sonnet 4.5 [cch]", "開始使用!" ] } diff --git a/next.config.ts b/next.config.ts index f3e726bfd..4049e7c97 100644 --- a/next.config.ts +++ b/next.config.ts @@ -27,7 +27,20 @@ const nextConfig: NextConfig = { // Next.js 依赖追踪无法正确追踪动态导入和类型导入的传递依赖 // 参考: https://nextjs.org/docs/app/api-reference/config/next-config-js/output outputFileTracingIncludes: { - "/**": ["./node_modules/undici/**/*", "./node_modules/fetch-socks/**/*"], + "/**": [ + "./node_modules/undici/**/*", + "./node_modules/fetch-socks/**/*", + // 自定义 Node 服务器(server.js)只用到 `ws` 与 next 的入口; + // 让 Next 的依赖追踪决定从 next 包里收纳什么文件,避免把 next 整个 + // node_modules 都拖进 standalone 产物(约数十 MB)。仅显式追加: + // - ws:standalone 默认追踪基于 import 静态分析,server.js 是 CJS + // 根入口,未被 Next 编译,必须手工列出。 + // - next/dist:自定义 server 通过 require("next") 进入;保留 dist + // 子树确保 programmatic API 可用。 + "./node_modules/ws/**/*", + "./node_modules/next/dist/**/*", + "./node_modules/next/package.json", + ], }, // 文件上传大小限制(用于数据库备份导入) diff --git a/package.json b/package.json index c033fb255..c0c4f65e3 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,12 @@ { "name": "claude-code-hub", - "version": "0.7.0", + "version": "0.8.0", "private": true, "scripts": { "dev": "tsgo -p tsconfig.json --noEmit && next dev --port 13500", - "build": "tsgo -p tsconfig.json --noEmit && next build && (node scripts/copy-version-to-standalone.cjs || bun scripts/copy-version-to-standalone.cjs)", - "start": "next start", + "dev:server": "NODE_ENV=development node server.js", + "build": "tsgo -p tsconfig.json --noEmit && next build && (node scripts/copy-version-to-standalone.cjs || bun scripts/copy-version-to-standalone.cjs) && (node scripts/copy-custom-server-to-standalone.cjs || bun scripts/copy-custom-server-to-standalone.cjs)", + "start": "node server.js", "lint": "biome check .", "lint:fix": "biome check --write .", "typecheck": "tsgo -p tsconfig.json --noEmit", @@ -26,6 +27,10 @@ "test:coverage:include-session-id-in-errors": "vitest run --config tests/configs/include-session-id-in-errors.config.ts --coverage", "test:coverage:usage-logs-sessionid-search": "vitest run --config tests/configs/usage-logs-sessionid-search.config.ts --coverage", "test:ci": "vitest run --reporter=default --reporter=junit --outputFile.junit=reports/vitest-junit.xml", + "test:v1": "vitest run --config tests/configs/v1.config.ts --coverage --reporter=verbose && bun scripts/check-v1-critical-coverage.ts", + "openapi:generate": "bun scripts/generate-v1-types.ts", + "openapi:check": "bun scripts/generate-v1-types.ts --check", + "openapi:lint": "bun scripts/lint-openapi.ts", "cui": "npx cui-server --host 0.0.0.0 --port 30000 --token a7564bc8882aa9a2d25d8b4ea6ea1e2e", "db:generate": "drizzle-kit generate && node scripts/validate-migrations.js", "db:migrate": "drizzle-kit migrate", @@ -38,20 +43,20 @@ "i18n:audit-messages-no-emoji:fail": "node scripts/audit-messages-no-emoji.js --format=tsv --fail" }, "dependencies": { - "@bull-board/api": "^6", - "@bull-board/express": "^6", + "@bull-board/api": "^7", + "@bull-board/express": "^7", "@dnd-kit/core": "^6.3.1", "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.2", - "@hono/swagger-ui": "^0.5", + "@hono/swagger-ui": "^0.6", "@hono/zod-openapi": "^1", "@hookform/resolvers": "^5", "@iarna/toml": "^2", - "@langfuse/client": "^4", - "@langfuse/otel": "^4", - "@langfuse/tracing": "^4", + "@langfuse/client": "^5", + "@langfuse/otel": "^5", + "@langfuse/tracing": "^5", "@lobehub/icons": "^2", - "@opentelemetry/sdk-node": "^0.212", + "@opentelemetry/sdk-node": "^0.217", "@radix-ui/react-alert-dialog": "^1", "@radix-ui/react-avatar": "^1", "@radix-ui/react-checkbox": "^1", @@ -68,12 +73,12 @@ "@radix-ui/react-switch": "^1", "@radix-ui/react-tabs": "^1", "@radix-ui/react-tooltip": "^1", - "@scalar/hono-api-reference": "^0.9", + "@scalar/hono-api-reference": "^0.10", "@tanstack/react-query": "^5", "@tanstack/react-virtual": "^3", "@tanstack/virtual-core": "^3", - "agentation": "^1", - "antd": "^6", + "agentation": "^3", + "antd": "~6.3", "bull": "^4", "class-variance-authority": "^0.7", "clsx": "^2", @@ -82,7 +87,7 @@ "date-fns-tz": "^3", "decimal.js-light": "^2", "dotenv": "^17", - "drizzle-orm": "^0.44", + "drizzle-orm": "^0.45", "fetch-socks": "^1", "framer-motion": "^12", "hono": "^4", @@ -98,7 +103,7 @@ "pino-pretty": "^13", "postgres": "^3", "react": "^19", - "react-day-picker": "^9", + "react-day-picker": "^10", "react-dom": "^19", "react-hook-form": "^7", "react-syntax-highlighter": "^16", @@ -111,25 +116,28 @@ "tw-animate-css": "^1", "undici": "^7", "vaul": "^1", + "ws": "^8", "zod": "^4" }, "devDependencies": { "@biomejs/biome": "^2", "@tailwindcss/postcss": "^4.2.0", "@types/ioredis": "^5", - "@types/node": "^24", + "@types/node": "^25", "@types/pg": "^8", "@types/react": "^19", "@types/react-dom": "^19", "@types/react-syntax-highlighter": "^15", - "@typescript/native-preview": "7.0.0-dev.20260425.1", + "@types/ws": "^8", + "@typescript/native-preview": "7.0.0-dev.20260512.1", "@vitest/coverage-v8": "^4", "@vitest/ui": "^4", "bun-types": "^1", "drizzle-kit": "^0.31", "happy-dom": "^20", + "openapi-typescript": "^7.13.0", "tailwindcss": "^4", - "typescript": "^5", + "typescript": "^6", "vitest": "^4" } } diff --git a/scripts/check-v1-critical-coverage.ts b/scripts/check-v1-critical-coverage.ts new file mode 100644 index 000000000..729902892 --- /dev/null +++ b/scripts/check-v1-critical-coverage.ts @@ -0,0 +1,80 @@ +import { existsSync, readFileSync } from "node:fs"; +import path from "node:path"; + +type CoverageMetric = Record; + +type CoverageFile = { + statementMap: Record; + fnMap: Record; + branchMap: Record; + s: CoverageMetric; + f: CoverageMetric; + b: Record; +}; + +const coveragePath = path.join(process.cwd(), "coverage", "coverage-final.json"); +const threshold = 80; + +const criticalFiles = [ + "src/app/api/v1/resources/system/handlers.ts", + "src/app/api/v1/resources/public/handlers.ts", + "src/app/api/v1/resources/notifications/handlers.ts", + "src/app/api/v1/resources/webhook-targets/handlers.ts", + "src/lib/api/v1/_shared/action-bridge.ts", +]; + +function percent(covered: number, total: number): number { + return total === 0 ? 100 : (covered / total) * 100; +} + +function summarize(file: CoverageFile) { + const statementsTotal = Object.keys(file.statementMap).length; + const statementsCovered = Object.values(file.s).filter((count) => count > 0).length; + const functionsTotal = Object.keys(file.fnMap).length; + const functionsCovered = Object.values(file.f).filter((count) => count > 0).length; + const branchesTotal = Object.values(file.branchMap).reduce( + (sum, branch) => sum + (branch.locations?.length ?? 0), + 0 + ); + const branchesCovered = Object.values(file.b).reduce( + (sum, counts) => sum + counts.filter((count) => count > 0).length, + 0 + ); + + return { + statements: percent(statementsCovered, statementsTotal), + functions: percent(functionsCovered, functionsTotal), + branches: percent(branchesCovered, branchesTotal), + }; +} + +if (!existsSync(coveragePath)) { + console.error(`Missing coverage file: ${coveragePath}`); + process.exit(1); +} + +const coverage = JSON.parse(readFileSync(coveragePath, "utf8")) as Record; +const failures: string[] = []; + +for (const expectedPath of criticalFiles) { + const entry = Object.entries(coverage).find(([actualPath]) => actualPath.endsWith(expectedPath)); + if (!entry) { + failures.push(`${expectedPath}: missing from coverage report`); + continue; + } + + const summary = summarize(entry[1]); + for (const metric of ["statements", "functions", "branches"] as const) { + if (summary[metric] < threshold) { + failures.push(`${expectedPath}: ${metric} ${summary[metric].toFixed(1)}% < ${threshold}%`); + } + } +} + +if (failures.length > 0) { + console.error("Critical v1 coverage check failed:"); + for (const failure of failures) console.error(`- ${failure}`); + process.exit(1); +} + +console.log("Critical v1 coverage check passed"); diff --git a/scripts/copy-custom-server-to-standalone.cjs b/scripts/copy-custom-server-to-standalone.cjs new file mode 100644 index 000000000..4640b75e6 --- /dev/null +++ b/scripts/copy-custom-server-to-standalone.cjs @@ -0,0 +1,49 @@ +/** + * Copy the custom Node server (with WebSocket upgrade support) into the + * Next.js standalone output, overwriting the generated server.js so Docker + * runtime (`CMD ["node", "server.js"]`) boots the custom one instead. + * + * Also copies the `server-lib/` helper directory it depends on (e.g. the + * standalone-config injector). Next's traced files only follow imports from + * the compiled app, not from our custom server entry, so anything server.js + * `require()`s from outside node_modules must be copied explicitly. + * + * The generated standalone server.js is the default Next.js minimal server; + * ours wraps Next.js programmatically plus adds WebSocket upgrade handling + * on /v1/responses. See server.js at the repo root for the full rationale. + */ + +const fs = require("node:fs"); +const path = require("node:path"); + +const cwd = process.cwd(); +const dstDir = path.resolve(cwd, ".next", "standalone"); + +if (!fs.existsSync(dstDir)) { + console.warn( + `[copy-custom-server] Standalone output dir missing at ${dstDir}; skipping (did next build run?)` + ); + process.exit(0); +} + +const serverSrc = path.resolve(cwd, "server.js"); +if (!fs.existsSync(serverSrc)) { + console.error(`[copy-custom-server] Custom server not found at ${serverSrc}`); + process.exit(1); +} +const serverDst = path.join(dstDir, "server.js"); +fs.copyFileSync(serverSrc, serverDst); +console.log(`[copy-custom-server] Copied ${serverSrc} -> ${serverDst}`); + +// server.js requires from server-lib/, so missing it would yield a standalone +// artifact that crashes on startup. Fail the build instead of warning. +const libSrc = path.resolve(cwd, "server-lib"); +if (!fs.existsSync(libSrc)) { + console.error( + `[copy-custom-server] server-lib/ not found at ${libSrc}; refusing to produce a broken standalone artifact` + ); + process.exit(1); +} +const libDst = path.join(dstDir, "server-lib"); +fs.cpSync(libSrc, libDst, { recursive: true }); +console.log(`[copy-custom-server] Copied ${libSrc} -> ${libDst}`); diff --git a/scripts/deploy.ps1 b/scripts/deploy.ps1 index edc8c1c1c..930d98ec0 100644 --- a/scripts/deploy.ps1 +++ b/scripts/deploy.ps1 @@ -43,6 +43,8 @@ $script:DEPLOY_DIR = "C:\ProgramData\claude-code-hub" $script:IMAGE_TAG = "latest" $script:BRANCH_NAME = "main" $script:APP_PORT = "23000" +$script:AUTH_SESSION_TTL_SECONDS = "604800" +$script:SESSION_TTL = "300" $script:ENABLE_CADDY = $false $script:DOMAIN_ARG = "" $script:UPDATE_MODE = $false @@ -212,32 +214,30 @@ function Select-Branch { Write-Host " 1) main (Stable release - recommended for production)" -ForegroundColor Green Write-Host " 2) dev (Latest features - for testing)" -ForegroundColor Yellow Write-Host "" - + while ($true) { - $choice = Read-Host "Enter your choice [1]" + $choice = Read-Host "Type 1 or 2 (or 'main'/'dev') and press Enter [default: 1]" if ([string]::IsNullOrWhiteSpace($choice)) { $choice = "1" } - - switch ($choice) { - "1" { - $script:IMAGE_TAG = "latest" - $script:BRANCH_NAME = "main" - Write-ColorOutput "Selected branch: main (image tag: latest)" -Type Success - break - } - "2" { - $script:IMAGE_TAG = "dev" - $script:BRANCH_NAME = "dev" - Write-ColorOutput "Selected branch: dev (image tag: dev)" -Type Success - break - } - default { - Write-ColorOutput "Invalid choice. Please enter 1 or 2." -Type Error - continue - } + $normalized = $choice.Trim().ToLower() + + # Use if/return rather than switch+break — in PowerShell `break` inside a + # switch exits the switch, not the surrounding while, so the loop control + # has to live at this scope. + if ($normalized -in "1", "main") { + $script:IMAGE_TAG = "latest" + $script:BRANCH_NAME = "main" + Write-ColorOutput "Selected branch: main (image tag: latest)" -Type Success + return + } + if ($normalized -in "2", "dev") { + $script:IMAGE_TAG = "dev" + $script:BRANCH_NAME = "dev" + Write-ColorOutput "Selected branch: dev (image tag: dev)" -Type Success + return } - break + Write-ColorOutput "Invalid choice. Type 1, 2, 'main', or 'dev' and press Enter." -Type Error } } @@ -335,6 +335,19 @@ function Import-ExistingEnv { $script:APP_PORT = ($portLine.Line -split '=', 2)[1] } } + + # 读取会话 TTL,升级时保留用户已有配置 + $authSessionTtlLine = Select-String -Path $envFile -Pattern '^AUTH_SESSION_TTL_SECONDS=' | Select-Object -First 1 + if ($authSessionTtlLine) { + $script:AUTH_SESSION_TTL_SECONDS = ($authSessionTtlLine.Line -split '=', 2)[1] + Write-ColorOutput "Preserved existing auth session TTL" -Type Info + } + + $sessionTtlLine = Select-String -Path $envFile -Pattern '^SESSION_TTL=' | Select-Object -First 1 + if ($sessionTtlLine) { + $script:SESSION_TTL = ($sessionTtlLine.Line -split '=', 2)[1] + Write-ColorOutput "Preserved existing proxy session TTL" -Type Info + } } function New-DeploymentDirectory { @@ -429,6 +442,7 @@ services: REDIS_URL: redis://claude-code-hub-redis-${SUFFIX}:6379 AUTO_MIGRATE: `${AUTO_MIGRATE:-true} ENABLE_RATE_LIMIT: `${ENABLE_RATE_LIMIT:-true} + AUTH_SESSION_TTL_SECONDS: `${AUTH_SESSION_TTL_SECONDS:-604800} SESSION_TTL: `${SESSION_TTL:-300} TZ: Asia/Shanghai $appPortsSection @@ -575,7 +589,8 @@ AUTO_MIGRATE=true ENABLE_RATE_LIMIT=true # Session Configuration -SESSION_TTL=300 +AUTH_SESSION_TTL_SECONDS=$($script:AUTH_SESSION_TTL_SECONDS) +SESSION_TTL=$($script:SESSION_TTL) STORE_SESSION_MESSAGES=false STORE_SESSION_RESPONSE_BODY=true @@ -600,7 +615,7 @@ LOG_LEVEL=info $managedKeys = @( "ADMIN_TOKEN", "DB_USER", "DB_PASSWORD", "DB_NAME", "APP_PORT", "APP_URL", "AUTO_MIGRATE", "ENABLE_RATE_LIMIT", - "SESSION_TTL", "STORE_SESSION_MESSAGES", "STORE_SESSION_RESPONSE_BODY", + "AUTH_SESSION_TTL_SECONDS", "SESSION_TTL", "STORE_SESSION_MESSAGES", "STORE_SESSION_RESPONSE_BODY", "ENABLE_SECURE_COOKIES", "ENABLE_CIRCUIT_BREAKER_ON_NETWORK_ERRORS", "ENABLE_ENDPOINT_CIRCUIT_BREAKER", "NODE_ENV", "TZ", "LOG_LEVEL" ) diff --git a/scripts/deploy.sh b/scripts/deploy.sh index e94183708..2d8a02d9a 100755 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -38,6 +38,8 @@ OS_TYPE="" IMAGE_TAG="latest" BRANCH_NAME="main" APP_PORT="23000" +AUTH_SESSION_TTL_SECONDS="604800" +SESSION_TTL="300" UPDATE_MODE=false FORCE_NEW=false @@ -244,27 +246,30 @@ select_branch() { echo -e " ${GREEN}1)${NC} main (Stable release - recommended for production)" echo -e " ${YELLOW}2)${NC} dev (Latest features - for testing)" echo "" - - local choice + + local choice normalized while true; do - read -p "Enter your choice [1]: " choice - choice=${choice:-1} - - case $choice in - 1) + read -r -p "Type 1 or 2 (or 'main'/'dev') and press Enter [default: 1]: " choice + # Trim whitespace, lowercase, then apply default — so whitespace-only input + # also falls back to "1" (the bare ${choice:-1} would not trigger on " "). + normalized=$(printf '%s' "$choice" | tr '[:upper:]' '[:lower:]' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') + normalized=${normalized:-1} + + case "$normalized" in + 1|main) IMAGE_TAG="latest" BRANCH_NAME="main" log_success "Selected branch: main (image tag: latest)" break ;; - 2) + 2|dev) IMAGE_TAG="dev" BRANCH_NAME="dev" log_success "Selected branch: dev (image tag: dev)" break ;; *) - log_error "Invalid choice. Please enter 1 or 2." + log_error "Invalid choice. Type 1, 2, 'main', or 'dev' and press Enter." ;; esac done @@ -415,6 +420,21 @@ load_existing_env() { APP_PORT="$existing_port" fi fi + + # 读取会话 TTL,升级时保留用户已有配置 + local existing_auth_session_ttl + existing_auth_session_ttl=$(grep '^AUTH_SESSION_TTL_SECONDS=' "$env_file" | head -1 | cut -d'=' -f2-) + if [[ -n "$existing_auth_session_ttl" ]]; then + AUTH_SESSION_TTL_SECONDS="$existing_auth_session_ttl" + log_info "Preserved existing auth session TTL" + fi + + local existing_session_ttl + existing_session_ttl=$(grep '^SESSION_TTL=' "$env_file" | head -1 | cut -d'=' -f2-) + if [[ -n "$existing_session_ttl" ]]; then + SESSION_TTL="$existing_session_ttl" + log_info "Preserved existing proxy session TTL" + fi } create_deployment_dir() { @@ -514,6 +534,7 @@ services: REDIS_URL: redis://claude-code-hub-redis-${SUFFIX}:6379 AUTO_MIGRATE: \${AUTO_MIGRATE:-true} ENABLE_RATE_LIMIT: \${ENABLE_RATE_LIMIT:-true} + AUTH_SESSION_TTL_SECONDS: \${AUTH_SESSION_TTL_SECONDS:-604800} SESSION_TTL: \${SESSION_TTL:-300} TZ: Asia/Shanghai EOF @@ -654,7 +675,8 @@ AUTO_MIGRATE=true ENABLE_RATE_LIMIT=true # Session Configuration -SESSION_TTL=300 +AUTH_SESSION_TTL_SECONDS=${AUTH_SESSION_TTL_SECONDS} +SESSION_TTL=${SESSION_TTL} STORE_SESSION_MESSAGES=false STORE_SESSION_RESPONSE_BODY=true @@ -673,7 +695,7 @@ EOF # Restore user custom variables from backup (variables not managed by this script) if [[ -n "$backup_file" ]] && [[ -f "$backup_file" ]]; then - local managed_keys="ADMIN_TOKEN|DB_USER|DB_PASSWORD|DB_NAME|APP_PORT|APP_URL|AUTO_MIGRATE|ENABLE_RATE_LIMIT|SESSION_TTL|STORE_SESSION_MESSAGES|STORE_SESSION_RESPONSE_BODY|ENABLE_SECURE_COOKIES|ENABLE_CIRCUIT_BREAKER_ON_NETWORK_ERRORS|ENABLE_ENDPOINT_CIRCUIT_BREAKER|NODE_ENV|TZ|LOG_LEVEL" + local managed_keys="ADMIN_TOKEN|DB_USER|DB_PASSWORD|DB_NAME|APP_PORT|APP_URL|AUTO_MIGRATE|ENABLE_RATE_LIMIT|AUTH_SESSION_TTL_SECONDS|SESSION_TTL|STORE_SESSION_MESSAGES|STORE_SESSION_RESPONSE_BODY|ENABLE_SECURE_COOKIES|ENABLE_CIRCUIT_BREAKER_ON_NETWORK_ERRORS|ENABLE_ENDPOINT_CIRCUIT_BREAKER|NODE_ENV|TZ|LOG_LEVEL" local custom_vars custom_vars=$(grep -v '^\s*#' "$backup_file" | grep -v '^\s*$' | grep -vE "^($managed_keys)=" || true) if [[ -n "$custom_vars" ]]; then diff --git a/scripts/generate-v1-types.ts b/scripts/generate-v1-types.ts new file mode 100644 index 000000000..359658610 --- /dev/null +++ b/scripts/generate-v1-types.ts @@ -0,0 +1,29 @@ +import { existsSync, readFileSync, writeFileSync } from "node:fs"; +import path from "node:path"; +import openapiTS, { astToString } from "openapi-typescript"; + +const outputPath = path.join(process.cwd(), "src/lib/api-client/v1/openapi-types.gen.ts"); +const checkOnly = process.argv.includes("--check"); + +const { mock } = await import("bun:test"); +mock.module("server-only", () => ({})); + +const [{ app }, { buildOpenApiDocument }] = await Promise.all([ + import("@/app/api/v1/_root/app"), + import("@/app/api/v1/_root/document"), +]); + +const document = buildOpenApiDocument(app); +const ast = await openapiTS(document); +const generated = `// AUTO-GENERATED - DO NOT EDIT\n${astToString(ast)}\n`; + +if (checkOnly) { + const current = existsSync(outputPath) ? readFileSync(outputPath, "utf8") : ""; + if (current !== generated) { + console.error("Generated OpenAPI types are out of date. Run `bun run openapi:generate`."); + process.exit(1); + } + process.exit(0); +} + +writeFileSync(outputPath, generated); diff --git a/scripts/lint-openapi.ts b/scripts/lint-openapi.ts new file mode 100644 index 000000000..8a0ee05be --- /dev/null +++ b/scripts/lint-openapi.ts @@ -0,0 +1,62 @@ +const { mock } = await import("bun:test"); +mock.module("server-only", () => ({})); + +const [{ app }, { buildOpenApiDocument }] = await Promise.all([ + import("@/app/api/v1/_root/app"), + import("@/app/api/v1/_root/document"), +]); + +const document = buildOpenApiDocument(app) as { + paths?: Record>; +}; + +const methods = new Set(["get", "post", "put", "patch", "delete", "options"]); +const failures: string[] = []; + +for (const [path, pathItem] of Object.entries(document.paths ?? {})) { + for (const [method, operation] of Object.entries(pathItem)) { + if (!methods.has(method)) continue; + const op = operation as { + summary?: string; + description?: string; + tags?: string[]; + responses?: Record; + security?: Array>; + "x-required-access"?: string; + }; + const label = `${method.toUpperCase()} ${path}`; + if (!path.startsWith("/api/v1/")) failures.push(`${label}: path must start with /api/v1/`); + if (!op.summary) failures.push(`${label}: missing summary`); + if (!op.description) failures.push(`${label}: missing description`); + if (!op.tags?.length) failures.push(`${label}: missing tags`); + if (!["public", "read", "admin"].includes(op["x-required-access"] ?? "")) { + failures.push(`${label}: missing x-required-access`); + } + if (!op.responses || Object.keys(op.responses).length === 0) { + failures.push(`${label}: missing responses`); + } + const normalizedPath = path.replace(/^\/api\/v1/, ""); + if ( + normalizedPath !== "/health" && + !op.security?.length && + !normalizedPath.startsWith("/public/status") + ) { + failures.push(`${label}: missing security declaration`); + } + } +} + +const serialized = JSON.stringify(document); +for (const hiddenProviderType of ["claude-auth", "gemini-cli"]) { + if (serialized.includes(hiddenProviderType)) { + failures.push(`OpenAPI document exposes hidden provider type ${hiddenProviderType}`); + } +} + +if (failures.length > 0) { + console.error("OpenAPI lint failed:"); + for (const failure of failures) console.error(`- ${failure}`); + process.exit(1); +} + +console.log("OpenAPI lint passed"); diff --git a/server-lib/standalone-config.js b/server-lib/standalone-config.js new file mode 100644 index 000000000..11fba578f --- /dev/null +++ b/server-lib/standalone-config.js @@ -0,0 +1,62 @@ +// Helper used by the custom Node server (server.js) to surface the Next.js +// configuration that `next build` baked into `.next/required-server-files.json`. +// +// Why this exists: in standalone mode there is no `next.config.{js,ts,mjs}` +// next to the entrypoint, so Next's `loadConfig()` would fall back to +// defaults and silently drop overrides such as +// `experimental.proxyClientMaxBodySize` (clamping proxied request bodies to +// the 10MB DEFAULT_BODY_CLONE_SIZE_LIMIT in body-streams.js). Next's own +// generated standalone server.js sets the same env var +// (`__NEXT_PRIVATE_STANDALONE_CONFIG`) that `loadConfig()` reads before +// falling back; we mirror that here for our custom server. +// +// Extracted to its own module so it can be unit-tested in isolation — +// requiring server.js would also start the HTTP listener. + +"use strict"; + +const path = require("node:path"); + +function applyStandaloneNextConfig({ rootDir, env, log } = {}) { + if (!env || typeof env !== "object") { + throw new TypeError("applyStandaloneNextConfig requires an env object"); + } + if (typeof rootDir !== "string" || rootDir.length === 0) { + throw new TypeError("applyStandaloneNextConfig requires a rootDir string"); + } + + if (env.__NEXT_PRIVATE_STANDALONE_CONFIG) { + return { applied: false, reason: "preset" }; + } + + const manifestPath = path.join(rootDir, ".next", "required-server-files.json"); + + let manifest; + try { + // Use a fresh require each call so unit tests can swap fixtures via + // tmp directories without fighting Node's module cache. + delete require.cache[require.resolve(manifestPath)]; + // eslint-disable-next-line global-require + manifest = require(manifestPath); + } catch (err) { + if (typeof log === "function") { + log("warn", "standalone_config_load_failed", { + error: String(err && err.message ? err.message : err), + manifestPath, + }); + } + return { applied: false, reason: "load_error", error: err }; + } + + if (!manifest || typeof manifest !== "object" || !manifest.config) { + if (typeof log === "function") { + log("warn", "standalone_config_missing_field", { manifestPath }); + } + return { applied: false, reason: "missing_config" }; + } + + env.__NEXT_PRIVATE_STANDALONE_CONFIG = JSON.stringify(manifest.config); + return { applied: true, manifestPath }; +} + +module.exports = { applyStandaloneNextConfig }; diff --git a/server.js b/server.js new file mode 100644 index 000000000..f7ede45ed --- /dev/null +++ b/server.js @@ -0,0 +1,876 @@ +// Custom Node.js server for claude-code-hub. +// +// Purpose: add WebSocket upgrade support on /v1/responses so clients that speak +// the OpenAI Responses WebSocket protocol (text JSON frames with +// type=response.create) can proxy through CCH. All other HTTP traffic is +// delegated to the Next.js App Router handler unchanged. +// +// Architecture: this server is a thin tunnel. For each client WebSocket frame, +// we build an equivalent HTTP POST against the same app's /v1/responses +// endpoint (with x-cch-client-transport and x-cch-responses-ws-session headers) +// so that auth, provider selection, guard pipeline, forwarder, circuit +// breakers, observability, and all existing TypeScript business logic run +// exactly once. Upstream WebSocket attempts and per-client upstream reuse live +// inside that TypeScript pipeline (forwarder), not here. +// +// Compatibility: +// - Non-WebSocket clients: unaffected. HTTP still flows through Next.js. +// - Non-Codex providers: the forwarder never attempts upstream WS; client WS +// is still accepted and tunneled through HTTP SSE. +// - Setting disabled: client WS handshake still succeeds (so clients don't +// break), but every frame is tunneled over HTTP with no upstream-WS attempt. + +"use strict"; + +const http = require("node:http"); +const { randomUUID } = require("node:crypto"); +const { parse } = require("node:url"); + +function isNextDevMode(nodeEnv) { + return nodeEnv !== "production"; +} + +// 保留既有本地语义:只有显式 production 才服务已构建产物;Docker/K8s +// 镜像会显式设置 NODE_ENV=production 和 PORT=3000。 +const dev = isNextDevMode(process.env.NODE_ENV); +const hostname = process.env.HOSTNAME || "0.0.0.0"; +const port = parseInt(process.env.PORT || (dev ? "13500" : "3000"), 10); + +// Loopback target for the in-process WS->HTTP tunnel. When the public bind +// hostname is a wildcard (0.0.0.0 / ::), tunnel via 127.0.0.1; otherwise use +// the configured hostname so we still hit the local listener even when bound +// to a specific interface. +const INTERNAL_TUNNEL_HOST = + hostname === "0.0.0.0" || hostname === "::" || hostname === "*" ? "127.0.0.1" : hostname; + +const WS_PATH = "/v1/responses"; +const CLIENT_TRANSPORT_HEADER = "x-cch-client-transport"; +const WS_FORWARD_FLAG_HEADER = "x-cch-responses-ws-forward"; +const WS_SESSION_HEADER = "x-cch-responses-ws-session"; +const INTERNAL_SECRET_HEADER = "x-cch-internal-secret"; +const INTERNAL_SECRET_ENV = "CCH_RESPONSES_WS_INTERNAL_SECRET"; + +// Header names a client must NEVER be allowed to set on inbound traffic. +// Anything starting with "x-cch-" is reserved for internal markers; the WS +// edge strips the entire prefix from inbound requests so an attacker cannot +// pre-set the WS-tunnel marker headers when they connect. +const RESERVED_INTERNAL_HEADER_PREFIX = "x-cch-"; + +// Per-WebSocket-connection guardrails: cap the queue depth and total queued +// bytes to make a misbehaving / malicious client a bounded-memory event. +const MAX_PENDING_FRAMES = 64; +const MAX_PENDING_BYTES = 64 * 1024 * 1024; // 64 MiB across all queued frames + +// Maximum payload size for any single inbound WS frame. The default `ws` +// limit is 100 MiB. We pick 32 MiB to accommodate Codex requests that ship +// large conversation history alongside the prompt — a tighter cap caused the +// `ws` library to socket.destroy() (TCP RST) without sending a close frame, +// surfacing on the client as "Connection reset without closing handshake". +const WS_MAX_PAYLOAD_BYTES = 32 * 1024 * 1024; // 32 MiB per frame + +const TERMINAL_EVENT_TYPES = new Set([ + "response.completed", + "response.failed", + "response.incomplete", + "error", +]); + +// Query-string keys we explicitly never want to log on the connection event. +// Anything outside this list is masked to "***". +const ALLOWED_LOGGED_QUERY_KEYS = new Set(["model"]); + +function log(level, msg, extra) { + const line = { ts: new Date().toISOString(), level, msg, ...(extra || {}) }; + try { + process.stdout.write(`${JSON.stringify(line)}\n`); + } catch { + // ignore + } +} + +function safeSend(ws, data) { + try { + if (ws.readyState === 1 /* OPEN */) { + ws.send(typeof data === "string" ? data : JSON.stringify(data)); + return true; + } + } catch (err) { + log("warn", "ws_send_failed", { error: String(err) }); + } + return false; +} + +function emitErrorEvent(ws, code, message) { + safeSend(ws, { + type: "error", + error: { code, message }, + }); +} + +function sanitizedRequestPath(rawUrl) { + if (typeof rawUrl !== "string" || rawUrl.length === 0) { + return "/"; + } + try { + const parsed = new URL(rawUrl, "http://localhost"); + const masked = new URLSearchParams(); + parsed.searchParams.forEach((value, key) => { + masked.append(key, ALLOWED_LOGGED_QUERY_KEYS.has(key.toLowerCase()) ? value : "***"); + }); + const qs = masked.toString(); + return qs.length > 0 ? `${parsed.pathname}?${qs}` : parsed.pathname; + } catch { + return "/"; + } +} + +async function handleWebSocketConnection(ws, req) { + const url = new URL(req.url, `http://${req.headers.host || "localhost"}`); + const queryModel = url.searchParams.get("model"); + const responsesWsSessionId = randomUUID(); + let inFlight = false; + const pending = []; + let pendingBytes = 0; + let closed = false; + // Track the in-flight internal HTTP ClientRequest so we can abort it when + // the client WebSocket disconnects mid-stream — otherwise the SSE consumer + // (and provider concurrency / breaker counters) keep running for minutes. + let currentInternalReq = null; + + const abortCurrentInternalReq = () => { + if (!currentInternalReq) return; + const reqToDestroy = currentInternalReq; + currentInternalReq = null; + try { + if (!reqToDestroy.destroyed) { + reqToDestroy.destroy(); + } + } catch { + // ignore + } + }; + + const cleanupUpstreamWsSession = () => { + const cleanup = globalThis.__cchCleanupResponsesWsSession; + if (typeof cleanup !== "function") return; + try { + cleanup(responsesWsSessionId); + } catch (err) { + log("warn", "ws_upstream_session_cleanup_failed", { + error: String(err && err.message ? err.message : err), + }); + } + }; + + const dropPendingFrames = () => { + if (pending.length > 0) { + log("warn", "ws_pending_dropped_on_close", { + droppedFrames: pending.length, + droppedBytes: pendingBytes, + }); + } + pending.length = 0; + pendingBytes = 0; + }; + + const finalize = () => { + if (closed) return; + closed = true; + abortCurrentInternalReq(); + dropPendingFrames(); + cleanupUpstreamWsSession(); + }; + + // Synchronously mark the connection closed so any pipelined frame in + // `pending` is dropped *before* drain() can dispatch another upstream + // request. Without this the gap between ws.close() and the async + // ws.on("close") event is wide enough for `drain()` to pop the next frame + // and run `forwardToInternalHttp` against the upstream — work the client + // can never receive (safeSend would fail) but the provider would still bill. + const requestClose = (code, reason) => { + if (closed) { + abortCurrentInternalReq(); + dropPendingFrames(); + return; + } + if (ws && ws.readyState >= 2) { + // Already closing/closed; just make sure local state matches. + finalize(); + return; + } + closed = true; + abortCurrentInternalReq(); + dropPendingFrames(); + cleanupUpstreamWsSession(); + log("info", "ws_client_close_initiated", { code, reason }); + try { + ws.close(code, reason); + } catch (err) { + log("warn", "ws_client_close_failed", { error: String(err) }); + } + }; + + ws.on("close", finalize); + ws.on("error", (err) => { + log("warn", "ws_client_error", { + error: String(err && err.message ? err.message : err), + }); + finalize(); + }); + + const processFrame = async (raw) => { + if (closed) return; + + if (typeof raw !== "string") { + emitErrorEvent(ws, "invalid_frame_type", "Only text WebSocket frames are supported"); + requestClose(1003, "binary_not_supported"); + return; + } + + let frame; + try { + frame = JSON.parse(raw); + } catch (err) { + emitErrorEvent( + ws, + "invalid_json", + `Invalid JSON frame: ${err && err.message ? err.message : "parse error"}` + ); + return; + } + + if (!frame || typeof frame !== "object") { + emitErrorEvent(ws, "invalid_frame", "Frame must be a JSON object"); + return; + } + + if (frame.type !== "response.create") { + emitErrorEvent( + ws, + "unsupported_event_type", + `Only type=response.create is supported; received: ${frame.type ?? "(missing)"}` + ); + return; + } + + const { type: _type, ...rawBody } = frame; + const body = { ...rawBody }; + // body.model wins over query; only fill from query when body lacks a model + // (LiteLLM/other compat). Drop transport-only fields. + if (queryModel && (body.model === undefined || body.model === null || body.model === "")) { + body.model = queryModel; + } + + log("info", "ws_request_started", { + model: typeof body.model === "string" ? body.model : null, + payloadBytes: Buffer.byteLength(raw, "utf8"), + hasPreviousResponseId: typeof body.previous_response_id === "string", + }); + + await forwardToInternalHttp( + ws, + req, + body, + responsesWsSessionId, + (clientReq) => { + currentInternalReq = clientReq; + }, + requestClose + ); + if (!closed) { + currentInternalReq = null; + } + }; + + const drain = async () => { + if (inFlight) return; + const next = pending.shift(); + if (next === undefined) return; + pendingBytes -= Buffer.byteLength(next, "utf8"); + if (pendingBytes < 0) pendingBytes = 0; + inFlight = true; + try { + await processFrame(next); + } finally { + inFlight = false; + if (pending.length > 0 && !closed) { + void drain().catch((err) => { + log("error", "ws_drain_failed", { + error: String(err && err.message ? err.message : err), + }); + emitErrorEvent(ws, "internal_error", "Failed to process queued request"); + requestClose(1011, "internal_error"); + }); + } + } + }; + + ws.on("message", (data, isBinary) => { + if (closed) return; + if (isBinary) { + emitErrorEvent(ws, "invalid_frame_type", "Only text WebSocket frames are supported"); + requestClose(1003, "binary_not_supported"); + return; + } + const text = data.toString("utf8"); + const size = Buffer.byteLength(text, "utf8"); + if (pending.length >= MAX_PENDING_FRAMES || pendingBytes + size > MAX_PENDING_BYTES) { + log("warn", "ws_pending_overflow", { + pendingFrames: pending.length, + pendingBytes, + attemptedFrameSize: size, + }); + emitErrorEvent(ws, "too_many_requests", "Pending frame limit exceeded"); + requestClose(1008, "too_many_requests"); + return; + } + pending.push(text); + pendingBytes += size; + void drain().catch((err) => { + log("error", "ws_drain_failed", { + error: String(err && err.message ? err.message : err), + }); + emitErrorEvent(ws, "internal_error", "Failed to process request"); + requestClose(1011, "internal_error"); + }); + }); +} + +async function forwardToInternalHttp( + ws, + originalReq, + body, + responsesWsSessionId, + registerInternalReq, + requestClose +) { + // requestClose(code, reason) initiates the WebSocket closing handshake AND + // synchronously marks the client connection closed so the caller's pending + // queue stops dispatching follow-up frames against the upstream. Tests that + // exercise this function in isolation can pass a no-op fallback. + const initiateClose = + typeof requestClose === "function" + ? requestClose + : (code, reason) => { + log("info", "ws_client_close_initiated", { code, reason }); + try { + ws.close(code, reason); + } catch (err) { + log("warn", "ws_client_close_failed", { error: String(err) }); + } + }; + const internalHeaders = {}; + for (const [k, v] of Object.entries(originalReq.headers)) { + const lower = k.toLowerCase(); + // Strip hop-by-hop / WS-specific transport headers. + if ( + lower === "host" || + lower === "connection" || + lower === "upgrade" || + lower === "sec-websocket-key" || + lower === "sec-websocket-version" || + lower === "sec-websocket-extensions" || + lower === "sec-websocket-protocol" || + lower === "content-length" || + lower === "transfer-encoding" + ) { + continue; + } + // Strip any `x-cch-*` header the client may have set: those names are + // reserved for internal markers that we'll attach below. Without this an + // external attacker could try to forge `x-cch-internal-secret` / + // `x-cch-responses-ws-forward` and bypass the loopback-only check. + if (lower.startsWith(RESERVED_INTERNAL_HEADER_PREFIX)) { + continue; + } + if (Array.isArray(v)) { + internalHeaders[k] = v.join(", "); + } else if (typeof v === "string") { + internalHeaders[k] = v; + } + } + internalHeaders["accept"] = "text/event-stream"; + internalHeaders["content-type"] = "application/json"; + internalHeaders[CLIENT_TRANSPORT_HEADER] = "websocket"; + internalHeaders[WS_FORWARD_FLAG_HEADER] = "1"; + if (typeof responsesWsSessionId === "string" && responsesWsSessionId.length > 0) { + internalHeaders[WS_SESSION_HEADER] = responsesWsSessionId; + } + // Per-process loopback secret. Read from process.env so it can be picked + // up by any code path that needs to verify (the TS forwarder reads the + // same env var via `internal-secret.ts`). The secret is generated at + // startup if no operator value is preset. + const internalSecret = process.env[INTERNAL_SECRET_ENV]; + if (internalSecret) { + internalHeaders[INTERNAL_SECRET_HEADER] = internalSecret; + } + + // Force streaming so we can translate SSE events to WS frames incrementally. + // The upstream pipeline will strip transport-only fields (stream, background) + // before forwarding to upstream WebSocket. + const bodyForHttp = { ...body, stream: true }; + delete bodyForHttp.background; + + const payload = Buffer.from(JSON.stringify(bodyForHttp), "utf8"); + internalHeaders["content-length"] = String(payload.length); + + await new Promise((resolve) => { + const req = http.request( + { + method: "POST", + hostname: INTERNAL_TUNNEL_HOST, + port, + path: "/v1/responses", + headers: internalHeaders, + }, + (res) => { + const contentType = (res.headers["content-type"] || "").toLowerCase(); + const isSse = contentType.includes("text/event-stream"); + let responseSettled = false; + const settleResponse = () => { + if (responseSettled) return false; + responseSettled = true; + resolve(); + return true; + }; + + if (!isSse) { + // Upstream returned non-stream JSON (e.g. error response). Collect + // and emit as a single terminal event. + const chunks = []; + res.on("data", (c) => chunks.push(c)); + res.on("end", () => { + if (responseSettled) return; + const text = Buffer.concat(chunks).toString("utf8"); + let parsed; + try { + parsed = JSON.parse(text); + } catch { + parsed = { raw: text }; + } + const isHttpError = !!(res.statusCode && res.statusCode >= 400); + if (isHttpError) { + safeSend(ws, { + type: "error", + status: res.statusCode, + error: + typeof parsed === "object" && parsed && parsed.error + ? parsed.error + : { code: `http_${res.statusCode}`, message: text.slice(0, 512) }, + }); + log("info", "ws_terminal_event_sent", { + type: "error", + source: "json", + status: res.statusCode, + }); + } else { + safeSend(ws, { + type: "response.completed", + response: parsed, + }); + log("info", "ws_terminal_event_sent", { type: "response.completed", source: "json" }); + } + settleResponse(); + }); + res.on("error", (err) => { + if (responseSettled) return; + emitErrorEvent( + ws, + "internal_response_error", + String(err && err.message ? err.message : err) + ); + initiateClose(1011, "internal_response_error"); + settleResponse(); + }); + res.on("close", () => { + if (responseSettled) return; + emitErrorEvent( + ws, + "internal_response_closed", + "Internal response closed before a complete JSON body was received" + ); + initiateClose(1011, "internal_response_closed"); + settleResponse(); + }); + return; + } + + // SSE path: decode `data:` events and emit each as a WS JSON frame. + // Accept both LF (`\n\n`) and CRLF (`\r\n\r\n`) event separators since + // upstreams in the wild emit either form. + let buffer = ""; + let sawTerminal = false; + let terminalEventType = null; + const EVENT_DELIMITER = /\r?\n\r?\n/; + const failIfUnsettled = (code, message, closeReason) => { + if (responseSettled) return; + if (!sawTerminal) { + emitErrorEvent(ws, code, message); + initiateClose(1011, closeReason); + } + settleResponse(); + }; + + const flushEvents = () => { + const parts = buffer.split(EVENT_DELIMITER); + // Last part may be a partial event still arriving — keep it buffered. + buffer = parts.pop() ?? ""; + for (const chunk of parts) { + const lines = chunk.split(/\r?\n/); + const dataLines = []; + for (const line of lines) { + if (line.startsWith("data:")) { + // Trim CR / leading whitespace so trailing \r from CRLF lines + // doesn't end up inside the payload. + dataLines.push(line.slice(5).trim()); + } + } + if (dataLines.length === 0) continue; + const dataText = dataLines.join("\n"); + if (dataText.trim() === "[DONE]") { + if (!sawTerminal) { + // Some upstreams close SSE with [DONE] without a preceding + // response.completed. Synthesize one so the client sees a + // clean terminal event. + safeSend(ws, { type: "response.completed", response: null }); + sawTerminal = true; + } + continue; + } + let event; + try { + event = JSON.parse(dataText); + } catch { + // Not JSON; forward as raw string event. + safeSend(ws, { type: "response.output_text.delta", delta: dataText }); + continue; + } + safeSend(ws, event); + if (event && typeof event.type === "string" && TERMINAL_EVENT_TYPES.has(event.type)) { + sawTerminal = true; + terminalEventType = event.type; + log("info", "ws_terminal_event_sent", { type: event.type, source: "sse" }); + } + } + }; + + res.setEncoding("utf8"); + res.on("data", (chunk) => { + if (responseSettled) return; + buffer += chunk; + flushEvents(); + }); + res.on("end", () => { + if (responseSettled) return; + // Flush any remaining buffered event + if (buffer.trim().length > 0) { + buffer += "\n\n"; + flushEvents(); + } + if (!sawTerminal) { + emitErrorEvent( + ws, + "stream_ended_without_terminal", + "Upstream stream ended before emitting a terminal response event" + ); + initiateClose(1011, "stream_ended_without_terminal"); + } else { + // OpenAI Responses WebSocket mode is persistent: after a terminal + // event, the same client connection can send the next + // response.create. Do not close here; only fatal transport/protocol + // errors initiate a close handshake. + log("info", "ws_turn_completed", { terminalEventType }); + } + settleResponse(); + }); + res.on("error", (err) => { + failIfUnsettled( + "internal_response_error", + String(err && err.message ? err.message : err), + "internal_response_error" + ); + }); + res.on("close", () => { + failIfUnsettled( + "internal_response_closed", + "Internal response closed before emitting a terminal response event", + "internal_response_closed" + ); + }); + } + ); + + req.on("error", (err) => { + // ECONNRESET when we destroy() the request on client disconnect is + // expected; downgrade to debug to avoid noisy logs in normal traffic. + const errCode = err && (err.code || err.name); + const isAbort = errCode === "ECONNRESET" || errCode === "ERR_STREAM_PREMATURE_CLOSE"; + if (!isAbort) { + emitErrorEvent( + ws, + "internal_request_error", + String(err && err.message ? err.message : err) + ); + initiateClose(1011, "internal_request_error"); + } + resolve(); + }); + + if (typeof registerInternalReq === "function") { + registerInternalReq(req); + } + req.write(payload); + req.end(); + }); +} + +function isResponsesWsUpgrade(req) { + if (!req.url) return false; + const parsed = parse(req.url); + return parsed.pathname === WS_PATH; +} + +async function main() { + // Surface the build-time Next config via the env var Next's own standalone + // template uses. See server-lib/standalone-config.js for the full rationale. + if (!dev) { + // eslint-disable-next-line global-require + const { applyStandaloneNextConfig } = require("./server-lib/standalone-config"); + applyStandaloneNextConfig({ rootDir: __dirname, env: process.env, log }); + } + + // Import Next programmatically. We require it lazily so that the server can + // still report a clean error if Next is not installed (unlikely but possible + // in a misconfigured deployment). + let nextModule; + try { + // eslint-disable-next-line global-require + nextModule = require("next"); + } catch (err) { + log("error", "next_import_failed", { + error: String(err && err.message ? err.message : err), + }); + process.exit(1); + return; + } + const nextFactory = typeof nextModule === "function" ? nextModule : nextModule.default; + + let WebSocketServer; + try { + // eslint-disable-next-line global-require + WebSocketServer = require("ws").WebSocketServer; + } catch (err) { + log("warn", "ws_module_unavailable_ws_disabled", { + error: String(err && err.message ? err.message : err), + }); + WebSocketServer = null; + } + + // Initialize the per-process internal secret BEFORE next.prepare() so that + // any module loaded by Next can read the same value from process.env. + // Operators may pre-seed the env var; otherwise we generate one. Either + // way the secret never leaves this process. + if (!process.env[INTERNAL_SECRET_ENV]) { + process.env[INTERNAL_SECRET_ENV] = randomUUID(); + } + + const app = nextFactory({ dev, hostname, port }); + const handler = app.getRequestHandler(); + await app.prepare(); + + const server = http.createServer(async (req, res) => { + try { + const parsedUrl = parse(req.url, true); + await handler(req, res, parsedUrl); + } catch (err) { + log("error", "http_handler_error", { + error: String(err && err.message ? err.message : err), + }); + if (!res.headersSent) { + res.statusCode = 500; + res.end("Internal Server Error"); + } + } + }); + + let wss = null; + if (WebSocketServer) { + wss = new WebSocketServer({ noServer: true, maxPayload: WS_MAX_PAYLOAD_BYTES }); + + server.on("upgrade", (req, socket, head) => { + if (!isResponsesWsUpgrade(req)) { + socket.destroy(); + return; + } + wss.handleUpgrade(req, socket, head, (ws) => { + log("info", "ws_client_connected", { path: sanitizedRequestPath(req.url) }); + handleWebSocketConnection(ws, req).catch((err) => { + log("error", "ws_handler_error", { + error: String(err && err.message ? err.message : err), + }); + try { + ws.close(1011, "internal_error"); + } catch { + // ignore + } + }); + }); + }); + } else { + server.on("upgrade", (_req, socket) => { + socket.destroy(); + }); + } + + server.listen(port, hostname, () => { + log("info", "server_listening", { + hostname, + port, + internalTunnelHost: INTERNAL_TUNNEL_HOST, + wsEnabled: !!WebSocketServer, + }); + }); + + registerOrchestratedShutdown(server, wss); +} + +// Graceful shutdown orchestration. Lives here (not in instrumentation.ts) because +// only this process owns the `server` handle returned by http.createServer(). +// +// Sequence (bounded by SHUTDOWN_HARD_EXIT_MS as the final safety net): +// 1. Mark shutdown flag -> /api/health/ready returns 503 -> Service drains +// 2. server.close() -> stop accepting; in-flight HTTP finishes +// 3. wss.close() -> reject new WS upgrades +// 4. Wait for drain -> bounded by SHUTDOWN_DRAIN_MS +// 5. runApplicationCleanup -> Redis / Langfuse / msg buffer / schedulers; bounded +// by SHUTDOWN_CLEANUP_MS. Inside cleanup, asyncTaskManager.cleanupAll() runs +// LAST so streaming responses had a chance to finish during step 4. +// 6. process.exit(0) +function registerOrchestratedShutdown(server, wss) { + let shuttingDown = false; + + // Positive integer parser: `Number("0") || default` would silently coerce an + // intentional 0 back to the default. Mirrors the parser in + // src/lib/langfuse/index.ts so operator overrides behave consistently. + const parsePosInt = (raw, fallback) => { + const n = Number(raw); + return Number.isFinite(n) && n > 0 ? n : fallback; + }; + + // Defaults: drain + cleanup = 25s, with a 3s gap before the hard-exit + // watchdog. Without the gap, when both phases hit their cap the watchdog + // (registered at T=0) fires at the same instant as `process.exit(0)` and + // wins by ordering, falsely logging the shutdown as failed. + const drainMs = parsePosInt(process.env.SHUTDOWN_DRAIN_MS, 15000); + const cleanupMs = parsePosInt(process.env.SHUTDOWN_CLEANUP_MS, 10000); + const hardExitMs = parsePosInt(process.env.SHUTDOWN_HARD_EXIT_MS, 28000); + + const orchestratedShutdown = async (signal) => { + if (shuttingDown) return; + shuttingDown = true; + log("info", "shutdown_received", { signal, drainMs, cleanupMs, hardExitMs }); + + // Final safety: even if every step below hangs, this terminates the process. + // .unref() so the timer itself doesn't keep the event loop alive. + const hardExit = setTimeout(() => { + log("error", "shutdown_hard_exit_watchdog", { hardExitMs }); + process.exit(1); + }, hardExitMs); + if (typeof hardExit.unref === "function") hardExit.unref(); + + // 1. Flip readiness BEFORE closing the listener so probes already in flight + // see 503 and the Service starts removing this pod from endpoints. + const lifecycle = globalThis.__CCH_LIFECYCLE__; + try { + lifecycle?.markShuttingDown?.(); + } catch (err) { + log("warn", "shutdown_mark_failed", { error: String(err && err.message ? err.message : err) }); + } + + // 2 + 3. Stop accepting new connections. + const closeServer = new Promise((resolve) => { + try { + server.close((err) => { + if (err) { + log("warn", "shutdown_server_close_error", { + error: String(err && err.message ? err.message : err), + }); + } + resolve(); + }); + } catch (err) { + log("warn", "shutdown_server_close_threw", { + error: String(err && err.message ? err.message : err), + }); + resolve(); + } + }); + if (wss && typeof wss.close === "function") { + try { + wss.close(); + } catch (err) { + log("warn", "shutdown_wss_close_error", { + error: String(err && err.message ? err.message : err), + }); + } + } + + // 4. Bounded drain — server.close() resolves only after every in-flight + // connection completes; we cap it so a stuck client can't hold us forever. + // Clearing the timer on natural close avoids a misleading + // "shutdown_drain_timeout" warning during the subsequent cleanup phase. + await Promise.race([ + closeServer, + new Promise((resolve) => { + const t = setTimeout(() => { + log("warn", "shutdown_drain_timeout", { drainMs }); + resolve(); + }, drainMs); + if (typeof t.unref === "function") t.unref(); + closeServer.finally(() => clearTimeout(t)); + }), + ]); + + // 5. Application-level cleanup. + try { + if (typeof lifecycle?.runApplicationCleanup === "function") { + await lifecycle.runApplicationCleanup(signal, { totalTimeoutMs: cleanupMs }); + } else { + log("warn", "shutdown_cleanup_unavailable", { + reason: "lifecycle_globals_not_bound", + }); + } + } catch (err) { + log("warn", "shutdown_cleanup_error", { + error: String(err && err.message ? err.message : err), + }); + } + + log("info", "shutdown_complete", { signal }); + clearTimeout(hardExit); + process.exit(0); + }; + + process.once("SIGTERM", () => void orchestratedShutdown("SIGTERM")); + process.once("SIGINT", () => void orchestratedShutdown("SIGINT")); +} + +// Exposed for tests; not part of the long-lived server entrypoint. +module.exports = { + sanitizedRequestPath, + isNextDevMode, + handleWebSocketConnection, + forwardToInternalHttp, + registerOrchestratedShutdown, + WS_MAX_PAYLOAD_BYTES, + MAX_PENDING_BYTES, +}; + +if (require.main === module) { + main().catch((err) => { + log("error", "server_bootstrap_failed", { + error: String(err && err.stack ? err.stack : err), + }); + process.exit(1); + }); +} diff --git a/src/actions/keys.ts b/src/actions/keys.ts index 645bc4ac2..a00882d42 100644 --- a/src/actions/keys.ts +++ b/src/actions/keys.ts @@ -107,7 +107,7 @@ export async function addKey(data: { limitConcurrentSessions?: number; providerGroup?: string | null; cacheTtlPreference?: "inherit" | "5m" | "1h"; -}): Promise> { +}): Promise> { try { // NOTE(#400): providerGroup 安全模型(废弃 null 语义): // - Key.providerGroup 必须显式存储(默认 "default"),不再允许 null @@ -355,7 +355,7 @@ export async function addKey(data: { }); // 返回生成的key供前端显示 - return { ok: true, data: { generatedKey, name: validatedData.name } }; + return { ok: true, data: { id: createdKey.id, generatedKey, name: validatedData.name } }; } catch (error) { logger.error("添加密钥失败:", error); const message = error instanceof Error ? error.message : "添加密钥失败,请稍后重试"; @@ -828,6 +828,69 @@ export async function getKeysWithStatistics( } } +/** + * 获取密钥的未脱敏值 + * - 管理员:可查看任意用户的密钥 + * - 普通用户:仅可查看自己拥有的密钥(与列表 canReveal/canCopy 契约保持一致) + * 用于安全展示和复制完整 Key + */ +export async function getUnmaskedKey(keyId: number): Promise> { + try { + const session = await getSession(); + if (!session) { + return { ok: false, error: "未登录" }; + } + + const key = await findKeyById(keyId); + if (!key) { + return { ok: false, error: "密钥不存在" }; + } + + const isAdmin = session.user.role === "admin"; + const isOwner = session.user.id === key.userId; + if (!isAdmin && !isOwner) { + return { ok: false, error: "无权限执行此操作" }; + } + + // 记录查看行为(不记录密钥内容) + logger.info("User viewed key", { + viewerId: session.user.id, + viewerRole: session.user.role, + keyId, + keyName: key.name, + keyOwnerId: key.userId, + }); + emitActionAudit({ + category: "key", + action: "key.key_reveal", + targetType: "key", + targetId: String(key.id), + targetName: key.name, + after: { + id: key.id, + name: key.name, + userId: key.userId, + }, + success: true, + redactExtraKeys: ["key"], + }); + + return { ok: true, data: { key: key.key } }; + } catch (error) { + logger.error("获取密钥失败:", error); + const message = error instanceof Error ? error.message : "获取密钥失败"; + emitActionAudit({ + category: "key", + action: "key.key_reveal", + targetType: "key", + targetId: String(keyId), + success: false, + errorMessage: "KEY_REVEAL_FAILED", + }); + return { ok: false, error: message }; + } +} + /** * 获取密钥的限额使用情况(实时数据) */ diff --git a/src/actions/providers.ts b/src/actions/providers.ts index 4a41bdc52..cdca252cc 100644 --- a/src/actions/providers.ts +++ b/src/actions/providers.ts @@ -9,6 +9,7 @@ import { buildProxyUrl } from "@/app/v1/_lib/url"; import { db } from "@/drizzle/db"; import { providers as providersTable } from "@/drizzle/schema"; import { normalizeAllowedModelRules } from "@/lib/allowed-model-rules"; +import { redactUrlCredentials } from "@/lib/api/v1/_shared/redaction"; import { emitActionAudit } from "@/lib/audit/emit"; import { getSession } from "@/lib/auth"; import { publishProviderCacheInvalidation } from "@/lib/cache/provider-cache"; @@ -21,6 +22,7 @@ import { resetCircuit, } from "@/lib/circuit-breaker"; import { PROVIDER_GROUP, PROVIDER_TIMEOUT_DEFAULTS } from "@/lib/constants/provider.constants"; +import { normalizeCustomHeadersRecord } from "@/lib/custom-headers"; import { logger } from "@/lib/logger"; import { PROVIDER_ALLOWED_MODEL_RULE_INPUT_LIST_SCHEMA } from "@/lib/provider-allowed-model-schema"; import { PROVIDER_BATCH_PATCH_ERROR_CODES } from "@/lib/provider-batch-patch-error-codes"; @@ -220,6 +222,25 @@ function shouldInvalidateStickySessionsOnProviderEdit( ); } +function redactProviderUrlFields(value: T): T { + if (!value || typeof value !== "object" || Array.isArray(value)) { + return value; + } + + const copy = { ...(value as Record) }; + for (const key of ["url", "providerUrl", "websiteUrl", "proxyUrl", "mcpPassthroughUrl"]) { + if (typeof copy[key] === "string") { + copy[key] = redactUrlCredentials(copy[key]); + } + } + for (const key of ["provider_url", "website_url", "proxy_url", "mcp_passthrough_url"]) { + if (typeof copy[key] === "string") { + copy[key] = redactUrlCredentials(copy[key]); + } + } + return copy as T; +} + // 获取服务商数据 export async function getProviders(): Promise { try { @@ -335,6 +356,7 @@ export async function getProviders(): Promise { circuitBreakerHalfOpenSuccessThreshold: provider.circuitBreakerHalfOpenSuccessThreshold, proxyUrl: provider.proxyUrl, proxyFallbackToDirect: provider.proxyFallbackToDirect, + customHeaders: provider.customHeaders, firstByteTimeoutStreamingMs: provider.firstByteTimeoutStreamingMs, streamingIdleTimeoutMs: provider.streamingIdleTimeoutMs, requestTimeoutNonStreamingMs: provider.requestTimeoutNonStreamingMs, @@ -541,6 +563,7 @@ export async function addProvider(data: { circuit_breaker_half_open_success_threshold?: number; proxy_url?: string | null; proxy_fallback_to_direct?: boolean; + custom_headers?: Record | null; first_byte_timeout_streaming_ms?: number; streaming_idle_timeout_ms?: number; request_timeout_non_streaming_ms?: number; @@ -551,7 +574,7 @@ export async function addProvider(data: { rpm: number | null; rpd: number | null; cc: number | null; -}): Promise { +}): Promise> { try { const session = await getSession(); if (!session || session.user.role !== "admin") { @@ -560,9 +583,9 @@ export async function addProvider(data: { logger.trace("addProvider:input", { name: data.name, - url: data.url, + url: redactUrlCredentials(data.url), provider_type: data.provider_type, - proxy_url: data.proxy_url ? data.proxy_url.replace(/:\/\/[^@]*@/, "://***@") : null, + proxy_url: redactUrlCredentials(data.proxy_url), }); // 验证代理 URL 格式 @@ -586,7 +609,7 @@ export async function addProvider(data: { logger.trace("addProvider:favicon_generated", { domain, faviconUrl }); } catch (error) { logger.warn("addProvider:favicon_fetch_failed", { - websiteUrl: validated.website_url, + websiteUrl: redactUrlCredentials(validated.website_url), error: error instanceof Error ? error.message : String(error), }); // Favicon 获取失败不影响主流程 @@ -682,12 +705,12 @@ export async function addProvider(data: { after: { id: provider.id, name: provider.name, - url: provider.url, + url: redactUrlCredentials(provider.url), isEnabled: provider.isEnabled, }, success: true, }); - return { ok: true }; + return { ok: true, data: { id: provider.id } }; } catch (error) { logger.trace("addProvider:error", { message: error instanceof Error ? error.message : String(error), @@ -755,6 +778,7 @@ export async function editProvider( circuit_breaker_half_open_success_threshold?: number; proxy_url?: string | null; proxy_fallback_to_direct?: boolean; + custom_headers?: Record | null; first_byte_timeout_streaming_ms?: number; streaming_idle_timeout_ms?: number; request_timeout_non_streaming_ms?: number; @@ -797,7 +821,7 @@ export async function editProvider( }); } catch (error) { logger.warn("editProvider:favicon_fetch_failed", { - websiteUrl: validated.website_url, + websiteUrl: redactUrlCredentials(validated.website_url), error: error instanceof Error ? error.message : String(error), }); faviconUrl = null; @@ -934,10 +958,10 @@ export async function editProvider( action: "provider.update", targetType: "provider", targetId: String(providerId), - before: preimageFields, - after: data, + before: redactProviderUrlFields(preimageFields), + after: redactProviderUrlFields(data), success: true, - redactExtraKeys: ["key"], + redactExtraKeys: ["key", "custom_headers", "customHeaders"], }); return { ok: true, @@ -1022,7 +1046,9 @@ export async function removeProvider( targetType: "provider", targetId: String(providerId), targetName: provider?.name ?? null, - before: provider ? { id: provider.id, name: provider.name, url: provider.url } : undefined, + before: provider + ? { id: provider.id, name: provider.name, url: redactUrlCredentials(provider.url) } + : undefined, success: true, }); return { @@ -1464,6 +1490,7 @@ const SINGLE_EDIT_PREIMAGE_FIELD_TO_PROVIDER_KEY: Record circuit_breaker_half_open_success_threshold: "circuitBreakerHalfOpenSuccessThreshold", proxy_url: "proxyUrl", proxy_fallback_to_direct: "proxyFallbackToDirect", + custom_headers: "customHeaders", first_byte_timeout_streaming_ms: "firstByteTimeoutStreamingMs", streaming_idle_timeout_ms: "streamingIdleTimeoutMs", request_timeout_non_streaming_ms: "requestTimeoutNonStreamingMs", @@ -3143,11 +3170,32 @@ export async function getUnmaskedProviderKey(id: number): Promise { return executeProviderApiTest(data, { path: "/v1/chat/completions", - defaultModel: "gpt-5.3-codex", + defaultModel: "gpt-5.4", headers: (apiKey, context) => { void context; return { @@ -4295,7 +4343,7 @@ export async function testProviderOpenAIResponses( ): Promise { return executeProviderApiTest(data, { path: "/v1/responses", - defaultModel: "gpt-5.3-codex", + defaultModel: "gpt-5.4", headers: (apiKey, context) => { void context; return { @@ -4353,7 +4401,7 @@ export async function testProviderGemini( } logger.debug("testProviderGemini: Starting test", { - providerUrl: data.providerUrl, + providerUrl: redactUrlCredentials(data.providerUrl), model: data.model, hasApiKey: !!data.apiKey, apiKeyLength: data.apiKey?.length, @@ -4632,6 +4680,19 @@ export async function testProviderUnified(data: UnifiedTestArgs): Promise | undefined; + if (data.customHeaders !== undefined) { + const headerValidation = normalizeCustomHeadersRecord(data.customHeaders); + if (!headerValidation.ok) { + return { + ok: false, + error: `custom_headers_${headerValidation.code}`, + }; + } + normalizedCustomHeaders = headerValidation.value ?? undefined; + } + try { // Build test configuration const config: ProviderTestConfig = { @@ -4647,7 +4708,7 @@ export async function testProviderUnified(data: UnifiedTestArgs): Promise> { + try { + const tError = await getTranslations("errors"); + + const session = await getSession(); + if (!session) { + return { + ok: false, + error: tError("UNAUTHORIZED"), + errorCode: ERROR_CODES.UNAUTHORIZED, + }; + } + + if (session.user.role !== "admin" && session.user.id !== userId) { + return { + ok: false, + error: tError("PERMISSION_DENIED"), + errorCode: ERROR_CODES.PERMISSION_DENIED, + }; + } + + const user = await findUserById(userId); + if (!user) { + return { + ok: false, + error: tError("USER_NOT_FOUND"), + errorCode: ERROR_CODES.NOT_FOUND, + }; + } + + return { ok: true, data: user }; + } catch (error) { + logger.error(`Failed to fetch user ${userId}:`, error); + const message = error instanceof Error ? error.message : "Failed to fetch user"; + return { ok: false, error: message, errorCode: ERROR_CODES.INTERNAL_ERROR }; + } +} + export async function searchUsersForFilter( searchTerm?: string, limit?: number @@ -696,7 +740,7 @@ export async function getUsersBatch( id: key.id, name: key.name, maskedKey: maskKey(key.key), - fullKey: canUserManageKey ? key.key : undefined, + canReveal: canUserManageKey, canCopy: canUserManageKey, expiresAt: key.expiresAt ? key.expiresAt.toISOString().split("T")[0] @@ -843,7 +887,7 @@ export async function getUsersBatchCore( id: key.id, name: key.name, maskedKey: maskKey(key.key), - fullKey: canUserManageKey ? key.key : undefined, + canReveal: canUserManageKey, canCopy: canUserManageKey, expiresAt: key.expiresAt ? key.expiresAt.toISOString().split("T")[0] : t("neverExpires"), status: key.isEnabled ? "enabled" : ("disabled" as const), @@ -878,7 +922,14 @@ export async function getUsersBatchCore( }; }); - return { ok: true, data: { users: userDisplays, nextCursor, hasMore } }; + return { + ok: true, + data: { + users: userDisplays.map(toActionTransportUserDisplay), + nextCursor, + hasMore, + }, + }; } catch (error) { logger.error("Failed to fetch user batch core data:", error); const message = error instanceof Error ? error.message : "Failed to fetch user batch core data"; @@ -886,6 +937,30 @@ export async function getUsersBatchCore( } } +function toActionTransportUserDisplay(user: UserDisplay): UserDisplay { + return { + ...user, + costResetAt: toActionTransportDate(user.costResetAt), + expiresAt: toActionTransportDate(user.expiresAt), + keys: user.keys.map((key) => ({ + ...key, + createdAt: toRequiredActionTransportDate(key.createdAt), + lastUsedAt: toActionTransportDate(key.lastUsedAt), + })), + }; +} + +function toActionTransportDate(value: Date | null | undefined): Date | null { + if (!value) return null; + const timestamp = value.getTime(); + return (Number.isFinite(timestamp) ? value.toISOString() : null) as unknown as Date; +} + +function toRequiredActionTransportDate(value: Date): Date { + const timestamp = value.getTime(); + return (Number.isFinite(timestamp) ? value.toISOString() : null) as unknown as Date; +} + /** * Lazy-load usage data for a batch of users. * Called after getUsersBatchCore to populate usage fields in the background. diff --git a/src/app/[locale]/dashboard/_components/bento/dashboard-bento.tsx b/src/app/[locale]/dashboard/_components/bento/dashboard-bento.tsx index a4e7c2db9..88dddaddf 100644 --- a/src/app/[locale]/dashboard/_components/bento/dashboard-bento.tsx +++ b/src/app/[locale]/dashboard/_components/bento/dashboard-bento.tsx @@ -5,10 +5,10 @@ import { Activity, Clock, DollarSign, TrendingUp } from "lucide-react"; import dynamic from "next/dynamic"; import { useTranslations } from "next-intl"; import { useMemo, useState } from "react"; -import { getActiveSessions } from "@/actions/active-sessions"; -import type { OverviewData } from "@/actions/overview"; -import { getOverviewData } from "@/actions/overview"; -import { getUserStatistics } from "@/actions/statistics"; +import { getActiveSessions } from "@/lib/api-client/v1/actions/active-sessions"; +import type { OverviewData } from "@/lib/api-client/v1/actions/overview"; +import { getOverviewData } from "@/lib/api-client/v1/actions/overview"; +import { getUserStatistics } from "@/lib/api-client/v1/actions/statistics"; import type { CurrencyCode } from "@/lib/utils"; import { cn } from "@/lib/utils"; import { formatCurrency } from "@/lib/utils/currency"; diff --git a/src/app/[locale]/dashboard/_components/rate-limit-top-users.tsx b/src/app/[locale]/dashboard/_components/rate-limit-top-users.tsx index 826098fb0..507f786f2 100644 --- a/src/app/[locale]/dashboard/_components/rate-limit-top-users.tsx +++ b/src/app/[locale]/dashboard/_components/rate-limit-top-users.tsx @@ -3,7 +3,6 @@ import { ArrowUpDown } from "lucide-react"; import { useLocale, useTranslations } from "next-intl"; import * as React from "react"; -import { searchUsers } from "@/actions/users"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { @@ -14,6 +13,7 @@ import { TableHeader, TableRow, } from "@/components/ui/table"; +import { searchUsers } from "@/lib/api-client/v1/actions/users"; export interface RateLimitTopUsersProps { data: Record; diff --git a/src/app/[locale]/dashboard/_components/statistics/wrapper.tsx b/src/app/[locale]/dashboard/_components/statistics/wrapper.tsx index 247467ad8..ed98d4e57 100644 --- a/src/app/[locale]/dashboard/_components/statistics/wrapper.tsx +++ b/src/app/[locale]/dashboard/_components/statistics/wrapper.tsx @@ -4,7 +4,7 @@ import { useQuery } from "@tanstack/react-query"; import { useTranslations } from "next-intl"; import * as React from "react"; import { toast } from "sonner"; -import { getUserStatistics } from "@/actions/statistics"; +import { getUserStatistics } from "@/lib/api-client/v1/actions/statistics"; import type { CurrencyCode } from "@/lib/utils"; import type { TimeRange, UserStatisticsData } from "@/types/statistics"; import { DEFAULT_TIME_RANGE } from "@/types/statistics"; diff --git a/src/app/[locale]/dashboard/_components/user/batch-edit/batch-edit-dialog.tsx b/src/app/[locale]/dashboard/_components/user/batch-edit/batch-edit-dialog.tsx index d466a2dde..cc85f7ed4 100644 --- a/src/app/[locale]/dashboard/_components/user/batch-edit/batch-edit-dialog.tsx +++ b/src/app/[locale]/dashboard/_components/user/batch-edit/batch-edit-dialog.tsx @@ -5,8 +5,6 @@ import { Loader2 } from "lucide-react"; import { useTranslations } from "next-intl"; import { useEffect, useMemo, useState } from "react"; import { toast } from "sonner"; -import { type BatchUpdateKeysParams, batchUpdateKeys } from "@/actions/keys"; -import { type BatchUpdateUsersParams, batchUpdateUsers } from "@/actions/users"; import { AlertDialog, AlertDialogAction, @@ -27,6 +25,8 @@ import { DialogTitle, } from "@/components/ui/dialog"; import { Separator } from "@/components/ui/separator"; +import { type BatchUpdateKeysParams, batchUpdateKeys } from "@/lib/api-client/v1/actions/keys"; +import { type BatchUpdateUsersParams, batchUpdateUsers } from "@/lib/api-client/v1/actions/users"; import { BatchKeySection, type BatchKeySectionState } from "./batch-key-section"; import { BatchUserSection, type BatchUserSectionState } from "./batch-user-section"; diff --git a/src/app/[locale]/dashboard/_components/user/create-user-dialog.tsx b/src/app/[locale]/dashboard/_components/user/create-user-dialog.tsx index 256925de3..82fa0033b 100644 --- a/src/app/[locale]/dashboard/_components/user/create-user-dialog.tsx +++ b/src/app/[locale]/dashboard/_components/user/create-user-dialog.tsx @@ -7,8 +7,6 @@ import { useTranslations } from "next-intl"; import { useMemo, useRef, useState, useTransition } from "react"; import { toast } from "sonner"; import { z } from "zod"; -import { addKey } from "@/actions/keys"; -import { createUserOnly, removeUser } from "@/actions/users"; import { Button } from "@/components/ui/button"; import { Dialog, @@ -21,6 +19,8 @@ import { import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Separator } from "@/components/ui/separator"; +import { addKey } from "@/lib/api-client/v1/actions/keys"; +import { createUserOnly, removeUser } from "@/lib/api-client/v1/actions/users"; import { PROVIDER_GROUP } from "@/lib/constants/provider.constants"; import { useZodForm } from "@/lib/hooks/use-zod-form"; import { KeyFormSchema, UpdateUserSchema } from "@/lib/validation/schemas"; diff --git a/src/app/[locale]/dashboard/_components/user/edit-user-dialog.tsx b/src/app/[locale]/dashboard/_components/user/edit-user-dialog.tsx index 90e36ca2c..16a5f1442 100644 --- a/src/app/[locale]/dashboard/_components/user/edit-user-dialog.tsx +++ b/src/app/[locale]/dashboard/_components/user/edit-user-dialog.tsx @@ -7,13 +7,6 @@ import { useLocale, useTranslations } from "next-intl"; import { useMemo, useState, useTransition } from "react"; import { toast } from "sonner"; import { z } from "zod"; -import { - editUser, - removeUser, - resetUserAllStatistics, - resetUserLimitsOnly, - toggleUserEnabled, -} from "@/actions/users"; import { AlertDialog, AlertDialogAction, @@ -34,6 +27,13 @@ import { DialogHeader, DialogTitle, } from "@/components/ui/dialog"; +import { + editUser, + removeUser, + resetUserAllStatistics, + resetUserLimitsOnly, + toggleUserEnabled, +} from "@/lib/api-client/v1/actions/users"; import { useZodForm } from "@/lib/hooks/use-zod-form"; import { cn } from "@/lib/utils"; import { UpdateUserSchema } from "@/lib/validation/schemas"; diff --git a/src/app/[locale]/dashboard/_components/user/forms/add-key-form.tsx b/src/app/[locale]/dashboard/_components/user/forms/add-key-form.tsx index 789e4742c..a9e8f5f16 100644 --- a/src/app/[locale]/dashboard/_components/user/forms/add-key-form.tsx +++ b/src/app/[locale]/dashboard/_components/user/forms/add-key-form.tsx @@ -3,8 +3,6 @@ import { useRouter } from "next/navigation"; import { useTranslations } from "next-intl"; import { useCallback, useEffect, useState, useTransition } from "react"; import { toast } from "sonner"; -import { addKey } from "@/actions/keys"; -import { getAvailableProviderGroups } from "@/actions/providers"; import { DatePickerField } from "@/components/form/date-picker-field"; import { NumberField, TagInputField, TextField } from "@/components/form/form-field"; import { DialogFormLayout, FormGrid } from "@/components/form/form-layout"; @@ -17,6 +15,8 @@ import { SelectValue, } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; +import { addKey } from "@/lib/api-client/v1/actions/keys"; +import { getAvailableProviderGroups } from "@/lib/api-client/v1/actions/providers"; import { PROVIDER_GROUP } from "@/lib/constants/provider.constants"; import { useZodForm } from "@/lib/hooks/use-zod-form"; import { getErrorMessage } from "@/lib/utils/error-messages"; @@ -231,7 +231,7 @@ export function AddKeyForm({ userId, user, isAdmin = false, onSuccess }: AddKeyF } > - + {t("cacheTtl.options.inherit")} diff --git a/src/app/[locale]/dashboard/_components/user/forms/delete-key-confirm.tsx b/src/app/[locale]/dashboard/_components/user/forms/delete-key-confirm.tsx index b1e76e759..65570e2b5 100644 --- a/src/app/[locale]/dashboard/_components/user/forms/delete-key-confirm.tsx +++ b/src/app/[locale]/dashboard/_components/user/forms/delete-key-confirm.tsx @@ -3,7 +3,6 @@ import { useRouter } from "next/navigation"; import { useTranslations } from "next-intl"; import { useTransition } from "react"; import { toast } from "sonner"; -import { removeKey } from "@/actions/keys"; import { Button } from "@/components/ui/button"; import { DialogClose, @@ -12,6 +11,7 @@ import { DialogHeader, DialogTitle, } from "@/components/ui/dialog"; +import { removeKey } from "@/lib/api-client/v1/actions/keys"; interface DeleteKeyConfirmProps { keyData?: { diff --git a/src/app/[locale]/dashboard/_components/user/forms/delete-user-confirm.tsx b/src/app/[locale]/dashboard/_components/user/forms/delete-user-confirm.tsx index dfbfc374f..2c61c774d 100644 --- a/src/app/[locale]/dashboard/_components/user/forms/delete-user-confirm.tsx +++ b/src/app/[locale]/dashboard/_components/user/forms/delete-user-confirm.tsx @@ -2,7 +2,6 @@ import { useRouter } from "next/navigation"; import { useTransition } from "react"; import { toast } from "sonner"; -import { removeUser } from "@/actions/users"; import { Button } from "@/components/ui/button"; import { DialogClose, @@ -11,6 +10,7 @@ import { DialogHeader, DialogTitle, } from "@/components/ui/dialog"; +import { removeUser } from "@/lib/api-client/v1/actions/users"; interface DeleteUserConfirmProps { user?: { diff --git a/src/app/[locale]/dashboard/_components/user/forms/edit-key-form.tsx b/src/app/[locale]/dashboard/_components/user/forms/edit-key-form.tsx index 3d82a7286..212c06f70 100644 --- a/src/app/[locale]/dashboard/_components/user/forms/edit-key-form.tsx +++ b/src/app/[locale]/dashboard/_components/user/forms/edit-key-form.tsx @@ -5,8 +5,6 @@ import { useRouter } from "next/navigation"; import { useLocale, useTranslations } from "next-intl"; import { useCallback, useEffect, useState, useTransition } from "react"; import { toast } from "sonner"; -import { editKey, resetKeyLimitsOnly } from "@/actions/keys"; -import { getAvailableProviderGroups } from "@/actions/providers"; import { DatePickerField } from "@/components/form/date-picker-field"; import { NumberField, TagInputField, TextField } from "@/components/form/form-field"; import { DialogFormLayout, FormGrid } from "@/components/form/form-layout"; @@ -31,6 +29,8 @@ import { SelectValue, } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; +import { editKey, resetKeyLimitsOnly } from "@/lib/api-client/v1/actions/keys"; +import { getAvailableProviderGroups } from "@/lib/api-client/v1/actions/providers"; import { PROVIDER_GROUP } from "@/lib/constants/provider.constants"; import { useZodForm } from "@/lib/hooks/use-zod-form"; import { getErrorMessage } from "@/lib/utils/error-messages"; @@ -292,7 +292,7 @@ export function EditKeyForm({ keyData, user, isAdmin = false, onSuccess }: EditK } > - + {tKeyEdit("cacheTtl.options.inherit")} diff --git a/src/app/[locale]/dashboard/_components/user/forms/key-edit-section.tsx b/src/app/[locale]/dashboard/_components/user/forms/key-edit-section.tsx index beaed6e92..f1f212ad0 100644 --- a/src/app/[locale]/dashboard/_components/user/forms/key-edit-section.tsx +++ b/src/app/[locale]/dashboard/_components/user/forms/key-edit-section.tsx @@ -488,23 +488,21 @@ export function KeyEditSection({ )}

- {translations.fields.providerGroup.editHint || "已有密钥的分组不可修改"} + {translations.fields.providerGroup.editHint}

) : ( // 创建模式:多选 true} - description={ - translations.fields.providerGroup.selectHint || "选择此 Key 可使用的供应商分组" - } + description={translations.fields.providerGroup.selectHint} /> )} @@ -524,12 +522,12 @@ export function KeyEditSection({ ))}

- {translations.fields.providerGroup.editHint || "已有密钥的分组不可修改"} + {translations.fields.providerGroup.editHint}

) : (
- {translations.fields.providerGroup.noGroupHint || "您没有分组限制,可以访问所有供应商"} + {translations.fields.providerGroup.noGroupHint}
)} diff --git a/src/app/[locale]/dashboard/_components/user/forms/provider-group-select.tsx b/src/app/[locale]/dashboard/_components/user/forms/provider-group-select.tsx index e3bd4c813..dc600eaba 100644 --- a/src/app/[locale]/dashboard/_components/user/forms/provider-group-select.tsx +++ b/src/app/[locale]/dashboard/_components/user/forms/provider-group-select.tsx @@ -2,9 +2,9 @@ import { useCallback, useEffect, useMemo, useState } from "react"; import { toast } from "sonner"; -import { getProviderGroupsWithCount } from "@/actions/providers"; import { TagInputField } from "@/components/form/form-field"; import type { TagInputSuggestion } from "@/components/ui/tag-input"; +import { getProviderGroupsWithCount } from "@/lib/api-client/v1/actions/providers"; import { PROVIDER_GROUP } from "@/lib/constants/provider.constants"; import { parseProviderGroups } from "@/lib/utils/provider-group"; diff --git a/src/app/[locale]/dashboard/_components/user/forms/user-form.tsx b/src/app/[locale]/dashboard/_components/user/forms/user-form.tsx index db0289ee8..aaa0c8244 100644 --- a/src/app/[locale]/dashboard/_components/user/forms/user-form.tsx +++ b/src/app/[locale]/dashboard/_components/user/forms/user-form.tsx @@ -4,13 +4,13 @@ import { useTranslations } from "next-intl"; import { useEffect, useMemo, useState, useTransition } from "react"; import { toast } from "sonner"; import { z } from "zod"; -import { getAvailableProviderGroups } from "@/actions/providers"; -import { addUser, editUser } from "@/actions/users"; import { DatePickerField } from "@/components/form/date-picker-field"; import { ArrayTagInputField, TagInputField, TextField } from "@/components/form/form-field"; import { DialogFormLayout, FormGrid } from "@/components/form/form-layout"; import { InlineWarning } from "@/components/ui/inline-warning"; import { Switch } from "@/components/ui/switch"; +import { getAvailableProviderGroups } from "@/lib/api-client/v1/actions/providers"; +import { addUser, editUser } from "@/lib/api-client/v1/actions/users"; import { PROVIDER_GROUP } from "@/lib/constants/provider.constants"; import { USER_LIMITS } from "@/lib/constants/user.constants"; import { useZodForm } from "@/lib/hooks/use-zod-form"; diff --git a/src/app/[locale]/dashboard/_components/user/hooks/use-key-translations.ts b/src/app/[locale]/dashboard/_components/user/hooks/use-key-translations.ts index d471b825d..182689b9f 100644 --- a/src/app/[locale]/dashboard/_components/user/hooks/use-key-translations.ts +++ b/src/app/[locale]/dashboard/_components/user/hooks/use-key-translations.ts @@ -24,6 +24,10 @@ export interface KeyEditTranslations { providerGroup: { label: string; placeholder: string; + selectHint: string; + editHint: string; + allGroups: string; + noGroupHint: string; }; cacheTtl: { label: string; @@ -151,6 +155,10 @@ export function useKeyTranslations(): KeyEditTranslations { providerGroup: { label: t("keyEditSection.fields.providerGroup.label"), placeholder: t("keyEditSection.fields.providerGroup.placeholder"), + selectHint: t("keyEditSection.fields.providerGroup.selectHint"), + editHint: t("keyEditSection.fields.providerGroup.editHint"), + allGroups: t("keyEditSection.fields.providerGroup.allGroups"), + noGroupHint: t("keyEditSection.fields.providerGroup.noGroupHint"), }, cacheTtl: { label: t("keyEditSection.fields.cacheTtl.label"), diff --git a/src/app/[locale]/dashboard/_components/user/hooks/use-model-suggestions.ts b/src/app/[locale]/dashboard/_components/user/hooks/use-model-suggestions.ts index e196592ee..7661dd5c2 100644 --- a/src/app/[locale]/dashboard/_components/user/hooks/use-model-suggestions.ts +++ b/src/app/[locale]/dashboard/_components/user/hooks/use-model-suggestions.ts @@ -1,7 +1,7 @@ "use client"; import { useEffect, useState } from "react"; -import { getModelSuggestionsByProviderGroup } from "@/actions/providers"; +import { getModelSuggestionsByProviderGroup } from "@/lib/api-client/v1/actions/providers"; /** * Hook to fetch model suggestions for autocomplete. diff --git a/src/app/[locale]/dashboard/_components/user/key-limit-usage.tsx b/src/app/[locale]/dashboard/_components/user/key-limit-usage.tsx index 22af74c87..a90698dd0 100644 --- a/src/app/[locale]/dashboard/_components/user/key-limit-usage.tsx +++ b/src/app/[locale]/dashboard/_components/user/key-limit-usage.tsx @@ -3,8 +3,8 @@ import { AlertCircle, Loader2 } from "lucide-react"; import { useTranslations } from "next-intl"; import { useEffect, useState } from "react"; -import { getKeyLimitUsage } from "@/actions/keys"; import { Progress } from "@/components/ui/progress"; +import { getKeyLimitUsage } from "@/lib/api-client/v1/actions/keys"; import { cn } from "@/lib/utils"; import { type CurrencyCode, formatCurrency } from "@/lib/utils/currency"; diff --git a/src/app/[locale]/dashboard/_components/user/key-list-header.tsx b/src/app/[locale]/dashboard/_components/user/key-list-header.tsx index f40ffce9e..530c4ceb6 100644 --- a/src/app/[locale]/dashboard/_components/user/key-list-header.tsx +++ b/src/app/[locale]/dashboard/_components/user/key-list-header.tsx @@ -4,7 +4,6 @@ import { formatInTimeZone } from "date-fns-tz"; import { CheckCircle, Copy, Eye, EyeOff, ListPlus } from "lucide-react"; import { useLocale, useTimeZone, useTranslations } from "next-intl"; import { useEffect, useMemo, useState } from "react"; -import { getProxyStatus } from "@/actions/proxy-status"; import { FormErrorBoundary } from "@/components/form-error-boundary"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; @@ -17,6 +16,7 @@ import { DialogTitle, DialogTrigger, } from "@/components/ui/dialog"; +import { getProxyStatus } from "@/lib/api-client/v1/actions/proxy-status"; import { copyToClipboard, isClipboardSupported } from "@/lib/utils/clipboard"; import { type CurrencyCode, formatCurrency } from "@/lib/utils/currency"; import { formatDate, formatDateDistance } from "@/lib/utils/date-format"; diff --git a/src/app/[locale]/dashboard/_components/user/key-list.tsx b/src/app/[locale]/dashboard/_components/user/key-list.tsx index 089acb827..c3d35713a 100644 --- a/src/app/[locale]/dashboard/_components/user/key-list.tsx +++ b/src/app/[locale]/dashboard/_components/user/key-list.tsx @@ -2,6 +2,7 @@ import { Check, ChevronDown, ChevronRight, Copy, ExternalLink, Eye, EyeOff } from "lucide-react"; import { useTranslations } from "next-intl"; import { useEffect, useState } from "react"; +import { toast } from "sonner"; import { Button } from "@/components/ui/button"; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; import { DataTable, TableColumnTypes } from "@/components/ui/data-table"; @@ -15,6 +16,7 @@ import { TableRow, } from "@/components/ui/table"; import { Link } from "@/i18n/routing"; +import { getUnmaskedKey } from "@/lib/api-client/v1/actions/keys"; import { copyToClipboard, isClipboardSupported } from "@/lib/utils/clipboard"; import { type CurrencyCode, formatCurrency } from "@/lib/utils/currency"; import type { User, UserKeyDisplay } from "@/types/user"; @@ -37,10 +39,14 @@ export function KeyList({ currencyCode = "USD", }: KeyListProps) { const t = useTranslations("dashboard.keyList"); + const tCommon = useTranslations("common"); const [copiedKeyId, setCopiedKeyId] = useState(null); const [expandedKeys, setExpandedKeys] = useState>(new Set()); const [visibleKeyIds, setVisibleKeyIds] = useState>(new Set()); const [clipboardAvailable, setClipboardAvailable] = useState(false); + // Cache unmasked keys after they have been revealed (lazy fetch via :reveal endpoint). + const [revealedKeys, setRevealedKeys] = useState>({}); + const [revealingKeyId, setRevealingKeyId] = useState(null); const canDeleteKeys = keys.length > 1; // 检测 clipboard 是否可用 @@ -48,6 +54,27 @@ export function KeyList({ setClipboardAvailable(isClipboardSupported()); }, []); + // Drop the reveal cache whenever the underlying key set changes so a removed + // / replaced id can't expose its previously cached plaintext on a new row. + useEffect(() => { + const currentIds = new Set(keys.map((k) => k.id)); + setRevealedKeys((prev) => { + const next: Record = {}; + for (const [idStr, value] of Object.entries(prev)) { + const id = Number(idStr); + if (currentIds.has(id)) next[id] = value; + } + return next; + }); + setVisibleKeyIds((prev) => { + const next = new Set(); + for (const id of prev) { + if (currentIds.has(id)) next.add(id); + } + return next; + }); + }, [keys]); + const toggleExpanded = (keyId: number) => { setExpandedKeys((prev) => { const newSet = new Set(prev); @@ -72,16 +99,51 @@ export function KeyList({ }); }; - const handleCopyKey = async (key: UserKeyDisplay) => { - if (!key.fullKey || !key.canCopy) return; + const fetchUnmaskedKey = async (keyId: number): Promise => { + if (revealedKeys[keyId]) return revealedKeys[keyId]; + setRevealingKeyId(keyId); + try { + const res = await getUnmaskedKey(keyId); + if (!res.ok) { + toast.error(res.error || t("copyFailedTooltip")); + return null; + } + if (!res.data?.key) { + toast.error(tCommon("copyFailed")); + return null; + } + setRevealedKeys((prev) => ({ ...prev, [keyId]: res.data.key })); + return res.data.key; + } catch (error) { + console.error("[KeyList] reveal failed", error); + toast.error(tCommon("copyFailed")); + return null; + } finally { + setRevealingKeyId(null); + } + }; - const success = await copyToClipboard(key.fullKey); + const handleCopyKey = async (key: UserKeyDisplay) => { + if (!key.canCopy) return; + const fullKey = await fetchUnmaskedKey(key.id); + if (!fullKey) return; + const success = await copyToClipboard(fullKey); if (success) { setCopiedKeyId(key.id); setTimeout(() => setCopiedKeyId(null), 2000); } }; + const handleToggleVisibility = async (keyId: number) => { + if (visibleKeyIds.has(keyId)) { + toggleKeyVisibility(keyId); + return; + } + const fullKey = await fetchUnmaskedKey(keyId); + if (!fullKey) return; + toggleKeyVisibility(keyId); + }; + const columns = [ TableColumnTypes.text("name", t("columns.name"), { render: (value, record) => { @@ -179,19 +241,21 @@ export function KeyList({ TableColumnTypes.text("maskedKey", t("columns.key"), { render: (_, record: UserKeyDisplay) => { const isVisible = visibleKeyIds.has(record.id); - const displayKey = isVisible && record.fullKey ? record.fullKey : record.maskedKey || "-"; + const fullKey = revealedKeys[record.id]; + const displayKey = isVisible && fullKey ? fullKey : record.maskedKey || "-"; + const isRevealing = revealingKeyId === record.id; return (
{displayKey}
- {record.canCopy && - record.fullKey && - (clipboardAvailable ? ( - // HTTPS 环境:显示复制按钮 + {record.canReveal && + (clipboardAvailable && record.canCopy ? ( + // HTTPS 环境:显示复制按钮(按需 fetch 完整 key) ) : ( - // HTTP 环境:显示显示/隐藏按钮 + // HTTP 环境(或不允许复制):显示显示/隐藏按钮(按需 fetch)
{/* Full Key Display Dialog */} - {keyData.fullKey && ( + {revealedKey && ( )} diff --git a/src/app/[locale]/dashboard/_components/user/user-key-table-row.tsx b/src/app/[locale]/dashboard/_components/user/user-key-table-row.tsx index 0528c4a05..3fedefe77 100644 --- a/src/app/[locale]/dashboard/_components/user/user-key-table-row.tsx +++ b/src/app/[locale]/dashboard/_components/user/user-key-table-row.tsx @@ -14,8 +14,6 @@ import { import { useLocale, useTranslations } from "next-intl"; import { useEffect, useMemo, useState, useTransition } from "react"; import { toast } from "sonner"; -import { removeKey } from "@/actions/keys"; -import { editUser, toggleUserEnabled } from "@/actions/users"; import { QuotaQuickEditPopover } from "@/components/quota/quota-quick-edit-popover"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; @@ -23,6 +21,8 @@ import { Checkbox } from "@/components/ui/checkbox"; import { Switch } from "@/components/ui/switch"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; import { useRouter } from "@/i18n/routing"; +import { removeKey } from "@/lib/api-client/v1/actions/keys"; +import { editUser, toggleUserEnabled } from "@/lib/api-client/v1/actions/users"; import { clearUsageCache } from "@/lib/dashboard/user-limit-usage-cache"; import { cn } from "@/lib/utils"; import { getContrastTextColor, getGroupColor } from "@/lib/utils/color"; @@ -640,7 +640,7 @@ export function UserKeyTableRow({ id: key.id, name: key.name, maskedKey: key.maskedKey, - fullKey: key.fullKey, + canReveal: key.canReveal, canCopy: key.canCopy, providerGroup: key.providerGroup, todayUsage: key.todayUsage, diff --git a/src/app/[locale]/dashboard/_components/user/user-list.tsx b/src/app/[locale]/dashboard/_components/user/user-list.tsx index cb64c5bd6..4e356fff5 100644 --- a/src/app/[locale]/dashboard/_components/user/user-list.tsx +++ b/src/app/[locale]/dashboard/_components/user/user-list.tsx @@ -4,7 +4,6 @@ import { Loader2, MoreVertical, SquarePen, Trash, Users } from "lucide-react"; import { useLocale, useTranslations } from "next-intl"; import { useCallback, useMemo, useState, useTransition } from "react"; import { toast } from "sonner"; -import { renewUser, toggleUserEnabled } from "@/actions/users"; import { DatePickerField } from "@/components/form/date-picker-field"; import { FormErrorBoundary } from "@/components/form-error-boundary"; import { Button } from "@/components/ui/button"; @@ -26,6 +25,7 @@ import { import { Label } from "@/components/ui/label"; import { ListContainer, ListItem, type ListItemData } from "@/components/ui/list"; import { Switch } from "@/components/ui/switch"; +import { renewUser, toggleUserEnabled } from "@/lib/api-client/v1/actions/users"; import { formatDate, formatDateDistance } from "@/lib/utils/date-format"; import type { User, UserDisplay } from "@/types/user"; import { AddUserDialog } from "./add-user-dialog"; diff --git a/src/app/[locale]/dashboard/_components/user/user-management-table.tsx b/src/app/[locale]/dashboard/_components/user/user-management-table.tsx index 31df9dc7a..07962baeb 100644 --- a/src/app/[locale]/dashboard/_components/user/user-management-table.tsx +++ b/src/app/[locale]/dashboard/_components/user/user-management-table.tsx @@ -6,9 +6,9 @@ import { useRouter } from "next/navigation"; import { useTranslations } from "next-intl"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { toast } from "sonner"; -import { renewUser } from "@/actions/users"; import { Button } from "@/components/ui/button"; import { useVirtualizer } from "@/hooks/use-virtualizer"; +import { renewUser } from "@/lib/api-client/v1/actions/users"; import { cn } from "@/lib/utils"; import type { User, UserDisplay } from "@/types/user"; import { BatchEditToolbar } from "./batch-edit/batch-edit-toolbar"; diff --git a/src/app/[locale]/dashboard/_components/webhook-migration-dialog.tsx b/src/app/[locale]/dashboard/_components/webhook-migration-dialog.tsx index a75bc256a..46fed4277 100644 --- a/src/app/[locale]/dashboard/_components/webhook-migration-dialog.tsx +++ b/src/app/[locale]/dashboard/_components/webhook-migration-dialog.tsx @@ -3,7 +3,6 @@ import { AlertCircle, ArrowRight, CheckCircle2, Loader2, Settings, Webhook } from "lucide-react"; import { useTranslations } from "next-intl"; import { useCallback, useEffect, useState } from "react"; -import { getNotificationSettingsAction } from "@/actions/notifications"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { @@ -22,6 +21,7 @@ import { SelectValue, } from "@/components/ui/select"; import { useRouter } from "@/i18n/routing"; +import { getNotificationSettingsAction } from "@/lib/api-client/v1/actions/notifications"; import { logger } from "@/lib/logger"; import { setOnboardingCompleted, shouldShowOnboarding } from "@/lib/onboarding"; import { cn } from "@/lib/utils"; diff --git a/src/app/[locale]/dashboard/audit-logs/_components/audit-logs-view.tsx b/src/app/[locale]/dashboard/audit-logs/_components/audit-logs-view.tsx index 6a15068cc..58807e4dd 100644 --- a/src/app/[locale]/dashboard/audit-logs/_components/audit-logs-view.tsx +++ b/src/app/[locale]/dashboard/audit-logs/_components/audit-logs-view.tsx @@ -4,7 +4,6 @@ import { useInfiniteQuery } from "@tanstack/react-query"; import { Loader2 } from "lucide-react"; import { useTranslations } from "next-intl"; import { useCallback, useMemo, useState } from "react"; -import { getAuditLogsBatch } from "@/actions/audit-logs"; import { IpDetailsDialog } from "@/app/[locale]/dashboard/_components/ip-details-dialog"; import { IpDisplayTrigger } from "@/app/[locale]/dashboard/_components/ip-display-trigger"; import { @@ -22,6 +21,7 @@ import { SelectValue, } from "@/components/ui/select"; import { useVirtualizedInfiniteList } from "@/hooks/use-virtualized-infinite-list"; +import { getAuditLogsBatch } from "@/lib/api-client/v1/actions/audit-logs"; import { cn } from "@/lib/utils"; import type { AuditCategory, AuditLogRow } from "@/types/audit-log"; import { AuditLogDetailSheet } from "./audit-log-detail-sheet"; @@ -42,6 +42,12 @@ const CATEGORIES: AuditCategory[] = [ ]; type StatusFilter = "all" | "success" | "failure"; +type AuditLogsPage = { + rows: AuditLogRow[]; + nextCursor: string | null; + hasMore: boolean; + limit?: number; +}; export function AuditLogsView() { const t = useTranslations("auditLogs"); @@ -68,18 +74,14 @@ export function AuditLogsView() { useInfiniteQuery({ queryKey: ["audit-logs-batch", filter], queryFn: async ({ pageParam }) => { - const result = await getAuditLogsBatch({ + return getAuditLogsBatch({ filter, cursor: pageParam ?? null, pageSize: BATCH_SIZE, - }); - if (!result.ok) { - throw new Error(result.error); - } - return result.data; + }) as Promise; }, getNextPageParam: (lastPage) => lastPage.nextCursor ?? undefined, - initialPageParam: undefined as { createdAt: string; id: number } | undefined, + initialPageParam: undefined as string | undefined, staleTime: 15000, refetchOnWindowFocus: false, }); diff --git a/src/app/[locale]/dashboard/availability/_components/endpoint-probe-history.tsx b/src/app/[locale]/dashboard/availability/_components/endpoint-probe-history.tsx index a695b7d01..1afbb04a4 100644 --- a/src/app/[locale]/dashboard/availability/_components/endpoint-probe-history.tsx +++ b/src/app/[locale]/dashboard/availability/_components/endpoint-probe-history.tsx @@ -5,12 +5,6 @@ import { Activity, CheckCircle2, Play, RefreshCw, XCircle } from "lucide-react"; import { useTimeZone, useTranslations } from "next-intl"; import { useCallback, useEffect, useState } from "react"; import { toast } from "sonner"; -import { - type DashboardProviderVendor, - getDashboardProviderEndpoints, - getDashboardProviderVendors, - probeProviderEndpoint, -} from "@/actions/provider-endpoints"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; @@ -29,6 +23,13 @@ import { TableHeader, TableRow, } from "@/components/ui/table"; +import { + type DashboardProviderVendor, + getDashboardProviderEndpoints, + getDashboardProviderVendors, + getProviderEndpointProbeLogs, + probeProviderEndpoint, +} from "@/lib/api-client/v1/actions/provider-endpoints"; import { cn } from "@/lib/utils"; import { getErrorMessage } from "@/lib/utils/error-messages"; import type { ProviderEndpoint, ProviderEndpointProbeLog, ProviderType } from "@/types/provider"; @@ -94,14 +95,12 @@ export function EndpointProbeHistory() { setLoadingLogs(true); try { - const params = new URLSearchParams({ - endpointId: selectedEndpointId.toString(), - limit: "50", + const result = await getProviderEndpointProbeLogs({ + endpointId: selectedEndpointId, + limit: 50, }); - const res = await fetch(`/api/availability/endpoints/probe-logs?${params.toString()}`); - const data = await res.json(); - if (data.logs) { - setLogs(data.logs); + if (result.ok) { + setLogs(result.data.logs ?? []); } } catch (error) { console.error("Failed to fetch logs", error); diff --git a/src/app/[locale]/dashboard/availability/_components/endpoint/endpoint-tab.tsx b/src/app/[locale]/dashboard/availability/_components/endpoint/endpoint-tab.tsx index 0fbc65c17..4eb7d15bd 100644 --- a/src/app/[locale]/dashboard/availability/_components/endpoint/endpoint-tab.tsx +++ b/src/app/[locale]/dashboard/availability/_components/endpoint/endpoint-tab.tsx @@ -4,13 +4,6 @@ import { Radio } from "lucide-react"; import { useTranslations } from "next-intl"; import { useCallback, useEffect, useRef, useState } from "react"; import { toast } from "sonner"; -import { - type DashboardProviderVendor, - getDashboardProviderEndpoints, - getDashboardProviderVendors, - getProviderEndpointProbeLogs, - probeProviderEndpoint, -} from "@/actions/provider-endpoints"; import { Button } from "@/components/ui/button"; import { Select, @@ -20,6 +13,13 @@ import { SelectValue, } from "@/components/ui/select"; import { Skeleton } from "@/components/ui/skeleton"; +import { + type DashboardProviderVendor, + getDashboardProviderEndpoints, + getDashboardProviderVendors, + getProviderEndpointProbeLogs, + probeProviderEndpoint, +} from "@/lib/api-client/v1/actions/provider-endpoints"; import { cn } from "@/lib/utils"; import type { ProviderEndpoint, ProviderEndpointProbeLog, ProviderType } from "@/types/provider"; import { LatencyCurve } from "./latency-curve"; diff --git a/src/app/[locale]/dashboard/leaderboard/_components/leaderboard-view.tsx b/src/app/[locale]/dashboard/leaderboard/_components/leaderboard-view.tsx index b69678271..0062129ae 100644 --- a/src/app/[locale]/dashboard/leaderboard/_components/leaderboard-view.tsx +++ b/src/app/[locale]/dashboard/leaderboard/_components/leaderboard-view.tsx @@ -3,7 +3,6 @@ import { useSearchParams } from "next/navigation"; import { useTranslations } from "next-intl"; import { useCallback, useEffect, useState } from "react"; -import { getAllUserKeyGroups, getAllUserTags } from "@/actions/users"; import { LeaderboardPrimaryTabs } from "@/app/[locale]/dashboard/leaderboard/_components/leaderboard-primary-tabs"; import { LeaderboardSecondaryTabs } from "@/app/[locale]/dashboard/leaderboard/_components/leaderboard-secondary-tabs"; import { @@ -23,6 +22,7 @@ import { Card, CardContent } from "@/components/ui/card"; import { Skeleton } from "@/components/ui/skeleton"; import { TagInput } from "@/components/ui/tag-input"; import { Link } from "@/i18n/routing"; +import { getAllUserKeyGroups, getAllUserTags } from "@/lib/api-client/v1/actions/users"; import { formatTokenAmount } from "@/lib/utils"; import type { DateRangeParams, diff --git a/src/app/[locale]/dashboard/leaderboard/user/[userId]/_components/filters/user-insights-filter-bar.tsx b/src/app/[locale]/dashboard/leaderboard/user/[userId]/_components/filters/user-insights-filter-bar.tsx index 943547026..a91387ba1 100644 --- a/src/app/[locale]/dashboard/leaderboard/user/[userId]/_components/filters/user-insights-filter-bar.tsx +++ b/src/app/[locale]/dashboard/leaderboard/user/[userId]/_components/filters/user-insights-filter-bar.tsx @@ -3,8 +3,6 @@ import { useQuery } from "@tanstack/react-query"; import { Filter, Key, Server } from "lucide-react"; import { useTranslations } from "next-intl"; -import { getKeys } from "@/actions/keys"; -import { getProviders } from "@/actions/providers"; import { useLazyModels } from "@/app/[locale]/dashboard/logs/_hooks/use-lazy-filter-options"; import { Button } from "@/components/ui/button"; import { @@ -14,6 +12,8 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; +import { getKeys } from "@/lib/api-client/v1/actions/keys"; +import { getProviders } from "@/lib/api-client/v1/actions/providers"; import type { TimeRangePreset, UserInsightsFilters } from "./types"; interface UserInsightsFilterBarProps { diff --git a/src/app/[locale]/dashboard/leaderboard/user/[userId]/_components/user-key-trend-chart.tsx b/src/app/[locale]/dashboard/leaderboard/user/[userId]/_components/user-key-trend-chart.tsx index a1f61131e..c2f0dc0fd 100644 --- a/src/app/[locale]/dashboard/leaderboard/user/[userId]/_components/user-key-trend-chart.tsx +++ b/src/app/[locale]/dashboard/leaderboard/user/[userId]/_components/user-key-trend-chart.tsx @@ -5,10 +5,10 @@ import { AlertCircle } from "lucide-react"; import { useTranslations } from "next-intl"; import { useMemo } from "react"; import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from "recharts"; -import { getUserInsightsKeyTrend } from "@/actions/admin-user-insights"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { type ChartConfig, ChartContainer, ChartTooltip } from "@/components/ui/chart"; import { Skeleton } from "@/components/ui/skeleton"; +import { getUserInsightsKeyTrend } from "@/lib/api-client/v1/actions/admin-user-insights"; import type { DatabaseKeyStatRow } from "@/types/statistics"; import type { TimeRangePreset } from "./filters/types"; diff --git a/src/app/[locale]/dashboard/leaderboard/user/[userId]/_components/user-model-breakdown.tsx b/src/app/[locale]/dashboard/leaderboard/user/[userId]/_components/user-model-breakdown.tsx index 477d9e2c5..eb0312070 100644 --- a/src/app/[locale]/dashboard/leaderboard/user/[userId]/_components/user-model-breakdown.tsx +++ b/src/app/[locale]/dashboard/leaderboard/user/[userId]/_components/user-model-breakdown.tsx @@ -3,7 +3,6 @@ import { useQuery } from "@tanstack/react-query"; import { AlertCircle, BarChart3 } from "lucide-react"; import { useTranslations } from "next-intl"; -import { getUserInsightsModelBreakdown } from "@/actions/admin-user-insights"; import { ModelBreakdownColumn, type ModelBreakdownItem, @@ -11,6 +10,7 @@ import { } from "@/components/analytics/model-breakdown-column"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Skeleton } from "@/components/ui/skeleton"; +import { getUserInsightsModelBreakdown } from "@/lib/api-client/v1/actions/admin-user-insights"; import type { CurrencyCode } from "@/lib/utils/currency"; interface UserModelBreakdownProps { diff --git a/src/app/[locale]/dashboard/leaderboard/user/[userId]/_components/user-overview-cards.tsx b/src/app/[locale]/dashboard/leaderboard/user/[userId]/_components/user-overview-cards.tsx index 9c7b9f16a..ec29dc976 100644 --- a/src/app/[locale]/dashboard/leaderboard/user/[userId]/_components/user-overview-cards.tsx +++ b/src/app/[locale]/dashboard/leaderboard/user/[userId]/_components/user-overview-cards.tsx @@ -3,9 +3,9 @@ import { useQuery } from "@tanstack/react-query"; import { Activity, AlertCircle, Clock, DollarSign, TrendingUp } from "lucide-react"; import { useTranslations } from "next-intl"; -import { getUserInsightsOverview } from "@/actions/admin-user-insights"; import { Card, CardContent } from "@/components/ui/card"; import { Skeleton } from "@/components/ui/skeleton"; +import { getUserInsightsOverview } from "@/lib/api-client/v1/actions/admin-user-insights"; import { type CurrencyCode, formatCurrency } from "@/lib/utils"; interface UserOverviewCardsProps { diff --git a/src/app/[locale]/dashboard/leaderboard/user/[userId]/_components/user-provider-breakdown.tsx b/src/app/[locale]/dashboard/leaderboard/user/[userId]/_components/user-provider-breakdown.tsx index 5c046a763..5d2947889 100644 --- a/src/app/[locale]/dashboard/leaderboard/user/[userId]/_components/user-provider-breakdown.tsx +++ b/src/app/[locale]/dashboard/leaderboard/user/[userId]/_components/user-provider-breakdown.tsx @@ -3,7 +3,6 @@ import { useQuery } from "@tanstack/react-query"; import { AlertCircle, Server } from "lucide-react"; import { useTranslations } from "next-intl"; -import { getUserInsightsProviderBreakdown } from "@/actions/admin-user-insights"; import { ModelBreakdownColumn, type ModelBreakdownItem, @@ -11,6 +10,7 @@ import { } from "@/components/analytics/model-breakdown-column"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Skeleton } from "@/components/ui/skeleton"; +import { getUserInsightsProviderBreakdown } from "@/lib/api-client/v1/actions/admin-user-insights"; import type { CurrencyCode } from "@/lib/utils/currency"; interface UserProviderBreakdownProps { diff --git a/src/app/[locale]/dashboard/logs/_components/error-details-dialog/components/LogicTraceTab.tsx b/src/app/[locale]/dashboard/logs/_components/error-details-dialog/components/LogicTraceTab.tsx index a2fccc2e2..c4abd1002 100644 --- a/src/app/[locale]/dashboard/logs/_components/error-details-dialog/components/LogicTraceTab.tsx +++ b/src/app/[locale]/dashboard/logs/_components/error-details-dialog/components/LogicTraceTab.tsx @@ -20,9 +20,9 @@ import { } from "lucide-react"; import { useTranslations } from "next-intl"; import { useState } from "react"; -import { getSessionOriginChain } from "@/actions/session-origin-chain"; import { Badge } from "@/components/ui/badge"; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; +import { getSessionOriginChain } from "@/lib/api-client/v1/actions/session-origin-chain"; import { cn } from "@/lib/utils"; import { formatProbability, formatProviderTimeline } from "@/lib/utils/provider-chain-formatter"; import type { ProviderChainItem } from "@/types/message"; diff --git a/src/app/[locale]/dashboard/logs/_components/error-details-dialog/index.tsx b/src/app/[locale]/dashboard/logs/_components/error-details-dialog/index.tsx index 866e8706a..0c3666f8c 100644 --- a/src/app/[locale]/dashboard/logs/_components/error-details-dialog/index.tsx +++ b/src/app/[locale]/dashboard/logs/_components/error-details-dialog/index.tsx @@ -3,11 +3,11 @@ import { FileText, Gauge, GitBranch } from "lucide-react"; import { useTranslations } from "next-intl"; import { useCallback, useEffect, useRef, useState } from "react"; -import { hasSessionMessages } from "@/actions/active-sessions"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { hasSessionMessages } from "@/lib/api-client/v1/actions/active-sessions"; import { cn } from "@/lib/utils"; import type { StoredCostBreakdown } from "@/types/cost-breakdown"; import type { ProviderChainItem } from "@/types/message"; diff --git a/src/app/[locale]/dashboard/logs/_components/filters/identity-filters.tsx b/src/app/[locale]/dashboard/logs/_components/filters/identity-filters.tsx index 2887ac65e..48a6eb718 100644 --- a/src/app/[locale]/dashboard/logs/_components/filters/identity-filters.tsx +++ b/src/app/[locale]/dashboard/logs/_components/filters/identity-filters.tsx @@ -4,8 +4,6 @@ import { Check, ChevronsUpDown } from "lucide-react"; import { useTranslations } from "next-intl"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { toast } from "sonner"; -import { getKeys } from "@/actions/keys"; -import { searchUsersForFilter } from "@/actions/users"; import { Button } from "@/components/ui/button"; import { Command, @@ -24,6 +22,8 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; +import { getKeys } from "@/lib/api-client/v1/actions/keys"; +import { searchUsersForFilter } from "@/lib/api-client/v1/actions/users"; import { useDebounce } from "@/lib/hooks/use-debounce"; import type { Key } from "@/types/key"; import type { UsageLogFilters } from "./types"; diff --git a/src/app/[locale]/dashboard/logs/_components/filters/request-filters.tsx b/src/app/[locale]/dashboard/logs/_components/filters/request-filters.tsx index dd53d0d4d..6af778544 100644 --- a/src/app/[locale]/dashboard/logs/_components/filters/request-filters.tsx +++ b/src/app/[locale]/dashboard/logs/_components/filters/request-filters.tsx @@ -3,7 +3,6 @@ import { Check, ChevronsUpDown } from "lucide-react"; import { useTranslations } from "next-intl"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; -import { getUsageLogSessionIdSuggestions } from "@/actions/usage-logs"; import { Button } from "@/components/ui/button"; import { Command, @@ -23,6 +22,7 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; +import { getUsageLogSessionIdSuggestions } from "@/lib/api-client/v1/actions/usage-logs"; import { SESSION_ID_SUGGESTION_MIN_LEN } from "@/lib/constants/usage-logs.constants"; import { useDebounce } from "@/lib/hooks/use-debounce"; import type { ProviderDisplay } from "@/types/provider"; diff --git a/src/app/[locale]/dashboard/logs/_components/usage-logs-filters.tsx b/src/app/[locale]/dashboard/logs/_components/usage-logs-filters.tsx index 9f1e70f89..dd515b41a 100644 --- a/src/app/[locale]/dashboard/logs/_components/usage-logs-filters.tsx +++ b/src/app/[locale]/dashboard/logs/_components/usage-logs-filters.tsx @@ -5,14 +5,14 @@ import { Clock, Download, Network, Server, User } from "lucide-react"; import { useTranslations } from "next-intl"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { toast } from "sonner"; +import { Button } from "@/components/ui/button"; +import { Progress } from "@/components/ui/progress"; import { downloadUsageLogsExport, getUsageLogsExportStatus, startUsageLogsExport, type UsageLogsExportStatus, -} from "@/actions/usage-logs"; -import { Button } from "@/components/ui/button"; -import { Progress } from "@/components/ui/progress"; +} from "@/lib/api-client/v1/actions/usage-logs"; import { getErrorMessage } from "@/lib/utils/error-messages"; import type { Key } from "@/types/key"; import type { ProviderDisplay } from "@/types/provider"; diff --git a/src/app/[locale]/dashboard/logs/_components/usage-logs-stats-panel.tsx b/src/app/[locale]/dashboard/logs/_components/usage-logs-stats-panel.tsx index b5d7d63f9..d03b7a3a1 100644 --- a/src/app/[locale]/dashboard/logs/_components/usage-logs-stats-panel.tsx +++ b/src/app/[locale]/dashboard/logs/_components/usage-logs-stats-panel.tsx @@ -3,8 +3,8 @@ import { BarChart3 } from "lucide-react"; import { useTranslations } from "next-intl"; import { useCallback, useEffect, useState } from "react"; -import { getUsageLogsStats } from "@/actions/usage-logs"; import { Skeleton } from "@/components/ui/skeleton"; +import { getUsageLogsStats } from "@/lib/api-client/v1/actions/usage-logs"; import { cn, formatTokenAmount } from "@/lib/utils"; import type { CurrencyCode } from "@/lib/utils/currency"; import { formatCurrency } from "@/lib/utils/currency"; diff --git a/src/app/[locale]/dashboard/logs/_components/usage-logs-view-virtualized.tsx b/src/app/[locale]/dashboard/logs/_components/usage-logs-view-virtualized.tsx index 9035499e8..8485b0b7d 100644 --- a/src/app/[locale]/dashboard/logs/_components/usage-logs-view-virtualized.tsx +++ b/src/app/[locale]/dashboard/logs/_components/usage-logs-view-virtualized.tsx @@ -6,16 +6,17 @@ import { useSearchParams } from "next/navigation"; import { useLocale, useTranslations } from "next-intl"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { toast } from "sonner"; -import { getKeys } from "@/actions/keys"; -import type { OverviewData } from "@/actions/overview"; -import { getOverviewData } from "@/actions/overview"; -import { getProviders } from "@/actions/providers"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; import { Switch } from "@/components/ui/switch"; import { useFullscreen } from "@/hooks/use-fullscreen"; import { useRouter } from "@/i18n/routing"; +import { getKeys } from "@/lib/api-client/v1/actions/keys"; +import type { OverviewData } from "@/lib/api-client/v1/actions/overview"; +import { getOverviewData } from "@/lib/api-client/v1/actions/overview"; +import { getProviders } from "@/lib/api-client/v1/actions/providers"; +import { getSystemSettings } from "@/lib/api-client/v1/actions/system-config"; import { getHiddenColumns, type LogsTableColumn } from "@/lib/column-visibility"; import { cn } from "@/lib/utils"; import type { CurrencyCode } from "@/lib/utils/currency"; @@ -44,14 +45,6 @@ interface UsageLogsViewVirtualizedProps { logsRefreshIntervalMs?: number; } -async function fetchSystemSettings(): Promise { - const response = await fetch("/api/system-settings"); - if (!response.ok) { - throw new Error("FETCH_SETTINGS_FAILED"); - } - return response.json() as Promise; -} - async function fetchOverviewData(): Promise { const result = await getOverviewData(); if (!result.ok) { @@ -132,7 +125,7 @@ function UsageLogsViewContent({ const shouldFetchSettings = !currencyCode || !billingModelSource; const { data: systemSettings } = useQuery({ queryKey: ["system-settings"], - queryFn: fetchSystemSettings, + queryFn: getSystemSettings, enabled: shouldFetchSettings || isFullscreenOpen, }); diff --git a/src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.tsx b/src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.tsx index 3ca467f64..3ac76edd7 100644 --- a/src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.tsx +++ b/src/app/[locale]/dashboard/logs/_components/virtualized-logs-table.tsx @@ -14,8 +14,6 @@ import { useState, } from "react"; import { toast } from "sonner"; -import type { ActionResult } from "@/actions/types"; -import { getUsageLogsBatch } from "@/actions/usage-logs"; import { IpDetailsDialog } from "@/app/[locale]/dashboard/_components/ip-details-dialog"; import { IpDisplayTrigger } from "@/app/[locale]/dashboard/_components/ip-display-trigger"; import { Badge } from "@/components/ui/badge"; @@ -24,6 +22,8 @@ import { RelativeTime } from "@/components/ui/relative-time"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; import type { IpGeoLookupMode } from "@/hooks/use-ip-geo"; import { useVirtualizedInfiniteList } from "@/hooks/use-virtualized-infinite-list"; +import type { ActionResult } from "@/lib/api-client/v1/actions/types"; +import { getUsageLogsBatch } from "@/lib/api-client/v1/actions/usage-logs"; import type { LogsTableColumn } from "@/lib/column-visibility"; import { cn, formatTokenAmount } from "@/lib/utils"; import { copyTextToClipboard } from "@/lib/utils/clipboard"; diff --git a/src/app/[locale]/dashboard/logs/_hooks/use-lazy-filter-options.ts b/src/app/[locale]/dashboard/logs/_hooks/use-lazy-filter-options.ts index 64dfbc811..b9e2cce80 100644 --- a/src/app/[locale]/dashboard/logs/_hooks/use-lazy-filter-options.ts +++ b/src/app/[locale]/dashboard/logs/_hooks/use-lazy-filter-options.ts @@ -1,8 +1,12 @@ "use client"; import { useCallback, useEffect, useRef, useState } from "react"; -import type { ActionResult } from "@/actions/types"; -import { getEndpointList, getModelList, getStatusCodeList } from "@/actions/usage-logs"; +import type { ActionResult } from "@/lib/api-client/v1/actions/types"; +import { + getEndpointList, + getModelList, + getStatusCodeList, +} from "@/lib/api-client/v1/actions/usage-logs"; /** * 惰性加载 Hook 返回类型 diff --git a/src/app/[locale]/dashboard/quotas/keys/_components/edit-key-quota-dialog.tsx b/src/app/[locale]/dashboard/quotas/keys/_components/edit-key-quota-dialog.tsx index aa76db359..202c5d34b 100644 --- a/src/app/[locale]/dashboard/quotas/keys/_components/edit-key-quota-dialog.tsx +++ b/src/app/[locale]/dashboard/quotas/keys/_components/edit-key-quota-dialog.tsx @@ -5,7 +5,6 @@ import { useRouter } from "next/navigation"; import { useTranslations } from "next-intl"; import { useState, useTransition } from "react"; import { toast } from "sonner"; -import { editKey } from "@/actions/keys"; import { Button } from "@/components/ui/button"; import { Dialog, @@ -25,6 +24,7 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; +import { editKey } from "@/lib/api-client/v1/actions/keys"; import { CURRENCY_CONFIG, type CurrencyCode } from "@/lib/utils/currency"; interface KeyQuota { diff --git a/src/app/[locale]/dashboard/quotas/keys/_components/edit-user-quota-dialog.tsx b/src/app/[locale]/dashboard/quotas/keys/_components/edit-user-quota-dialog.tsx index 3967c2f97..a91cb398b 100644 --- a/src/app/[locale]/dashboard/quotas/keys/_components/edit-user-quota-dialog.tsx +++ b/src/app/[locale]/dashboard/quotas/keys/_components/edit-user-quota-dialog.tsx @@ -5,7 +5,6 @@ import { useRouter } from "next/navigation"; import { useTranslations } from "next-intl"; import { useState, useTransition } from "react"; import { toast } from "sonner"; -import { editUser } from "@/actions/users"; import { Button } from "@/components/ui/button"; import { Dialog, @@ -18,6 +17,7 @@ import { } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; +import { editUser } from "@/lib/api-client/v1/actions/users"; import { CURRENCY_CONFIG, type CurrencyCode } from "@/lib/utils/currency"; interface UserQuota { diff --git a/src/app/[locale]/dashboard/quotas/keys/_components/keys-quota-client.tsx b/src/app/[locale]/dashboard/quotas/keys/_components/keys-quota-client.tsx index 486608fee..7398f77b9 100644 --- a/src/app/[locale]/dashboard/quotas/keys/_components/keys-quota-client.tsx +++ b/src/app/[locale]/dashboard/quotas/keys/_components/keys-quota-client.tsx @@ -5,7 +5,6 @@ import { useRouter } from "next/navigation"; import { useTranslations } from "next-intl"; import { useCallback, useState } from "react"; import { toast } from "sonner"; -import { type PatchKeyLimitField, patchKeyLimit } from "@/actions/keys"; import { QuotaCountdownCompact } from "@/components/quota/quota-countdown"; import { QuotaProgress } from "@/components/quota/quota-progress"; import { QuotaQuickEditPopover } from "@/components/quota/quota-quick-edit-popover"; @@ -23,6 +22,7 @@ import { TableHeader, TableRow, } from "@/components/ui/table"; +import { type PatchKeyLimitField, patchKeyLimit } from "@/lib/api-client/v1/actions/keys"; import type { CurrencyCode } from "@/lib/utils/currency"; import { formatCurrency } from "@/lib/utils/currency"; import { getUsageRate, hasKeyQuotaSet, isUserExceeded } from "@/lib/utils/quota-helpers"; diff --git a/src/app/[locale]/dashboard/rate-limits/_components/rate-limit-dashboard.tsx b/src/app/[locale]/dashboard/rate-limits/_components/rate-limit-dashboard.tsx index f7574af9c..7499808f6 100644 --- a/src/app/[locale]/dashboard/rate-limits/_components/rate-limit-dashboard.tsx +++ b/src/app/[locale]/dashboard/rate-limits/_components/rate-limit-dashboard.tsx @@ -3,7 +3,7 @@ import { Loader2 } from "lucide-react"; import { useTranslations } from "next-intl"; import * as React from "react"; -import { getRateLimitStats } from "@/actions/rate-limit-stats"; +import { getRateLimitStats } from "@/lib/api-client/v1/actions/rate-limit-stats"; import type { CurrencyCode } from "@/lib/utils"; import type { RateLimitEventFilters, RateLimitEventStats } from "@/types/statistics"; import { RateLimitEventsChart } from "../../_components/rate-limit-events-chart"; diff --git a/src/app/[locale]/dashboard/rate-limits/_components/rate-limit-filters.tsx b/src/app/[locale]/dashboard/rate-limits/_components/rate-limit-filters.tsx index ad84a931c..11873318e 100644 --- a/src/app/[locale]/dashboard/rate-limits/_components/rate-limit-filters.tsx +++ b/src/app/[locale]/dashboard/rate-limits/_components/rate-limit-filters.tsx @@ -4,8 +4,6 @@ import { format } from "date-fns"; import { Calendar, X } from "lucide-react"; import { useTranslations } from "next-intl"; import * as React from "react"; -import { getProviders } from "@/actions/providers"; -import { searchUsers } from "@/actions/users"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; @@ -16,6 +14,8 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; +import { getProviders } from "@/lib/api-client/v1/actions/providers"; +import { searchUsers } from "@/lib/api-client/v1/actions/users"; import type { RateLimitEventFilters, RateLimitType } from "@/types/statistics"; export interface RateLimitFiltersProps { diff --git a/src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/request-list-sidebar.tsx b/src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/request-list-sidebar.tsx index 0d040d3d2..3eddefc61 100644 --- a/src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/request-list-sidebar.tsx +++ b/src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/request-list-sidebar.tsx @@ -11,11 +11,11 @@ import { } from "lucide-react"; import { useTimeZone, useTranslations } from "next-intl"; import { useCallback, useEffect, useState } from "react"; -import { getSessionRequests } from "@/actions/active-sessions"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Skeleton } from "@/components/ui/skeleton"; +import { getSessionRequests } from "@/lib/api-client/v1/actions/active-sessions"; import { cn } from "@/lib/utils"; interface RequestItem { @@ -165,7 +165,7 @@ export function RequestListSidebar({
diff --git a/src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/session-messages-client-actions.test.tsx b/src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/session-messages-client-actions.test.tsx index c476c168f..2b40f8de6 100644 --- a/src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/session-messages-client-actions.test.tsx +++ b/src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/session-messages-client-actions.test.tsx @@ -123,7 +123,7 @@ function createSnapshots(): SessionDetailSnapshots { defaultView: DEFAULT_SESSION_DETAIL_VIEW_MODE, request: { before: { - body: { model: "gpt-5.2", input: "before" }, + body: { model: "gpt-5.4", input: "before" }, messages: { role: "user", content: "before" }, headers: { "x-before": "1" }, meta: { @@ -133,7 +133,7 @@ function createSnapshots(): SessionDetailSnapshots { }, }, after: { - body: { model: "gpt-5.2", input: "after" }, + body: { model: "gpt-5.4", input: "after" }, messages: { role: "user", content: "after" }, headers: { "x-after": "1" }, meta: { @@ -174,7 +174,7 @@ function buildDetailsData( }> = {} ) { return { - requestBody: { model: "gpt-5.2", input: "legacy" }, + requestBody: { model: "gpt-5.4", input: "legacy" }, messages: { role: "user", content: "legacy" }, response: '{"legacy":true}', requestHeaders: { "x-legacy": "1" }, @@ -322,7 +322,7 @@ describe("SessionMessagesClient (request export actions)", () => { lastRequestAt: "2026-01-01T00:01:00.000Z", totalDurationMs: 1500, providers: [{ id: 1, name: "p1" }], - models: ["gpt-5.2"], + models: ["gpt-5.4"], totalInputTokens: 10, totalOutputTokens: 20, totalCacheCreationTokens: 30, @@ -589,7 +589,7 @@ describe("SessionMessagesClient (request export actions)", () => { lastRequestAt: "2026-01-01T00:01:00.000Z", totalDurationMs: 1500, providers: [{ id: 1, name: "p1" }], - models: ["gpt-5.2"], + models: ["gpt-5.4"], totalInputTokens: 10, totalOutputTokens: 20, totalCacheCreationTokens: 30, diff --git a/src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/session-messages-client.test.tsx b/src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/session-messages-client.test.tsx index 1342544e1..edbda27c7 100644 --- a/src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/session-messages-client.test.tsx +++ b/src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/session-messages-client.test.tsx @@ -55,7 +55,7 @@ function createSnapshots(): SessionDetailSnapshots { defaultView: DEFAULT_SESSION_DETAIL_VIEW_MODE, request: { before: { - body: { model: "gpt-5.2", instructions: "before body" }, + body: { model: "gpt-5.4", instructions: "before body" }, messages: { role: "user", content: "before hi" }, headers: { "x-before-request": "1" }, meta: { @@ -65,7 +65,7 @@ function createSnapshots(): SessionDetailSnapshots { }, }, after: { - body: { model: "gpt-5.2", instructions: "after body" }, + body: { model: "gpt-5.4", instructions: "after body" }, messages: { role: "user", content: "after hi" }, headers: { "x-after-request": "1" }, meta: { @@ -196,7 +196,10 @@ describe("SessionMessagesDetailsTabs", () => { if (!snapshots.request.after) { throw new Error("after snapshot missing"); } - snapshots.request.after.body = { model: "gpt-5.2", input: [{ role: "user", content: "hi" }] }; + snapshots.request.after.body = { + model: "gpt-5.4", + input: [{ role: "user", content: "hi" }], + }; snapshots.request.after.messages = null; const { container, unmount } = renderWithIntl( diff --git a/src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/session-messages-client.tsx b/src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/session-messages-client.tsx index 2fb74a4a1..c1b5058c5 100644 --- a/src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/session-messages-client.tsx +++ b/src/app/[locale]/dashboard/sessions/[sessionId]/messages/_components/session-messages-client.tsx @@ -16,7 +16,6 @@ import { useParams, useSearchParams } from "next/navigation"; import { useTranslations } from "next-intl"; import { useCallback, useEffect, useState } from "react"; import { toast } from "sonner"; -import { getSessionDetails, terminateActiveSession } from "@/actions/active-sessions"; import { AlertDialog, AlertDialogAction, @@ -38,7 +37,11 @@ import { import { Sheet, SheetContent, SheetHeader, SheetTitle } from "@/components/ui/sheet"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; import { usePathname, useRouter } from "@/i18n/routing"; -import type { CurrencyCode } from "@/lib/utils/currency"; +import { + getSessionDetails, + terminateActiveSession, +} from "@/lib/api-client/v1/actions/active-sessions"; +import { getSystemSettings } from "@/lib/api-client/v1/actions/system-config"; import { DEFAULT_SESSION_DETAIL_VIEW_MODE, type SessionDetailSnapshots, @@ -49,16 +52,6 @@ import { SessionMessagesDetailsTabs } from "./session-details-tabs"; import { hasSnapshotData } from "./session-messages-guards"; import { SessionStats } from "./session-stats"; -async function fetchSystemSettings(): Promise<{ - currencyDisplay: CurrencyCode; -}> { - const response = await fetch("/api/system-settings"); - if (!response.ok) { - throw new Error("Failed to fetch system settings"); - } - return response.json(); -} - export function SessionMessagesClient() { const t = useTranslations("dashboard.sessions"); @@ -117,7 +110,7 @@ export function SessionMessagesClient() { const { data: systemSettings } = useQuery({ queryKey: ["system-settings"], - queryFn: fetchSystemSettings, + queryFn: getSystemSettings, }); const currencyCode = systemSettings?.currencyDisplay || "USD"; diff --git a/src/app/[locale]/dashboard/sessions/_components/active-sessions-client.tsx b/src/app/[locale]/dashboard/sessions/_components/active-sessions-client.tsx index ed32d82dc..14dd12312 100644 --- a/src/app/[locale]/dashboard/sessions/_components/active-sessions-client.tsx +++ b/src/app/[locale]/dashboard/sessions/_components/active-sessions-client.tsx @@ -4,11 +4,11 @@ import { useQuery } from "@tanstack/react-query"; import { ArrowLeft, ChevronLeft, ChevronRight } from "lucide-react"; import { useTranslations } from "next-intl"; import { useState } from "react"; -import { getAllSessions } from "@/actions/active-sessions"; import { Section } from "@/components/section"; import { Button } from "@/components/ui/button"; import { useRouter } from "@/i18n/routing"; -import type { CurrencyCode } from "@/lib/utils/currency"; +import { getAllSessions } from "@/lib/api-client/v1/actions/active-sessions"; +import { getSystemSettings } from "@/lib/api-client/v1/actions/system-config"; import type { ActiveSessionInfo } from "@/types/session"; import { ActiveSessionsTable } from "./active-sessions-table"; @@ -49,13 +49,7 @@ export function ActiveSessionsClient() { const { data: systemSettings } = useQuery({ queryKey: ["system-settings"], - queryFn: async () => { - const response = await fetch("/api/system-settings"); - if (!response.ok) { - throw new Error("FETCH_SETTINGS_FAILED"); - } - return response.json() as Promise<{ currencyDisplay: CurrencyCode }>; - }, + queryFn: getSystemSettings, }); const activeSessions = data?.active || []; diff --git a/src/app/[locale]/dashboard/sessions/_components/active-sessions-table.tsx b/src/app/[locale]/dashboard/sessions/_components/active-sessions-table.tsx index ae4061eec..fd375c51b 100644 --- a/src/app/[locale]/dashboard/sessions/_components/active-sessions-table.tsx +++ b/src/app/[locale]/dashboard/sessions/_components/active-sessions-table.tsx @@ -4,7 +4,6 @@ import { Circle, Eye, XCircle } from "lucide-react"; import { useTranslations } from "next-intl"; import { useEffect, useMemo, useState } from "react"; import { toast } from "sonner"; -import { terminateActiveSession, terminateActiveSessionsBatch } from "@/actions/active-sessions"; import { AlertDialog, AlertDialogAction, @@ -29,6 +28,10 @@ import { } from "@/components/ui/table"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; import { Link } from "@/i18n/routing"; +import { + terminateActiveSession, + terminateActiveSessionsBatch, +} from "@/lib/api-client/v1/actions/active-sessions"; import { getSessionDisplayStatus, SESSION_DISPLAY_STATUS } from "@/lib/session-status"; import { cn } from "@/lib/utils"; import type { CurrencyCode } from "@/lib/utils/currency"; diff --git a/src/app/[locale]/dashboard/sessions/_components/session-messages-dialog.tsx b/src/app/[locale]/dashboard/sessions/_components/session-messages-dialog.tsx index d5cae7334..12916192c 100644 --- a/src/app/[locale]/dashboard/sessions/_components/session-messages-dialog.tsx +++ b/src/app/[locale]/dashboard/sessions/_components/session-messages-dialog.tsx @@ -3,7 +3,6 @@ import { Eye } from "lucide-react"; import { useTranslations } from "next-intl"; import { useState } from "react"; -import { getSessionMessages } from "@/actions/active-sessions"; import { Button } from "@/components/ui/button"; import { Dialog, @@ -13,6 +12,7 @@ import { DialogTitle, DialogTrigger, } from "@/components/ui/dialog"; +import { getSessionMessages } from "@/lib/api-client/v1/actions/active-sessions"; interface SessionMessagesDialogProps { sessionId: string; diff --git a/src/app/[locale]/dashboard/users/users-page-client.tsx b/src/app/[locale]/dashboard/users/users-page-client.tsx index 8b8cc8347..2673fc466 100644 --- a/src/app/[locale]/dashboard/users/users-page-client.tsx +++ b/src/app/[locale]/dashboard/users/users-page-client.tsx @@ -4,14 +4,6 @@ import { useInfiniteQuery, useQuery, useQueryClient } from "@tanstack/react-quer import { Layers, Loader2, Plus, Search, ShieldCheck } from "lucide-react"; import { useTranslations } from "next-intl"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; -import type { KeyUsageData } from "@/actions/users"; -import { - getAllUserKeyGroups, - getAllUserTags, - getUsers, - getUsersBatchCore, - getUsersUsageBatch, -} from "@/actions/users"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { @@ -23,10 +15,18 @@ import { } from "@/components/ui/select"; import { Skeleton } from "@/components/ui/skeleton"; import { TagInput } from "@/components/ui/tag-input"; +import { getSystemSettings } from "@/lib/api-client/v1/actions/system-config"; +import type { KeyUsageData } from "@/lib/api-client/v1/actions/users"; +import { + getAllUserKeyGroups, + getAllUserTags, + getUsers, + getUsersBatchCore, + getUsersUsageBatch, +} from "@/lib/api-client/v1/actions/users"; import { clearUsageCache } from "@/lib/dashboard/user-limit-usage-cache"; import { loadUserUsagePagesSequentially } from "@/lib/dashboard/user-usage-loader"; import { useDebounce } from "@/lib/hooks/use-debounce"; -import type { CurrencyCode } from "@/lib/utils/currency"; import { parseProviderGroups } from "@/lib/utils/provider-group"; import type { User, UserDisplay } from "@/types/user"; import { AddKeyDialog } from "../_components/user/add-key-dialog"; @@ -186,11 +186,7 @@ function UsersPageContent({ currentUser }: UsersPageClientProps) { // Fetch system settings for currency display const { data: systemSettings } = useQuery({ queryKey: ["system-settings"], - queryFn: async () => { - const response = await fetch("/api/system-settings"); - if (!response.ok) throw new Error("Failed to fetch settings"); - return response.json() as Promise<{ currencyDisplay: CurrencyCode }>; - }, + queryFn: getSystemSettings, staleTime: 30_000, }); @@ -382,7 +378,6 @@ function UsersPageContent({ currentUser }: UsersPageClientProps) { hasSearch && (key.name.toLowerCase().includes(normalizedTerm) || key.maskedKey.toLowerCase().includes(normalizedTerm) || - (key.fullKey || "").toLowerCase().includes(normalizedTerm) || (key.providerGroup || "").toLowerCase().includes(normalizedTerm)); const matchesKeyGroup = diff --git a/src/app/[locale]/internal/dashboard/big-screen/page.tsx b/src/app/[locale]/internal/dashboard/big-screen/page.tsx index 8366aa72b..655a62456 100644 --- a/src/app/[locale]/internal/dashboard/big-screen/page.tsx +++ b/src/app/[locale]/internal/dashboard/big-screen/page.tsx @@ -36,11 +36,12 @@ import { YAxis, } from "recharts"; import useSWR from "swr"; -import { getDashboardRealtimeData } from "@/actions/dashboard-realtime"; import { type Locale, localeLabels, locales } from "@/i18n/config"; import { normalizePathnameForLocaleNavigation } from "@/i18n/pathname"; import { usePathname, useRouter } from "@/i18n/routing"; -import { CURRENCY_CONFIG, type CurrencyCode } from "@/lib/utils/currency"; +import { getDashboardRealtimeData } from "@/lib/api-client/v1/actions/dashboard-realtime"; +import { getSystemSettings } from "@/lib/api-client/v1/actions/system-config"; +import { CURRENCY_CONFIG } from "@/lib/utils/currency"; /** * ============================================================================ @@ -756,15 +757,10 @@ export default function BigScreenPage() { ); // Fetch system settings for currency display - const { data: systemSettings } = useSWR( - "system-settings", - async () => { - const response = await fetch("/api/system-settings"); - if (!response.ok) throw new Error("Failed to fetch settings"); - return response.json() as Promise<{ currencyDisplay: CurrencyCode }>; - }, - { revalidateOnFocus: false, refreshWhenHidden: false } - ); + const { data: systemSettings } = useSWR("system-settings", getSystemSettings, { + revalidateOnFocus: false, + refreshWhenHidden: false, + }); const currencySymbol = CURRENCY_CONFIG[systemSettings?.currencyDisplay ?? "USD"]?.symbol ?? "$"; diff --git a/src/app/[locale]/my-usage/_components/collapsible-quota-card.tsx b/src/app/[locale]/my-usage/_components/collapsible-quota-card.tsx index 4d172d1fd..9225fe1af 100644 --- a/src/app/[locale]/my-usage/_components/collapsible-quota-card.tsx +++ b/src/app/[locale]/my-usage/_components/collapsible-quota-card.tsx @@ -3,8 +3,8 @@ import { AlertTriangle, ChevronDown, Infinity as InfinityIcon, PieChart } from "lucide-react"; import { useTranslations } from "next-intl"; import { useState } from "react"; -import type { MyUsageQuota } from "@/actions/my-usage"; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; +import type { MyUsageQuota } from "@/lib/api-client/v1/actions/my-usage"; import type { CurrencyCode } from "@/lib/utils"; import { cn } from "@/lib/utils"; import { calculateUsagePercent } from "@/lib/utils/limit-helpers"; diff --git a/src/app/[locale]/my-usage/_components/quota-cards.tsx b/src/app/[locale]/my-usage/_components/quota-cards.tsx index 9f7496fc9..a17870f4a 100644 --- a/src/app/[locale]/my-usage/_components/quota-cards.tsx +++ b/src/app/[locale]/my-usage/_components/quota-cards.tsx @@ -3,9 +3,9 @@ import { Infinity as InfinityIcon } from "lucide-react"; import { useTranslations } from "next-intl"; import { useMemo } from "react"; -import type { MyUsageQuota } from "@/actions/my-usage"; import { Progress } from "@/components/ui/progress"; import { Skeleton } from "@/components/ui/skeleton"; +import type { MyUsageQuota } from "@/lib/api-client/v1/actions/my-usage"; import type { CurrencyCode } from "@/lib/utils"; import { cn } from "@/lib/utils"; import { formatCurrency } from "@/lib/utils/currency"; diff --git a/src/app/[locale]/my-usage/_components/statistics-summary-card.tsx b/src/app/[locale]/my-usage/_components/statistics-summary-card.tsx index 16d3f1a64..f9999ccc3 100644 --- a/src/app/[locale]/my-usage/_components/statistics-summary-card.tsx +++ b/src/app/[locale]/my-usage/_components/statistics-summary-card.tsx @@ -4,12 +4,12 @@ import { format } from "date-fns"; import { BarChart3, ChevronLeft, ChevronRight, RefreshCw } from "lucide-react"; import { useTranslations } from "next-intl"; import { useCallback, useEffect, useRef, useState } from "react"; -import { getMyStatsSummary, type MyStatsSummary } from "@/actions/my-usage"; import { ModelBreakdownColumn } from "@/components/analytics/model-breakdown-column"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Separator } from "@/components/ui/separator"; import { Skeleton } from "@/components/ui/skeleton"; +import { getMyStatsSummary, type MyStatsSummary } from "@/lib/api-client/v1/actions/my-usage"; import { formatTokenAmount } from "@/lib/utils"; import { formatCurrency } from "@/lib/utils/currency"; import { LogsDateRangePicker } from "../../dashboard/logs/_components/logs-date-range-picker"; diff --git a/src/app/[locale]/my-usage/_components/usage-logs-section.tsx b/src/app/[locale]/my-usage/_components/usage-logs-section.tsx index 80158fe40..66a854518 100644 --- a/src/app/[locale]/my-usage/_components/usage-logs-section.tsx +++ b/src/app/[locale]/my-usage/_components/usage-logs-section.tsx @@ -3,12 +3,6 @@ import { ChevronDown, Filter, RefreshCw, ScrollText } from "lucide-react"; import { useTranslations } from "next-intl"; import { useCallback, useEffect, useMemo, useState } from "react"; -import { - getMyAvailableEndpoints, - getMyAvailableModels, - getMyUsageLogsBatchFull, - getMyUsageMetadata, -} from "@/actions/my-usage"; import { LogsDateRangePicker } from "@/app/[locale]/dashboard/logs/_components/logs-date-range-picker"; import { type LogsFetchFn, @@ -27,6 +21,12 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; +import { + getMyAvailableEndpoints, + getMyAvailableModels, + getMyUsageLogsBatchFull, + getMyUsageMetadata, +} from "@/lib/api-client/v1/actions/my-usage"; import type { LogsTableColumn } from "@/lib/column-visibility"; import { cn } from "@/lib/utils"; import type { CurrencyCode } from "@/lib/utils/currency"; diff --git a/src/app/[locale]/my-usage/_components/usage-logs-table.tsx b/src/app/[locale]/my-usage/_components/usage-logs-table.tsx index 3693742fc..a45ea6170 100644 --- a/src/app/[locale]/my-usage/_components/usage-logs-table.tsx +++ b/src/app/[locale]/my-usage/_components/usage-logs-table.tsx @@ -5,13 +5,13 @@ import { ArrowUp, Loader2 } from "lucide-react"; import { useTimeZone, useTranslations } from "next-intl"; import { useCallback, useEffect, useEffectEvent, useMemo, useRef } from "react"; import { toast } from "sonner"; -import type { MyUsageLogEntry } from "@/actions/my-usage"; import { ModelVendorIcon } from "@/components/customs/model-vendor-icon"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Skeleton } from "@/components/ui/skeleton"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; import { useVirtualizedInfiniteList } from "@/hooks/use-virtualized-infinite-list"; +import type { MyUsageLogEntry } from "@/lib/api-client/v1/actions/my-usage"; import { CURRENCY_CONFIG, type CurrencyCode } from "@/lib/utils"; import { copyTextToClipboard } from "@/lib/utils/clipboard"; diff --git a/src/app/[locale]/my-usage/page.tsx b/src/app/[locale]/my-usage/page.tsx index c88f0f729..ef3ad7ee5 100644 --- a/src/app/[locale]/my-usage/page.tsx +++ b/src/app/[locale]/my-usage/page.tsx @@ -1,9 +1,9 @@ "use client"; import { useCallback, useEffect, useState } from "react"; -import { getMyQuota, type MyUsageQuota } from "@/actions/my-usage"; -import { getServerTimeZone } from "@/actions/system-config"; import { useRouter } from "@/i18n/routing"; +import { getMyQuota, type MyUsageQuota } from "@/lib/api-client/v1/actions/my-usage"; +import { getServerTimeZone } from "@/lib/api-client/v1/actions/system-config"; import { CollapsibleQuotaCard } from "./_components/collapsible-quota-card"; import { ExpirationInfo } from "./_components/expiration-info"; import { MyUsageHeader } from "./_components/my-usage-header"; diff --git a/src/app/[locale]/settings/_lib/nav-items.ts b/src/app/[locale]/settings/_lib/nav-items.ts index 7171cf634..0d73167e0 100644 --- a/src/app/[locale]/settings/_lib/nav-items.ts +++ b/src/app/[locale]/settings/_lib/nav-items.ts @@ -85,7 +85,7 @@ export const SETTINGS_NAV_ITEMS: SettingsNavItem[] = [ iconName: "file-text", }, { - href: "/api/actions/scalar", + href: "/api/v1/scalar", labelKey: "nav.apiDocs", label: "API Docs", external: true, diff --git a/src/app/[locale]/settings/client-versions/_components/client-version-toggle.tsx b/src/app/[locale]/settings/client-versions/_components/client-version-toggle.tsx index 04613d3bf..6f1847cf6 100644 --- a/src/app/[locale]/settings/client-versions/_components/client-version-toggle.tsx +++ b/src/app/[locale]/settings/client-versions/_components/client-version-toggle.tsx @@ -4,7 +4,7 @@ import { AlertCircle, Shield, ShieldCheck } from "lucide-react"; import { useTranslations } from "next-intl"; import { useState, useTransition } from "react"; import { toast } from "sonner"; -import { saveSystemSettings } from "@/actions/system-config"; +import { saveSystemSettings } from "@/lib/api-client/v1/actions/system-config"; import { cn } from "@/lib/utils"; import { SettingsToggleRow } from "../../_components/ui/settings-ui"; diff --git a/src/app/[locale]/settings/config/_components/system-settings-form.tsx b/src/app/[locale]/settings/config/_components/system-settings-form.tsx index c60180fff..72423e3e8 100644 --- a/src/app/[locale]/settings/config/_components/system-settings-form.tsx +++ b/src/app/[locale]/settings/config/_components/system-settings-form.tsx @@ -5,6 +5,7 @@ import { ChevronDown, CircleHelp, Clock, + Coins, Eye, FileCode, Globe, @@ -20,7 +21,6 @@ import { useRouter } from "next/navigation"; import { useTranslations } from "next-intl"; import { useState, useTransition } from "react"; import { toast } from "sonner"; -import { saveSystemSettings } from "@/actions/system-config"; import { Button } from "@/components/ui/button"; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; import { InlineWarning } from "@/components/ui/inline-warning"; @@ -36,6 +36,7 @@ import { import { Switch } from "@/components/ui/switch"; import { Textarea } from "@/components/ui/textarea"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; +import { saveSystemSettings } from "@/lib/api-client/v1/actions/system-config"; import type { CurrencyCode } from "@/lib/utils"; import { CURRENCY_CONFIG } from "@/lib/utils"; import { COMMON_TIMEZONES, getTimezoneLabel } from "@/lib/utils/timezone"; @@ -49,6 +50,7 @@ import { DEFAULT_IP_EXTRACTION_CONFIG, type IpExtractionConfig } from "@/types/i import type { BillingModelSource, CodexPriorityBillingSource, + FakeStreamingWhitelistEntry, SystemSettings, } from "@/types/system-config"; @@ -60,10 +62,12 @@ interface SystemSettingsFormProps { | "currencyDisplay" | "billingModelSource" | "codexPriorityBillingSource" + | "billNonSuccessfulRequests" | "timezone" | "verboseProviderError" | "passThroughUpstreamErrorMessage" | "enableHttp2" + | "enableOpenaiResponsesWebsocket" | "enableHighConcurrencyMode" | "interceptAnthropicWarmupRequests" | "enableThinkingSignatureRectifier" @@ -71,6 +75,7 @@ interface SystemSettingsFormProps { | "enableResponseInputRectifier" | "enableThinkingBudgetRectifier" | "allowNonConversationEndpointProviderFallback" + | "fakeStreamingWhitelist" | "enableCodexSessionIdCompletion" | "enableClaudeMetadataUserIdInjection" | "enableResponseFixer" @@ -117,6 +122,9 @@ export function SystemSettingsForm({ initialSettings }: SystemSettingsFormProps) ); const [codexPriorityBillingSource, setCodexPriorityBillingSource] = useState(initialSettings.codexPriorityBillingSource); + const [billNonSuccessfulRequests, setBillNonSuccessfulRequests] = useState( + initialSettings.billNonSuccessfulRequests + ); const [timezone, setTimezone] = useState(initialSettings.timezone); const [verboseProviderError, setVerboseProviderError] = useState( initialSettings.verboseProviderError @@ -125,6 +133,9 @@ export function SystemSettingsForm({ initialSettings }: SystemSettingsFormProps) initialSettings.passThroughUpstreamErrorMessage ); const [enableHttp2, setEnableHttp2] = useState(initialSettings.enableHttp2); + const [enableOpenaiResponsesWebsocket, setEnableOpenaiResponsesWebsocket] = useState( + initialSettings.enableOpenaiResponsesWebsocket + ); const [enableHighConcurrencyMode, setEnableHighConcurrencyMode] = useState( initialSettings.enableHighConcurrencyMode ); @@ -144,6 +155,14 @@ export function SystemSettingsForm({ initialSettings }: SystemSettingsFormProps) allowNonConversationEndpointProviderFallback, setAllowNonConversationEndpointProviderFallback, ] = useState(initialSettings.allowNonConversationEndpointProviderFallback); + const [fakeStreamingWhitelist, setFakeStreamingWhitelist] = useState< + FakeStreamingWhitelistEntry[] + >(() => + (initialSettings.fakeStreamingWhitelist ?? []).map((entry) => ({ + model: entry.model, + groupTags: [...entry.groupTags], + })) + ); const [enableThinkingBudgetRectifier, setEnableThinkingBudgetRectifier] = useState( initialSettings.enableThinkingBudgetRectifier ); @@ -233,6 +252,46 @@ export function SystemSettingsForm({ initialSettings }: SystemSettingsFormProps) ipExtractionConfigToSave = parsed as IpExtractionConfig; } + const sanitizedFakeStreamingWhitelist: FakeStreamingWhitelistEntry[] = (() => { + // If the same model is listed multiple times, merge their groupTags + // (deduped, trimmed) instead of silently dropping later entries. The + // server-side schema rejects duplicates, so this aggregates client + // intent before submission. + // + // Empty groupTags means "all groups" — that is strictly broader than any + // explicit tag set, so once any entry for a model selects "all groups" + // the merged result must remain empty (do not narrow it by unioning in + // explicit tags from sibling rows). + const merged = new Map>(); + const allGroupsModels = new Set(); + const order: string[] = []; + for (const entry of fakeStreamingWhitelist) { + const model = entry.model.trim(); + if (!model) continue; + if (!merged.has(model)) { + merged.set(model, new Set()); + order.push(model); + } + if (entry.groupTags.length === 0) { + allGroupsModels.add(model); + continue; + } + if (allGroupsModels.has(model)) continue; + const groups = merged.get(model); + if (!groups) continue; + for (const tag of entry.groupTags) { + const trimmed = tag.trim(); + if (trimmed) groups.add(trimmed); + } + } + return order.map((model) => ({ + model, + groupTags: allGroupsModels.has(model) + ? [] + : Array.from(merged.get(model) ?? new Set()), + })); + })(); + startTransition(async () => { const result = await saveSystemSettings({ siteTitle, @@ -240,16 +299,19 @@ export function SystemSettingsForm({ initialSettings }: SystemSettingsFormProps) currencyDisplay, billingModelSource, codexPriorityBillingSource, + billNonSuccessfulRequests, timezone, verboseProviderError, passThroughUpstreamErrorMessage, enableHttp2, + enableOpenaiResponsesWebsocket, enableHighConcurrencyMode, interceptAnthropicWarmupRequests, enableThinkingSignatureRectifier, enableBillingHeaderRectifier, enableResponseInputRectifier, allowNonConversationEndpointProviderFallback, + fakeStreamingWhitelist: sanitizedFakeStreamingWhitelist, enableThinkingBudgetRectifier, enableCodexSessionIdCompletion, enableClaudeMetadataUserIdInjection, @@ -276,10 +338,12 @@ export function SystemSettingsForm({ initialSettings }: SystemSettingsFormProps) setCurrencyDisplay(result.data.currencyDisplay); setBillingModelSource(result.data.billingModelSource); setCodexPriorityBillingSource(result.data.codexPriorityBillingSource); + setBillNonSuccessfulRequests(result.data.billNonSuccessfulRequests); setTimezone(result.data.timezone); setVerboseProviderError(result.data.verboseProviderError); setPassThroughUpstreamErrorMessage(result.data.passThroughUpstreamErrorMessage); setEnableHttp2(result.data.enableHttp2); + setEnableOpenaiResponsesWebsocket(result.data.enableOpenaiResponsesWebsocket); setEnableHighConcurrencyMode(result.data.enableHighConcurrencyMode); setInterceptAnthropicWarmupRequests(result.data.interceptAnthropicWarmupRequests); setEnableThinkingSignatureRectifier(result.data.enableThinkingSignatureRectifier); @@ -288,6 +352,12 @@ export function SystemSettingsForm({ initialSettings }: SystemSettingsFormProps) setAllowNonConversationEndpointProviderFallback( result.data.allowNonConversationEndpointProviderFallback ); + setFakeStreamingWhitelist( + (result.data.fakeStreamingWhitelist ?? []).map((entry) => ({ + model: entry.model, + groupTags: [...entry.groupTags], + })) + ); setEnableThinkingBudgetRectifier(result.data.enableThinkingBudgetRectifier); setEnableCodexSessionIdCompletion(result.data.enableCodexSessionIdCompletion); setEnableClaudeMetadataUserIdInjection(result.data.enableClaudeMetadataUserIdInjection); @@ -474,6 +544,46 @@ export function SystemSettingsForm({ initialSettings }: SystemSettingsFormProps) />
+ {/* Bill Non-Successful Requests */} +
+
+
+ +
+
+
+

+ {t("billNonSuccessfulRequests")} +

+ + + + + + {t("billNonSuccessfulRequestsTooltip")} + + +
+

+ {t("billNonSuccessfulRequestsDesc")} +

+
+
+ setBillNonSuccessfulRequests(checked)} + disabled={isPending} + /> +
+ {/* Verbose Provider Error */}
diff --git a/src/app/[locale]/settings/config/page.tsx b/src/app/[locale]/settings/config/page.tsx index d139c43eb..03958076e 100644 --- a/src/app/[locale]/settings/config/page.tsx +++ b/src/app/[locale]/settings/config/page.tsx @@ -50,10 +50,12 @@ async function SettingsConfigContent({ locale }: { locale: string }) { currencyDisplay: settings.currencyDisplay, billingModelSource: settings.billingModelSource, codexPriorityBillingSource: settings.codexPriorityBillingSource, + billNonSuccessfulRequests: settings.billNonSuccessfulRequests, timezone: settings.timezone, verboseProviderError: settings.verboseProviderError, passThroughUpstreamErrorMessage: settings.passThroughUpstreamErrorMessage, enableHttp2: settings.enableHttp2, + enableOpenaiResponsesWebsocket: settings.enableOpenaiResponsesWebsocket, enableHighConcurrencyMode: settings.enableHighConcurrencyMode, interceptAnthropicWarmupRequests: settings.interceptAnthropicWarmupRequests, enableThinkingSignatureRectifier: settings.enableThinkingSignatureRectifier, @@ -62,6 +64,7 @@ async function SettingsConfigContent({ locale }: { locale: string }) { enableResponseInputRectifier: settings.enableResponseInputRectifier, allowNonConversationEndpointProviderFallback: settings.allowNonConversationEndpointProviderFallback, + fakeStreamingWhitelist: settings.fakeStreamingWhitelist, enableCodexSessionIdCompletion: settings.enableCodexSessionIdCompletion, enableClaudeMetadataUserIdInjection: settings.enableClaudeMetadataUserIdInjection, enableResponseFixer: settings.enableResponseFixer, diff --git a/src/app/[locale]/settings/error-rules/_components/add-rule-dialog.tsx b/src/app/[locale]/settings/error-rules/_components/add-rule-dialog.tsx index f7d29b68a..f10b6dad3 100644 --- a/src/app/[locale]/settings/error-rules/_components/add-rule-dialog.tsx +++ b/src/app/[locale]/settings/error-rules/_components/add-rule-dialog.tsx @@ -4,7 +4,6 @@ import { Plus } from "lucide-react"; import { useTranslations } from "next-intl"; import { useState } from "react"; import { toast } from "sonner"; -import { createErrorRuleAction } from "@/actions/error-rules"; import { Button } from "@/components/ui/button"; import { Dialog, @@ -23,6 +22,7 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; +import { createErrorRuleAction } from "@/lib/api-client/v1/actions/error-rules"; import { cn } from "@/lib/utils"; import type { ErrorOverrideResponse } from "@/repository/error-rules"; import { OverrideSection } from "./override-section"; diff --git a/src/app/[locale]/settings/error-rules/_components/edit-rule-dialog.tsx b/src/app/[locale]/settings/error-rules/_components/edit-rule-dialog.tsx index 0fa148454..05b03d083 100644 --- a/src/app/[locale]/settings/error-rules/_components/edit-rule-dialog.tsx +++ b/src/app/[locale]/settings/error-rules/_components/edit-rule-dialog.tsx @@ -3,7 +3,6 @@ import { useTranslations } from "next-intl"; import { useEffect, useState } from "react"; import { toast } from "sonner"; -import { updateErrorRuleAction } from "@/actions/error-rules"; import { Button } from "@/components/ui/button"; import { Dialog, @@ -21,6 +20,7 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; +import { updateErrorRuleAction } from "@/lib/api-client/v1/actions/error-rules"; import { cn } from "@/lib/utils"; import type { ErrorOverrideResponse, ErrorRule } from "@/repository/error-rules"; import { OverrideSection } from "./override-section"; diff --git a/src/app/[locale]/settings/error-rules/_components/error-rule-tester.tsx b/src/app/[locale]/settings/error-rules/_components/error-rule-tester.tsx index 6f8b5f52e..872f29502 100644 --- a/src/app/[locale]/settings/error-rules/_components/error-rule-tester.tsx +++ b/src/app/[locale]/settings/error-rules/_components/error-rule-tester.tsx @@ -4,10 +4,10 @@ import { AlertTriangle, CheckCircle2, Loader2, XCircle } from "lucide-react"; import { useTranslations } from "next-intl"; import { useState } from "react"; import { toast } from "sonner"; -import { testErrorRuleAction } from "@/actions/error-rules"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Label } from "@/components/ui/label"; +import { testErrorRuleAction } from "@/lib/api-client/v1/actions/error-rules"; import { cn } from "@/lib/utils"; import type { ErrorOverrideResponse } from "@/repository/error-rules"; diff --git a/src/app/[locale]/settings/error-rules/_components/refresh-cache-button.tsx b/src/app/[locale]/settings/error-rules/_components/refresh-cache-button.tsx index b8f76af55..f2f0660cb 100644 --- a/src/app/[locale]/settings/error-rules/_components/refresh-cache-button.tsx +++ b/src/app/[locale]/settings/error-rules/_components/refresh-cache-button.tsx @@ -4,8 +4,8 @@ import { RefreshCw } from "lucide-react"; import { useTranslations } from "next-intl"; import { useState } from "react"; import { toast } from "sonner"; -import { refreshCacheAction } from "@/actions/error-rules"; import { Button } from "@/components/ui/button"; +import { refreshCacheAction } from "@/lib/api-client/v1/actions/error-rules"; import { cn } from "@/lib/utils"; interface RefreshCacheButtonProps { diff --git a/src/app/[locale]/settings/error-rules/_components/rule-list-table.tsx b/src/app/[locale]/settings/error-rules/_components/rule-list-table.tsx index 5ba8d6f2d..05976713b 100644 --- a/src/app/[locale]/settings/error-rules/_components/rule-list-table.tsx +++ b/src/app/[locale]/settings/error-rules/_components/rule-list-table.tsx @@ -5,11 +5,14 @@ import { AlertTriangle, Pencil, Trash2 } from "lucide-react"; import { useTimeZone, useTranslations } from "next-intl"; import { useState } from "react"; import { toast } from "sonner"; -import { deleteErrorRuleAction, updateErrorRuleAction } from "@/actions/error-rules"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Switch } from "@/components/ui/switch"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; +import { + deleteErrorRuleAction, + updateErrorRuleAction, +} from "@/lib/api-client/v1/actions/error-rules"; import { cn } from "@/lib/utils"; import type { ErrorRule } from "@/repository/error-rules"; import { EditRuleDialog } from "./edit-rule-dialog"; diff --git a/src/app/[locale]/settings/notifications/_lib/hooks.ts b/src/app/[locale]/settings/notifications/_lib/hooks.ts index ea5cdd4c0..87e1f96d2 100644 --- a/src/app/[locale]/settings/notifications/_lib/hooks.ts +++ b/src/app/[locale]/settings/notifications/_lib/hooks.ts @@ -1,18 +1,21 @@ "use client"; import { useCallback, useEffect, useMemo, useState } from "react"; -import { getBindingsForTypeAction, updateBindingsAction } from "@/actions/notification-bindings"; +import { + getBindingsForTypeAction, + updateBindingsAction, +} from "@/lib/api-client/v1/actions/notification-bindings"; import { getNotificationSettingsAction, updateNotificationSettingsAction, -} from "@/actions/notifications"; +} from "@/lib/api-client/v1/actions/notifications"; import { createWebhookTargetAction, deleteWebhookTargetAction, getWebhookTargetsAction, testWebhookTargetAction, updateWebhookTargetAction, -} from "@/actions/webhook-targets"; +} from "@/lib/api-client/v1/actions/webhook-targets"; import { type CacheHitRateAlertSettingsWindowMode, isCacheHitRateAlertSettingsWindowMode, diff --git a/src/app/[locale]/settings/prices/_components/delete-model-dialog.tsx b/src/app/[locale]/settings/prices/_components/delete-model-dialog.tsx index e11781ccc..8d07c4ca5 100644 --- a/src/app/[locale]/settings/prices/_components/delete-model-dialog.tsx +++ b/src/app/[locale]/settings/prices/_components/delete-model-dialog.tsx @@ -4,7 +4,6 @@ import { Loader2, Trash2 } from "lucide-react"; import { useTranslations } from "next-intl"; import { useState } from "react"; import { toast } from "sonner"; -import { deleteSingleModelPrice } from "@/actions/model-prices"; import { AlertDialog, AlertDialogAction, @@ -17,6 +16,7 @@ import { AlertDialogTrigger, } from "@/components/ui/alert-dialog"; import { Button } from "@/components/ui/button"; +import { deleteSingleModelPrice } from "@/lib/api-client/v1/actions/model-prices"; interface DeleteModelDialogProps { modelName: string; diff --git a/src/app/[locale]/settings/prices/_components/model-price-dialog.tsx b/src/app/[locale]/settings/prices/_components/model-price-dialog.tsx index dba1c1c34..b487c3435 100644 --- a/src/app/[locale]/settings/prices/_components/model-price-dialog.tsx +++ b/src/app/[locale]/settings/prices/_components/model-price-dialog.tsx @@ -4,7 +4,6 @@ import { Loader2, Pencil, Plus } from "lucide-react"; import { useTranslations } from "next-intl"; import { useEffect, useState } from "react"; import { toast } from "sonner"; -import { upsertSingleModelPrice } from "@/actions/model-prices"; import { Button } from "@/components/ui/button"; import { Dialog, @@ -24,6 +23,7 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; +import { upsertSingleModelPrice } from "@/lib/api-client/v1/actions/model-prices"; import type { ModelPrice } from "@/types/model-price"; interface ModelPriceDialogProps { diff --git a/src/app/[locale]/settings/prices/_components/model-price-drawer.tsx b/src/app/[locale]/settings/prices/_components/model-price-drawer.tsx index 0fb81f0dc..e8ef9b6e1 100644 --- a/src/app/[locale]/settings/prices/_components/model-price-drawer.tsx +++ b/src/app/[locale]/settings/prices/_components/model-price-drawer.tsx @@ -4,7 +4,6 @@ import { Loader2, Pencil, Plus, Search } from "lucide-react"; import { useTranslations } from "next-intl"; import { useCallback, useEffect, useState } from "react"; import { toast } from "sonner"; -import { upsertSingleModelPrice } from "@/actions/model-prices"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { @@ -34,6 +33,7 @@ import { } from "@/components/ui/sheet"; import { Switch } from "@/components/ui/switch"; import { Textarea } from "@/components/ui/textarea"; +import { upsertSingleModelPrice } from "@/lib/api-client/v1/actions/model-prices"; import { useDebounce } from "@/lib/hooks/use-debounce"; import { getEditableExtraPriceData } from "@/lib/utils/model-price-fields"; import type { ModelPrice } from "@/types/model-price"; diff --git a/src/app/[locale]/settings/prices/_components/provider-pricing-dialog.tsx b/src/app/[locale]/settings/prices/_components/provider-pricing-dialog.tsx index 4dea729d4..2fc817fe4 100644 --- a/src/app/[locale]/settings/prices/_components/provider-pricing-dialog.tsx +++ b/src/app/[locale]/settings/prices/_components/provider-pricing-dialog.tsx @@ -4,7 +4,6 @@ import { ArrowRightLeft, Loader2, Pin } from "lucide-react"; import { useTranslations } from "next-intl"; import { useMemo, useState } from "react"; import { toast } from "sonner"; -import { pinModelPricingProviderAsManual } from "@/actions/model-prices"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { @@ -15,6 +14,7 @@ import { DialogTitle, DialogTrigger, } from "@/components/ui/dialog"; +import { pinModelPricingProviderAsManual } from "@/lib/api-client/v1/actions/model-prices"; import type { ModelPrice } from "@/types/model-price"; interface ProviderPricingDialogProps { diff --git a/src/app/[locale]/settings/prices/_components/sync-litellm-button.tsx b/src/app/[locale]/settings/prices/_components/sync-litellm-button.tsx index d99efef7d..279bd1d8e 100644 --- a/src/app/[locale]/settings/prices/_components/sync-litellm-button.tsx +++ b/src/app/[locale]/settings/prices/_components/sync-litellm-button.tsx @@ -5,8 +5,11 @@ import { useRouter } from "next/navigation"; import { useTranslations } from "next-intl"; import { useState } from "react"; import { toast } from "sonner"; -import { checkLiteLLMSyncConflicts, syncLiteLLMPrices } from "@/actions/model-prices"; import { Button } from "@/components/ui/button"; +import { + checkLiteLLMSyncConflicts, + syncLiteLLMPrices, +} from "@/lib/api-client/v1/actions/model-prices"; import type { SyncConflict } from "@/types/model-price"; import { SyncConflictDialog } from "./sync-conflict-dialog"; diff --git a/src/app/[locale]/settings/prices/_components/upload-price-dialog.tsx b/src/app/[locale]/settings/prices/_components/upload-price-dialog.tsx index 5099dd296..7aaa066f8 100644 --- a/src/app/[locale]/settings/prices/_components/upload-price-dialog.tsx +++ b/src/app/[locale]/settings/prices/_components/upload-price-dialog.tsx @@ -5,7 +5,6 @@ import { useTranslations } from "next-intl"; import { useEffect, useState } from "react"; import { createPortal } from "react-dom"; import { toast } from "sonner"; -import { uploadPriceTable } from "@/actions/model-prices"; import { Button } from "@/components/ui/button"; import { Dialog, @@ -16,6 +15,7 @@ import { DialogTrigger, } from "@/components/ui/dialog"; import { useRouter } from "@/i18n/routing"; +import { uploadPriceTable } from "@/lib/api-client/v1/actions/model-prices"; import type { PriceUpdateResult } from "@/types/model-price"; interface PageLoadingOverlayProps { diff --git a/src/app/[locale]/settings/providers/_components/auto-sort-priority-dialog.tsx b/src/app/[locale]/settings/providers/_components/auto-sort-priority-dialog.tsx index 13b2d9db6..1b97a70a4 100644 --- a/src/app/[locale]/settings/providers/_components/auto-sort-priority-dialog.tsx +++ b/src/app/[locale]/settings/providers/_components/auto-sort-priority-dialog.tsx @@ -5,7 +5,6 @@ import { ArrowRight, ListOrdered, Loader2 } from "lucide-react"; import { useTranslations } from "next-intl"; import { useState, useTransition } from "react"; import { toast } from "sonner"; -import { autoSortProviderPriority } from "@/actions/providers"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { @@ -25,6 +24,7 @@ import { TableHeader, TableRow, } from "@/components/ui/table"; +import { autoSortProviderPriority } from "@/lib/api-client/v1/actions/providers"; type AutoSortResult = { groups: Array<{ diff --git a/src/app/[locale]/settings/providers/_components/batch-edit/provider-batch-dialog.tsx b/src/app/[locale]/settings/providers/_components/batch-edit/provider-batch-dialog.tsx index ef4946acf..9e90d4078 100644 --- a/src/app/[locale]/settings/providers/_components/batch-edit/provider-batch-dialog.tsx +++ b/src/app/[locale]/settings/providers/_components/batch-edit/provider-batch-dialog.tsx @@ -5,15 +5,6 @@ import { Loader2 } from "lucide-react"; import { useTranslations } from "next-intl"; import { useCallback, useMemo, useState } from "react"; import { toast } from "sonner"; -import { - applyProviderBatchPatch, - batchDeleteProviders, - batchResetProviderCircuits, - type PreviewProviderBatchPatchResult, - previewProviderBatchPatch, - undoProviderDelete, - undoProviderPatch, -} from "@/actions/providers"; import { AlertDialog, AlertDialogAction, @@ -33,6 +24,15 @@ import { DialogHeader, DialogTitle, } from "@/components/ui/dialog"; +import { + applyProviderBatchPatch, + batchDeleteProviders, + batchResetProviderCircuits, + type PreviewProviderBatchPatchResult, + previewProviderBatchPatch, + undoProviderDelete, + undoProviderPatch, +} from "@/lib/api-client/v1/actions/providers"; import { PROVIDER_BATCH_PATCH_ERROR_CODES } from "@/lib/provider-batch-patch-error-codes"; import type { ProviderDisplay } from "@/types/provider"; import { FormTabNav } from "../forms/provider-form/components/form-tab-nav"; diff --git a/src/app/[locale]/settings/providers/_components/batch-edit/provider-batch-preview-step.tsx b/src/app/[locale]/settings/providers/_components/batch-edit/provider-batch-preview-step.tsx index 62168bda0..7b58c22da 100644 --- a/src/app/[locale]/settings/providers/_components/batch-edit/provider-batch-preview-step.tsx +++ b/src/app/[locale]/settings/providers/_components/batch-edit/provider-batch-preview-step.tsx @@ -20,8 +20,8 @@ const FIELD_LABEL_KEYS: Record = { anthropic_adaptive_thinking: "fields.adaptiveThinking", }; -import type { ProviderBatchPreviewRow } from "@/actions/providers"; import { Checkbox } from "@/components/ui/checkbox"; +import type { ProviderBatchPreviewRow } from "@/lib/api-client/v1/actions/providers"; // --------------------------------------------------------------------------- // Props diff --git a/src/app/[locale]/settings/providers/_components/dispatch-simulator-dialog.tsx b/src/app/[locale]/settings/providers/_components/dispatch-simulator-dialog.tsx index 3efb07ad5..5bed9e844 100644 --- a/src/app/[locale]/settings/providers/_components/dispatch-simulator-dialog.tsx +++ b/src/app/[locale]/settings/providers/_components/dispatch-simulator-dialog.tsx @@ -9,7 +9,6 @@ import { } from "lucide-react"; import { useTranslations } from "next-intl"; import { useMemo, useState, useTransition } from "react"; -import { simulateDispatchAction } from "@/actions/dispatch-simulator"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; @@ -32,6 +31,7 @@ import { SheetTitle, SheetTrigger, } from "@/components/ui/sheet"; +import { simulateDispatchAction } from "@/lib/api-client/v1/actions/dispatch-simulator"; import { parseProviderGroups } from "@/lib/utils/provider-group"; import type { DispatchSimulatorResult } from "@/types/dispatch-simulator"; import type { ProviderDisplay } from "@/types/provider"; diff --git a/src/app/[locale]/settings/providers/_components/forms/api-test-button.tsx b/src/app/[locale]/settings/providers/_components/forms/api-test-button.tsx index b05fb1bab..7bbc578eb 100644 --- a/src/app/[locale]/settings/providers/_components/forms/api-test-button.tsx +++ b/src/app/[locale]/settings/providers/_components/forms/api-test-button.tsx @@ -2,18 +2,25 @@ import { Activity, AlertTriangle, CheckCircle2, Loader2, XCircle } from "lucide-react"; import { useTranslations } from "next-intl"; -import { useEffect, useMemo, useState } from "react"; +import { useEffect, useMemo, useRef, useState } from "react"; import { toast } from "sonner"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Textarea } from "@/components/ui/textarea"; +import { normalizeAllowedModelRules } from "@/lib/allowed-model-rules"; import { getUnmaskedProviderKey, type ProviderApiTestSuccessDetails, testProviderGemini, testProviderUnified, -} from "@/actions/providers"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { normalizeAllowedModelRules } from "@/lib/allowed-model-rules"; +} from "@/lib/api-client/v1/actions/providers"; +import { + CUSTOM_HEADERS_PLACEHOLDER, + type CustomHeadersValidationErrorCode, + parseCustomHeadersJsonText, + stringifyCustomHeadersForTextarea, +} from "@/lib/custom-headers"; import { isValidUrl } from "@/lib/utils/validation"; import type { AllowedModelRuleInput, ProviderType } from "@/types/provider"; import { TestResultCard, type UnifiedTestResultData } from "./test-result-card"; @@ -26,7 +33,7 @@ const API_TEST_UI_CONFIG = { const DEFAULT_MODELS: Record = { claude: "claude-haiku-4-5-20251001", "claude-auth": "claude-haiku-4-5-20251001", - codex: "gpt-5.3-codex", + codex: "gpt-5.4", "openai-compatible": "gpt-4.1-mini", gemini: "gemini-2.5-flash", "gemini-cli": "gemini-2.5-flash", @@ -82,6 +89,7 @@ interface ApiTestButtonProps { providerId?: number; providerType?: ProviderType | null; allowedModels?: AllowedModelRuleInput[]; + customHeaders?: Record | null; enableMultiProviderTypes: boolean; } @@ -94,6 +102,7 @@ export function ApiTestButton({ providerId, providerType, allowedModels = [], + customHeaders, enableMultiProviderTypes: _enableMultiProviderTypes, }: ApiTestButtonProps) { const t = useTranslations("settings.providers.form.apiTest"); @@ -113,6 +122,13 @@ export function ApiTestButton({ const [testModel, setTestModel] = useState(() => getDefaultModelForProvider(providerType, normalizedAllowedModels[0]) ); + const initialCustomHeadersText = useMemo( + () => stringifyCustomHeadersForTextarea(customHeaders ?? null), + [customHeaders] + ); + const previousProviderId = useRef(providerId); + const [customHeadersText, setCustomHeadersText] = useState(initialCustomHeadersText); + const [isCustomHeadersManuallyEdited, setIsCustomHeadersManuallyEdited] = useState(false); const [testResult, setTestResult] = useState(null); useEffect(() => { @@ -123,6 +139,31 @@ export function ApiTestButton({ setTestModel(getDefaultModelForProvider(providerType, normalizedAllowedModels[0])); }, [isModelManuallyEdited, normalizedAllowedModels, providerType]); + // 仅在用户未手动编辑时随 prop 变更同步;切换 provider 身份时重置编辑标志 + useEffect(() => { + if (previousProviderId.current === providerId) { + return; + } + previousProviderId.current = providerId; + setIsCustomHeadersManuallyEdited(false); + }, [providerId]); + + useEffect(() => { + if (isCustomHeadersManuallyEdited) return; + setCustomHeadersText(initialCustomHeadersText); + }, [initialCustomHeadersText, isCustomHeadersManuallyEdited]); + + const CUSTOM_HEADER_ERROR_KEYS: Record = { + invalid_json: "customHeaders.errors.invalidJson", + not_object: "customHeaders.errors.notObject", + invalid_name: "customHeaders.errors.invalidName", + duplicate_name: "customHeaders.errors.duplicateName", + protected_name: "customHeaders.errors.protectedName", + invalid_value: "customHeaders.errors.invalidValue", + empty_name: "customHeaders.errors.emptyName", + crlf: "customHeaders.errors.crlf", + }; + const handleTest = async () => { if (!providerUrl.trim()) { toast.error(t("fillUrlFirst")); @@ -134,6 +175,21 @@ export function ApiTestButton({ return; } + const parsedCustomHeaders = parseCustomHeadersJsonText(customHeadersText); + if (!parsedCustomHeaders.ok) { + toast.error(t(CUSTOM_HEADER_ERROR_KEYS[parsedCustomHeaders.code])); + return; + } + const customHeadersValue = parsedCustomHeaders.value; + + if ( + customHeadersValue && + (resolvedProviderType === "gemini" || resolvedProviderType === "gemini-cli") + ) { + toast.warning(t("customHeaders.geminiNotSupported")); + return; + } + setIsTesting(true); setTestResult(null); @@ -276,6 +332,7 @@ export function ApiTestButton({ proxyUrl: proxyUrl?.trim() || null, proxyFallbackToDirect, timeoutMs: getTimeoutMsForProvider(resolvedProviderType), + customHeaders: customHeadersValue ?? undefined, }); if (!response.ok) { @@ -388,6 +445,23 @@ export function ApiTestButton({
{t("testModelDesc")}
+
+ +