diff --git a/.dockerignore b/.dockerignore index bf4c6f1541..e9671028b9 100644 --- a/.dockerignore +++ b/.dockerignore @@ -2,6 +2,9 @@ .git .gitignore +# Go edge executor (built by edge/Dockerfile) +edge + # Node modules and build artifacts node_modules .next diff --git a/.env.example b/.env.example index bc3aeb1b50..b8cd2584a6 100644 --- a/.env.example +++ b/.env.example @@ -6,6 +6,29 @@ ADMIN_TOKEN=change-me # 单域名部署无需设置,留空则自动使用当前站点域名 # NEXT_PUBLIC_API_BASE_URL=https://api.example.com +# 代理 API 额外监听前缀(可选,逗号分隔) +# 用于按路径分流的共享域名场景:设置后 /gateway/v1/messages、/gateway/messages +# 与原有的 /v1/messages、/messages 同时生效(/v1beta Gemini 路径同理)。 +# 规则: +# - 可写多个,逗号分隔;开头斜杠可省略,结尾斜杠会被忽略;支持多段(/ai/gateway) +# - 区分大小写,不做百分号解码;不允许前缀之间互相嵌套(/a 与 /a/b) +# - 首段不能占用既有路由:api、v1、v1beta、_next、favicon.ico、login、status、 +# usage-doc、dashboard、chat、responses、models、messages 及语言代码(zh-CN 等) +# - 前缀下的非代理路径(如 /gateway/dashboard)不受影响,仍按普通页面处理 +# - ${prefix}/v1/responses 的 WebSocket 需要自定义服务(生产 cluster.js 或 dev:server); +# 纯 next dev 下前缀请求会经过 middleware,正文上限为 proxyClientMaxBodySize(100mb) +# - 反向代理(nginx/k8s)需同步为 ${prefix}/v1/responses 增加 WebSocket location +# PROXY_LISTEN_PREFIX=/gateway + +# 服务器出站代理(可选)。这是出站流量,不是上面的监听前缀。 +# 协议:http://、https://、socks5://、socks4://。留空则直连。 +# 供应商 proxyUrl 与 Webhook 目标 proxyUrl 优先。 +# 回环(localhost、127.0.0.1、::1、0.0.0.0)以及 NO_PROXY / no_proxy 不走该代理。 +# Webhook 仅在本变量未设置时才回退到 PROXY / HTTPS_PROXY / HTTP_PROXY / ALL_PROXY。 +# Postgres(DSN)和 Redis(REDIS_URL)不受影响。 +# 非回环的 ClickHouse 主机会走该代理,除非写进 NO_PROXY。 +# OUTBOUND_PROXY_URL=http://127.0.0.1:7890 + # 自动迁移控制(生产环境默认开启) # 设置为 false 可禁用自动迁移 AUTO_MIGRATE=true @@ -44,6 +67,8 @@ ENABLE_API_KEY_VACUUM_FILTER="true" # - 单进程默认拆分:生产环境 20 = 15/4/1,开发与测试环境 10 = 7/2/1 # - 每条 pool 另有有界 outstanding admission,满载时以 DB_POOL_ADMISSION_EXCEEDED 快速失败 # - k8s 多 Pod 时每个 Pod 都有一份该预算,仍需按 Pod 数核算数据库总连接数 +# - 多进程时建议每个网关进程约 5 条连接再加 4(例如 6 进程约 34),并确认 PostgreSQL +# max_connections 能覆盖「副本数 × DB_POOL_MAX」;每进程 data pool 少于 2 条时启动会告警 DB_POOL_MAX=20 DB_POOL_IDLE_TIMEOUT=20 # 空闲连接回收(秒) DB_POOL_CONNECT_TIMEOUT=10 # 建立连接超时(秒) @@ -60,6 +85,20 @@ MESSAGE_REQUEST_ASYNC_FLUSH_INTERVAL_MS=250 MESSAGE_REQUEST_ASYNC_BATCH_SIZE=200 MESSAGE_REQUEST_ASYNC_MAX_PENDING=5000 +# message_request 初始 INSERT 模式(仅在 MESSAGE_REQUEST_WRITE_MODE=async 时生效) +# - sync (默认):转发前同步 INSERT 一行 +# - async:应用侧从序列预取 id,按批 multi-row INSERT,热路径不再等待数据库; +# 行在约一个 flush 周期后才可见,进程崩溃时尚未刷盘的行会丢失。以下两个参数按进程生效,不在多进程间分摊 +MESSAGE_REQUEST_INSERT_MODE=sync +MESSAGE_REQUEST_INSERT_ID_CHUNK_SIZE=128 +MESSAGE_REQUEST_INSERT_MAX_PENDING=2000 + +# 可用性投影表保留期(天) +# - PROJECTION_OUTBOX_RETENTION_DAYS:已发布的 outbox_events 保留天数(默认 3) +# - PROJECTION_APPLIED_RETENTION_DAYS:proj_applied_requests 去重记录保留天数(默认 7) +PROJECTION_OUTBOX_RETENTION_DAYS=3 +PROJECTION_APPLIED_RETENTION_DAYS=7 + # 数据库配置(Docker Compose 部署时使用) DB_USER=postgres DB_PASSWORD=your-secure-password_change-me @@ -170,6 +209,11 @@ ENABLE_ENDPOINT_CIRCUIT_BREAKER=false # - false:禁用缓存,每次请求直接查询数据库(适用于调试或单机低并发场景) ENABLE_PROVIDER_CACHE=true +# 模型价格查询缓存 +# - true (默认):计费路径按模型名缓存价格 60s,价格写入后立即失效并通过 Redis 广播 +# - false:每次计费直接查询 model_prices 表 +ENABLE_MODEL_PRICE_CACHE=true + # Fetch 连接超时配置 # 功能说明:控制 TCP 连接建立超时时间(包括 DNS 查询、TCP 握手、TLS 握手) # - 默认值:30000 毫秒(30 秒) @@ -248,6 +292,34 @@ LANGFUSE_SAMPLE_RATE=1.0 # Trace sampling rate (0.0-1.0, defa LANGFUSE_DEBUG=false # Enable Langfuse debug logging LANGFUSE_TRACING_ENVIRONMENT= # Optional environment tag (production/staging). Do not start with langfuse LANGFUSE_RELEASE= # Optional release identifier (git sha or VERSION) +LANGFUSE_OTLP_COMPRESSION=gzip # OTLP HTTP body compression (gzip|none, default: gzip) + +# Prometheus metrics (GET /metrics) +# - METRICS_ENABLED: default true. Set false to 404 the scrape endpoint. +# - METRICS_TOKEN: optional. When set, require Authorization: Bearer or X-Metrics-Token. +# Bind /metrics to localhost or set a token before exposing the process port. +METRICS_ENABLED=true +METRICS_TOKEN= + +# ClickHouse 请求日志同步 (optional, auto-enabled when CLICKHOUSE_URL is set) +# 功能说明:后台 worker 按行上的同步标记(clickhouse_synced_at)读取未同步的 message_request, +# 把已终态的请求日志批量写入外部 ClickHouse,用于中期留存与分析(谁、何时、从哪个 IP、请求了什么模型)。 +# - 只写不读:Dashboard 依然查 PostgreSQL,本功能不改变任何查询路径 +# - 对代理热路径零侵入;ClickHouse 不可用时只告警,请求不受影响 +# - 多副本下由 Redis leader lock 选出唯一同步者,需要可用的 REDIS_URL +# - 启用后日志清理会自动加上"只删已同步行"的围栏,可以放心缩短 cleanupRetentionDays +# - 表结构在首次运行时自动创建(ReplacingMergeTree,按月分区) +CLICKHOUSE_URL= # 例如 http://clickhouse:8123,留空则不启用 +# CLICKHOUSE_USER=default +# CLICKHOUSE_PASSWORD= +# CLICKHOUSE_DATABASE=default +# CLICKHOUSE_TABLE=cch_request_log +# CLICKHOUSE_TTL_DAYS= # 表 TTL(天);留空表示不设 TTL,由运维自行管理 +# CLICKHOUSE_REQUEST_TIMEOUT_MS=10000 # 单次 HTTP 请求超时 +# CLICKHOUSE_SYNC_INTERVAL_MS=5000 # 同步周期 +# CLICKHOUSE_SYNC_BATCH_SIZE=5000 # 单批最大行数 +# CLICKHOUSE_SYNC_SETTLE_MS=150000 # 终态静置:等 hedge 败者计费落库后再发(建议 >= HEDGE_LOSER_DRAIN_TIMEOUT_MS + 30s) +# CLICKHOUSE_SYNC_MAX_PENDING_AGE_MS=3600000 # 未终态行最长等待;超时后以 status_code=0 发出 # 智能探测配置 # 功能说明:当熔断器处于 OPEN 状态时,定期探测供应商以实现更快恢复 @@ -291,3 +363,12 @@ ENDPOINT_PROBE_METHOD=TCP # - 自动清理任务每 24 小时运行,删除过期记录 ENDPOINT_PROBE_LOG_RETENTION_DAYS=1 ENDPOINT_PROBE_LOG_CLEANUP_BATCH_SIZE=10000 + +# Edge 执行器控制面(远端 Go 节点经 /api/internal/edge 接入) +# - 未配置共享密钥时 /api/internal/edge/* 一律返回 503,watchdog 也不会启动 +# - 还需在系统设置中开启「Edge 执行器」;鉴权、限流、选路、重试与计费仍在本服务 +# - 密钥建议 32 位以上随机串,并只在可信内网(如 tailnet)中传输 +# CCH_EDGE_SHARED_SECRET= +# CCH_EDGE_STATE_TTL_SECONDS=1800 # edge 请求状态在 Redis 中的保留时长(需覆盖最长流式请求) +# CCH_EDGE_HEARTBEAT_INTERVAL_MS=15000 # 远端心跳间隔;3 个间隔内无任何上报即按失败结算 +# CCH_EDGE_WATCHDOG_INTERVAL_MS=5000 # watchdog 扫描间隔(仅后台任务所属 worker 运行) diff --git a/.gitignore b/.gitignore index 040c45f630..940e236035 100644 --- a/.gitignore +++ b/.gitignore @@ -92,4 +92,8 @@ tmp/ .omx/ .opencode/ bunx-*/ + +# Go edge executor build output +edge/bin/ +edge/coverage.out .research/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 182b8c5784..6d45014e89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,19 +8,27 @@ ### 新增 +- 供应商自定义请求头支持动态模板:可用 `{{header.Name}}` 复制非敏感入站请求头,`{{session.id}}` 写入 Session ID,`{{session.client_id}}` 写入客户端 Session ID;来源缺失时跳过该头,不允许读取凭据、Cookie 或内部传输头 +- 整合上游 v0.9.7:供应商列表新增按密钥查询余额及手动刷新,支持 New API 系统访问令牌和用户 ID;保留本地上游额度调度与探测配置 +- 供应商选项新增「复写响应模型 ID」:开启后把返回给客户端的 model 字段强制写成用户请求的模型 ID,用于隐藏上游映射 +- 按可用内存分配流门禁预算:128 KiB 起步增长;内存准入默认关闭,只记账不限制、不溢写、不排队;开启后才执行正文溢写、本地容量排队与 429 - Langfuse 流式请求还原完整最终输出(Claude / OpenAI Chat Completions / Responses / Gemini),避免把原始 SSE 文本当作 generation output - Langfuse 将客户端原始请求头写入 generation `client_metadata`(凭据中间打码) - 支持 `LANGFUSE_TRACING_ENVIRONMENT` / `LANGFUSE_RELEASE` 传入 LangfuseSpanProcessor -- 供应商自定义请求头支持动态模板:可用 `{{header.Name}}` 复制入站请求头,`{{session.id}}` 写入 Session ID,`{{session.client_id}}` 写入客户端 Session ID;来源缺失时跳过该头 +- 兼容无 `/v1` 前缀的代理入口:`/chat/completions`、`/responses`、`/models`、`/messages` 映射到对应 `/v1/...` 路径 +- 支持 `PROXY_LISTEN_PREFIX` 配置代理 API 的额外监听前缀(逗号分隔,如 `/gateway`),`/gateway/v1/...`、`/gateway/...` 与原有路径同时生效,含 `/v1beta` 与 `/v1/responses` WebSocket ### 优化 +- 客户端故障告警不再重复已发送内容:相同错误指纹在冷却期内不入桶、不重推,后续窗口只报新样本 - Langfuse trace 名称改为 `user:shortModel`,去掉供应商前缀 - 按 Langfuse JS SDK v5 在 `propagateAttributes` 内创建 observation -- 大请求/响应体 1 MiB 截断,并在异步发送前快照,避免观测路径拖住完整 body +- 保留本地 Langfuse 完整请求/响应及异步压缩保留策略,不引入上游 1 MiB 正文截断;合入 usage 互斥计费桶修正 +- 保留本地 edge 执行器、quota 调度、provider prefix、全局出站代理、Prometheus 与 ClickHouse;上游 hedge attempt ID 和错误诊断迁入共享重试 helper ### 修复 +- 修复客户端故障告警对 OpenAI cyber risk 关键词匹配过窄的问题:覆盖完整报错文案、Trusted Access 以及结构化 `cyber_policy` 响应体,样本只保留命中关键词 - 修复上游响应流发生 error 后 Node/Undici body 未完成销毁的问题:Node-to-Web adapter 和 demand-driven pump 现在在源流错误终态显式取消、销毁底层流,并为异步 destroy error 保留有界保护;同时将 raw body 的兜底错误监听改为一次性监听, 避免 HTTP/2 reset、客户端断开和竞速取消路径长期保留 socket 与 ArrayBuffer backing store (#1430) @@ -34,6 +42,10 @@ - 修复 Anthropic 请求级拒绝(`stop_reason=refusal` 且无内容块)被流式内容门控判为 `empty_stream` 并伪造 502、 重试切商并计入供应商熔断的问题:门控、Discovery 竞速、非流式空响应检测与 fake-streaming 校验统一把 refusal 视为可交付结果原样透传;门控本地错误体与日志新增上游真实状态 `upstream_status_code` 与熔断计入标记 (#1491) +- Responses WebSocket 在首事件前收到请求过大错误时,在同一供应商回退 HTTP,不缓存为“不支持 WS”;Next 的自动 upgrade handler 绑定私有 loopback listener,避免与公网 Responses WS handler 争用连接 +- 历史 Drizzle 迁移编号与已应用 SQL 保持不变;生成 `0131_talented_adam_warlock` 添加余额凭据字段、内存准入开关及 session 前缀索引防阻塞升级,已有大表先走并发索引 preflight +- 移除 OpenCode Go 保存适配弹窗及原始 Session ID 模板注入,保留转发阶段哈希 `x-opencode-session` 自动注入 + --- diff --git a/CLAUDE.md b/CLAUDE.md index 4c628b6a14..03ba876845 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -118,7 +118,7 @@ Key components: - **Format Converters** (`converters/`): Bidirectional format translation ### API Layer -- **Proxy endpoints**: `/v1/messages`, `/v1/chat/completions`, `/v1/responses` +- **Proxy endpoints**: `/v1/messages`, `/v1/chat/completions`, `/v1/responses`, `/v1/models` (also accepted without the `/v1` prefix) - **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` @@ -141,3 +141,87 @@ Critical variables (see `.env.example` for full list): - `ENABLE_RATE_LIMIT`: Toggle rate limiting - `SESSION_TTL`: Session cache TTL (default 300s) - `AUTO_MIGRATE`: Auto-run migrations on startup + +## Production Deploy (systemd, traced standalone) + +This project's live install is **~288–292M**, not 1.5G. The 292M tree is Next's file-traced standalone. It is **not** `bun install --production` on the server. + +### Why the size jumps + +`next.config.ts` sets `output: "standalone"`. `bun run build` then: + +1. Next traces runtime files into `.next/standalone/` (app, `.next/server`, **a trimmed `node_modules`**) +2. `scripts/copy-version-to-standalone.cjs` copies `VERSION`, `.next/static`, `public` +3. `scripts/copy-custom-server-to-standalone.cjs` copies `server.js`, `cluster.js`, `server-lib/` + +Traced `node_modules` is ~116 packages (~247M). It already contains the server-external packages Next does not bundle (`ioredis`, `postgres`, `drizzle-orm`, `bull`, `@bull-board/*`, `pino`, `ws`, `undici`, `fetch-socks`, `@next/env`) plus native `sharp` for the build arch. + +The trap: standalone `package.json` is a copy of the **root** file (all 78 `dependencies`). `bun install --production --frozen-lockfile` ignores tracing and installs the full graph (~869 packages, ~1.5G). `bun.lock` is gitignored (`.gitignore`); copying it only matters if you are intentionally doing a remote install. + +`.next/node_modules/-` entries are **symlinks** into `../../node_modules/` (`ioredis`, `postgres`, `drizzle-orm`, `bull`, `pino`, …). `rsync --exclude '/node_modules/'` while keeping `.next/node_modules` leaves those stubs dangling unless you then `bun install` (and that is the 1.5G path). + +Docker already uses the traced tree: `COPY --from=builder /app/.next/standalone ./` (see `Dockerfile`). systemd hosts should do the same. + +### Extra trim to ~288M (same-arch glibc x86_64) + +Local traced tree is ~399M. Two runtime-unused cuts bring it to the historical 292M: + +| Cut | Typical size | Safe? | +|---|---|---| +| `find .next/standalone -name '*.map' -delete` | ~89M, almost all `node_modules/next/dist` | yes, source maps are not loaded at runtime | +| remove `@img/sharp-linuxmusl-x64` and `@img/sharp-libvips-linuxmusl-x64` | ~18M | yes on glibc hosts only | + +Do **not** delete glibc sharp (`sharp-linux-x64`, `sharp-libvips-linux-x64`). Do **not** strip maps/native binaries on a cross-arch copy. + +### Copy set (include these) + +- traced `node_modules/` (top-level, ~116 packages) +- `.next/` (`server`, `static`, hashed `node_modules` stubs) +- `cluster.js`, `server.js`, `server-lib/`, `package.json`, `VERSION` +- `drizzle/`, `public/` + +Do **not** copy `.env` / `.env.*`. Preserve the live `.env` across the swap (`install -m 600 -o cch -g cch`). + +Do **not** copy `bun.lock` / `bunfig.toml` unless the target will run `bun install`. Same-arch systemd deploys should not. + +### Do not + +- Remote `bun install --production` on x86_64 hosts (inflates 292M → 1.5G) +- `rsync --exclude node_modules` blindly (drops hashed Next externals *and* the traced packages they point at) +- Ship an x86 `node_modules` to aarch64 (native `sharp` / optional extracts). Rebuild on the target, or use the 1.5G remote-install path there only +- Keep `/opt/claude-code-hub.prev` on `172.16.96.115` (12G disk; live + prev will not fit) + +### Same-arch procedure + +```bash +bun run build +# optional same-arch trim +find .next/standalone -name '*.map' -delete +rm -rf .next/standalone/node_modules/@img/sharp-linuxmusl-x64 \ + .next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64 + +# stage, then atomic swap; LIVE/NEXT/PREV differ per host +rsync -a --delete --exclude '/.env' --exclude '/.env.*' \ + .next/standalone/ "$HOST:$NEXT/" +# or tar-over-ssh when the host has no rsync: +# tar -C .next/standalone --exclude './.env' --exclude './.env.*' -cf - . \ +# | ssh "$HOST" "tar -C $NEXT -xf -" + +ssh "$HOST" "install -m 600 -o cch -g cch $LIVE/.env $NEXT/.env + chown -R cch:cch $NEXT + systemctl stop cch + rm -rf $PREV && mv $LIVE $PREV && mv $NEXT $LIVE # 115: rm -rf \$LIVE instead of keeping PREV + systemctl start cch" +``` + +ExecStart is `/usr/bin/node cluster.js` (or `/opt/cch/cluster.js` on dslab), **not** bun. After restart, `/api/health` may report redis `down` for ~15s (client connect race); `/api/health/ready` should be `healthy` once workers are up. Confirm `version` is `0.9.5` (or current `VERSION`) and `du -sh $LIVE` is ~288M with ~116 `node_modules` entries. + +### Hosts + +| Host | Live dir | Copy | Staging | Notes | +|---|---|---|---|---| +| `root@100.64.1.38` | `/opt/claude-code-hub` | rsync | `.next` / `.prev` | `HOSTNAME=127.0.0.1` `LOG_LEVEL=error`; MemoryMax=6G; do not kill `/opt/cch-go/ui` (`PORT=13501`) | +| `root@cch.in.dslab.top` | `/opt/cch` | rsync | `/opt/cch.next` / `.prev` | `HOSTNAME=0.0.0.0`; bun `/usr/bin/bun` | +| `root@172.16.96.115` | `/opt/claude-code-hub` | tar-over-ssh (no rsync) | no `.prev` | 12G disk; `CCH_MULTICORE_WORKERS=6`; bun `/usr/local/bin/bun` | + +aarch64 (`xiaol@cpa1`) cannot reuse an x86 traced `node_modules`. Build on that host, or copy the tree **without** native `node_modules` and `bun install --production` there (accept ~1.5G). diff --git a/Makefile b/Makefile index 430343618a..8ed8e59e0a 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ # 这个 Makefile 将命令转发到 dev/Makefile # 可以在项目根目录直接执行 make 命令 -.PHONY: help dev-help dev db app build build-nocache app-rebuild app-nocache prune-images rm-app-image compose clean migrate db-shell redis-shell logs logs-app logs-db logs-redis reset status stop +.PHONY: edge-build edge-test edge-lint edge-docker help dev-help dev db app build build-nocache app-rebuild app-nocache prune-images rm-app-image compose clean migrate db-shell redis-shell logs logs-app logs-db logs-redis reset status stop # 默认目标:显示 dev 工具帮助 .DEFAULT_GOAL := dev-help @@ -17,3 +17,7 @@ dev-help: # 转发所有命令到 dev/Makefile dev db app build build-nocache app-rebuild app-nocache prune-images rm-app-image compose clean migrate db-shell redis-shell logs logs-app logs-db logs-redis reset status stop: @cd dev && $(MAKE) $@ + +# Go edge 执行器(转发到 edge/Makefile) +edge-build edge-test edge-lint edge-docker: + @cd edge && $(MAKE) $(patsubst edge-%,%,$@) diff --git a/README.en.md b/README.en.md index 8cfc9d081a..ebfcbf1258 100644 --- a/README.en.md +++ b/README.en.md @@ -352,6 +352,8 @@ See **[docs/k8s-deployment.md](docs/k8s-deployment.md)** for full options, place | `ENABLE_CIRCUIT_BREAKER_ON_NETWORK_ERRORS` | `false` | When `true`, network errors also trip the circuit breaker for quicker isolation. | | `APP_PORT` | `23000` | Production port (override via container or process manager). | | `APP_URL` | empty | Populate to expose correct `servers` entries in OpenAPI docs. | +| `PROXY_LISTEN_PREFIX` | empty | Extra listen prefixes for the proxy API (comma-separated, e.g. `/gateway`); `/gateway/v1/...` and `/gateway/...` work alongside the existing paths. | +| `OUTBOUND_PROXY_URL` | empty | Global proxy for server outbound HTTP(S) and the upstream Responses WebSocket. A provider or webhook target proxyUrl wins. Empty means direct. Loopback and NO_PROXY are not proxied. | | `API_TEST_TIMEOUT_MS` | `15000` | Timeout (ms) for provider API connectivity tests. Accepts 5000-120000 for regional tuning. | > Boolean values support `true/false` or `1/0`. Quoting in `.env` is also fine (dotenv will strip quotes). See `.env.example` for the full list. diff --git a/README.md b/README.md index a1a366063d..6027dd12d3 100644 --- a/README.md +++ b/README.md @@ -365,6 +365,8 @@ cch doctor # 诊断集群与部署状态 | `ENABLE_CIRCUIT_BREAKER_ON_NETWORK_ERRORS` | `false` | 是否将网络错误计入熔断器;开启后能更激进地阻断异常线路。 | | `APP_PORT` | `23000` | 生产端口,可被容器或进程管理器覆盖。 | | `APP_URL` | 空 | 设置后 OpenAPI 文档 `servers` 将展示正确域名/端口。 | +| `PROXY_LISTEN_PREFIX` | 空 | 代理 API 的额外监听前缀(逗号分隔,如 `/gateway`);`/gateway/v1/...` 与 `/gateway/...` 和原有路径同时生效。 | +| `OUTBOUND_PROXY_URL` | 空 | 服务器出站 HTTP(S) 与上游 Responses WebSocket 的全局代理。供应商或 Webhook 目标自己的 proxyUrl 优先。留空则直连。回环与 NO_PROXY 不走该代理。 | | `API_TEST_TIMEOUT_MS` | `15000` | 供应商 API 测试超时时间(毫秒,范围 5000-120000),跨境网络可适当提高。 | > 布尔变量支持 `true/false` 或 `1/0`;在 `.env` 文件里写成带引号形式也没问题(dotenv 会解析并去掉引号)。更多字段参考 `.env.example`。 diff --git a/biome.json b/biome.json index 236dff9372..cab8b99f64 100644 --- a/biome.json +++ b/biome.json @@ -114,7 +114,8 @@ "!**/dist", "!**/drizzle", "!**/docs-site", - "!src/lib/api-client/v1/openapi-types.gen.ts" + "!src/lib/api-client/v1/openapi-types.gen.ts", + "!tests/fixtures/edge" ] } } diff --git a/deploy/.env.example b/deploy/.env.example index 9766b27299..1791addcab 100644 --- a/deploy/.env.example +++ b/deploy/.env.example @@ -26,3 +26,7 @@ REDIS_URL=redis://redis:6379 # Redis 连接地址(Docker Compose # 降低该值会按签发时间收紧已签发 ADMIN_TOKEN 签名 cookie 的剩余寿命,且不会延长其原始 exp。 AUTH_SESSION_TTL_SECONDS=604800 # Web UI 登录态过期时间(秒,默认 604800 = 7 天,范围 60-31536000) SESSION_TTL=300 # 代理请求上下文缓存时间(秒,默认 300 = 5 分钟;不控制 Web UI 登录态) + +# Prometheus scrape at GET /metrics +METRICS_ENABLED=true +METRICS_TOKEN= diff --git a/docs/api-key-validation-workflow.md b/docs/api-key-validation-workflow.md new file mode 100644 index 0000000000..165f14fb30 --- /dev/null +++ b/docs/api-key-validation-workflow.md @@ -0,0 +1,162 @@ +# API Key Validation Workflow + +The `POST /api/v1/keys:validate` endpoint lets an administrator verify whether a +plaintext API key is currently valid in the system without using the key for a +proxy request. It is useful for onboarding checks, billing reconciliation, audit +tools, and support workflows. + +This endpoint is **admin-tier**. It only accepts credentials that can call admin +routes, and it intentionally returns the same `404` response for every failure +case so callers cannot distinguish between "key does not exist", "user is +disabled", and "user is expired". + +## Authentication + +Use any credential that is allowed for admin-tier `/api/v1/*` routes: + +- Browser session cookie: `Cookie: auth-token=` +- Admin bearer token: `Authorization: Bearer ` +- User API key (requires `ENABLE_API_KEY_ADMIN_ACCESS=true` and an owner with + `role=admin`): `X-Api-Key: ` + +See `api-authentication-guide.md` and `security/api-key-admin-access.md` for +details about admin access tiers and CSRF requirements. + +## Request + +```http +POST /api/v1/keys:validate +Content-Type: application/json + +{ + "key": "sk-..." +} +``` + +### Request body + +| Field | Type | Required | Description | +|-------|--------|----------|------------------------------------| +| key | string | yes | The plaintext API key to validate. | + +## Validation Rules + +The key is considered **valid** only when **all** of the following are true: + +1. The key string exists in the database. +2. The key belongs to a user whose `isEnabled` flag is `true`. +3. The key's owner has not expired (`expiresAt` is either `null` or in the + future). + +If any of the above conditions is not met, the endpoint returns `404 Not Found` +with a generic problem response. + +## Success Response + +`200 OK` + +```json +{ + "owner": "alice", + "name": "production-key" +} +``` + +| Field | Type | Description | +|-------|--------|------------------------------------------| +| owner | string | User name of the API key owner. | +| name | string | Human-readable name assigned to the key. | + +## Error Response + +### 404 Not Found + +Returned for any of the validation failure cases. The response body is a +standard `application/problem+json` envelope: + +```json +{ + "type": "urn:claude-code-hub:problem:not_found", + "title": "Not Found", + "status": 404, + "detail": "API key not found or not valid.", + "instance": "/api/v1/keys:validate", + "errorCode": "not_found", + "errorParams": {} +} +``` + +### 401 Unauthorized + +The request did not carry a valid admin credential. + +### 403 Forbidden + +The caller is authenticated but is not allowed to access admin-tier routes. + +### 400 Bad Request + +The request body is missing the `key` field or is malformed. + +## Example: curl + +```bash +# Validate a key using the ADMIN_TOKEN as a Bearer token. +curl -X POST 'https://cch.ts.zenkexi.com/api/v1/keys:validate' \ + -H 'Content-Type: application/json' \ + -H 'Authorization: Bearer your-admin-token' \ + -d '{"key":"sk-abc123"}' +``` + +## Example: Node.js + +```javascript +const response = await fetch( + "https://cch.ts.zenkexi.com/api/v1/keys:validate", + { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${process.env.CCH_ADMIN_TOKEN}`, + }, + body: JSON.stringify({ key: "sk-abc123" }), + } +); + +if (!response.ok) { + const problem = await response.json(); + throw new Error(`${problem.errorCode}: ${problem.detail}`); +} + +const { owner, name } = await response.json(); +console.log(`Key belongs to ${owner} (${name})`); +``` + +## Use Cases + +- **Pre-flight check**: Confirm a key exists before allowing it to be used in a + third-party integration. +- **Billing reconciliation**: Map a reported key string back to its owner and + key name for invoice line items. +- **Audit and support**: Determine whether a key that is failing proxy requests + has been disabled, expired, or never existed. +- **Key rotation helpers**: Verify that an old key has been revoked (returning + `404`) before deleting local records. + +## Security Notes + +- The endpoint accepts the plaintext key in the request body. Always send it + over HTTPS and avoid logging the value. +- Do not expose this endpoint to non-admin callers. It allows enumeration of + valid keys by testing arbitrary strings, so the generic `404` response is + intentional to limit information leakage. +- Combine the validation response with usage limits and quota checks when + authorizing a third-party tool. + +## Related Resources + +- OpenAPI JSON: `/api/v1/openapi.json` +- Scalar UI: `/api/v1/scalar` +- Swagger UI: `/api/v1/docs` +- API Authentication Guide: `api-authentication-guide.md` +- API Key Admin Access: `security/api-key-admin-access.md` diff --git a/docs/clickhouse-request-log-sync.md b/docs/clickhouse-request-log-sync.md new file mode 100644 index 0000000000..0259f12b86 --- /dev/null +++ b/docs/clickhouse-request-log-sync.md @@ -0,0 +1,230 @@ +# ClickHouse 请求日志同步 + +把请求日志增量同步到外部 ClickHouse,用于中期留存与分析:谁、在什么时候、从什么 IP、请求了什么模型, +以及对应的用量、耗时、状态和费用。 + +**只写不读**:Dashboard 的日志页、统计、排行榜全部仍然查 PostgreSQL。本功能不改变任何查询路径。 + +## 为什么这样设计 + +一条 `message_request` 记录的生命周期是碎片化的:guard 链末尾先 INSERT 一行空指标记录,随后是若干 +fire-and-forget 的元数据补丁,费用由专门的函数绕过写缓冲直写,最后才是带 `status_code` 的终态补丁。 +终态点分散在 `response-handler.ts`、`error-handler.ts`、各 guard 的拦截路径里,有七八处。 + +因此同步**不在代理热路径埋点**,而是由一个后台 worker 扫描 `message_request` 中尚未同步的行, +写入 ClickHouse 之后在行上打同步标记(`clickhouse_synced_at`)。 +好处: + +- 采集点只有一个。新增的拦截路径、未来的新字段都会自动覆盖,ClickHouse 的内容与 PostgreSQL 一致 +- PostgreSQL 本身就是持久缓冲。ClickHouse 宕机时行保持未标记,恢复后自动追平,不需要额外的落盘队列 +- 单 leader 大批量写入,正是 ClickHouse 偏好的写入方式 +- 对请求延迟零影响 + +代价是数据有分钟级延迟(默认约 2.5 分钟静置 + 一个同步周期,可调)。 + +## 配置 + +设置 `CLICKHOUSE_URL` 即启用,其余变量都有默认值。完整列表见 `.env.example`。 + +```bash +CLICKHOUSE_URL=http://clickhouse:8123 +CLICKHOUSE_USER=default +CLICKHOUSE_PASSWORD=secret +CLICKHOUSE_DATABASE=default +CLICKHOUSE_TABLE=cch_request_log +CLICKHOUSE_TTL_DAYS=180 # 可选;留空则不设 TTL +``` + +时序相关的可调项: + +| 变量 | 默认 | 含义 | +| --- | --- | --- | +| `CLICKHOUSE_SYNC_INTERVAL_MS` | 5000 | 同步周期 | +| `CLICKHOUSE_SYNC_BATCH_SIZE` | 5000 | 单批最大行数 | +| `CLICKHOUSE_SYNC_SETTLE_MS` | 150000 | 终态静置:终态后再等这么久才发送 | +| `CLICKHOUSE_SYNC_MAX_PENDING_AGE_MS` | 3600000 | 未终态行的最长等待时间 | + +前置条件:需要可用的 `REDIS_URL`,多副本部署靠 Redis leader lock 选出唯一同步者。 +同步进度本身存在 PostgreSQL(行上的标记 + 单行表 `clickhouse_sync_state`),Redis 丢失不影响进度。 + +旧版本的 `CLICKHOUSE_SYNC_LAG_MS`、`CLICKHOUSE_SYNC_MAX_PENDING` 已移除,留在 `.env` 里会被忽略。 + +表结构在首次运行时自动创建(`CREATE TABLE IF NOT EXISTS` + 逐列 `ADD COLUMN IF NOT EXISTS`), +不需要手工建表。 + +## 同步语义 + +每一行的同步状态记录在它自己身上: + +- `message_request.clickhouse_synced_at`:ClickHouse 确认写入这一行已静置的内容之后才设置 +- `clickhouse_sync_state.floor_at`(单行表):同步范围下界,`created_at` 早于它的行不同步、不回填 + +每一轮读取一批满足下列条件的行,写入 ClickHouse,然后给它们打标记: + +- `clickhouse_synced_at IS NULL`、未软删除、不是 warmup 抢答行 +- `created_at >= floor_at` +- `updated_at` 已经过了静置窗口 +- 已终态(`status_code IS NOT NULL`),或者已超过 `MAX_PENDING_AGE_MS` 的孤儿行 + +按 `(created_at, id)` 排序,由部分索引 `idx_message_request_clickhouse_unsynced` 支撑; +稳态下这个索引只包含在途的行。 + +### 为什么不用 id 游标 + +旧版按 `id > cursor` 增量读取,并假设游标以下不会再出现新行,靠 5 分钟回看延迟规避 `serial` +的提交序空洞。`MESSAGE_REQUEST_INSERT_MODE=async` 打破了这个假设:每个进程一次预留 128 个 id, +真正有请求时才使用其中一个并打上 `created_at`。流量不均时,一个进程预留的低 id 可以闲置数小时, +其间其他进程的高 id 已经被同步、游标已经越过;这些低 id 行之后才写入,就永远不会被选中。 +固定的回看延迟没有上界能覆盖"预留但未使用"的闲置时间,所以进度改为记录在行上, +选取条件不再依赖 id 或提交顺序。 + +### 静置与标记 + +一行"可发送"的条件是 `status_code IS NOT NULL`(仓库内统一的终态判定)且 `updated_at` 已经过了静置窗口。 +静置是必要的:终态之后 hedge 竞速败者的计费仍会落库,上限是 `HEDGE_LOSER_DRAIN_TIMEOUT_MS`(默认 120 秒), +过早同步会取到中间值。默认静置 150 秒 = 120 秒 + 30 秒余量;静置短于这个值时 worker 启动会告警。 + +标记语句只标记 `updated_at` 仍不晚于本轮静置截止点的行,并按 id 顺序 `FOR UPDATE SKIP LOCKED`: + +- 读取与标记之间被补写的行(`updated_at` 变成 `NOW()`)不会被标记,静置后带着新内容重新发送 +- 正被补写锁住的行留给下一轮,不会与写缓冲的多行 UPDATE 互相死锁 +- 标记不修改 `updated_at`(它是 ClickHouse 的版本列),也不触发 `message_request` 上按列声明的触发器 + +标记之后仍然落库的补写(超过静置窗口的迟到计费)不会再同步,这与旧版相同。 + +几个刻意的取舍: + +- **投递语义是"至少一次"**。ClickHouse 写入成功之后才打标记,进程在两者之间被杀会导致重复。 + 表引擎是 `ReplacingMergeTree(updated_at)`,排序键 `(created_at, id)`,重复行会在 merge 时折叠。 + 查询时用 `FINAL` 或按 `id` 去重可以立刻看到收敛后的结果。 + 如果 PostgreSQL 能读不能写(标记失败),worker 在进程内记住"已发送未标记"的行及其 `updated_at`, + 不会每轮重发同一批;内容变化过的行仍会重发。 +- **孤儿行有兜底**。进程崩溃后 `status_code` 会永久为 NULL,PostgreSQL 侧没有清扫者。 + 这类行等满 `MAX_PENDING_AGE_MS` 后按原样发出,`status_code` 落成 `0`。 + 查询时可以用 `status_code = 0` 把它们识别出来。 + 代价:万一这行后来真的终态了(例如超过一小时的流式请求),ClickHouse 里会留下 `0`。 +- **不同步的内容**:`message_request.key`(API key 原文,**绝不外发**)、 + `routing_trace` / `provider_chain` / `cost_breakdown` 等大 jsonb 字段、`error_stack`。 + `routing_trace` 在终态后最长 7 天内仍可能被 outbox 补写,不适合这套静置模型。 +- **排除 `blocked_by = 'warmup'`** 的抢答探测行,与 Dashboard 各类聚合口径保持一致。 +- **用户名/密钥名/供应商名是快照**。ClickHouse 无法回 JOIN PostgreSQL,所以同步时一并写入名称。 + 改名或删除之后,历史审计记录依然可读。三个维度表全部 LEFT JOIN,被拦截的行(`provider_id = 0`) + 和已删除的密钥都不会丢。 + +失败时的行为:ClickHouse 或 Redis 不可用,本周期直接放弃,行保持未标记,日志里每分钟最多一条告警。 +代理请求永远不受影响。 + +### 同步范围下界 + +首个 leader 初始化 `floor_at`,之后不再自动修改: + +- ClickHouse 已有数据:取其中最早的 `created_at`。这个范围内缺少标记的行会被(重新)发送 +- ClickHouse 为空:`now - MAX_PENDING_AGE_MS`,不回填历史,但覆盖正在进行中的请求 + +之后 ClickHouse 的 TTL 过期或手工导入都不会移动下界。需要调整时手工修改该行。 + +## 与日志清理的联动 + +启用同步后,`cleanupLogs` 会自动加上同步围栏:只允许删除 +**已打标记、已软删除、warmup,或 `created_at` 早于同步下界**的行。 +尚未确认进入 ClickHouse 的范围内行无论 id 大小都不会被删除,可以放心把 `cleanupRetentionDays` 调短。 + +如果同步下界尚未初始化或读不出来,清理会直接中止并告警——宁可多留数据,也不删可能还没同步的行。 +这意味着 ClickHouse 长期宕机时,PostgreSQL 会相应地多留数据。 + +缩短保留期之前请注意:Dashboard 的日志页和会话页读 `message_request`,可见历史会随之缩短; +排行榜、概览、配额走 `usage_ledger`,不受影响。 + +## 查询示例 + +```sql +-- 某个 IP 最近的请求 +SELECT created_at, user_name, key_name, model, status_code, cost_usd +FROM cch_request_log +WHERE client_ip = toIPv6('::ffff:203.0.113.9') + AND created_at > now() - INTERVAL 7 DAY +ORDER BY created_at DESC +LIMIT 100; + +-- 按用户和模型的用量 +SELECT user_name, model, count() AS requests, + sum(input_tokens) AS input, sum(output_tokens) AS output, + round(sum(cost_usd), 4) AS cost +FROM cch_request_log FINAL +WHERE created_at >= toStartOfMonth(now()) AND status_code = 200 +GROUP BY user_name, model +ORDER BY cost DESC; + +-- 一个用户用过哪些 IP +SELECT client_ip, count() AS requests, min(created_at) AS first_seen, max(created_at) AS last_seen +FROM cch_request_log +WHERE user_name = 'alice' AND created_at > now() - INTERVAL 30 DAY +GROUP BY client_ip +ORDER BY last_seen DESC; +``` + +`client_ip` 是 `IPv6` 列,IPv4 以 IPv4-mapped 形式(`::ffff:a.b.c.d`)存储,未知 IP 是 `::`。 +统一成一种表示之后按 CIDR 过滤才不会漏: + +```sql +WHERE isIPAddressInRange(toString(client_ip), '::ffff:10.0.0.0/104') +``` + +## 实现位置 + +| 文件 | 职责 | +| --- | --- | +| `src/lib/clickhouse/config.ts` | 读 env,`isClickHouseSyncEnabled()` | +| `src/lib/clickhouse/client.ts` | 基于 `fetch` 的 HTTP 客户端(无 SDK 依赖) | +| `src/lib/clickhouse/schema.ts` | DDL 生成与 `ensureSchema()` | +| `src/lib/clickhouse/row-mapper.ts` | PostgreSQL 行 -> ClickHouse 行(纯函数) | +| `src/lib/clickhouse/source.ts` | PostgreSQL 侧查询:未同步行、同步标记、下界读写 | +| `src/lib/clickhouse/sync-state.ts` | 同步范围下界、清理围栏 | +| `src/lib/clickhouse/sync-worker.ts` | leader 选举与同步循环 | + +启动在 `src/instrumentation.ts`(生产与开发两处调度块);停止是 `src/lib/lifecycle/shutdown.ts` 的 +步骤 7d,位于关闭数据库连接池之前。 + +进程内状态可以通过 `getClickHouseSyncStatus()` 读取(同步下界、已发送未标记数、累计发送量、最近错误)。 + +## 运维要点 + +- 同步进度在 PostgreSQL 里,Redis 被清不会造成缺口,也不会触发回看重扫。 +- ClickHouse 表为空且没有下界时,从 `now - MAX_PENDING_AGE_MS` 开始,**不回填历史**。 + 需要回填更早的历史,把 `clickhouse_sync_state.floor_at` 改早即可,缺少标记的行会被自动发送。 +- `CLICKHOUSE_TTL_DAYS` 只在建表时生效。表已存在时不会自动 `MODIFY TTL`(那会触发数据分片重写), + 只会在日志里给出待执行的 `ALTER` 语句。 +- 只有后台任务进程(`CCH_MULTICORE_BACKGROUND_OWNER != "0"`)会参与同步; + 跨副本再由 Redis leader lock 收敛到唯一一个。 +- 迁移 `0128_clickhouse_sync_marker` 会在 `message_request` 上建部分索引,大表上会持有数秒 SHARE 锁。 + 繁忙或多副本部署请先按迁移文件头部的语句 `CREATE INDEX CONCURRENTLY` 预建索引,再部署。 + +### 从 id 游标版本升级 + +升级后第一个 leader 会忽略 Redis 里旧的 `clickhouse_sync:state`(并删除它),以 ClickHouse 中最早的 +`created_at` 作为下界,然后把范围内所有尚未打标记的行重新发送一遍。这一步就是对旧版漏同步行的幂等回填: + +- 已在 ClickHouse 里的行会多出一条 `(created_at, id, updated_at)` 相同的副本,merge 时折叠 +- 首次发送之后在 PostgreSQL 里又被更新过的行(包括后来终态的孤儿行)会被纠正 +- 速度上限是每个周期 `SYNC_BATCH_SIZE × 20` 行(默认每 5 秒 10 万行),几十万行在数分钟内完成 + +完成后可以执行 `OPTIMIZE TABLE . FINAL` 提前折叠副本。 +升级前请确认没有按旧版 `id <= 游标` 围栏运行的短保留期清理,它可能已经删掉部分漏同步的行,这些行无法恢复。 + +### 核对缺口 + +```sql +-- PostgreSQL:应当同步但尚未标记的行(稳态下应接近 0) +SELECT count(*) FROM message_request +WHERE clickhouse_synced_at IS NULL + AND deleted_at IS NULL + AND (blocked_by IS NULL OR blocked_by <> 'warmup') + AND created_at >= (SELECT floor_at FROM clickhouse_sync_state) + AND status_code IS NOT NULL + AND updated_at < now() - interval '5 minutes'; +``` + +```sql +-- ClickHouse:按 id 去重后的行数,与 PostgreSQL 范围内的行数对比 +SELECT count() FROM cch_request_log FINAL WHERE created_at >= ''; +``` diff --git a/docs/k8s-deployment.md b/docs/k8s-deployment.md index 853c3476c1..b9a7b596ef 100644 --- a/docs/k8s-deployment.md +++ b/docs/k8s-deployment.md @@ -239,6 +239,10 @@ WebSocket 连接仍具有单 worker 亲和性,压测时应同时检查每个进 `stream disconnected before completion: WebSocket protocol error: Connection reset without closing handshake`(直连 CCH 正常,经反代失败时多半是这一项)。 +> 配置了 `PROXY_LISTEN_PREFIX` 时,反代还需为 `${prefix}/v1/responses` 增加同样的 +> WebSocket location(例如 `location ~ ^(/gateway)?/v1/responses`),否则前缀路径下 +> 的 Codex 客户端会在升级阶段失败。 + **Nginx** 建议在 `http {}` 中先定义连接头映射,避免普通 HTTP 请求也被写死为 diff --git a/docs/multicore-gateway.md b/docs/multicore-gateway.md index ac5fe971ad..5953c57722 100644 --- a/docs/multicore-gateway.md +++ b/docs/multicore-gateway.md @@ -101,6 +101,10 @@ Provider group 倍率缓存额外使用版本号阻止“更新通知到达后 例如默认 2 worker 时,`DB_POOL_MAX=20` 变成 10/10;3 worker 时是 7/7/6。Kubernetes 的多个 Pod 仍各自拥有一份聚合预算,所以数据库总连接上限需要按 Pod 数继续核算。 +分摊后每个 worker 的连接还会再拆成 data/control/writer 三条 pool。worker 数较多而 `DB_POOL_MAX` 保持默认时(例如 6 worker × 20 → 每 worker 3 条,即 data 1 / control 1 / writer 1),代理热路径上的数据库读取会在唯一的 data 连接上排队,worker 启动时会打印 `[Multicore] Database pool budget per worker is very small` 告警。建议按每 worker 约 5 条再加 4 设置(6 worker 约 34),并确认 PostgreSQL `max_connections` 覆盖「副本数 × DB_POOL_MAX」加上管理连接余量。launcher 不会自动提高默认值,以免在未调整 `max_connections` 的环境里超出连接上限。 + +`MESSAGE_REQUEST_INSERT_MAX_PENDING` 与 `MESSAGE_REQUEST_INSERT_ID_CHUNK_SIZE`(`MESSAGE_REQUEST_INSERT_MODE=async` 时生效)按进程生效,不参与分摊:前者达到上限时新请求回落为同步 INSERT,不会丢数据。 + 显式 worker 数超过内存容量或无法为每个 worker 生成合法分片时,launcher 会 fail fast,而不是悄悄启动一个可能 OOM 或配置校验失败的集群。自动模式则安全回退单进程。每请求正文上限、单响应上限等请求级限制不会分摊。 ### 为什么不用正文 IPC diff --git a/docs/research/clickhouse-id-cursor-root-cause-handoff.md b/docs/research/clickhouse-id-cursor-root-cause-handoff.md new file mode 100644 index 0000000000..577c951fbb --- /dev/null +++ b/docs/research/clickhouse-id-cursor-root-cause-handoff.md @@ -0,0 +1,43 @@ +# ClickHouse 请求日志漏同步:根因分析 handoff + +> 2026-09-26 调查;针对 CCH 0.9.5,工作树和线上 CPS、cch-prod-la 的 `.next/BUILD_ID` 均为 `E4CIgiz29Ypt-ij2pcbKj`。本文是给接手分析/修复的工程师的事实、复现与验收边界,不代表已经修复。 + +## 结论:先看 ID 预取与游标,不要先排查网络 + +`MESSAGE_REQUEST_INSERT_MODE=async` 时,每个应用进程向 PostgreSQL sequence **成批预取** `message_request.id`,稍后请求到达才使用其中一个 ID 并 INSERT。ClickHouse worker 却假设按 `id` 前进的游标之后不会再出现更小的新行。多进程流量不均时,高 ID 所属进程先写入并被同步,低 ID 所属进程可以闲置数小时后才使用早已预留的 ID;新行因此落在 `cursor` 以下,永远不被增量查询选中。5 分钟 `created_at` 延迟不能限制“预留但未使用”的 ID 会闲置多久。 + +这是**静默漏数**,不是同步器停机:Redis 的 `pending` 只含已经被查询到、但未终态或未静置的行,无法代表尚不存在的预留 ID。ClickHouse 连通且 CCH readiness 为 healthy 时仍会漏行。 + +### 线上证据(2026-09-26 约 15:40 UTC,只读) + +| 实例 | INSERT 模式/并行度 | CH 表及已推进最高 ID | 后续 PG 行与结果 | +| --- | --- | --- | --- | +| CPS(原 38 的 CCH,PG 位于 38) | `async`;16 workers;每进程默认预取 128 ID | dslab `cch.cch_logs`,Redis `clickhouse_sync:state` 为 `{"cursor":651558,"pending":[]}`;CH 最后 `created_at` 为 10:00:59 UTC,`synced_at` 为 10:06 UTC | 至少 100 条在此后创建、已终态且静置、未软删/非 warmup 的 PG 行,其 ID 不大于游标。例:PG ID `651314`、HTTP 200,CH 按 ID 查询为 0 行。 | +| cch-prod-la | `async`;4 workers;每进程默认预取 128 ID | GCE `cch.cch_prod_la`,CH 最高 ID `287186`;最后 `created_at` 为 13:51:19 UTC、`synced_at` 为 13:56 UTC | 此后约 97 条已终态且静置的 PG 行的 ID 不大于 CH 最高 ID(其中一行是 CH 时间精度截断导致的边界重叠);例:PG ID `286991`、HTTP 200,CH 按 ID 查询为 0 行。 | + +CPS 从应用主机使用现有凭据直接查询 dslab ClickHouse 成功;两个实例的数据库序列 `cache_size=1`,**不是 PostgreSQL sequence cache 配错**。异步预取发生在应用层。ID 跨 worker 乱序的具体对应关系未记录 worker slot;上表的代码机制与实测 ID/时间关系吻合,不要把某个具体 ID 归到某个 worker 当作已观测事实。 + +另外 `cch-daily-la`、`cch.in.dslab.top` 也启用了异步 INSERT。它们的 CH 最新时间在推进,只能证明同步没整体停摆,**不能证明没有散落的低 ID 漏行**。`cch-3rd-la` 是现行 `cch-jp.zenkexi.com` 的 Cloudflare tunnel 源站;旧 `cch_jp` 表在 09-22 停止不属于此故障。ThinkPad CCH 未配置 ClickHouse。 + +## 必读源码:具体哪条不变量被破坏 + +1. `src/repository/message-insert-buffer.ts:73-151`:异步开关、`nextval(... ) FROM generate_series(1, chunkSize)`、每进程 `availableIds`、使用 ID 时才 `createdAt = new Date()`。默认 chunk 128 在 `src/lib/config/env.schema.ts:101-110`;`src/repository/message.ts:333-350` 优先走该缓冲,失败才同步 INSERT。 +2. `src/repository/message-insert-buffer.ts:199-231`:真正提交发生在后续 multi-row flush,保留预先取得的 ID。缓冲 flush 很快也**不能解决闲置预留 ID**:行甚至尚未 enqueue。 +3. `src/lib/clickhouse/source.ts:80-92`:只查 `id > cursor`,按 ID 升序;排除软删及 `blocked_by='warmup'`。 +4. `src/lib/clickhouse/sync-worker.ts:89-154`:按 `createdAt` 做 5 分钟 lag,推进到看到的最大 ID;`pending` 只包含已看到但不可发送的行,写 CH 后才落 Redis 状态。 +5. `src/lib/clickhouse/sync-state.ts:13-27,98-159`:Redis 游标与恢复策略;状态丢失时用 CH 最新时间往前回看 `MAX_PENDING_AGE_MS`(默认一小时),**不足以补数小时前漏掉的 ID**。 +6. `src/lib/log-cleanup/service.ts:90-100,140-149`:清理仅以 `id <= fence` 保证“已同步”;该不变量目前为假,达到保留期后可能永久删掉尚未进 CH 的 PG 行。 +7. `docs/clickhouse-request-log-sync.md:53-95`:现有设计文档声称 5 分钟 lag 可以避开 `serial` 的乱序空洞;该解释没有考虑应用进程预留 128 ID 且空闲任意时长的路径。 +8. `tests/unit/lib/clickhouse/sync-worker.test.ts:163-215`:现有测试仅用已可见的顺序 ID,未模拟低 ID 在游标超过之后才 INSERT。 + +最小反例:进程 A 先预留 `1..128` 但没有请求;进程 B 预留 `129..256` 并写入 `129`;五分钟后同步器看到 `129`,将游标推进到 `129`,`pending=[]`;数小时后 A 首次请求才写入 `id=1, created_at=此时`。`fetchBatchAfter(129)` 不会看到它。增加固定 lag 或修改 PG sequence cache 都没有有限上界保证。 + +## 接手任务与安全边界 + +- **先确认缺口**:按源 PG 的 `id`、`created_at`、`status_code`、`updated_at`、`deleted_at`、`blocked_by` 与目标 CH 的 ID 作差集,窗口覆盖 CH 已越过的历史 ID,而不是只看两边最新时间/行数。CH 用 `ReplacingMergeTree(updated_at)`、排序键 `(created_at,id)`;普通 `count()` 在 merge 前可能含重投行,验收用按 `(created_at,id)` 去重或 `FINAL`。源查询必须继续排除软删和 warmup,且不得将 API key 原文发往 CH。 +- **立即止血与补数是两件事**:暂切 `MESSAGE_REQUEST_INSERT_MODE=sync` 并重启所有受影响 worker,可以使后续请求由 DB 在 INSERT 时取 ID,但会增加请求写库延迟;还需要另行对 PG 仍保留的漏行做幂等回填。不要误称切模式即可修复既有缺口。 +- **不要盲清或回退 Redis 游标**:一小时 watermark 恢复可能漏掉更旧行;重复导入需处理 CH 现有主键/版本语义。先保护 PG 留存,尤其不要运行会受错误 `fence` 放行的清理来消耗尚未回填的历史记录。 +- **永久修复需恢复可证明的不变量**:可用数据库提交顺序安全的 outbox/逐行同步标记与确认,或一个有严格保留、重扫和去重语义且不依赖预留 ID 单调性的方案。不能仅把 lag 从 5 分钟调大、只记录已存在行的 `pending`、或只在某些 UA/实例上特判。同步进度与 `cleanupLogs` 的删除围栏须一起重新设计。 +- **验收测试**:两进程预留并乱序消费 ID;游标越过后才出现的低 ID;跨多个周期和数小时闲置(用虚拟时钟);重启/Redis 状态丢失;CH 插入成功但状态落盘前失败的重投;软删/warmup 排除;清理围栏不得覆盖任何尚未确认入 CH 的行。用这一反例测试证明旧实现会漏、新实现不漏;再比较线上 PG 与 CH 的可同步行差集,并观察至少两个同步周期的新请求。 + +本 handoff 未修改应用源码、线上 INSERT 模式、Redis 游标或历史 ClickHouse 数据。 diff --git a/drizzle/0119_common_madame_masque.sql b/drizzle/0119_common_madame_masque.sql new file mode 100644 index 0000000000..57bb5d118c --- /dev/null +++ b/drizzle/0119_common_madame_masque.sql @@ -0,0 +1,82 @@ +ALTER TABLE "providers" ADD COLUMN "reasoning_effort_override_rules" jsonb; + +UPDATE "providers" +SET "reasoning_effort_override_rules" = CASE + WHEN "provider_type" = 'codex' + AND "codex_reasoning_effort_preference" IN ('none', 'minimal', 'low', 'medium', 'high', 'xhigh') + THEN jsonb_build_array( + jsonb_build_object( + 'when', '{}'::jsonb, + 'overrideEffort', "codex_reasoning_effort_preference" + ) + ) + WHEN "provider_type" IN ('claude', 'claude-auth') + AND jsonb_typeof("anthropic_adaptive_thinking") = 'object' + AND "anthropic_adaptive_thinking"->>'effort' IN ('low', 'medium', 'high', 'xhigh', 'max') + AND jsonb_typeof("anthropic_adaptive_thinking"->'models') = 'array' + AND NOT EXISTS ( + SELECT 1 + FROM jsonb_array_elements("anthropic_adaptive_thinking"->'models') AS model(value) + WHERE jsonb_typeof(model.value) <> 'string' + ) + AND "anthropic_adaptive_thinking"->>'modelMatchMode' = 'all' + THEN jsonb_build_array( + jsonb_build_object( + 'when', '{}'::jsonb, + 'overrideEffort', "anthropic_adaptive_thinking"->>'effort' + ) + ) + WHEN "provider_type" IN ('claude', 'claude-auth') + AND jsonb_typeof("anthropic_adaptive_thinking") = 'object' + AND "anthropic_adaptive_thinking"->>'effort' IN ('low', 'medium', 'high', 'xhigh', 'max') + AND jsonb_typeof("anthropic_adaptive_thinking"->'models') = 'array' + AND NOT EXISTS ( + SELECT 1 + FROM jsonb_array_elements("anthropic_adaptive_thinking"->'models') AS model(value) + WHERE jsonb_typeof(model.value) <> 'string' + ) + AND "anthropic_adaptive_thinking"->>'modelMatchMode' = 'specific' + THEN COALESCE( + ( + SELECT jsonb_agg( + jsonb_build_object( + 'when', jsonb_build_object( + 'originalModel', jsonb_build_object( + 'matchType', rule.match_type, + 'pattern', rule.pattern + ) + ), + 'overrideEffort', "anthropic_adaptive_thinking"->>'effort' + ) + ORDER BY model.ordinality, rule.rule_order + ) + FROM jsonb_array_elements_text("anthropic_adaptive_thinking"->'models') + WITH ORDINALITY AS model(model, ordinality) + CROSS JOIN LATERAL ( + VALUES + (0, 'exact'::text, model.model), + (1, 'prefix'::text, model.model || '-') + ) AS rule(rule_order, match_type, pattern) + ), + '[]'::jsonb + ) +END +WHERE "reasoning_effort_override_rules" IS NULL + AND ( + ( + "provider_type" = 'codex' + AND "codex_reasoning_effort_preference" IN ('none', 'minimal', 'low', 'medium', 'high', 'xhigh') + ) + OR ( + "provider_type" IN ('claude', 'claude-auth') + AND jsonb_typeof("anthropic_adaptive_thinking") = 'object' + AND "anthropic_adaptive_thinking"->>'effort' IN ('low', 'medium', 'high', 'xhigh', 'max') + AND jsonb_typeof("anthropic_adaptive_thinking"->'models') = 'array' + AND NOT EXISTS ( + SELECT 1 + FROM jsonb_array_elements("anthropic_adaptive_thinking"->'models') AS model(value) + WHERE jsonb_typeof(model.value) <> 'string' + ) + AND "anthropic_adaptive_thinking"->>'modelMatchMode' IN ('all', 'specific') + ) + ); diff --git a/drizzle/0122_overwrite_response_model.sql b/drizzle/0122_overwrite_response_model.sql new file mode 100644 index 0000000000..4696d85315 --- /dev/null +++ b/drizzle/0122_overwrite_response_model.sql @@ -0,0 +1 @@ +ALTER TABLE "providers" ADD COLUMN IF NOT EXISTS "overwrite_response_model" boolean DEFAULT false NOT NULL; diff --git a/drizzle/0123_provider_new_api_access_token.sql b/drizzle/0123_provider_new_api_access_token.sql deleted file mode 100644 index 2737d2229d..0000000000 --- a/drizzle/0123_provider_new_api_access_token.sql +++ /dev/null @@ -1,2 +0,0 @@ -ALTER TABLE "providers" ADD COLUMN "new_api_access_token" varchar;--> statement-breakpoint -ALTER TABLE "providers" ADD COLUMN "new_api_user_id" integer; \ No newline at end of file diff --git a/drizzle/0124_cool_the_call.sql b/drizzle/0124_cool_the_call.sql new file mode 100644 index 0000000000..6901bcdb5a --- /dev/null +++ b/drizzle/0124_cool_the_call.sql @@ -0,0 +1,8 @@ +ALTER TYPE "public"."notification_type" ADD VALUE 'client_problem';--> statement-breakpoint +ALTER TABLE "notification_settings" ADD COLUMN "client_problem_enabled" boolean DEFAULT false NOT NULL;--> statement-breakpoint +ALTER TABLE "notification_settings" ADD COLUMN "client_problem_webhook" varchar(512);--> statement-breakpoint +ALTER TABLE "notification_settings" ADD COLUMN "client_problem_count_threshold" integer DEFAULT 10;--> statement-breakpoint +ALTER TABLE "notification_settings" ADD COLUMN "client_problem_window_minutes" integer DEFAULT 5;--> statement-breakpoint +ALTER TABLE "notification_settings" ADD COLUMN "client_problem_cyber_count_threshold" integer DEFAULT 3;--> statement-breakpoint +ALTER TABLE "notification_settings" ADD COLUMN "client_problem_cyber_window_minutes" integer DEFAULT 5;--> statement-breakpoint +ALTER TABLE "notification_settings" ADD COLUMN "title_prefix" varchar(64); \ No newline at end of file diff --git a/drizzle/0124_system_settings_memory_admission.sql b/drizzle/0124_system_settings_memory_admission.sql deleted file mode 100644 index 87658698f7..0000000000 --- a/drizzle/0124_system_settings_memory_admission.sql +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE "system_settings" ADD COLUMN "enable_memory_admission" boolean DEFAULT false NOT NULL; \ No newline at end of file diff --git a/drizzle/0125_ledger_terminal_projection.sql b/drizzle/0125_ledger_terminal_projection.sql new file mode 100644 index 0000000000..bf5addc675 --- /dev/null +++ b/drizzle/0125_ledger_terminal_projection.sql @@ -0,0 +1,220 @@ +-- Ledger projection only for finalized message_request rows, and skip batched updates whose +-- projected columns did not change. Canonical copy: src/lib/ledger-backfill/trigger.sql. +-- +-- Effect: a normal request writes one usage_ledger row at completion instead of an insert at +-- request start plus one upsert per batched update. In-flight requests appear in usage_ledger +-- only once they finish; abandoned rows are filled by the ledger backfill. +-- +-- Rollback: re-apply the fn_upsert_usage_ledger function definition from +-- drizzle/0116_gigantic_zombie.sql (idempotent, journal untouched), then run the ledger backfill. +CREATE OR REPLACE FUNCTION fn_upsert_usage_ledger() +RETURNS TRIGGER AS $$ +DECLARE + v_final_provider_id integer; + v_is_success boolean; + v_success_rate_outcome varchar; +BEGIN + -- Gate 1: only finalized requests are projected. An in-flight INSERT (and in-flight metadata + -- updates) no longer write a ledger row that is rewritten again at completion. Every + -- finalization changes at least one of blocked_by/status_code/error_message/provider_chain, + -- all of which are in the trigger column list, so the finalizing UPDATE always reaches the + -- upsert below. Rows abandoned before finalization are filled by the ledger backfill. + IF NOT fn_is_message_request_finalized( + NEW.blocked_by, + NEW.status_code, + NEW.provider_chain, + NEW.error_message + ) THEN + RETURN NEW; + END IF; + + -- Gate 2: batched CASE updates list every patched column for every row of the batch, which + -- fires this trigger for rows whose projected values did not change. Skip those rows. The + -- compared columns are exactly the trigger's UPDATE OF column list. + IF TG_OP = 'UPDATE' AND + ROW(NEW.blocked_by, NEW.status_code, NEW.error_message, NEW.provider_chain, + NEW.actual_response_model, NEW.endpoint, NEW.provider_id, NEW.user_id, NEW."key", + NEW.model, NEW.original_model, NEW.api_type, NEW.session_id, NEW.session_identity, + NEW.session_identity_kind, NEW.affinity_scope_tag, NEW.affinity_fingerprint, + NEW.affinity_fingerprint_chain, NEW.is_replay, NEW.replay_source_request_id, NEW.cost_usd, + NEW.cost_multiplier, NEW.group_cost_multiplier, NEW.input_tokens, NEW.output_tokens, + NEW.cache_creation_input_tokens, NEW.cache_read_input_tokens, + NEW.cache_creation_5m_input_tokens, NEW.cache_creation_1h_input_tokens, + NEW.cache_ttl_applied, NEW.context_1m_applied, NEW.swap_cache_ttl_applied, NEW.duration_ms, + NEW.ttfb_ms, NEW.first_byte_ms, NEW.client_ip, NEW.created_at) + IS NOT DISTINCT FROM + ROW(OLD.blocked_by, OLD.status_code, OLD.error_message, OLD.provider_chain, + OLD.actual_response_model, OLD.endpoint, OLD.provider_id, OLD.user_id, OLD."key", + OLD.model, OLD.original_model, OLD.api_type, OLD.session_id, OLD.session_identity, + OLD.session_identity_kind, OLD.affinity_scope_tag, OLD.affinity_fingerprint, + OLD.affinity_fingerprint_chain, OLD.is_replay, OLD.replay_source_request_id, OLD.cost_usd, + OLD.cost_multiplier, OLD.group_cost_multiplier, OLD.input_tokens, OLD.output_tokens, + OLD.cache_creation_input_tokens, OLD.cache_read_input_tokens, + OLD.cache_creation_5m_input_tokens, OLD.cache_creation_1h_input_tokens, + OLD.cache_ttl_applied, OLD.context_1m_applied, OLD.swap_cache_ttl_applied, OLD.duration_ms, + OLD.ttfb_ms, OLD.first_byte_ms, OLD.client_ip, OLD.created_at) THEN + RETURN NEW; + END IF; + + v_success_rate_outcome := fn_compute_message_request_success_rate_outcome( + NEW.blocked_by, + NEW.status_code, + NEW.error_message, + NEW.provider_chain + ); + + IF NEW.blocked_by = 'warmup' THEN + -- If a ledger row already exists (row was originally non-warmup), mark it as warmup + -- and sync the latest actual_response_model so audit stays consistent across tables. + -- On INSERT no ledger row can exist yet, but the early RETURN still applies so warmup + -- rows never reach the upsert. + IF TG_OP = 'UPDATE' THEN + UPDATE usage_ledger + SET blocked_by = 'warmup', + success_rate_outcome = v_success_rate_outcome, + actual_response_model = NEW.actual_response_model + WHERE request_id = NEW.id; + END IF; + RETURN NEW; + END IF; + + IF LOWER(REGEXP_REPLACE(COALESCE(NEW.endpoint, ''), '/+$', '')) + IN ('/v1/messages/count_tokens', '/v1/responses/compact') THEN + -- Non-billing endpoints never keep a ledger row. On INSERT there is nothing to delete, but + -- the early RETURN still applies. + IF TG_OP = 'UPDATE' THEN + DELETE FROM usage_ledger WHERE request_id = NEW.id; + END IF; + RETURN NEW; + END IF; + + IF NEW.provider_chain IS NOT NULL + AND jsonb_typeof(NEW.provider_chain) = 'array' + AND jsonb_array_length(NEW.provider_chain) > 0 + AND jsonb_typeof(NEW.provider_chain -> -1) = 'object' + AND (NEW.provider_chain -> -1 ? 'id') + AND (NEW.provider_chain -> -1 ->> 'id') ~ '^[0-9]+$' THEN + v_final_provider_id := (NEW.provider_chain -> -1 ->> 'id')::integer; + ELSE + v_final_provider_id := NEW.provider_id; + END IF; + + v_is_success := (NEW.error_message IS NULL OR NEW.error_message = '') + AND (NEW.status_code IS NULL OR NEW.status_code < 400); + + INSERT INTO usage_ledger ( + request_id, user_id, key, provider_id, final_provider_id, + model, original_model, actual_response_model, endpoint, api_type, session_id, + session_identity, session_identity_kind, affinity_scope_tag, + affinity_fingerprint, affinity_fingerprint_chain, is_replay, replay_source_request_id, + status_code, is_success, success_rate_outcome, blocked_by, + cost_usd, cost_multiplier, group_cost_multiplier, + input_tokens, output_tokens, + cache_creation_input_tokens, cache_read_input_tokens, + cache_creation_5m_input_tokens, cache_creation_1h_input_tokens, + cache_ttl_applied, context_1m_applied, swap_cache_ttl_applied, + duration_ms, ttfb_ms, first_byte_ms, client_ip, created_at + ) VALUES ( + NEW.id, NEW.user_id, NEW.key, NEW.provider_id, v_final_provider_id, + NEW.model, NEW.original_model, NEW.actual_response_model, NEW.endpoint, NEW.api_type, NEW.session_id, + NEW.session_identity, NEW.session_identity_kind, NEW.affinity_scope_tag, + NEW.affinity_fingerprint, NEW.affinity_fingerprint_chain, NEW.is_replay, NEW.replay_source_request_id, + NEW.status_code, v_is_success, v_success_rate_outcome, NEW.blocked_by, + CASE WHEN NEW.is_replay THEN 0 ELSE NEW.cost_usd END, + NEW.cost_multiplier, NEW.group_cost_multiplier, + NEW.input_tokens, NEW.output_tokens, + NEW.cache_creation_input_tokens, NEW.cache_read_input_tokens, + NEW.cache_creation_5m_input_tokens, NEW.cache_creation_1h_input_tokens, + NEW.cache_ttl_applied, NEW.context_1m_applied, NEW.swap_cache_ttl_applied, + NEW.duration_ms, NEW.ttfb_ms, NEW.first_byte_ms, NEW.client_ip, NEW.created_at + ) + ON CONFLICT (request_id) DO UPDATE SET + user_id = EXCLUDED.user_id, + key = EXCLUDED.key, + provider_id = EXCLUDED.provider_id, + final_provider_id = EXCLUDED.final_provider_id, + model = EXCLUDED.model, + original_model = EXCLUDED.original_model, + actual_response_model = EXCLUDED.actual_response_model, + endpoint = EXCLUDED.endpoint, + api_type = EXCLUDED.api_type, + session_id = EXCLUDED.session_id, + session_identity = EXCLUDED.session_identity, + session_identity_kind = EXCLUDED.session_identity_kind, + affinity_scope_tag = EXCLUDED.affinity_scope_tag, + affinity_fingerprint = EXCLUDED.affinity_fingerprint, + affinity_fingerprint_chain = EXCLUDED.affinity_fingerprint_chain, + is_replay = EXCLUDED.is_replay, + replay_source_request_id = EXCLUDED.replay_source_request_id, + status_code = EXCLUDED.status_code, + is_success = EXCLUDED.is_success, + success_rate_outcome = EXCLUDED.success_rate_outcome, + blocked_by = EXCLUDED.blocked_by, + cost_usd = EXCLUDED.cost_usd, + cost_multiplier = EXCLUDED.cost_multiplier, + group_cost_multiplier = EXCLUDED.group_cost_multiplier, + input_tokens = EXCLUDED.input_tokens, + output_tokens = EXCLUDED.output_tokens, + cache_creation_input_tokens = EXCLUDED.cache_creation_input_tokens, + cache_read_input_tokens = EXCLUDED.cache_read_input_tokens, + cache_creation_5m_input_tokens = EXCLUDED.cache_creation_5m_input_tokens, + cache_creation_1h_input_tokens = EXCLUDED.cache_creation_1h_input_tokens, + cache_ttl_applied = EXCLUDED.cache_ttl_applied, + context_1m_applied = EXCLUDED.context_1m_applied, + swap_cache_ttl_applied = EXCLUDED.swap_cache_ttl_applied, + duration_ms = EXCLUDED.duration_ms, + ttfb_ms = EXCLUDED.ttfb_ms, + first_byte_ms = EXCLUDED.first_byte_ms, + client_ip = EXCLUDED.client_ip; + -- created_at deliberately NOT updated on conflict: it represents the + -- original insert time of the ledger row, which is immutable by design. + + RETURN NEW; +EXCEPTION WHEN OTHERS THEN + RAISE WARNING 'fn_upsert_usage_ledger failed for request_id=%: %', NEW.id, SQLERRM; + RETURN NEW; +END; +$$ LANGUAGE plpgsql;--> statement-breakpoint +DROP TRIGGER IF EXISTS trg_upsert_usage_ledger ON message_request;--> statement-breakpoint +CREATE TRIGGER trg_upsert_usage_ledger +AFTER INSERT OR UPDATE OF + blocked_by, + status_code, + error_message, + provider_chain, + actual_response_model, + endpoint, + provider_id, + user_id, + "key", + model, + original_model, + api_type, + session_id, + session_identity, + session_identity_kind, + affinity_scope_tag, + affinity_fingerprint, + affinity_fingerprint_chain, + is_replay, + replay_source_request_id, + cost_usd, + cost_multiplier, + group_cost_multiplier, + input_tokens, + output_tokens, + cache_creation_input_tokens, + cache_read_input_tokens, + cache_creation_5m_input_tokens, + cache_creation_1h_input_tokens, + cache_ttl_applied, + context_1m_applied, + swap_cache_ttl_applied, + duration_ms, + ttfb_ms, + first_byte_ms, + client_ip, + created_at +ON message_request +FOR EACH ROW +EXECUTE FUNCTION fn_upsert_usage_ledger(); diff --git a/drizzle/0126_session_identity_prefix_indexes.sql b/drizzle/0126_session_identity_prefix_indexes.sql new file mode 100644 index 0000000000..d91885aca5 --- /dev/null +++ b/drizzle/0126_session_identity_prefix_indexes.sql @@ -0,0 +1,3 @@ +-- Session identity prefix indexes are installed by the concurrent index preflight. +-- This migration records the schema revision without running a blocking CREATE INDEX. +SELECT 1; diff --git a/drizzle/0127_provider_prefix.sql b/drizzle/0127_provider_prefix.sql new file mode 100644 index 0000000000..22698ec89e --- /dev/null +++ b/drizzle/0127_provider_prefix.sql @@ -0,0 +1 @@ +ALTER TABLE "providers" ADD COLUMN IF NOT EXISTS "provider_prefix" varchar(64); diff --git a/drizzle/0128_clickhouse_sync_marker.sql b/drizzle/0128_clickhouse_sync_marker.sql new file mode 100644 index 0000000000..8f1017d6d7 --- /dev/null +++ b/drizzle/0128_clickhouse_sync_marker.sql @@ -0,0 +1,26 @@ +-- ClickHouse request-log sync: per-row sync marker replaces the id cursor. +-- +-- In MESSAGE_REQUEST_INSERT_MODE=async each process reserves ids in chunks and may use a +-- reserved id hours later, so "every row below the cursor has been synced" is false. The +-- sync worker now selects rows by clickhouse_synced_at IS NULL and marks them only after +-- ClickHouse acknowledged the insert; log cleanup only deletes marked (or out-of-scope) rows. +-- +-- ADD COLUMN (nullable, no default) is catalog-only. The partial index build takes a SHARE +-- lock on message_request for a few seconds on large tables, and Drizzle's migrator runs +-- inside a transaction so CREATE INDEX CONCURRENTLY cannot be inlined here. On busy or +-- multi-replica installs, pre-create the identical index BEFORE deploying (psql, outside a +-- transaction); IF NOT EXISTS below then turns the build into a no-op: +-- CREATE INDEX CONCURRENTLY IF NOT EXISTS "idx_message_request_clickhouse_unsynced" +-- ON "message_request" USING btree ("created_at","id") +-- WHERE "deleted_at" IS NULL AND "clickhouse_synced_at" IS NULL +-- AND ("blocked_by" IS NULL OR "blocked_by" <> 'warmup'); +-- (Requires the column first: ALTER TABLE "message_request" ADD COLUMN IF NOT EXISTS +-- "clickhouse_synced_at" timestamp with time zone;) +CREATE TABLE IF NOT EXISTS "clickhouse_sync_state" ( + "key" varchar(32) PRIMARY KEY DEFAULT 'default' NOT NULL, + "floor_at" timestamp with time zone NOT NULL, + "created_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +ALTER TABLE "message_request" ADD COLUMN IF NOT EXISTS "clickhouse_synced_at" timestamp with time zone;--> statement-breakpoint +CREATE INDEX IF NOT EXISTS "idx_message_request_clickhouse_unsynced" ON "message_request" USING btree ("created_at","id") WHERE "message_request"."deleted_at" IS NULL AND "message_request"."clickhouse_synced_at" IS NULL AND ("message_request"."blocked_by" IS NULL OR "message_request"."blocked_by" <> 'warmup'); diff --git a/drizzle/0129_upstream_quota_scheduling.sql b/drizzle/0129_upstream_quota_scheduling.sql new file mode 100644 index 0000000000..b193ec9eb7 --- /dev/null +++ b/drizzle/0129_upstream_quota_scheduling.sql @@ -0,0 +1,36 @@ +-- Upstream quota scheduling: probe Coding Plan windows (Kimi / Zhipu GLM / MiniMax / OpenCode Go) +-- and stop routing new sessions to providers whose remaining upstream quota is below a threshold. +CREATE TABLE IF NOT EXISTS "provider_upstream_quota_snapshots" ( + "provider_id" integer PRIMARY KEY NOT NULL, + "probe_type" varchar(20), + "windows" jsonb DEFAULT '[]'::jsonb NOT NULL, + "plan_level" varchar(128), + "credential_valid" boolean DEFAULT true NOT NULL, + "last_error" text, + "last_error_status" integer, + "fetched_at" timestamp with time zone, + "probed_at" timestamp with time zone NOT NULL, + "reactive_pause_until" timestamp with time zone, + "reactive_pause_reason" varchar(32), + "updated_at" timestamp with time zone DEFAULT now() NOT NULL +); +--> statement-breakpoint +ALTER TABLE "providers" ADD COLUMN IF NOT EXISTS "upstream_quota_probe_type" varchar(20) DEFAULT 'auto' NOT NULL;--> statement-breakpoint +ALTER TABLE "providers" ADD COLUMN IF NOT EXISTS "upstream_quota_threshold_percent" integer;--> statement-breakpoint +ALTER TABLE "providers" ADD COLUMN IF NOT EXISTS "upstream_quota_probe_options" jsonb;--> statement-breakpoint +ALTER TABLE "system_settings" ADD COLUMN IF NOT EXISTS "upstream_quota_scheduling_enabled" boolean DEFAULT false NOT NULL;--> statement-breakpoint +ALTER TABLE "system_settings" ADD COLUMN IF NOT EXISTS "upstream_quota_threshold_percent" integer DEFAULT 10 NOT NULL;--> statement-breakpoint +ALTER TABLE "system_settings" ADD COLUMN IF NOT EXISTS "upstream_quota_probe_interval_minutes" integer DEFAULT 10 NOT NULL;--> statement-breakpoint +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'provider_upstream_quota_snapshots_provider_id_providers_id_fk' + ) THEN + ALTER TABLE "provider_upstream_quota_snapshots" + ADD CONSTRAINT "provider_upstream_quota_snapshots_provider_id_providers_id_fk" + FOREIGN KEY ("provider_id") REFERENCES "public"."providers"("id") + ON DELETE cascade ON UPDATE no action; + END IF; +END $$; diff --git a/drizzle/0130_chunky_patch.sql b/drizzle/0130_chunky_patch.sql new file mode 100644 index 0000000000..653a58e0f4 --- /dev/null +++ b/drizzle/0130_chunky_patch.sql @@ -0,0 +1,2 @@ +-- Edge executor kill switch (default off: all traffic stays on the local proxy path). +ALTER TABLE "system_settings" ADD COLUMN IF NOT EXISTS "edge_execution_enabled" boolean DEFAULT false NOT NULL; diff --git a/drizzle/0122_session_identity_prefix_indexes.sql b/drizzle/0131_talented_adam_warlock.sql similarity index 91% rename from drizzle/0122_session_identity_prefix_indexes.sql rename to drizzle/0131_talented_adam_warlock.sql index e7be275d07..068f061e3b 100644 --- a/drizzle/0122_session_identity_prefix_indexes.sql +++ b/drizzle/0131_talented_adam_warlock.sql @@ -1,3 +1,7 @@ +ALTER TABLE "providers" ADD COLUMN "new_api_access_token" varchar;--> statement-breakpoint +ALTER TABLE "providers" ADD COLUMN "new_api_user_id" integer;--> statement-breakpoint +ALTER TABLE "system_settings" ADD COLUMN "enable_memory_admission" boolean DEFAULT false NOT NULL; +--> statement-breakpoint -- runMigrations() builds these indexes with CREATE INDEX CONCURRENTLY before this migration runs, -- so IF NOT EXISTS skips them there. Migrations applied outside runMigrations() create them here, -- and stop when an index is missing on a table larger than 64 MiB, because a plain CREATE INDEX @@ -18,7 +22,7 @@ BEGIN AND pg_relation_size(format('public.%I', spec.table_name)::regclass) > 64 * 1024 * 1024; IF concurrent_statements IS NOT NULL THEN - RAISE EXCEPTION 'Migration 0122 would block writes while building session prefix indexes on large tables' + RAISE EXCEPTION 'Migration 0131 would block writes while building session prefix indexes on large tables' USING DETAIL = E'Build them without blocking writes, then rerun this migration:\n' || concurrent_statements, HINT = 'runMigrations() (AUTO_MIGRATE=true or bun run db:migrate) builds these indexes with CREATE INDEX CONCURRENTLY automatically.'; END IF; @@ -30,4 +34,4 @@ CREATE INDEX IF NOT EXISTS "idx_usage_ledger_session_id_prefix" ON "public"."usa COMMENT ON INDEX "public"."idx_message_request_session_identity_prefix" IS 'cch:migration:0121:session-identity-prefix-index:v1';--> statement-breakpoint COMMENT ON INDEX "public"."idx_message_request_session_id_prefix_cover" IS 'cch:migration:0121:session-identity-prefix-index:v1';--> statement-breakpoint COMMENT ON INDEX "public"."idx_usage_ledger_session_identity_prefix" IS 'cch:migration:0121:session-identity-prefix-index:v1';--> statement-breakpoint -COMMENT ON INDEX "public"."idx_usage_ledger_session_id_prefix" IS 'cch:migration:0121:session-identity-prefix-index:v1'; +COMMENT ON INDEX "public"."idx_usage_ledger_session_id_prefix" IS 'cch:migration:0121:session-identity-prefix-index:v1'; \ No newline at end of file diff --git a/drizzle/0132_aspiring_hellcat.sql b/drizzle/0132_aspiring_hellcat.sql new file mode 100644 index 0000000000..fc41e89aec --- /dev/null +++ b/drizzle/0132_aspiring_hellcat.sql @@ -0,0 +1,2 @@ +-- Admin-authored Markdown appended to the model catalog (/v1/models/catalog and /models). +ALTER TABLE "system_settings" ADD COLUMN IF NOT EXISTS "agent_catalog_notes" text; diff --git a/drizzle/0133_slow_winter_soldier.sql b/drizzle/0133_slow_winter_soldier.sql new file mode 100644 index 0000000000..c2e5c11b01 --- /dev/null +++ b/drizzle/0133_slow_winter_soldier.sql @@ -0,0 +1,2 @@ +-- Case-insensitive model name lookups for price fallback matching. +CREATE INDEX IF NOT EXISTS "idx_model_prices_model_name_lower" ON "model_prices" USING btree (lower("model_name")); diff --git a/drizzle/0134_clammy_sir_ram.sql b/drizzle/0134_clammy_sir_ram.sql new file mode 100644 index 0000000000..2312904dbe --- /dev/null +++ b/drizzle/0134_clammy_sir_ram.sql @@ -0,0 +1,11 @@ +ALTER TABLE "providers" ADD COLUMN "service_tier_override_rules" jsonb; +UPDATE "providers" +SET "service_tier_override_rules" = jsonb_build_array( + jsonb_build_object( + 'when', '{}'::jsonb, + 'overrideServiceTier', "codex_service_tier_preference" + ) +) +WHERE "service_tier_override_rules" IS NULL + AND "provider_type" = 'codex' + AND "codex_service_tier_preference" IN ('auto', 'default', 'flex', 'priority'); diff --git a/drizzle/meta/0109_snapshot.json b/drizzle/meta/0109_snapshot.json index 96fc05b0da..71ddcdb2f5 100644 --- a/drizzle/meta/0109_snapshot.json +++ b/drizzle/meta/0109_snapshot.json @@ -4787,4 +4787,4 @@ "schemas": {}, "tables": {} } -} \ No newline at end of file +} diff --git a/drizzle/meta/0119_snapshot.json b/drizzle/meta/0119_snapshot.json index 39af8c9a9b..5c49dc28bb 100644 --- a/drizzle/meta/0119_snapshot.json +++ b/drizzle/meta/0119_snapshot.json @@ -1,5 +1,5 @@ { - "id": "12d909a0-d12b-4617-b7af-5a64a50201a7", + "id": "70b51b09-cd25-4cc7-b7fb-378a12a60734", "prevId": "0a61db74-24d2-408d-b247-974f2413fa50", "version": "7", "dialect": "postgresql", @@ -3300,6 +3300,12 @@ "notNull": false, "default": "'null'::jsonb" }, + "reasoning_effort_override_rules": { + "name": "reasoning_effort_override_rules", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, "gemini_google_search_preference": { "name": "gemini_google_search_preference", "type": "varchar(20)", @@ -4333,13 +4339,6 @@ "primaryKey": false, "notNull": false }, - "replay_cache_ttl_minutes": { - "name": "replay_cache_ttl_minutes", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 30 - }, "cache_effectiveness_enabled": { "name": "cache_effectiveness_enabled", "type": "boolean", diff --git a/drizzle/meta/0120_snapshot.json b/drizzle/meta/0120_snapshot.json index 728e3da759..ce0c388a79 100644 --- a/drizzle/meta/0120_snapshot.json +++ b/drizzle/meta/0120_snapshot.json @@ -1,6 +1,6 @@ { - "id": "5a73b354-be53-4ef5-bce8-e3eb4b3c3546", - "prevId": "12d909a0-d12b-4617-b7af-5a64a50201a7", + "id": "8c7b6672-bd57-4a03-8910-9a54d439315d", + "prevId": "70b51b09-cd25-4cc7-b7fb-378a12a60734", "version": "7", "dialect": "postgresql", "tables": { @@ -3300,6 +3300,12 @@ "notNull": false, "default": "'null'::jsonb" }, + "reasoning_effort_override_rules": { + "name": "reasoning_effort_override_rules", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, "gemini_google_search_preference": { "name": "gemini_google_search_preference", "type": "varchar(20)", @@ -5358,347 +5364,6 @@ "policies": {}, "checkConstraints": {}, "isRLSEnabled": false - }, - "public.outbox_events": { - "name": "outbox_events", - "schema": "public", - "columns": { - "id": { - "name": "id", - "type": "bigserial", - "primaryKey": true, - "notNull": true - }, - "event_id": { - "name": "event_id", - "type": "uuid", - "primaryKey": false, - "notNull": true, - "default": "gen_random_uuid()" - }, - "event_type": { - "name": "event_type", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "aggregate_type": { - "name": "aggregate_type", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "aggregate_id": { - "name": "aggregate_id", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "occurred_at": { - "name": "occurred_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "payload": { - "name": "payload", - "type": "jsonb", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "published_at": { - "name": "published_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "attempts": { - "name": "attempts", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": "0" - }, - "last_error": { - "name": "last_error", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "idx_outbox_events_unpublished": { - "name": "idx_outbox_events_unpublished", - "columns": [ - { - "expression": "id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {}, - "where": "\"outbox_events\".\"published_at\" IS NULL" - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "outbox_events_event_id_key": { - "name": "outbox_events_event_id_key", - "nullsNotDistinct": false, - "columns": [ - "event_id" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.outbox_processed": { - "name": "outbox_processed", - "schema": "public", - "columns": { - "event_id": { - "name": "event_id", - "type": "uuid", - "primaryKey": true, - "notNull": true - }, - "processed_at": { - "name": "processed_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.proj_applied_requests": { - "name": "proj_applied_requests", - "schema": "public", - "columns": { - "request_id": { - "name": "request_id", - "type": "bigint", - "primaryKey": true, - "notNull": true - }, - "event_id": { - "name": "event_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "applied_at": { - "name": "applied_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.avail_bucket_1m": { - "name": "avail_bucket_1m", - "schema": "public", - "columns": { - "provider_id": { - "name": "provider_id", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "bucket_start": { - "name": "bucket_start", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "success_cnt": { - "name": "success_cnt", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": "0" - }, - "failure_cnt": { - "name": "failure_cnt", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": "0" - }, - "excluded_cnt": { - "name": "excluded_cnt", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": "0" - }, - "latency_cnt": { - "name": "latency_cnt", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": "0" - }, - "latency_sum_ms": { - "name": "latency_sum_ms", - "type": "bigint", - "primaryKey": false, - "notNull": true, - "default": "0" - }, - "last_request_at": { - "name": "last_request_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "idx_avail_bucket_1m_time": { - "name": "idx_avail_bucket_1m_time", - "columns": [ - { - "expression": "bucket_start", - "isExpression": false, - "asc": false, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": { - "avail_bucket_1m_pkey": { - "name": "avail_bucket_1m_pkey", - "columns": [ - "provider_id", - "bucket_start" - ] - } - }, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.avail_current": { - "name": "avail_current", - "schema": "public", - "columns": { - "provider_id": { - "name": "provider_id", - "type": "integer", - "primaryKey": true, - "notNull": true - }, - "state": { - "name": "state", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'unknown'" - }, - "availability": { - "name": "availability", - "type": "double precision", - "primaryKey": false, - "notNull": true, - "default": "0" - }, - "request_count": { - "name": "request_count", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": "0" - }, - "last_request_at": { - "name": "last_request_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.projection_meta": { - "name": "projection_meta", - "schema": "public", - "columns": { - "key": { - "name": "key", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "value": { - "name": "value", - "type": "jsonb", - "primaryKey": false, - "notNull": true, - "default": "'{}'::jsonb" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false } }, "enums": { diff --git a/drizzle/meta/0121_snapshot.json b/drizzle/meta/0121_snapshot.json index 810de4af31..eba3609662 100644 --- a/drizzle/meta/0121_snapshot.json +++ b/drizzle/meta/0121_snapshot.json @@ -1,6 +1,6 @@ { - "id": "57129cf2-37e4-47e1-8f1e-b1c955914f9a", - "prevId": "5a73b354-be53-4ef5-bce8-e3eb4b3c3546", + "id": "ea6f89a9-567c-4b3d-9b57-e06d1cfdb6cf", + "prevId": "8c7b6672-bd57-4a03-8910-9a54d439315d", "version": "7", "dialect": "postgresql", "tables": { @@ -3354,6 +3354,12 @@ "notNull": false, "default": "'null'::jsonb" }, + "reasoning_effort_override_rules": { + "name": "reasoning_effort_override_rules", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, "gemini_google_search_preference": { "name": "gemini_google_search_preference", "type": "varchar(20)", @@ -4084,13 +4090,6 @@ "notNull": true, "default": true }, - "legacy_hedge_max_in_flight": { - "name": "legacy_hedge_max_in_flight", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 2 - }, "discovery_enabled": { "name": "discovery_enabled", "type": "boolean", @@ -4427,12 +4426,7 @@ "compositePrimaryKeys": {}, "uniqueConstraints": {}, "policies": {}, - "checkConstraints": { - "system_settings_legacy_hedge_max_in_flight_range": { - "name": "system_settings_legacy_hedge_max_in_flight_range", - "value": "\"legacy_hedge_max_in_flight\" >= 1 AND \"legacy_hedge_max_in_flight\" <= 4" - } - }, + "checkConstraints": {}, "isRLSEnabled": false }, "public.usage_ledger": { diff --git a/drizzle/meta/0122_snapshot.json b/drizzle/meta/0122_snapshot.json index ce3c46cf8d..8459b3d586 100644 --- a/drizzle/meta/0122_snapshot.json +++ b/drizzle/meta/0122_snapshot.json @@ -1,6 +1,6 @@ { - "id": "a2215042-841c-4749-8f0a-7fdcd1e0cd7d", - "prevId": "57129cf2-37e4-47e1-8f1e-b1c955914f9a", + "id": "0dabb5c9-989c-4012-995e-77315687b34e", + "prevId": "ea6f89a9-567c-4b3d-9b57-e06d1cfdb6cf", "version": "7", "dialect": "postgresql", "tables": { @@ -229,149 +229,6 @@ "checkConstraints": {}, "isRLSEnabled": false }, - "public.avail_bucket_1m": { - "name": "avail_bucket_1m", - "schema": "", - "columns": { - "provider_id": { - "name": "provider_id", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "bucket_start": { - "name": "bucket_start", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "success_cnt": { - "name": "success_cnt", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "failure_cnt": { - "name": "failure_cnt", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "excluded_cnt": { - "name": "excluded_cnt", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "latency_cnt": { - "name": "latency_cnt", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "latency_sum_ms": { - "name": "latency_sum_ms", - "type": "bigint", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "last_request_at": { - "name": "last_request_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "idx_avail_bucket_1m_time": { - "name": "idx_avail_bucket_1m_time", - "columns": [ - { - "expression": "bucket_start", - "isExpression": false, - "asc": false, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": { - "avail_bucket_1m_pkey": { - "name": "avail_bucket_1m_pkey", - "columns": [ - "provider_id", - "bucket_start" - ] - } - }, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.avail_current": { - "name": "avail_current", - "schema": "", - "columns": { - "provider_id": { - "name": "provider_id", - "type": "integer", - "primaryKey": true, - "notNull": true - }, - "state": { - "name": "state", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'unknown'" - }, - "availability": { - "name": "availability", - "type": "double precision", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "request_count": { - "name": "request_count", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "last_request_at": { - "name": "last_request_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, "public.cloud_pricing_catalog": { "name": "cloud_pricing_catalog", "schema": "", @@ -1408,62 +1265,6 @@ "method": "btree", "with": {} }, - "idx_message_request_session_identity_prefix": { - "name": "idx_message_request_session_identity_prefix", - "columns": [ - { - "expression": "COALESCE(\"session_identity\", \"session_id\") varchar_pattern_ops", - "asc": true, - "isExpression": 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 AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", - "concurrently": false, - "method": "btree", - "with": {} - }, - "idx_message_request_session_id_prefix_cover": { - "name": "idx_message_request_session_id_prefix_cover", - "columns": [ - { - "expression": "\"session_id\" varchar_pattern_ops", - "asc": true, - "isExpression": 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 AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", - "concurrently": false, - "method": "btree", - "with": {} - }, "idx_message_request_endpoint": { "name": "idx_message_request_endpoint", "columns": [ @@ -2293,246 +2094,48 @@ "checkConstraints": {}, "isRLSEnabled": false }, - "public.outbox_events": { - "name": "outbox_events", + "public.provider_batch_apply_operations": { + "name": "provider_batch_apply_operations", "schema": "", "columns": { - "id": { - "name": "id", - "type": "bigserial", + "claim_key": { + "name": "claim_key", + "type": "varchar(256)", "primaryKey": true, "notNull": true }, - "event_id": { - "name": "event_id", - "type": "uuid", - "primaryKey": false, - "notNull": true, - "default": "gen_random_uuid()" - }, - "event_type": { - "name": "event_type", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "aggregate_type": { - "name": "aggregate_type", - "type": "text", + "preview_token": { + "name": "preview_token", + "type": "varchar(256)", "primaryKey": false, "notNull": true }, - "aggregate_id": { - "name": "aggregate_id", - "type": "bigint", + "payload_fingerprint": { + "name": "payload_fingerprint", + "type": "varchar(128)", "primaryKey": false, "notNull": true }, - "occurred_at": { - "name": "occurred_at", - "type": "timestamp with time zone", + "operation_id": { + "name": "operation_id", + "type": "varchar(256)", "primaryKey": false, "notNull": true }, - "payload": { - "name": "payload", - "type": "jsonb", + "undo_token": { + "name": "undo_token", + "type": "varchar(256)", "primaryKey": false, "notNull": true }, - "created_at": { - "name": "created_at", + "undo_expires_at": { + "name": "undo_expires_at", "type": "timestamp with time zone", "primaryKey": false, - "notNull": true, - "default": "now()" + "notNull": false }, - "published_at": { - "name": "published_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "attempts": { - "name": "attempts", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "last_error": { - "name": "last_error", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "idx_outbox_events_unpublished": { - "name": "idx_outbox_events_unpublished", - "columns": [ - { - "expression": "id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"outbox_events\".\"published_at\" IS NULL", - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "outbox_events_event_id_key": { - "name": "outbox_events_event_id_key", - "nullsNotDistinct": false, - "columns": [ - "event_id" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.outbox_processed": { - "name": "outbox_processed", - "schema": "", - "columns": { - "event_id": { - "name": "event_id", - "type": "uuid", - "primaryKey": true, - "notNull": true - }, - "processed_at": { - "name": "processed_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.proj_applied_requests": { - "name": "proj_applied_requests", - "schema": "", - "columns": { - "request_id": { - "name": "request_id", - "type": "bigint", - "primaryKey": true, - "notNull": true - }, - "event_id": { - "name": "event_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "applied_at": { - "name": "applied_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.projection_meta": { - "name": "projection_meta", - "schema": "", - "columns": { - "key": { - "name": "key", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "value": { - "name": "value", - "type": "jsonb", - "primaryKey": false, - "notNull": true, - "default": "'{}'::jsonb" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.provider_batch_apply_operations": { - "name": "provider_batch_apply_operations", - "schema": "", - "columns": { - "claim_key": { - "name": "claim_key", - "type": "varchar(256)", - "primaryKey": true, - "notNull": true - }, - "preview_token": { - "name": "preview_token", - "type": "varchar(256)", - "primaryKey": false, - "notNull": true - }, - "payload_fingerprint": { - "name": "payload_fingerprint", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true - }, - "operation_id": { - "name": "operation_id", - "type": "varchar(256)", - "primaryKey": false, - "notNull": true - }, - "undo_token": { - "name": "undo_token", - "type": "varchar(256)", - "primaryKey": false, - "notNull": true - }, - "undo_expires_at": { - "name": "undo_expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "undo_consumed_at": { - "name": "undo_consumed_at", + "undo_consumed_at": { + "name": "undo_consumed_at", "type": "timestamp with time zone", "primaryKey": false, "notNull": false @@ -3697,6 +3300,12 @@ "notNull": false, "default": "'null'::jsonb" }, + "reasoning_effort_override_rules": { + "name": "reasoning_effort_override_rules", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, "gemini_google_search_preference": { "name": "gemini_google_search_preference", "type": "varchar(20)", @@ -4427,13 +4036,6 @@ "notNull": true, "default": true }, - "legacy_hedge_max_in_flight": { - "name": "legacy_hedge_max_in_flight", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 2 - }, "discovery_enabled": { "name": "discovery_enabled", "type": "boolean", @@ -4763,6 +4365,13 @@ "primaryKey": false, "notNull": false, "default": "now()" + }, + "legacy_hedge_max_in_flight": { + "name": "legacy_hedge_max_in_flight", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 2 } }, "indexes": {}, @@ -4773,7 +4382,7 @@ "checkConstraints": { "system_settings_legacy_hedge_max_in_flight_range": { "name": "system_settings_legacy_hedge_max_in_flight_range", - "value": "\"system_settings\".\"legacy_hedge_max_in_flight\" >= 1 AND \"system_settings\".\"legacy_hedge_max_in_flight\" <= 4" + "value": "\"legacy_hedge_max_in_flight\" >= 1 AND \"legacy_hedge_max_in_flight\" <= 4" } }, "isRLSEnabled": false @@ -5220,62 +4829,6 @@ "method": "btree", "with": {} }, - "idx_usage_ledger_session_identity_prefix": { - "name": "idx_usage_ledger_session_identity_prefix", - "columns": [ - { - "expression": "COALESCE(\"session_identity\", \"session_id\") varchar_pattern_ops", - "asc": true, - "isExpression": true, - "nulls": "last" - }, - { - "expression": "created_at", - "isExpression": false, - "asc": false, - "nulls": "last" - }, - { - "expression": "id", - "isExpression": false, - "asc": false, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"usage_ledger\".\"blocked_by\" IS NULL", - "concurrently": false, - "method": "btree", - "with": {} - }, - "idx_usage_ledger_session_id_prefix": { - "name": "idx_usage_ledger_session_id_prefix", - "columns": [ - { - "expression": "\"session_id\" varchar_pattern_ops", - "asc": true, - "isExpression": true, - "nulls": "last" - }, - { - "expression": "created_at", - "isExpression": false, - "asc": false, - "nulls": "last" - }, - { - "expression": "id", - "isExpression": false, - "asc": false, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"usage_ledger\".\"blocked_by\" IS NULL", - "concurrently": false, - "method": "btree", - "with": {} - }, "idx_usage_ledger_model": { "name": "idx_usage_ledger_model", "columns": [ @@ -5823,6 +5376,347 @@ "policies": {}, "checkConstraints": {}, "isRLSEnabled": false + }, + "public.outbox_events": { + "name": "outbox_events", + "schema": "public", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "event_id": { + "name": "event_id", + "type": "uuid", + "primaryKey": false, + "notNull": true, + "default": "gen_random_uuid()" + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "aggregate_type": { + "name": "aggregate_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "aggregate_id": { + "name": "aggregate_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "published_at": { + "name": "published_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": "0" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_outbox_events_unpublished": { + "name": "idx_outbox_events_unpublished", + "columns": [ + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {}, + "where": "\"outbox_events\".\"published_at\" IS NULL" + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "outbox_events_event_id_key": { + "name": "outbox_events_event_id_key", + "nullsNotDistinct": false, + "columns": [ + "event_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.outbox_processed": { + "name": "outbox_processed", + "schema": "public", + "columns": { + "event_id": { + "name": "event_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "processed_at": { + "name": "processed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.proj_applied_requests": { + "name": "proj_applied_requests", + "schema": "public", + "columns": { + "request_id": { + "name": "request_id", + "type": "bigint", + "primaryKey": true, + "notNull": true + }, + "event_id": { + "name": "event_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "applied_at": { + "name": "applied_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.avail_bucket_1m": { + "name": "avail_bucket_1m", + "schema": "public", + "columns": { + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "bucket_start": { + "name": "bucket_start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "success_cnt": { + "name": "success_cnt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": "0" + }, + "failure_cnt": { + "name": "failure_cnt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": "0" + }, + "excluded_cnt": { + "name": "excluded_cnt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": "0" + }, + "latency_cnt": { + "name": "latency_cnt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": "0" + }, + "latency_sum_ms": { + "name": "latency_sum_ms", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": "0" + }, + "last_request_at": { + "name": "last_request_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_avail_bucket_1m_time": { + "name": "idx_avail_bucket_1m_time", + "columns": [ + { + "expression": "bucket_start", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "avail_bucket_1m_pkey": { + "name": "avail_bucket_1m_pkey", + "columns": [ + "provider_id", + "bucket_start" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.avail_current": { + "name": "avail_current", + "schema": "public", + "columns": { + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": true, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "availability": { + "name": "availability", + "type": "double precision", + "primaryKey": false, + "notNull": true, + "default": "0" + }, + "request_count": { + "name": "request_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": "0" + }, + "last_request_at": { + "name": "last_request_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.projection_meta": { + "name": "projection_meta", + "schema": "public", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "value": { + "name": "value", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false } }, "enums": { @@ -5866,4 +5760,4 @@ "schemas": {}, "tables": {} } -} \ No newline at end of file +} diff --git a/drizzle/meta/0123_snapshot.json b/drizzle/meta/0123_snapshot.json index ac775767b4..fb199c5032 100644 --- a/drizzle/meta/0123_snapshot.json +++ b/drizzle/meta/0123_snapshot.json @@ -1,6 +1,6 @@ { - "id": "631bcd2f-bab9-4088-8813-e5e66585e20a", - "prevId": "a2215042-841c-4749-8f0a-7fdcd1e0cd7d", + "id": "c28c7e44-eb1c-47a4-a736-9844badfe591", + "prevId": "0dabb5c9-989c-4012-995e-77315687b34e", "version": "7", "dialect": "postgresql", "tables": { @@ -229,149 +229,6 @@ "checkConstraints": {}, "isRLSEnabled": false }, - "public.avail_bucket_1m": { - "name": "avail_bucket_1m", - "schema": "", - "columns": { - "provider_id": { - "name": "provider_id", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "bucket_start": { - "name": "bucket_start", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "success_cnt": { - "name": "success_cnt", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "failure_cnt": { - "name": "failure_cnt", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "excluded_cnt": { - "name": "excluded_cnt", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "latency_cnt": { - "name": "latency_cnt", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "latency_sum_ms": { - "name": "latency_sum_ms", - "type": "bigint", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "last_request_at": { - "name": "last_request_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "idx_avail_bucket_1m_time": { - "name": "idx_avail_bucket_1m_time", - "columns": [ - { - "expression": "bucket_start", - "isExpression": false, - "asc": false, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": { - "avail_bucket_1m_pkey": { - "name": "avail_bucket_1m_pkey", - "columns": [ - "provider_id", - "bucket_start" - ] - } - }, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.avail_current": { - "name": "avail_current", - "schema": "", - "columns": { - "provider_id": { - "name": "provider_id", - "type": "integer", - "primaryKey": true, - "notNull": true - }, - "state": { - "name": "state", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'unknown'" - }, - "availability": { - "name": "availability", - "type": "double precision", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "request_count": { - "name": "request_count", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "last_request_at": { - "name": "last_request_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, "public.cloud_pricing_catalog": { "name": "cloud_pricing_catalog", "schema": "", @@ -1408,62 +1265,6 @@ "method": "btree", "with": {} }, - "idx_message_request_session_identity_prefix": { - "name": "idx_message_request_session_identity_prefix", - "columns": [ - { - "expression": "COALESCE(\"session_identity\", \"session_id\") varchar_pattern_ops", - "asc": true, - "isExpression": 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 AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", - "concurrently": false, - "method": "btree", - "with": {} - }, - "idx_message_request_session_id_prefix_cover": { - "name": "idx_message_request_session_id_prefix_cover", - "columns": [ - { - "expression": "\"session_id\" varchar_pattern_ops", - "asc": true, - "isExpression": 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 AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", - "concurrently": false, - "method": "btree", - "with": {} - }, "idx_message_request_endpoint": { "name": "idx_message_request_endpoint", "columns": [ @@ -2293,246 +2094,48 @@ "checkConstraints": {}, "isRLSEnabled": false }, - "public.outbox_events": { - "name": "outbox_events", + "public.provider_batch_apply_operations": { + "name": "provider_batch_apply_operations", "schema": "", "columns": { - "id": { - "name": "id", - "type": "bigserial", + "claim_key": { + "name": "claim_key", + "type": "varchar(256)", "primaryKey": true, "notNull": true }, - "event_id": { - "name": "event_id", - "type": "uuid", - "primaryKey": false, - "notNull": true, - "default": "gen_random_uuid()" - }, - "event_type": { - "name": "event_type", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "aggregate_type": { - "name": "aggregate_type", - "type": "text", + "preview_token": { + "name": "preview_token", + "type": "varchar(256)", "primaryKey": false, "notNull": true }, - "aggregate_id": { - "name": "aggregate_id", - "type": "bigint", + "payload_fingerprint": { + "name": "payload_fingerprint", + "type": "varchar(128)", "primaryKey": false, "notNull": true }, - "occurred_at": { - "name": "occurred_at", - "type": "timestamp with time zone", + "operation_id": { + "name": "operation_id", + "type": "varchar(256)", "primaryKey": false, "notNull": true }, - "payload": { - "name": "payload", - "type": "jsonb", + "undo_token": { + "name": "undo_token", + "type": "varchar(256)", "primaryKey": false, "notNull": true }, - "created_at": { - "name": "created_at", + "undo_expires_at": { + "name": "undo_expires_at", "type": "timestamp with time zone", "primaryKey": false, - "notNull": true, - "default": "now()" + "notNull": false }, - "published_at": { - "name": "published_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "attempts": { - "name": "attempts", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 0 - }, - "last_error": { - "name": "last_error", - "type": "text", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "idx_outbox_events_unpublished": { - "name": "idx_outbox_events_unpublished", - "columns": [ - { - "expression": "id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"outbox_events\".\"published_at\" IS NULL", - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "outbox_events_event_id_key": { - "name": "outbox_events_event_id_key", - "nullsNotDistinct": false, - "columns": [ - "event_id" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.outbox_processed": { - "name": "outbox_processed", - "schema": "", - "columns": { - "event_id": { - "name": "event_id", - "type": "uuid", - "primaryKey": true, - "notNull": true - }, - "processed_at": { - "name": "processed_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.proj_applied_requests": { - "name": "proj_applied_requests", - "schema": "", - "columns": { - "request_id": { - "name": "request_id", - "type": "bigint", - "primaryKey": true, - "notNull": true - }, - "event_id": { - "name": "event_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "applied_at": { - "name": "applied_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.projection_meta": { - "name": "projection_meta", - "schema": "", - "columns": { - "key": { - "name": "key", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "value": { - "name": "value", - "type": "jsonb", - "primaryKey": false, - "notNull": true, - "default": "'{}'::jsonb" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.provider_batch_apply_operations": { - "name": "provider_batch_apply_operations", - "schema": "", - "columns": { - "claim_key": { - "name": "claim_key", - "type": "varchar(256)", - "primaryKey": true, - "notNull": true - }, - "preview_token": { - "name": "preview_token", - "type": "varchar(256)", - "primaryKey": false, - "notNull": true - }, - "payload_fingerprint": { - "name": "payload_fingerprint", - "type": "varchar(128)", - "primaryKey": false, - "notNull": true - }, - "operation_id": { - "name": "operation_id", - "type": "varchar(256)", - "primaryKey": false, - "notNull": true - }, - "undo_token": { - "name": "undo_token", - "type": "varchar(256)", - "primaryKey": false, - "notNull": true - }, - "undo_expires_at": { - "name": "undo_expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "undo_consumed_at": { - "name": "undo_consumed_at", + "undo_consumed_at": { + "name": "undo_consumed_at", "type": "timestamp with time zone", "primaryKey": false, "notNull": false @@ -3351,18 +2954,6 @@ "primaryKey": false, "notNull": true }, - "new_api_access_token": { - "name": "new_api_access_token", - "type": "varchar", - "primaryKey": false, - "notNull": false - }, - "new_api_user_id": { - "name": "new_api_user_id", - "type": "integer", - "primaryKey": false, - "notNull": false - }, "provider_vendor_id": { "name": "provider_vendor_id", "type": "integer", @@ -3431,6 +3022,13 @@ "notNull": true, "default": false }, + "overwrite_response_model": { + "name": "overwrite_response_model", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, "model_redirects": { "name": "model_redirects", "type": "jsonb", @@ -3709,6 +3307,12 @@ "notNull": false, "default": "'null'::jsonb" }, + "reasoning_effort_override_rules": { + "name": "reasoning_effort_override_rules", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, "gemini_google_search_preference": { "name": "gemini_google_search_preference", "type": "varchar(20)", @@ -4439,13 +4043,6 @@ "notNull": true, "default": true }, - "legacy_hedge_max_in_flight": { - "name": "legacy_hedge_max_in_flight", - "type": "integer", - "primaryKey": false, - "notNull": true, - "default": 2 - }, "discovery_enabled": { "name": "discovery_enabled", "type": "boolean", @@ -4775,6 +4372,13 @@ "primaryKey": false, "notNull": false, "default": "now()" + }, + "legacy_hedge_max_in_flight": { + "name": "legacy_hedge_max_in_flight", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 2 } }, "indexes": {}, @@ -4785,7 +4389,7 @@ "checkConstraints": { "system_settings_legacy_hedge_max_in_flight_range": { "name": "system_settings_legacy_hedge_max_in_flight_range", - "value": "\"system_settings\".\"legacy_hedge_max_in_flight\" >= 1 AND \"system_settings\".\"legacy_hedge_max_in_flight\" <= 4" + "value": "\"legacy_hedge_max_in_flight\" >= 1 AND \"legacy_hedge_max_in_flight\" <= 4" } }, "isRLSEnabled": false @@ -5232,117 +4836,61 @@ "method": "btree", "with": {} }, - "idx_usage_ledger_session_identity_prefix": { - "name": "idx_usage_ledger_session_identity_prefix", + "idx_usage_ledger_model": { + "name": "idx_usage_ledger_model", "columns": [ { - "expression": "COALESCE(\"session_identity\", \"session_id\") varchar_pattern_ops", - "asc": true, - "isExpression": true, - "nulls": "last" - }, - { - "expression": "created_at", - "isExpression": false, - "asc": false, - "nulls": "last" - }, - { - "expression": "id", + "expression": "model", "isExpression": false, - "asc": false, + "asc": true, "nulls": "last" } ], "isUnique": false, - "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "where": "\"usage_ledger\".\"model\" IS NOT NULL", "concurrently": false, "method": "btree", "with": {} }, - "idx_usage_ledger_session_id_prefix": { - "name": "idx_usage_ledger_session_id_prefix", + "idx_usage_ledger_key_cost": { + "name": "idx_usage_ledger_key_cost", "columns": [ { - "expression": "\"session_id\" varchar_pattern_ops", + "expression": "key", + "isExpression": false, "asc": true, - "isExpression": true, "nulls": "last" }, { "expression": "created_at", "isExpression": false, - "asc": false, + "asc": true, "nulls": "last" }, { - "expression": "id", + "expression": "cost_usd", "isExpression": false, - "asc": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "endpoint", + "isExpression": false, + "asc": true, "nulls": "last" } ], "isUnique": false, - "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "where": "\"usage_ledger\".\"blocked_by\" IS NULL AND \"usage_ledger\".\"is_replay\" = false", "concurrently": false, "method": "btree", "with": {} }, - "idx_usage_ledger_model": { - "name": "idx_usage_ledger_model", + "idx_usage_ledger_user_cost_cover": { + "name": "idx_usage_ledger_user_cost_cover", "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 AND \"usage_ledger\".\"is_replay\" = false", - "concurrently": false, - "method": "btree", - "with": {} - }, - "idx_usage_ledger_user_cost_cover": { - "name": "idx_usage_ledger_user_cost_cover", - "columns": [ - { - "expression": "user_id", + "expression": "user_id", "isExpression": false, "asc": true, "nulls": "last" @@ -5835,6 +5383,347 @@ "policies": {}, "checkConstraints": {}, "isRLSEnabled": false + }, + "public.outbox_events": { + "name": "outbox_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "event_id": { + "name": "event_id", + "type": "uuid", + "primaryKey": false, + "notNull": true, + "default": "gen_random_uuid()" + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "aggregate_type": { + "name": "aggregate_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "aggregate_id": { + "name": "aggregate_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "published_at": { + "name": "published_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": "0" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_outbox_events_unpublished": { + "name": "idx_outbox_events_unpublished", + "columns": [ + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {}, + "where": "\"outbox_events\".\"published_at\" IS NULL" + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "outbox_events_event_id_key": { + "name": "outbox_events_event_id_key", + "nullsNotDistinct": false, + "columns": [ + "event_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.outbox_processed": { + "name": "outbox_processed", + "schema": "", + "columns": { + "event_id": { + "name": "event_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "processed_at": { + "name": "processed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.proj_applied_requests": { + "name": "proj_applied_requests", + "schema": "", + "columns": { + "request_id": { + "name": "request_id", + "type": "bigint", + "primaryKey": true, + "notNull": true + }, + "event_id": { + "name": "event_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "applied_at": { + "name": "applied_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.avail_bucket_1m": { + "name": "avail_bucket_1m", + "schema": "", + "columns": { + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "bucket_start": { + "name": "bucket_start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "success_cnt": { + "name": "success_cnt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": "0" + }, + "failure_cnt": { + "name": "failure_cnt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": "0" + }, + "excluded_cnt": { + "name": "excluded_cnt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": "0" + }, + "latency_cnt": { + "name": "latency_cnt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": "0" + }, + "latency_sum_ms": { + "name": "latency_sum_ms", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": "0" + }, + "last_request_at": { + "name": "last_request_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_avail_bucket_1m_time": { + "name": "idx_avail_bucket_1m_time", + "columns": [ + { + "expression": "bucket_start", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "avail_bucket_1m_pkey": { + "name": "avail_bucket_1m_pkey", + "columns": [ + "provider_id", + "bucket_start" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.avail_current": { + "name": "avail_current", + "schema": "", + "columns": { + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": true, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "availability": { + "name": "availability", + "type": "double precision", + "primaryKey": false, + "notNull": true, + "default": "0" + }, + "request_count": { + "name": "request_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": "0" + }, + "last_request_at": { + "name": "last_request_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.projection_meta": { + "name": "projection_meta", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "value": { + "name": "value", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false } }, "enums": { @@ -5878,4 +5767,4 @@ "schemas": {}, "tables": {} } -} \ No newline at end of file +} diff --git a/drizzle/meta/0124_snapshot.json b/drizzle/meta/0124_snapshot.json index 864cf66edc..0fbecc8e0c 100644 --- a/drizzle/meta/0124_snapshot.json +++ b/drizzle/meta/0124_snapshot.json @@ -1,6 +1,6 @@ { - "id": "bd6f4b40-3271-4735-b93c-e3afeaa2d3dc", - "prevId": "631bcd2f-bab9-4088-8813-e5e66585e20a", + "id": "1bb37324-6c5b-4e63-8bb7-798d0dd321cb", + "prevId": "c28c7e44-eb1c-47a4-a736-9844badfe591", "version": "7", "dialect": "postgresql", "tables": { @@ -1408,62 +1408,6 @@ "method": "btree", "with": {} }, - "idx_message_request_session_identity_prefix": { - "name": "idx_message_request_session_identity_prefix", - "columns": [ - { - "expression": "COALESCE(\"session_identity\", \"session_id\") varchar_pattern_ops", - "asc": true, - "isExpression": 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 AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", - "concurrently": false, - "method": "btree", - "with": {} - }, - "idx_message_request_session_id_prefix_cover": { - "name": "idx_message_request_session_id_prefix_cover", - "columns": [ - { - "expression": "\"session_id\" varchar_pattern_ops", - "asc": true, - "isExpression": 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 AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", - "concurrently": false, - "method": "btree", - "with": {} - }, "idx_message_request_endpoint": { "name": "idx_message_request_endpoint", "columns": [ @@ -2128,6 +2072,53 @@ "notNull": false, "default": 10 }, + "client_problem_enabled": { + "name": "client_problem_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "client_problem_webhook": { + "name": "client_problem_webhook", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "client_problem_count_threshold": { + "name": "client_problem_count_threshold", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 10 + }, + "client_problem_window_minutes": { + "name": "client_problem_window_minutes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 5 + }, + "client_problem_cyber_count_threshold": { + "name": "client_problem_cyber_count_threshold", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 3 + }, + "client_problem_cyber_window_minutes": { + "name": "client_problem_cyber_window_minutes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 5 + }, + "title_prefix": { + "name": "title_prefix", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, "created_at": { "name": "created_at", "type": "timestamp with time zone", @@ -3351,18 +3342,6 @@ "primaryKey": false, "notNull": true }, - "new_api_access_token": { - "name": "new_api_access_token", - "type": "varchar", - "primaryKey": false, - "notNull": false - }, - "new_api_user_id": { - "name": "new_api_user_id", - "type": "integer", - "primaryKey": false, - "notNull": false - }, "provider_vendor_id": { "name": "provider_vendor_id", "type": "integer", @@ -3431,6 +3410,13 @@ "notNull": true, "default": false }, + "overwrite_response_model": { + "name": "overwrite_response_model", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, "model_redirects": { "name": "model_redirects", "type": "jsonb", @@ -3709,6 +3695,12 @@ "notNull": false, "default": "'null'::jsonb" }, + "reasoning_effort_override_rules": { + "name": "reasoning_effort_override_rules", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, "gemini_google_search_preference": { "name": "gemini_google_search_preference", "type": "varchar(20)", @@ -4762,13 +4754,6 @@ "primaryKey": false, "notNull": false }, - "enable_memory_admission": { - "name": "enable_memory_admission", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, "created_at": { "name": "created_at", "type": "timestamp with time zone", @@ -5239,62 +5224,6 @@ "method": "btree", "with": {} }, - "idx_usage_ledger_session_identity_prefix": { - "name": "idx_usage_ledger_session_identity_prefix", - "columns": [ - { - "expression": "COALESCE(\"session_identity\", \"session_id\") varchar_pattern_ops", - "asc": true, - "isExpression": true, - "nulls": "last" - }, - { - "expression": "created_at", - "isExpression": false, - "asc": false, - "nulls": "last" - }, - { - "expression": "id", - "isExpression": false, - "asc": false, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"usage_ledger\".\"blocked_by\" IS NULL", - "concurrently": false, - "method": "btree", - "with": {} - }, - "idx_usage_ledger_session_id_prefix": { - "name": "idx_usage_ledger_session_id_prefix", - "columns": [ - { - "expression": "\"session_id\" varchar_pattern_ops", - "asc": true, - "isExpression": true, - "nulls": "last" - }, - { - "expression": "created_at", - "isExpression": false, - "asc": false, - "nulls": "last" - }, - { - "expression": "id", - "isExpression": false, - "asc": false, - "nulls": "last" - } - ], - "isUnique": false, - "where": "\"usage_ledger\".\"blocked_by\" IS NULL", - "concurrently": false, - "method": "btree", - "with": {} - }, "idx_usage_ledger_model": { "name": "idx_usage_ledger_model", "columns": [ @@ -5860,7 +5789,8 @@ "circuit_breaker", "daily_leaderboard", "cost_alert", - "cache_hit_rate_alert" + "cache_hit_rate_alert", + "client_problem" ] }, "public.webhook_provider_type": { diff --git a/drizzle/meta/0125_snapshot.json b/drizzle/meta/0125_snapshot.json new file mode 100644 index 0000000000..fb91d8bc5e --- /dev/null +++ b/drizzle/meta/0125_snapshot.json @@ -0,0 +1,5818 @@ +{ + "id": "9081e8b6-b857-41b4-9be6-b026c2d1dc3a", + "prevId": "1bb37324-6c5b-4e63-8bb7-798d0dd321cb", + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"audit_log\".\"operator_user_id\" IS NOT NULL", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"audit_log\".\"operator_ip\" IS NOT NULL", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"audit_log\".\"target_type\" IS NOT NULL", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.avail_bucket_1m": { + "name": "avail_bucket_1m", + "schema": "", + "columns": { + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "bucket_start": { + "name": "bucket_start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "success_cnt": { + "name": "success_cnt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "failure_cnt": { + "name": "failure_cnt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "excluded_cnt": { + "name": "excluded_cnt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "latency_cnt": { + "name": "latency_cnt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "latency_sum_ms": { + "name": "latency_sum_ms", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_request_at": { + "name": "last_request_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_avail_bucket_1m_time": { + "name": "idx_avail_bucket_1m_time", + "columns": [ + { + "expression": "bucket_start", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "avail_bucket_1m_pkey": { + "name": "avail_bucket_1m_pkey", + "columns": [ + "provider_id", + "bucket_start" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.avail_current": { + "name": "avail_current", + "schema": "", + "columns": { + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": true, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "availability": { + "name": "availability", + "type": "double precision", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "request_count": { + "name": "request_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_request_at": { + "name": "last_request_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cloud_pricing_catalog": { + "name": "cloud_pricing_catalog", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "version": { + "name": "version", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "currency": { + "name": "currency", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'USD'" + }, + "refreshed_at": { + "name": "refreshed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "providers": { + "name": "providers", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "vendors": { + "name": "vendors", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "model_count": { + "name": "model_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "synced_at": { + "name": "synced_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "unique_pattern": { + "name": "unique_pattern", + "columns": [ + { + "expression": "pattern", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_category": { + "name": "idx_category", + "columns": [ + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_match_type": { + "name": "idx_match_type", + "columns": [ + { + "expression": "match_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_keys_key": { + "name": "idx_keys_key", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_keys_created_at": { + "name": "idx_keys_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_keys_deleted_at": { + "name": "idx_keys_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "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 + }, + "session_identity": { + "name": "session_identity", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "session_identity_kind": { + "name": "session_identity_kind", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "affinity_scope_tag": { + "name": "affinity_scope_tag", + "type": "varchar(16)", + "primaryKey": false, + "notNull": false + }, + "affinity_fingerprint": { + "name": "affinity_fingerprint", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "affinity_fingerprint_chain": { + "name": "affinity_fingerprint_chain", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "is_replay": { + "name": "is_replay", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "replay_source_request_id": { + "name": "replay_source_request_id", + "type": "integer", + "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 + }, + "routing_trace": { + "name": "routing_trace", + "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 + }, + "first_byte_ms": { + "name": "first_byte_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 + }, + "hedge_losers": { + "name": "hedge_losers", + "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 + }, + "cache_compatibility_key": { + "name": "cache_compatibility_key", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "cache_score_eligible": { + "name": "cache_score_eligible", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "cache_score_excluded_reason": { + "name": "cache_score_excluded_reason", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false + }, + "theoretical_cache_tokens": { + "name": "theoretical_cache_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_ttl_bucket": { + "name": "cache_ttl_bucket", + "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_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, + "with": {}, + "method": "btree", + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"message_request\".\"deleted_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"message_request\".\"deleted_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"status_code\" IS NOT NULL", + "concurrently": false + }, + "idx_message_request_proxy_status_active": { + "name": "idx_message_request_proxy_status_active", + "columns": [ + { + "expression": "\"created_at\" DESC NULLS LAST", + "isExpression": true, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"is_replay\" = false AND \"message_request\".\"status_code\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false + }, + "idx_message_request_proxy_status_latest": { + "name": "idx_message_request_proxy_status_latest", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"updated_at\" DESC NULLS LAST", + "isExpression": true, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"is_replay\" = false AND \"message_request\".\"status_code\" IS NOT NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false + }, + "idx_message_request_session_id": { + "name": "idx_message_request_session_id", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false + }, + "idx_message_request_session_id_prefix": { + "name": "idx_message_request_session_id_prefix", + "columns": [ + { + "expression": "\"session_id\" varchar_pattern_ops", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"message_request\".\"deleted_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false + }, + "idx_message_request_session_identity_created_at": { + "name": "idx_message_request_session_identity_created_at", + "columns": [ + { + "expression": "COALESCE(\"session_identity\", \"session_id\")", + "isExpression": true, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"created_at\" DESC NULLS LAST", + "isExpression": true, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false + }, + "idx_message_request_endpoint": { + "name": "idx_message_request_endpoint", + "columns": [ + { + "expression": "endpoint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false + }, + "idx_message_request_blocked_by": { + "name": "idx_message_request_blocked_by", + "columns": [ + { + "expression": "blocked_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false + }, + "idx_message_request_provider_id": { + "name": "idx_message_request_provider_id", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_message_request_user_id": { + "name": "idx_message_request_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_message_request_key": { + "name": "idx_message_request_key", + "columns": [ + { + "expression": "key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "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 + }, + "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, + "with": {}, + "method": "btree", + "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 + }, + "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, + "with": {}, + "method": "btree", + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false + }, + "idx_message_request_model_active": { + "name": "idx_message_request_model_active", + "columns": [ + { + "expression": "model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"model\" IS NOT NULL", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"status_code\" IS NOT NULL", + "concurrently": false + }, + "idx_message_request_created_at": { + "name": "idx_message_request_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_message_request_deleted_at": { + "name": "idx_message_request_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"message_request\".\"deleted_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"message_request\".\"deleted_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"message_request\".\"deleted_at\" IS NULL", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"client_ip\" IS NOT NULL", + "concurrently": false + } + }, + "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": "'cloud'" + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_model_prices_model_name": { + "name": "idx_model_prices_model_name", + "columns": [ + { + "expression": "model_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_model_prices_created_at": { + "name": "idx_model_prices_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_model_prices_source": { + "name": "idx_model_prices_source", + "columns": [ + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_model_prices_vendor": { + "name": "idx_model_prices_vendor", + "columns": [ + { + "expression": "((\"price_data\" ->> 'vendor'))", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_model_prices_aliases": { + "name": "idx_model_prices_aliases", + "columns": [ + { + "expression": "((\"price_data\" -> 'aliases'))", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "gin", + "concurrently": false + } + }, + "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 + }, + "client_problem_enabled": { + "name": "client_problem_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "client_problem_webhook": { + "name": "client_problem_webhook", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "client_problem_count_threshold": { + "name": "client_problem_count_threshold", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 10 + }, + "client_problem_window_minutes": { + "name": "client_problem_window_minutes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 5 + }, + "client_problem_cyber_count_threshold": { + "name": "client_problem_cyber_count_threshold", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 3 + }, + "client_problem_cyber_window_minutes": { + "name": "client_problem_cyber_window_minutes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 5 + }, + "title_prefix": { + "name": "title_prefix", + "type": "varchar(64)", + "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 + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "notification_target_bindings_target_id_webhook_targets_id_fk": { + "name": "notification_target_bindings_target_id_webhook_targets_id_fk", + "tableFrom": "notification_target_bindings", + "columnsFrom": [ + "target_id" + ], + "tableTo": "webhook_targets", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.outbox_events": { + "name": "outbox_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "event_id": { + "name": "event_id", + "type": "uuid", + "primaryKey": false, + "notNull": true, + "default": "gen_random_uuid()" + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "aggregate_type": { + "name": "aggregate_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "aggregate_id": { + "name": "aggregate_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "published_at": { + "name": "published_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_outbox_events_unpublished": { + "name": "idx_outbox_events_unpublished", + "columns": [ + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"outbox_events\".\"published_at\" IS NULL", + "concurrently": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "outbox_events_event_id_key": { + "name": "outbox_events_event_id_key", + "columns": [ + "event_id" + ], + "nullsNotDistinct": false + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.outbox_processed": { + "name": "outbox_processed", + "schema": "", + "columns": { + "event_id": { + "name": "event_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "processed_at": { + "name": "processed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.proj_applied_requests": { + "name": "proj_applied_requests", + "schema": "", + "columns": { + "request_id": { + "name": "request_id", + "type": "bigint", + "primaryKey": true, + "notNull": true + }, + "event_id": { + "name": "event_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "applied_at": { + "name": "applied_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.projection_meta": { + "name": "projection_meta", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "value": { + "name": "value", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_batch_apply_operations": { + "name": "provider_batch_apply_operations", + "schema": "", + "columns": { + "claim_key": { + "name": "claim_key", + "type": "varchar(256)", + "primaryKey": true, + "notNull": true + }, + "preview_token": { + "name": "preview_token", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "payload_fingerprint": { + "name": "payload_fingerprint", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "operation_id": { + "name": "operation_id", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "undo_token": { + "name": "undo_token", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "undo_expires_at": { + "name": "undo_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "undo_consumed_at": { + "name": "undo_consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "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": { + "uniq_provider_batch_apply_operations_preview_token": { + "name": "uniq_provider_batch_apply_operations_preview_token", + "columns": [ + { + "expression": "preview_token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "uniq_provider_batch_apply_operations_operation_id": { + "name": "uniq_provider_batch_apply_operations_operation_id", + "columns": [ + { + "expression": "operation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "uniq_provider_batch_apply_operations_undo_token": { + "name": "uniq_provider_batch_apply_operations_undo_token", + "columns": [ + { + "expression": "undo_token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_provider_batch_apply_operations_expires_at": { + "name": "idx_provider_batch_apply_operations_expires_at", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_cache_effectiveness": { + "name": "provider_cache_effectiveness", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "cache_ttl_bucket": { + "name": "cache_ttl_bucket", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "window_start": { + "name": "window_start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "window_end": { + "name": "window_end", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "sample_count": { + "name": "sample_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "eligible_count": { + "name": "eligible_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "theoretical_cache_tokens": { + "name": "theoretical_cache_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "observed_cache_read_tokens": { + "name": "observed_cache_read_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "raw_effectiveness_bp": { + "name": "raw_effectiveness_bp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "confidence_bp": { + "name": "confidence_bp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "effectiveness_bp": { + "name": "effectiveness_bp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_provider_cache_effectiveness_window": { + "name": "idx_provider_cache_effectiveness_window", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "model", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_start", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": {}, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "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", + "columnsFrom": [ + "endpoint_id" + ], + "tableTo": "provider_endpoints", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "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, + "with": {}, + "method": "btree", + "where": "\"provider_endpoints\".\"deleted_at\" IS NULL", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"provider_endpoints\".\"deleted_at\" IS NULL", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"provider_endpoints\".\"deleted_at\" IS NULL", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"provider_endpoints\".\"deleted_at\" IS NULL", + "concurrently": false + }, + "idx_provider_endpoints_created_at": { + "name": "idx_provider_endpoints_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_provider_endpoints_deleted_at": { + "name": "idx_provider_endpoints_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": { + "provider_endpoints_vendor_id_provider_vendors_id_fk": { + "name": "provider_endpoints_vendor_id_provider_vendors_id_fk", + "tableFrom": "provider_endpoints", + "columnsFrom": [ + "vendor_id" + ], + "tableTo": "provider_vendors", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "cascade" + } + }, + "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", + "columns": [ + "name" + ], + "nullsNotDistinct": false + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_provider_vendors_created_at": { + "name": "idx_provider_vendors_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "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 + }, + "overwrite_response_model": { + "name": "overwrite_response_model", + "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_image_generation_preference": { + "name": "codex_image_generation_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" + }, + "reasoning_effort_override_rules": { + "name": "reasoning_effort_override_rules", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"providers\".\"deleted_at\" IS NULL", + "concurrently": false + }, + "idx_providers_group": { + "name": "idx_providers_group", + "columns": [ + { + "expression": "group_tag", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"providers\".\"deleted_at\" IS NULL", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"providers\".\"deleted_at\" IS NULL", + "concurrently": false + }, + "idx_providers_created_at": { + "name": "idx_providers_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_providers_deleted_at": { + "name": "idx_providers_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"providers\".\"deleted_at\" IS NULL", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "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 + } + }, + "foreignKeys": { + "providers_provider_vendor_id_provider_vendors_id_fk": { + "name": "providers_provider_vendor_id_provider_vendors_id_fk", + "tableFrom": "providers", + "columnsFrom": [ + "provider_vendor_id" + ], + "tableTo": "provider_vendors", + "columnsTo": [ + "id" + ], + "onUpdate": "no action", + "onDelete": "restrict" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.replay_payloads": { + "name": "replay_payloads", + "schema": "", + "columns": { + "replay_id": { + "name": "replay_id", + "type": "varchar(64)", + "primaryKey": true, + "notNull": true + }, + "verifier": { + "name": "verifier", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "scope_tag": { + "name": "scope_tag", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "key_id": { + "name": "key_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "format": { + "name": "format", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "status_code": { + "name": "status_code", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "headers_json": { + "name": "headers_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "byte_size": { + "name": "byte_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "source_message_request_id": { + "name": "source_message_request_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_replay_payloads_key_id": { + "name": "idx_replay_payloads_key_id", + "columns": [ + { + "expression": "key_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_replay_payloads_expires_at": { + "name": "idx_replay_payloads_expires_at", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "foreignKeys": {}, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_request_filters_scope": { + "name": "idx_request_filters_scope", + "columns": [ + { + "expression": "scope", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_request_filters_action": { + "name": "idx_request_filters_action", + "columns": [ + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_sensitive_words_created_at": { + "name": "idx_sensitive_words_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "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": "'CC 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 + }, + "bill_hedge_losers": { + "name": "bill_hedge_losers", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "legacy_hedge_max_in_flight": { + "name": "legacy_hedge_max_in_flight", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 2 + }, + "discovery_enabled": { + "name": "discovery_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "discovery_concurrency": { + "name": "discovery_concurrency", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 2 + }, + "max_discovery_rounds": { + "name": "max_discovery_rounds", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 2 + }, + "discovery_sla_ms": { + "name": "discovery_sla_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 10000 + }, + "sticky_sla_ms": { + "name": "sticky_sla_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 20000 + }, + "racing_total_timeout_ms": { + "name": "racing_total_timeout_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 60000 + }, + "sticky_timeout_cooldown_ms": { + "name": "sticky_timeout_cooldown_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 300000 + }, + "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_thinking_effort_conflict_rectifier": { + "name": "enable_thinking_effort_conflict_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_gemini_function_id_rectifier": { + "name": "enable_gemini_function_id_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 + }, + "stream_gate_mode": { + "name": "stream_gate_mode", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "default": "'enforce'" + }, + "affinity_ignore_client_session_id": { + "name": "affinity_ignore_client_session_id", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "replay_enabled": { + "name": "replay_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "replay_cache_ttl_minutes": { + "name": "replay_cache_ttl_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 30 + }, + "cache_effectiveness_enabled": { + "name": "cache_effectiveness_enabled", + "type": "boolean", + "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": { + "system_settings_legacy_hedge_max_in_flight_range": { + "name": "system_settings_legacy_hedge_max_in_flight_range", + "value": "\"system_settings\".\"legacy_hedge_max_in_flight\" >= 1 AND \"system_settings\".\"legacy_hedge_max_in_flight\" <= 4" + } + }, + "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 + }, + "session_identity": { + "name": "session_identity", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "session_identity_kind": { + "name": "session_identity_kind", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "affinity_scope_tag": { + "name": "affinity_scope_tag", + "type": "varchar(16)", + "primaryKey": false, + "notNull": false + }, + "affinity_fingerprint": { + "name": "affinity_fingerprint", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "affinity_fingerprint_chain": { + "name": "affinity_fingerprint_chain", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "is_replay": { + "name": "is_replay", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "replay_source_request_id": { + "name": "replay_source_request_id", + "type": "integer", + "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 + }, + "first_byte_ms": { + "name": "first_byte_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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"usage_ledger\".\"blocked_by\" IS NULL AND \"usage_ledger\".\"is_replay\" = false", + "concurrently": false + }, + "idx_usage_ledger_user_id_reset": { + "name": "idx_usage_ledger_user_id_reset", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"usage_ledger\".\"blocked_by\" IS NULL AND \"usage_ledger\".\"is_replay\" = false", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"usage_ledger\".\"blocked_by\" IS NULL AND \"usage_ledger\".\"is_replay\" = false", + "concurrently": false + }, + "idx_usage_ledger_created_at_minute": { + "name": "idx_usage_ledger_created_at_minute", + "columns": [ + { + "expression": "date_trunc('minute', \"created_at\" AT TIME ZONE 'UTC')", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_usage_ledger_session_id": { + "name": "idx_usage_ledger_session_id", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"usage_ledger\".\"session_id\" IS NOT NULL", + "concurrently": false + }, + "idx_usage_ledger_session_identity_created_at": { + "name": "idx_usage_ledger_session_identity_created_at", + "columns": [ + { + "expression": "COALESCE(\"session_identity\", \"session_id\")", + "isExpression": true, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"created_at\" DESC NULLS LAST", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"usage_ledger\".\"blocked_by\" IS NULL AND \"usage_ledger\".\"is_replay\" = false", + "concurrently": false + }, + "idx_usage_ledger_session_identity": { + "name": "idx_usage_ledger_session_identity", + "columns": [ + { + "expression": "COALESCE(\"session_identity\", \"session_id\")", + "isExpression": true, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_usage_ledger_model": { + "name": "idx_usage_ledger_model", + "columns": [ + { + "expression": "model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"usage_ledger\".\"model\" IS NOT NULL", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"usage_ledger\".\"blocked_by\" IS NULL AND \"usage_ledger\".\"is_replay\" = false", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"usage_ledger\".\"blocked_by\" IS NULL AND \"usage_ledger\".\"is_replay\" = false", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"usage_ledger\".\"blocked_by\" IS NULL AND \"usage_ledger\".\"is_replay\" = false", + "concurrently": false + }, + "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", + "isExpression": true, + "asc": true, + "nulls": "last" + }, + { + "expression": "final_provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "where": "\"usage_ledger\".\"blocked_by\" IS NULL AND \"usage_ledger\".\"is_replay\" = false", + "concurrently": false + } + }, + "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, + "with": {}, + "method": "btree", + "where": "\"users\".\"deleted_at\" IS NULL", + "concurrently": false + }, + "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, + "with": {}, + "method": "btree", + "where": "\"users\".\"deleted_at\" IS NULL", + "concurrently": false + }, + "idx_users_tags_gin": { + "name": "idx_users_tags_gin", + "columns": [ + { + "expression": "tags", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "gin", + "where": "\"users\".\"deleted_at\" IS NULL", + "concurrently": false + }, + "idx_users_created_at": { + "name": "idx_users_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + }, + "idx_users_deleted_at": { + "name": "idx_users_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "with": {}, + "method": "btree", + "concurrently": false + } + }, + "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", + "client_problem" + ] + }, + "public.webhook_provider_type": { + "name": "webhook_provider_type", + "schema": "public", + "values": [ + "wechat", + "feishu", + "dingtalk", + "telegram", + "custom" + ] + } + }, + "schemas": {}, + "views": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/0127_snapshot.json b/drizzle/meta/0127_snapshot.json new file mode 100644 index 0000000000..a2389a974c --- /dev/null +++ b/drizzle/meta/0127_snapshot.json @@ -0,0 +1,5936 @@ +{ + "id": "be9420d3-c709-4f9f-9f6b-977d36ea4fb2", + "prevId": "9081e8b6-b857-41b4-9be6-b026c2d1dc3a", + "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.avail_bucket_1m": { + "name": "avail_bucket_1m", + "schema": "", + "columns": { + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "bucket_start": { + "name": "bucket_start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "success_cnt": { + "name": "success_cnt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "failure_cnt": { + "name": "failure_cnt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "excluded_cnt": { + "name": "excluded_cnt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "latency_cnt": { + "name": "latency_cnt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "latency_sum_ms": { + "name": "latency_sum_ms", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_request_at": { + "name": "last_request_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_avail_bucket_1m_time": { + "name": "idx_avail_bucket_1m_time", + "columns": [ + { + "expression": "bucket_start", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "avail_bucket_1m_pkey": { + "name": "avail_bucket_1m_pkey", + "columns": [ + "provider_id", + "bucket_start" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.avail_current": { + "name": "avail_current", + "schema": "", + "columns": { + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": true, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "availability": { + "name": "availability", + "type": "double precision", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "request_count": { + "name": "request_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_request_at": { + "name": "last_request_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cloud_pricing_catalog": { + "name": "cloud_pricing_catalog", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "version": { + "name": "version", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "currency": { + "name": "currency", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'USD'" + }, + "refreshed_at": { + "name": "refreshed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "providers": { + "name": "providers", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "vendors": { + "name": "vendors", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "model_count": { + "name": "model_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "synced_at": { + "name": "synced_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "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 + }, + "session_identity": { + "name": "session_identity", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "session_identity_kind": { + "name": "session_identity_kind", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "affinity_scope_tag": { + "name": "affinity_scope_tag", + "type": "varchar(16)", + "primaryKey": false, + "notNull": false + }, + "affinity_fingerprint": { + "name": "affinity_fingerprint", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "affinity_fingerprint_chain": { + "name": "affinity_fingerprint_chain", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "is_replay": { + "name": "is_replay", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "replay_source_request_id": { + "name": "replay_source_request_id", + "type": "integer", + "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 + }, + "routing_trace": { + "name": "routing_trace", + "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 + }, + "first_byte_ms": { + "name": "first_byte_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 + }, + "hedge_losers": { + "name": "hedge_losers", + "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 + }, + "cache_compatibility_key": { + "name": "cache_compatibility_key", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "cache_score_eligible": { + "name": "cache_score_eligible", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "cache_score_excluded_reason": { + "name": "cache_score_excluded_reason", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false + }, + "theoretical_cache_tokens": { + "name": "theoretical_cache_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_ttl_bucket": { + "name": "cache_ttl_bucket", + "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_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_proxy_status_active": { + "name": "idx_message_request_proxy_status_active", + "columns": [ + { + "expression": "\"created_at\" DESC NULLS LAST", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"is_replay\" = false AND \"message_request\".\"status_code\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_proxy_status_latest": { + "name": "idx_message_request_proxy_status_latest", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"updated_at\" DESC NULLS LAST", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"is_replay\" = false AND \"message_request\".\"status_code\" IS NOT NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "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_session_identity_created_at": { + "name": "idx_message_request_session_identity_created_at", + "columns": [ + { + "expression": "COALESCE(\"session_identity\", \"session_id\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "\"created_at\" DESC NULLS LAST", + "asc": true, + "isExpression": true, + "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_session_identity_prefix": { + "name": "idx_message_request_session_identity_prefix", + "columns": [ + { + "expression": "COALESCE(\"session_identity\", \"session_id\") varchar_pattern_ops", + "asc": true, + "isExpression": 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 AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_session_id_prefix_cover": { + "name": "idx_message_request_session_id_prefix_cover", + "columns": [ + { + "expression": "\"session_id\" varchar_pattern_ops", + "asc": true, + "isExpression": 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 AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "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": "'cloud'" + }, + "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": {} + }, + "idx_model_prices_vendor": { + "name": "idx_model_prices_vendor", + "columns": [ + { + "expression": "((\"price_data\" ->> 'vendor'))", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_model_prices_aliases": { + "name": "idx_model_prices_aliases", + "columns": [ + { + "expression": "((\"price_data\" -> 'aliases'))", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "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 + }, + "title_prefix": { + "name": "title_prefix", + "type": "varchar(64)", + "primaryKey": false, + "notNull": 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 + }, + "client_problem_enabled": { + "name": "client_problem_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "client_problem_webhook": { + "name": "client_problem_webhook", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "client_problem_count_threshold": { + "name": "client_problem_count_threshold", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 10 + }, + "client_problem_window_minutes": { + "name": "client_problem_window_minutes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 5 + }, + "client_problem_cyber_count_threshold": { + "name": "client_problem_cyber_count_threshold", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 3 + }, + "client_problem_cyber_window_minutes": { + "name": "client_problem_cyber_window_minutes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "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.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.outbox_events": { + "name": "outbox_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "event_id": { + "name": "event_id", + "type": "uuid", + "primaryKey": false, + "notNull": true, + "default": "gen_random_uuid()" + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "aggregate_type": { + "name": "aggregate_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "aggregate_id": { + "name": "aggregate_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "published_at": { + "name": "published_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_outbox_events_unpublished": { + "name": "idx_outbox_events_unpublished", + "columns": [ + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"outbox_events\".\"published_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "outbox_events_event_id_key": { + "name": "outbox_events_event_id_key", + "nullsNotDistinct": false, + "columns": [ + "event_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.outbox_processed": { + "name": "outbox_processed", + "schema": "", + "columns": { + "event_id": { + "name": "event_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "processed_at": { + "name": "processed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.proj_applied_requests": { + "name": "proj_applied_requests", + "schema": "", + "columns": { + "request_id": { + "name": "request_id", + "type": "bigint", + "primaryKey": true, + "notNull": true + }, + "event_id": { + "name": "event_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "applied_at": { + "name": "applied_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.projection_meta": { + "name": "projection_meta", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "value": { + "name": "value", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_batch_apply_operations": { + "name": "provider_batch_apply_operations", + "schema": "", + "columns": { + "claim_key": { + "name": "claim_key", + "type": "varchar(256)", + "primaryKey": true, + "notNull": true + }, + "preview_token": { + "name": "preview_token", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "payload_fingerprint": { + "name": "payload_fingerprint", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "operation_id": { + "name": "operation_id", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "undo_token": { + "name": "undo_token", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "undo_expires_at": { + "name": "undo_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "undo_consumed_at": { + "name": "undo_consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "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": { + "uniq_provider_batch_apply_operations_preview_token": { + "name": "uniq_provider_batch_apply_operations_preview_token", + "columns": [ + { + "expression": "preview_token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "uniq_provider_batch_apply_operations_operation_id": { + "name": "uniq_provider_batch_apply_operations_operation_id", + "columns": [ + { + "expression": "operation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "uniq_provider_batch_apply_operations_undo_token": { + "name": "uniq_provider_batch_apply_operations_undo_token", + "columns": [ + { + "expression": "undo_token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_batch_apply_operations_expires_at": { + "name": "idx_provider_batch_apply_operations_expires_at", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_cache_effectiveness": { + "name": "provider_cache_effectiveness", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "cache_ttl_bucket": { + "name": "cache_ttl_bucket", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "window_start": { + "name": "window_start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "window_end": { + "name": "window_end", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "sample_count": { + "name": "sample_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "eligible_count": { + "name": "eligible_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "theoretical_cache_tokens": { + "name": "theoretical_cache_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "observed_cache_read_tokens": { + "name": "observed_cache_read_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "raw_effectiveness_bp": { + "name": "raw_effectiveness_bp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "confidence_bp": { + "name": "confidence_bp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "effectiveness_bp": { + "name": "effectiveness_bp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_provider_cache_effectiveness_window": { + "name": "idx_provider_cache_effectiveness_window", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "model", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_start", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "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 + }, + "overwrite_response_model": { + "name": "overwrite_response_model", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "provider_prefix": { + "name": "provider_prefix", + "type": "varchar(64)", + "primaryKey": false, + "notNull": 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_image_generation_preference": { + "name": "codex_image_generation_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" + }, + "reasoning_effort_override_rules": { + "name": "reasoning_effort_override_rules", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "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.replay_payloads": { + "name": "replay_payloads", + "schema": "", + "columns": { + "replay_id": { + "name": "replay_id", + "type": "varchar(64)", + "primaryKey": true, + "notNull": true + }, + "verifier": { + "name": "verifier", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "scope_tag": { + "name": "scope_tag", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "key_id": { + "name": "key_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "format": { + "name": "format", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "status_code": { + "name": "status_code", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "headers_json": { + "name": "headers_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "byte_size": { + "name": "byte_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "source_message_request_id": { + "name": "source_message_request_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_replay_payloads_key_id": { + "name": "idx_replay_payloads_key_id", + "columns": [ + { + "expression": "key_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_replay_payloads_expires_at": { + "name": "idx_replay_payloads_expires_at", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "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": "'CC 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 + }, + "bill_hedge_losers": { + "name": "bill_hedge_losers", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "legacy_hedge_max_in_flight": { + "name": "legacy_hedge_max_in_flight", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 2 + }, + "discovery_enabled": { + "name": "discovery_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "discovery_concurrency": { + "name": "discovery_concurrency", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 2 + }, + "max_discovery_rounds": { + "name": "max_discovery_rounds", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 2 + }, + "discovery_sla_ms": { + "name": "discovery_sla_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 10000 + }, + "sticky_sla_ms": { + "name": "sticky_sla_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 20000 + }, + "racing_total_timeout_ms": { + "name": "racing_total_timeout_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 60000 + }, + "sticky_timeout_cooldown_ms": { + "name": "sticky_timeout_cooldown_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 300000 + }, + "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_thinking_effort_conflict_rectifier": { + "name": "enable_thinking_effort_conflict_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_gemini_function_id_rectifier": { + "name": "enable_gemini_function_id_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 + }, + "stream_gate_mode": { + "name": "stream_gate_mode", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "default": "'enforce'" + }, + "affinity_ignore_client_session_id": { + "name": "affinity_ignore_client_session_id", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "replay_enabled": { + "name": "replay_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "replay_cache_ttl_minutes": { + "name": "replay_cache_ttl_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 30 + }, + "cache_effectiveness_enabled": { + "name": "cache_effectiveness_enabled", + "type": "boolean", + "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": { + "system_settings_legacy_hedge_max_in_flight_range": { + "name": "system_settings_legacy_hedge_max_in_flight_range", + "value": "\"system_settings\".\"legacy_hedge_max_in_flight\" >= 1 AND \"system_settings\".\"legacy_hedge_max_in_flight\" <= 4" + } + }, + "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 + }, + "session_identity": { + "name": "session_identity", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "session_identity_kind": { + "name": "session_identity_kind", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "affinity_scope_tag": { + "name": "affinity_scope_tag", + "type": "varchar(16)", + "primaryKey": false, + "notNull": false + }, + "affinity_fingerprint": { + "name": "affinity_fingerprint", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "affinity_fingerprint_chain": { + "name": "affinity_fingerprint_chain", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "is_replay": { + "name": "is_replay", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "replay_source_request_id": { + "name": "replay_source_request_id", + "type": "integer", + "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 + }, + "first_byte_ms": { + "name": "first_byte_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 AND \"usage_ledger\".\"is_replay\" = false", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_user_id_reset": { + "name": "idx_usage_ledger_user_id_reset", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "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 AND \"usage_ledger\".\"is_replay\" = false", + "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 AND \"usage_ledger\".\"is_replay\" = false", + "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_session_identity_created_at": { + "name": "idx_usage_ledger_session_identity_created_at", + "columns": [ + { + "expression": "COALESCE(\"session_identity\", \"session_id\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"created_at\" DESC NULLS LAST", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL AND \"usage_ledger\".\"is_replay\" = false", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_session_identity": { + "name": "idx_usage_ledger_session_identity", + "columns": [ + { + "expression": "COALESCE(\"session_identity\", \"session_id\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_session_identity_prefix": { + "name": "idx_usage_ledger_session_identity_prefix", + "columns": [ + { + "expression": "COALESCE(\"session_identity\", \"session_id\") varchar_pattern_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_session_id_prefix": { + "name": "idx_usage_ledger_session_id_prefix", + "columns": [ + { + "expression": "\"session_id\" varchar_pattern_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS 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 AND \"usage_ledger\".\"is_replay\" = false", + "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 AND \"usage_ledger\".\"is_replay\" = false", + "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 AND \"usage_ledger\".\"is_replay\" = false", + "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 AND \"usage_ledger\".\"is_replay\" = false", + "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", + "client_problem" + ] + }, + "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/0128_snapshot.json b/drizzle/meta/0128_snapshot.json new file mode 100644 index 0000000000..515c627090 --- /dev/null +++ b/drizzle/meta/0128_snapshot.json @@ -0,0 +1,5997 @@ +{ + "id": "451c460e-5625-417d-9ddc-ea31338be4d9", + "prevId": "be9420d3-c709-4f9f-9f6b-977d36ea4fb2", + "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.avail_bucket_1m": { + "name": "avail_bucket_1m", + "schema": "", + "columns": { + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "bucket_start": { + "name": "bucket_start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "success_cnt": { + "name": "success_cnt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "failure_cnt": { + "name": "failure_cnt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "excluded_cnt": { + "name": "excluded_cnt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "latency_cnt": { + "name": "latency_cnt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "latency_sum_ms": { + "name": "latency_sum_ms", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_request_at": { + "name": "last_request_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_avail_bucket_1m_time": { + "name": "idx_avail_bucket_1m_time", + "columns": [ + { + "expression": "bucket_start", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "avail_bucket_1m_pkey": { + "name": "avail_bucket_1m_pkey", + "columns": [ + "provider_id", + "bucket_start" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.avail_current": { + "name": "avail_current", + "schema": "", + "columns": { + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": true, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "availability": { + "name": "availability", + "type": "double precision", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "request_count": { + "name": "request_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_request_at": { + "name": "last_request_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.clickhouse_sync_state": { + "name": "clickhouse_sync_state", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "varchar(32)", + "primaryKey": true, + "notNull": true, + "default": "'default'" + }, + "floor_at": { + "name": "floor_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cloud_pricing_catalog": { + "name": "cloud_pricing_catalog", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "version": { + "name": "version", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "currency": { + "name": "currency", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'USD'" + }, + "refreshed_at": { + "name": "refreshed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "providers": { + "name": "providers", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "vendors": { + "name": "vendors", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "model_count": { + "name": "model_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "synced_at": { + "name": "synced_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "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 + }, + "session_identity": { + "name": "session_identity", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "session_identity_kind": { + "name": "session_identity_kind", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "affinity_scope_tag": { + "name": "affinity_scope_tag", + "type": "varchar(16)", + "primaryKey": false, + "notNull": false + }, + "affinity_fingerprint": { + "name": "affinity_fingerprint", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "affinity_fingerprint_chain": { + "name": "affinity_fingerprint_chain", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "is_replay": { + "name": "is_replay", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "replay_source_request_id": { + "name": "replay_source_request_id", + "type": "integer", + "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 + }, + "routing_trace": { + "name": "routing_trace", + "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 + }, + "first_byte_ms": { + "name": "first_byte_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 + }, + "hedge_losers": { + "name": "hedge_losers", + "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 + }, + "cache_compatibility_key": { + "name": "cache_compatibility_key", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "cache_score_eligible": { + "name": "cache_score_eligible", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "cache_score_excluded_reason": { + "name": "cache_score_excluded_reason", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false + }, + "theoretical_cache_tokens": { + "name": "theoretical_cache_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_ttl_bucket": { + "name": "cache_ttl_bucket", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "clickhouse_synced_at": { + "name": "clickhouse_synced_at", + "type": "timestamp with time zone", + "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_proxy_status_active": { + "name": "idx_message_request_proxy_status_active", + "columns": [ + { + "expression": "\"created_at\" DESC NULLS LAST", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"is_replay\" = false AND \"message_request\".\"status_code\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_proxy_status_latest": { + "name": "idx_message_request_proxy_status_latest", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"updated_at\" DESC NULLS LAST", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"is_replay\" = false AND \"message_request\".\"status_code\" IS NOT NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "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_session_identity_created_at": { + "name": "idx_message_request_session_identity_created_at", + "columns": [ + { + "expression": "COALESCE(\"session_identity\", \"session_id\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "\"created_at\" DESC NULLS LAST", + "asc": true, + "isExpression": true, + "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_session_identity_prefix": { + "name": "idx_message_request_session_identity_prefix", + "columns": [ + { + "expression": "COALESCE(\"session_identity\", \"session_id\") varchar_pattern_ops", + "asc": true, + "isExpression": 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 AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_session_id_prefix_cover": { + "name": "idx_message_request_session_id_prefix_cover", + "columns": [ + { + "expression": "\"session_id\" varchar_pattern_ops", + "asc": true, + "isExpression": 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 AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "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": {} + }, + "idx_message_request_clickhouse_unsynced": { + "name": "idx_message_request_clickhouse_unsynced", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"clickhouse_synced_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "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": "'cloud'" + }, + "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": {} + }, + "idx_model_prices_vendor": { + "name": "idx_model_prices_vendor", + "columns": [ + { + "expression": "((\"price_data\" ->> 'vendor'))", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_model_prices_aliases": { + "name": "idx_model_prices_aliases", + "columns": [ + { + "expression": "((\"price_data\" -> 'aliases'))", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "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 + }, + "title_prefix": { + "name": "title_prefix", + "type": "varchar(64)", + "primaryKey": false, + "notNull": 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 + }, + "client_problem_enabled": { + "name": "client_problem_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "client_problem_webhook": { + "name": "client_problem_webhook", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "client_problem_count_threshold": { + "name": "client_problem_count_threshold", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 10 + }, + "client_problem_window_minutes": { + "name": "client_problem_window_minutes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 5 + }, + "client_problem_cyber_count_threshold": { + "name": "client_problem_cyber_count_threshold", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 3 + }, + "client_problem_cyber_window_minutes": { + "name": "client_problem_cyber_window_minutes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "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.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.outbox_events": { + "name": "outbox_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "event_id": { + "name": "event_id", + "type": "uuid", + "primaryKey": false, + "notNull": true, + "default": "gen_random_uuid()" + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "aggregate_type": { + "name": "aggregate_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "aggregate_id": { + "name": "aggregate_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "published_at": { + "name": "published_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_outbox_events_unpublished": { + "name": "idx_outbox_events_unpublished", + "columns": [ + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"outbox_events\".\"published_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "outbox_events_event_id_key": { + "name": "outbox_events_event_id_key", + "nullsNotDistinct": false, + "columns": [ + "event_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.outbox_processed": { + "name": "outbox_processed", + "schema": "", + "columns": { + "event_id": { + "name": "event_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "processed_at": { + "name": "processed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.proj_applied_requests": { + "name": "proj_applied_requests", + "schema": "", + "columns": { + "request_id": { + "name": "request_id", + "type": "bigint", + "primaryKey": true, + "notNull": true + }, + "event_id": { + "name": "event_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "applied_at": { + "name": "applied_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.projection_meta": { + "name": "projection_meta", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "value": { + "name": "value", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_batch_apply_operations": { + "name": "provider_batch_apply_operations", + "schema": "", + "columns": { + "claim_key": { + "name": "claim_key", + "type": "varchar(256)", + "primaryKey": true, + "notNull": true + }, + "preview_token": { + "name": "preview_token", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "payload_fingerprint": { + "name": "payload_fingerprint", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "operation_id": { + "name": "operation_id", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "undo_token": { + "name": "undo_token", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "undo_expires_at": { + "name": "undo_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "undo_consumed_at": { + "name": "undo_consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "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": { + "uniq_provider_batch_apply_operations_preview_token": { + "name": "uniq_provider_batch_apply_operations_preview_token", + "columns": [ + { + "expression": "preview_token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "uniq_provider_batch_apply_operations_operation_id": { + "name": "uniq_provider_batch_apply_operations_operation_id", + "columns": [ + { + "expression": "operation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "uniq_provider_batch_apply_operations_undo_token": { + "name": "uniq_provider_batch_apply_operations_undo_token", + "columns": [ + { + "expression": "undo_token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_batch_apply_operations_expires_at": { + "name": "idx_provider_batch_apply_operations_expires_at", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_cache_effectiveness": { + "name": "provider_cache_effectiveness", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "cache_ttl_bucket": { + "name": "cache_ttl_bucket", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "window_start": { + "name": "window_start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "window_end": { + "name": "window_end", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "sample_count": { + "name": "sample_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "eligible_count": { + "name": "eligible_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "theoretical_cache_tokens": { + "name": "theoretical_cache_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "observed_cache_read_tokens": { + "name": "observed_cache_read_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "raw_effectiveness_bp": { + "name": "raw_effectiveness_bp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "confidence_bp": { + "name": "confidence_bp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "effectiveness_bp": { + "name": "effectiveness_bp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_provider_cache_effectiveness_window": { + "name": "idx_provider_cache_effectiveness_window", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "model", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_start", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "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 + }, + "overwrite_response_model": { + "name": "overwrite_response_model", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "provider_prefix": { + "name": "provider_prefix", + "type": "varchar(64)", + "primaryKey": false, + "notNull": 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_image_generation_preference": { + "name": "codex_image_generation_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" + }, + "reasoning_effort_override_rules": { + "name": "reasoning_effort_override_rules", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "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.replay_payloads": { + "name": "replay_payloads", + "schema": "", + "columns": { + "replay_id": { + "name": "replay_id", + "type": "varchar(64)", + "primaryKey": true, + "notNull": true + }, + "verifier": { + "name": "verifier", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "scope_tag": { + "name": "scope_tag", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "key_id": { + "name": "key_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "format": { + "name": "format", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "status_code": { + "name": "status_code", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "headers_json": { + "name": "headers_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "byte_size": { + "name": "byte_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "source_message_request_id": { + "name": "source_message_request_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_replay_payloads_key_id": { + "name": "idx_replay_payloads_key_id", + "columns": [ + { + "expression": "key_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_replay_payloads_expires_at": { + "name": "idx_replay_payloads_expires_at", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "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": "'CC 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 + }, + "bill_hedge_losers": { + "name": "bill_hedge_losers", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "legacy_hedge_max_in_flight": { + "name": "legacy_hedge_max_in_flight", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 2 + }, + "discovery_enabled": { + "name": "discovery_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "discovery_concurrency": { + "name": "discovery_concurrency", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 2 + }, + "max_discovery_rounds": { + "name": "max_discovery_rounds", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 2 + }, + "discovery_sla_ms": { + "name": "discovery_sla_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 10000 + }, + "sticky_sla_ms": { + "name": "sticky_sla_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 20000 + }, + "racing_total_timeout_ms": { + "name": "racing_total_timeout_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 60000 + }, + "sticky_timeout_cooldown_ms": { + "name": "sticky_timeout_cooldown_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 300000 + }, + "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_thinking_effort_conflict_rectifier": { + "name": "enable_thinking_effort_conflict_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_gemini_function_id_rectifier": { + "name": "enable_gemini_function_id_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 + }, + "stream_gate_mode": { + "name": "stream_gate_mode", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "default": "'enforce'" + }, + "affinity_ignore_client_session_id": { + "name": "affinity_ignore_client_session_id", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "replay_enabled": { + "name": "replay_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "replay_cache_ttl_minutes": { + "name": "replay_cache_ttl_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 30 + }, + "cache_effectiveness_enabled": { + "name": "cache_effectiveness_enabled", + "type": "boolean", + "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": { + "system_settings_legacy_hedge_max_in_flight_range": { + "name": "system_settings_legacy_hedge_max_in_flight_range", + "value": "\"system_settings\".\"legacy_hedge_max_in_flight\" >= 1 AND \"system_settings\".\"legacy_hedge_max_in_flight\" <= 4" + } + }, + "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 + }, + "session_identity": { + "name": "session_identity", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "session_identity_kind": { + "name": "session_identity_kind", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "affinity_scope_tag": { + "name": "affinity_scope_tag", + "type": "varchar(16)", + "primaryKey": false, + "notNull": false + }, + "affinity_fingerprint": { + "name": "affinity_fingerprint", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "affinity_fingerprint_chain": { + "name": "affinity_fingerprint_chain", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "is_replay": { + "name": "is_replay", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "replay_source_request_id": { + "name": "replay_source_request_id", + "type": "integer", + "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 + }, + "first_byte_ms": { + "name": "first_byte_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 AND \"usage_ledger\".\"is_replay\" = false", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_user_id_reset": { + "name": "idx_usage_ledger_user_id_reset", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "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 AND \"usage_ledger\".\"is_replay\" = false", + "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 AND \"usage_ledger\".\"is_replay\" = false", + "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_session_identity_created_at": { + "name": "idx_usage_ledger_session_identity_created_at", + "columns": [ + { + "expression": "COALESCE(\"session_identity\", \"session_id\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"created_at\" DESC NULLS LAST", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL AND \"usage_ledger\".\"is_replay\" = false", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_session_identity": { + "name": "idx_usage_ledger_session_identity", + "columns": [ + { + "expression": "COALESCE(\"session_identity\", \"session_id\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_session_identity_prefix": { + "name": "idx_usage_ledger_session_identity_prefix", + "columns": [ + { + "expression": "COALESCE(\"session_identity\", \"session_id\") varchar_pattern_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_session_id_prefix": { + "name": "idx_usage_ledger_session_id_prefix", + "columns": [ + { + "expression": "\"session_id\" varchar_pattern_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS 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 AND \"usage_ledger\".\"is_replay\" = false", + "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 AND \"usage_ledger\".\"is_replay\" = false", + "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 AND \"usage_ledger\".\"is_replay\" = false", + "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 AND \"usage_ledger\".\"is_replay\" = false", + "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", + "client_problem" + ] + }, + "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/0129_snapshot.json b/drizzle/meta/0129_snapshot.json new file mode 100644 index 0000000000..900d8e4de6 --- /dev/null +++ b/drizzle/meta/0129_snapshot.json @@ -0,0 +1,6139 @@ +{ + "id": "81722777-c786-49fc-b475-170efb66a89c", + "prevId": "451c460e-5625-417d-9ddc-ea31338be4d9", + "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.avail_bucket_1m": { + "name": "avail_bucket_1m", + "schema": "", + "columns": { + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "bucket_start": { + "name": "bucket_start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "success_cnt": { + "name": "success_cnt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "failure_cnt": { + "name": "failure_cnt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "excluded_cnt": { + "name": "excluded_cnt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "latency_cnt": { + "name": "latency_cnt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "latency_sum_ms": { + "name": "latency_sum_ms", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_request_at": { + "name": "last_request_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_avail_bucket_1m_time": { + "name": "idx_avail_bucket_1m_time", + "columns": [ + { + "expression": "bucket_start", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "avail_bucket_1m_pkey": { + "name": "avail_bucket_1m_pkey", + "columns": [ + "provider_id", + "bucket_start" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.avail_current": { + "name": "avail_current", + "schema": "", + "columns": { + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": true, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "availability": { + "name": "availability", + "type": "double precision", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "request_count": { + "name": "request_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_request_at": { + "name": "last_request_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.clickhouse_sync_state": { + "name": "clickhouse_sync_state", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "varchar(32)", + "primaryKey": true, + "notNull": true, + "default": "'default'" + }, + "floor_at": { + "name": "floor_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cloud_pricing_catalog": { + "name": "cloud_pricing_catalog", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "version": { + "name": "version", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "currency": { + "name": "currency", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'USD'" + }, + "refreshed_at": { + "name": "refreshed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "providers": { + "name": "providers", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "vendors": { + "name": "vendors", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "model_count": { + "name": "model_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "synced_at": { + "name": "synced_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "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 + }, + "session_identity": { + "name": "session_identity", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "session_identity_kind": { + "name": "session_identity_kind", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "affinity_scope_tag": { + "name": "affinity_scope_tag", + "type": "varchar(16)", + "primaryKey": false, + "notNull": false + }, + "affinity_fingerprint": { + "name": "affinity_fingerprint", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "affinity_fingerprint_chain": { + "name": "affinity_fingerprint_chain", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "is_replay": { + "name": "is_replay", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "replay_source_request_id": { + "name": "replay_source_request_id", + "type": "integer", + "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 + }, + "routing_trace": { + "name": "routing_trace", + "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 + }, + "first_byte_ms": { + "name": "first_byte_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 + }, + "hedge_losers": { + "name": "hedge_losers", + "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 + }, + "cache_compatibility_key": { + "name": "cache_compatibility_key", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "cache_score_eligible": { + "name": "cache_score_eligible", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "cache_score_excluded_reason": { + "name": "cache_score_excluded_reason", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false + }, + "theoretical_cache_tokens": { + "name": "theoretical_cache_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_ttl_bucket": { + "name": "cache_ttl_bucket", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "clickhouse_synced_at": { + "name": "clickhouse_synced_at", + "type": "timestamp with time zone", + "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_proxy_status_active": { + "name": "idx_message_request_proxy_status_active", + "columns": [ + { + "expression": "\"created_at\" DESC NULLS LAST", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"is_replay\" = false AND \"message_request\".\"status_code\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_proxy_status_latest": { + "name": "idx_message_request_proxy_status_latest", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"updated_at\" DESC NULLS LAST", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"is_replay\" = false AND \"message_request\".\"status_code\" IS NOT NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "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_session_identity_created_at": { + "name": "idx_message_request_session_identity_created_at", + "columns": [ + { + "expression": "COALESCE(\"session_identity\", \"session_id\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "\"created_at\" DESC NULLS LAST", + "asc": true, + "isExpression": true, + "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_session_identity_prefix": { + "name": "idx_message_request_session_identity_prefix", + "columns": [ + { + "expression": "COALESCE(\"session_identity\", \"session_id\") varchar_pattern_ops", + "asc": true, + "isExpression": 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 AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_session_id_prefix_cover": { + "name": "idx_message_request_session_id_prefix_cover", + "columns": [ + { + "expression": "\"session_id\" varchar_pattern_ops", + "asc": true, + "isExpression": 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 AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "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": {} + }, + "idx_message_request_clickhouse_unsynced": { + "name": "idx_message_request_clickhouse_unsynced", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"clickhouse_synced_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "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": "'cloud'" + }, + "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": {} + }, + "idx_model_prices_vendor": { + "name": "idx_model_prices_vendor", + "columns": [ + { + "expression": "((\"price_data\" ->> 'vendor'))", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_model_prices_aliases": { + "name": "idx_model_prices_aliases", + "columns": [ + { + "expression": "((\"price_data\" -> 'aliases'))", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "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 + }, + "title_prefix": { + "name": "title_prefix", + "type": "varchar(64)", + "primaryKey": false, + "notNull": 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 + }, + "client_problem_enabled": { + "name": "client_problem_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "client_problem_webhook": { + "name": "client_problem_webhook", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "client_problem_count_threshold": { + "name": "client_problem_count_threshold", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 10 + }, + "client_problem_window_minutes": { + "name": "client_problem_window_minutes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 5 + }, + "client_problem_cyber_count_threshold": { + "name": "client_problem_cyber_count_threshold", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 3 + }, + "client_problem_cyber_window_minutes": { + "name": "client_problem_cyber_window_minutes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "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.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.outbox_events": { + "name": "outbox_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "event_id": { + "name": "event_id", + "type": "uuid", + "primaryKey": false, + "notNull": true, + "default": "gen_random_uuid()" + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "aggregate_type": { + "name": "aggregate_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "aggregate_id": { + "name": "aggregate_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "published_at": { + "name": "published_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_outbox_events_unpublished": { + "name": "idx_outbox_events_unpublished", + "columns": [ + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"outbox_events\".\"published_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "outbox_events_event_id_key": { + "name": "outbox_events_event_id_key", + "nullsNotDistinct": false, + "columns": [ + "event_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.outbox_processed": { + "name": "outbox_processed", + "schema": "", + "columns": { + "event_id": { + "name": "event_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "processed_at": { + "name": "processed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.proj_applied_requests": { + "name": "proj_applied_requests", + "schema": "", + "columns": { + "request_id": { + "name": "request_id", + "type": "bigint", + "primaryKey": true, + "notNull": true + }, + "event_id": { + "name": "event_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "applied_at": { + "name": "applied_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.projection_meta": { + "name": "projection_meta", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "value": { + "name": "value", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_batch_apply_operations": { + "name": "provider_batch_apply_operations", + "schema": "", + "columns": { + "claim_key": { + "name": "claim_key", + "type": "varchar(256)", + "primaryKey": true, + "notNull": true + }, + "preview_token": { + "name": "preview_token", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "payload_fingerprint": { + "name": "payload_fingerprint", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "operation_id": { + "name": "operation_id", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "undo_token": { + "name": "undo_token", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "undo_expires_at": { + "name": "undo_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "undo_consumed_at": { + "name": "undo_consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "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": { + "uniq_provider_batch_apply_operations_preview_token": { + "name": "uniq_provider_batch_apply_operations_preview_token", + "columns": [ + { + "expression": "preview_token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "uniq_provider_batch_apply_operations_operation_id": { + "name": "uniq_provider_batch_apply_operations_operation_id", + "columns": [ + { + "expression": "operation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "uniq_provider_batch_apply_operations_undo_token": { + "name": "uniq_provider_batch_apply_operations_undo_token", + "columns": [ + { + "expression": "undo_token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_batch_apply_operations_expires_at": { + "name": "idx_provider_batch_apply_operations_expires_at", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_cache_effectiveness": { + "name": "provider_cache_effectiveness", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "cache_ttl_bucket": { + "name": "cache_ttl_bucket", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "window_start": { + "name": "window_start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "window_end": { + "name": "window_end", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "sample_count": { + "name": "sample_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "eligible_count": { + "name": "eligible_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "theoretical_cache_tokens": { + "name": "theoretical_cache_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "observed_cache_read_tokens": { + "name": "observed_cache_read_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "raw_effectiveness_bp": { + "name": "raw_effectiveness_bp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "confidence_bp": { + "name": "confidence_bp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "effectiveness_bp": { + "name": "effectiveness_bp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_provider_cache_effectiveness_window": { + "name": "idx_provider_cache_effectiveness_window", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "model", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_start", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "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_upstream_quota_snapshots": { + "name": "provider_upstream_quota_snapshots", + "schema": "", + "columns": { + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": true, + "notNull": true + }, + "probe_type": { + "name": "probe_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "windows": { + "name": "windows", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "plan_level": { + "name": "plan_level", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "credential_valid": { + "name": "credential_valid", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_error_status": { + "name": "last_error_status", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "fetched_at": { + "name": "fetched_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "probed_at": { + "name": "probed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "reactive_pause_until": { + "name": "reactive_pause_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "reactive_pause_reason": { + "name": "reactive_pause_reason", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "provider_upstream_quota_snapshots_provider_id_providers_id_fk": { + "name": "provider_upstream_quota_snapshots_provider_id_providers_id_fk", + "tableFrom": "provider_upstream_quota_snapshots", + "tableTo": "providers", + "columnsFrom": [ + "provider_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "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 + }, + "overwrite_response_model": { + "name": "overwrite_response_model", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "provider_prefix": { + "name": "provider_prefix", + "type": "varchar(64)", + "primaryKey": false, + "notNull": 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 + }, + "upstream_quota_probe_type": { + "name": "upstream_quota_probe_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'auto'" + }, + "upstream_quota_threshold_percent": { + "name": "upstream_quota_threshold_percent", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "upstream_quota_probe_options": { + "name": "upstream_quota_probe_options", + "type": "jsonb", + "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_image_generation_preference": { + "name": "codex_image_generation_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" + }, + "reasoning_effort_override_rules": { + "name": "reasoning_effort_override_rules", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "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.replay_payloads": { + "name": "replay_payloads", + "schema": "", + "columns": { + "replay_id": { + "name": "replay_id", + "type": "varchar(64)", + "primaryKey": true, + "notNull": true + }, + "verifier": { + "name": "verifier", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "scope_tag": { + "name": "scope_tag", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "key_id": { + "name": "key_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "format": { + "name": "format", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "status_code": { + "name": "status_code", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "headers_json": { + "name": "headers_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "byte_size": { + "name": "byte_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "source_message_request_id": { + "name": "source_message_request_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_replay_payloads_key_id": { + "name": "idx_replay_payloads_key_id", + "columns": [ + { + "expression": "key_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_replay_payloads_expires_at": { + "name": "idx_replay_payloads_expires_at", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "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": "'CC 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 + }, + "bill_hedge_losers": { + "name": "bill_hedge_losers", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "legacy_hedge_max_in_flight": { + "name": "legacy_hedge_max_in_flight", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 2 + }, + "discovery_enabled": { + "name": "discovery_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "discovery_concurrency": { + "name": "discovery_concurrency", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 2 + }, + "max_discovery_rounds": { + "name": "max_discovery_rounds", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 2 + }, + "discovery_sla_ms": { + "name": "discovery_sla_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 10000 + }, + "sticky_sla_ms": { + "name": "sticky_sla_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 20000 + }, + "racing_total_timeout_ms": { + "name": "racing_total_timeout_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 60000 + }, + "sticky_timeout_cooldown_ms": { + "name": "sticky_timeout_cooldown_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 300000 + }, + "upstream_quota_scheduling_enabled": { + "name": "upstream_quota_scheduling_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "upstream_quota_threshold_percent": { + "name": "upstream_quota_threshold_percent", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 10 + }, + "upstream_quota_probe_interval_minutes": { + "name": "upstream_quota_probe_interval_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 10 + }, + "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_thinking_effort_conflict_rectifier": { + "name": "enable_thinking_effort_conflict_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_gemini_function_id_rectifier": { + "name": "enable_gemini_function_id_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 + }, + "stream_gate_mode": { + "name": "stream_gate_mode", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "default": "'enforce'" + }, + "affinity_ignore_client_session_id": { + "name": "affinity_ignore_client_session_id", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "replay_enabled": { + "name": "replay_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "replay_cache_ttl_minutes": { + "name": "replay_cache_ttl_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 30 + }, + "cache_effectiveness_enabled": { + "name": "cache_effectiveness_enabled", + "type": "boolean", + "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": { + "system_settings_legacy_hedge_max_in_flight_range": { + "name": "system_settings_legacy_hedge_max_in_flight_range", + "value": "\"system_settings\".\"legacy_hedge_max_in_flight\" >= 1 AND \"system_settings\".\"legacy_hedge_max_in_flight\" <= 4" + } + }, + "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 + }, + "session_identity": { + "name": "session_identity", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "session_identity_kind": { + "name": "session_identity_kind", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "affinity_scope_tag": { + "name": "affinity_scope_tag", + "type": "varchar(16)", + "primaryKey": false, + "notNull": false + }, + "affinity_fingerprint": { + "name": "affinity_fingerprint", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "affinity_fingerprint_chain": { + "name": "affinity_fingerprint_chain", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "is_replay": { + "name": "is_replay", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "replay_source_request_id": { + "name": "replay_source_request_id", + "type": "integer", + "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 + }, + "first_byte_ms": { + "name": "first_byte_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 AND \"usage_ledger\".\"is_replay\" = false", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_user_id_reset": { + "name": "idx_usage_ledger_user_id_reset", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "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 AND \"usage_ledger\".\"is_replay\" = false", + "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 AND \"usage_ledger\".\"is_replay\" = false", + "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_session_identity_created_at": { + "name": "idx_usage_ledger_session_identity_created_at", + "columns": [ + { + "expression": "COALESCE(\"session_identity\", \"session_id\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"created_at\" DESC NULLS LAST", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL AND \"usage_ledger\".\"is_replay\" = false", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_session_identity": { + "name": "idx_usage_ledger_session_identity", + "columns": [ + { + "expression": "COALESCE(\"session_identity\", \"session_id\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_session_identity_prefix": { + "name": "idx_usage_ledger_session_identity_prefix", + "columns": [ + { + "expression": "COALESCE(\"session_identity\", \"session_id\") varchar_pattern_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_session_id_prefix": { + "name": "idx_usage_ledger_session_id_prefix", + "columns": [ + { + "expression": "\"session_id\" varchar_pattern_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS 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 AND \"usage_ledger\".\"is_replay\" = false", + "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 AND \"usage_ledger\".\"is_replay\" = false", + "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 AND \"usage_ledger\".\"is_replay\" = false", + "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 AND \"usage_ledger\".\"is_replay\" = false", + "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", + "client_problem" + ] + }, + "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/0130_snapshot.json b/drizzle/meta/0130_snapshot.json new file mode 100644 index 0000000000..6c512e39d3 --- /dev/null +++ b/drizzle/meta/0130_snapshot.json @@ -0,0 +1,6146 @@ +{ + "id": "10c7ef0d-d763-4622-b116-4b96c3cd20ed", + "prevId": "81722777-c786-49fc-b475-170efb66a89c", + "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.avail_bucket_1m": { + "name": "avail_bucket_1m", + "schema": "", + "columns": { + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "bucket_start": { + "name": "bucket_start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "success_cnt": { + "name": "success_cnt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "failure_cnt": { + "name": "failure_cnt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "excluded_cnt": { + "name": "excluded_cnt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "latency_cnt": { + "name": "latency_cnt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "latency_sum_ms": { + "name": "latency_sum_ms", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_request_at": { + "name": "last_request_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_avail_bucket_1m_time": { + "name": "idx_avail_bucket_1m_time", + "columns": [ + { + "expression": "bucket_start", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "avail_bucket_1m_pkey": { + "name": "avail_bucket_1m_pkey", + "columns": [ + "provider_id", + "bucket_start" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.avail_current": { + "name": "avail_current", + "schema": "", + "columns": { + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": true, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "availability": { + "name": "availability", + "type": "double precision", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "request_count": { + "name": "request_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_request_at": { + "name": "last_request_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.clickhouse_sync_state": { + "name": "clickhouse_sync_state", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "varchar(32)", + "primaryKey": true, + "notNull": true, + "default": "'default'" + }, + "floor_at": { + "name": "floor_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cloud_pricing_catalog": { + "name": "cloud_pricing_catalog", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "version": { + "name": "version", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "currency": { + "name": "currency", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'USD'" + }, + "refreshed_at": { + "name": "refreshed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "providers": { + "name": "providers", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "vendors": { + "name": "vendors", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "model_count": { + "name": "model_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "synced_at": { + "name": "synced_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "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 + }, + "session_identity": { + "name": "session_identity", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "session_identity_kind": { + "name": "session_identity_kind", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "affinity_scope_tag": { + "name": "affinity_scope_tag", + "type": "varchar(16)", + "primaryKey": false, + "notNull": false + }, + "affinity_fingerprint": { + "name": "affinity_fingerprint", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "affinity_fingerprint_chain": { + "name": "affinity_fingerprint_chain", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "is_replay": { + "name": "is_replay", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "replay_source_request_id": { + "name": "replay_source_request_id", + "type": "integer", + "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 + }, + "routing_trace": { + "name": "routing_trace", + "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 + }, + "first_byte_ms": { + "name": "first_byte_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 + }, + "hedge_losers": { + "name": "hedge_losers", + "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 + }, + "cache_compatibility_key": { + "name": "cache_compatibility_key", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "cache_score_eligible": { + "name": "cache_score_eligible", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "cache_score_excluded_reason": { + "name": "cache_score_excluded_reason", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false + }, + "theoretical_cache_tokens": { + "name": "theoretical_cache_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_ttl_bucket": { + "name": "cache_ttl_bucket", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "clickhouse_synced_at": { + "name": "clickhouse_synced_at", + "type": "timestamp with time zone", + "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_proxy_status_active": { + "name": "idx_message_request_proxy_status_active", + "columns": [ + { + "expression": "\"created_at\" DESC NULLS LAST", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"is_replay\" = false AND \"message_request\".\"status_code\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_proxy_status_latest": { + "name": "idx_message_request_proxy_status_latest", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"updated_at\" DESC NULLS LAST", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"is_replay\" = false AND \"message_request\".\"status_code\" IS NOT NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "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_session_identity_created_at": { + "name": "idx_message_request_session_identity_created_at", + "columns": [ + { + "expression": "COALESCE(\"session_identity\", \"session_id\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "\"created_at\" DESC NULLS LAST", + "asc": true, + "isExpression": true, + "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_session_identity_prefix": { + "name": "idx_message_request_session_identity_prefix", + "columns": [ + { + "expression": "COALESCE(\"session_identity\", \"session_id\") varchar_pattern_ops", + "asc": true, + "isExpression": 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 AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_session_id_prefix_cover": { + "name": "idx_message_request_session_id_prefix_cover", + "columns": [ + { + "expression": "\"session_id\" varchar_pattern_ops", + "asc": true, + "isExpression": 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 AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "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": {} + }, + "idx_message_request_clickhouse_unsynced": { + "name": "idx_message_request_clickhouse_unsynced", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"clickhouse_synced_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "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": "'cloud'" + }, + "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": {} + }, + "idx_model_prices_vendor": { + "name": "idx_model_prices_vendor", + "columns": [ + { + "expression": "((\"price_data\" ->> 'vendor'))", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_model_prices_aliases": { + "name": "idx_model_prices_aliases", + "columns": [ + { + "expression": "((\"price_data\" -> 'aliases'))", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "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 + }, + "title_prefix": { + "name": "title_prefix", + "type": "varchar(64)", + "primaryKey": false, + "notNull": 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 + }, + "client_problem_enabled": { + "name": "client_problem_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "client_problem_webhook": { + "name": "client_problem_webhook", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "client_problem_count_threshold": { + "name": "client_problem_count_threshold", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 10 + }, + "client_problem_window_minutes": { + "name": "client_problem_window_minutes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 5 + }, + "client_problem_cyber_count_threshold": { + "name": "client_problem_cyber_count_threshold", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 3 + }, + "client_problem_cyber_window_minutes": { + "name": "client_problem_cyber_window_minutes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "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.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.outbox_events": { + "name": "outbox_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "event_id": { + "name": "event_id", + "type": "uuid", + "primaryKey": false, + "notNull": true, + "default": "gen_random_uuid()" + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "aggregate_type": { + "name": "aggregate_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "aggregate_id": { + "name": "aggregate_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "published_at": { + "name": "published_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_outbox_events_unpublished": { + "name": "idx_outbox_events_unpublished", + "columns": [ + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"outbox_events\".\"published_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "outbox_events_event_id_key": { + "name": "outbox_events_event_id_key", + "nullsNotDistinct": false, + "columns": [ + "event_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.outbox_processed": { + "name": "outbox_processed", + "schema": "", + "columns": { + "event_id": { + "name": "event_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "processed_at": { + "name": "processed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.proj_applied_requests": { + "name": "proj_applied_requests", + "schema": "", + "columns": { + "request_id": { + "name": "request_id", + "type": "bigint", + "primaryKey": true, + "notNull": true + }, + "event_id": { + "name": "event_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "applied_at": { + "name": "applied_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.projection_meta": { + "name": "projection_meta", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "value": { + "name": "value", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_batch_apply_operations": { + "name": "provider_batch_apply_operations", + "schema": "", + "columns": { + "claim_key": { + "name": "claim_key", + "type": "varchar(256)", + "primaryKey": true, + "notNull": true + }, + "preview_token": { + "name": "preview_token", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "payload_fingerprint": { + "name": "payload_fingerprint", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "operation_id": { + "name": "operation_id", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "undo_token": { + "name": "undo_token", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "undo_expires_at": { + "name": "undo_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "undo_consumed_at": { + "name": "undo_consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "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": { + "uniq_provider_batch_apply_operations_preview_token": { + "name": "uniq_provider_batch_apply_operations_preview_token", + "columns": [ + { + "expression": "preview_token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "uniq_provider_batch_apply_operations_operation_id": { + "name": "uniq_provider_batch_apply_operations_operation_id", + "columns": [ + { + "expression": "operation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "uniq_provider_batch_apply_operations_undo_token": { + "name": "uniq_provider_batch_apply_operations_undo_token", + "columns": [ + { + "expression": "undo_token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_batch_apply_operations_expires_at": { + "name": "idx_provider_batch_apply_operations_expires_at", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_cache_effectiveness": { + "name": "provider_cache_effectiveness", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "cache_ttl_bucket": { + "name": "cache_ttl_bucket", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "window_start": { + "name": "window_start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "window_end": { + "name": "window_end", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "sample_count": { + "name": "sample_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "eligible_count": { + "name": "eligible_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "theoretical_cache_tokens": { + "name": "theoretical_cache_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "observed_cache_read_tokens": { + "name": "observed_cache_read_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "raw_effectiveness_bp": { + "name": "raw_effectiveness_bp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "confidence_bp": { + "name": "confidence_bp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "effectiveness_bp": { + "name": "effectiveness_bp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_provider_cache_effectiveness_window": { + "name": "idx_provider_cache_effectiveness_window", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "model", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_start", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "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_upstream_quota_snapshots": { + "name": "provider_upstream_quota_snapshots", + "schema": "", + "columns": { + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": true, + "notNull": true + }, + "probe_type": { + "name": "probe_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "windows": { + "name": "windows", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "plan_level": { + "name": "plan_level", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "credential_valid": { + "name": "credential_valid", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_error_status": { + "name": "last_error_status", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "fetched_at": { + "name": "fetched_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "probed_at": { + "name": "probed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "reactive_pause_until": { + "name": "reactive_pause_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "reactive_pause_reason": { + "name": "reactive_pause_reason", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "provider_upstream_quota_snapshots_provider_id_providers_id_fk": { + "name": "provider_upstream_quota_snapshots_provider_id_providers_id_fk", + "tableFrom": "provider_upstream_quota_snapshots", + "tableTo": "providers", + "columnsFrom": [ + "provider_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "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 + }, + "overwrite_response_model": { + "name": "overwrite_response_model", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "provider_prefix": { + "name": "provider_prefix", + "type": "varchar(64)", + "primaryKey": false, + "notNull": 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 + }, + "upstream_quota_probe_type": { + "name": "upstream_quota_probe_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'auto'" + }, + "upstream_quota_threshold_percent": { + "name": "upstream_quota_threshold_percent", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "upstream_quota_probe_options": { + "name": "upstream_quota_probe_options", + "type": "jsonb", + "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_image_generation_preference": { + "name": "codex_image_generation_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" + }, + "reasoning_effort_override_rules": { + "name": "reasoning_effort_override_rules", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "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.replay_payloads": { + "name": "replay_payloads", + "schema": "", + "columns": { + "replay_id": { + "name": "replay_id", + "type": "varchar(64)", + "primaryKey": true, + "notNull": true + }, + "verifier": { + "name": "verifier", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "scope_tag": { + "name": "scope_tag", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "key_id": { + "name": "key_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "format": { + "name": "format", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "status_code": { + "name": "status_code", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "headers_json": { + "name": "headers_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "byte_size": { + "name": "byte_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "source_message_request_id": { + "name": "source_message_request_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_replay_payloads_key_id": { + "name": "idx_replay_payloads_key_id", + "columns": [ + { + "expression": "key_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_replay_payloads_expires_at": { + "name": "idx_replay_payloads_expires_at", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "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": "'CC 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 + }, + "bill_hedge_losers": { + "name": "bill_hedge_losers", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "legacy_hedge_max_in_flight": { + "name": "legacy_hedge_max_in_flight", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 2 + }, + "discovery_enabled": { + "name": "discovery_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "discovery_concurrency": { + "name": "discovery_concurrency", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 2 + }, + "max_discovery_rounds": { + "name": "max_discovery_rounds", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 2 + }, + "discovery_sla_ms": { + "name": "discovery_sla_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 10000 + }, + "sticky_sla_ms": { + "name": "sticky_sla_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 20000 + }, + "racing_total_timeout_ms": { + "name": "racing_total_timeout_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 60000 + }, + "sticky_timeout_cooldown_ms": { + "name": "sticky_timeout_cooldown_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 300000 + }, + "upstream_quota_scheduling_enabled": { + "name": "upstream_quota_scheduling_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "upstream_quota_threshold_percent": { + "name": "upstream_quota_threshold_percent", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 10 + }, + "upstream_quota_probe_interval_minutes": { + "name": "upstream_quota_probe_interval_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 10 + }, + "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_thinking_effort_conflict_rectifier": { + "name": "enable_thinking_effort_conflict_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_gemini_function_id_rectifier": { + "name": "enable_gemini_function_id_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 + }, + "stream_gate_mode": { + "name": "stream_gate_mode", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "default": "'enforce'" + }, + "affinity_ignore_client_session_id": { + "name": "affinity_ignore_client_session_id", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "edge_execution_enabled": { + "name": "edge_execution_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "replay_enabled": { + "name": "replay_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "replay_cache_ttl_minutes": { + "name": "replay_cache_ttl_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 30 + }, + "cache_effectiveness_enabled": { + "name": "cache_effectiveness_enabled", + "type": "boolean", + "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": { + "system_settings_legacy_hedge_max_in_flight_range": { + "name": "system_settings_legacy_hedge_max_in_flight_range", + "value": "\"system_settings\".\"legacy_hedge_max_in_flight\" >= 1 AND \"system_settings\".\"legacy_hedge_max_in_flight\" <= 4" + } + }, + "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 + }, + "session_identity": { + "name": "session_identity", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "session_identity_kind": { + "name": "session_identity_kind", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "affinity_scope_tag": { + "name": "affinity_scope_tag", + "type": "varchar(16)", + "primaryKey": false, + "notNull": false + }, + "affinity_fingerprint": { + "name": "affinity_fingerprint", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "affinity_fingerprint_chain": { + "name": "affinity_fingerprint_chain", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "is_replay": { + "name": "is_replay", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "replay_source_request_id": { + "name": "replay_source_request_id", + "type": "integer", + "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 + }, + "first_byte_ms": { + "name": "first_byte_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 AND \"usage_ledger\".\"is_replay\" = false", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_user_id_reset": { + "name": "idx_usage_ledger_user_id_reset", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "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 AND \"usage_ledger\".\"is_replay\" = false", + "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 AND \"usage_ledger\".\"is_replay\" = false", + "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_session_identity_created_at": { + "name": "idx_usage_ledger_session_identity_created_at", + "columns": [ + { + "expression": "COALESCE(\"session_identity\", \"session_id\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"created_at\" DESC NULLS LAST", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL AND \"usage_ledger\".\"is_replay\" = false", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_session_identity": { + "name": "idx_usage_ledger_session_identity", + "columns": [ + { + "expression": "COALESCE(\"session_identity\", \"session_id\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_session_identity_prefix": { + "name": "idx_usage_ledger_session_identity_prefix", + "columns": [ + { + "expression": "COALESCE(\"session_identity\", \"session_id\") varchar_pattern_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_session_id_prefix": { + "name": "idx_usage_ledger_session_id_prefix", + "columns": [ + { + "expression": "\"session_id\" varchar_pattern_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS 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 AND \"usage_ledger\".\"is_replay\" = false", + "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 AND \"usage_ledger\".\"is_replay\" = false", + "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 AND \"usage_ledger\".\"is_replay\" = false", + "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 AND \"usage_ledger\".\"is_replay\" = false", + "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", + "client_problem" + ] + }, + "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/0131_snapshot.json b/drizzle/meta/0131_snapshot.json new file mode 100644 index 0000000000..7f855b9255 --- /dev/null +++ b/drizzle/meta/0131_snapshot.json @@ -0,0 +1,6165 @@ +{ + "id": "2eff74bb-d4da-4072-9194-32a255e17788", + "prevId": "10c7ef0d-d763-4622-b116-4b96c3cd20ed", + "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.avail_bucket_1m": { + "name": "avail_bucket_1m", + "schema": "", + "columns": { + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "bucket_start": { + "name": "bucket_start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "success_cnt": { + "name": "success_cnt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "failure_cnt": { + "name": "failure_cnt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "excluded_cnt": { + "name": "excluded_cnt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "latency_cnt": { + "name": "latency_cnt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "latency_sum_ms": { + "name": "latency_sum_ms", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_request_at": { + "name": "last_request_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_avail_bucket_1m_time": { + "name": "idx_avail_bucket_1m_time", + "columns": [ + { + "expression": "bucket_start", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "avail_bucket_1m_pkey": { + "name": "avail_bucket_1m_pkey", + "columns": [ + "provider_id", + "bucket_start" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.avail_current": { + "name": "avail_current", + "schema": "", + "columns": { + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": true, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "availability": { + "name": "availability", + "type": "double precision", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "request_count": { + "name": "request_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_request_at": { + "name": "last_request_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.clickhouse_sync_state": { + "name": "clickhouse_sync_state", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "varchar(32)", + "primaryKey": true, + "notNull": true, + "default": "'default'" + }, + "floor_at": { + "name": "floor_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cloud_pricing_catalog": { + "name": "cloud_pricing_catalog", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "version": { + "name": "version", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "currency": { + "name": "currency", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'USD'" + }, + "refreshed_at": { + "name": "refreshed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "providers": { + "name": "providers", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "vendors": { + "name": "vendors", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "model_count": { + "name": "model_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "synced_at": { + "name": "synced_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "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 + }, + "session_identity": { + "name": "session_identity", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "session_identity_kind": { + "name": "session_identity_kind", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "affinity_scope_tag": { + "name": "affinity_scope_tag", + "type": "varchar(16)", + "primaryKey": false, + "notNull": false + }, + "affinity_fingerprint": { + "name": "affinity_fingerprint", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "affinity_fingerprint_chain": { + "name": "affinity_fingerprint_chain", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "is_replay": { + "name": "is_replay", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "replay_source_request_id": { + "name": "replay_source_request_id", + "type": "integer", + "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 + }, + "routing_trace": { + "name": "routing_trace", + "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 + }, + "first_byte_ms": { + "name": "first_byte_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 + }, + "hedge_losers": { + "name": "hedge_losers", + "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 + }, + "cache_compatibility_key": { + "name": "cache_compatibility_key", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "cache_score_eligible": { + "name": "cache_score_eligible", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "cache_score_excluded_reason": { + "name": "cache_score_excluded_reason", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false + }, + "theoretical_cache_tokens": { + "name": "theoretical_cache_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_ttl_bucket": { + "name": "cache_ttl_bucket", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "clickhouse_synced_at": { + "name": "clickhouse_synced_at", + "type": "timestamp with time zone", + "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_proxy_status_active": { + "name": "idx_message_request_proxy_status_active", + "columns": [ + { + "expression": "\"created_at\" DESC NULLS LAST", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"is_replay\" = false AND \"message_request\".\"status_code\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_proxy_status_latest": { + "name": "idx_message_request_proxy_status_latest", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"updated_at\" DESC NULLS LAST", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"is_replay\" = false AND \"message_request\".\"status_code\" IS NOT NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "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_session_identity_created_at": { + "name": "idx_message_request_session_identity_created_at", + "columns": [ + { + "expression": "COALESCE(\"session_identity\", \"session_id\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "\"created_at\" DESC NULLS LAST", + "asc": true, + "isExpression": true, + "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_session_identity_prefix": { + "name": "idx_message_request_session_identity_prefix", + "columns": [ + { + "expression": "COALESCE(\"session_identity\", \"session_id\") varchar_pattern_ops", + "asc": true, + "isExpression": 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 AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_session_id_prefix_cover": { + "name": "idx_message_request_session_id_prefix_cover", + "columns": [ + { + "expression": "\"session_id\" varchar_pattern_ops", + "asc": true, + "isExpression": 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 AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "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": {} + }, + "idx_message_request_clickhouse_unsynced": { + "name": "idx_message_request_clickhouse_unsynced", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"clickhouse_synced_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "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": "'cloud'" + }, + "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": {} + }, + "idx_model_prices_vendor": { + "name": "idx_model_prices_vendor", + "columns": [ + { + "expression": "((\"price_data\" ->> 'vendor'))", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_model_prices_aliases": { + "name": "idx_model_prices_aliases", + "columns": [ + { + "expression": "((\"price_data\" -> 'aliases'))", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "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 + }, + "title_prefix": { + "name": "title_prefix", + "type": "varchar(64)", + "primaryKey": false, + "notNull": 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 + }, + "client_problem_enabled": { + "name": "client_problem_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "client_problem_webhook": { + "name": "client_problem_webhook", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "client_problem_count_threshold": { + "name": "client_problem_count_threshold", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 10 + }, + "client_problem_window_minutes": { + "name": "client_problem_window_minutes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 5 + }, + "client_problem_cyber_count_threshold": { + "name": "client_problem_cyber_count_threshold", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 3 + }, + "client_problem_cyber_window_minutes": { + "name": "client_problem_cyber_window_minutes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "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.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.outbox_events": { + "name": "outbox_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "event_id": { + "name": "event_id", + "type": "uuid", + "primaryKey": false, + "notNull": true, + "default": "gen_random_uuid()" + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "aggregate_type": { + "name": "aggregate_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "aggregate_id": { + "name": "aggregate_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "published_at": { + "name": "published_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_outbox_events_unpublished": { + "name": "idx_outbox_events_unpublished", + "columns": [ + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"outbox_events\".\"published_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "outbox_events_event_id_key": { + "name": "outbox_events_event_id_key", + "nullsNotDistinct": false, + "columns": [ + "event_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.outbox_processed": { + "name": "outbox_processed", + "schema": "", + "columns": { + "event_id": { + "name": "event_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "processed_at": { + "name": "processed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.proj_applied_requests": { + "name": "proj_applied_requests", + "schema": "", + "columns": { + "request_id": { + "name": "request_id", + "type": "bigint", + "primaryKey": true, + "notNull": true + }, + "event_id": { + "name": "event_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "applied_at": { + "name": "applied_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.projection_meta": { + "name": "projection_meta", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "value": { + "name": "value", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_batch_apply_operations": { + "name": "provider_batch_apply_operations", + "schema": "", + "columns": { + "claim_key": { + "name": "claim_key", + "type": "varchar(256)", + "primaryKey": true, + "notNull": true + }, + "preview_token": { + "name": "preview_token", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "payload_fingerprint": { + "name": "payload_fingerprint", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "operation_id": { + "name": "operation_id", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "undo_token": { + "name": "undo_token", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "undo_expires_at": { + "name": "undo_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "undo_consumed_at": { + "name": "undo_consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "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": { + "uniq_provider_batch_apply_operations_preview_token": { + "name": "uniq_provider_batch_apply_operations_preview_token", + "columns": [ + { + "expression": "preview_token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "uniq_provider_batch_apply_operations_operation_id": { + "name": "uniq_provider_batch_apply_operations_operation_id", + "columns": [ + { + "expression": "operation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "uniq_provider_batch_apply_operations_undo_token": { + "name": "uniq_provider_batch_apply_operations_undo_token", + "columns": [ + { + "expression": "undo_token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_batch_apply_operations_expires_at": { + "name": "idx_provider_batch_apply_operations_expires_at", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_cache_effectiveness": { + "name": "provider_cache_effectiveness", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "cache_ttl_bucket": { + "name": "cache_ttl_bucket", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "window_start": { + "name": "window_start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "window_end": { + "name": "window_end", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "sample_count": { + "name": "sample_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "eligible_count": { + "name": "eligible_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "theoretical_cache_tokens": { + "name": "theoretical_cache_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "observed_cache_read_tokens": { + "name": "observed_cache_read_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "raw_effectiveness_bp": { + "name": "raw_effectiveness_bp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "confidence_bp": { + "name": "confidence_bp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "effectiveness_bp": { + "name": "effectiveness_bp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_provider_cache_effectiveness_window": { + "name": "idx_provider_cache_effectiveness_window", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "model", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_start", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "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_upstream_quota_snapshots": { + "name": "provider_upstream_quota_snapshots", + "schema": "", + "columns": { + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": true, + "notNull": true + }, + "probe_type": { + "name": "probe_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "windows": { + "name": "windows", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "plan_level": { + "name": "plan_level", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "credential_valid": { + "name": "credential_valid", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_error_status": { + "name": "last_error_status", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "fetched_at": { + "name": "fetched_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "probed_at": { + "name": "probed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "reactive_pause_until": { + "name": "reactive_pause_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "reactive_pause_reason": { + "name": "reactive_pause_reason", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "provider_upstream_quota_snapshots_provider_id_providers_id_fk": { + "name": "provider_upstream_quota_snapshots_provider_id_providers_id_fk", + "tableFrom": "provider_upstream_quota_snapshots", + "tableTo": "providers", + "columnsFrom": [ + "provider_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "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 + }, + "new_api_access_token": { + "name": "new_api_access_token", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "new_api_user_id": { + "name": "new_api_user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "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 + }, + "overwrite_response_model": { + "name": "overwrite_response_model", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "provider_prefix": { + "name": "provider_prefix", + "type": "varchar(64)", + "primaryKey": false, + "notNull": 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 + }, + "upstream_quota_probe_type": { + "name": "upstream_quota_probe_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'auto'" + }, + "upstream_quota_threshold_percent": { + "name": "upstream_quota_threshold_percent", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "upstream_quota_probe_options": { + "name": "upstream_quota_probe_options", + "type": "jsonb", + "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_image_generation_preference": { + "name": "codex_image_generation_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" + }, + "reasoning_effort_override_rules": { + "name": "reasoning_effort_override_rules", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "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.replay_payloads": { + "name": "replay_payloads", + "schema": "", + "columns": { + "replay_id": { + "name": "replay_id", + "type": "varchar(64)", + "primaryKey": true, + "notNull": true + }, + "verifier": { + "name": "verifier", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "scope_tag": { + "name": "scope_tag", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "key_id": { + "name": "key_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "format": { + "name": "format", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "status_code": { + "name": "status_code", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "headers_json": { + "name": "headers_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "byte_size": { + "name": "byte_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "source_message_request_id": { + "name": "source_message_request_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_replay_payloads_key_id": { + "name": "idx_replay_payloads_key_id", + "columns": [ + { + "expression": "key_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_replay_payloads_expires_at": { + "name": "idx_replay_payloads_expires_at", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "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": "'CC 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 + }, + "bill_hedge_losers": { + "name": "bill_hedge_losers", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "legacy_hedge_max_in_flight": { + "name": "legacy_hedge_max_in_flight", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 2 + }, + "discovery_enabled": { + "name": "discovery_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "discovery_concurrency": { + "name": "discovery_concurrency", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 2 + }, + "max_discovery_rounds": { + "name": "max_discovery_rounds", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 2 + }, + "discovery_sla_ms": { + "name": "discovery_sla_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 10000 + }, + "sticky_sla_ms": { + "name": "sticky_sla_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 20000 + }, + "racing_total_timeout_ms": { + "name": "racing_total_timeout_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 60000 + }, + "sticky_timeout_cooldown_ms": { + "name": "sticky_timeout_cooldown_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 300000 + }, + "upstream_quota_scheduling_enabled": { + "name": "upstream_quota_scheduling_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "upstream_quota_threshold_percent": { + "name": "upstream_quota_threshold_percent", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 10 + }, + "upstream_quota_probe_interval_minutes": { + "name": "upstream_quota_probe_interval_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 10 + }, + "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_thinking_effort_conflict_rectifier": { + "name": "enable_thinking_effort_conflict_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_gemini_function_id_rectifier": { + "name": "enable_gemini_function_id_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 + }, + "stream_gate_mode": { + "name": "stream_gate_mode", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "default": "'enforce'" + }, + "affinity_ignore_client_session_id": { + "name": "affinity_ignore_client_session_id", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "edge_execution_enabled": { + "name": "edge_execution_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "replay_enabled": { + "name": "replay_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "replay_cache_ttl_minutes": { + "name": "replay_cache_ttl_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 30 + }, + "cache_effectiveness_enabled": { + "name": "cache_effectiveness_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "enable_memory_admission": { + "name": "enable_memory_admission", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": 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": { + "system_settings_legacy_hedge_max_in_flight_range": { + "name": "system_settings_legacy_hedge_max_in_flight_range", + "value": "\"system_settings\".\"legacy_hedge_max_in_flight\" >= 1 AND \"system_settings\".\"legacy_hedge_max_in_flight\" <= 4" + } + }, + "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 + }, + "session_identity": { + "name": "session_identity", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "session_identity_kind": { + "name": "session_identity_kind", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "affinity_scope_tag": { + "name": "affinity_scope_tag", + "type": "varchar(16)", + "primaryKey": false, + "notNull": false + }, + "affinity_fingerprint": { + "name": "affinity_fingerprint", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "affinity_fingerprint_chain": { + "name": "affinity_fingerprint_chain", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "is_replay": { + "name": "is_replay", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "replay_source_request_id": { + "name": "replay_source_request_id", + "type": "integer", + "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 + }, + "first_byte_ms": { + "name": "first_byte_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 AND \"usage_ledger\".\"is_replay\" = false", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_user_id_reset": { + "name": "idx_usage_ledger_user_id_reset", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "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 AND \"usage_ledger\".\"is_replay\" = false", + "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 AND \"usage_ledger\".\"is_replay\" = false", + "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_session_identity_created_at": { + "name": "idx_usage_ledger_session_identity_created_at", + "columns": [ + { + "expression": "COALESCE(\"session_identity\", \"session_id\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"created_at\" DESC NULLS LAST", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL AND \"usage_ledger\".\"is_replay\" = false", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_session_identity": { + "name": "idx_usage_ledger_session_identity", + "columns": [ + { + "expression": "COALESCE(\"session_identity\", \"session_id\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_session_identity_prefix": { + "name": "idx_usage_ledger_session_identity_prefix", + "columns": [ + { + "expression": "COALESCE(\"session_identity\", \"session_id\") varchar_pattern_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_session_id_prefix": { + "name": "idx_usage_ledger_session_id_prefix", + "columns": [ + { + "expression": "\"session_id\" varchar_pattern_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS 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 AND \"usage_ledger\".\"is_replay\" = false", + "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 AND \"usage_ledger\".\"is_replay\" = false", + "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 AND \"usage_ledger\".\"is_replay\" = false", + "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 AND \"usage_ledger\".\"is_replay\" = false", + "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", + "client_problem" + ] + }, + "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/0132_snapshot.json b/drizzle/meta/0132_snapshot.json new file mode 100644 index 0000000000..593e277551 --- /dev/null +++ b/drizzle/meta/0132_snapshot.json @@ -0,0 +1,6171 @@ +{ + "id": "f92bd6da-2c3c-45ac-bcc9-3b470f79e47d", + "prevId": "2eff74bb-d4da-4072-9194-32a255e17788", + "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.avail_bucket_1m": { + "name": "avail_bucket_1m", + "schema": "", + "columns": { + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "bucket_start": { + "name": "bucket_start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "success_cnt": { + "name": "success_cnt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "failure_cnt": { + "name": "failure_cnt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "excluded_cnt": { + "name": "excluded_cnt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "latency_cnt": { + "name": "latency_cnt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "latency_sum_ms": { + "name": "latency_sum_ms", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_request_at": { + "name": "last_request_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_avail_bucket_1m_time": { + "name": "idx_avail_bucket_1m_time", + "columns": [ + { + "expression": "bucket_start", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "avail_bucket_1m_pkey": { + "name": "avail_bucket_1m_pkey", + "columns": [ + "provider_id", + "bucket_start" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.avail_current": { + "name": "avail_current", + "schema": "", + "columns": { + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": true, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "availability": { + "name": "availability", + "type": "double precision", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "request_count": { + "name": "request_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_request_at": { + "name": "last_request_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.clickhouse_sync_state": { + "name": "clickhouse_sync_state", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "varchar(32)", + "primaryKey": true, + "notNull": true, + "default": "'default'" + }, + "floor_at": { + "name": "floor_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cloud_pricing_catalog": { + "name": "cloud_pricing_catalog", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "version": { + "name": "version", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "currency": { + "name": "currency", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'USD'" + }, + "refreshed_at": { + "name": "refreshed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "providers": { + "name": "providers", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "vendors": { + "name": "vendors", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "model_count": { + "name": "model_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "synced_at": { + "name": "synced_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "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 + }, + "session_identity": { + "name": "session_identity", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "session_identity_kind": { + "name": "session_identity_kind", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "affinity_scope_tag": { + "name": "affinity_scope_tag", + "type": "varchar(16)", + "primaryKey": false, + "notNull": false + }, + "affinity_fingerprint": { + "name": "affinity_fingerprint", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "affinity_fingerprint_chain": { + "name": "affinity_fingerprint_chain", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "is_replay": { + "name": "is_replay", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "replay_source_request_id": { + "name": "replay_source_request_id", + "type": "integer", + "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 + }, + "routing_trace": { + "name": "routing_trace", + "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 + }, + "first_byte_ms": { + "name": "first_byte_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 + }, + "hedge_losers": { + "name": "hedge_losers", + "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 + }, + "cache_compatibility_key": { + "name": "cache_compatibility_key", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "cache_score_eligible": { + "name": "cache_score_eligible", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "cache_score_excluded_reason": { + "name": "cache_score_excluded_reason", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false + }, + "theoretical_cache_tokens": { + "name": "theoretical_cache_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_ttl_bucket": { + "name": "cache_ttl_bucket", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "clickhouse_synced_at": { + "name": "clickhouse_synced_at", + "type": "timestamp with time zone", + "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_proxy_status_active": { + "name": "idx_message_request_proxy_status_active", + "columns": [ + { + "expression": "\"created_at\" DESC NULLS LAST", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"is_replay\" = false AND \"message_request\".\"status_code\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_proxy_status_latest": { + "name": "idx_message_request_proxy_status_latest", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"updated_at\" DESC NULLS LAST", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"is_replay\" = false AND \"message_request\".\"status_code\" IS NOT NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "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_session_identity_created_at": { + "name": "idx_message_request_session_identity_created_at", + "columns": [ + { + "expression": "COALESCE(\"session_identity\", \"session_id\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "\"created_at\" DESC NULLS LAST", + "asc": true, + "isExpression": true, + "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_session_identity_prefix": { + "name": "idx_message_request_session_identity_prefix", + "columns": [ + { + "expression": "COALESCE(\"session_identity\", \"session_id\") varchar_pattern_ops", + "asc": true, + "isExpression": 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 AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_session_id_prefix_cover": { + "name": "idx_message_request_session_id_prefix_cover", + "columns": [ + { + "expression": "\"session_id\" varchar_pattern_ops", + "asc": true, + "isExpression": 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 AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "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": {} + }, + "idx_message_request_clickhouse_unsynced": { + "name": "idx_message_request_clickhouse_unsynced", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"clickhouse_synced_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "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": "'cloud'" + }, + "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": {} + }, + "idx_model_prices_vendor": { + "name": "idx_model_prices_vendor", + "columns": [ + { + "expression": "((\"price_data\" ->> 'vendor'))", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_model_prices_aliases": { + "name": "idx_model_prices_aliases", + "columns": [ + { + "expression": "((\"price_data\" -> 'aliases'))", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "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 + }, + "title_prefix": { + "name": "title_prefix", + "type": "varchar(64)", + "primaryKey": false, + "notNull": 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 + }, + "client_problem_enabled": { + "name": "client_problem_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "client_problem_webhook": { + "name": "client_problem_webhook", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "client_problem_count_threshold": { + "name": "client_problem_count_threshold", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 10 + }, + "client_problem_window_minutes": { + "name": "client_problem_window_minutes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 5 + }, + "client_problem_cyber_count_threshold": { + "name": "client_problem_cyber_count_threshold", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 3 + }, + "client_problem_cyber_window_minutes": { + "name": "client_problem_cyber_window_minutes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "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.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.outbox_events": { + "name": "outbox_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "event_id": { + "name": "event_id", + "type": "uuid", + "primaryKey": false, + "notNull": true, + "default": "gen_random_uuid()" + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "aggregate_type": { + "name": "aggregate_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "aggregate_id": { + "name": "aggregate_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "published_at": { + "name": "published_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_outbox_events_unpublished": { + "name": "idx_outbox_events_unpublished", + "columns": [ + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"outbox_events\".\"published_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "outbox_events_event_id_key": { + "name": "outbox_events_event_id_key", + "nullsNotDistinct": false, + "columns": [ + "event_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.outbox_processed": { + "name": "outbox_processed", + "schema": "", + "columns": { + "event_id": { + "name": "event_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "processed_at": { + "name": "processed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.proj_applied_requests": { + "name": "proj_applied_requests", + "schema": "", + "columns": { + "request_id": { + "name": "request_id", + "type": "bigint", + "primaryKey": true, + "notNull": true + }, + "event_id": { + "name": "event_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "applied_at": { + "name": "applied_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.projection_meta": { + "name": "projection_meta", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "value": { + "name": "value", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_batch_apply_operations": { + "name": "provider_batch_apply_operations", + "schema": "", + "columns": { + "claim_key": { + "name": "claim_key", + "type": "varchar(256)", + "primaryKey": true, + "notNull": true + }, + "preview_token": { + "name": "preview_token", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "payload_fingerprint": { + "name": "payload_fingerprint", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "operation_id": { + "name": "operation_id", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "undo_token": { + "name": "undo_token", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "undo_expires_at": { + "name": "undo_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "undo_consumed_at": { + "name": "undo_consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "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": { + "uniq_provider_batch_apply_operations_preview_token": { + "name": "uniq_provider_batch_apply_operations_preview_token", + "columns": [ + { + "expression": "preview_token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "uniq_provider_batch_apply_operations_operation_id": { + "name": "uniq_provider_batch_apply_operations_operation_id", + "columns": [ + { + "expression": "operation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "uniq_provider_batch_apply_operations_undo_token": { + "name": "uniq_provider_batch_apply_operations_undo_token", + "columns": [ + { + "expression": "undo_token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_batch_apply_operations_expires_at": { + "name": "idx_provider_batch_apply_operations_expires_at", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_cache_effectiveness": { + "name": "provider_cache_effectiveness", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "cache_ttl_bucket": { + "name": "cache_ttl_bucket", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "window_start": { + "name": "window_start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "window_end": { + "name": "window_end", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "sample_count": { + "name": "sample_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "eligible_count": { + "name": "eligible_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "theoretical_cache_tokens": { + "name": "theoretical_cache_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "observed_cache_read_tokens": { + "name": "observed_cache_read_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "raw_effectiveness_bp": { + "name": "raw_effectiveness_bp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "confidence_bp": { + "name": "confidence_bp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "effectiveness_bp": { + "name": "effectiveness_bp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_provider_cache_effectiveness_window": { + "name": "idx_provider_cache_effectiveness_window", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "model", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_start", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "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_upstream_quota_snapshots": { + "name": "provider_upstream_quota_snapshots", + "schema": "", + "columns": { + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": true, + "notNull": true + }, + "probe_type": { + "name": "probe_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "windows": { + "name": "windows", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "plan_level": { + "name": "plan_level", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "credential_valid": { + "name": "credential_valid", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_error_status": { + "name": "last_error_status", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "fetched_at": { + "name": "fetched_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "probed_at": { + "name": "probed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "reactive_pause_until": { + "name": "reactive_pause_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "reactive_pause_reason": { + "name": "reactive_pause_reason", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "provider_upstream_quota_snapshots_provider_id_providers_id_fk": { + "name": "provider_upstream_quota_snapshots_provider_id_providers_id_fk", + "tableFrom": "provider_upstream_quota_snapshots", + "tableTo": "providers", + "columnsFrom": [ + "provider_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "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 + }, + "new_api_access_token": { + "name": "new_api_access_token", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "new_api_user_id": { + "name": "new_api_user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "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 + }, + "overwrite_response_model": { + "name": "overwrite_response_model", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "provider_prefix": { + "name": "provider_prefix", + "type": "varchar(64)", + "primaryKey": false, + "notNull": 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 + }, + "upstream_quota_probe_type": { + "name": "upstream_quota_probe_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'auto'" + }, + "upstream_quota_threshold_percent": { + "name": "upstream_quota_threshold_percent", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "upstream_quota_probe_options": { + "name": "upstream_quota_probe_options", + "type": "jsonb", + "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_image_generation_preference": { + "name": "codex_image_generation_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" + }, + "reasoning_effort_override_rules": { + "name": "reasoning_effort_override_rules", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "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.replay_payloads": { + "name": "replay_payloads", + "schema": "", + "columns": { + "replay_id": { + "name": "replay_id", + "type": "varchar(64)", + "primaryKey": true, + "notNull": true + }, + "verifier": { + "name": "verifier", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "scope_tag": { + "name": "scope_tag", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "key_id": { + "name": "key_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "format": { + "name": "format", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "status_code": { + "name": "status_code", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "headers_json": { + "name": "headers_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "byte_size": { + "name": "byte_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "source_message_request_id": { + "name": "source_message_request_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_replay_payloads_key_id": { + "name": "idx_replay_payloads_key_id", + "columns": [ + { + "expression": "key_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_replay_payloads_expires_at": { + "name": "idx_replay_payloads_expires_at", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "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": "'CC 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 + }, + "bill_hedge_losers": { + "name": "bill_hedge_losers", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "legacy_hedge_max_in_flight": { + "name": "legacy_hedge_max_in_flight", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 2 + }, + "discovery_enabled": { + "name": "discovery_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "discovery_concurrency": { + "name": "discovery_concurrency", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 2 + }, + "max_discovery_rounds": { + "name": "max_discovery_rounds", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 2 + }, + "discovery_sla_ms": { + "name": "discovery_sla_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 10000 + }, + "sticky_sla_ms": { + "name": "sticky_sla_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 20000 + }, + "racing_total_timeout_ms": { + "name": "racing_total_timeout_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 60000 + }, + "sticky_timeout_cooldown_ms": { + "name": "sticky_timeout_cooldown_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 300000 + }, + "upstream_quota_scheduling_enabled": { + "name": "upstream_quota_scheduling_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "upstream_quota_threshold_percent": { + "name": "upstream_quota_threshold_percent", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 10 + }, + "upstream_quota_probe_interval_minutes": { + "name": "upstream_quota_probe_interval_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 10 + }, + "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_thinking_effort_conflict_rectifier": { + "name": "enable_thinking_effort_conflict_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_gemini_function_id_rectifier": { + "name": "enable_gemini_function_id_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 + }, + "stream_gate_mode": { + "name": "stream_gate_mode", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "default": "'enforce'" + }, + "affinity_ignore_client_session_id": { + "name": "affinity_ignore_client_session_id", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "edge_execution_enabled": { + "name": "edge_execution_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "replay_enabled": { + "name": "replay_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "replay_cache_ttl_minutes": { + "name": "replay_cache_ttl_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 30 + }, + "cache_effectiveness_enabled": { + "name": "cache_effectiveness_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "enable_memory_admission": { + "name": "enable_memory_admission", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "agent_catalog_notes": { + "name": "agent_catalog_notes", + "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": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "system_settings_legacy_hedge_max_in_flight_range": { + "name": "system_settings_legacy_hedge_max_in_flight_range", + "value": "\"system_settings\".\"legacy_hedge_max_in_flight\" >= 1 AND \"system_settings\".\"legacy_hedge_max_in_flight\" <= 4" + } + }, + "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 + }, + "session_identity": { + "name": "session_identity", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "session_identity_kind": { + "name": "session_identity_kind", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "affinity_scope_tag": { + "name": "affinity_scope_tag", + "type": "varchar(16)", + "primaryKey": false, + "notNull": false + }, + "affinity_fingerprint": { + "name": "affinity_fingerprint", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "affinity_fingerprint_chain": { + "name": "affinity_fingerprint_chain", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "is_replay": { + "name": "is_replay", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "replay_source_request_id": { + "name": "replay_source_request_id", + "type": "integer", + "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 + }, + "first_byte_ms": { + "name": "first_byte_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 AND \"usage_ledger\".\"is_replay\" = false", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_user_id_reset": { + "name": "idx_usage_ledger_user_id_reset", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "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 AND \"usage_ledger\".\"is_replay\" = false", + "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 AND \"usage_ledger\".\"is_replay\" = false", + "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_session_identity_created_at": { + "name": "idx_usage_ledger_session_identity_created_at", + "columns": [ + { + "expression": "COALESCE(\"session_identity\", \"session_id\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"created_at\" DESC NULLS LAST", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL AND \"usage_ledger\".\"is_replay\" = false", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_session_identity": { + "name": "idx_usage_ledger_session_identity", + "columns": [ + { + "expression": "COALESCE(\"session_identity\", \"session_id\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_session_identity_prefix": { + "name": "idx_usage_ledger_session_identity_prefix", + "columns": [ + { + "expression": "COALESCE(\"session_identity\", \"session_id\") varchar_pattern_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_session_id_prefix": { + "name": "idx_usage_ledger_session_id_prefix", + "columns": [ + { + "expression": "\"session_id\" varchar_pattern_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS 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 AND \"usage_ledger\".\"is_replay\" = false", + "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 AND \"usage_ledger\".\"is_replay\" = false", + "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 AND \"usage_ledger\".\"is_replay\" = false", + "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 AND \"usage_ledger\".\"is_replay\" = false", + "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", + "client_problem" + ] + }, + "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/0133_snapshot.json b/drizzle/meta/0133_snapshot.json new file mode 100644 index 0000000000..42c30ab8c6 --- /dev/null +++ b/drizzle/meta/0133_snapshot.json @@ -0,0 +1,6186 @@ +{ + "id": "64cd2f2d-d302-49fc-a30b-7db0a5ccfdc3", + "prevId": "f92bd6da-2c3c-45ac-bcc9-3b470f79e47d", + "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.avail_bucket_1m": { + "name": "avail_bucket_1m", + "schema": "", + "columns": { + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "bucket_start": { + "name": "bucket_start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "success_cnt": { + "name": "success_cnt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "failure_cnt": { + "name": "failure_cnt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "excluded_cnt": { + "name": "excluded_cnt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "latency_cnt": { + "name": "latency_cnt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "latency_sum_ms": { + "name": "latency_sum_ms", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_request_at": { + "name": "last_request_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_avail_bucket_1m_time": { + "name": "idx_avail_bucket_1m_time", + "columns": [ + { + "expression": "bucket_start", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "avail_bucket_1m_pkey": { + "name": "avail_bucket_1m_pkey", + "columns": [ + "provider_id", + "bucket_start" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.avail_current": { + "name": "avail_current", + "schema": "", + "columns": { + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": true, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "availability": { + "name": "availability", + "type": "double precision", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "request_count": { + "name": "request_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_request_at": { + "name": "last_request_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.clickhouse_sync_state": { + "name": "clickhouse_sync_state", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "varchar(32)", + "primaryKey": true, + "notNull": true, + "default": "'default'" + }, + "floor_at": { + "name": "floor_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cloud_pricing_catalog": { + "name": "cloud_pricing_catalog", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "version": { + "name": "version", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "currency": { + "name": "currency", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'USD'" + }, + "refreshed_at": { + "name": "refreshed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "providers": { + "name": "providers", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "vendors": { + "name": "vendors", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "model_count": { + "name": "model_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "synced_at": { + "name": "synced_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "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 + }, + "session_identity": { + "name": "session_identity", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "session_identity_kind": { + "name": "session_identity_kind", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "affinity_scope_tag": { + "name": "affinity_scope_tag", + "type": "varchar(16)", + "primaryKey": false, + "notNull": false + }, + "affinity_fingerprint": { + "name": "affinity_fingerprint", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "affinity_fingerprint_chain": { + "name": "affinity_fingerprint_chain", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "is_replay": { + "name": "is_replay", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "replay_source_request_id": { + "name": "replay_source_request_id", + "type": "integer", + "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 + }, + "routing_trace": { + "name": "routing_trace", + "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 + }, + "first_byte_ms": { + "name": "first_byte_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 + }, + "hedge_losers": { + "name": "hedge_losers", + "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 + }, + "cache_compatibility_key": { + "name": "cache_compatibility_key", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "cache_score_eligible": { + "name": "cache_score_eligible", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "cache_score_excluded_reason": { + "name": "cache_score_excluded_reason", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false + }, + "theoretical_cache_tokens": { + "name": "theoretical_cache_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_ttl_bucket": { + "name": "cache_ttl_bucket", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "clickhouse_synced_at": { + "name": "clickhouse_synced_at", + "type": "timestamp with time zone", + "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_proxy_status_active": { + "name": "idx_message_request_proxy_status_active", + "columns": [ + { + "expression": "\"created_at\" DESC NULLS LAST", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"is_replay\" = false AND \"message_request\".\"status_code\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_proxy_status_latest": { + "name": "idx_message_request_proxy_status_latest", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"updated_at\" DESC NULLS LAST", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"is_replay\" = false AND \"message_request\".\"status_code\" IS NOT NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "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_session_identity_created_at": { + "name": "idx_message_request_session_identity_created_at", + "columns": [ + { + "expression": "COALESCE(\"session_identity\", \"session_id\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "\"created_at\" DESC NULLS LAST", + "asc": true, + "isExpression": true, + "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_session_identity_prefix": { + "name": "idx_message_request_session_identity_prefix", + "columns": [ + { + "expression": "COALESCE(\"session_identity\", \"session_id\") varchar_pattern_ops", + "asc": true, + "isExpression": 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 AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_session_id_prefix_cover": { + "name": "idx_message_request_session_id_prefix_cover", + "columns": [ + { + "expression": "\"session_id\" varchar_pattern_ops", + "asc": true, + "isExpression": 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 AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "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": {} + }, + "idx_message_request_clickhouse_unsynced": { + "name": "idx_message_request_clickhouse_unsynced", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"clickhouse_synced_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "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": "'cloud'" + }, + "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_model_name_lower": { + "name": "idx_model_prices_model_name_lower", + "columns": [ + { + "expression": "lower(\"model_name\")", + "asc": true, + "isExpression": 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": {} + }, + "idx_model_prices_vendor": { + "name": "idx_model_prices_vendor", + "columns": [ + { + "expression": "((\"price_data\" ->> 'vendor'))", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_model_prices_aliases": { + "name": "idx_model_prices_aliases", + "columns": [ + { + "expression": "((\"price_data\" -> 'aliases'))", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "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 + }, + "title_prefix": { + "name": "title_prefix", + "type": "varchar(64)", + "primaryKey": false, + "notNull": 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 + }, + "client_problem_enabled": { + "name": "client_problem_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "client_problem_webhook": { + "name": "client_problem_webhook", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "client_problem_count_threshold": { + "name": "client_problem_count_threshold", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 10 + }, + "client_problem_window_minutes": { + "name": "client_problem_window_minutes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 5 + }, + "client_problem_cyber_count_threshold": { + "name": "client_problem_cyber_count_threshold", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 3 + }, + "client_problem_cyber_window_minutes": { + "name": "client_problem_cyber_window_minutes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "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.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.outbox_events": { + "name": "outbox_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "event_id": { + "name": "event_id", + "type": "uuid", + "primaryKey": false, + "notNull": true, + "default": "gen_random_uuid()" + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "aggregate_type": { + "name": "aggregate_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "aggregate_id": { + "name": "aggregate_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "published_at": { + "name": "published_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_outbox_events_unpublished": { + "name": "idx_outbox_events_unpublished", + "columns": [ + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"outbox_events\".\"published_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "outbox_events_event_id_key": { + "name": "outbox_events_event_id_key", + "nullsNotDistinct": false, + "columns": [ + "event_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.outbox_processed": { + "name": "outbox_processed", + "schema": "", + "columns": { + "event_id": { + "name": "event_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "processed_at": { + "name": "processed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.proj_applied_requests": { + "name": "proj_applied_requests", + "schema": "", + "columns": { + "request_id": { + "name": "request_id", + "type": "bigint", + "primaryKey": true, + "notNull": true + }, + "event_id": { + "name": "event_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "applied_at": { + "name": "applied_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.projection_meta": { + "name": "projection_meta", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "value": { + "name": "value", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_batch_apply_operations": { + "name": "provider_batch_apply_operations", + "schema": "", + "columns": { + "claim_key": { + "name": "claim_key", + "type": "varchar(256)", + "primaryKey": true, + "notNull": true + }, + "preview_token": { + "name": "preview_token", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "payload_fingerprint": { + "name": "payload_fingerprint", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "operation_id": { + "name": "operation_id", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "undo_token": { + "name": "undo_token", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "undo_expires_at": { + "name": "undo_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "undo_consumed_at": { + "name": "undo_consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "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": { + "uniq_provider_batch_apply_operations_preview_token": { + "name": "uniq_provider_batch_apply_operations_preview_token", + "columns": [ + { + "expression": "preview_token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "uniq_provider_batch_apply_operations_operation_id": { + "name": "uniq_provider_batch_apply_operations_operation_id", + "columns": [ + { + "expression": "operation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "uniq_provider_batch_apply_operations_undo_token": { + "name": "uniq_provider_batch_apply_operations_undo_token", + "columns": [ + { + "expression": "undo_token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_batch_apply_operations_expires_at": { + "name": "idx_provider_batch_apply_operations_expires_at", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_cache_effectiveness": { + "name": "provider_cache_effectiveness", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "cache_ttl_bucket": { + "name": "cache_ttl_bucket", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "window_start": { + "name": "window_start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "window_end": { + "name": "window_end", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "sample_count": { + "name": "sample_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "eligible_count": { + "name": "eligible_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "theoretical_cache_tokens": { + "name": "theoretical_cache_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "observed_cache_read_tokens": { + "name": "observed_cache_read_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "raw_effectiveness_bp": { + "name": "raw_effectiveness_bp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "confidence_bp": { + "name": "confidence_bp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "effectiveness_bp": { + "name": "effectiveness_bp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_provider_cache_effectiveness_window": { + "name": "idx_provider_cache_effectiveness_window", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "model", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_start", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "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_upstream_quota_snapshots": { + "name": "provider_upstream_quota_snapshots", + "schema": "", + "columns": { + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": true, + "notNull": true + }, + "probe_type": { + "name": "probe_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "windows": { + "name": "windows", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "plan_level": { + "name": "plan_level", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "credential_valid": { + "name": "credential_valid", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_error_status": { + "name": "last_error_status", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "fetched_at": { + "name": "fetched_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "probed_at": { + "name": "probed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "reactive_pause_until": { + "name": "reactive_pause_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "reactive_pause_reason": { + "name": "reactive_pause_reason", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "provider_upstream_quota_snapshots_provider_id_providers_id_fk": { + "name": "provider_upstream_quota_snapshots_provider_id_providers_id_fk", + "tableFrom": "provider_upstream_quota_snapshots", + "tableTo": "providers", + "columnsFrom": [ + "provider_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "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 + }, + "new_api_access_token": { + "name": "new_api_access_token", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "new_api_user_id": { + "name": "new_api_user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "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 + }, + "overwrite_response_model": { + "name": "overwrite_response_model", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "provider_prefix": { + "name": "provider_prefix", + "type": "varchar(64)", + "primaryKey": false, + "notNull": 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 + }, + "upstream_quota_probe_type": { + "name": "upstream_quota_probe_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'auto'" + }, + "upstream_quota_threshold_percent": { + "name": "upstream_quota_threshold_percent", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "upstream_quota_probe_options": { + "name": "upstream_quota_probe_options", + "type": "jsonb", + "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_image_generation_preference": { + "name": "codex_image_generation_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" + }, + "reasoning_effort_override_rules": { + "name": "reasoning_effort_override_rules", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "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.replay_payloads": { + "name": "replay_payloads", + "schema": "", + "columns": { + "replay_id": { + "name": "replay_id", + "type": "varchar(64)", + "primaryKey": true, + "notNull": true + }, + "verifier": { + "name": "verifier", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "scope_tag": { + "name": "scope_tag", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "key_id": { + "name": "key_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "format": { + "name": "format", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "status_code": { + "name": "status_code", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "headers_json": { + "name": "headers_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "byte_size": { + "name": "byte_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "source_message_request_id": { + "name": "source_message_request_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_replay_payloads_key_id": { + "name": "idx_replay_payloads_key_id", + "columns": [ + { + "expression": "key_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_replay_payloads_expires_at": { + "name": "idx_replay_payloads_expires_at", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "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": "'CC 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 + }, + "bill_hedge_losers": { + "name": "bill_hedge_losers", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "legacy_hedge_max_in_flight": { + "name": "legacy_hedge_max_in_flight", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 2 + }, + "discovery_enabled": { + "name": "discovery_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "discovery_concurrency": { + "name": "discovery_concurrency", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 2 + }, + "max_discovery_rounds": { + "name": "max_discovery_rounds", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 2 + }, + "discovery_sla_ms": { + "name": "discovery_sla_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 10000 + }, + "sticky_sla_ms": { + "name": "sticky_sla_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 20000 + }, + "racing_total_timeout_ms": { + "name": "racing_total_timeout_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 60000 + }, + "sticky_timeout_cooldown_ms": { + "name": "sticky_timeout_cooldown_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 300000 + }, + "upstream_quota_scheduling_enabled": { + "name": "upstream_quota_scheduling_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "upstream_quota_threshold_percent": { + "name": "upstream_quota_threshold_percent", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 10 + }, + "upstream_quota_probe_interval_minutes": { + "name": "upstream_quota_probe_interval_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 10 + }, + "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_thinking_effort_conflict_rectifier": { + "name": "enable_thinking_effort_conflict_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_gemini_function_id_rectifier": { + "name": "enable_gemini_function_id_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 + }, + "stream_gate_mode": { + "name": "stream_gate_mode", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "default": "'enforce'" + }, + "affinity_ignore_client_session_id": { + "name": "affinity_ignore_client_session_id", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "edge_execution_enabled": { + "name": "edge_execution_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "replay_enabled": { + "name": "replay_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "replay_cache_ttl_minutes": { + "name": "replay_cache_ttl_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 30 + }, + "cache_effectiveness_enabled": { + "name": "cache_effectiveness_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "enable_memory_admission": { + "name": "enable_memory_admission", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "agent_catalog_notes": { + "name": "agent_catalog_notes", + "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": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "system_settings_legacy_hedge_max_in_flight_range": { + "name": "system_settings_legacy_hedge_max_in_flight_range", + "value": "\"system_settings\".\"legacy_hedge_max_in_flight\" >= 1 AND \"system_settings\".\"legacy_hedge_max_in_flight\" <= 4" + } + }, + "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 + }, + "session_identity": { + "name": "session_identity", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "session_identity_kind": { + "name": "session_identity_kind", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "affinity_scope_tag": { + "name": "affinity_scope_tag", + "type": "varchar(16)", + "primaryKey": false, + "notNull": false + }, + "affinity_fingerprint": { + "name": "affinity_fingerprint", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "affinity_fingerprint_chain": { + "name": "affinity_fingerprint_chain", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "is_replay": { + "name": "is_replay", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "replay_source_request_id": { + "name": "replay_source_request_id", + "type": "integer", + "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 + }, + "first_byte_ms": { + "name": "first_byte_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 AND \"usage_ledger\".\"is_replay\" = false", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_user_id_reset": { + "name": "idx_usage_ledger_user_id_reset", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "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 AND \"usage_ledger\".\"is_replay\" = false", + "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 AND \"usage_ledger\".\"is_replay\" = false", + "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_session_identity_created_at": { + "name": "idx_usage_ledger_session_identity_created_at", + "columns": [ + { + "expression": "COALESCE(\"session_identity\", \"session_id\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"created_at\" DESC NULLS LAST", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL AND \"usage_ledger\".\"is_replay\" = false", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_session_identity": { + "name": "idx_usage_ledger_session_identity", + "columns": [ + { + "expression": "COALESCE(\"session_identity\", \"session_id\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_session_identity_prefix": { + "name": "idx_usage_ledger_session_identity_prefix", + "columns": [ + { + "expression": "COALESCE(\"session_identity\", \"session_id\") varchar_pattern_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_session_id_prefix": { + "name": "idx_usage_ledger_session_id_prefix", + "columns": [ + { + "expression": "\"session_id\" varchar_pattern_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS 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 AND \"usage_ledger\".\"is_replay\" = false", + "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 AND \"usage_ledger\".\"is_replay\" = false", + "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 AND \"usage_ledger\".\"is_replay\" = false", + "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 AND \"usage_ledger\".\"is_replay\" = false", + "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", + "client_problem" + ] + }, + "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/0134_snapshot.json b/drizzle/meta/0134_snapshot.json new file mode 100644 index 0000000000..2c585950aa --- /dev/null +++ b/drizzle/meta/0134_snapshot.json @@ -0,0 +1,6192 @@ +{ + "id": "a0bfe450-4e90-4e00-861e-8e22635504fc", + "prevId": "64cd2f2d-d302-49fc-a30b-7db0a5ccfdc3", + "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.avail_bucket_1m": { + "name": "avail_bucket_1m", + "schema": "", + "columns": { + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "bucket_start": { + "name": "bucket_start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "success_cnt": { + "name": "success_cnt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "failure_cnt": { + "name": "failure_cnt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "excluded_cnt": { + "name": "excluded_cnt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "latency_cnt": { + "name": "latency_cnt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "latency_sum_ms": { + "name": "latency_sum_ms", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_request_at": { + "name": "last_request_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_avail_bucket_1m_time": { + "name": "idx_avail_bucket_1m_time", + "columns": [ + { + "expression": "bucket_start", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "avail_bucket_1m_pkey": { + "name": "avail_bucket_1m_pkey", + "columns": [ + "provider_id", + "bucket_start" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.avail_current": { + "name": "avail_current", + "schema": "", + "columns": { + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": true, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "availability": { + "name": "availability", + "type": "double precision", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "request_count": { + "name": "request_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_request_at": { + "name": "last_request_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.clickhouse_sync_state": { + "name": "clickhouse_sync_state", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "varchar(32)", + "primaryKey": true, + "notNull": true, + "default": "'default'" + }, + "floor_at": { + "name": "floor_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cloud_pricing_catalog": { + "name": "cloud_pricing_catalog", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "version": { + "name": "version", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "currency": { + "name": "currency", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true, + "default": "'USD'" + }, + "refreshed_at": { + "name": "refreshed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "providers": { + "name": "providers", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "vendors": { + "name": "vendors", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "model_count": { + "name": "model_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "synced_at": { + "name": "synced_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": {}, + "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 + }, + "session_identity": { + "name": "session_identity", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "session_identity_kind": { + "name": "session_identity_kind", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "affinity_scope_tag": { + "name": "affinity_scope_tag", + "type": "varchar(16)", + "primaryKey": false, + "notNull": false + }, + "affinity_fingerprint": { + "name": "affinity_fingerprint", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "affinity_fingerprint_chain": { + "name": "affinity_fingerprint_chain", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "is_replay": { + "name": "is_replay", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "replay_source_request_id": { + "name": "replay_source_request_id", + "type": "integer", + "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 + }, + "routing_trace": { + "name": "routing_trace", + "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 + }, + "first_byte_ms": { + "name": "first_byte_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 + }, + "hedge_losers": { + "name": "hedge_losers", + "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 + }, + "cache_compatibility_key": { + "name": "cache_compatibility_key", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "cache_score_eligible": { + "name": "cache_score_eligible", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "cache_score_excluded_reason": { + "name": "cache_score_excluded_reason", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false + }, + "theoretical_cache_tokens": { + "name": "theoretical_cache_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "cache_ttl_bucket": { + "name": "cache_ttl_bucket", + "type": "varchar(10)", + "primaryKey": false, + "notNull": false + }, + "clickhouse_synced_at": { + "name": "clickhouse_synced_at", + "type": "timestamp with time zone", + "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_proxy_status_active": { + "name": "idx_message_request_proxy_status_active", + "columns": [ + { + "expression": "\"created_at\" DESC NULLS LAST", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"is_replay\" = false AND \"message_request\".\"status_code\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_proxy_status_latest": { + "name": "idx_message_request_proxy_status_latest", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"updated_at\" DESC NULLS LAST", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"is_replay\" = false AND \"message_request\".\"status_code\" IS NOT NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "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_session_identity_created_at": { + "name": "idx_message_request_session_identity_created_at", + "columns": [ + { + "expression": "COALESCE(\"session_identity\", \"session_id\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "\"created_at\" DESC NULLS LAST", + "asc": true, + "isExpression": true, + "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_session_identity_prefix": { + "name": "idx_message_request_session_identity_prefix", + "columns": [ + { + "expression": "COALESCE(\"session_identity\", \"session_id\") varchar_pattern_ops", + "asc": true, + "isExpression": 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 AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_message_request_session_id_prefix_cover": { + "name": "idx_message_request_session_id_prefix_cover", + "columns": [ + { + "expression": "\"session_id\" varchar_pattern_ops", + "asc": true, + "isExpression": 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 AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "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": {} + }, + "idx_message_request_clickhouse_unsynced": { + "name": "idx_message_request_clickhouse_unsynced", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"message_request\".\"deleted_at\" IS NULL AND \"message_request\".\"clickhouse_synced_at\" IS NULL AND (\"message_request\".\"blocked_by\" IS NULL OR \"message_request\".\"blocked_by\" <> 'warmup')", + "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": "'cloud'" + }, + "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_model_name_lower": { + "name": "idx_model_prices_model_name_lower", + "columns": [ + { + "expression": "lower(\"model_name\")", + "asc": true, + "isExpression": 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": {} + }, + "idx_model_prices_vendor": { + "name": "idx_model_prices_vendor", + "columns": [ + { + "expression": "((\"price_data\" ->> 'vendor'))", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_model_prices_aliases": { + "name": "idx_model_prices_aliases", + "columns": [ + { + "expression": "((\"price_data\" -> 'aliases'))", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "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 + }, + "title_prefix": { + "name": "title_prefix", + "type": "varchar(64)", + "primaryKey": false, + "notNull": 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 + }, + "client_problem_enabled": { + "name": "client_problem_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "client_problem_webhook": { + "name": "client_problem_webhook", + "type": "varchar(512)", + "primaryKey": false, + "notNull": false + }, + "client_problem_count_threshold": { + "name": "client_problem_count_threshold", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 10 + }, + "client_problem_window_minutes": { + "name": "client_problem_window_minutes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 5 + }, + "client_problem_cyber_count_threshold": { + "name": "client_problem_cyber_count_threshold", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 3 + }, + "client_problem_cyber_window_minutes": { + "name": "client_problem_cyber_window_minutes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "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.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.outbox_events": { + "name": "outbox_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "event_id": { + "name": "event_id", + "type": "uuid", + "primaryKey": false, + "notNull": true, + "default": "gen_random_uuid()" + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "aggregate_type": { + "name": "aggregate_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "aggregate_id": { + "name": "aggregate_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "published_at": { + "name": "published_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_outbox_events_unpublished": { + "name": "idx_outbox_events_unpublished", + "columns": [ + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"outbox_events\".\"published_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "outbox_events_event_id_key": { + "name": "outbox_events_event_id_key", + "nullsNotDistinct": false, + "columns": [ + "event_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.outbox_processed": { + "name": "outbox_processed", + "schema": "", + "columns": { + "event_id": { + "name": "event_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "processed_at": { + "name": "processed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.proj_applied_requests": { + "name": "proj_applied_requests", + "schema": "", + "columns": { + "request_id": { + "name": "request_id", + "type": "bigint", + "primaryKey": true, + "notNull": true + }, + "event_id": { + "name": "event_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "applied_at": { + "name": "applied_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.projection_meta": { + "name": "projection_meta", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "value": { + "name": "value", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_batch_apply_operations": { + "name": "provider_batch_apply_operations", + "schema": "", + "columns": { + "claim_key": { + "name": "claim_key", + "type": "varchar(256)", + "primaryKey": true, + "notNull": true + }, + "preview_token": { + "name": "preview_token", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "payload_fingerprint": { + "name": "payload_fingerprint", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "operation_id": { + "name": "operation_id", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "undo_token": { + "name": "undo_token", + "type": "varchar(256)", + "primaryKey": false, + "notNull": true + }, + "undo_expires_at": { + "name": "undo_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "undo_consumed_at": { + "name": "undo_consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "result": { + "name": "result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "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": { + "uniq_provider_batch_apply_operations_preview_token": { + "name": "uniq_provider_batch_apply_operations_preview_token", + "columns": [ + { + "expression": "preview_token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "uniq_provider_batch_apply_operations_operation_id": { + "name": "uniq_provider_batch_apply_operations_operation_id", + "columns": [ + { + "expression": "operation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "uniq_provider_batch_apply_operations_undo_token": { + "name": "uniq_provider_batch_apply_operations_undo_token", + "columns": [ + { + "expression": "undo_token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_provider_batch_apply_operations_expires_at": { + "name": "idx_provider_batch_apply_operations_expires_at", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.provider_cache_effectiveness": { + "name": "provider_cache_effectiveness", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": true + }, + "cache_ttl_bucket": { + "name": "cache_ttl_bucket", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true + }, + "window_start": { + "name": "window_start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "window_end": { + "name": "window_end", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "sample_count": { + "name": "sample_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "eligible_count": { + "name": "eligible_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "theoretical_cache_tokens": { + "name": "theoretical_cache_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "observed_cache_read_tokens": { + "name": "observed_cache_read_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "raw_effectiveness_bp": { + "name": "raw_effectiveness_bp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "confidence_bp": { + "name": "confidence_bp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "effectiveness_bp": { + "name": "effectiveness_bp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + } + }, + "indexes": { + "idx_provider_cache_effectiveness_window": { + "name": "idx_provider_cache_effectiveness_window", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "model", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_start", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "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_upstream_quota_snapshots": { + "name": "provider_upstream_quota_snapshots", + "schema": "", + "columns": { + "provider_id": { + "name": "provider_id", + "type": "integer", + "primaryKey": true, + "notNull": true + }, + "probe_type": { + "name": "probe_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "windows": { + "name": "windows", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "plan_level": { + "name": "plan_level", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "credential_valid": { + "name": "credential_valid", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_error_status": { + "name": "last_error_status", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "fetched_at": { + "name": "fetched_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "probed_at": { + "name": "probed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "reactive_pause_until": { + "name": "reactive_pause_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "reactive_pause_reason": { + "name": "reactive_pause_reason", + "type": "varchar(32)", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "provider_upstream_quota_snapshots_provider_id_providers_id_fk": { + "name": "provider_upstream_quota_snapshots_provider_id_providers_id_fk", + "tableFrom": "provider_upstream_quota_snapshots", + "tableTo": "providers", + "columnsFrom": [ + "provider_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "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 + }, + "new_api_access_token": { + "name": "new_api_access_token", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "new_api_user_id": { + "name": "new_api_user_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "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 + }, + "overwrite_response_model": { + "name": "overwrite_response_model", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "provider_prefix": { + "name": "provider_prefix", + "type": "varchar(64)", + "primaryKey": false, + "notNull": 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 + }, + "upstream_quota_probe_type": { + "name": "upstream_quota_probe_type", + "type": "varchar(20)", + "primaryKey": false, + "notNull": true, + "default": "'auto'" + }, + "upstream_quota_threshold_percent": { + "name": "upstream_quota_threshold_percent", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "upstream_quota_probe_options": { + "name": "upstream_quota_probe_options", + "type": "jsonb", + "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_image_generation_preference": { + "name": "codex_image_generation_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" + }, + "reasoning_effort_override_rules": { + "name": "reasoning_effort_override_rules", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "service_tier_override_rules": { + "name": "service_tier_override_rules", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "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.replay_payloads": { + "name": "replay_payloads", + "schema": "", + "columns": { + "replay_id": { + "name": "replay_id", + "type": "varchar(64)", + "primaryKey": true, + "notNull": true + }, + "verifier": { + "name": "verifier", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "scope_tag": { + "name": "scope_tag", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "key_id": { + "name": "key_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "format": { + "name": "format", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "status_code": { + "name": "status_code", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "headers_json": { + "name": "headers_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "byte_size": { + "name": "byte_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "source_message_request_id": { + "name": "source_message_request_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_replay_payloads_key_id": { + "name": "idx_replay_payloads_key_id", + "columns": [ + { + "expression": "key_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_replay_payloads_expires_at": { + "name": "idx_replay_payloads_expires_at", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "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": "'CC 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 + }, + "bill_hedge_losers": { + "name": "bill_hedge_losers", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "legacy_hedge_max_in_flight": { + "name": "legacy_hedge_max_in_flight", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 2 + }, + "discovery_enabled": { + "name": "discovery_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "discovery_concurrency": { + "name": "discovery_concurrency", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 2 + }, + "max_discovery_rounds": { + "name": "max_discovery_rounds", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 2 + }, + "discovery_sla_ms": { + "name": "discovery_sla_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 10000 + }, + "sticky_sla_ms": { + "name": "sticky_sla_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 20000 + }, + "racing_total_timeout_ms": { + "name": "racing_total_timeout_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 60000 + }, + "sticky_timeout_cooldown_ms": { + "name": "sticky_timeout_cooldown_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 300000 + }, + "upstream_quota_scheduling_enabled": { + "name": "upstream_quota_scheduling_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "upstream_quota_threshold_percent": { + "name": "upstream_quota_threshold_percent", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 10 + }, + "upstream_quota_probe_interval_minutes": { + "name": "upstream_quota_probe_interval_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 10 + }, + "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_thinking_effort_conflict_rectifier": { + "name": "enable_thinking_effort_conflict_rectifier", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "enable_gemini_function_id_rectifier": { + "name": "enable_gemini_function_id_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 + }, + "stream_gate_mode": { + "name": "stream_gate_mode", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "default": "'enforce'" + }, + "affinity_ignore_client_session_id": { + "name": "affinity_ignore_client_session_id", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "edge_execution_enabled": { + "name": "edge_execution_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "replay_enabled": { + "name": "replay_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "replay_cache_ttl_minutes": { + "name": "replay_cache_ttl_minutes", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 30 + }, + "cache_effectiveness_enabled": { + "name": "cache_effectiveness_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "enable_memory_admission": { + "name": "enable_memory_admission", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "agent_catalog_notes": { + "name": "agent_catalog_notes", + "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": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "system_settings_legacy_hedge_max_in_flight_range": { + "name": "system_settings_legacy_hedge_max_in_flight_range", + "value": "\"system_settings\".\"legacy_hedge_max_in_flight\" >= 1 AND \"system_settings\".\"legacy_hedge_max_in_flight\" <= 4" + } + }, + "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 + }, + "session_identity": { + "name": "session_identity", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "session_identity_kind": { + "name": "session_identity_kind", + "type": "varchar(20)", + "primaryKey": false, + "notNull": false + }, + "affinity_scope_tag": { + "name": "affinity_scope_tag", + "type": "varchar(16)", + "primaryKey": false, + "notNull": false + }, + "affinity_fingerprint": { + "name": "affinity_fingerprint", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "affinity_fingerprint_chain": { + "name": "affinity_fingerprint_chain", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "is_replay": { + "name": "is_replay", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "replay_source_request_id": { + "name": "replay_source_request_id", + "type": "integer", + "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 + }, + "first_byte_ms": { + "name": "first_byte_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 AND \"usage_ledger\".\"is_replay\" = false", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_user_id_reset": { + "name": "idx_usage_ledger_user_id_reset", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "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 AND \"usage_ledger\".\"is_replay\" = false", + "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 AND \"usage_ledger\".\"is_replay\" = false", + "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_session_identity_created_at": { + "name": "idx_usage_ledger_session_identity_created_at", + "columns": [ + { + "expression": "COALESCE(\"session_identity\", \"session_id\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "\"created_at\" DESC NULLS LAST", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL AND \"usage_ledger\".\"is_replay\" = false", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_session_identity": { + "name": "idx_usage_ledger_session_identity", + "columns": [ + { + "expression": "COALESCE(\"session_identity\", \"session_id\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_session_identity_prefix": { + "name": "idx_usage_ledger_session_identity_prefix", + "columns": [ + { + "expression": "COALESCE(\"session_identity\", \"session_id\") varchar_pattern_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_usage_ledger_session_id_prefix": { + "name": "idx_usage_ledger_session_id_prefix", + "columns": [ + { + "expression": "\"session_id\" varchar_pattern_ops", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"usage_ledger\".\"blocked_by\" IS 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 AND \"usage_ledger\".\"is_replay\" = false", + "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 AND \"usage_ledger\".\"is_replay\" = false", + "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 AND \"usage_ledger\".\"is_replay\" = false", + "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 AND \"usage_ledger\".\"is_replay\" = false", + "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", + "client_problem" + ] + }, + "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 d87039e76e..d01eee49ea 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -838,43 +838,113 @@ { "idx": 119, "version": "7", + "when": 1785770702012, + "tag": "0119_common_madame_masque", + "breakpoints": true + }, + { + "idx": 120, + "version": "7", "when": 1786038550610, "tag": "0119_tiresome_banshee", "breakpoints": true }, { - "idx": 120, + "idx": 121, "version": "7", "when": 1786500000000, "tag": "0120_availability_projection", "breakpoints": true }, { - "idx": 121, + "idx": 122, "version": "7", "when": 1788182990280, "tag": "0121_legacy_hedge_abort_health", "breakpoints": true }, { - "idx": 122, + "idx": 123, "version": "7", - "when": 1788950528054, - "tag": "0122_session_identity_prefix_indexes", + "when": 1789000000000, + "tag": "0122_overwrite_response_model", "breakpoints": true }, { - "idx": 123, + "idx": 124, "version": "7", - "when": 1790352239389, - "tag": "0123_provider_new_api_access_token", + "when": 1789100000000, + "tag": "0124_cool_the_call", "breakpoints": true }, { - "idx": 124, + "idx": 125, + "version": "7", + "when": 1789394771027, + "tag": "0125_ledger_terminal_projection", + "breakpoints": true + }, + { + "idx": 126, + "version": "7", + "when": 1789500000000, + "tag": "0126_session_identity_prefix_indexes", + "breakpoints": true + }, + { + "idx": 127, + "version": "7", + "when": 1790095569876, + "tag": "0127_provider_prefix", + "breakpoints": true + }, + { + "idx": 128, + "version": "7", + "when": 1790438793913, + "tag": "0128_clickhouse_sync_marker", + "breakpoints": true + }, + { + "idx": 129, + "version": "7", + "when": 1790508624502, + "tag": "0129_upstream_quota_scheduling", + "breakpoints": true + }, + { + "idx": 130, + "version": "7", + "when": 1790521138887, + "tag": "0130_chunky_patch", + "breakpoints": true + }, + { + "idx": 131, + "version": "7", + "when": 1790963726152, + "tag": "0131_talented_adam_warlock", + "breakpoints": true + }, + { + "idx": 132, + "version": "7", + "when": 1791395724171, + "tag": "0132_aspiring_hellcat", + "breakpoints": true + }, + { + "idx": 133, + "version": "7", + "when": 1791397453243, + "tag": "0133_slow_winter_soldier", + "breakpoints": true + }, + { + "idx": 134, "version": "7", - "when": 1790674741240, - "tag": "0124_system_settings_memory_admission", + "when": 1791519045175, + "tag": "0134_clammy_sir_ram", "breakpoints": true } ] diff --git a/edge/Dockerfile b/edge/Dockerfile new file mode 100644 index 0000000000..8cd7946c15 --- /dev/null +++ b/edge/Dockerfile @@ -0,0 +1,15 @@ +# Build context: the edge/ directory (see Makefile `docker` target). +FROM golang:1.25-alpine AS build +WORKDIR /src +COPY go.mod go.sum ./ +RUN go mod download +COPY . . +ARG VERSION=dev +RUN CGO_ENABLED=0 go build -trimpath -ldflags "-s -w -X main.version=${VERSION}" -o /out/edge ./cmd/edge + +FROM gcr.io/distroless/static-debian12:nonroot +COPY --from=build /out/edge /edge +VOLUME ["/var/lib/cch-edge/outbox"] +EXPOSE 13580 +USER nonroot:nonroot +ENTRYPOINT ["/edge"] diff --git a/edge/Makefile b/edge/Makefile new file mode 100644 index 0000000000..c0295f5148 --- /dev/null +++ b/edge/Makefile @@ -0,0 +1,24 @@ +# CCH edge executor +VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo dev) +BIN := bin/edge + +.PHONY: build test lint fmt docker run + +build: + CGO_ENABLED=0 go build -trimpath -ldflags "-s -w -X main.version=$(VERSION)" -o $(BIN) ./cmd/edge + +test: + go test -race -count=1 ./... + +lint: + go vet ./... + @test -z "$$(gofmt -l .)" || (echo "gofmt needed:"; gofmt -l .; exit 1) + +fmt: + gofmt -w . + +docker: + docker build -f Dockerfile --build-arg VERSION=$(VERSION) -t cch-edge:$(VERSION) . + +run: build + ./$(BIN) diff --git a/edge/cmd/edge/main.go b/edge/cmd/edge/main.go new file mode 100644 index 0000000000..ed969c039a --- /dev/null +++ b/edge/cmd/edge/main.go @@ -0,0 +1,129 @@ +// Command edge runs the CCH edge executor: it fronts client traffic, executes +// eligible Anthropic /v1/messages upstream calls under the control plane's +// direction and reverse-proxies everything else to the control plane. +package main + +import ( + "context" + "errors" + "log/slog" + "net/http" + "os" + "os/signal" + "strings" + "syscall" + "time" + + "github.com/ding113/claude-code-hub/edge/internal/config" + "github.com/ding113/claude-code-hub/edge/internal/control" + "github.com/ding113/claude-code-hub/edge/internal/delegate" + "github.com/ding113/claude-code-hub/edge/internal/gate" + "github.com/ding113/claude-code-hub/edge/internal/report" + "github.com/ding113/claude-code-hub/edge/internal/server" + "github.com/ding113/claude-code-hub/edge/internal/upstream" +) + +var version = "dev" + +func newLogger(level string) *slog.Logger { + var slogLevel slog.Level + switch strings.ToLower(level) { + case "debug": + slogLevel = slog.LevelDebug + case "warn": + slogLevel = slog.LevelWarn + case "error": + slogLevel = slog.LevelError + default: + slogLevel = slog.LevelInfo + } + return slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slogLevel})) +} + +func main() { + cfg, err := config.Load() + if err != nil { + slog.Error("invalid configuration", "error", err) + os.Exit(2) + } + logger := newLogger(cfg.LogLevel).With("edgeId", cfg.EdgeID) + + ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) + defer stop() + backgroundCtx, cancelBackground := context.WithCancel(context.Background()) + defer cancelBackground() + + client := control.New(control.Options{ + BaseURL: cfg.ControlURL, + Secret: cfg.SharedSecret, + EdgeID: cfg.EdgeID, + DecideTimeout: cfg.DecideTimeout, + NextTimeout: cfg.NextTimeout, + CompleteTimeout: cfg.CompleteTimeout, + HeartbeatTimeout: cfg.HeartbeatTimeout, + }) + outbox, err := report.NewOutbox(cfg.OutboxDir, cfg.OutboxMaxBytes) + if err != nil { + logger.Error("outbox unavailable, completion reports will not be spooled", "dir", cfg.OutboxDir, "error", err) + outbox = nil + } + reporter := report.NewReporter(client, outbox, logger) + + handler := server.NewHandler(server.Deps{ + Config: cfg, + Control: client, + Reporter: reporter, + Delegator: delegate.New(cfg.DelegateURL, logger), + Upstream: upstream.NewClient(), + Budget: gate.NewBudget(cfg.GatePrebufferBytes), + Logger: logger, + BaseCtx: backgroundCtx, + }) + + outboxBytes := func() int64 { return 0 } + if outbox != nil { + outboxBytes = outbox.PendingBytes + } + probe := server.NewReadinessProbe(outboxBytes, cfg.OutboxMaxBytes/2) + go server.RunControlProbe(backgroundCtx, probe, client.Health, 15*time.Second) + go reporter.RunOutboxDrainer(backgroundCtx, 5*time.Second) + + httpServer := &http.Server{ + Addr: cfg.ListenAddr, + Handler: server.Mux(handler, probe), + ReadHeaderTimeout: 30 * time.Second, + IdleTimeout: 120 * time.Second, + } + + serveErr := make(chan error, 1) + go func() { + logger.Info("edge executor listening", "addr", cfg.ListenAddr, "version", version, + "control", cfg.ControlURL.Redacted(), "delegate", cfg.DelegateURL.Redacted()) + serveErr <- httpServer.ListenAndServe() + }() + + select { + case err := <-serveErr: + if err != nil && !errors.Is(err, http.ErrServerClosed) { + logger.Error("server failed", "error", err) + os.Exit(1) + } + case <-ctx.Done(): + } + + logger.Info("shutting down: draining in-flight requests", "grace", cfg.ShutdownGrace.String()) + probe.MarkDraining() + shutdownCtx, cancel := context.WithTimeout(context.Background(), cfg.ShutdownGrace) + defer cancel() + if err := httpServer.Shutdown(shutdownCtx); err != nil { + logger.Warn("graceful shutdown incomplete", "error", err) + } + reporter.Wait() + drainCtx, cancelDrain := context.WithTimeout(context.Background(), 15*time.Second) + defer cancelDrain() + if delivered, err := reporter.DrainOutbox(drainCtx); delivered > 0 || err != nil { + logger.Info("final outbox drain", "delivered", delivered, "error", err) + } + cancelBackground() + logger.Info("edge executor stopped") +} diff --git a/edge/go.mod b/edge/go.mod new file mode 100644 index 0000000000..b9cdc26a5d --- /dev/null +++ b/edge/go.mod @@ -0,0 +1,8 @@ +module github.com/ding113/claude-code-hub/edge + +go 1.25 + +require ( + github.com/andybalholm/brotli v1.2.5 + github.com/klauspost/compress v1.20.1 +) diff --git a/edge/go.sum b/edge/go.sum new file mode 100644 index 0000000000..f9a39a475f --- /dev/null +++ b/edge/go.sum @@ -0,0 +1,6 @@ +github.com/andybalholm/brotli v1.2.5 h1:BSI8V4zmx/3BAn6OKjF1PmfVq7Aoi52AdFsi6bpCx+s= +github.com/andybalholm/brotli v1.2.5/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= +github.com/klauspost/compress v1.20.1 h1:T7kKElXUMXrUJ2E9QhQhxFtcK5rPyLdsGZvdbLMPdiQ= +github.com/klauspost/compress v1.20.1/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI= +github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= +github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= diff --git a/edge/internal/bodyops/bodyops.go b/edge/internal/bodyops/bodyops.go new file mode 100644 index 0000000000..ea39516ed0 --- /dev/null +++ b/edge/internal/bodyops/bodyops.go @@ -0,0 +1,385 @@ +// Package bodyops applies contract.BodyOp mutations to a parsed request +// body, mirroring src/app/v1/_lib/edge/body-ops.ts applyBodyOps and the +// underlying rectifiers it calls (billing-header-rectifier.ts, +// forwarder.ts#applyCacheTtlOverrideToMessage/filterPrivateParameters, +// thinking-signature-rectifier.ts#rectifyAnthropicRequestMessage). +package bodyops + +import ( + "fmt" + "regexp" + "strings" + + "github.com/ding113/claude-code-hub/edge/internal/contract" + "github.com/ding113/claude-code-hub/edge/internal/ojson" +) + +// Apply clones body, applies ops in order and returns the resulting body plus +// the accumulated OpResults. body is never mutated. +func Apply(body *ojson.Value, ops []contract.BodyOp) (*ojson.Value, contract.OpResults, error) { + message := body.Clone() + if message == nil { + message = ojson.NewObject() + } + var results contract.OpResults + + for _, op := range ops { + switch op.Op { + case contract.OpSetTopLevel: + if !contract.MutableTopLevelKeys[op.Key] { + return nil, contract.OpResults{}, fmt.Errorf("bodyops: key %q not mutable for set_top_level", op.Key) + } + val, err := ojson.Parse(op.Value, ojson.DefaultMaxDepth) + if err != nil { + return nil, contract.OpResults{}, fmt.Errorf("bodyops: invalid value for set_top_level: %w", err) + } + message.ObjectSet(op.Key, val) + case contract.OpDeleteTopLevel: + if !contract.MutableTopLevelKeys[op.Key] { + return nil, contract.OpResults{}, fmt.Errorf("bodyops: key %q not mutable for delete_top_level", op.Key) + } + message.ObjectDelete(op.Key) + case contract.OpRemoveSystemBillingHeader: + result := rectifyBillingHeader(message) + if result.applied { + if results.BillingHeader == nil { + results.BillingHeader = &contract.BillingHeaderResult{} + } + results.BillingHeader.RemovedCount += result.removedCount + results.BillingHeader.ExtractedValues = append(results.BillingHeader.ExtractedValues, result.extractedValues...) + } + case contract.OpSetCacheControlTTL: + if op.TTL != "5m" && op.TTL != "1h" { + return nil, contract.OpResults{}, fmt.Errorf("bodyops: invalid ttl %q", op.TTL) + } + applyCacheTtlOverrideToMessage(message, op.TTL) + case contract.OpApplyThinkingSignatureRectifier: + hadThinking := message.ObjectHas("thinking") + result := rectifyAnthropicRequestMessage(message) + results.ThinkingSignature = &contract.ThinkingSignatureResult{ + Applied: result.applied, + RemovedThinkingBlocks: result.removedThinkingBlocks, + RemovedRedactedThinkingBlocks: result.removedRedactedThinkingBlocks, + RemovedSignatureFields: result.removedSignatureFields, + RemovedTopLevelThinking: hadThinking && !message.ObjectHas("thinking"), + } + case contract.OpStripPrivateParams: + message = filterPrivateParameters(message) + case contract.OpNormalizeResponseInput: + rectifyResponseInput(message) + default: + return nil, contract.OpResults{}, fmt.Errorf("bodyops: unknown op %q", op.Op) + } + } + + return message, results, nil +} + +// ---- billing header rectifier (billing-header-rectifier.ts) ---- + +var billingHeaderPattern = regexp.MustCompile(`(?i)^\s*x-anthropic-billing-header\s*:`) + +type billingHeaderResult struct { + applied bool + removedCount int + extractedValues []string +} + +func rectifyBillingHeader(message *ojson.Value) billingHeaderResult { + system, ok := message.ObjectGet("system") + if !ok || system.IsNull() { + return billingHeaderResult{} + } + + if system.IsString() { + if billingHeaderPattern.MatchString(system.String()) { + message.ObjectDelete("system") + return billingHeaderResult{applied: true, removedCount: 1, extractedValues: []string{strings.TrimSpace(system.String())}} + } + return billingHeaderResult{} + } + + if system.IsArray() { + var extracted []string + filtered := ojson.NewArray() + for _, block := range system.ArrayItems() { + if block.IsObject() { + typ, _ := block.ObjectGet("type") + textVal, hasText := block.ObjectGet("text") + if typ.IsString() && typ.String() == "text" && hasText && textVal.IsString() && + billingHeaderPattern.MatchString(textVal.String()) { + extracted = append(extracted, strings.TrimSpace(textVal.String())) + continue + } + } + filtered.ArrayAppend(block) + } + if len(extracted) > 0 { + message.ObjectSet("system", filtered) + return billingHeaderResult{applied: true, removedCount: len(extracted), extractedValues: extracted} + } + return billingHeaderResult{} + } + + return billingHeaderResult{} +} + +// ---- cache ttl override (forwarder.ts#applyCacheTtlOverrideToMessage) ---- + +func applyTtlToContentBlocks(blocks *ojson.Value, ttl string) bool { + applied := false + for _, item := range blocks.ArrayItems() { + if !item.IsObject() { + continue + } + cc, ok := item.ObjectGet("cache_control") + if !ok || !cc.IsObject() { + continue + } + typ, ok := cc.ObjectGet("type") + if !ok || !typ.IsString() || typ.String() != "ephemeral" { + continue + } + applied = true + ttlVal := "5m" + if ttl == "1h" { + ttlVal = "1h" + } + cc.ObjectSet("ttl", ojson.NewString(ttlVal)) + } + return applied +} + +func applyCacheTtlOverrideToMessage(message *ojson.Value, ttl string) bool { + applied := false + + if system, ok := message.ObjectGet("system"); ok && system.IsArray() { + if applyTtlToContentBlocks(system, ttl) { + applied = true + } + } + + if messages, ok := message.ObjectGet("messages"); ok && messages.IsArray() { + for _, msg := range messages.ArrayItems() { + if !msg.IsObject() { + continue + } + content, ok := msg.ObjectGet("content") + if !ok || !content.IsArray() { + continue + } + if applyTtlToContentBlocks(content, ttl) { + applied = true + } + } + } + + return applied +} + +// ---- thinking signature rectifier (thinking-signature-rectifier.ts) ---- + +type thinkingRectifierResult struct { + applied bool + removedThinkingBlocks int + removedRedactedThinkingBlocks int + removedSignatureFields int +} + +func rectifyAnthropicRequestMessage(message *ojson.Value) thinkingRectifierResult { + var result thinkingRectifierResult + + messages, ok := message.ObjectGet("messages") + if !ok || !messages.IsArray() { + return result + } + + for _, msg := range messages.ArrayItems() { + if !msg.IsObject() { + continue + } + content, ok := msg.ObjectGet("content") + if !ok || !content.IsArray() { + continue + } + + newContent := ojson.NewArray() + contentWasModified := false + + for _, block := range content.ArrayItems() { + if !block.IsObject() { + newContent.ArrayAppend(block) + continue + } + typ, _ := block.ObjectGet("type") + typStr := "" + if typ.IsString() { + typStr = typ.String() + } + + if typStr == "thinking" { + result.removedThinkingBlocks++ + contentWasModified = true + continue + } + if typStr == "redacted_thinking" { + result.removedRedactedThinkingBlocks++ + contentWasModified = true + continue + } + if block.ObjectHas("signature") { + rest := ojson.NewObject() + for _, k := range block.ObjectKeys() { + if k == "signature" { + continue + } + v, _ := block.ObjectGet(k) + rest.ObjectSet(k, v) + } + result.removedSignatureFields++ + contentWasModified = true + newContent.ArrayAppend(rest) + continue + } + newContent.ArrayAppend(block) + } + + if contentWasModified { + result.applied = true + msg.ObjectSet("content", newContent) + } + } + + thinking, hasThinking := message.ObjectGet("thinking") + thinkingEnabled := false + if hasThinking && thinking.IsObject() { + if t, ok := thinking.ObjectGet("type"); ok && t.IsString() && t.String() == "enabled" { + thinkingEnabled = true + } + } + + if thinkingEnabled { + var lastAssistantContent *ojson.Value + items := messages.ArrayItems() + for i := len(items) - 1; i >= 0; i-- { + msg := items[i] + if !msg.IsObject() { + continue + } + role, ok := msg.ObjectGet("role") + if !ok || !role.IsString() || role.String() != "assistant" { + continue + } + content, ok := msg.ObjectGet("content") + if !ok || !content.IsArray() { + continue + } + lastAssistantContent = content + break + } + + if lastAssistantContent != nil && lastAssistantContent.ArrayLen() > 0 { + firstBlock := lastAssistantContent.ArrayGet(0) + firstBlockType := "" + if firstBlock.IsObject() { + if t, ok := firstBlock.ObjectGet("type"); ok && t.IsString() { + firstBlockType = t.String() + } + } + missingThinkingPrefix := firstBlockType != "thinking" && firstBlockType != "redacted_thinking" + + if missingThinkingPrefix { + hasToolUse := false + for _, block := range lastAssistantContent.ArrayItems() { + if !block.IsObject() { + continue + } + if t, ok := block.ObjectGet("type"); ok && t.IsString() && t.String() == "tool_use" { + hasToolUse = true + break + } + } + if hasToolUse { + message.ObjectDelete("thinking") + result.applied = true + } + } + } + } + + return result +} + +// ---- response input rectifier (response-input-rectifier.ts#rectifyResponseInput) ---- +// +// Mutates message.input in place, matching applyBodyOps' `case "normalize_response_input": +// rectifyResponseInput(message);` (the op result is not tracked in OpResults; only the +// digest's separate responseInputRectify field records the action/originalType). +func rectifyResponseInput(message *ojson.Value) { + input, hasInput := message.ObjectGet("input") + + // Case 1: array -- passthrough. + if hasInput && input.IsArray() { + return + } + + // Case 2: string. + if hasInput && input.IsString() { + if input.String() == "" { + message.ObjectSet("input", ojson.NewArray()) + return + } + wrapped := ojson.NewArray() + item := ojson.NewObject() + item.ObjectSet("role", ojson.NewString("user")) + contentArr := ojson.NewArray() + contentBlock := ojson.NewObject() + contentBlock.ObjectSet("type", ojson.NewString("input_text")) + contentBlock.ObjectSet("text", ojson.NewString(input.String())) + contentArr.ArrayAppend(contentBlock) + item.ObjectSet("content", contentArr) + wrapped.ArrayAppend(item) + message.ObjectSet("input", wrapped) + return + } + + // Case 3: single object (MessageInput has role, ToolOutputsInput has type). + if hasInput && input.IsObject() { + if input.ObjectHas("role") || input.ObjectHas("type") { + wrapped := ojson.NewArray() + wrapped.ArrayAppend(input) + message.ObjectSet("input", wrapped) + return + } + } + + // Case 4: undefined/null/other -- passthrough, let downstream handle the error. +} + +// ---- private parameter filtering (forwarder.ts#filterPrivateParameters) ---- + +func filterPrivateParameters(v *ojson.Value) *ojson.Value { + if v == nil { + return v + } + switch { + case v.IsArray(): + out := ojson.NewArray() + for _, item := range v.ArrayItems() { + out.ArrayAppend(filterPrivateParameters(item)) + } + return out + case v.IsObject(): + out := ojson.NewObject() + for _, k := range v.ObjectKeys() { + if strings.HasPrefix(k, "_") { + continue + } + child, _ := v.ObjectGet(k) + out.ObjectSet(k, filterPrivateParameters(child)) + } + return out + default: + return v + } +} diff --git a/edge/internal/bodyops/bodyops_test.go b/edge/internal/bodyops/bodyops_test.go new file mode 100644 index 0000000000..02527ae0d6 --- /dev/null +++ b/edge/internal/bodyops/bodyops_test.go @@ -0,0 +1,103 @@ +package bodyops + +import ( + "encoding/json" + "os" + "testing" + + "github.com/ding113/claude-code-hub/edge/internal/contract" + "github.com/ding113/claude-code-hub/edge/internal/ojson" +) + +const fixtureDir = "../../../tests/fixtures/edge/body" + +type bodyOpsCase struct { + Name string `json:"name"` + Body string `json:"body"` + Ops []contract.BodyOp `json:"ops"` + ExpectedBody string `json:"expectedBody"` + ExpectedOpResults json.RawMessage `json:"expectedOpResults"` +} + +func TestApplyFixtures(t *testing.T) { + data, err := os.ReadFile(fixtureDir + "/bodyops.json") + if err != nil { + t.Fatalf("read fixture: %v", err) + } + var cases []bodyOpsCase + if err := json.Unmarshal(data, &cases); err != nil { + t.Fatalf("unmarshal fixture: %v", err) + } + if len(cases) == 0 { + t.Fatal("no fixture cases loaded") + } + + for _, c := range cases { + t.Run(c.Name, func(t *testing.T) { + body, err := ojson.Parse([]byte(c.Body), 0) + if err != nil { + t.Fatalf("parse body: %v", err) + } + original := string(body.Marshal()) + + got, opResults, err := Apply(body, c.Ops) + if err != nil { + t.Fatalf("Apply: %v", err) + } + + if string(body.Marshal()) != original { + t.Fatalf("Apply mutated the input body: before=%s after=%s", original, body.Marshal()) + } + + gotBody := string(got.Marshal()) + if gotBody != c.ExpectedBody { + t.Fatalf("body mismatch\n got: %s\nwant: %s", gotBody, c.ExpectedBody) + } + + gotResults, err := json.Marshal(opResults) + if err != nil { + t.Fatalf("marshal opResults: %v", err) + } + if !jsonEqual(t, gotResults, c.ExpectedOpResults) { + t.Fatalf("opResults mismatch\n got: %s\nwant: %s", gotResults, c.ExpectedOpResults) + } + }) + } +} + +func jsonEqual(t *testing.T, a, b json.RawMessage) bool { + t.Helper() + var av, bv interface{} + if err := json.Unmarshal(a, &av); err != nil { + t.Fatalf("unmarshal a: %v", err) + } + if len(b) == 0 { + b = json.RawMessage("{}") + } + if err := json.Unmarshal(b, &bv); err != nil { + t.Fatalf("unmarshal b: %v", err) + } + ab, _ := json.Marshal(av) + bb, _ := json.Marshal(bv) + return string(ab) == string(bb) +} + +func TestApplyUnknownOpErrors(t *testing.T) { + body, _ := ojson.Parse([]byte(`{}`), 0) + _, _, err := Apply(body, []contract.BodyOp{{Op: "not_a_real_op"}}) + if err == nil { + t.Fatal("expected error for unknown op") + } +} + +func TestApplyDisallowedKeyErrors(t *testing.T) { + body, _ := ojson.Parse([]byte(`{}`), 0) + _, _, err := Apply(body, []contract.BodyOp{{Op: contract.OpSetTopLevel, Key: "stream", Value: json.RawMessage("true")}}) + if err == nil { + t.Fatal("expected error for disallowed set_top_level key (stream is not mutable)") + } + _, _, err = Apply(body, []contract.BodyOp{{Op: contract.OpDeleteTopLevel, Key: "previous_response_id"}}) + if err == nil { + t.Fatal("expected error for disallowed delete_top_level key (previous_response_id is not mutable)") + } +} diff --git a/edge/internal/capture/compact.go b/edge/internal/capture/compact.go new file mode 100644 index 0000000000..d3f4b63049 --- /dev/null +++ b/edge/internal/capture/compact.go @@ -0,0 +1,525 @@ +package capture + +import ( + "bytes" + "encoding/json" + "regexp" + "sort" + "strings" +) + +// CompactCapture builds a small SSE (or raw-JSON) text from a normally +// completed upstream response, suitable for feeding into the existing TS +// settlement parsers (parseUsageFromResponseText, parseSSEData, +// extractThinkingSignatureModelFromStream, resolveAnthropicStreamActualResponseModel, +// detectUpstreamErrorFromSseOrJsonText, extractActualResponseModelForProvider, +// hasStreamCompletionMarker / hasTerminalStreamUsageEvidence, +// SessionManager.extractCodexPromptCacheKey). +// +// Two rule sets are implemented, selected by NewCompactCaptureForFormat: +// +// - "claude" (the original, and NewCompactCapture's default): kept, in wire +// order, the first message_start frame, every message_delta frame, the +// first message_stop frame, every error frame, and up to 4 +// content_block_delta frames whose data contains "signature_delta". Each +// kept frame is re-emitted as `event: \ndata: \n\n` (LF +// only), synthesizing the event name from the JSON `type` field when the +// source frame had no `event:` line. message_delta and error frames are +// always kept even if doing so exceeds maxBytes; once the budget is +// exceeded, no further non-priority frames are added and Result() +// reports truncated=true. This has no direct TS equivalent; it is +// logic designed to this package's spec. +// +// - "response" / "openai": kept, in original wire order: the first frame +// observed (carries model / prompt_cache_key in realistic streams), the +// first few (up to compactUsageFrameCap) frames carrying a usage object +// (data.usage or data.response.usage) plus always the LAST such frame, +// the last frame carrying a non-empty service_tier (top-level or +// data.response.service_tier), every terminal/completion frame +// (response.completed / response.done / response.incomplete / +// response.failed for "response"; any chunk with a non-empty +// choices[].finish_reason, and the literal `data: [DONE]` frame, for +// "openai"), and every error-relevant frame (non-empty `error`, +// `failed`, type error/response.error/response.failed, a nested +// response.error or response.status "failed", or a small JSON body +// whose `message` field matches /error/i). Unlike the claude variant, +// every kept frame is "priority" (always kept even over maxBytes, +// tracked via truncated); nothing else is retained since no downstream +// consumer needs response/openai body content for settlement. Frames +// are re-emitted verbatim: `event: \ndata: \n\n` when the +// source frame had an `event:` line, or `data: \n\n` when it +// did not (parseSSEData treats both the same, defaulting to event +// "message", so this preserves that behavior instead of synthesizing a +// name the way the claude variant does). If the stream never contained a +// single parseable SSE/data-only frame (e.g. a forced-stream codex +// response that is a bare JSON body), Result() falls back to the raw +// text prefix (up to maxBytes) so settlement still sees a non-empty +// body -- this also guarantees the "empty body" case of +// detectUpstreamErrorFromSseOrJsonText agrees between full and compact, +// since the first frame is always retained whenever at least one frame +// was observed. +// +// This has no direct TS equivalent; it is new logic designed to this +// package's spec. +type CompactCapture struct { + format string + maxBytes int + + buf []byte + + eventName *string + dataLines []string + + out []byte + truncated bool + overBudget bool + eventCount int + + // claude-only state + messageStartKept bool + messageStopKept bool + signatureDeltaKept int + + // response/openai-only state + curSeq int + kept map[int]compactFrameEntry + usageKeptCount int + lastUsageEntry *compactFrameEntry + lastTierEntry *compactFrameEntry + sawAnyFrame bool + rawFallbackBuf []byte + rawFallbackFull bool +} + +// compactUsageFrameCap bounds how many *early* usage-bearing frames are kept +// unconditionally for response/openai formats (the last usage-bearing frame +// is always kept too, regardless of this cap). +const compactUsageFrameCap = 4 + +type compactFrameEntry struct { + seq int + eventName *string + data string +} + +func NewCompactCapture(maxBytes int) *CompactCapture { + return NewCompactCaptureForFormat("claude", maxBytes) +} + +// NewCompactCaptureForFormat constructs a CompactCapture for client format +// "claude" | "response" | "openai". Unknown formats fall back to "claude". +func NewCompactCaptureForFormat(format string, maxBytes int) *CompactCapture { + c := &CompactCapture{format: format, maxBytes: maxBytes} + if format == "response" || format == "openai" { + c.kept = make(map[int]compactFrameEntry) + } + return c +} + +func (c *CompactCapture) isClaude() bool { + return c.format != "response" && c.format != "openai" +} + +func (c *CompactCapture) Observe(chunk []byte) { + if len(chunk) == 0 { + return + } + if !c.isClaude() && !c.rawFallbackFull { + room := c.maxBytes - len(c.rawFallbackBuf) + if room > 0 { + take := chunk + if len(take) > room { + take = take[:room] + } + c.rawFallbackBuf = append(c.rawFallbackBuf, take...) + } + if len(c.rawFallbackBuf) >= c.maxBytes { + c.rawFallbackFull = true + } + } + + c.buf = append(c.buf, chunk...) + for { + idx := bytes.IndexByte(c.buf, '\n') + if idx < 0 { + break + } + line := c.buf[:idx] + if len(line) > 0 && line[len(line)-1] == '\r' { + line = line[:len(line)-1] + } + lineCopy := append([]byte(nil), line...) + c.buf = c.buf[idx+1:] + c.processLine(lineCopy) + } +} + +func (c *CompactCapture) processLine(line []byte) { + if len(line) == 0 { + c.finalizeFrame() + return + } + if bytes.HasPrefix(line, []byte(":")) { + return + } + if bytes.HasPrefix(line, []byte("event:")) { + v := strings.TrimSpace(string(line[6:])) + c.eventName = &v + return + } + if bytes.HasPrefix(line, []byte("data:")) { + d := line[5:] + if len(d) > 0 && d[0] == ' ' { + d = d[1:] + } + c.dataLines = append(c.dataLines, string(d)) + return + } + // id:/retry:/unrecognized lines are not needed by the downstream parsers + // this capture feeds; ignore them without breaking frame boundaries. +} + +func (c *CompactCapture) finalizeFrame() { + if len(c.dataLines) == 0 { + c.eventName = nil + return + } + + data := strings.Join(c.dataLines, "\n") + eventName := c.eventName + c.eventName = nil + c.dataLines = nil + + c.eventCount++ + + if c.isClaude() { + c.finalizeFrameClaude(eventName, data) + return + } + c.finalizeFrameOther(eventName, data) +} + +// --- claude ------------------------------------------------------------ + +func (c *CompactCapture) finalizeFrameClaude(eventName *string, data string) { + name := "" + if eventName != nil { + name = *eventName + } + if name == "" { + name = extractJSONType(data) + } + + keep, isPriority := c.shouldKeep(name, data) + if !keep { + return + } + + emitted := "event: " + name + "\ndata: " + data + "\n\n" + + if !isPriority { + if c.overBudget { + return + } + if len(c.out)+len(emitted) > c.maxBytes { + c.overBudget = true + c.truncated = true + return + } + c.out = append(c.out, emitted...) + return + } + + // message_delta / error: always kept, even if it pushes past the budget. + if len(c.out)+len(emitted) > c.maxBytes { + c.truncated = true + } + c.out = append(c.out, emitted...) +} + +func (c *CompactCapture) shouldKeep(name, data string) (keep bool, isPriority bool) { + switch name { + case "message_start": + if c.messageStartKept { + return false, false + } + c.messageStartKept = true + return true, false + case "message_delta": + return true, true + case "message_stop": + if c.messageStopKept { + return false, false + } + c.messageStopKept = true + return true, false + case "error": + return true, true + case "content_block_delta": + if strings.Contains(data, "signature_delta") && c.signatureDeltaKept < 4 { + c.signatureDeltaKept++ + return true, false + } + return false, false + default: + return false, false + } +} + +func extractJSONType(data string) string { + var v struct { + Type string `json:"type"` + } + if err := json.Unmarshal([]byte(data), &v); err != nil { + return "" + } + return v.Type +} + +// --- response / openai --------------------------------------------------- + +var compactErrorMessageKeywordRe = regexp.MustCompile(`(?i)error`) + +func (c *CompactCapture) finalizeFrameOther(eventName *string, data string) { + seq := c.curSeq + c.curSeq++ + c.sawAnyFrame = true + + entry := compactFrameEntry{seq: seq, eventName: eventName, data: data} + trimmed := strings.TrimSpace(data) + + isDoneSentinel := c.format == "openai" && trimmed == "[DONE]" + + var m map[string]interface{} + isJSON := false + if !isDoneSentinel { + if err := json.Unmarshal([]byte(data), &m); err == nil && m != nil { + isJSON = true + } + } + + isFirst := seq == 0 + hasUsage := isJSON && compactHasUsage(m) + hasTier := isJSON && compactServiceTier(m) != "" + isTerminal := isDoneSentinel + isError := false + if isJSON { + switch c.format { + case "response": + isTerminal = isTerminal || compactIsResponsesTerminal(m) + isError = compactIsResponsesError(eventName, m) + case "openai": + isTerminal = isTerminal || compactIsOpenAIChatTerminal(m) + isError = compactIsOpenAIChatError(m) + } + } + + if isFirst || isTerminal || isError { + c.addKept(entry) + } + if hasUsage { + if c.usageKeptCount < compactUsageFrameCap { + c.addKept(entry) + c.usageKeptCount++ + } + c.lastUsageEntry = &entry + } + if hasTier { + c.lastTierEntry = &entry + } +} + +func (c *CompactCapture) addKept(entry compactFrameEntry) { + c.kept[entry.seq] = entry +} + +func compactAsRecord(v interface{}) (map[string]interface{}, bool) { + m, ok := v.(map[string]interface{}) + return m, ok +} + +func compactIsNonEmptyValue(value interface{}) bool { + switch v := value.(type) { + case nil: + return false + case string: + return strings.TrimSpace(v) != "" + case float64: + return v != 0 + case bool: + return v + case []interface{}: + return len(v) > 0 + case map[string]interface{}: + return len(v) > 0 + default: + return true + } +} + +func compactHasUsage(m map[string]interface{}) bool { + if u, ok := m["usage"]; ok { + if _, isObj := compactAsRecord(u); isObj { + return true + } + } + if resp, ok := compactAsRecord(m["response"]); ok { + if u, ok := resp["usage"]; ok { + if _, isObj := compactAsRecord(u); isObj { + return true + } + } + } + return false +} + +func compactServiceTier(m map[string]interface{}) string { + if s, ok := m["service_tier"].(string); ok && strings.TrimSpace(s) != "" { + return strings.TrimSpace(s) + } + if resp, ok := compactAsRecord(m["response"]); ok { + if s, ok := resp["service_tier"].(string); ok && strings.TrimSpace(s) != "" { + return strings.TrimSpace(s) + } + } + return "" +} + +// compactIsResponsesTerminal ports the "response" branch of +// inspectStreamCompletion (response-handler.ts) plus response.failed, which +// is a terminal marker for compaction purposes even though it is also an +// error. +func compactIsResponsesTerminal(m map[string]interface{}) bool { + t, _ := m["type"].(string) + switch t { + case "response.completed", "response.done", "response.incomplete", "response.failed": + return true + } + return false +} + +// compactIsResponsesError ports isDiscoveryProtocolErrorPayload +// (discovery-validity.ts) plus the small-message-keyword branch of +// detectUpstreamErrorFromSseOrJsonText (upstream-error-detection.ts). +func compactIsResponsesError(eventName *string, m map[string]interface{}) bool { + if compactIsNonEmptyValue(m["error"]) { + return true + } + if b, ok := m["failed"].(bool); ok && b { + return true + } + if t, ok := m["type"].(string); ok { + if t == "error" || t == "response.error" || t == "response.failed" { + return true + } + } + if resp, ok := compactAsRecord(m["response"]); ok { + if compactIsNonEmptyValue(resp["error"]) { + return true + } + if st, ok := resp["status"].(string); ok && st == "failed" { + return true + } + } + if eventName != nil && *eventName == "error" { + return true + } + if msg, ok := m["message"].(string); ok && compactErrorMessageKeywordRe.MatchString(msg) { + return true + } + return false +} + +// compactIsOpenAIChatTerminal ports hasOpenAIChatCompletionMarker +// (response-handler.ts): any choices[] entry with a non-empty finish_reason. +func compactIsOpenAIChatTerminal(m map[string]interface{}) bool { + choices, ok := m["choices"].([]interface{}) + if !ok { + return false + } + for _, ch := range choices { + chM, ok := compactAsRecord(ch) + if !ok { + continue + } + if fr, ok := chM["finish_reason"].(string); ok && strings.TrimSpace(fr) != "" { + return true + } + } + return false +} + +// compactIsOpenAIChatError ports the JSON-object branch of +// detectUpstreamErrorFromSseOrJsonText for a Chat Completions chunk. +func compactIsOpenAIChatError(m map[string]interface{}) bool { + if compactIsNonEmptyValue(m["error"]) { + return true + } + if msg, ok := m["message"].(string); ok && compactErrorMessageKeywordRe.MatchString(msg) { + return true + } + return false +} + +func renderCompactFrame(entry compactFrameEntry) string { + if entry.eventName != nil { + return "event: " + *entry.eventName + "\ndata: " + entry.data + "\n\n" + } + return "data: " + entry.data + "\n\n" +} + +func (c *CompactCapture) resultOther() (string, bool, int) { + if c.lastUsageEntry != nil { + c.addKept(*c.lastUsageEntry) + } + if c.lastTierEntry != nil { + c.addKept(*c.lastTierEntry) + } + + if len(c.kept) == 0 { + if !c.sawAnyFrame { + // No parseable SSE/data-only frame was ever observed (e.g. a + // forced-stream codex response that is a bare JSON body): + // fall back to the raw text prefix so settlement still sees a + // non-empty body. + return string(c.rawFallbackBuf), c.rawFallbackFull, c.eventCount + } + return "", false, c.eventCount + } + + seqs := make([]int, 0, len(c.kept)) + for s := range c.kept { + seqs = append(seqs, s) + } + sort.Ints(seqs) + + var out []byte + for _, s := range seqs { + out = append(out, renderCompactFrame(c.kept[s])...) + } + + return string(out), len(out) > c.maxBytes, c.eventCount +} + +// Result finalizes the capture (flushing any pending unterminated frame) +// and reports the compact text, whether it was truncated, and the total +// number of frames observed (kept or not). +func (c *CompactCapture) Result() (text string, truncated bool, eventCount int) { + if len(c.buf) > 0 { + line := c.buf + if len(line) > 0 && line[len(line)-1] == '\r' { + line = line[:len(line)-1] + } + c.processLine(line) + c.buf = nil + } + if len(c.dataLines) > 0 { + c.finalizeFrame() + } + + if !c.isClaude() { + return c.resultOther() + } + + return string(c.out), c.truncated, c.eventCount +} diff --git a/edge/internal/capture/compact_formats_test.go b/edge/internal/capture/compact_formats_test.go new file mode 100644 index 0000000000..51514f9528 --- /dev/null +++ b/edge/internal/capture/compact_formats_test.go @@ -0,0 +1,374 @@ +package capture + +import ( + "encoding/json" + "fmt" + "os" + "strings" + "testing" +) + +// compactFormatsCase mirrors one entry of +// tests/fixtures/edge/capture/compact-formats.json: {name, format, +// providerType, full, compact}. `full` and `compact` are plain SSE/JSON +// text (no base64 needed -- these fixtures are ASCII/JSON only). +type compactFormatsCase struct { + Name string `json:"name"` + Format string `json:"format"` + ProviderType string `json:"providerType"` + Full string `json:"full"` + Compact string `json:"compact"` +} + +const compactFormatsFixturePath = "../../../tests/fixtures/edge/capture/compact-formats.json" + +// buildCompactFormatsCases constructs realistic full SSE (and one bare-JSON) +// streams for the "response" (OpenAI Responses) and "openai" (OpenAI Chat +// Completions) client formats, and computes the compact text +// NewCompactCaptureForFormat produces for each. These pairs are the fixture +// tests/unit/edge/compact-formats.test.ts cross-checks the real TS +// settlement parsers against (full vs compact must agree). +func buildCompactFormatsCases() []compactFormatsCase { + var cases []compactFormatsCase + add := func(name, format, providerType, full string) { + cc := NewCompactCaptureForFormat(format, 64*1024) + cc.Observe([]byte(full)) + compact, _, _ := cc.Result() + cases = append(cases, compactFormatsCase{ + Name: name, Format: format, ProviderType: providerType, + Full: full, Compact: compact, + }) + } + + // ----------------------------------------------------------------- + // response (codex / OpenAI Responses API) + // ----------------------------------------------------------------- + + respCreated := frame("response.created", obj{ + "type": "response.created", + "response": obj{ + "id": "resp_1", "object": "response", "status": "in_progress", + "model": "gpt-5-codex", "prompt_cache_key": "cache-key-1", + "service_tier": "auto", + }, + }) + respInProgress := frame("response.in_progress", obj{ + "type": "response.in_progress", + "response": obj{ + "id": "resp_1", "status": "in_progress", "model": "gpt-5-codex", + }, + }) + outputItemAdded := frame("response.output_item.added", obj{ + "type": "response.output_item.added", "output_index": 0, + "item": obj{"id": "msg_1", "type": "message", "role": "assistant"}, + }) + contentPartAdded := frame("response.content_part.added", obj{ + "type": "response.content_part.added", "output_index": 0, "content_index": 0, + "part": obj{"type": "output_text", "text": ""}, + }) + var deltas []string + for i, word := range []string{"Hello", ",", " world", "!"} { + deltas = append(deltas, frame("response.output_text.delta", obj{ + "type": "response.output_text.delta", "output_index": 0, "content_index": 0, + "item_id": "msg_1", "delta": word, + })) + _ = i + } + outputTextDone := frame("response.output_text.done", obj{ + "type": "response.output_text.done", "output_index": 0, "content_index": 0, + "item_id": "msg_1", "text": "Hello, world!", + }) + outputItemDone := frame("response.output_item.done", obj{ + "type": "response.output_item.done", "output_index": 0, + "item": obj{"id": "msg_1", "type": "message", "role": "assistant"}, + }) + respCompleted := frame("response.completed", obj{ + "type": "response.completed", + "response": obj{ + "id": "resp_1", "object": "response", "status": "completed", + "model": "gpt-5-codex", "prompt_cache_key": "cache-key-1", + "service_tier": "default", + "usage": obj{ + "input_tokens": 120, "output_tokens": 42, "total_tokens": 162, + "input_tokens_details": obj{"cached_tokens": 30}, + "output_tokens_details": obj{"reasoning_tokens": 8}, + }, + }, + }) + + full := respCreated + respInProgress + outputItemAdded + contentPartAdded + + strings.Join(deltas, "") + outputTextDone + outputItemDone + respCompleted + add("response_normal_complete", "response", "codex", full) + + // service_tier changes on a frame that is otherwise not kept for any + // other reason (not first, no usage, not terminal/error): the + // last-service_tier-wins rule must still surface it. + respInProgressWithTier := frame("response.in_progress", obj{ + "type": "response.in_progress", + "response": obj{ + "id": "resp_1", "status": "in_progress", "model": "gpt-5-codex", + "service_tier": "priority_only_here", + }, + }) + respCompletedNoTier := frame("response.completed", obj{ + "type": "response.completed", + "response": obj{ + "id": "resp_1", "object": "response", "status": "completed", + "model": "gpt-5-codex", + "usage": obj{"input_tokens": 12, "output_tokens": 4}, + }, + }) + fullMidStreamTier := respCreated + respInProgressWithTier + outputItemAdded + + strings.Join(deltas[:2], "") + respCompletedNoTier + add("response_service_tier_only_on_middle_frame", "response", "codex", fullMidStreamTier) + + // More usage-bearing frames than compactUsageFrameCap, and the true last + // usage-bearing frame is NOT the terminal frame: only the first N + // deltas and the true last usage frame should be retained (plus the + // terminal frame itself, which carries no usage of its own here). + var manyUsageDeltas []string + for i := 0; i < 8; i++ { + manyUsageDeltas = append(manyUsageDeltas, frame("response.output_text.delta", obj{ + "type": "response.output_text.delta", "output_index": 0, "content_index": 0, + "item_id": "msg_1", "delta": "x", + "usage": obj{"input_tokens": 100, "output_tokens": i + 1}, + })) + } + respCompletedNoUsageNoTier := frame("response.completed", obj{ + "type": "response.completed", + "response": obj{ + "id": "resp_1", "object": "response", "status": "completed", "model": "gpt-5-codex", + }, + }) + fullManyUsage := respCreated + strings.Join(manyUsageDeltas, "") + respCompletedNoUsageNoTier + add("response_many_usage_frames_exceed_cap", "response", "codex", fullManyUsage) + + respIncomplete := frame("response.incomplete", obj{ + "type": "response.incomplete", + "response": obj{ + "id": "resp_2", "status": "incomplete", "model": "gpt-5-codex", + "incomplete_details": obj{"reason": "max_output_tokens"}, + "usage": obj{"input_tokens": 50, "output_tokens": 10}, + }, + }) + fullIncomplete := respCreated + respInProgress + outputItemAdded + + strings.Join(deltas[:2], "") + respIncomplete + add("response_incomplete", "response", "codex", fullIncomplete) + + respFailed := frame("response.failed", obj{ + "type": "response.failed", + "response": obj{ + "id": "resp_3", "status": "failed", + "error": obj{"code": "server_error", "message": "internal error"}, + }, + }) + fullFailed := respCreated + respInProgress + respFailed + add("response_failed", "response", "codex", fullFailed) + + errorEvent := frame("error", obj{ + "type": "error", "code": "rate_limit_exceeded", "message": "too many requests", + }) + fullErrorEvent := respCreated + respInProgress + errorEvent + add("response_error_event", "response", "codex", fullErrorEvent) + + fullMissingTerminal := respCreated + respInProgress + outputItemAdded + contentPartAdded + + strings.Join(deltas[:2], "") // client aborted mid-stream, no terminal frame at all + add("response_missing_terminal", "response", "codex", fullMissingTerminal) + + // Forced-stream codex response that is not actually SSE: a single raw + // JSON body (no "data:"/"event:" framing at all). + nonSSE, _ := json.Marshal(obj{ + "id": "resp_4", "object": "response", "status": "completed", + "model": "gpt-5-codex", + "usage": obj{"input_tokens": 5, "output_tokens": 3}, + }) + add("response_non_sse_forced_stream", "response", "codex", string(nonSSE)) + + // ----------------------------------------------------------------- + // openai (OpenAI-compatible Chat Completions) + // ----------------------------------------------------------------- + + chatID := "chatcmpl-1" + roleChunk := dataFrame(obj{ + "id": chatID, "object": "chat.completion.chunk", "model": "gpt-4o-mini", + "choices": []obj{{"index": 0, "delta": obj{"role": "assistant", "content": ""}, "finish_reason": nil}}, + }) + var contentChunks []string + for _, word := range []string{"Hello", ",", " world", "!"} { + contentChunks = append(contentChunks, dataFrame(obj{ + "id": chatID, "object": "chat.completion.chunk", "model": "gpt-4o-mini", + "choices": []obj{{"index": 0, "delta": obj{"content": word}, "finish_reason": nil}}, + })) + } + finishChunk := dataFrame(obj{ + "id": chatID, "object": "chat.completion.chunk", "model": "gpt-4o-mini", + "choices": []obj{{"index": 0, "delta": obj{}, "finish_reason": "stop"}}, + }) + usageChunk := dataFrame(obj{ + "id": chatID, "object": "chat.completion.chunk", "model": "gpt-4o-mini", + "choices": []interface{}{}, + "usage": obj{"prompt_tokens": 20, "completion_tokens": 6, "total_tokens": 26}, + }) + done := "data: [DONE]\n\n" + + fullChat := roleChunk + strings.Join(contentChunks, "") + finishChunk + usageChunk + done + add("openai_normal_complete", "openai", "openai-compatible", fullChat) + + // Provider variant: usage present in every chunk (cumulative), and no + // separate empty-choices usage chunk. + var everyChunkUsage []string + everyChunkUsage = append(everyChunkUsage, dataFrame(obj{ + "id": chatID, "object": "chat.completion.chunk", "model": "gpt-4o-mini", + "choices": []obj{{"index": 0, "delta": obj{"role": "assistant", "content": "Hi"}, "finish_reason": nil}}, + "usage": obj{"prompt_tokens": 20, "completion_tokens": 1, "total_tokens": 21}, + })) + everyChunkUsage = append(everyChunkUsage, dataFrame(obj{ + "id": chatID, "object": "chat.completion.chunk", "model": "gpt-4o-mini", + "choices": []obj{{"index": 0, "delta": obj{"content": " there"}, "finish_reason": nil}}, + "usage": obj{"prompt_tokens": 20, "completion_tokens": 2, "total_tokens": 22}, + })) + everyChunkUsage = append(everyChunkUsage, dataFrame(obj{ + "id": chatID, "object": "chat.completion.chunk", "model": "gpt-4o-mini", + "choices": []obj{{"index": 0, "delta": obj{}, "finish_reason": "stop"}}, + "usage": obj{"prompt_tokens": 20, "completion_tokens": 3, "total_tokens": 23}, + })) + fullEveryChunkUsage := strings.Join(everyChunkUsage, "") + done + add("openai_usage_in_every_chunk", "openai", "openai-compatible", fullEveryChunkUsage) + + // Mid-stream error object (no choices at all). + errChunk := dataFrame(obj{ + "error": obj{"message": "internal error occurred", "type": "server_error", "code": "internal"}, + }) + fullErrorMidStream := roleChunk + contentChunks[0] + errChunk + add("openai_error_object_mid_stream", "openai", "openai-compatible", fullErrorMidStream) + + // Same logical stream with explicit `event: message` lines vs none + // (real OpenAI chat streams never send `event:`, but some + // OpenAI-compatible relays do; parseSSEData treats both identically). + fullNoEventLines := roleChunk + strings.Join(contentChunks, "") + finishChunk + usageChunk + done + add("openai_no_event_lines", "openai", "openai-compatible", fullNoEventLines) + + withEventLines := eventFrame("message", obj{ + "id": chatID, "object": "chat.completion.chunk", "model": "gpt-4o-mini", + "choices": []obj{{"index": 0, "delta": obj{"role": "assistant", "content": ""}, "finish_reason": nil}}, + }) + var withEventContentChunks []string + for _, word := range []string{"Hello", ",", " world", "!"} { + withEventContentChunks = append(withEventContentChunks, eventFrame("message", obj{ + "id": chatID, "object": "chat.completion.chunk", "model": "gpt-4o-mini", + "choices": []obj{{"index": 0, "delta": obj{"content": word}, "finish_reason": nil}}, + })) + } + withEventFinish := eventFrame("message", obj{ + "id": chatID, "object": "chat.completion.chunk", "model": "gpt-4o-mini", + "choices": []obj{{"index": 0, "delta": obj{}, "finish_reason": "stop"}}, + }) + withEventUsage := eventFrame("message", obj{ + "id": chatID, "object": "chat.completion.chunk", "model": "gpt-4o-mini", + "choices": []interface{}{}, + "usage": obj{"prompt_tokens": 20, "completion_tokens": 6, "total_tokens": 26}, + }) + fullWithEventLines := withEventLines + strings.Join(withEventContentChunks, "") + withEventFinish + withEventUsage + done + add("openai_with_event_lines", "openai", "openai-compatible", fullWithEventLines) + + return cases +} + +type obj = map[string]interface{} + +func frame(event string, data obj) string { + b, err := json.Marshal(data) + if err != nil { + panic(err) + } + return fmt.Sprintf("event: %s\ndata: %s\n\n", event, string(b)) +} + +func eventFrame(event string, data obj) string { + return frame(event, data) +} + +func dataFrame(data interface{}) string { + b, err := json.Marshal(data) + if err != nil { + panic(err) + } + return fmt.Sprintf("data: %s\n\n", string(b)) +} + +func TestCompactCaptureFormatsFixtures(t *testing.T) { + cases := buildCompactFormatsCases() + + if os.Getenv("UPDATE_FIXTURES") == "1" { + out, err := json.MarshalIndent(cases, "", " ") + if err != nil { + t.Fatalf("marshal fixtures: %v", err) + } + out = append(out, '\n') + if err := os.WriteFile(compactFormatsFixturePath, out, 0o644); err != nil { + t.Fatalf("write fixture: %v", err) + } + t.Logf("wrote %s (%d cases)", compactFormatsFixturePath, len(cases)) + return + } + + var want []compactFormatsCase + loadFixture(t, "compact-formats.json", &want) + + if len(want) != len(cases) { + t.Fatalf("fixture has %d cases, generator produced %d -- run with UPDATE_FIXTURES=1", len(want), len(cases)) + } + + for i, c := range cases { + w := want[i] + if c.Name != w.Name { + t.Fatalf("case %d name = %q, fixture has %q (order changed?) -- run with UPDATE_FIXTURES=1", i, c.Name, w.Name) + } + t.Run(c.Name, func(t *testing.T) { + if c.Full != w.Full { + t.Errorf("full stream changed for %q -- run with UPDATE_FIXTURES=1", c.Name) + } + if c.Compact != w.Compact { + t.Errorf("compact output changed for %q:\ngot: %q\nwant: %q", c.Name, c.Compact, w.Compact) + } + }) + } +} + +// TestCompactCaptureFormatsSplitInvariance feeds each full stream through +// NewCompactCaptureForFormat in different chunk splits and checks the +// compact output is identical regardless of how bytes arrived. +func TestCompactCaptureFormatsSplitInvariance(t *testing.T) { + for _, c := range buildCompactFormatsCases() { + c := c + t.Run(c.Name, func(t *testing.T) { + full := []byte(c.Full) + splits := [][]int{{len(full)}, splitEvery(full, 1), splitEvery(full, 7)} + + var ref string + for i, sizes := range splits { + cc := NewCompactCaptureForFormat(c.Format, 64*1024) + offset := 0 + for _, size := range sizes { + if offset >= len(full) { + break + } + end := offset + size + if end > len(full) { + end = len(full) + } + cc.Observe(full[offset:end]) + offset = end + } + got, _, _ := cc.Result() + if i == 0 { + ref = got + continue + } + if got != ref { + t.Errorf("split %d differs.\ngot: %q\nwant: %q", i, got, ref) + } + } + }) + } +} diff --git a/edge/internal/capture/compact_test.go b/edge/internal/capture/compact_test.go new file mode 100644 index 0000000000..06d91692c8 --- /dev/null +++ b/edge/internal/capture/compact_test.go @@ -0,0 +1,99 @@ +package capture + +import "testing" + +type compactCase struct { + Name string `json:"name"` + MaxBytes int `json:"maxBytes"` + FullSseB64 string `json:"fullSseB64"` + ExpectedText string `json:"expectedText"` + ExpectedTruncated bool `json:"expectedTruncated"` + ExpectedEventCount int `json:"expectedEventCount"` +} + +func TestCompactCaptureFixtures(t *testing.T) { + var cases []compactCase + loadFixture(t, "compact.json", &cases) + + for _, c := range cases { + c := c + t.Run(c.Name, func(t *testing.T) { + full := mustB64(t, c.FullSseB64) + + cc := NewCompactCapture(c.MaxBytes) + cc.Observe(full) + text, truncated, eventCount := cc.Result() + + if text != c.ExpectedText { + t.Errorf("text =\n%q\nwant:\n%q", text, c.ExpectedText) + } + if truncated != c.ExpectedTruncated { + t.Errorf("truncated = %v, want %v", truncated, c.ExpectedTruncated) + } + if eventCount != c.ExpectedEventCount { + t.Errorf("eventCount = %d, want %d", eventCount, c.ExpectedEventCount) + } + }) + } +} + +// TestCompactCaptureSplitInvariance is a Go-only test (no TS fixture): +// feeding the same overall bytes through CompactCapture in different chunk +// splits must produce identical Result(). +func TestCompactCaptureSplitInvariance(t *testing.T) { + full := []byte( + "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"model\":\"claude-x\"}}\n\n" + + "event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"delta\":{\"type\":\"signature_delta\",\"signature\":\"abc\"}}\n\n" + + "event: message_delta\ndata: {\"type\":\"message_delta\",\"usage\":{\"output_tokens\":5}}\n\n" + + "event: message_stop\ndata: {\"type\":\"message_stop\"}\n\n", + ) + + splitPatterns := [][]int{ + {len(full)}, + splitEvery(full, 1), + splitEvery(full, 5), + splitEvery(full, 23), + } + + var refText string + var refTruncated bool + var refCount int + + for i, sizes := range splitPatterns { + cc := NewCompactCapture(64 * 1024) + offset := 0 + for _, size := range sizes { + if offset >= len(full) { + break + } + end := offset + size + if end > len(full) { + end = len(full) + } + cc.Observe(full[offset:end]) + offset = end + } + text, truncated, count := cc.Result() + if i == 0 { + refText, refTruncated, refCount = text, truncated, count + continue + } + if text != refText { + t.Errorf("split %d: text differs.\ngot: %q\nwant: %q", i, text, refText) + } + if truncated != refTruncated { + t.Errorf("split %d: truncated = %v, want %v", i, truncated, refTruncated) + } + if count != refCount { + t.Errorf("split %d: eventCount = %d, want %d", i, count, refCount) + } + } +} + +func splitEvery(data []byte, n int) []int { + var out []int + for i := 0; i < len(data); i += n { + out = append(out, n) + } + return out +} diff --git a/edge/internal/capture/fixture_test.go b/edge/internal/capture/fixture_test.go new file mode 100644 index 0000000000..0191016842 --- /dev/null +++ b/edge/internal/capture/fixture_test.go @@ -0,0 +1,29 @@ +package capture + +import ( + "encoding/base64" + "encoding/json" + "os" + "testing" +) + +func loadFixture(t *testing.T, name string, out interface{}) { + t.Helper() + path := "../../../tests/fixtures/edge/capture/" + name + data, err := os.ReadFile(path) + if err != nil { + t.Fatalf("read fixture %s: %v", path, err) + } + if err := json.Unmarshal(data, out); err != nil { + t.Fatalf("unmarshal fixture %s: %v", path, err) + } +} + +func mustB64(t *testing.T, s string) []byte { + t.Helper() + b, err := base64.StdEncoding.DecodeString(s) + if err != nil { + t.Fatalf("decode base64: %v", err) + } + return b +} diff --git a/edge/internal/capture/frame_classifier.go b/edge/internal/capture/frame_classifier.go new file mode 100644 index 0000000000..8823a76d27 --- /dev/null +++ b/edge/internal/capture/frame_classifier.go @@ -0,0 +1,549 @@ +package capture + +import "strings" + +// ProtocolFamily ports ProtocolFamily from stream-gate/frame-classifier.ts. +type ProtocolFamily string + +const ( + FamilyAnthropic ProtocolFamily = "anthropic" + FamilyOpenAIChat ProtocolFamily = "openai-chat" + FamilyOpenAIResponses ProtocolFamily = "openai-responses" + FamilyGemini ProtocolFamily = "gemini" +) + +// FrameVerdict ports FrameVerdict. +type FrameVerdict string + +const ( + VerdictContent FrameVerdict = "content" + VerdictError FrameVerdict = "error" + VerdictMalformed FrameVerdict = "malformed" + VerdictTerminal FrameVerdict = "terminal" + VerdictNeutral FrameVerdict = "neutral" +) + +// TerminalKind ports TerminalKind ("" == null). +type TerminalKind string + +const ( + TerminalComplete TerminalKind = "complete" + TerminalIncomplete TerminalKind = "incomplete" + TerminalNone TerminalKind = "" +) + +type valueMatch struct { + path string + values []string +} + +type frameRule struct { + eventTypes []string + anyPaths []string + valueMatches []valueMatch +} + +type streamSignal struct { + contentRules []frameRule + errorRules []frameRule + terminalRules []frameRule + terminalEvents []string + doneSentinel string +} + +// streamSignals ports STREAM_SIGNALS verbatim from frame-classifier.ts. +var streamSignals = map[ProtocolFamily]streamSignal{ + FamilyAnthropic: { + contentRules: []frameRule{ + { + eventTypes: []string{"content_block_delta"}, + anyPaths: []string{ + "delta.text", "delta.partial_json", "delta.thinking", + "delta.signature", "delta.citation", + }, + }, + { + eventTypes: []string{"content_block_start"}, + anyPaths: []string{ + "content_block.data", "content_block.content", "content_block.file_id", + "content_block.fileId", "content_block.url", "content_block.result", + }, + valueMatches: []valueMatch{ + { + path: "content_block.type", + values: []string{ + "redacted_thinking", "web_search_tool_result", "web_fetch_tool_result", + "code_execution_tool_result", "bash_code_execution_tool_result", + "text_editor_code_execution_tool_result", "tool_search_tool_result", + "mcp_tool_result", "container_upload", + }, + }, + }, + }, + }, + errorRules: []frameRule{ + {eventTypes: []string{"error", "response.error"}}, + {anyPaths: []string{"error"}}, + }, + terminalEvents: []string{"message_stop"}, + }, + FamilyOpenAIChat: { + contentRules: []frameRule{ + { + anyPaths: []string{ + "choices.#.delta.content", + "choices.#.delta.reasoning_content", + "choices.#.delta.tool_calls.#.function.arguments", + "choices.#.delta.function_call.arguments", + "choices.#.delta.refusal", + "choices.#.delta.audio.data", + "choices.#.delta.audio.transcript", + }, + }, + }, + errorRules: []frameRule{ + {anyPaths: []string{"error"}}, + }, + doneSentinel: "[DONE]", + }, + FamilyOpenAIResponses: { + contentRules: []frameRule{ + { + eventTypes: []string{ + "response.output_text.delta", "response.refusal.delta", + "response.reasoning_text.delta", "response.reasoning_summary_text.delta", + "response.audio.delta", "response.audio.transcript.delta", + "response.function_call_arguments.delta", "response.custom_tool_call_input.delta", + "response.code_interpreter_call_code.delta", "response.mcp_call_arguments.delta", + }, + anyPaths: []string{"delta"}, + }, + { + eventTypes: []string{"response.image_generation_call.partial_image"}, + anyPaths: []string{"partial_image_b64"}, + }, + { + eventTypes: []string{ + "response.output_text.done", "response.reasoning_text.done", + "response.reasoning_summary_text.done", + }, + anyPaths: []string{"text"}, + }, + { + eventTypes: []string{"response.audio.transcript.done"}, + anyPaths: []string{"transcript", "text"}, + }, + { + eventTypes: []string{"response.refusal.done"}, + anyPaths: []string{"refusal"}, + }, + { + eventTypes: []string{"response.function_call_arguments.done", "response.mcp_call_arguments.done"}, + anyPaths: []string{"arguments"}, + }, + { + eventTypes: []string{"response.custom_tool_call_input.done"}, + anyPaths: []string{"input"}, + }, + { + eventTypes: []string{"response.code_interpreter_call_code.done"}, + anyPaths: []string{"code"}, + }, + { + eventTypes: []string{"response.output_item.added", "response.output_item.done"}, + anyPaths: []string{ + "item.content.#.text", "item.summary.#.text", "item.arguments", "item.input", + "item.action", "item.queries", "item.query", "item.code", "item.command", + "item.operation", + }, + }, + }, + errorRules: []frameRule{ + {eventTypes: []string{"error", "response.error"}}, + {eventTypes: []string{"response.failed"}}, + {anyPaths: []string{"error", "response.error"}}, + }, + terminalEvents: []string{"response.completed", "response.incomplete", "response.done"}, + }, + FamilyGemini: { + contentRules: []frameRule{ + { + anyPaths: []string{ + "candidates.#.content.parts.#.text", + "candidates.#.content.parts.#.inlineData.data", + "candidates.#.content.parts.#.fileData.fileUri", + "candidates.#.content.parts.#.functionCall.name", + "candidates.#.content.parts.#.functionResponse.name", + "candidates.#.content.parts.#.executableCode.code", + "candidates.#.content.parts.#.codeExecutionResult.output", + }, + }, + }, + errorRules: []frameRule{ + {anyPaths: []string{"error"}}, + {anyPaths: []string{"promptFeedback.blockReason"}}, + { + valueMatches: []valueMatch{ + { + path: "candidates.#.finishReason", + values: []string{ + "SAFETY", "RECITATION", "LANGUAGE", "BLOCKLIST", "PROHIBITED_CONTENT", + "SPII", "MALFORMED_FUNCTION_CALL", "IMAGE_SAFETY", "UNEXPECTED_TOOL_CALL", + "IMAGE_PROHIBITED_CONTENT", "NO_IMAGE", "IMAGE_RECITATION", "IMAGE_OTHER", + "OTHER", + }, + }, + }, + }, + }, + terminalRules: []frameRule{ + {anyPaths: []string{"candidates.#.finishReason"}}, + }, + }, +} + +func mapClientFormatToProtocolFamily(format string) ProtocolFamily { + switch format { + case "response": + return FamilyOpenAIResponses + case "claude": + return FamilyAnthropic + case "openai": + return FamilyOpenAIChat + case "gemini", "gemini-cli": + return FamilyGemini + default: + return FamilyAnthropic + } +} + +// isNonEmptyValue ports isNonEmptyValue (gjson-style non-empty semantics). +func isNonEmptyValue(v interface{}) bool { + switch val := v.(type) { + case nil: + return false + case bool: + return val + case string: + return val != "" + case float64: + return true + case []interface{}: + for _, item := range val { + if isNonEmptyValue(item) { + return true + } + } + return false + case map[string]interface{}: + return len(val) > 0 + default: + return false + } +} + +// resolvePath ports resolvePath/resolveSegments (gjson-style dotted path with `#` collection). +func resolvePath(node interface{}, path string) interface{} { + segments := strings.Split(path, ".") + return resolveSegments(node, segments, 0) +} + +func resolveSegments(node interface{}, segments []string, index int) interface{} { + if index == len(segments) { + return node + } + segment := segments[index] + if segment == "#" { + arr, ok := node.([]interface{}) + if !ok { + return nil + } + hasNestedHash := false + for _, s := range segments[index+1:] { + if s == "#" { + hasNestedHash = true + break + } + } + var collected []interface{} + for _, item := range arr { + resolved := resolveSegments(item, segments, index+1) + if resolved == nil { + continue + } + if sub, ok := resolved.([]interface{}); ok && hasNestedHash { + collected = append(collected, sub...) + } else { + collected = append(collected, resolved) + } + } + return collected + } + m, ok := node.(map[string]interface{}) + if !ok { + return nil + } + child, exists := m[segment] + if !exists { + return nil + } + return resolveSegments(child, segments, index+1) +} + +func frameRuleMatches(rule frameRule, eventType string, parsed interface{}) bool { + if len(rule.eventTypes) > 0 { + found := false + for _, e := range rule.eventTypes { + if e == eventType { + found = true + break + } + } + if !found { + return false + } + } + if len(rule.anyPaths) > 0 { + hit := false + for _, path := range rule.anyPaths { + if isNonEmptyValue(resolvePath(parsed, path)) { + hit = true + break + } + } + if !hit { + return false + } + } + for _, match := range rule.valueMatches { + if !valueMatchHits(match, parsed) { + return false + } + } + return len(rule.eventTypes) > 0 || len(rule.anyPaths) > 0 || len(rule.valueMatches) > 0 +} + +func valueMatchHits(match valueMatch, parsed interface{}) bool { + if match.path == "" || len(match.values) == 0 { + return false + } + resolved := resolvePath(parsed, match.path) + if resolved == nil { + return false + } + var candidates []interface{} + if arr, ok := resolved.([]interface{}); ok { + candidates = arr + } else { + candidates = []interface{}{resolved} + } + for _, c := range candidates { + switch cv := c.(type) { + case string: + for _, v := range match.values { + if v == cv { + return true + } + } + case bool: + s := "false" + if cv { + s = "true" + } + for _, v := range match.values { + if v == s { + return true + } + } + } + } + return false +} + +func classifyParsedFrame(family ProtocolFamily, signal streamSignal, eventName *string, parsed interface{}) FrameVerdict { + effective := "" + if eventName != nil { + effective = strings.TrimSpace(*eventName) + } + if effective == "" { + if m, ok := parsed.(map[string]interface{}); ok { + if t, ok := m["type"].(string); ok { + effective = t + } + } + } + + for _, rule := range signal.errorRules { + if frameRuleMatches(rule, effective, parsed) { + return VerdictError + } + } + if family == FamilyOpenAIResponses && isResponsesCompactionContent(effective, parsed) { + return VerdictContent + } + for _, rule := range signal.contentRules { + if frameRuleMatches(rule, effective, parsed) { + return VerdictContent + } + } + for _, rule := range signal.terminalRules { + if frameRuleMatches(rule, effective, parsed) { + return VerdictTerminal + } + } + if effective != "" { + for _, e := range signal.terminalEvents { + if e == effective { + return VerdictTerminal + } + } + } + return VerdictNeutral +} + +// classifyStructuredFrame ports classifyStructuredFrame. +func classifyStructuredFrame(family ProtocolFamily, eventName *string, parsed interface{}) FrameVerdict { + signal := streamSignals[family] + outer := classifyParsedFrame(family, signal, eventName, parsed) + + _, isArray := parsed.([]interface{}) + if outer != VerdictNeutral || family != FamilyGemini || isArray { + return outer + } + + m, ok := parsed.(map[string]interface{}) + if !ok { + return outer + } + respRaw, exists := m["response"] + if !exists { + return outer + } + if respM, ok := respRaw.(map[string]interface{}); ok { + return classifyParsedFrame(family, signal, eventName, respM) + } + return outer +} + +// classifyStructuredTerminalKind ports classifyStructuredTerminalKind. +func classifyStructuredTerminalKind(family ProtocolFamily, eventName *string, parsed interface{}) TerminalKind { + effective := "" + if eventName != nil { + effective = strings.TrimSpace(*eventName) + } + _, isArray := parsed.([]interface{}) + if (effective == "" || effective == "message") && !isArray { + if m, ok := parsed.(map[string]interface{}); ok { + if t, ok := m["type"].(string); ok { + effective = t + } + } + } + + if family == FamilyOpenAIResponses { + switch effective { + case "response.incomplete": + return TerminalIncomplete + case "response.completed", "response.done": + return TerminalComplete + default: + return TerminalNone + } + } + + signal := streamSignals[family] + if effective != "" { + for _, e := range signal.terminalEvents { + if e == effective { + return TerminalComplete + } + } + } + for _, rule := range signal.terminalRules { + if frameRuleMatches(rule, effective, parsed) { + return TerminalComplete + } + } + return TerminalNone +} + +// classifyTerminalKind ports classifyTerminalKind. +func classifyTerminalKind(family ProtocolFamily, eventName *string, parsed interface{}) TerminalKind { + if parsed != nil { + if k := classifyStructuredTerminalKind(family, eventName, parsed); k != TerminalNone { + return k + } + } + if family != FamilyOpenAIResponses { + return TerminalComplete + } + effective := "" + if eventName != nil { + effective = strings.TrimSpace(*eventName) + } + if (effective == "" || effective == "message") && parsed != nil { + if _, isArray := parsed.([]interface{}); !isArray { + if m, ok := parsed.(map[string]interface{}); ok { + if t, ok := m["type"].(string); ok { + effective = t + } + } + } + } + switch effective { + case "response.incomplete": + return TerminalIncomplete + case "response.completed", "response.done": + return TerminalComplete + default: + return TerminalNone + } +} + +func isResponsesCompactionContent(eventType string, parsed interface{}) bool { + if _, ok := parsed.([]interface{}); ok { + return false + } + m, ok := parsed.(map[string]interface{}) + if !ok { + return false + } + if eventType == "response.output_item.done" { + return isNonEmptyCompactionItem(m["item"]) + } + if eventType != "response.completed" { + return false + } + respRaw, ok := m["response"] + if !ok { + return false + } + resp, ok := respRaw.(map[string]interface{}) + if !ok { + return false + } + outputRaw, ok := resp["output"] + if !ok { + return false + } + output, ok := outputRaw.([]interface{}) + if !ok { + return false + } + for _, item := range output { + if isNonEmptyCompactionItem(item) { + return true + } + } + return false +} + +func isNonEmptyCompactionItem(item interface{}) bool { + m, ok := item.(map[string]interface{}) + if !ok { + return false + } + t, _ := m["type"].(string) + ec, ecOk := m["encrypted_content"].(string) + return t == "compaction" && ecOk && ec != "" +} diff --git a/edge/internal/capture/framer.go b/edge/internal/capture/framer.go new file mode 100644 index 0000000000..a7366cbb09 --- /dev/null +++ b/edge/internal/capture/framer.go @@ -0,0 +1,273 @@ +package capture + +import "strings" + +// parsedFrame ports ParsedFrame. +type parsedFrame struct { + eventName *string + data string +} + +const frameDataLineOverheadCharacters = 16 + +// boundedEventFramer ports the TS BoundedEventFramer class: incrementally +// frames SSE, data-only SSE, and bounded NDJSON input. +// +// Note: the TS implementation budgets in UTF-16 code units (JS string +// length); this port budgets in bytes of the Go string (which is already +// UTF-8). For the ASCII-dominated protocol frames this code processes the +// two are equivalent; for frames containing many multi-byte characters near +// the budget boundary, byte-length here is a stricter (smaller) count of +// "characters" than TS's UTF-16 length, so this port may drop a frame +// slightly earlier than TS would in that specific edge case. +type boundedEventFramer struct { + decoder *utf8StreamDecoder + + line string + lineOverflow bool + overflowedRawJSONLine bool + pendingCR bool + eventName *string + dataLines []string + frameCharacters int + droppingFrame bool + pendingMaxFrame *int + skippedOversizedFrames int + + maxFrameCharacters int + onFrame func(parsedFrame) +} + +func newBoundedEventFramer(maxFrameCharacters int, onFrame func(parsedFrame)) *boundedEventFramer { + return &boundedEventFramer{ + decoder: newUTF8StreamDecoder(), + maxFrameCharacters: maxFrameCharacters, + onFrame: onFrame, + } +} + +func (fr *boundedEventFramer) maxRetainedCharacters() int { return fr.maxFrameCharacters } + +func (fr *boundedEventFramer) setMaxFrameCharacters(n int) { + if n >= fr.maxFrameCharacters { + return + } + hasInFlightFrame := len(fr.line) > 0 || fr.lineOverflow || fr.frameCharacters > 0 || + len(fr.dataLines) > 0 || fr.eventName != nil || fr.droppingFrame + if !hasInFlightFrame { + fr.maxFrameCharacters = n + return + } + if fr.pendingMaxFrame == nil { + v := n + fr.pendingMaxFrame = &v + } else if n < *fr.pendingMaxFrame { + *fr.pendingMaxFrame = n + } +} + +func (fr *boundedEventFramer) push(chunk []byte) { + if len(chunk) == 0 { + return + } + fr.consume(fr.decoder.Push(chunk)) +} + +func (fr *boundedEventFramer) finish() { + fr.consume(fr.decoder.Finish()) + if len(fr.line) > 0 || fr.lineOverflow { + fr.consumeLine() + } + if fr.droppingFrame { + fr.completeOversizedFrame() + } else { + fr.flushFrame() + } +} + +func (fr *boundedEventFramer) consume(text string) { + offset := 0 + if fr.pendingCR { + fr.pendingCR = false + if strings.HasPrefix(text, "\n") { + offset = 1 + } + } + + for offset < len(text) { + rel := text[offset:] + nextLf := strings.IndexByte(rel, '\n') + nextCr := strings.IndexByte(rel, '\r') + + lineEnd := -1 + switch { + case nextLf == -1 && nextCr == -1: + lineEnd = -1 + case nextLf == -1: + lineEnd = offset + nextCr + case nextCr == -1: + lineEnd = offset + nextLf + case nextLf < nextCr: + lineEnd = offset + nextLf + default: + lineEnd = offset + nextCr + } + + if lineEnd == -1 { + fr.appendLineSegment(text[offset:]) + return + } + + fr.appendLineSegment(text[offset:lineEnd]) + fr.consumeLine() + endedWithCr := text[lineEnd] == '\r' + offset = lineEnd + 1 + if endedWithCr { + if offset < len(text) && text[offset] == '\n' { + offset++ + } else if offset == len(text) { + fr.pendingCR = true + } + } + } +} + +func (fr *boundedEventFramer) appendLineSegment(segment string) { + if fr.lineOverflow || len(segment) == 0 { + return + } + available := fr.maxFrameCharacters - fr.frameCharacters - len(fr.line) + if len(segment) <= available { + fr.line += segment + return + } + + cut := available + if cut < 0 { + cut = 0 + } + prefix := fr.line + segment[:cut] + fr.overflowedRawJSONLine = fr.eventName == nil && len(fr.dataLines) == 0 && + strings.HasPrefix(strings.TrimLeft(prefix, " \t\n\r\f\v"), "{") + fr.line = "" + fr.lineOverflow = true + fr.dropCurrentFrame() +} + +func (fr *boundedEventFramer) consumeLine() { + line := fr.line + overflowed := fr.lineOverflow + overflowedRawJSONLine := fr.overflowedRawJSONLine + fr.line = "" + fr.lineOverflow = false + fr.overflowedRawJSONLine = false + + if overflowed && overflowedRawJSONLine { + fr.completeOversizedFrame() + return + } + + if len(line) == 0 && !overflowed { + if fr.droppingFrame { + fr.completeOversizedFrame() + } else { + fr.flushFrame() + } + return + } + if fr.droppingFrame || overflowed { + return + } + if strings.HasPrefix(line, ":") { + return + } + if strings.HasPrefix(line, "event:") { + v := strings.TrimSpace(line[6:]) + if len(v) > 256 { + v = v[:256] + } + fr.eventName = &v + fr.frameCharacters += len(line) + fr.enforceFrameLimit() + return + } + if strings.HasPrefix(line, "data:") { + data := trimOneLeadingWhitespace(line[5:]) + fr.dataLines = append(fr.dataLines, data) + fr.frameCharacters += len(data) + frameDataLineOverheadCharacters + fr.enforceFrameLimit() + return + } + + candidate := strings.TrimSpace(line) + if fr.eventName == nil && len(fr.dataLines) == 0 && strings.HasPrefix(candidate, "{") { + if len(candidate) <= fr.maxFrameCharacters { + fr.onFrame(parsedFrame{eventName: nil, data: candidate}) + } else { + fr.skippedOversizedFrames++ + } + fr.applyPendingFrameLimit() + } +} + +func trimOneLeadingWhitespace(s string) string { + if len(s) == 0 { + return s + } + switch s[0] { + case ' ', '\t', '\n', '\r', '\f', '\v': + return s[1:] + } + return s +} + +func (fr *boundedEventFramer) enforceFrameLimit() { + if fr.frameCharacters <= fr.maxFrameCharacters { + return + } + fr.dropCurrentFrame() +} + +func (fr *boundedEventFramer) dropCurrentFrame() { + if !fr.droppingFrame { + fr.skippedOversizedFrames++ + } + fr.droppingFrame = true + fr.resetFrame() +} + +func (fr *boundedEventFramer) completeOversizedFrame() { + if !fr.droppingFrame { + return + } + fr.droppingFrame = false + fr.resetFrame() + fr.applyPendingFrameLimit() +} + +func (fr *boundedEventFramer) flushFrame() { + if fr.droppingFrame || len(fr.dataLines) == 0 { + fr.resetFrame() + fr.applyPendingFrameLimit() + return + } + fr.onFrame(parsedFrame{eventName: fr.eventName, data: strings.Join(fr.dataLines, "\n")}) + fr.resetFrame() + fr.applyPendingFrameLimit() +} + +func (fr *boundedEventFramer) resetFrame() { + fr.eventName = nil + fr.dataLines = nil + fr.frameCharacters = 0 +} + +func (fr *boundedEventFramer) applyPendingFrameLimit() { + if fr.pendingMaxFrame == nil { + return + } + if *fr.pendingMaxFrame < fr.maxFrameCharacters { + fr.maxFrameCharacters = *fr.pendingMaxFrame + } + fr.pendingMaxFrame = nil +} diff --git a/edge/internal/capture/metering.go b/edge/internal/capture/metering.go new file mode 100644 index 0000000000..ce6714abce --- /dev/null +++ b/edge/internal/capture/metering.go @@ -0,0 +1,714 @@ +// Package capture ports src/app/v1/_lib/proxy/client-abort-metering.ts +// (loser/client-abort metering) and adds a compact-SSE-capture helper for +// normally-completed streams, stdlib only. +package capture + +import ( + "encoding/json" + "strings" + + "github.com/ding113/claude-code-hub/edge/internal/contract" + "github.com/ding113/claude-code-hub/edge/internal/ojson" +) + +const ( + clientAbortMeterMaxRetainedBytes = 64 * 1024 + clientAbortMeterMaxFrameBytes = 64 * 1024 +) + +var usageNumberFields = []string{ + "cachedContentTokenCount", + "cache_creation_1h_input_tokens", + "cache_creation_5m_input_tokens", + "cache_creation_input_tokens", + "cache_read_input_tokens", + "candidatesTokenCount", + "claude_cache_creation_1_h_tokens", + "claude_cache_creation_5_m_tokens", + "completion_tokens", + "input_tokens", + "output_tokens", + "promptTokenCount", + "prompt_tokens", + "thoughtsTokenCount", +} + +func asRecord(v interface{}) (map[string]interface{}, bool) { + m, ok := v.(map[string]interface{}) + return m, ok +} + +func truncate(s string, max int) string { + if len(s) <= max { + return s + } + return s[:max] +} + +// compactTokenDetails ports compactTokenDetails. +func compactTokenDetails(value interface{}) *ojson.Value { + arr, ok := value.([]interface{}) + if !ok { + return nil + } + if len(arr) > 16 { + arr = arr[:16] + } + out := ojson.NewArray() + for _, entry := range arr { + em, ok := asRecord(entry) + if !ok { + continue + } + c := ojson.NewObject() + if mod, ok := em["modality"].(string); ok { + c.ObjectSet("modality", ojson.NewString(truncate(mod, 32))) + } + if tc, ok := em["tokenCount"].(float64); ok { + c.ObjectSet("tokenCount", ojson.NewNumberFromFloat(tc)) + } + if c.ObjectLen() > 0 { + out.ArrayAppend(c) + } + } + if out.ArrayLen() == 0 { + return nil + } + return out +} + +// compactUsage ports compactUsage. Returns (value, true) if it produced a +// non-empty compact object, else (nil, false). +func compactUsage(value interface{}) (*ojson.Value, bool) { + m, ok := asRecord(value) + if !ok { + return nil, false + } + compact := ojson.NewObject() + for _, field := range usageNumberFields { + if v, ok := m[field].(float64); ok { + compact.ObjectSet(field, ojson.NewNumberFromFloat(v)) + } + } + + for _, field := range []string{"input_tokens_details", "prompt_tokens_details"} { + details, ok := asRecord(m[field]) + if !ok { + continue + } + sub := ojson.NewObject() + for _, f2 := range []string{"cached_tokens", "cache_write_tokens"} { + if v, ok := details[f2].(float64); ok { + sub.ObjectSet(f2, ojson.NewNumberFromFloat(v)) + } + } + if sub.ObjectLen() > 0 { + compact.ObjectSet(field, sub) + } + } + + if cc, ok := asRecord(m["cache_creation"]); ok { + cacheCreation := ojson.NewObject() + for _, f2 := range []string{"ephemeral_1h_input_tokens", "ephemeral_5m_input_tokens"} { + if v, ok := cc[f2].(float64); ok { + cacheCreation.ObjectSet(f2, ojson.NewNumberFromFloat(v)) + } + } + if cacheCreation.ObjectLen() > 0 { + compact.ObjectSet("cache_creation", cacheCreation) + } + } + + for _, field := range []string{"candidatesTokensDetails", "promptTokensDetails"} { + if details := compactTokenDetails(m[field]); details != nil { + compact.ObjectSet(field, details) + } + } + + if compact.ObjectLen() == 0 { + return nil, false + } + return compact, true +} + +// compactError ports compactError. +func compactError(value interface{}) *ojson.Value { + if s, ok := value.(string); ok { + return ojson.NewString(truncate(s, 1024)) + } + m, ok := asRecord(value) + if !ok { + if b, ok := value.(bool); ok && b { + return ojson.NewBool(true) + } + return nil + } + compact := ojson.NewObject() + for _, field := range []string{"code", "message", "type"} { + if s, ok := m[field].(string); ok { + compact.ObjectSet(field, ojson.NewString(truncate(s, 1024))) + } + } + if compact.ObjectLen() > 0 { + return compact + } + return ojson.NewBool(true) +} + +// compactPayload ports compactPayload. +func compactPayload(value map[string]interface{}, depth int) *ojson.Value { + compact := ojson.NewObject() + for _, field := range []string{"id", "model", "prompt_cache_key", "service_tier", "status", "type"} { + if s, ok := value[field].(string); ok { + compact.ObjectSet(field, ojson.NewString(truncate(s, 256))) + } + } + if b, ok := value["failed"].(bool); ok && b { + compact.ObjectSet("failed", ojson.NewBool(true)) + } + if errVal, exists := value["error"]; exists { + if ec := compactError(errVal); ec != nil { + compact.ObjectSet("error", ec) + } + } + + for _, field := range []string{"usage", "usageMetadata"} { + if u, ok := compactUsage(value[field]); ok { + compact.ObjectSet(field, u) + } + } + + if msgM, ok := asRecord(value["message"]); ok { + message := ojson.NewObject() + for _, field := range []string{"id", "model"} { + if s, ok := msgM[field].(string); ok { + message.ObjectSet(field, ojson.NewString(truncate(s, 256))) + } + } + if u, ok := compactUsage(msgM["usage"]); ok { + message.ObjectSet("usage", u) + } + if message.ObjectLen() > 0 { + compact.ObjectSet("message", message) + } + } + + if deltaM, ok := asRecord(value["delta"]); ok { + delta := ojson.NewObject() + if s, ok := deltaM["type"].(string); ok { + delta.ObjectSet("type", ojson.NewString(truncate(s, 128))) + } + if s, ok := deltaM["stop_reason"].(string); ok { + delta.ObjectSet("stop_reason", ojson.NewString(truncate(s, 128))) + } + if s, ok := deltaM["signature"].(string); ok { + delta.ObjectSet("signature", ojson.NewString(truncate(s, 8192))) + } + if u, ok := compactUsage(deltaM["usage"]); ok { + delta.ObjectSet("usage", u) + } + if delta.ObjectLen() > 0 { + compact.ObjectSet("delta", delta) + } + } + + if depth < 4 { + if respM, ok := asRecord(value["response"]); ok { + compact.ObjectSet("response", compactPayload(respM, depth+1)) + } + } + + if choicesArr, ok := value["choices"].([]interface{}); ok { + entries := choicesArr + if len(entries) > 16 { + entries = entries[:16] + } + choices := ojson.NewArray() + for _, ch := range entries { + o := ojson.NewObject() + if chM, ok := asRecord(ch); ok { + if fr, ok := chM["finish_reason"].(string); ok { + o.ObjectSet("finish_reason", ojson.NewString(truncate(fr, 128))) + } + } + choices.ArrayAppend(o) + } + compact.ObjectSet("choices", choices) + } + + if candidatesArr, ok := value["candidates"].([]interface{}); ok { + entries := candidatesArr + if len(entries) > 16 { + entries = entries[:16] + } + candidates := ojson.NewArray() + for _, cd := range entries { + o := ojson.NewObject() + if cdM, ok := asRecord(cd); ok { + if fr, ok := cdM["finishReason"].(string); ok { + o.ObjectSet("finishReason", ojson.NewString(truncate(fr, 128))) + } + } + candidates.ArrayAppend(o) + } + compact.ObjectSet("candidates", candidates) + } + + return compact +} + +// hasCompactUsage ports hasCompactUsage. +func hasCompactUsage(value map[string]interface{}, depth int) bool { + if _, ok := compactUsage(value["usage"]); ok { + return true + } + if _, ok := compactUsage(value["usageMetadata"]); ok { + return true + } + if msgM, ok := asRecord(value["message"]); ok { + if _, ok := compactUsage(msgM["usage"]); ok { + return true + } + } + if deltaM, ok := asRecord(value["delta"]); ok { + if _, ok := compactUsage(deltaM["usage"]); ok { + return true + } + } + if depth < 4 { + if respM, ok := asRecord(value["response"]); ok { + return hasCompactUsage(respM, depth+1) + } + } + return false +} + +func isProtocolError(value map[string]interface{}) bool { + if isNonEmptyValue(value["error"]) { + return true + } + if b, ok := value["failed"].(bool); ok && b { + return true + } + if t, ok := value["type"].(string); ok { + if t == "error" || t == "response.error" || t == "response.failed" { + return true + } + } + if respM, ok := asRecord(value["response"]); ok { + if isNonEmptyValue(respM["error"]) { + return true + } + } + return false +} + +func hasOpenAiCompletion(value map[string]interface{}) bool { + arr, ok := value["choices"].([]interface{}) + if !ok { + return false + } + for _, ch := range arr { + chM, ok := asRecord(ch) + if !ok { + continue + } + if fr, ok := chM["finish_reason"].(string); ok && strings.TrimSpace(fr) != "" { + return true + } + } + return false +} + +func hasGeminiCompletion(value map[string]interface{}) bool { + payload := value + if respM, ok := asRecord(value["response"]); ok { + payload = respM + } + arr, ok := payload["candidates"].([]interface{}) + if !ok { + return false + } + for _, cd := range arr { + cdM, ok := asRecord(cd) + if !ok { + continue + } + if fr, ok := cdM["finishReason"].(string); ok && strings.TrimSpace(fr) != "" { + return true + } + } + return false +} + +// shouldInspectFrame ports shouldInspectFrame. +func shouldInspectFrame(format string, frame parsedFrame) bool { + if frame.eventName == nil || *frame.eventName == "message" { + return true + } + ev := *frame.eventName + switch format { + case "response": + switch ev { + case "error", "response.created", "response.in_progress", "response.completed", + "response.done", "response.incomplete", "response.error", "response.failed": + return true + } + return false + case "claude": + switch ev { + case "error", "message_start", "message_delta", "message_stop": + return true + } + return ev == "content_block_delta" && strings.Contains(frame.data, "signature_delta") + case "openai", "gemini", "gemini-cli": + return true + default: + return false + } +} + +type protocolFailureState struct { + Verdict string + EventName *string + AfterContent bool + SawMalformed bool +} + +// MeteringObserver ports createClientAbortMeteringObserver. +type MeteringObserver struct { + format string + framer *boundedEventFramer + + evidenceOrder []string + evidence map[string]string + evidenceBytes map[string]int + evidenceValueCounts map[string]int + retainedByteTotal int + + drainComplete bool + replayDrainComplete bool + openAiCompletionSeen bool + sawContent bool + terminalSeen bool + incompleteSeen bool + protocolFailure *protocolFailureState + finished bool +} + +func NewMeteringObserver(format string, attachedMaxFrameBytes int) *MeteringObserver { + attached := clientAbortMeterMaxFrameBytes + if attachedMaxFrameBytes > clientAbortMeterMaxFrameBytes { + attached = attachedMaxFrameBytes + } + m := &MeteringObserver{ + format: format, + evidence: make(map[string]string), + evidenceBytes: make(map[string]int), + evidenceValueCounts: make(map[string]int), + } + m.framer = newBoundedEventFramer(attached, m.recordFrame) + return m +} + +func (m *MeteringObserver) setEvidence(slot, value string) { + previous := m.evidence[slot] + if previous == value { + return + } + previousBytes := m.evidenceBytes[slot] + nextBytes := len(value) + previousCount := 0 + if previous != "" { + previousCount = m.evidenceValueCounts[previous] + } + nextCount := m.evidenceValueCounts[value] + nextTotal := m.retainedByteTotal + if previousCount == 1 { + nextTotal -= previousBytes + } + if nextCount == 0 { + nextTotal += nextBytes + } + if nextTotal > clientAbortMeterMaxRetainedBytes { + return + } + + if previous != "" { + if previousCount <= 1 { + delete(m.evidenceValueCounts, previous) + m.retainedByteTotal -= previousBytes + } else { + m.evidenceValueCounts[previous] = previousCount - 1 + } + } + if _, existed := m.evidence[slot]; !existed { + m.evidenceOrder = append(m.evidenceOrder, slot) + } + m.evidence[slot] = value + m.evidenceBytes[slot] = nextBytes + m.evidenceValueCounts[value] = nextCount + 1 + if nextCount == 0 { + m.retainedByteTotal += nextBytes + } +} + +func (m *MeteringObserver) recordProtocolFailure(verdict string, eventName *string) { + if verdict == "error" { + m.drainComplete = true + m.replayDrainComplete = true + } + if m.protocolFailure == nil { + m.protocolFailure = &protocolFailureState{Verdict: verdict, EventName: eventName, AfterContent: m.sawContent} + return + } + if verdict == "error" && m.protocolFailure.Verdict == "malformed" { + m.protocolFailure = &protocolFailureState{ + Verdict: verdict, EventName: eventName, AfterContent: m.sawContent, SawMalformed: true, + } + } else if verdict == "malformed" && m.protocolFailure.Verdict == "error" { + m.protocolFailure.SawMalformed = true + } +} + +func normalizedFrameText(eventName *string, compactData string) string { + if eventName != nil { + return "event: " + *eventName + "\ndata: " + compactData + "\n\n" + } + return "data: " + compactData + "\n\n" +} + +func (m *MeteringObserver) recordFrame(frame parsedFrame) { + trimmed := strings.TrimSpace(frame.data) + if trimmed == "[DONE]" { + if m.format == "openai" { + m.drainComplete = true + m.replayDrainComplete = true + m.terminalSeen = true + m.setEvidence("terminal", "data: [DONE]\n\n") + } else { + m.recordProtocolFailure("malformed", frame.eventName) + } + return + } + + var parsed interface{} + if err := json.Unmarshal([]byte(trimmed), &parsed); err != nil { + m.recordProtocolFailure("malformed", frame.eventName) + return + } + if parsed == nil { + m.recordProtocolFailure("malformed", frame.eventName) + return + } + switch parsed.(type) { + case map[string]interface{}, []interface{}: + // ok + default: + m.recordProtocolFailure("malformed", frame.eventName) + return + } + + family := mapClientFormatToProtocolFamily(m.format) + verdict := classifyStructuredFrame(family, frame.eventName, parsed) + terminalKind := TerminalNone + if verdict == VerdictTerminal { + terminalKind = classifyTerminalKind(family, frame.eventName, parsed) + } + if verdict == VerdictContent { + m.sawContent = true + } + if verdict == VerdictError || verdict == VerdictMalformed { + vs := "error" + if verdict == VerdictMalformed { + vs = "malformed" + } + m.recordProtocolFailure(vs, frame.eventName) + } + + parsedMap, isRecord := parsed.(map[string]interface{}) + if !isRecord || !shouldInspectFrame(m.format, frame) { + return + } + + isErrorEvent := frame.eventName != nil && *frame.eventName == "error" + protocolError := verdict == VerdictError || isProtocolError(parsedMap) || isErrorEvent + typeStr, hasType := parsedMap["type"].(string) + if !hasType { + typeStr = "" + } + + terminal := false + switch m.format { + case "response": + terminal = (typeStr == "response.completed" || typeStr == "response.done") && + (frame.eventName == nil || *frame.eventName == "message" || *frame.eventName == typeStr) + case "claude": + terminal = typeStr == "message_stop" && + (frame.eventName == nil || *frame.eventName == "message" || *frame.eventName == "message_stop") + case "openai": + terminal = hasOpenAiCompletion(parsedMap) + case "gemini", "gemini-cli": + terminal = hasGeminiCompletion(parsedMap) + } + + incompleteTerminal := terminalKind == TerminalIncomplete + hasSignature := false + if deltaM, ok := asRecord(parsedMap["delta"]); ok { + if t, _ := deltaM["type"].(string); t == "signature_delta" { + if s, ok := deltaM["signature"].(string); ok && s != "" { + hasSignature = true + } + } + } + hasMetadata := false + if _, ok := parsedMap["model"].(string); ok { + hasMetadata = true + } + if !hasMetadata { + if _, ok := parsedMap["prompt_cache_key"].(string); ok { + hasMetadata = true + } + } + if !hasMetadata { + if _, ok := parsedMap["service_tier"].(string); ok { + hasMetadata = true + } + } + if !hasMetadata { + if msgM, ok := asRecord(parsedMap["message"]); ok { + if _, ok := msgM["model"].(string); ok { + hasMetadata = true + } + } + } + if !hasMetadata { + if respM, ok := asRecord(parsedMap["response"]); ok { + if _, ok := respM["model"].(string); ok { + hasMetadata = true + } else if _, ok := respM["service_tier"].(string); ok { + hasMetadata = true + } + } + } + hasUsage := hasCompactUsage(parsedMap, 0) + + if terminal { + m.terminalSeen = true + if m.format == "openai" { + m.openAiCompletionSeen = true + if hasUsage { + m.drainComplete = true + } + } else { + m.drainComplete = true + m.replayDrainComplete = true + } + } else if m.format == "openai" && m.openAiCompletionSeen && hasUsage { + m.drainComplete = true + } + if incompleteTerminal { + m.incompleteSeen = true + m.drainComplete = true + m.replayDrainComplete = true + } + + if !(protocolError || terminal || incompleteTerminal || hasUsage || hasSignature || hasMetadata) { + return + } + + compact := compactPayload(parsedMap, 0) + compactData := string(compact.Marshal()) + normalized := normalizedFrameText(frame.eventName, compactData) + + if protocolError { + m.recordProtocolFailure("error", frame.eventName) + m.setEvidence("error", normalized) + } + if terminal || incompleteTerminal { + m.setEvidence("terminal", normalized) + } + if hasUsage { + isInitialClaudeUsage := m.format == "claude" && + (typeStr == "message_start" || (frame.eventName != nil && *frame.eventName == "message_start")) + slot := "latest-usage" + if isInitialClaudeUsage { + slot = "initial-usage" + } + m.setEvidence(slot, normalized) + } + if hasSignature { + m.setEvidence("signature", normalized) + } + if hasMetadata { + m.setEvidence("metadata", normalized) + } +} + +// Observe feeds chunk into the framer and reports errorSeen/drainComplete +// as of after processing this chunk. +func (m *MeteringObserver) Observe(chunk []byte) (errorSeen bool, drainComplete bool) { + if !m.finished { + m.framer.push(chunk) + } + errorSeen = m.protocolFailure != nil && m.protocolFailure.Verdict == "error" + return errorSeen, m.drainComplete +} + +// SwitchToDetachedMode tightens the per-frame byte cap for frames not yet +// fully buffered (in-flight frames keep their attached budget until the +// next frame boundary). +func (m *MeteringObserver) SwitchToDetachedMode() { + if !m.finished { + m.framer.setMaxFrameCharacters(clientAbortMeterMaxFrameBytes) + } +} + +// MeteringResult ports ClientAbortMeteringSnapshot. +type MeteringResult struct { + Text string + SawContent bool + TerminalSeen bool + IncompleteSeen bool + RetainedBytes int + SkippedOversizedFrames int + ProtocolFailure *contract.ProtocolFailure +} + +func (m *MeteringObserver) Finish() MeteringResult { + if !m.finished { + m.finished = true + m.framer.finish() + } + + seen := make(map[string]bool, len(m.evidenceOrder)) + var parts []string + for _, slot := range m.evidenceOrder { + v := m.evidence[slot] + if seen[v] { + continue + } + seen[v] = true + parts = append(parts, v) + } + + var pf *contract.ProtocolFailure + if m.protocolFailure != nil { + pf = &contract.ProtocolFailure{ + Verdict: m.protocolFailure.Verdict, + EventName: m.protocolFailure.EventName, + AfterContent: m.protocolFailure.AfterContent, + SawMalformed: m.protocolFailure.SawMalformed, + } + } + + return MeteringResult{ + Text: strings.Join(parts, ""), + SawContent: m.sawContent, + TerminalSeen: m.terminalSeen, + IncompleteSeen: m.incompleteSeen, + RetainedBytes: m.retainedByteTotal, + SkippedOversizedFrames: m.framer.skippedOversizedFrames, + ProtocolFailure: pf, + } +} diff --git a/edge/internal/capture/metering_test.go b/edge/internal/capture/metering_test.go new file mode 100644 index 0000000000..3fd56d5018 --- /dev/null +++ b/edge/internal/capture/metering_test.go @@ -0,0 +1,132 @@ +package capture + +import "testing" + +type meteringAction struct { + Kind string `json:"kind"` + ChunkB64 string `json:"chunkB64"` + ErrorSeen bool `json:"errorSeen"` + DrainComplete bool `json:"drainComplete"` +} + +type meteringProtocolFailure struct { + AfterContent bool `json:"afterContent"` + Verdict string `json:"verdict"` + EventName *string `json:"eventName"` +} + +type meteringFinish struct { + Text string `json:"text"` + SawContent bool `json:"sawContent"` + TerminalSeen bool `json:"terminalSeen"` + IncompleteSeen bool `json:"incompleteSeen"` + RetainedBytes int `json:"retainedBytes"` + SkippedOversizedFrames int `json:"skippedOversizedFrames"` + ProtocolFailure *meteringProtocolFailure `json:"protocolFailure"` +} + +type meteringCase struct { + Name string `json:"name"` + Format string `json:"format"` + AttachedMaxFrameBytes *int `json:"attachedMaxFrameBytes"` + Actions []meteringAction `json:"actions"` + FinishAfterActions int `json:"finishAfterActions"` + Finish meteringFinish `json:"finish"` + FinishAgain *meteringFinish `json:"finishAgain"` +} + +func assertFinish(t *testing.T, got MeteringResult, want meteringFinish) { + t.Helper() + if got.Text != want.Text { + t.Errorf("Text = %q, want %q", got.Text, want.Text) + } + if got.SawContent != want.SawContent { + t.Errorf("SawContent = %v, want %v", got.SawContent, want.SawContent) + } + if got.TerminalSeen != want.TerminalSeen { + t.Errorf("TerminalSeen = %v, want %v", got.TerminalSeen, want.TerminalSeen) + } + if got.IncompleteSeen != want.IncompleteSeen { + t.Errorf("IncompleteSeen = %v, want %v", got.IncompleteSeen, want.IncompleteSeen) + } + if got.RetainedBytes != want.RetainedBytes { + t.Errorf("RetainedBytes = %d, want %d", got.RetainedBytes, want.RetainedBytes) + } + if got.SkippedOversizedFrames != want.SkippedOversizedFrames { + t.Errorf("SkippedOversizedFrames = %d, want %d", got.SkippedOversizedFrames, want.SkippedOversizedFrames) + } + if (got.ProtocolFailure == nil) != (want.ProtocolFailure == nil) { + t.Errorf("ProtocolFailure = %v, want %v", got.ProtocolFailure, want.ProtocolFailure) + } else if got.ProtocolFailure != nil { + if got.ProtocolFailure.Verdict != want.ProtocolFailure.Verdict { + t.Errorf("ProtocolFailure.Verdict = %q, want %q", got.ProtocolFailure.Verdict, want.ProtocolFailure.Verdict) + } + if got.ProtocolFailure.AfterContent != want.ProtocolFailure.AfterContent { + t.Errorf("ProtocolFailure.AfterContent = %v, want %v", got.ProtocolFailure.AfterContent, want.ProtocolFailure.AfterContent) + } + gotEvent := "" + if got.ProtocolFailure.EventName != nil { + gotEvent = *got.ProtocolFailure.EventName + } + wantEvent := "" + if want.ProtocolFailure.EventName != nil { + wantEvent = *want.ProtocolFailure.EventName + } + if gotEvent != wantEvent { + t.Errorf("ProtocolFailure.EventName = %q, want %q", gotEvent, wantEvent) + } + } +} + +func TestMeteringObserverFixtures(t *testing.T) { + var cases []meteringCase + loadFixture(t, "metering.json", &cases) + + for _, c := range cases { + c := c + t.Run(c.Name, func(t *testing.T) { + attached := 0 + if c.AttachedMaxFrameBytes != nil { + attached = *c.AttachedMaxFrameBytes + } + observer := NewMeteringObserver(c.Format, attached) + + applyAction := func(i int, action meteringAction) { + switch action.Kind { + case "detach": + observer.SwitchToDetachedMode() + case "observe": + chunk := mustB64(t, action.ChunkB64) + errorSeen, drainComplete := observer.Observe(chunk) + if errorSeen != action.ErrorSeen { + t.Errorf("action[%d] errorSeen = %v, want %v", i, errorSeen, action.ErrorSeen) + } + if drainComplete != action.DrainComplete { + t.Errorf("action[%d] drainComplete = %v, want %v", i, drainComplete, action.DrainComplete) + } + default: + t.Fatalf("unknown action kind %q", action.Kind) + } + } + + finishAfter := c.FinishAfterActions + if finishAfter <= 0 { + finishAfter = len(c.Actions) + } + + for i := 0; i < finishAfter; i++ { + applyAction(i, c.Actions[i]) + } + result := observer.Finish() + assertFinish(t, result, c.Finish) + + for i := finishAfter; i < len(c.Actions); i++ { + applyAction(i, c.Actions[i]) + } + if c.FinishAgain != nil { + second := observer.Finish() + assertFinish(t, second, *c.FinishAgain) + } + }) + } +} diff --git a/edge/internal/capture/utf8_stream_decoder.go b/edge/internal/capture/utf8_stream_decoder.go new file mode 100644 index 0000000000..7092431253 --- /dev/null +++ b/edge/internal/capture/utf8_stream_decoder.go @@ -0,0 +1,104 @@ +package capture + +import "strings" + +// utf8StreamDecoder incrementally decodes UTF-8 bytes into Go strings the +// way JavaScript's `new TextDecoder("utf-8").decode(chunk, {stream: true})` +// does: it never emits an error for a multi-byte sequence that is merely +// incomplete at the end of a Push() call (the tail bytes are held across +// calls); Finish() flushes the decoder the way a final non-streaming +// decode() call would, turning any still-pending partial sequence into a +// single U+FFFD. See https://encoding.spec.whatwg.org/#utf-8-decoder. +type utf8StreamDecoder struct { + codePoint int32 + bytesSeen int + bytesNeeded int + lowerBound byte + upperBound byte +} + +func newUTF8StreamDecoder() *utf8StreamDecoder { + return &utf8StreamDecoder{lowerBound: 0x80, upperBound: 0xBF} +} + +func (d *utf8StreamDecoder) reset() { + d.codePoint = 0 + d.bytesSeen = 0 + d.bytesNeeded = 0 + d.lowerBound = 0x80 + d.upperBound = 0xBF +} + +func (d *utf8StreamDecoder) Push(data []byte) string { + var sb strings.Builder + sb.Grow(len(data)) + + i := 0 + for i < len(data) { + b := data[i] + + if d.bytesNeeded == 0 { + switch { + case b <= 0x7F: + sb.WriteByte(b) + i++ + case b >= 0xC2 && b <= 0xDF: + d.bytesNeeded = 1 + d.codePoint = int32(b & 0x1F) + i++ + case b >= 0xE0 && b <= 0xEF: + if b == 0xE0 { + d.lowerBound = 0xA0 + } + if b == 0xED { + d.upperBound = 0x9F + } + d.bytesNeeded = 2 + d.codePoint = int32(b & 0x0F) + i++ + case b >= 0xF0 && b <= 0xF4: + if b == 0xF0 { + d.lowerBound = 0x90 + } + if b == 0xF4 { + d.upperBound = 0x8F + } + d.bytesNeeded = 3 + d.codePoint = int32(b & 0x07) + i++ + default: + sb.WriteRune('�') + i++ + } + continue + } + + if b < d.lowerBound || b > d.upperBound { + d.reset() + sb.WriteRune('�') + continue // reprocess this same byte with fresh state + } + + d.lowerBound = 0x80 + d.upperBound = 0xBF + d.codePoint = (d.codePoint << 6) | int32(b&0x3F) + d.bytesSeen++ + i++ + if d.bytesSeen != d.bytesNeeded { + continue + } + sb.WriteRune(rune(d.codePoint)) + d.reset() + } + + return sb.String() +} + +// Finish flushes any pending incomplete sequence into a single U+FFFD. +func (d *utf8StreamDecoder) Finish() string { + if d.bytesNeeded != 0 { + d.reset() + return "�" + } + return "" +} diff --git a/edge/internal/codec/codec.go b/edge/internal/codec/codec.go new file mode 100644 index 0000000000..6639aac05f --- /dev/null +++ b/edge/internal/codec/codec.go @@ -0,0 +1,122 @@ +// Package codec decodes inbound request bodies by Content-Encoding. +// +// Ports decodeRequestBody from src/app/v1/_lib/proxy/request-body-codec.ts: +// a single supported layer (zstd, gzip, x-gzip, deflate with raw-deflate fallback, +// br) is decoded with an output size cap. Anything the control plane would reject +// or pass through untouched is reported as an error so the caller delegates the +// original bytes and the control plane produces its usual response. +package codec + +import ( + "bytes" + "compress/flate" + "compress/gzip" + "compress/zlib" + "errors" + "fmt" + "io" + "strings" + + "github.com/andybalholm/brotli" + "github.com/klauspost/compress/zstd" +) + +var ( + ErrUnsupportedEncoding = errors.New("unsupported content-encoding") + ErrTooManyLayers = errors.New("too many content-encoding layers") + ErrTooLarge = errors.New("request body exceeds the size limit") + ErrCorrupt = errors.New("corrupt compressed request body") +) + +// Result describes a decoded body. +type Result struct { + Body []byte + Decoded bool + Encoding string +} + +// ParseContentEncoding ports parseContentEncoding: lowercase, trimmed, identity removed. +func ParseContentEncoding(header string) []string { + var tokens []string + for _, token := range strings.Split(header, ",") { + token = strings.ToLower(strings.TrimSpace(token)) + if token != "" && token != "identity" { + tokens = append(tokens, token) + } + } + return tokens +} + +func readLimited(reader io.Reader, maxOutput int64) ([]byte, error) { + data, err := io.ReadAll(io.LimitReader(reader, maxOutput+1)) + if err != nil { + if errors.Is(err, zstd.ErrDecoderSizeExceeded) { + return nil, ErrTooLarge + } + return nil, fmt.Errorf("%w: %w", ErrCorrupt, err) + } + if int64(len(data)) > maxOutput { + return nil, ErrTooLarge + } + return data, nil +} + +func decodeOne(input []byte, encoding string, maxOutput int64) ([]byte, error) { + switch encoding { + case "zstd": + decoder, err := zstd.NewReader(bytes.NewReader(input), zstd.WithDecoderMaxMemory(uint64(maxOutput)+1)) + if err != nil { + return nil, fmt.Errorf("%w: %v", ErrCorrupt, err) + } + defer decoder.Close() + return readLimited(decoder, maxOutput) + case "gzip", "x-gzip": + reader, err := gzip.NewReader(bytes.NewReader(input)) + if err != nil { + return nil, fmt.Errorf("%w: %v", ErrCorrupt, err) + } + defer reader.Close() + return readLimited(reader, maxOutput) + case "br": + return readLimited(brotli.NewReader(bytes.NewReader(input)), maxOutput) + case "deflate": + // HTTP deflate is nominally zlib-wrapped, but many clients send raw deflate. + if reader, err := zlib.NewReader(bytes.NewReader(input)); err == nil { + data, readErr := readLimited(reader, maxOutput) + _ = reader.Close() + if readErr == nil || errors.Is(readErr, ErrTooLarge) { + return data, readErr + } + } + raw := flate.NewReader(bytes.NewReader(input)) + defer raw.Close() + return readLimited(raw, maxOutput) + default: + return nil, ErrUnsupportedEncoding + } +} + +// Decode returns the plaintext body for a Content-Encoding header value. +func Decode(input []byte, contentEncoding string, maxOutput, maxCompressed int64) (Result, error) { + encodings := ParseContentEncoding(contentEncoding) + if len(encodings) == 0 || len(input) == 0 { + return Result{Body: input}, nil + } + if len(encodings) > 1 { + return Result{}, ErrTooManyLayers + } + encoding := encodings[0] + switch encoding { + case "zstd", "gzip", "x-gzip", "deflate", "br": + default: + return Result{}, ErrUnsupportedEncoding + } + if int64(len(input)) > maxCompressed { + return Result{}, ErrTooLarge + } + body, err := decodeOne(input, encoding, maxOutput) + if err != nil { + return Result{}, err + } + return Result{Body: body, Decoded: true, Encoding: encoding}, nil +} diff --git a/edge/internal/codec/codec_test.go b/edge/internal/codec/codec_test.go new file mode 100644 index 0000000000..48ade2edd2 --- /dev/null +++ b/edge/internal/codec/codec_test.go @@ -0,0 +1,102 @@ +package codec + +import ( + "bytes" + "compress/flate" + "compress/gzip" + "compress/zlib" + "errors" + "testing" + + "github.com/andybalholm/brotli" + "github.com/klauspost/compress/zstd" +) + +var payload = []byte(`{"model":"claude","messages":[{"role":"user","content":"hello"}]}`) + +func compress(t *testing.T, encoding string, data []byte) []byte { + t.Helper() + var buf bytes.Buffer + switch encoding { + case "gzip": + w := gzip.NewWriter(&buf) + _, _ = w.Write(data) + _ = w.Close() + case "deflate": + w := zlib.NewWriter(&buf) + _, _ = w.Write(data) + _ = w.Close() + case "raw-deflate": + w, _ := flate.NewWriter(&buf, flate.DefaultCompression) + _, _ = w.Write(data) + _ = w.Close() + case "br": + w := brotli.NewWriter(&buf) + _, _ = w.Write(data) + _ = w.Close() + case "zstd": + w, _ := zstd.NewWriter(&buf) + _, _ = w.Write(data) + _ = w.Close() + } + return buf.Bytes() +} + +func TestDecodeSupportedEncodings(t *testing.T) { + for _, tc := range []struct{ header, kind string }{ + {"gzip", "gzip"}, + {"x-gzip", "gzip"}, + {" GZIP ", "gzip"}, + {"deflate", "deflate"}, + {"deflate", "raw-deflate"}, + {"br", "br"}, + {"zstd", "zstd"}, + {"identity, zstd", "zstd"}, + } { + result, err := Decode(compress(t, tc.kind, payload), tc.header, 1<<20, 1<<20) + if err != nil { + t.Fatalf("%s/%s: %v", tc.header, tc.kind, err) + } + if !result.Decoded || !bytes.Equal(result.Body, payload) { + t.Fatalf("%s/%s: unexpected result %+v", tc.header, tc.kind, result) + } + } +} + +func TestDecodePassthrough(t *testing.T) { + for _, header := range []string{"", "identity", " , "} { + result, err := Decode(payload, header, 10, 10) + if err != nil || result.Decoded || !bytes.Equal(result.Body, payload) { + t.Fatalf("header %q: %+v %v", header, result, err) + } + } + empty, err := Decode(nil, "gzip, br", 10, 10) + if err != nil || empty.Decoded { + t.Fatalf("empty body should pass through: %+v %v", empty, err) + } +} + +func TestDecodeErrors(t *testing.T) { + cases := []struct { + name string + input []byte + header string + maxOut int64 + maxIn int64 + expected error + }{ + {"layers", []byte("x"), "gzip, br", 100, 100, ErrTooManyLayers}, + {"unsupported", []byte("x"), "compress", 100, 100, ErrUnsupportedEncoding}, + {"compressed too large", compress(t, "gzip", payload), "gzip", 1000, 5, ErrTooLarge}, + {"output too large", compress(t, "gzip", payload), "gzip", 10, 1000, ErrTooLarge}, + {"zstd bomb", compress(t, "zstd", bytes.Repeat([]byte("a"), 1<<16)), "zstd", 1024, 1 << 20, ErrTooLarge}, + {"corrupt gzip", []byte("not gzip"), "gzip", 100, 100, ErrCorrupt}, + {"corrupt deflate", []byte{0xff, 0xff, 0xff}, "deflate", 100, 100, ErrCorrupt}, + } + for _, tc := range cases { + _, err := Decode(tc.input, tc.header, tc.maxOut, tc.maxIn) + if !errors.Is(err, tc.expected) { + t.Errorf("%s: got %v, want %v", tc.name, err, tc.expected) + } + } +} diff --git a/edge/internal/config/config.go b/edge/internal/config/config.go new file mode 100644 index 0000000000..d8b1c31bfb --- /dev/null +++ b/edge/internal/config/config.go @@ -0,0 +1,170 @@ +// Package config loads the edge executor configuration from environment variables. +package config + +import ( + "errors" + "fmt" + "net/url" + "os" + "strconv" + "strings" + "time" +) + +type Config struct { + ListenAddr string + ControlURL *url.URL + DelegateURL *url.URL + SharedSecret string + EdgeID string + + OutboxDir string + OutboxMaxBytes int64 + + MaxBodyBytes int64 + MaxCompressedBodyBytes int64 + MaxJSONDepth int + MaxConcurrency int + OverflowToDelegate bool + FallbackToDelegate bool + + DecideTimeout time.Duration + NextTimeout time.Duration + CompleteTimeout time.Duration + HeartbeatTimeout time.Duration + + GatePrebufferBytes int64 + ShutdownGrace time.Duration + TrustedProxyCIDRs []string + LogLevel string +} + +func env(name, fallback string) string { + if value, ok := os.LookupEnv(name); ok && strings.TrimSpace(value) != "" { + return strings.TrimSpace(value) + } + return fallback +} + +func envInt64(name string, fallback int64) (int64, error) { + raw := env(name, "") + if raw == "" { + return fallback, nil + } + value, err := strconv.ParseInt(raw, 10, 64) + if err != nil { + return 0, fmt.Errorf("%s: %w", name, err) + } + return value, nil +} + +func envBool(name string, fallback bool) (bool, error) { + raw := env(name, "") + if raw == "" { + return fallback, nil + } + value, err := strconv.ParseBool(raw) + if err != nil { + return false, fmt.Errorf("%s: %w", name, err) + } + return value, nil +} + +func envDurationMs(name string, fallback time.Duration) (time.Duration, error) { + ms, err := envInt64(name, fallback.Milliseconds()) + if err != nil { + return 0, err + } + return time.Duration(ms) * time.Millisecond, nil +} + +func parseBaseURL(name, raw string) (*url.URL, error) { + if raw == "" { + return nil, fmt.Errorf("%s is required", name) + } + parsed, err := url.Parse(strings.TrimRight(raw, "/")) + if err != nil || parsed.Scheme == "" || parsed.Host == "" { + return nil, fmt.Errorf("%s must be an absolute URL", name) + } + return parsed, nil +} + +// Load reads and validates the configuration. +func Load() (*Config, error) { + var errs []error + collect := func(err error) { + if err != nil { + errs = append(errs, err) + } + } + + cfg := &Config{ + ListenAddr: env("EDGE_LISTEN_ADDR", ":13580"), + OutboxDir: env("EDGE_OUTBOX_DIR", "/var/lib/cch-edge/outbox"), + LogLevel: env("EDGE_LOG_LEVEL", "info"), + } + + controlURL, err := parseBaseURL("EDGE_CONTROL_URL", env("EDGE_CONTROL_URL", "")) + collect(err) + cfg.ControlURL = controlURL + delegateURL, err := parseBaseURL("EDGE_DELEGATE_URL", env("EDGE_DELEGATE_URL", env("EDGE_CONTROL_URL", ""))) + collect(err) + cfg.DelegateURL = delegateURL + + cfg.SharedSecret = env("EDGE_SHARED_SECRET", env("CCH_EDGE_SHARED_SECRET", "")) + if len(cfg.SharedSecret) < 32 { + collect(errors.New("EDGE_SHARED_SECRET must be at least 32 characters")) + } + + hostname, _ := os.Hostname() + cfg.EdgeID = env("EDGE_ID", hostname) + if cfg.EdgeID == "" { + cfg.EdgeID = "edge" + } + + cfg.OutboxMaxBytes, err = envInt64("EDGE_OUTBOX_MAX_BYTES", 1<<30) + collect(err) + cfg.MaxBodyBytes, err = envInt64("EDGE_MAX_BODY_BYTES", 100<<20) + collect(err) + cfg.MaxCompressedBodyBytes, err = envInt64("EDGE_MAX_COMPRESSED_BODY_BYTES", cfg.MaxBodyBytes) + collect(err) + depth, err := envInt64("EDGE_MAX_JSON_DEPTH", 512) + collect(err) + cfg.MaxJSONDepth = int(depth) + concurrency, err := envInt64("EDGE_MAX_CONCURRENCY", 1024) + collect(err) + cfg.MaxConcurrency = int(concurrency) + cfg.OverflowToDelegate, err = envBool("EDGE_OVERFLOW_TO_DELEGATE", true) + collect(err) + cfg.FallbackToDelegate, err = envBool("EDGE_FALLBACK_TO_DELEGATE", true) + collect(err) + + cfg.DecideTimeout, err = envDurationMs("EDGE_DECIDE_TIMEOUT_MS", 5*time.Second) + collect(err) + cfg.NextTimeout, err = envDurationMs("EDGE_NEXT_TIMEOUT_MS", 5*time.Second) + collect(err) + cfg.CompleteTimeout, err = envDurationMs("EDGE_COMPLETE_TIMEOUT_MS", 15*time.Second) + collect(err) + cfg.HeartbeatTimeout, err = envDurationMs("EDGE_HEARTBEAT_TIMEOUT_MS", 5*time.Second) + collect(err) + cfg.GatePrebufferBytes, err = envInt64("EDGE_GATE_GLOBAL_PREBUFFER_BYTES", 256<<20) + collect(err) + cfg.ShutdownGrace, err = envDurationMs("EDGE_SHUTDOWN_GRACE_MS", 120*time.Second) + collect(err) + + if raw := env("EDGE_TRUSTED_PROXY_CIDRS", ""); raw != "" { + for _, part := range strings.Split(raw, ",") { + if trimmed := strings.TrimSpace(part); trimmed != "" { + cfg.TrustedProxyCIDRs = append(cfg.TrustedProxyCIDRs, trimmed) + } + } + } + + if cfg.MaxConcurrency < 1 { + collect(errors.New("EDGE_MAX_CONCURRENCY must be positive")) + } + if len(errs) > 0 { + return nil, errors.Join(errs...) + } + return cfg, nil +} diff --git a/edge/internal/contract/contract_test.go b/edge/internal/contract/contract_test.go new file mode 100644 index 0000000000..bd9a935afd --- /dev/null +++ b/edge/internal/contract/contract_test.go @@ -0,0 +1,145 @@ +package contract + +import ( + "bytes" + "encoding/json" + "os" + "path/filepath" + "testing" +) + +const fixtureDir = "../../../tests/fixtures/edge/contract" + +func int64Ptr(value int64) *int64 { return &value } +func strPtr(value string) *string { return &value } + +// goSamples are Go-produced payloads validated by the TS zod schemas +// (tests/unit/edge/contract-fixtures.test.ts). +func goSamples() map[string]any { + timing := &AttemptTiming{DispatchedAtMs: int64Ptr(1000), FirstByteAtMs: nil, EndedAtMs: 1500, HealthElapsedMs: 500} + failures := []AttemptFailure{ + {Kind: "upstream_status", Status: 529, StatusText: "Overloaded", Headers: []HeaderPair{{"content-type", "application/json"}}, BodyText: `{"error":{}}`}, + {Kind: "transport", Code: "ECONNRESET", Message: "reset"}, + {Kind: "timeout", TimeoutType: "streaming_first_byte"}, + {Kind: "gate", Reason: "gate_error", FrameData: "{}", InferenceText: "{}", FramesSeen: 1, BufferedBytes: 10}, + {Kind: "empty_response", Reason: "empty_body"}, + {Kind: "client_abort"}, + {Kind: "local_capacity", Message: "full"}, + {Kind: "invalid_step", Message: "unknown op"}, + } + samples := map[string]any{} + for _, failure := range failures { + copied := failure + samples["next_failure_"+failure.Kind] = NextRequest{ + RequestID: 1, EdgeToken: "tttttttttttttttttttttttttttttttt", StepID: "1:1:1", + Event: NextEvent{Type: "failure", Failure: &copied, Dispatched: true, Timing: timing, + OpResults: &OpResults{BillingHeader: &BillingHeaderResult{RemovedCount: 1, ExtractedValues: []string{"x"}}}}, + } + } + samples["next_failure_client_abort_peers"] = NextRequest{RequestID: 1, EdgeToken: "tttttttttttttttttttttttttttttttt", StepID: "1:h1:1", + Event: NextEvent{Type: "failure", Failure: &AttemptFailure{Kind: "client_abort"}, Dispatched: true, Timing: timing, + Peers: []PeerTiming{{StepID: "1:h2:1", Dispatched: true, HealthElapsedMs: 40}}}} + samples["next_hedge_threshold"] = NextRequest{RequestID: 1, EdgeToken: "tttttttttttttttttttttttttttttttt", StepID: "1:1:1", + Event: NextEvent{Type: "hedge_threshold", OpResults: &OpResults{}}} + samples["next_rectifier_not_applicable"] = NextRequest{RequestID: 1, EdgeToken: "tttttttttttttttttttttttttttttttt", StepID: "1:1:2", + Event: NextEvent{Type: "rectifier_not_applicable", OpResults: &OpResults{ThinkingSignature: &ThinkingSignatureResult{}}}} + samples["next_suspect_2xx"] = NextRequest{RequestID: 1, EdgeToken: "tttttttttttttttttttttttttttttttt", StepID: "1:1:1", + Event: NextEvent{Type: "suspect_2xx", Status: 200, BodyText: ""}} + samples["complete"] = CompleteRequest{ + RequestID: 1, EdgeToken: "tttttttttttttttttttttttttttttttt", + Winner: WinnerResult{ + StepID: "1:1:1", UpstreamStatus: 200, ResponseHeaders: []HeaderPair{{"content-type", "text/event-stream"}}, + IsStreaming: true, StreamEndedNormally: true, AbortReason: nil, FirstByteSeen: true, + SSEEventCount: 3, CompactSSE: "event: message_stop\ndata: {}\n\n", + Protocol: &ProtocolObservation{SawContent: true, SawTerminal: true, Failure: &ProtocolFailure{Verdict: "error", EventName: strPtr("error")}}, + GateCommit: &GateCommit{FrameIndex: 2, ChunkIndex: 1, EventName: strPtr("content_block_delta"), BufferedBytes: 10, GateWaitMs: 3}, + Fixer: &FixerAudit{Hit: true, FixersApplied: []FixerApplied{{Fixer: "sse", Applied: true}}}, + Timing: WinnerTiming{DispatchedAtMs: 1000, FirstByteAtMs: int64Ptr(1100), FirstTokenAtMs: int64Ptr(1200), EndedAtMs: 2000, HealthElapsedMs: 1000}, + BytesToClient: 42, + }, + Losers: []LoserResult{}, + } + samples["complete_non_stream"] = CompleteRequest{ + RequestID: 2, EdgeToken: "tttttttttttttttttttttttttttttttt", + Winner: WinnerResult{ + StepID: "2:1:1", UpstreamStatus: 200, ResponseHeaders: []HeaderPair{}, + NonStreamBody: &NonStreamBody{Text: `{"usage":{}}`}, AbortReason: strPtr(AbortClientAborted), + Timing: WinnerTiming{DispatchedAtMs: 1, EndedAtMs: 2}, + }, + Losers: []LoserResult{{StepID: "2:2:1", UpstreamStatus: 200, MeteringText: "", EndedAtMs: 3}}, + } + samples["heartbeat"] = HeartbeatRequest{RequestID: 1, EdgeToken: "tttttttttttttttttttttttttttttttt", BytesForwarded: 10} + return samples +} + +func TestGoSamplesFixtureIsCurrent(t *testing.T) { + encoded, err := json.MarshalIndent(goSamples(), "", " ") + if err != nil { + t.Fatal(err) + } + encoded = append(encoded, '\n') + path := filepath.Join(fixtureDir, "go-samples.json") + if os.Getenv("UPDATE_FIXTURES") != "" { + if err := os.MkdirAll(fixtureDir, 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(path, encoded, 0o644); err != nil { + t.Fatal(err) + } + } + current, err := os.ReadFile(path) + if err != nil { + t.Fatalf("missing %s (run with UPDATE_FIXTURES=1): %v", path, err) + } + var want, got any + _ = json.Unmarshal(current, &want) + _ = json.Unmarshal(encoded, &got) + wantJSON, _ := json.Marshal(want) + gotJSON, _ := json.Marshal(got) + if !bytes.Equal(wantJSON, gotJSON) { + t.Fatalf("%s is stale; run with UPDATE_FIXTURES=1", path) + } +} + +func strictDecode(t *testing.T, raw json.RawMessage, target any) { + t.Helper() + decoder := json.NewDecoder(bytes.NewReader(raw)) + decoder.DisallowUnknownFields() + if err := decoder.Decode(target); err != nil { + t.Fatalf("decode %T: %v\n%s", target, err, raw) + } +} + +// TS-produced responses (written by tests/unit/edge/contract-fixtures.test.ts) +// must decode into the Go types without unknown fields. +func TestTSSamplesDecodeStrictly(t *testing.T) { + data, err := os.ReadFile(filepath.Join(fixtureDir, "ts-samples.json")) + if err != nil { + t.Skipf("ts-samples.json not generated yet: %v", err) + } + var samples map[string]json.RawMessage + if err := json.Unmarshal(data, &samples); err != nil { + t.Fatal(err) + } + if len(samples) == 0 { + t.Fatal("no TS samples") + } + for name, raw := range samples { + switch { + case len(name) >= 6 && name[:6] == "digest": + var digest RequestDigest + strictDecode(t, raw, &digest) + case len(name) >= 6 && name[:6] == "decide": + var response DecideResponse + strictDecode(t, raw, &response) + if response.Action == "execute" && (response.Step == nil || response.Step.StepID == "") { + t.Fatalf("%s: execute without step", name) + } + case len(name) >= 4 && name[:4] == "next": + var response NextResponse + strictDecode(t, raw, &response) + default: + t.Fatalf("unknown TS sample %s", name) + } + } +} diff --git a/edge/internal/contract/types.go b/edge/internal/contract/types.go new file mode 100644 index 0000000000..16ea620f77 --- /dev/null +++ b/edge/internal/contract/types.go @@ -0,0 +1,463 @@ +// Package contract mirrors the TypeScript wire contract in +// src/app/v1/_lib/edge/contract.ts (schemaVersion 1). Field names and JSON tags +// must stay identical to the TS zod schemas. +package contract + +import "encoding/json" + +const SchemaVersion = 1 + +// HeaderPair is a [name, value] tuple; names are lowercase and duplicates are kept. +type HeaderPair [2]string + +// FingerprintBoundary mirrors FingerprintBoundary in affinity/fingerprint.ts. +type FingerprintBoundary struct { + Depth int `json:"depth"` + FP string `json:"fp"` + PrefixBytes int `json:"prefixBytes"` + HasCacheControl bool `json:"hasCacheControl,omitempty"` +} + +// FingerprintChain mirrors FingerprintChain (sys + tail, tail is shallow -> deep). +type FingerprintChain struct { + Sys FingerprintBoundary `json:"sys"` + Tail []FingerprintBoundary `json:"tail"` +} + +// RequestDigest is the body of POST /api/internal/edge/decide. +type RequestDigest struct { + SchemaVersion int `json:"schemaVersion"` + EdgeID string `json:"edgeId"` + EdgeRequestID string `json:"edgeRequestId"` + ReceivedAtMs int64 `json:"receivedAtMs"` + Method string `json:"method"` + Path string `json:"path"` + Headers []HeaderPair `json:"headers"` + ClientIP *string `json:"clientIp"` + BodyBytes int `json:"bodyBytes"` + BodyParseError *string `json:"bodyParseError"` + // Format is the client format resolved from the normalized path: + // claude | response | openai (unsupported paths resolve to claude). + Format string `json:"format"` + TopLevel map[string]json.RawMessage `json:"topLevel"` + // MessagesCount / InputCount are the lengths of messages / input when they + // are arrays (response format: after input normalization), otherwise null. + MessagesCount *int `json:"messagesCount"` + InputCount *int `json:"inputCount"` + ResponseInputRectify *ResponseInputRectify `json:"responseInputRectify"` + IsRemoteCompactionV2 bool `json:"isRemoteCompactionV2"` + CodexInitialTextHash *string `json:"codexInitialTextHash"` + SystemKind string `json:"systemKind"` + HasPrivateParams bool `json:"hasPrivateParams"` + IsProbe bool `json:"isProbe"` + IsWarmup bool `json:"isWarmup"` + MessagesHash *string `json:"messagesHash"` + Fingerprint *FingerprintChain `json:"fingerprint"` +} + +// ResponseInputRectify mirrors rectifyResponseInput's result (response format only). +type ResponseInputRectify struct { + Action string `json:"action"` + OriginalType string `json:"originalType"` +} + +// Client formats (RequestDigest.Format / ExecutionStep.ClientFormat). +const ( + FormatClaude = "claude" + FormatResponse = "response" + FormatOpenAI = "openai" +) + +// TopLevelKeys are the body fields carried in RequestDigest.TopLevel +// (EDGE_TOP_LEVEL_KEYS in contract.ts, same order). +var TopLevelKeys = []string{ + "model", "stream", "stream_options", "max_tokens", "thinking", "output_config", + "reasoning_effort", "reasoning", "text", "service_tier", "parallel_tool_calls", + "prompt_cache_key", "previous_response_id", "metadata", +} + +// BodyOp is a typed body transformation. Only the fields relevant to Op are set. +type BodyOp struct { + Op string `json:"op"` + Key string `json:"key,omitempty"` + Value json.RawMessage `json:"value,omitempty"` + TTL string `json:"ttl,omitempty"` +} + +const ( + OpSetTopLevel = "set_top_level" + OpDeleteTopLevel = "delete_top_level" + OpRemoveSystemBillingHeader = "remove_system_billing_header" + OpSetCacheControlTTL = "set_cache_control_ttl" + OpApplyThinkingSignatureRectifier = "apply_thinking_signature_rectifier" + OpStripPrivateParams = "strip_private_params" + OpNormalizeResponseInput = "normalize_response_input" +) + +// MutableTopLevelKeys lists the keys set_top_level / delete_top_level may touch. +var MutableTopLevelKeys = map[string]bool{ + "model": true, "max_tokens": true, "thinking": true, + "output_config": true, "reasoning_effort": true, "reasoning": true, + "text": true, "service_tier": true, "parallel_tool_calls": true, + "prompt_cache_key": true, "stream_options": true, "metadata": true, +} + +type StepProvider struct { + ID int64 `json:"id"` + Name string `json:"name"` + Priority float64 `json:"priority"` + Type string `json:"type"` +} + +type StepEndpoint struct { + ID *int64 `json:"id"` + URL string `json:"url"` +} + +type StepTimeouts struct { + ConnectMs int64 `json:"connectMs"` + FirstByteMs int64 `json:"firstByteMs"` + NonStreamTotalMs int64 `json:"nonStreamTotalMs"` + IdleMs int64 `json:"idleMs"` + HeadersMs int64 `json:"headersMs"` + BodyMs int64 `json:"bodyMs"` +} + +type StepTransport struct { + ProxyURL *string `json:"proxyUrl"` + ProxyFallbackToDirect bool `json:"proxyFallbackToDirect"` + HTTP2 bool `json:"http2"` +} + +type StepGate struct { + Mode string `json:"mode"` // off | shadow | enforce + HighConcurrency bool `json:"highConcurrency"` + IdleMs int64 `json:"idleMs"` + EventCap int `json:"eventCap"` + ByteCap int `json:"byteCap"` + CaptureCommitMarker bool `json:"captureCommitMarker"` +} + +type StepFixer struct { + Enabled bool `json:"enabled"` + FixTruncatedJSON bool `json:"fixTruncatedJson"` + FixSseFormat bool `json:"fixSseFormat"` + FixEncoding bool `json:"fixEncoding"` + MaxJSONDepth int `json:"maxJsonDepth"` + MaxFixSize int `json:"maxFixSize"` +} + +type StepHedge struct { + ThresholdMs int64 `json:"thresholdMs"` + MaxInFlight int `json:"maxInFlight"` + BillLosers bool `json:"billLosers"` + LoserDrainMs int64 `json:"loserDrainMs"` +} + +type StepReporting struct { + MaxCompactBytes int `json:"maxCompactBytes"` + MaxHeadBytes int `json:"maxHeadBytes"` + MaxNonStreamBodyBytes int `json:"maxNonStreamBodyBytes"` + MaxErrorBodyBytes int `json:"maxErrorBodyBytes"` + HeartbeatIntervalMs int64 `json:"heartbeatIntervalMs"` +} + +// ExecutionStep describes exactly one upstream attempt. +type ExecutionStep struct { + StepID string `json:"stepId"` + AttemptNumber int `json:"attemptNumber"` + TotalProvidersAttempted int `json:"totalProvidersAttempted"` + AttemptKind string `json:"attemptKind"` + Provider StepProvider `json:"provider"` + // ClientFormat selects the gate protocol family, compact capture and metering rules. + ClientFormat string `json:"clientFormat"` + // ForceStreamHandling treats a 2xx non-SSE/HTML/JSON body as a stream (codex). + ForceStreamHandling bool `json:"forceStreamHandling"` + Endpoint StepEndpoint `json:"endpoint"` + Method string `json:"method"` + URL string `json:"url"` + Headers []HeaderPair `json:"headers"` + BodyOps []BodyOp `json:"bodyOps"` + DelayMs int64 `json:"delayMs"` + IsStreaming bool `json:"isStreaming"` + Timeouts StepTimeouts `json:"timeouts"` + Transport StepTransport `json:"transport"` + Gate StepGate `json:"gate"` + Fixer StepFixer `json:"fixer"` + Hedge *StepHedge `json:"hedge"` + Reporting StepReporting `json:"reporting"` + ClientAbortDrainMs int64 `json:"clientAbortDrainMs"` +} + +// FailResponse is relayed to the client verbatim. +type FailResponse struct { + Status int `json:"status"` + Headers []HeaderPair `json:"headers"` + BodyText string `json:"bodyText"` +} + +type BillingHeaderResult struct { + RemovedCount int `json:"removedCount"` + ExtractedValues []string `json:"extractedValues"` +} + +type ThinkingSignatureResult struct { + Applied bool `json:"applied"` + RemovedThinkingBlocks int `json:"removedThinkingBlocks"` + RemovedRedactedThinkingBlocks int `json:"removedRedactedThinkingBlocks"` + RemovedSignatureFields int `json:"removedSignatureFields"` + RemovedTopLevelThinking bool `json:"removedTopLevelThinking"` +} + +// OpResults reports the outcome of content body ops executed for an attempt. +type OpResults struct { + BillingHeader *BillingHeaderResult `json:"billingHeader,omitempty"` + ThinkingSignature *ThinkingSignatureResult `json:"thinkingSignature,omitempty"` +} + +type AttemptTiming struct { + DispatchedAtMs *int64 `json:"dispatchedAtMs"` + FirstByteAtMs *int64 `json:"firstByteAtMs"` + EndedAtMs int64 `json:"endedAtMs"` + HealthElapsedMs int64 `json:"healthElapsedMs"` +} + +// AttemptFailure is a discriminated union keyed by Kind. +type AttemptFailure struct { + Kind string `json:"kind"` + // upstream_status + Status int `json:"status,omitempty"` + StatusText string `json:"statusText,omitempty"` + Headers []HeaderPair `json:"headers,omitempty"` + BodyText string `json:"bodyText,omitempty"` + BodyTruncated bool `json:"bodyTruncated,omitempty"` + // transport + Code string `json:"code,omitempty"` + Message string `json:"message,omitempty"` + // timeout + TimeoutType string `json:"timeoutType,omitempty"` + // gate + Reason string `json:"reason,omitempty"` + FrameData string `json:"frameData,omitempty"` + InferenceText string `json:"inferenceText,omitempty"` + TerminalBeforeContent bool `json:"terminalBeforeContent,omitempty"` + FramesSeen int `json:"framesSeen,omitempty"` + BufferedBytes int `json:"bufferedBytes,omitempty"` + EchoExcludedBytes int `json:"echoExcludedBytes,omitempty"` +} + +// MarshalJSON emits only the fields that belong to the failure kind so the +// TS discriminated union validates (required fields present, no strays). +func (f AttemptFailure) MarshalJSON() ([]byte, error) { + out := map[string]any{"kind": f.Kind} + switch f.Kind { + case "upstream_status": + headers := f.Headers + if headers == nil { + headers = []HeaderPair{} + } + out["status"] = f.Status + out["statusText"] = f.StatusText + out["headers"] = headers + out["bodyText"] = f.BodyText + out["bodyTruncated"] = f.BodyTruncated + case "transport": + out["code"] = f.Code + out["message"] = f.Message + case "timeout": + out["timeoutType"] = f.TimeoutType + case "gate": + out["reason"] = f.Reason + out["frameData"] = f.FrameData + out["inferenceText"] = f.InferenceText + out["terminalBeforeContent"] = f.TerminalBeforeContent + out["framesSeen"] = f.FramesSeen + out["bufferedBytes"] = f.BufferedBytes + out["echoExcludedBytes"] = f.EchoExcludedBytes + case "empty_response": + out["reason"] = f.Reason + case "local_capacity", "invalid_step": + out["message"] = f.Message + } + return json.Marshal(out) +} + +// NextEvent is a discriminated union keyed by Type. +type NextEvent struct { + Type string `json:"type"` + // failure + Failure *AttemptFailure `json:"failure,omitempty"` + Dispatched bool `json:"dispatched"` + FirstByteSeen bool `json:"firstByteSeen"` + Timing *AttemptTiming `json:"timing,omitempty"` + // Peers carries the other in-flight hedge attempts on a pre-commit client abort. + Peers []PeerTiming `json:"peers,omitempty"` + // suspect_2xx + Status int `json:"status,omitempty"` + Headers []HeaderPair `json:"headers,omitempty"` + BodyText string `json:"bodyText,omitempty"` + BodyTruncated bool `json:"bodyTruncated,omitempty"` + OpResults *OpResults `json:"opResults,omitempty"` +} + +// MarshalJSON emits the fields of the event type only. +func (e NextEvent) MarshalJSON() ([]byte, error) { + out := map[string]any{"type": e.Type} + switch e.Type { + case "failure": + out["failure"] = e.Failure + out["dispatched"] = e.Dispatched + out["firstByteSeen"] = e.FirstByteSeen + out["timing"] = e.Timing + if len(e.Peers) > 0 { + out["peers"] = e.Peers + } + case "suspect_2xx": + headers := e.Headers + if headers == nil { + headers = []HeaderPair{} + } + out["status"] = e.Status + out["headers"] = headers + out["bodyText"] = e.BodyText + out["bodyTruncated"] = e.BodyTruncated + } + if e.OpResults != nil && e.Type != "hedge_threshold" { + out["opResults"] = e.OpResults + } + return json.Marshal(out) +} + +// PeerTiming is the dispatch/first-byte state of another in-flight hedge attempt. +type PeerTiming struct { + StepID string `json:"stepId"` + Dispatched bool `json:"dispatched"` + FirstByteSeen bool `json:"firstByteSeen"` + HealthElapsedMs int64 `json:"healthElapsedMs"` +} + +type NextRequest struct { + RequestID int64 `json:"requestId"` + EdgeToken string `json:"edgeToken"` + StepID string `json:"stepId"` + Event NextEvent `json:"event"` +} + +// NextResponse: action is retry | launch | wait | none | commit | fail | delegate. +type NextResponse struct { + Action string `json:"action"` + Step *ExecutionStep `json:"step,omitempty"` + Response *FailResponse `json:"response,omitempty"` + Reason string `json:"reason,omitempty"` +} + +// DecideResponse: action is delegate | fail | execute. +type DecideResponse struct { + Action string `json:"action"` + Reason string `json:"reason,omitempty"` + Response *FailResponse `json:"response,omitempty"` + RequestID int64 `json:"requestId,omitempty"` + EdgeToken string `json:"edgeToken,omitempty"` + Step *ExecutionStep `json:"step,omitempty"` +} + +type ProtocolFailure struct { + Verdict string `json:"verdict"` + EventName *string `json:"eventName"` + AfterContent bool `json:"afterContent"` + SawMalformed bool `json:"sawMalformed"` +} + +type ProtocolObservation struct { + SawContent bool `json:"sawContent"` + SawTerminal bool `json:"sawTerminal"` + SawIncomplete bool `json:"sawIncomplete"` + ObservationIncomplete bool `json:"observationIncomplete"` + Failure *ProtocolFailure `json:"failure"` +} + +type GateCommit struct { + FrameIndex int `json:"frameIndex"` + ChunkIndex int `json:"chunkIndex"` + EventName *string `json:"eventName"` + BufferedBytes int `json:"bufferedBytes"` + EchoExcludedBytes int `json:"echoExcludedBytes"` + GateWaitMs int64 `json:"gateWaitMs"` +} + +type FixerApplied struct { + Fixer string `json:"fixer"` + Applied bool `json:"applied"` + Details *string `json:"details,omitempty"` +} + +type FixerAudit struct { + Hit bool `json:"hit"` + FixersApplied []FixerApplied `json:"fixersApplied"` + TotalBytesProcessed int64 `json:"totalBytesProcessed"` + ProcessingTimeMs int64 `json:"processingTimeMs"` +} + +type NonStreamBody struct { + Text string `json:"text"` + Truncated bool `json:"truncated"` +} + +type WinnerTiming struct { + DispatchedAtMs int64 `json:"dispatchedAtMs"` + FirstByteAtMs *int64 `json:"firstByteAtMs"` + FirstTokenAtMs *int64 `json:"firstTokenAtMs"` + EndedAtMs int64 `json:"endedAtMs"` + HealthElapsedMs int64 `json:"healthElapsedMs"` +} + +type WinnerResult struct { + StepID string `json:"stepId"` + UpstreamStatus int `json:"upstreamStatus"` + ResponseHeaders []HeaderPair `json:"responseHeaders"` + IsStreaming bool `json:"isStreaming"` + StreamEndedNormally bool `json:"streamEndedNormally"` + ClientAborted bool `json:"clientAborted"` + AbortReason *string `json:"abortReason"` + FirstByteSeen bool `json:"firstByteSeen"` + SSEEventCount int `json:"sseEventCount"` + CompactSSE string `json:"compactSse"` + CompactTruncated bool `json:"compactTruncated"` + NonStreamBody *NonStreamBody `json:"nonStreamBody"` + Protocol *ProtocolObservation `json:"protocol"` + GateCommit *GateCommit `json:"gateCommit"` + Fixer *FixerAudit `json:"fixer"` + Timing WinnerTiming `json:"timing"` + BytesToClient int64 `json:"bytesToClient"` + OpResults *OpResults `json:"opResults,omitempty"` +} + +type LoserResult struct { + StepID string `json:"stepId"` + UpstreamStatus int `json:"upstreamStatus"` + DrainComplete bool `json:"drainComplete"` + MeteringText string `json:"meteringText"` + EndedAtMs int64 `json:"endedAtMs"` +} + +type CompleteRequest struct { + RequestID int64 `json:"requestId"` + EdgeToken string `json:"edgeToken"` + Winner WinnerResult `json:"winner"` + Losers []LoserResult `json:"losers"` +} + +type HeartbeatRequest struct { + RequestID int64 `json:"requestId"` + EdgeToken string `json:"edgeToken"` + BytesForwarded int64 `json:"bytesForwarded"` +} + +// Abort reasons reported in WinnerResult.AbortReason. +const ( + AbortStreamResponseTimeout = "STREAM_RESPONSE_TIMEOUT" + AbortStreamIdleTimeout = "STREAM_IDLE_TIMEOUT" + AbortClientAborted = "CLIENT_ABORTED" + AbortStreamUpstreamAborted = "STREAM_UPSTREAM_ABORTED" + AbortStreamProcessingError = "STREAM_PROCESSING_ERROR" +) diff --git a/edge/internal/control/client.go b/edge/internal/control/client.go new file mode 100644 index 0000000000..53d0a6e94e --- /dev/null +++ b/edge/internal/control/client.go @@ -0,0 +1,178 @@ +// Package control is the HTTP client for the control plane's /api/internal/edge API. +package control + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "time" + + "github.com/ding113/claude-code-hub/edge/internal/contract" +) + +const ( + SecretHeader = "x-cch-edge-secret" + EdgeIDHeader = "x-cch-edge-id" + maxResponse = 8 << 20 +) + +// APIError is a non-2xx response from the control plane. +type APIError struct { + Status int + Code string + Body string +} + +func (e *APIError) Error() string { + return fmt.Sprintf("control plane returned %d (%s)", e.Status, e.Code) +} + +// IsStale reports control-plane rejections that mean the request state has moved on +// (already settled, stale step, unknown request). Retrying them is pointless. +func IsStale(err error) bool { + var apiErr *APIError + if !errors.As(err, &apiErr) { + return false + } + return apiErr.Status == http.StatusConflict || apiErr.Status == http.StatusNotFound || + apiErr.Status == http.StatusForbidden +} + +type Client struct { + base *url.URL + secret string + edgeID string + http *http.Client + decideTimeout time.Duration + nextTimeout time.Duration + completeTimeout time.Duration + heartbeatTimeout time.Duration +} + +type Options struct { + BaseURL *url.URL + Secret string + EdgeID string + HTTPClient *http.Client + DecideTimeout time.Duration + NextTimeout time.Duration + CompleteTimeout time.Duration + HeartbeatTimeout time.Duration +} + +func New(opts Options) *Client { + httpClient := opts.HTTPClient + if httpClient == nil { + transport := http.DefaultTransport.(*http.Transport).Clone() + transport.MaxIdleConnsPerHost = 64 + httpClient = &http.Client{Transport: transport} + } + return &Client{ + base: opts.BaseURL, + secret: opts.Secret, + edgeID: opts.EdgeID, + http: httpClient, + decideTimeout: opts.DecideTimeout, + nextTimeout: opts.NextTimeout, + completeTimeout: opts.CompleteTimeout, + heartbeatTimeout: opts.HeartbeatTimeout, + } +} + +func (c *Client) post(ctx context.Context, path string, timeout time.Duration, in, out any) error { + body, err := json.Marshal(in) + if err != nil { + return err + } + if timeout > 0 { + var cancel context.CancelFunc + ctx, cancel = context.WithTimeout(ctx, timeout) + defer cancel() + } + endpoint := c.base.JoinPath("/api/internal/edge", path) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint.String(), bytes.NewReader(body)) + if err != nil { + return err + } + req.Header.Set("content-type", "application/json") + req.Header.Set(SecretHeader, c.secret) + req.Header.Set(EdgeIDHeader, c.edgeID) + resp, err := c.http.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + data, err := io.ReadAll(io.LimitReader(resp.Body, maxResponse)) + if err != nil { + return err + } + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + apiErr := &APIError{Status: resp.StatusCode, Body: string(data)} + var envelope struct { + Error struct { + Code string `json:"code"` + } `json:"error"` + } + if json.Unmarshal(data, &envelope) == nil { + apiErr.Code = envelope.Error.Code + } + return apiErr + } + if out == nil { + return nil + } + return json.Unmarshal(data, out) +} + +func (c *Client) Decide(ctx context.Context, digest *contract.RequestDigest) (*contract.DecideResponse, error) { + var out contract.DecideResponse + if err := c.post(ctx, "decide", c.decideTimeout, digest, &out); err != nil { + return nil, err + } + return &out, nil +} + +func (c *Client) Next(ctx context.Context, request *contract.NextRequest) (*contract.NextResponse, error) { + var out contract.NextResponse + if err := c.post(ctx, "next", c.nextTimeout, request, &out); err != nil { + return nil, err + } + return &out, nil +} + +func (c *Client) Complete(ctx context.Context, request *contract.CompleteRequest) error { + return c.post(ctx, "complete", c.completeTimeout, request, nil) +} + +// CompleteRaw posts an already serialized completion report (outbox replay). +func (c *Client) CompleteRaw(ctx context.Context, body json.RawMessage) error { + return c.post(ctx, "complete", c.completeTimeout, body, nil) +} + +func (c *Client) Heartbeat(ctx context.Context, request *contract.HeartbeatRequest) error { + return c.post(ctx, "heartbeat", c.heartbeatTimeout, request, nil) +} + +// Health probes the control plane (used for readiness). +func (c *Client) Health(ctx context.Context) error { + endpoint := c.base.JoinPath("/api/internal/edge/health") + req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint.String(), nil) + if err != nil { + return err + } + req.Header.Set(SecretHeader, c.secret) + resp, err := c.http.Do(req) + if err != nil { + return err + } + _ = resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return &APIError{Status: resp.StatusCode} + } + return nil +} diff --git a/edge/internal/delegate/delegate.go b/edge/internal/delegate/delegate.go new file mode 100644 index 0000000000..86e770f443 --- /dev/null +++ b/edge/internal/delegate/delegate.go @@ -0,0 +1,91 @@ +// Package delegate reverse-proxies requests the edge does not execute to the +// control plane's regular /v1 proxy, streaming the response back untouched. +package delegate + +import ( + "bytes" + "encoding/json" + "io" + "log/slog" + "net/http" + "net/http/httputil" + "net/url" + "strconv" + "strings" + "time" +) + +// ReasonHeader tells operators (and tests) why the edge delegated a request. +const ReasonHeader = "x-cch-edge-delegated" + +type Delegator struct { + proxy *httputil.ReverseProxy +} + +func New(target *url.URL, logger *slog.Logger) *Delegator { + transport := http.DefaultTransport.(*http.Transport).Clone() + transport.MaxIdleConnsPerHost = 128 + transport.DisableCompression = true + transport.ResponseHeaderTimeout = 0 + proxy := &httputil.ReverseProxy{ + Rewrite: func(pr *httputil.ProxyRequest) { + pr.SetURL(target) + pr.Out.Host = target.Host + // Rewrite has already removed inbound Forwarded / X-Forwarded-* headers; + // the control plane sees the client IP observed by the edge. + pr.SetXForwarded() + if ip := clientIP(pr.In); ip != "" { + pr.Out.Header.Set("X-Real-Ip", ip) + } + }, + Transport: transport, + FlushInterval: -1, + ErrorHandler: func(w http.ResponseWriter, r *http.Request, err error) { + logger.Warn("delegate upstream failed", "path", r.URL.Path, "error", err.Error()) + WriteError(w, http.StatusBadGateway, "edge delegate unavailable", "bad_gateway_error") + }, + } + return &Delegator{proxy: proxy} +} + +// Serve proxies r. When body is non-nil it replaces the (already consumed) +// inbound body; the original Content-Encoding header is kept so the control plane +// decodes exactly what the client sent. +func (d *Delegator) Serve(w http.ResponseWriter, r *http.Request, body []byte, reason string) { + if body != nil { + r.Body = io.NopCloser(bytes.NewReader(body)) + r.ContentLength = int64(len(body)) + r.Header.Set("Content-Length", strconv.Itoa(len(body))) + r.GetBody = func() (io.ReadCloser, error) { return io.NopCloser(bytes.NewReader(body)), nil } + } + if reason != "" { + w.Header().Set(ReasonHeader, reason) + } + d.proxy.ServeHTTP(w, r) +} + +func clientIP(r *http.Request) string { + host := r.RemoteAddr + if index := strings.LastIndexByte(host, ':'); index >= 0 && !strings.HasSuffix(host, "]") { + host = host[:index] + } + return strings.Trim(host, "[]") +} + +// WriteError writes the CCH proxy error envelope ({"error":{"message","type","code"}}). +func WriteError(w http.ResponseWriter, status int, message, errorType string) { + type body struct { + Message string `json:"message"` + Type string `json:"type"` + Code string `json:"code"` + } + payload, _ := json.Marshal(struct { + Error body `json:"error"` + }{Error: body{Message: message, Type: errorType, Code: errorType}}) + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(status) + _, _ = w.Write(payload) +} + +// Timeout used by callers when they buffer a delegated body. +const DefaultTimeout = 10 * time.Minute diff --git a/edge/internal/detect/detect.go b/edge/internal/detect/detect.go new file mode 100644 index 0000000000..327e8a7fd4 --- /dev/null +++ b/edge/internal/detect/detect.go @@ -0,0 +1,177 @@ +// Package detect ports the non-streaming subset of +// src/lib/utils/upstream-error-detection.ts detectUpstreamErrorFromSseOrJsonText +// (as used from src/app/v1/_lib/proxy/forwarder.ts's non-stream 2xx branch, +// called with maxJsonCharsForMessageCheck: 0, i.e. the weak "message contains +// 'error'" signal is never triggered) plus the forwarder's non-stream empty +// body / missing-content checks used when Content-Length is absent. +package detect + +import ( + "strings" + + "github.com/ding113/claude-code-hub/edge/internal/ojson" +) + +const htmlDocSniffMaxChars = 1024 + +// SuspectNonStream reports whether body looks like an upstream "fake 200": +// an HTML error document, an empty body, or a JSON object carrying a +// non-empty `error` field or an OpenAI Responses `response.failed` / +// `status: "failed"` payload. This is a superset of the TS "strong signal" +// codes (FAKE_200_EMPTY_BODY, FAKE_200_HTML_BODY, FAKE_200_JSON_ERROR_NON_EMPTY, +// FAKE_200_JSON_ERROR_MESSAGE_NON_EMPTY, FAKE_200_OPENAI_RESPONSE_FAILED); the +// control plane re-verifies with the full TS logic before acting on it. +func SuspectNonStream(body []byte, contentType string) bool { + trimmed := strings.TrimSpace(string(body)) + if trimmed == "" { + return true + } + trimmed = stripBOM(trimmed) + + if isLikelyHTMLDocument(trimmed) { + return true + } + + if strings.HasPrefix(trimmed, "{") { + v, err := ojson.Parse([]byte(trimmed), ojson.DefaultMaxDepth) + if err != nil || !v.IsObject() { + return false + } + return detectFromJSONObject(v) + } + + return false +} + +// EmptyReason ports the forwarder's non-stream empty-response checks used +// when Content-Length is absent or invalid: +// - empty/whitespace body -> "empty_body" +// - Claude `{"type":"message","content":[]}` -> "missing_content" +// - OpenAI `{"choices":[]}` (choices present but empty/non-array) -> "missing_content" +// +// Returns "" when none of the checks trigger (including malformed JSON, +// which the TS code silently tolerates by skipping the content check). +func EmptyReason(body []byte) string { + trimmed := strings.TrimSpace(string(body)) + if trimmed == "" { + return "empty_body" + } + + v, err := ojson.Parse([]byte(trimmed), ojson.DefaultMaxDepth) + if err != nil || !v.IsObject() { + return "" + } + + if typ, ok := v.ObjectGet("type"); ok && typ.IsString() && typ.String() == "message" { + content, ok := v.ObjectGet("content") + if !ok || !content.IsArray() || content.ArrayLen() == 0 { + return "missing_content" + } + } + + if choices, ok := v.ObjectGet("choices"); ok { + if !choices.IsArray() || choices.ArrayLen() == 0 { + return "missing_content" + } + } + + return "" +} + +var bomRune = string(rune(0xFEFF)) + +func stripBOM(s string) string { + if strings.HasPrefix(s, bomRune) { + return strings.TrimLeft(s[len(bomRune):], " \t\n\r\v\f") + } + return s +} + +func isLikelyHTMLDocument(trimmed string) bool { + if !strings.HasPrefix(trimmed, "<") { + return false + } + head := trimmed + if len(head) > htmlDocSniffMaxChars { + head = head[:htmlDocSniffMaxChars] + } + lower := strings.ToLower(head) + if strings.HasPrefix(lower, "' { + return true + } + } + if strings.HasPrefix(lower, "' { + return true + } + } + return false +} + +func detectFromJSONObject(obj *ojson.Value) bool { + if detectOpenAIResponsesFailed(obj) { + return true + } + + errVal, ok := obj.ObjectGet("error") + if ok && hasNonEmptyValue(errVal) { + return true + } + + // maxJsonCharsForMessageCheck is always 0 for the non-stream detector + // use in forwarder.ts, so the weak `message` keyword signal never fires. + return false +} + +func detectOpenAIResponsesFailed(obj *ojson.Value) bool { + eventType := "" + if t, ok := obj.ObjectGet("type"); ok && t.IsString() { + eventType = strings.TrimSpace(t.String()) + } + response := obj + if r, ok := obj.ObjectGet("response"); ok && r.IsObject() { + response = r + } + responseStatus := "" + if s, ok := response.ObjectGet("status"); ok && s.IsString() { + responseStatus = strings.TrimSpace(s.String()) + } + responseObject := "" + if o, ok := response.ObjectGet("object"); ok && o.IsString() { + responseObject = strings.TrimSpace(o.String()) + } + responseID := "" + if id, ok := response.ObjectGet("id"); ok && id.IsString() { + responseID = strings.TrimSpace(id.String()) + } + + looksLikeOpenAIResponse := strings.HasPrefix(eventType, "response.") || + responseObject == "response" || strings.HasPrefix(responseID, "resp_") + isFailedResponse := eventType == "response.failed" || responseStatus == "failed" + + return looksLikeOpenAIResponse && isFailedResponse +} + +func hasNonEmptyValue(v *ojson.Value) bool { + if v == nil || v.IsNull() { + return false + } + switch { + case v.IsString(): + return strings.TrimSpace(v.String()) != "" + case v.IsNumber(): + f, err := v.Float64() + return err == nil && f != 0 + case v.IsBool(): + return v.Bool() + case v.IsArray(): + return v.ArrayLen() > 0 + case v.IsObject(): + return v.ObjectLen() > 0 + default: + return true + } +} diff --git a/edge/internal/detect/detect_test.go b/edge/internal/detect/detect_test.go new file mode 100644 index 0000000000..28396e8ea1 --- /dev/null +++ b/edge/internal/detect/detect_test.go @@ -0,0 +1,66 @@ +package detect + +import ( + "encoding/json" + "os" + "testing" +) + +const fixtureDir = "../../../tests/fixtures/edge/body" + +type detectCase struct { + Name string `json:"name"` + BodyText string `json:"bodyText"` + Expected struct { + IsError bool `json:"isError"` + Code string `json:"code"` + } `json:"expected"` +} + +func TestSuspectNonStreamFixtures(t *testing.T) { + data, err := os.ReadFile(fixtureDir + "/detect.json") + if err != nil { + t.Fatalf("read fixture: %v", err) + } + var cases []detectCase + if err := json.Unmarshal(data, &cases); err != nil { + t.Fatalf("unmarshal fixture: %v", err) + } + if len(cases) == 0 { + t.Fatal("no fixture cases loaded") + } + + for _, c := range cases { + t.Run(c.Name, func(t *testing.T) { + got := SuspectNonStream([]byte(c.BodyText), "application/json") + if got != c.Expected.IsError { + t.Fatalf("SuspectNonStream(%q) = %v, want %v (ts code=%q)", c.BodyText, got, c.Expected.IsError, c.Expected.Code) + } + }) + } +} + +func TestEmptyReason(t *testing.T) { + cases := []struct { + name string + body string + want string + }{ + {"empty", "", "empty_body"}, + {"whitespace", " \n", "empty_body"}, + {"claude_missing_content", `{"type":"message","content":[]}`, "missing_content"}, + {"claude_has_content", `{"type":"message","content":[{"type":"text","text":"hi"}]}`, ""}, + {"openai_missing_choices", `{"choices":[]}`, "missing_content"}, + {"openai_has_choices", `{"choices":[{"index":0}]}`, ""}, + {"malformed_json", "{not json", ""}, + {"unrelated_json", `{"foo":"bar"}`, ""}, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + got := EmptyReason([]byte(c.body)) + if got != c.want { + t.Fatalf("EmptyReason(%q) = %q, want %q", c.body, got, c.want) + } + }) + } +} diff --git a/edge/internal/digest/digest.go b/edge/internal/digest/digest.go new file mode 100644 index 0000000000..1eb3041199 --- /dev/null +++ b/edge/internal/digest/digest.go @@ -0,0 +1,522 @@ +// Package digest builds contract.RequestDigest from a parsed request body, +// mirroring src/app/v1/_lib/edge/digest.ts buildRequestDigest (which in turn +// calls into response-input-rectifier.ts#rectifyResponseInput, +// ProxySession.getMessages/isProbeRequest/isWarmupRequest, +// SessionManager.calculateMessagesHash, remote-compaction.ts#isRemoteCompactionV2Request, +// codex/session-completer.ts#extractInitialMessageTextHash and computeFingerprintChain). +package digest + +import ( + "crypto/sha256" + "encoding/hex" + "encoding/json" + "net/url" + "strings" + "unicode" + + "github.com/ding113/claude-code-hub/edge/internal/contract" + "github.com/ding113/claude-code-hub/edge/internal/ojson" +) + +// Input carries everything Build needs to compute a RequestDigest. +type Input struct { + EdgeID string + EdgeRequestID string + ReceivedAtMs int64 + Method string + Path string + Headers []contract.HeaderPair + ClientIP *string + Body *ojson.Value // parsed body; may be nil if BodyParseError is set + BodyBytes int + BodyParseError *string +} + +// Build computes the RequestDigest for in, matching buildRequestDigest in +// src/app/v1/_lib/edge/digest.ts field for field. +func Build(in Input) contract.RequestDigest { + originalBody := in.Body + if originalBody == nil || !originalBody.IsObject() { + // Mirrors the TS caller always passing a parsed object; when parsing + // failed upstream we degrade to an empty object like TS would. + originalBody = ojson.NewObject() + } + + format := resolveEdgeClientFormat(in.Path) + + // Response Input Rectifier runs before the guard pipeline: content-derived + // digest fields are computed on a normalized clone of the body, matching + // digest.ts's `const body = structuredClone(params.body)` + conditional rectify. + normalized := originalBody.Clone() + var responseInputRectify *contract.ResponseInputRectify + if format == contract.FormatResponse { + result := rectifyResponseInput(normalized) + responseInputRectify = &contract.ResponseInputRectify{ + Action: result.action, + OriginalType: result.originalType, + } + } + + pn := pathname(in.Path) + messages := getMessagesLike(normalized) + + var codexInitialTextHash *string + if inputVal, ok := normalized.ObjectGet("input"); ok && inputVal.IsArray() { + codexInitialTextHash = extractInitialMessageTextHash(normalized) + } + + return contract.RequestDigest{ + SchemaVersion: contract.SchemaVersion, + EdgeID: in.EdgeID, + EdgeRequestID: in.EdgeRequestID, + ReceivedAtMs: in.ReceivedAtMs, + Method: in.Method, + Path: in.Path, + Format: format, + Headers: in.Headers, + ClientIP: in.ClientIP, + BodyBytes: in.BodyBytes, + BodyParseError: in.BodyParseError, + TopLevel: buildTopLevel(normalized), + MessagesCount: arrayLenPtr(normalized, "messages"), + InputCount: arrayLenPtr(normalized, "input"), + ResponseInputRectify: responseInputRectify, + // isRemoteCompactionV2Request is called with the ORIGINAL, un-normalized + // body in digest.ts (params.body, not the clone), so use originalBody here. + IsRemoteCompactionV2: isRemoteCompactionV2Request(pn, originalBody), + CodexInitialTextHash: codexInitialTextHash, + SystemKind: resolveSystemKind(normalized), + HasPrivateParams: hasPrivateKeys(normalized), + IsProbe: isProbeRequest(messages), + IsWarmup: isWarmupRequest(normalized, in.Path), + MessagesHash: calculateMessagesHash(messages), + Fingerprint: ComputeFingerprintChain(normalized, format, MaxAffinityWindow), + } +} + +// resolveEdgeClientFormat ports resolveEdgeClientFormat in digest.ts: response/openai +// are kept, everything else (including unsupported paths) resolves to "claude". +func resolveEdgeClientFormat(rawPath string) string { + p := normalizeEndpointPath(pathname(rawPath)) + switch { + case p == "/v1/responses" || strings.HasPrefix(p, "/v1/responses/"): + return contract.FormatResponse + case p == "/v1/chat/completions" || strings.HasPrefix(p, "/v1/chat/completions/"): + return contract.FormatOpenAI + default: + return contract.FormatClaude + } +} + +func buildTopLevel(body *ojson.Value) map[string]json.RawMessage { + out := make(map[string]json.RawMessage) + for _, key := range contract.TopLevelKeys { + if v, ok := body.ObjectGet(key); ok { + out[key] = json.RawMessage(v.Marshal()) + } + } + return out +} + +func arrayLenPtr(body *ojson.Value, key string) *int { + if v, ok := body.ObjectGet(key); ok && v.IsArray() { + n := v.ArrayLen() + return &n + } + return nil +} + +func resolveSystemKind(body *ojson.Value) string { + v, ok := body.ObjectGet("system") + if !ok { + return "absent" + } + switch { + case v.IsString(): + return "string" + case v.IsArray(): + return "array" + default: + return "other" + } +} + +// hasPrivateKeys ports hasPrivateKeys(value) from digest.ts: recursively +// scans arrays and objects for any object key starting with "_". +func hasPrivateKeys(v *ojson.Value) bool { + if v == nil { + return false + } + switch { + case v.IsArray(): + for _, item := range v.ArrayItems() { + if hasPrivateKeys(item) { + return true + } + } + return false + case v.IsObject(): + for _, k := range v.ObjectKeys() { + if strings.HasPrefix(k, "_") { + return true + } + child, _ := v.ObjectGet(k) + if hasPrivateKeys(child) { + return true + } + } + return false + default: + return false + } +} + +// getMessagesLike ports ProxySession.getMessages (session.ts ~L1100): messages, +// else input, else contents, else request.contents, else absent. Returns the +// raw value (which may not be an array); callers that need array semantics +// check IsArray() themselves, matching how isProbeRequest/calculateMessagesHash +// treat a present-but-non-array value as "not usable" rather than falling +// through to the next candidate. +func getMessagesLike(body *ojson.Value) *ojson.Value { + if v, ok := body.ObjectGet("messages"); ok { + return v + } + if v, ok := body.ObjectGet("input"); ok { + return v + } + if v, ok := body.ObjectGet("contents"); ok { + return v + } + if reqVal, ok := body.ObjectGet("request"); ok && reqVal.IsObject() { + if v, ok := reqVal.ObjectGet("contents"); ok { + return v + } + } + return nil +} + +// isProbeRequest ports ProxySession.isProbeRequest (session.ts ~L1656), applied +// to the result of getMessages() (not hardcoded to the "messages" field, so it +// also covers Response API "input" arrays). +func isProbeRequest(messages *ojson.Value) bool { + if !messages.IsArray() || messages.ArrayLen() != 1 { + return false + } + first := messages.ArrayGet(0) + if !first.IsObject() { + return false + } + content, ok := first.ObjectGet("content") + if !ok || !content.IsString() { + return false + } + trimmed := strings.ToLower(jsTrim(content.String())) + return trimmed == "foo" || trimmed == "count" +} + +// isWarmupRequest ports ProxySession.isWarmupRequest (session.ts ~L1688). Unlike +// isProbeRequest, the TS source reads `message.messages` directly rather than +// through getMessages(); this only matters when combined with the endpoint +// check ("/v1/messages"), which restricts the format to "claude" in practice. +func isWarmupRequest(body *ojson.Value, rawPath string) bool { + if normalizeEndpointPath(pathname(rawPath)) != "/v1/messages" { + return false + } + messages, ok := body.ObjectGet("messages") + if !ok || !messages.IsArray() || messages.ArrayLen() != 1 { + return false + } + first := messages.ArrayGet(0) + if !first.IsObject() { + return false + } + role, ok := first.ObjectGet("role") + if !ok || !role.IsString() || role.String() != "user" { + return false + } + content, ok := first.ObjectGet("content") + if !ok || !content.IsArray() || content.ArrayLen() != 1 { + return false + } + block := content.ArrayGet(0) + if !block.IsObject() { + return false + } + typ, ok := block.ObjectGet("type") + if !ok || !typ.IsString() || typ.String() != "text" { + return false + } + text := "" + if tv, ok := block.ObjectGet("text"); ok && tv.IsString() { + text = jsTrim(tv.String()) + } + if strings.ToLower(text) != "warmup" { + return false + } + cc, ok := block.ObjectGet("cache_control") + if !ok || !cc.IsObject() { + return false + } + ccType, ok := cc.ObjectGet("type") + return ok && ccType.IsString() && ccType.String() == "ephemeral" +} + +// pathname mirrors ProxySession.getEndpoint(): new URL(path, "http://edge.local").pathname. +func pathname(rawPath string) string { + u, err := url.Parse(rawPath) + if err != nil { + return "" + } + if u.Path == "" { + return "/" + } + return u.Path +} + +// normalizeEndpointPath ports normalizeEndpointPath (endpoint-paths.ts): strip +// query, strip one trailing slash, lowercase. +func normalizeEndpointPath(p string) string { + if idx := strings.IndexByte(p, '?'); idx >= 0 { + p = p[:idx] + } + if len(p) > 1 && strings.HasSuffix(p, "/") { + p = strings.TrimSuffix(p, "/") + } + return strings.ToLower(p) +} + +// jsTrim strips the same set of characters JS String.prototype.trim strips: +// Unicode whitespace plus the line terminators (LS/PS) and BOM. +func jsTrim(s string) string { + return strings.TrimFunc(s, isJSWhitespace) +} + +func isJSWhitespace(r rune) bool { + switch r { + case rune(0xFEFF), rune(0x2028), rune(0x2029): + return true + } + return unicode.IsSpace(r) +} + +// calculateMessagesHash ports SessionManager.calculateMessagesHash +// (session-manager.ts ~L827-893), applied to the result of getMessages() +// (probe.getMessages() in digest.ts, not hardcoded to the "messages" field). +func calculateMessagesHash(messages *ojson.Value) *string { + if !messages.IsArray() || messages.ArrayLen() == 0 { + return nil + } + + count := messages.ArrayLen() + if count > 3 { + count = 3 + } + + contents := make([]string, 0, count) + for i := 0; i < count; i++ { + msg := messages.ArrayGet(i) + if msg == nil || !msg.IsObject() { + continue + } + content, ok := msg.ObjectGet("content") + if !ok { + continue + } + if content.IsString() { + contents = append(contents, content.String()) + } else if content.IsArray() { + var sb strings.Builder + for _, item := range content.ArrayItems() { + if !item.IsObject() { + continue + } + typ, ok := item.ObjectGet("type") + if !ok || !typ.IsString() || typ.String() != "text" { + continue + } + if textVal, ok := item.ObjectGet("text"); ok { + sb.WriteString(jsStringOrEmpty(textVal)) + } + } + contents = append(contents, sb.String()) + } + } + + if len(contents) == 0 { + return nil + } + + combined := strings.Join(contents, "|") + sum := sha256.Sum256([]byte(combined)) + hash := hex.EncodeToString(sum[:])[:16] + return &hash +} + +// jsStringOrEmpty mirrors how the TS `.map((item) => item.text)` would +// coerce a non-string `text` field when joined into a string (Array.prototype.join +// stringifies each element; undefined/null become ""). +func jsStringOrEmpty(v *ojson.Value) string { + if v == nil || v.IsNull() { + return "" + } + if v.IsString() { + return v.String() + } + // Non-string text fields are not expected in valid payloads; TS would + // coerce via string concatenation. We fall back to the compact JSON form, + // which only matters for malformed inputs outside the ported contract. + return string(v.Marshal()) +} + +// ---- response-input-rectifier.ts#rectifyResponseInput ---- + +type responseInputRectifyResult struct { + action string + originalType string +} + +// rectifyResponseInput ports rectifyResponseInput (response-input-rectifier.ts). +// It mutates message.input in place, matching the TS "在 message 对象上原地修改" contract. +func rectifyResponseInput(message *ojson.Value) responseInputRectifyResult { + input, hasInput := message.ObjectGet("input") + + // Case 1: array -- passthrough. + if hasInput && input.IsArray() { + return responseInputRectifyResult{action: "passthrough", originalType: "array"} + } + + // Case 2: string. + if hasInput && input.IsString() { + if input.String() == "" { + message.ObjectSet("input", ojson.NewArray()) + return responseInputRectifyResult{action: "empty_string_to_empty_array", originalType: "string"} + } + + wrapped := ojson.NewArray() + item := ojson.NewObject() + item.ObjectSet("role", ojson.NewString("user")) + contentArr := ojson.NewArray() + contentBlock := ojson.NewObject() + contentBlock.ObjectSet("type", ojson.NewString("input_text")) + contentBlock.ObjectSet("text", ojson.NewString(input.String())) + contentArr.ArrayAppend(contentBlock) + item.ObjectSet("content", contentArr) + wrapped.ArrayAppend(item) + message.ObjectSet("input", wrapped) + return responseInputRectifyResult{action: "string_to_array", originalType: "string"} + } + + // Case 3: single object (MessageInput has role, ToolOutputsInput has type). + if hasInput && input.IsObject() { + if input.ObjectHas("role") || input.ObjectHas("type") { + wrapped := ojson.NewArray() + wrapped.ArrayAppend(input) + message.ObjectSet("input", wrapped) + return responseInputRectifyResult{action: "object_to_array", originalType: "object"} + } + } + + // Case 4: undefined/null/other -- passthrough, let downstream handle the error. + return responseInputRectifyResult{action: "passthrough", originalType: "other"} +} + +// ---- remote-compaction.ts#isRemoteCompactionV2Request ---- + +func isRemoteCompactionV2Request(pn string, body *ojson.Value) bool { + if normalizeEndpointPath(pn) != "/v1/responses" { + return false + } + if body == nil || !body.IsObject() { + return false + } + + input, ok := body.ObjectGet("input") + if !ok { + // items = [undefined]; typeof undefined !== "object" -> no match. + return false + } + + if input.IsArray() { + for _, item := range input.ArrayItems() { + if isCompactionTriggerItem(item) { + return true + } + } + return false + } + + // items = [input] (single-object shorthand uses the same item semantics). + return isCompactionTriggerItem(input) +} + +func isCompactionTriggerItem(item *ojson.Value) bool { + if item == nil || !item.IsObject() { + return false + } + t, ok := item.ObjectGet("type") + return ok && t.IsString() && t.String() == "compaction_trigger" +} + +// ---- codex/session-completer.ts#extractInitialMessageTextHash ---- + +// extractInitialMessageTextHash ports extractInitialMessageTextHash +// (session-completer.ts ~L63-108). Callers only invoke this when body.input +// is a non-empty array (matching digest.ts's `Array.isArray(body.input) ? ... : null`). +func extractInitialMessageTextHash(body *ojson.Value) *string { + input, ok := body.ObjectGet("input") + if !ok || !input.IsArray() || input.ArrayLen() == 0 { + return nil + } + + var texts []string + for _, item := range input.ArrayItems() { + if item == nil || !item.IsObject() { + continue + } + + itemType := "" + if tv, ok := item.ObjectGet("type"); ok && tv.IsString() { + itemType = tv.String() + } + // Only consider "message" items for conversation fingerprinting. + if itemType != "" && itemType != "message" { + continue + } + + content, hasContent := item.ObjectGet("content") + if hasContent && content.IsString() { + if jsTrim(content.String()) != "" { + texts = append(texts, content.String()) + } + } else if hasContent && content.IsArray() { + var parts []string + for _, part := range content.ArrayItems() { + if part == nil || !part.IsObject() { + continue + } + textVal, ok := part.ObjectGet("text") + if !ok || !textVal.IsString() || textVal.String() == "" { + continue + } + parts = append(parts, textVal.String()) + } + joined := strings.Join(parts, "") + if jsTrim(joined) != "" { + texts = append(texts, joined) + } + } + + if len(texts) >= 3 { + break + } + } + + if len(texts) == 0 { + return nil + } + + combined := strings.Join(texts, "|") + sum := sha256.Sum256([]byte(combined)) + hash := hex.EncodeToString(sum[:])[:16] + return &hash +} diff --git a/edge/internal/digest/digest_test.go b/edge/internal/digest/digest_test.go new file mode 100644 index 0000000000..f52b7e6e74 --- /dev/null +++ b/edge/internal/digest/digest_test.go @@ -0,0 +1,111 @@ +package digest + +import ( + "encoding/json" + "os" + "reflect" + "testing" + + "github.com/ding113/claude-code-hub/edge/internal/contract" + "github.com/ding113/claude-code-hub/edge/internal/ojson" +) + +const fixtureDir = "../../../tests/fixtures/edge/body" + +type digestCase struct { + Name string `json:"name"` + Body string `json:"body"` + Path string `json:"path"` + Method string `json:"method"` + Headers [][2]string `json:"headers"` + ClientIP *string `json:"clientIp"` + Expected json.RawMessage `json:"expected"` +} + +func TestBuildFixtures(t *testing.T) { + data, err := os.ReadFile(fixtureDir + "/digest.json") + if err != nil { + t.Fatalf("read fixture: %v", err) + } + var cases []digestCase + if err := json.Unmarshal(data, &cases); err != nil { + t.Fatalf("unmarshal fixture: %v", err) + } + if len(cases) == 0 { + t.Fatal("no fixture cases loaded") + } + + for _, c := range cases { + t.Run(c.Name, func(t *testing.T) { + body, err := ojson.Parse([]byte(c.Body), 0) + if err != nil { + t.Fatalf("parse body: %v", err) + } + + var expected map[string]json.RawMessage + if err := json.Unmarshal(c.Expected, &expected); err != nil { + t.Fatalf("unmarshal expected: %v", err) + } + + method := c.Method + if method == "" { + method = "POST" + } + + headers := make([]contract.HeaderPair, len(c.Headers)) + for i, h := range c.Headers { + headers[i] = contract.HeaderPair{h[0], h[1]} + } + + got := Build(Input{ + EdgeID: "edge-fixture", + EdgeRequestID: "req-fixture", + ReceivedAtMs: 1_700_000_000_000, + Method: method, + Path: c.Path, + Headers: headers, + ClientIP: c.ClientIP, + Body: body, + BodyBytes: len(c.Body), + }) + + gotJSON, err := json.Marshal(got) + if err != nil { + t.Fatalf("marshal got: %v", err) + } + var gotMap map[string]json.RawMessage + if err := json.Unmarshal(gotJSON, &gotMap); err != nil { + t.Fatalf("unmarshal got: %v", err) + } + // receivedAtMs/edgeId/edgeRequestId are excluded from the fixture. + delete(gotMap, "receivedAtMs") + delete(gotMap, "edgeId") + delete(gotMap, "edgeRequestId") + + for key, wantRaw := range expected { + gotRaw, ok := gotMap[key] + if !ok { + t.Fatalf("field %q missing from Build() output", key) + } + if !canonicalJSONEqual(t, gotRaw, wantRaw) { + t.Fatalf("field %q mismatch\n got: %s\nwant: %s", key, gotRaw, wantRaw) + } + } + }) + } +} + +// canonicalJSONEqual compares two JSON fragments by structural equality after +// decoding into `any` (numbers become float64 on both sides, matching how the +// TS reference's own JSON.parse/JSON.stringify round-trip normalizes them). +func canonicalJSONEqual(t *testing.T, a, b json.RawMessage) bool { + t.Helper() + var av, bv interface{} + if err := json.Unmarshal(a, &av); err != nil { + t.Fatalf("unmarshal a: %v", err) + } + if err := json.Unmarshal(b, &bv); err != nil { + t.Fatalf("unmarshal b: %v", err) + } + return reflect.DeepEqual(av, bv) +} diff --git a/edge/internal/digest/fingerprint.go b/edge/internal/digest/fingerprint.go new file mode 100644 index 0000000000..1e87fc1df7 --- /dev/null +++ b/edge/internal/digest/fingerprint.go @@ -0,0 +1,459 @@ +package digest + +import ( + "crypto/sha256" + "encoding/hex" + "sort" + + "github.com/ding113/claude-code-hub/edge/internal/contract" + "github.com/ding113/claude-code-hub/edge/internal/ojson" +) + +// sep mirrors SEP in affinity/fingerprint.ts: `const SEP = "\x1f";` (ASCII unit +// separator), not an empty string as it might appear when the source is +// rendered by tools that strip control characters. +const sep = "\x1f" + +// MaxAffinityWindow mirrors MAX_AFFINITY_WINDOW in affinity/fingerprint.ts. +const MaxAffinityWindow = 64 + +// DefaultAffinityWindow mirrors DEFAULT_AFFINITY_WINDOW. +const DefaultAffinityWindow = 8 + +var volatileKeys = map[string]bool{ + "id": true, "call_id": true, "tool_use_id": true, "tool_call_id": true, "cache_control": true, +} + +type normalizedMessage struct { + bytes string + hasCacheControl bool +} + +// ComputeFingerprintChain ports computeFingerprintChain(message, format, window) +// from src/app/v1/_lib/proxy/affinity/fingerprint.ts for format in +// {claude, openai, response} (the edge-supported EdgeClientFormat set; other +// formats fall through extractConversation's default case and return nil, +// matching computeChainInner's null on an unrecognized format). It fails open +// (returns nil) on any malformed input, matching the TS try/catch wrapper. +func ComputeFingerprintChain(body *ojson.Value, format string, window int) (chain *contract.FingerprintChain) { + defer func() { + if r := recover(); r != nil { + chain = nil + } + }() + if window <= 0 { + window = DefaultAffinityWindow + } + if window > MaxAffinityWindow { + window = MaxAffinityWindow + } + + extracted := extractConversation(body, format) + if extracted == nil { + return nil + } + + sysBytes := joinStrings(extracted.sysSegments) + sysFP := h32(sysBytes) + cumBytes := byteLen(sysBytes) + sys := contract.FingerprintBoundary{Depth: 0, FP: sysFP, PrefixBytes: cumBytes} + + tail := make([]contract.FingerprintBoundary, 0, len(extracted.messages)) + prev := sysFP + depth := 0 + for _, m := range extracted.messages { + if len(m.bytes) == 0 { + continue + } + depth++ + prev = h32(prev + m.bytes) + cumBytes += byteLen(m.bytes) + b := contract.FingerprintBoundary{Depth: depth, FP: prev, PrefixBytes: cumBytes} + if m.hasCacheControl { + b.HasCacheControl = true + } + tail = append(tail, b) + } + + if len(tail) > window { + tail = tail[len(tail)-window:] + } + + return &contract.FingerprintChain{Sys: sys, Tail: tail} +} + +func h32(s string) string { + sum := sha256.Sum256([]byte(s)) + return hex.EncodeToString(sum[:])[:32] +} + +func byteLen(s string) int { return len(s) } + +func joinStrings(parts []string) string { + total := 0 + for _, p := range parts { + total += len(p) + } + out := make([]byte, 0, total) + for _, p := range parts { + out = append(out, p...) + } + return string(out) +} + +type extractedConversation struct { + sysSegments []string + messages []normalizedMessage +} + +// extractConversation ports the switch in computeChainInner (fingerprint.ts +// extractConversation), restricted to the edge-supported formats: claude, +// openai, response. gemini/gemini-cli never reach the edge digest (their +// paths resolve to "claude" via resolveEdgeClientFormat), so they are not +// ported here; any other format value falls through to nil, matching the +// TS `default: return null`. +func extractConversation(body *ojson.Value, format string) *extractedConversation { + switch format { + case contract.FormatClaude: + return extractClaude(body) + case contract.FormatOpenAI: + return extractOpenAIChat(body) + case contract.FormatResponse: + return extractResponses(body) + default: + return nil + } +} + +func extractClaude(body *ojson.Value) *extractedConversation { + messages, ok := body.ObjectGet("messages") + if !ok || !messages.IsArray() { + return nil + } + + sysSegments := []string{sep} + if system, ok := body.ObjectGet("system"); ok { + if system.IsString() { + sysSegments = append(sysSegments, system.String()) + } else if system.IsArray() { + for _, block := range system.ArrayItems() { + sysSegments = append(sysSegments, normalizeContentBlock(block)) + } + } + } + if tools, ok := body.ObjectGet("tools"); ok { + appendTools(&sysSegments, tools, func(tool *ojson.Value) toolSpec { + return toolSpec{ + name: readString(tool, "name"), + description: readString(tool, "description"), + parameters: readRecord(tool, "input_schema"), + } + }) + } + + normalized := make([]normalizedMessage, 0, messages.ArrayLen()) + for _, raw := range messages.ArrayItems() { + if !raw.IsObject() { + continue + } + parts := []string{sep, readString(raw, "role")} + hasCacheControl := false + if content, ok := raw.ObjectGet("content"); ok { + if content.IsString() { + parts = append(parts, sep, "text:", content.String()) + } else if content.IsArray() { + for _, block := range content.ArrayItems() { + parts = append(parts, normalizeContentBlock(block)) + if block.IsObject() { + if cc, ok := block.ObjectGet("cache_control"); ok && !cc.IsNull() { + hasCacheControl = true + } + } + } + } + } + normalized = append(normalized, finishMessage(parts, hasCacheControl)) + } + + return &extractedConversation{sysSegments: sysSegments, messages: normalized} +} + +func normalizeContentBlock(block *ojson.Value) string { + if block == nil || !block.IsObject() { + if block != nil && block.IsString() { + return sep + "text:" + block.String() + } + return "" + } + typ := readString(block, "type") + switch typ { + case "text": + return sep + "text:" + readString(block, "text") + case "thinking": + return sep + "thinking:" + readString(block, "thinking") + case "redacted_thinking": + return sep + "redacted_thinking:" + readString(block, "data") + case "tool_use": + input, _ := block.ObjectGet("input") + return sep + "tool_use:" + readString(block, "name") + ":" + ojson.StableStringify(input) + case "tool_result": + content, _ := block.ObjectGet("content") + return sep + "tool_result:" + serializeUnknownContent(content) + case "image", "document": + source := readRecord(block, "source") + return sep + typ + ":" + digestMediaSource(source) + default: + if typ == "" { + return "" + } + return sep + typ + ":" + ojson.StableStringify(stripVolatileKeys(block)) + } +} + +func digestMediaSource(source *ojson.Value) string { + if source == nil { + return "" + } + mediaType := readString(source, "media_type") + if mediaType == "" { + mediaType = readString(source, "mediaType") + } + data := readString(source, "data") + if data != "" { + return mediaType + ":" + h32(data) + } + url := readString(source, "url") + return mediaType + ":" + url +} + +// ===== openai (Chat Completions) ===== + +func extractOpenAIChat(body *ojson.Value) *extractedConversation { + messages, ok := body.ObjectGet("messages") + if !ok || !messages.IsArray() { + return nil + } + + sysSegments := []string{sep} + if tools, ok := body.ObjectGet("tools"); ok { + appendTools(&sysSegments, tools, func(tool *ojson.Value) toolSpec { + fn := readRecord(tool, "function") + if fn != nil { + return toolSpec{ + name: readString(fn, "name"), + description: readString(fn, "description"), + parameters: readRecord(fn, "parameters"), + } + } + return toolSpec{ + name: readString(tool, "name"), + description: readString(tool, "description"), + parameters: nil, + } + }) + } + + normalized := make([]normalizedMessage, 0, messages.ArrayLen()) + inLeadingSystem := true + for _, raw := range messages.ArrayItems() { + if raw == nil || !raw.IsObject() { + continue + } + role := readString(raw, "role") + content, _ := raw.ObjectGet("content") + // Leading system/developer messages belong to the cross-conversation + // stable segment and are folded into F_sys. + if inLeadingSystem && (role == "system" || role == "developer") { + sysSegments = append(sysSegments, sep, role, ":", serializeUnknownContent(content)) + continue + } + inLeadingSystem = false + + parts := []string{sep, role} + parts = append(parts, sep, "content:", serializeUnknownContent(content)) + if toolCalls, ok := raw.ObjectGet("tool_calls"); ok && toolCalls.IsArray() { + for _, call := range toolCalls.ArrayItems() { + if call == nil || !call.IsObject() { + continue + } + fn := readRecord(call, "function") + fnName, fnArgs := "", "" + if fn != nil { + fnName = readString(fn, "name") + fnArgs = readString(fn, "arguments") + } + // Strip the volatile call id. + parts = append(parts, sep, "tool_call:", fnName, ":", fnArgs) + } + } + if raw.ObjectHas("tool_call_id") { + // tool-role message: strip tool_call_id, content already captured above. + parts = append(parts, sep, "tool_result") + } + normalized = append(normalized, finishMessage(parts, false)) + } + + return &extractedConversation{sysSegments: sysSegments, messages: normalized} +} + +// ===== response (OpenAI Responses / Codex) ===== + +func extractResponses(body *ojson.Value) *extractedConversation { + input, hasInput := body.ObjectGet("input") + + sysSegments := []string{sep} + if instructions, ok := body.ObjectGet("instructions"); ok && instructions.IsString() { + sysSegments = append(sysSegments, instructions.String()) + } + if tools, ok := body.ObjectGet("tools"); ok { + appendTools(&sysSegments, tools, func(tool *ojson.Value) toolSpec { + return toolSpec{ + name: readString(tool, "name"), + description: readString(tool, "description"), + parameters: readRecord(tool, "parameters"), + } + }) + } + + var normalized []normalizedMessage + switch { + case hasInput && input.IsString(): + normalized = append(normalized, normalizedMessage{ + bytes: sep + "user" + sep + "text:" + input.String(), + hasCacheControl: false, + }) + case hasInput && input.IsArray(): + normalized = make([]normalizedMessage, 0, input.ArrayLen()) + for _, raw := range input.ArrayItems() { + if raw == nil || !raw.IsObject() { + continue + } + typ := readString(raw, "type") + if typ == "" { + typ = "message" + } + parts := []string{sep} + switch typ { + case "message": + content, _ := raw.ObjectGet("content") + parts = append(parts, readString(raw, "role"), sep, "content:", serializeUnknownContent(content)) + case "function_call": + // Strip the volatile call_id / id. + parts = append(parts, "function_call", sep, readString(raw, "name"), ":", readString(raw, "arguments")) + case "function_call_output": + output, _ := raw.ObjectGet("output") + parts = append(parts, "function_call_output", sep, serializeUnknownContent(output)) + case "reasoning": + summaryOrContent := nullishCoalesce(raw, "summary", "content") + parts = append(parts, "reasoning", sep, serializeUnknownContent(summaryOrContent)) + default: + parts = append(parts, typ, sep, ojson.StableStringify(stripVolatileKeys(raw))) + } + normalized = append(normalized, finishMessage(parts, false)) + } + default: + return nil + } + + return &extractedConversation{sysSegments: sysSegments, messages: normalized} +} + +// nullishCoalesce reads record[primaryKey], falling back to record[fallbackKey] +// only when primaryKey is absent or explicitly null (mirrors JS `a ?? b`, which +// only falls through on null/undefined -- not on other falsy values). +func nullishCoalesce(record *ojson.Value, primaryKey, fallbackKey string) *ojson.Value { + if v, ok := record.ObjectGet(primaryKey); ok && !v.IsNull() { + return v + } + if v, ok := record.ObjectGet(fallbackKey); ok { + return v + } + return nil +} + +type toolSpec struct { + name string + description string + parameters *ojson.Value +} + +func appendTools(segments *[]string, tools *ojson.Value, project func(*ojson.Value) toolSpec) { + if tools == nil || !tools.IsArray() || tools.ArrayLen() == 0 { + return + } + specs := make([]toolSpec, 0, tools.ArrayLen()) + for _, raw := range tools.ArrayItems() { + if raw == nil || !raw.IsObject() { + continue + } + spec := project(raw) + if spec.name == "" { + continue + } + specs = append(specs, spec) + } + sort.SliceStable(specs, func(i, j int) bool { return ojson.LessUTF16(specs[i].name, specs[j].name) }) + for _, spec := range specs { + params := "" + if spec.parameters != nil { + params = ojson.StableStringify(spec.parameters) + } + *segments = append(*segments, sep, spec.name, ":", spec.description, ":", params) + } +} + +func finishMessage(parts []string, hasCacheControl bool) normalizedMessage { + bytesStr := joinStrings(parts) + meaningful := len(parts) > 2 + if !meaningful { + return normalizedMessage{bytes: "", hasCacheControl: hasCacheControl} + } + return normalizedMessage{bytes: bytesStr, hasCacheControl: hasCacheControl} +} + +func serializeUnknownContent(content *ojson.Value) string { + if content == nil || content.IsNull() { + return "" + } + if content.IsString() { + return content.String() + } + return ojson.StableStringify(content) +} + +func stripVolatileKeys(record *ojson.Value) *ojson.Value { + out := ojson.NewObject() + if record == nil || !record.IsObject() { + return out + } + for _, k := range record.ObjectKeys() { + if volatileKeys[k] { + continue + } + v, _ := record.ObjectGet(k) + out.ObjectSet(k, v) + } + return out +} + +func readString(record *ojson.Value, key string) string { + if record == nil || !record.IsObject() { + return "" + } + v, ok := record.ObjectGet(key) + if !ok || !v.IsString() { + return "" + } + return v.String() +} + +func readRecord(record *ojson.Value, key string) *ojson.Value { + if record == nil || !record.IsObject() { + return nil + } + v, ok := record.ObjectGet(key) + if !ok || !v.IsObject() { + return nil + } + return v +} diff --git a/edge/internal/fixer/encoding.go b/edge/internal/fixer/encoding.go new file mode 100644 index 0000000000..09dab0151a --- /dev/null +++ b/edge/internal/fixer/encoding.go @@ -0,0 +1,86 @@ +package fixer + +import ( + "bytes" + "unicode/utf8" +) + +// EncodingFixer ports encoding-fixer.ts. +type EncodingFixer struct{} + +func NewEncodingFixer() *EncodingFixer { return &EncodingFixer{} } + +func hasUTF8BOM(data []byte) bool { + return len(data) >= 3 && data[0] == 0xEF && data[1] == 0xBB && data[2] == 0xBF +} + +func hasUTF16BOM(data []byte) bool { + if len(data) < 2 { + return false + } + return (data[0] == 0xFE && data[1] == 0xFF) || (data[0] == 0xFF && data[1] == 0xFE) +} + +func stripBOM(data []byte) (out []byte, stripped bool, details string) { + if hasUTF8BOM(data) { + return data[3:], true, "removed_utf8_bom" + } + if hasUTF16BOM(data) { + return data[2:], true, "removed_utf16_bom" + } + return data, false, "" +} + +func stripNullBytes(data []byte) (out []byte, stripped bool) { + firstNull := bytes.IndexByte(data, 0) + if firstNull < 0 { + return data, false + } + + out = make([]byte, 0, len(data)) + out = append(out, data[:firstNull]...) + for i := firstNull + 1; i < len(data); i++ { + if data[i] != 0 { + out = append(out, data[i]) + } + } + return out, true +} + +func isValidUTF8(data []byte) bool { + return utf8.Valid(data) +} + +// CanFix mirrors EncodingFixer.canFix. +func (f *EncodingFixer) CanFix(data []byte) bool { + if hasUTF8BOM(data) || hasUTF16BOM(data) { + return true + } + if bytes.IndexByte(data, 0) >= 0 { + return true + } + return !isValidUTF8(data) +} + +// Fix mirrors EncodingFixer.fix. +func (f *EncodingFixer) Fix(input []byte) FixResult { + if !f.CanFix(input) { + return FixResult{Data: input, Applied: false} + } + + bomData, bomStripped, bomDetails := stripBOM(input) + nulData, nulStripped := stripNullBytes(bomData) + + changedByStrip := bomStripped || nulStripped + + if isValidUTF8(nulData) { + details := bomDetails + if details == "" && nulStripped { + details = "removed_null_bytes" + } + return FixResult{Data: nulData, Applied: changedByStrip, Details: details} + } + + lossy := decodeUTF8Lossy(nulData) + return FixResult{Data: lossy, Applied: true, Details: "lossy_utf8_decode_encode"} +} diff --git a/edge/internal/fixer/encoding_test.go b/edge/internal/fixer/encoding_test.go new file mode 100644 index 0000000000..37aa2b4ba3 --- /dev/null +++ b/edge/internal/fixer/encoding_test.go @@ -0,0 +1,47 @@ +package fixer + +import ( + "bytes" + "testing" +) + +type encodingCase struct { + Name string `json:"name"` + InputB64 string `json:"inputB64"` + ExpectedDataB64 string `json:"expectedDataB64"` + ExpectedApplied bool `json:"expectedApplied"` + ExpectedDetails string `json:"expectedDetails"` + ExpectedDetailsOK bool `json:"-"` +} + +func TestEncodingFixerFixtures(t *testing.T) { + var raw []map[string]interface{} + loadFixture(t, "encoding.json", &raw) + + var cases []encodingCase + loadFixture(t, "encoding.json", &cases) + + for i, c := range cases { + c := c + hasDetails := raw[i]["expectedDetails"] != nil + t.Run(c.Name, func(t *testing.T) { + input := mustB64(t, c.InputB64) + expected := mustB64(t, c.ExpectedDataB64) + + res := NewEncodingFixer().Fix(input) + if res.Applied != c.ExpectedApplied { + t.Errorf("Applied = %v, want %v", res.Applied, c.ExpectedApplied) + } + if !bytes.Equal(res.Data, expected) { + t.Errorf("Data = %q, want %q", res.Data, expected) + } + if hasDetails { + if res.Details != c.ExpectedDetails { + t.Errorf("Details = %q, want %q", res.Details, c.ExpectedDetails) + } + } else if res.Details != "" { + t.Errorf("Details = %q, want empty", res.Details) + } + }) + } +} diff --git a/edge/internal/fixer/fixture_test.go b/edge/internal/fixer/fixture_test.go new file mode 100644 index 0000000000..0b31b8724f --- /dev/null +++ b/edge/internal/fixer/fixture_test.go @@ -0,0 +1,29 @@ +package fixer + +import ( + "encoding/base64" + "encoding/json" + "os" + "testing" +) + +func loadFixture(t *testing.T, name string, out interface{}) { + t.Helper() + path := "../../../tests/fixtures/edge/fixer/" + name + data, err := os.ReadFile(path) + if err != nil { + t.Fatalf("read fixture %s: %v", path, err) + } + if err := json.Unmarshal(data, out); err != nil { + t.Fatalf("unmarshal fixture %s: %v", path, err) + } +} + +func mustB64(t *testing.T, s string) []byte { + t.Helper() + b, err := base64.StdEncoding.DecodeString(s) + if err != nil { + t.Fatalf("decode base64: %v", err) + } + return b +} diff --git a/edge/internal/fixer/json.go b/edge/internal/fixer/json.go new file mode 100644 index 0000000000..377dc9c694 --- /dev/null +++ b/edge/internal/fixer/json.go @@ -0,0 +1,176 @@ +package fixer + +import "encoding/json" + +// JsonFixer ports json-fixer.ts. +type JsonFixer struct { + maxDepth int + maxSize int +} + +func NewJsonFixer(maxDepth, maxSize int) *JsonFixer { + return &JsonFixer{maxDepth: maxDepth, maxSize: maxSize} +} + +func isWhitespace(b byte) bool { + return b == 0x20 || b == 0x09 || b == 0x0A || b == 0x0D +} + +func looksLikeJSON(data []byte) bool { + for _, b := range data { + if isWhitespace(b) { + continue + } + return b == 0x7B || b == 0x5B // { or [ + } + return false +} + +func removeTrailingComma(bytes *[]byte) { + b := *bytes + idx := len(b) - 1 + for idx >= 0 && isWhitespace(b[idx]) { + idx-- + } + if idx >= 0 && b[idx] == 0x2C { // , + *bytes = b[:idx] + } +} + +func needsNullValue(bytes []byte, stack []byte) bool { + if len(stack) == 0 || stack[len(stack)-1] != 0x7D { // } + return false + } + idx := len(bytes) - 1 + for idx >= 0 && isWhitespace(bytes[idx]) { + idx-- + } + return idx >= 0 && bytes[idx] == 0x3A // : +} + +func (f *JsonFixer) CanFix(data []byte) bool { + return looksLikeJSON(data) +} + +// Fix mirrors JsonFixer.fix. +func (f *JsonFixer) Fix(data []byte) FixResult { + if len(data) > f.maxSize { + return FixResult{Data: data, Applied: false, Details: "exceeded_max_size"} + } + + if !f.CanFix(data) { + return FixResult{Data: data, Applied: false} + } + + // TS validates via `JSON.parse(TextDecoder.decode(data))`, i.e. against the + // lossily-decoded text (invalid UTF-8 becomes U+FFFD before parsing), not + // the raw bytes. Mirror that so a JSON-syntax-only fixer decision does not + // also depend on separately-scoped encoding issues. + if json.Valid(decodeUTF8Lossy(data)) { + return FixResult{Data: data, Applied: false} + } + + repaired := f.repair(data) + if repaired == nil { + return FixResult{Data: data, Applied: false, Details: "repair_failed"} + } + + if json.Valid(decodeUTF8Lossy(repaired)) { + return FixResult{Data: repaired, Applied: true} + } + return FixResult{Data: data, Applied: false, Details: "validate_repaired_failed"} +} + +func (f *JsonFixer) repair(data []byte) []byte { + out := make([]byte, 0, len(data)+8) + var stack []byte + + inString := false + escapeNext := false + depth := 0 + + for _, b := range data { + if escapeNext { + escapeNext = false + out = append(out, b) + continue + } + + if inString && b == 0x5C { // backslash + escapeNext = true + out = append(out, b) + continue + } + + if b == 0x22 { // " + inString = !inString + out = append(out, b) + continue + } + + if !inString { + switch b { + case 0x7B: // { + depth++ + if depth > f.maxDepth { + return nil + } + stack = append(stack, 0x7D) + out = append(out, b) + continue + case 0x5B: // [ + depth++ + if depth > f.maxDepth { + return nil + } + stack = append(stack, 0x5D) + out = append(out, b) + continue + case 0x7D: // } + removeTrailingComma(&out) + if len(stack) > 0 && stack[len(stack)-1] == b { + stack = stack[:len(stack)-1] + if depth > 0 { + depth-- + } + out = append(out, b) + } + continue + case 0x5D: // ] + removeTrailingComma(&out) + if len(stack) > 0 && stack[len(stack)-1] == b { + stack = stack[:len(stack)-1] + if depth > 0 { + depth-- + } + out = append(out, b) + } + continue + } + } + + out = append(out, b) + } + + if escapeNext && len(out) > 0 { + out = out[:len(out)-1] + } + + if inString { + out = append(out, 0x22) + } + + removeTrailingComma(&out) + + if needsNullValue(out, stack) { + out = append(out, 'n', 'u', 'l', 'l') + } + + for len(stack) > 0 { + removeTrailingComma(&out) + out = append(out, stack[len(stack)-1]) + stack = stack[:len(stack)-1] + } + + return out +} diff --git a/edge/internal/fixer/json_test.go b/edge/internal/fixer/json_test.go new file mode 100644 index 0000000000..53d8455b53 --- /dev/null +++ b/edge/internal/fixer/json_test.go @@ -0,0 +1,62 @@ +package fixer + +import ( + "bytes" + "encoding/json" + "reflect" + "testing" +) + +type jsonCase struct { + Name string `json:"name"` + InputB64 string `json:"inputB64"` + MaxDepth int `json:"maxDepth"` + MaxSize int `json:"maxSize"` + ExpectedApplied bool `json:"expectedApplied"` + ExpectedDetails *string `json:"expectedDetails"` + ExpectedDataB64 string `json:"expectedDataB64"` + ExpectAppliedDataParsesTo json.RawMessage `json:"expectAppliedDataParsesTo"` +} + +func TestJsonFixerFixtures(t *testing.T) { + var cases []jsonCase + loadFixture(t, "json.json", &cases) + + for _, c := range cases { + c := c + t.Run(c.Name, func(t *testing.T) { + input := mustB64(t, c.InputB64) + + res := NewJsonFixer(c.MaxDepth, c.MaxSize).Fix(input) + if res.Applied != c.ExpectedApplied { + t.Fatalf("Applied = %v, want %v", res.Applied, c.ExpectedApplied) + } + wantDetails := "" + if c.ExpectedDetails != nil { + wantDetails = *c.ExpectedDetails + } + if res.Details != wantDetails { + t.Errorf("Details = %q, want %q", res.Details, wantDetails) + } + + if len(c.ExpectAppliedDataParsesTo) > 0 { + var got, want interface{} + if err := json.Unmarshal(res.Data, &got); err != nil { + t.Fatalf("repaired data is not valid JSON: %v (data=%s)", err, res.Data) + } + if err := json.Unmarshal(c.ExpectAppliedDataParsesTo, &want); err != nil { + t.Fatalf("bad fixture expectAppliedDataParsesTo: %v", err) + } + if !reflect.DeepEqual(got, want) { + t.Errorf("parsed repaired data = %#v, want %#v", got, want) + } + return + } + + expected := mustB64(t, c.ExpectedDataB64) + if !bytes.Equal(res.Data, expected) { + t.Errorf("Data = %q, want %q", res.Data, expected) + } + }) + } +} diff --git a/edge/internal/fixer/nonstream_test.go b/edge/internal/fixer/nonstream_test.go new file mode 100644 index 0000000000..f82b52e920 --- /dev/null +++ b/edge/internal/fixer/nonstream_test.go @@ -0,0 +1,78 @@ +package fixer + +import ( + "bytes" + "encoding/json" + "testing" +) + +type nonStreamCfg struct { + FixEncoding bool `json:"fixEncoding"` + FixTruncatedJSON bool `json:"fixTruncatedJson"` + MaxJSONDepth int `json:"maxJsonDepth"` + MaxFixSize int `json:"maxFixSize"` +} + +type nonStreamCase struct { + Name string `json:"name"` + InputB64 string `json:"inputB64"` + Cfg nonStreamCfg `json:"cfg"` + ExpectedEncodingApplied bool `json:"expectedEncodingApplied"` + ExpectedJSONApplied bool `json:"expectedJsonApplied"` + ExpectedHit bool `json:"expectedHit"` + ExpectedDataParsesAsJSON bool `json:"expectedDataParsesAsJson"` + ExpectedDataB64ForNonJSONCase *string `json:"expectedDataB64ForNonJsonCase"` +} + +func TestFixNonStreamFixtures(t *testing.T) { + var cases []nonStreamCase + loadFixture(t, "nonstream.json", &cases) + + for _, c := range cases { + c := c + t.Run(c.Name, func(t *testing.T) { + input := mustB64(t, c.InputB64) + cfg := Config{ + FixEncoding: c.Cfg.FixEncoding, + FixTruncatedJSON: c.Cfg.FixTruncatedJSON, + MaxJSONDepth: c.Cfg.MaxJSONDepth, + MaxFixSize: c.Cfg.MaxFixSize, + } + + data, audit := FixNonStream(input, cfg) + + if audit.TotalBytesProcessed != int64(len(input)) { + t.Errorf("TotalBytesProcessed = %d, want %d", audit.TotalBytesProcessed, len(input)) + } + if audit.ProcessingTimeMs < 0 { + t.Errorf("ProcessingTimeMs = %d, want >= 0", audit.ProcessingTimeMs) + } + if len(audit.FixersApplied) != 2 { + t.Fatalf("FixersApplied len = %d, want 2 (encoding, json)", len(audit.FixersApplied)) + } + if audit.FixersApplied[0].Fixer != "encoding" || audit.FixersApplied[1].Fixer != "json" { + t.Errorf("FixersApplied order = %v", audit.FixersApplied) + } + if audit.FixersApplied[0].Applied != c.ExpectedEncodingApplied { + t.Errorf("encoding.Applied = %v, want %v", audit.FixersApplied[0].Applied, c.ExpectedEncodingApplied) + } + if audit.FixersApplied[1].Applied != c.ExpectedJSONApplied { + t.Errorf("json.Applied = %v, want %v", audit.FixersApplied[1].Applied, c.ExpectedJSONApplied) + } + if audit.Hit != c.ExpectedHit { + t.Errorf("Hit = %v, want %v", audit.Hit, c.ExpectedHit) + } + + if c.ExpectedDataParsesAsJSON { + if !json.Valid(data) { + t.Errorf("output data is not valid JSON: %s", data) + } + } else if c.ExpectedDataB64ForNonJSONCase != nil { + want := mustB64(t, *c.ExpectedDataB64ForNonJSONCase) + if !bytes.Equal(data, want) { + t.Errorf("Data = %q, want %q", data, want) + } + } + }) + } +} diff --git a/edge/internal/fixer/response_format.go b/edge/internal/fixer/response_format.go new file mode 100644 index 0000000000..b389b90fa0 --- /dev/null +++ b/edge/internal/fixer/response_format.go @@ -0,0 +1,173 @@ +package fixer + +import ( + "encoding/json" + "strings" +) + +// chatCompletionChunkMarker is the ASCII byte sequence for +// `"chat.completion.chunk"`, used for a cheap byte-level pre-scan before +// decoding/splitting a chunk (ports the CHAT_COMPLETION_CHUNK_MARKER +// pre-scan in response-fixer/index.ts). +var chatCompletionChunkMarker = []byte(`"chat.completion.chunk"`) + +func bytesIndexOf(haystack, needle []byte) int { + if len(needle) == 0 { + return 0 + } + limit := len(haystack) - len(needle) + for i := 0; i <= limit; i++ { + if haystack[i] != needle[0] { + continue + } + match := true + for j := 1; j < len(needle); j++ { + if haystack[i+j] != needle[j] { + match = false + break + } + } + if match { + return i + } + } + return -1 +} + +func hasMeaningfulValue(value interface{}) bool { + switch v := value.(type) { + case nil: + return false + case string: + return len(v) > 0 + case []interface{}: + return len(v) > 0 + case map[string]interface{}: + return len(v) > 0 + default: + return true + } +} + +func isInertChatCompletionChoice(choice interface{}) bool { + m, ok := choice.(map[string]interface{}) + if !ok { + return false + } + if fr, exists := m["finish_reason"]; exists && fr != nil { + return false + } + delta, ok := m["delta"].(map[string]interface{}) + if !ok { + // A missing/non-object delta cannot carry content: inert. + return true + } + for key, value := range delta { + if key == "role" { + continue + } + if hasMeaningfulValue(value) { + return false + } + } + return true +} + +func isInertChatCompletionChunkPayload(payload interface{}) bool { + m, ok := payload.(map[string]interface{}) + if !ok { + return false + } + if obj, _ := m["object"].(string); obj != "chat.completion.chunk" { + return false + } + if hasMeaningfulValue(m["usage"]) { + return false + } + choices, ok := m["choices"].([]interface{}) + if !ok || len(choices) == 0 { + return false + } + for _, ch := range choices { + if !isInertChatCompletionChoice(ch) { + return false + } + } + return true +} + +func isBlankSseSeparatorLine(line string) bool { + return line == "" || line == "\r" +} + +func isInertChatCompletionDataLine(line string) bool { + if !strings.HasPrefix(line, "data:") { + return false + } + payloadText := line[len("data:"):] + if strings.HasPrefix(payloadText, " ") { + payloadText = payloadText[1:] + } + if !strings.HasPrefix(payloadText, "{") { + return false + } + var parsed interface{} + if err := json.Unmarshal([]byte(payloadText), &parsed); err != nil { + return false + } + return isInertChatCompletionChunkPayload(parsed) +} + +// filterInertResponsesChatCompletionChunks ports +// ResponseFixer.filterInertResponsesChatCompletionChunks: for the "response" +// (OpenAI Responses API) client format, some OpenAI-compatible upstreams +// wrap their Responses-shaped stream with interleaved, content-free +// `chat.completion.chunk` frames (role-only delta, no usage, no +// finish_reason). Those frames are dropped, along with their trailing blank +// SSE separator line, so the client only sees the (already-handled) +// Responses-shaped events. +func filterInertResponsesChatCompletionChunks(format string, data []byte) FixResult { + if format != "response" { + return FixResult{Data: data, Applied: false} + } + if bytesIndexOf(data, chatCompletionChunkMarker) < 0 { + return FixResult{Data: data, Applied: false} + } + + text := string(data) + lines := strings.Split(text, "\n") + out := make([]string, 0, len(lines)) + applied := false + skipNextBlank := false + + for i, line := range lines { + hasLineBreak := i < len(lines)-1 + + if skipNextBlank && isBlankSseSeparatorLine(line) { + skipNextBlank = false + continue + } + skipNextBlank = false + + if isInertChatCompletionDataLine(line) { + applied = true + skipNextBlank = true + continue + } + + out = append(out, line) + if hasLineBreak { + out = append(out, "\n") + } + } + + if !applied { + return FixResult{Data: data, Applied: false} + } + + return FixResult{ + Data: []byte(strings.Join(out, "")), + Applied: true, + Details: "filtered_inert_chat_completion_chunk", + } +} diff --git a/edge/internal/fixer/response_output_normalizer.go b/edge/internal/fixer/response_output_normalizer.go new file mode 100644 index 0000000000..959300e342 --- /dev/null +++ b/edge/internal/fixer/response_output_normalizer.go @@ -0,0 +1,169 @@ +package fixer + +import ( + "github.com/ding113/claude-code-hub/edge/internal/ojson" +) + +// NormalizeResponseOutput ports normalizeResponseOutputPayload +// (src/app/v1/_lib/proxy/response-output-normalizer.ts): for a non-stream +// OpenAI Responses API JSON body (top-level "object": "response"), replace +// null `output`/`tools` with `[]`, and within each `output[]` item replace +// null `content`/`summary` with `[]`, null `content[].text` with `""`, null +// `content[].annotations`/`content[].logprobs` with `[]`, and stringify any +// non-string `arguments` field (top-level on the item, on `item.function`, +// and on each `item.tool_calls[].function`) via JSON.stringify semantics +// (null/undefined -> "{}"). +// +// Returns (body, false) unchanged when the body is not a JSON object with +// "object": "response", or when no field needed fixing. +// +// When to call this (see normalizeResponseOutput in the TS source and +// ResponseFixer.process / processNonStream): +// - ONLY for the "response" (OpenAI Responses API) client format. +// - ONLY for a non-stream (non-SSE) response body. +// - ONLY when the response status is 2xx and content-type is JSON. +// - It runs unconditionally whenever ResponseFixer.processNonStream runs, +// regardless of the fixTruncatedJson/fixEncoding config flags -- but +// ResponseFixer.process itself short-circuits and returns the response +// untouched (skipping normalizeResponseOutput entirely) when the +// response-fixer feature is disabled (settings.enableResponseFixer === +// false). So: call NormalizeResponseOutput iff the response-fixer +// feature is enabled AND the response is non-stream AND +// format == "response" AND status is 2xx AND content-type is JSON; +// apply it to the body AFTER FixNonStream(body, cfg). +// - It is NEVER applied to a streamed (SSE) Responses response: TS's +// processStream has no equivalent call. +func NormalizeResponseOutput(body []byte) ([]byte, bool) { + v, err := ojson.Parse(body, ojson.DefaultMaxDepth) + if err != nil || !v.IsObject() { + return body, false + } + + objType, ok := v.ObjectGet("object") + if !ok || !objType.IsString() || objType.String() != "response" { + return body, false + } + + applied := false + + if output, ok := v.ObjectGet("output"); ok { + if output.IsNull() { + v.ObjectSet("output", ojson.NewArray()) + applied = true + } else if output.IsArray() { + for _, item := range output.ArrayItems() { + if normalizeOutputItem(item) { + applied = true + } + } + } + } + + if tools, ok := v.ObjectGet("tools"); ok && tools.IsNull() { + v.ObjectSet("tools", ojson.NewArray()) + applied = true + } + + if !applied { + return body, false + } + return v.Marshal(), true +} + +func normalizeOutputItem(item *ojson.Value) bool { + if !item.IsObject() { + return false + } + changed := false + + if content, ok := item.ObjectGet("content"); ok { + if content.IsNull() { + item.ObjectSet("content", ojson.NewArray()) + changed = true + } else if content.IsArray() { + for _, part := range content.ArrayItems() { + if normalizeContentPart(part) { + changed = true + } + } + } + } + + if summary, ok := item.ObjectGet("summary"); ok && summary.IsNull() { + item.ObjectSet("summary", ojson.NewArray()) + changed = true + } + + if normalizeFunctionArguments(item, "arguments") { + changed = true + } + + if fn, ok := item.ObjectGet("function"); ok && fn.IsObject() { + if normalizeFunctionArguments(fn, "arguments") { + changed = true + } + } + + if toolCalls, ok := item.ObjectGet("tool_calls"); ok && toolCalls.IsArray() { + for _, call := range toolCalls.ArrayItems() { + if normalizeToolCall(call) { + changed = true + } + } + } + + return changed +} + +func normalizeContentPart(part *ojson.Value) bool { + if !part.IsObject() { + return false + } + changed := false + + if text, ok := part.ObjectGet("text"); ok && text.IsNull() { + part.ObjectSet("text", ojson.NewString("")) + changed = true + } + if annotations, ok := part.ObjectGet("annotations"); ok && annotations.IsNull() { + part.ObjectSet("annotations", ojson.NewArray()) + changed = true + } + if logprobs, ok := part.ObjectGet("logprobs"); ok && logprobs.IsNull() { + part.ObjectSet("logprobs", ojson.NewArray()) + changed = true + } + + return changed +} + +func normalizeToolCall(call *ojson.Value) bool { + if !call.IsObject() { + return false + } + if fn, ok := call.ObjectGet("function"); ok && fn.IsObject() { + return normalizeFunctionArguments(fn, "arguments") + } + return false +} + +// normalizeFunctionArguments ports normalizeFunctionArguments + +// stringifyArguments: leaves an already-string `arguments` field untouched +// (mirrors the TS `normalized === value` no-op for strings), replaces +// null/undefined with the literal string "{}", and JSON.stringifies any +// other JSON value. +func normalizeFunctionArguments(target *ojson.Value, key string) bool { + v, ok := target.ObjectGet(key) + if !ok { + return false + } + if v.IsString() { + return false + } + normalized := "{}" + if !v.IsNull() { + normalized = string(v.Marshal()) + } + target.ObjectSet(key, ojson.NewString(normalized)) + return true +} diff --git a/edge/internal/fixer/response_output_normalizer_test.go b/edge/internal/fixer/response_output_normalizer_test.go new file mode 100644 index 0000000000..418950bb63 --- /dev/null +++ b/edge/internal/fixer/response_output_normalizer_test.go @@ -0,0 +1,74 @@ +package fixer + +import ( + "encoding/json" + "reflect" + "testing" +) + +type responseNormalizeCase struct { + Name string `json:"name"` + InputJSON string `json:"inputJson"` + ExpectedApplied bool `json:"expectedApplied"` + ExpectedOutputJSON string `json:"expectedOutputJson"` + ExpectedFixes []string `json:"expectedFixes"` +} + +func TestNormalizeResponseOutputFixtures(t *testing.T) { + var cases []responseNormalizeCase + loadFixture(t, "response-normalize.json", &cases) + + for _, c := range cases { + c := c + t.Run(c.Name, func(t *testing.T) { + out, applied := NormalizeResponseOutput([]byte(c.InputJSON)) + + if applied != c.ExpectedApplied { + t.Fatalf("applied = %v, want %v", applied, c.ExpectedApplied) + } + + // Compare parsed JSON (not bytes) since Go's ojson key order + // matches JS insertion order but the fixture's expectedOutputJson + // was produced by JSON.stringify on the same source object, so + // bytes should already match -- parsed comparison is the robust + // check either way. + var gotVal, wantVal interface{} + if err := json.Unmarshal(out, &gotVal); err != nil { + t.Fatalf("output is not valid JSON: %v (%s)", err, out) + } + if err := json.Unmarshal([]byte(c.ExpectedOutputJSON), &wantVal); err != nil { + t.Fatalf("fixture expectedOutputJson is not valid JSON: %v", err) + } + if !reflect.DeepEqual(gotVal, wantVal) { + t.Errorf("output mismatch.\ngot: %s\nwant: %s", out, c.ExpectedOutputJSON) + } + + if !applied { + // Unapplied cases must return the body unchanged (same bytes). + if string(out) != c.InputJSON { + t.Errorf("unapplied case returned different bytes.\ngot: %s\nwant: %s", out, c.InputJSON) + } + } + }) + } +} + +func TestNormalizeResponseOutputNonJSONBody(t *testing.T) { + out, applied := NormalizeResponseOutput([]byte("not json")) + if applied { + t.Fatalf("applied = true for non-JSON body") + } + if string(out) != "not json" { + t.Fatalf("body changed for non-JSON input: %q", out) + } +} + +func TestNormalizeResponseOutputNonObjectBody(t *testing.T) { + out, applied := NormalizeResponseOutput([]byte("[1,2,3]")) + if applied { + t.Fatalf("applied = true for array body") + } + if string(out) != "[1,2,3]" { + t.Fatalf("body changed for array input: %q", out) + } +} diff --git a/edge/internal/fixer/sse.go b/edge/internal/fixer/sse.go new file mode 100644 index 0000000000..6484eda638 --- /dev/null +++ b/edge/internal/fixer/sse.go @@ -0,0 +1,358 @@ +package fixer + +// SseFixer ports sse-fixer.ts. +type SseFixer struct{} + +func NewSseFixer() *SseFixer { return &SseFixer{} } + +const ( + lfByte byte = 0x0A + crByte byte = 0x0D +) + +var dataColon = []byte("data:") + +func isAsciiWhitespace(b byte) bool { + return b == 0x20 || b == 0x09 || b == 0x0A || b == 0x0D +} + +func startsWithBytes(data []byte, prefix []byte) bool { + if len(data) < len(prefix) { + return false + } + for i, p := range prefix { + if data[i] != p { + return false + } + } + return true +} + +func toLowerASCII(b byte) byte { + if b >= 0x41 && b <= 0x5A { + return b + 0x20 + } + return b +} + +func includesDataColon(data []byte) bool { + if len(data) < len(dataColon) { + return false + } + for i := 0; i <= len(data)-len(dataColon); i++ { + match := true + for j := range dataColon { + if data[i+j] != dataColon[j] { + match = false + break + } + } + if match { + return true + } + } + return false +} + +func looksLikeJSONLine(line []byte) bool { + i := 0 + for i < len(line) && isAsciiWhitespace(line[i]) { + i++ + } + if i >= len(line) { + return false + } + + first := line[i] + if first == 0x7B || first == 0x5B { // { or [ + return true + } + + done := []byte("[DONE]") + if len(line)-i >= len(done) { + for j := 0; j < len(done); j++ { + if line[i+j] != done[j] { + return false + } + } + return true + } + return false +} + +func fixDataLine(line []byte) ([]byte, bool) { + prefix := []byte("data:") + if !startsWithBytes(line, prefix) { + return line, false + } + after := line[len(prefix):] + if len(after) > 0 && after[0] == 0x20 { + return line, false + } + out := make([]byte, 0, len(prefix)+1+len(after)) + out = append(out, prefix...) + out = append(out, 0x20) + out = append(out, after...) + return out, true +} + +func fixFieldLine(line []byte, prefix []byte) ([]byte, bool) { + if !startsWithBytes(line, prefix) { + return line, false + } + after := line[len(prefix):] + if len(after) > 0 && after[0] == 0x20 { + return line, false + } + out := make([]byte, 0, len(prefix)+1+len(after)) + out = append(out, prefix...) + out = append(out, 0x20) + out = append(out, after...) + return out, true +} + +func tryFixMalformed(line []byte) ([]byte, bool, bool) { + // returns (line, applied, matched) -- matched=false means "no rule applied at all" (TS returns null) + dataPrefix := []byte("data") + if startsWithBytes(line, dataPrefix) { + rest := line[len(dataPrefix):] + colonPos := -1 + for i := 0; i < len(rest); i++ { + if rest[i] == 0x3A { + colonPos = i + break + } + } + if colonPos >= 0 { + ok := true + for i := 0; i < colonPos; i++ { + if !isAsciiWhitespace(rest[i]) { + ok = false + break + } + } + if ok { + afterColon := rest[colonPos+1:] + j := 0 + for j < len(afterColon) && afterColon[j] == 0x20 { + j++ + } + trimmed := afterColon[j:] + out := make([]byte, 0, 6+len(trimmed)) + out = append(out, 'd', 'a', 't', 'a', ':', ' ') + out = append(out, trimmed...) + return out, true, true + } + } + } + + // Pattern 2: Data:/DATA: etc. + if len(line) >= 5 { + l0 := toLowerASCII(line[0]) + l1 := toLowerASCII(line[1]) + l2 := toLowerASCII(line[2]) + l3 := toLowerASCII(line[3]) + l4 := toLowerASCII(line[4]) + if l0 == 'd' && l1 == 'a' && l2 == 't' && l3 == 'a' && l4 == ':' { + normalized := make([]byte, len(line)) + copy(normalized, []byte("data:")) + copy(normalized[5:], line[5:]) + fixed, applied := fixDataLine(normalized) + if applied { + return fixed, true, true + } + return normalized, true, true + } + } + + return nil, false, false +} + +func (f *SseFixer) CanFix(data []byte) bool { + if startsWithBytes(data, []byte("data:")) || + startsWithBytes(data, []byte("event:")) || + startsWithBytes(data, []byte("id:")) || + startsWithBytes(data, []byte("retry:")) || + startsWithBytes(data, []byte(":")) { + return true + } + + if len(data) >= 4 { + b0 := toLowerASCII(data[0]) + b1 := toLowerASCII(data[1]) + b2 := toLowerASCII(data[2]) + b3 := toLowerASCII(data[3]) + if b0 == 'd' && b1 == 'a' && b2 == 't' && b3 == 'a' { + return true + } + } + + if looksLikeJSONLine(data) { + return true + } + + return includesDataColon(data) +} + +func (f *SseFixer) fixLine(line []byte) ([]byte, bool) { + if startsWithBytes(line, []byte("data:")) { + return fixDataLine(line) + } + if startsWithBytes(line, []byte("event:")) { + return fixFieldLine(line, []byte("event:")) + } + if startsWithBytes(line, []byte("id:")) { + return fixFieldLine(line, []byte("id:")) + } + if startsWithBytes(line, []byte("retry:")) { + return fixFieldLine(line, []byte("retry:")) + } + if startsWithBytes(line, []byte(":")) { + return line, false + } + + if looksLikeJSONLine(line) { + out := make([]byte, 0, 6+len(line)) + out = append(out, 'd', 'a', 't', 'a', ':', ' ') + out = append(out, line...) + return out, true + } + + if fixed, applied, matched := tryFixMalformed(line); matched { + return fixed, applied + } + + return line, false +} + +// Fix mirrors SseFixer.fix. +func (f *SseFixer) Fix(input []byte) FixResult { + if !f.CanFix(input) { + return FixResult{Data: input, Applied: false} + } + + var out [][]byte + cursor := 0 + changed := false + lastWasEmpty := false + + pos := 0 + for pos < len(input) { + start := pos + scan := start + lineEnd := len(input) + nextPos := len(input) + newlineNormalized := false + + for scan < len(input) { + b := input[scan] + if b == lfByte { + lineEnd = scan + nextPos = scan + 1 + break + } + if b == crByte { + lineEnd = scan + nextPos = scan + 1 + if nextPos < len(input) && input[nextPos] == lfByte { + nextPos++ + } + newlineNormalized = true + break + } + scan++ + } + + if nextPos == len(input) && lineEnd == len(input) { + newlineNormalized = true + } + + pos = nextPos + line := input[start:lineEnd] + + if len(line) == 0 { + if lastWasEmpty { + changed = true + if out == nil { + if start > 0 { + out = append(out, input[0:start]) + } + cursor = start + } else if cursor < start { + out = append(out, input[cursor:start]) + cursor = start + } + cursor = pos + } else if newlineNormalized { + changed = true + if out == nil { + if start > 0 { + out = append(out, input[0:start]) + } + cursor = start + } else if cursor < start { + out = append(out, input[cursor:start]) + cursor = start + } + out = append(out, []byte{lfByte}) + cursor = pos + } else if out != nil { + out = append(out, input[cursor:pos]) + cursor = pos + } + lastWasEmpty = true + continue + } + lastWasEmpty = false + + fixedLine, applied := f.fixLine(line) + segmentChanged := applied || newlineNormalized + if segmentChanged { + changed = true + } + + if segmentChanged { + if out == nil { + if start > 0 { + out = append(out, input[0:start]) + } + cursor = start + } else if cursor < start { + out = append(out, input[cursor:start]) + cursor = start + } + if applied { + out = append(out, fixedLine) + } else { + out = append(out, line) + } + out = append(out, []byte{lfByte}) + cursor = pos + } else if out != nil { + out = append(out, input[cursor:pos]) + cursor = pos + } + } + + if out == nil { + return FixResult{Data: input, Applied: false} + } + + if cursor < len(input) { + out = append(out, input[cursor:]) + } + + return FixResult{Data: concatBytes(out), Applied: changed} +} + +func concatBytes(chunks [][]byte) []byte { + total := 0 + for _, c := range chunks { + total += len(c) + } + out := make([]byte, 0, total) + for _, c := range chunks { + out = append(out, c...) + } + return out +} diff --git a/edge/internal/fixer/sse_test.go b/edge/internal/fixer/sse_test.go new file mode 100644 index 0000000000..c7417b5899 --- /dev/null +++ b/edge/internal/fixer/sse_test.go @@ -0,0 +1,34 @@ +package fixer + +import ( + "bytes" + "testing" +) + +type sseCase struct { + Name string `json:"name"` + InputB64 string `json:"inputB64"` + ExpectedDataB64 string `json:"expectedDataB64"` + ExpectedApplied bool `json:"expectedApplied"` +} + +func TestSseFixerFixtures(t *testing.T) { + var cases []sseCase + loadFixture(t, "sse.json", &cases) + + for _, c := range cases { + c := c + t.Run(c.Name, func(t *testing.T) { + input := mustB64(t, c.InputB64) + expected := mustB64(t, c.ExpectedDataB64) + + res := NewSseFixer().Fix(input) + if res.Applied != c.ExpectedApplied { + t.Errorf("Applied = %v, want %v", res.Applied, c.ExpectedApplied) + } + if !bytes.Equal(res.Data, expected) { + t.Errorf("Data = %q, want %q", res.Data, expected) + } + }) + } +} diff --git a/edge/internal/fixer/stream.go b/edge/internal/fixer/stream.go new file mode 100644 index 0000000000..228e8de77b --- /dev/null +++ b/edge/internal/fixer/stream.go @@ -0,0 +1,377 @@ +package fixer + +import ( + "time" + + "github.com/ding113/claude-code-hub/edge/internal/contract" +) + +// chunkBuffer ports the TS ChunkBuffer class: it accumulates chunks and +// tracks the index up to which the buffered bytes end on a complete line +// (LF, or CRLF split across chunk boundaries). +type chunkBuffer struct { + buf []byte + pendingCR bool + processableEnd int +} + +func (b *chunkBuffer) length() int { return len(b.buf) } + +func (b *chunkBuffer) push(chunk []byte) { + if len(chunk) == 0 { + return + } + prevTotal := len(b.buf) + b.buf = append(b.buf, chunk...) + + if b.pendingCR { + if chunk[0] == lfByte { + b.processableEnd = prevTotal + 1 + } else { + b.processableEnd = prevTotal + } + b.pendingCR = false + } + + for i := 0; i < len(chunk); i++ { + c := chunk[i] + if c == lfByte { + b.processableEnd = prevTotal + i + 1 + continue + } + if c != crByte { + continue + } + if i+1 < len(chunk) { + if chunk[i+1] != lfByte { + b.processableEnd = prevTotal + i + 1 + } + continue + } + b.pendingCR = true + } +} + +func (b *chunkBuffer) findProcessableEnd() int { + if len(b.buf) == 0 { + return 0 + } + if b.pendingCR { + return 0 + } + return b.processableEnd +} + +func (b *chunkBuffer) take(size int) []byte { + if size <= 0 { + return nil + } + if size > len(b.buf) { + panic("chunkBuffer.take size exceeds buffered length") + } + out := make([]byte, size) + copy(out, b.buf[:size]) + b.buf = b.buf[size:] + b.processableEnd -= size + if b.processableEnd < 0 { + b.processableEnd = 0 + } + return out +} + +func (b *chunkBuffer) drain() []byte { + out := b.take(len(b.buf)) + b.clear() + return out +} + +func (b *chunkBuffer) clear() { + b.buf = nil + b.pendingCR = false + b.processableEnd = 0 +} + +// StreamFixer ports ResponseFixer.processStream's TransformStream pipeline +// (minus filterInertResponsesChatCompletionChunks and +// normalizeResponseOutput, which are Responses-API-only / out of scope). +type StreamFixer struct { + cfg Config + + encodingFixer *EncodingFixer + sseFixer *SseFixer + jsonFixer *JsonFixer + + buf chunkBuffer + passthrough bool + + totalBytesProcessed int64 + + appliedEncoding bool + encodingDetails string + appliedSse bool + sseDetails string + appliedJSON bool + jsonDetails string + + start time.Time +} + +func NewStreamFixer(cfg Config) *StreamFixer { + f := &StreamFixer{cfg: cfg, start: time.Now()} + if cfg.FixEncoding { + f.encodingFixer = NewEncodingFixer() + } + if cfg.FixSseFormat { + f.sseFixer = NewSseFixer() + } + if cfg.FixTruncatedJSON { + f.jsonFixer = NewJsonFixer(cfg.MaxJSONDepth, cfg.MaxFixSize) + } + return f +} + +// Write mirrors the transform() step: bytes ready for the client (may be nil/empty). +func (f *StreamFixer) Write(chunk []byte) []byte { + f.totalBytesProcessed += int64(len(chunk)) + + if f.passthrough { + return chunk + } + + if f.buf.length()+len(chunk) > f.cfg.MaxFixSize { + f.passthrough = true + out := f.buf.drain() + out = append(out, chunk...) + return out + } + + f.buf.push(chunk) + + end := f.buf.findProcessableEnd() + if end <= 0 { + return nil + } + + return f.applyStreamFixers(f.buf.take(end)) +} + +// Flush mirrors the flush() step: call once at upstream EOF. +func (f *StreamFixer) Flush() []byte { + if f.buf.length() > 0 { + return f.applyStreamFixers(f.buf.drain()) + } + return nil +} + +func (f *StreamFixer) applyStreamFixers(input []byte) []byte { + data := input + + if f.encodingFixer != nil { + res := f.encodingFixer.Fix(data) + if res.Applied { + f.appliedEncoding = true + if f.encodingDetails == "" { + f.encodingDetails = res.Details + } + data = res.Data + } + } + + if f.sseFixer != nil { + res := f.sseFixer.Fix(data) + if res.Applied { + f.appliedSse = true + if f.sseDetails == "" { + f.sseDetails = res.Details + } + data = res.Data + } + } + + if f.jsonFixer != nil { + fixed, applied, details := fixSseJSONLines(data, f.jsonFixer) + if applied { + f.appliedJSON = true + if f.jsonDetails == "" { + f.jsonDetails = details + } + data = fixed + } + } + + // filterInertResponsesChatCompletionChunks is the fixed final step + // (response format only); the audit counts it as an "sse" fix, same as + // TS (see ResponseFixer.processStream's applyStreamFixers). + if filtered := filterInertResponsesChatCompletionChunks(f.cfg.Format, data); filtered.Applied { + f.appliedSse = true + if f.sseDetails == "" { + f.sseDetails = filtered.Details + } + data = filtered.Data + } + + return data +} + +func detailsPtr(s string) *string { + if s == "" { + return nil + } + return &s +} + +// Audit mirrors buildFixersApplied(applied, includeSse=true) plus hit / +// totalBytesProcessed / processingTimeMs bookkeeping. +func (f *StreamFixer) Audit() contract.FixerAudit { + hit := f.appliedEncoding || f.appliedSse || f.appliedJSON + fa := []contract.FixerApplied{ + {Fixer: "encoding", Applied: f.appliedEncoding, Details: detailsPtr(f.encodingDetails)}, + {Fixer: "sse", Applied: f.appliedSse, Details: detailsPtr(f.sseDetails)}, + {Fixer: "json", Applied: f.appliedJSON, Details: detailsPtr(f.jsonDetails)}, + } + ms := time.Since(f.start).Milliseconds() + if ms < 0 { + ms = 0 + } + return contract.FixerAudit{ + Hit: hit, + FixersApplied: fa, + TotalBytesProcessed: f.totalBytesProcessed, + ProcessingTimeMs: ms, + } +} + +// fixSseJSONLines ports ResponseFixer.fixSseJsonLines: applies the JSON +// fixer only to (LF-terminated) lines that look like an SSE `data:` line. +func fixSseJSONLines(data []byte, jsonFixer *JsonFixer) (out []byte, applied bool, details string) { + var chunks [][]byte + cursor := 0 + lineStart := 0 + + for i := 0; i < len(data); i++ { + if data[i] != lfByte { + continue + } + line := data[lineStart:i] + fixedLine, lineApplied := fixMaybeDataJSONLine(line, jsonFixer) + + if !lineApplied { + if chunks != nil { + chunks = append(chunks, data[cursor:i+1]) + cursor = i + 1 + } + lineStart = i + 1 + continue + } + + applied = true + if chunks == nil { + chunks = [][]byte{} + } + if cursor < lineStart { + chunks = append(chunks, data[cursor:lineStart]) + } + chunks = append(chunks, fixedLine) + chunks = append(chunks, []byte{lfByte}) + cursor = i + 1 + lineStart = i + 1 + } + + if lineStart < len(data) { + line := data[lineStart:] + fixedLine, lineApplied := fixMaybeDataJSONLine(line, jsonFixer) + + if !lineApplied { + if chunks != nil { + chunks = append(chunks, data[cursor:]) + } + } else { + applied = true + if chunks == nil { + chunks = [][]byte{} + } + if cursor < lineStart { + chunks = append(chunks, data[cursor:lineStart]) + } + chunks = append(chunks, fixedLine) + } + } + + if chunks == nil { + return data, false, "" + } + return concatBytes(chunks), applied, "" +} + +var sseDataPrefixWithSpace = []byte("data: ") + +func fixMaybeDataJSONLine(line []byte, jsonFixer *JsonFixer) ([]byte, bool) { + prefix := dataColon + if len(line) < len(prefix) { + return line, false + } + if !startsWithBytes(line, prefix) { + return line, false + } + + payloadStart := len(prefix) + if payloadStart < len(line) && line[payloadStart] == 0x20 { + payloadStart++ + } + + payload := line[payloadStart:] + res := jsonFixer.Fix(payload) + if !res.Applied { + return line, false + } + + out := make([]byte, 0, len(sseDataPrefixWithSpace)+len(res.Data)) + out = append(out, sseDataPrefixWithSpace...) + out = append(out, res.Data...) + return out, true +} + +// FixNonStream mirrors ResponseFixer.processNonStream's fixer sequence +// (encoding -> json, applied once to the whole body). +func FixNonStream(body []byte, cfg Config) ([]byte, contract.FixerAudit) { + start := time.Now() + + data := body + appliedEncoding, encodingDetails := false, "" + appliedJSON, jsonDetails := false, "" + + if cfg.FixEncoding { + res := NewEncodingFixer().Fix(data) + if res.Applied { + appliedEncoding = true + encodingDetails = res.Details + data = res.Data + } + } + + if cfg.FixTruncatedJSON { + res := NewJsonFixer(cfg.MaxJSONDepth, cfg.MaxFixSize).Fix(data) + if res.Applied { + appliedJSON = true + jsonDetails = res.Details + data = res.Data + } + } + + hit := appliedEncoding || appliedJSON + fa := []contract.FixerApplied{ + {Fixer: "encoding", Applied: appliedEncoding, Details: detailsPtr(encodingDetails)}, + {Fixer: "json", Applied: appliedJSON, Details: detailsPtr(jsonDetails)}, + } + ms := time.Since(start).Milliseconds() + if ms < 0 { + ms = 0 + } + + return data, contract.FixerAudit{ + Hit: hit, + FixersApplied: fa, + TotalBytesProcessed: int64(len(body)), + ProcessingTimeMs: ms, + } +} diff --git a/edge/internal/fixer/stream_test.go b/edge/internal/fixer/stream_test.go new file mode 100644 index 0000000000..11e6c1ab16 --- /dev/null +++ b/edge/internal/fixer/stream_test.go @@ -0,0 +1,168 @@ +package fixer + +import ( + "bytes" + "testing" +) + +type streamFixtureCfg struct { + FixEncoding bool `json:"fixEncoding"` + FixSseFormat bool `json:"fixSseFormat"` + FixTruncatedJSON bool `json:"fixTruncatedJson"` + MaxJSONDepth int `json:"maxJsonDepth"` + MaxFixSize int `json:"maxFixSize"` + Format string `json:"format"` +} + +type streamCase struct { + Name string `json:"name"` + FullInputB64 string `json:"fullInputB64"` + SplitPattern string `json:"splitPattern"` + ChunksB64 []string `json:"chunksB64"` + Cfg streamFixtureCfg `json:"cfg"` + ExpectedOutputB64 string `json:"expectedOutputB64"` +} + +func TestStreamFixerFixtures(t *testing.T) { + var cases []streamCase + loadFixture(t, "stream.json", &cases) + + for _, c := range cases { + c := c + t.Run(c.Name, func(t *testing.T) { + cfg := Config{ + FixEncoding: c.Cfg.FixEncoding, + FixSseFormat: c.Cfg.FixSseFormat, + FixTruncatedJSON: c.Cfg.FixTruncatedJSON, + MaxJSONDepth: c.Cfg.MaxJSONDepth, + MaxFixSize: c.Cfg.MaxFixSize, + Format: c.Cfg.Format, + } + + sf := NewStreamFixer(cfg) + var out []byte + for _, chunkB64 := range c.ChunksB64 { + chunk := mustB64(t, chunkB64) + out = append(out, sf.Write(chunk)...) + } + out = append(out, sf.Flush()...) + + expected := mustB64(t, c.ExpectedOutputB64) + if !bytes.Equal(out, expected) { + t.Errorf("output = %q, want %q", out, expected) + } + + audit := sf.Audit() + var totalIn int + for _, chunkB64 := range c.ChunksB64 { + totalIn += len(mustB64(t, chunkB64)) + } + if audit.TotalBytesProcessed != int64(totalIn) { + t.Errorf("TotalBytesProcessed = %d, want %d", audit.TotalBytesProcessed, totalIn) + } + if audit.ProcessingTimeMs < 0 { + t.Errorf("ProcessingTimeMs = %d, want >= 0", audit.ProcessingTimeMs) + } + if len(audit.FixersApplied) != 3 { + t.Fatalf("FixersApplied len = %d, want 3", len(audit.FixersApplied)) + } + names := []string{"encoding", "sse", "json"} + for i, n := range names { + if audit.FixersApplied[i].Fixer != n { + t.Errorf("FixersApplied[%d].Fixer = %q, want %q", i, audit.FixersApplied[i].Fixer, n) + } + } + }) + } +} + +// TestStreamFixerSplitInvariance is a Go-only test (no TS fixture): feeding +// the same overall bytes through the stream pipeline in different chunk +// splits must produce identical final output and audit. +func TestStreamFixerSplitInvariance(t *testing.T) { + full := []byte( + "event: message_start\ndata: {\"type\":\"message_start\"}\n\n" + + "event: content_block_delta\ndata:{\"type\":\"content_block_delta\",\"delta\":{\"text\":\"hello world\"}}\n\n" + + "event: message_stop\ndata: {\"type\":\"message_stop\"\n\n", // intentionally truncated JSON + ) + + cfg := Config{ + FixEncoding: true, + FixSseFormat: true, + FixTruncatedJSON: true, + MaxJSONDepth: 200, + MaxFixSize: 1024 * 1024, + } + + splits := [][]int{ + {len(full)}, // single chunk + splitEvery(full, 1), // byte at a time + splitEvery(full, 3), + splitEvery(full, 17), + {10, 25, 40, len(full)}, + } + + var referenceOut []byte + var referenceAudit []string + + for i, sizes := range splits { + sf := NewStreamFixer(cfg) + var out []byte + offset := 0 + for _, size := range sizes { + if offset >= len(full) { + break + } + end := offset + size + if end > len(full) { + end = len(full) + } + out = append(out, sf.Write(full[offset:end])...) + offset = end + } + out = append(out, sf.Flush()...) + + audit := sf.Audit() + auditSig := []string{} + for _, fa := range audit.FixersApplied { + details := "" + if fa.Details != nil { + details = *fa.Details + } + auditSig = append(auditSig, fa.Fixer+"="+boolStr(fa.Applied)+":"+details) + } + + if i == 0 { + referenceOut = out + referenceAudit = auditSig + continue + } + + if !bytes.Equal(out, referenceOut) { + t.Errorf("split %d: output differs from reference.\ngot: %q\nwant: %q", i, out, referenceOut) + } + if len(auditSig) != len(referenceAudit) { + t.Fatalf("split %d: audit signature length differs", i) + } + for j := range auditSig { + if auditSig[j] != referenceAudit[j] { + t.Errorf("split %d: audit[%d] = %q, want %q", i, j, auditSig[j], referenceAudit[j]) + } + } + } +} + +func splitEvery(data []byte, n int) []int { + var out []int + for i := 0; i < len(data); i += n { + out = append(out, n) + } + return out +} + +func boolStr(b bool) string { + if b { + return "true" + } + return "false" +} diff --git a/edge/internal/fixer/types.go b/edge/internal/fixer/types.go new file mode 100644 index 0000000000..ae7c492707 --- /dev/null +++ b/edge/internal/fixer/types.go @@ -0,0 +1,28 @@ +// Package fixer ports the TypeScript response-fixer pipeline +// (src/app/v1/_lib/proxy/response-fixer/*) to Go, stdlib only. +package fixer + +// FixResult mirrors the TS FixResult shape. Details == "" means +// "no details" (the TS field was omitted). +type FixResult struct { + Data []byte + Applied bool + Details string +} + +// Config mirrors ResponseFixerConfig in src/types/system-config.ts, plus a +// Format field (not part of the TS config type) that selects the +// format-specific extra steps ResponseFixer.process applies based on +// session.originalFormat. Format == "" or "claude" is the original, +// format-agnostic behaviour; Format == "response" additionally applies +// filterInertResponsesChatCompletionChunks to streamed chunks (see +// StreamFixer.applyStreamFixers / filterInertResponsesChatCompletionChunks +// in response-fixer/index.ts). +type Config struct { + FixTruncatedJSON bool + FixSseFormat bool + FixEncoding bool + MaxJSONDepth int + MaxFixSize int + Format string +} diff --git a/edge/internal/fixer/utf8_decoder.go b/edge/internal/fixer/utf8_decoder.go new file mode 100644 index 0000000000..bd58843065 --- /dev/null +++ b/edge/internal/fixer/utf8_decoder.go @@ -0,0 +1,130 @@ +package fixer + +// decodeUTF8Lossy re-encodes data as valid UTF-8, replacing each maximal +// invalid subsequence with exactly one U+FFFD, per the WHATWG Encoding +// Standard's UTF-8 decoder algorithm: +// https://encoding.spec.whatwg.org/#utf-8-decoder +// +// This matches JavaScript's `new TextDecoder("utf-8").decode(...)` (fatal: +// false) behavior, which differs from a naive Go utf8.DecodeRune loop in +// some multi-byte error cases (e.g. an overlong-encoding lead byte followed +// by a valid continuation byte for a *different* sequence: WHATWG emits one +// replacement for the lead byte and reprocesses the continuation byte, +// whereas some naive decoders emit one replacement per invalid byte). +const replacementChar = "�" + +func decodeUTF8Lossy(data []byte) []byte { + out := make([]byte, 0, len(data)) + + var ( + codePoint int32 + bytesSeen int + bytesNeeded int + lowerBound byte = 0x80 + upperBound byte = 0xBF + ) + + reset := func() { + codePoint = 0 + bytesSeen = 0 + bytesNeeded = 0 + lowerBound = 0x80 + upperBound = 0xBF + } + + i := 0 + for i < len(data) { + b := data[i] + + if bytesNeeded == 0 { + switch { + case b <= 0x7F: + out = append(out, b) + i++ + case b >= 0xC2 && b <= 0xDF: + bytesNeeded = 1 + codePoint = int32(b & 0x1F) + i++ + case b >= 0xE0 && b <= 0xEF: + if b == 0xE0 { + lowerBound = 0xA0 + } + if b == 0xED { + upperBound = 0x9F + } + bytesNeeded = 2 + codePoint = int32(b & 0x0F) + i++ + case b >= 0xF0 && b <= 0xF4: + if b == 0xF0 { + lowerBound = 0x90 + } + if b == 0xF4 { + upperBound = 0x8F + } + bytesNeeded = 3 + codePoint = int32(b & 0x07) + i++ + default: + // Invalid lead byte: single error, consume the byte. + out = append(out, replacementChar...) + i++ + } + continue + } + + if b < lowerBound || b > upperBound { + // Error; reset state and reprocess this same byte (do not advance i). + reset() + out = append(out, replacementChar...) + continue + } + + lowerBound = 0x80 + upperBound = 0xBF + codePoint = (codePoint << 6) | int32(b&0x3F) + bytesSeen++ + i++ + if bytesSeen != bytesNeeded { + continue + } + + out = appendRune(out, codePoint) + reset() + } + + // End of input with an incomplete sequence pending: one error. + if bytesNeeded != 0 { + out = append(out, replacementChar...) + } + + return out +} + +// appendRune appends the UTF-8 encoding of a validated code point (already +// known to be in range and not a surrogate, by construction of the decoder +// above) to out. +func appendRune(out []byte, r int32) []byte { + switch { + case r <= 0x7F: + return append(out, byte(r)) + case r <= 0x7FF: + return append(out, + byte(0xC0|(r>>6)), + byte(0x80|(r&0x3F)), + ) + case r <= 0xFFFF: + return append(out, + byte(0xE0|(r>>12)), + byte(0x80|((r>>6)&0x3F)), + byte(0x80|(r&0x3F)), + ) + default: + return append(out, + byte(0xF0|(r>>18)), + byte(0x80|((r>>12)&0x3F)), + byte(0x80|((r>>6)&0x3F)), + byte(0x80|(r&0x3F)), + ) + } +} diff --git a/edge/internal/gate/budget.go b/edge/internal/gate/budget.go new file mode 100644 index 0000000000..32eb095fa5 --- /dev/null +++ b/edge/internal/gate/budget.go @@ -0,0 +1,219 @@ +package gate + +import ( + "context" + "errors" + "fmt" + "sync" + "time" +) + +// ErrLocalCapacity is returned when a Budget cannot grant a reservation +// because the process-wide prebuffer budget is exhausted and the caller has +// waited past the queue depth or wait timeout. +// +// This is a simplified, single-tier stand-in for the TypeScript +// StreamGatePrebufferBudget (prebuffer-budget.ts), which layers a local +// queue on top of a shared MemoryGovernor lease. Go's Budget only implements +// the local layer: a process-wide byte ceiling with FIFO waiting. +var ErrLocalCapacity = errors.New("gate: local prebuffer capacity exhausted") + +// defaultAcquireTimeout bounds how long Acquire waits in its FIFO queue when +// ctx carries no deadline, mirroring the TS budget's 20s default. +const defaultAcquireTimeout = 20 * time.Second + +// maxQueueDepth bounds the number of waiters queued behind the budget, +// mirroring the TS budget's waitingCount >= 1024 rejection. +const maxQueueDepth = 1024 + +// Budget is a process-wide shared byte budget for stream-gate prebuffering. +// It is safe for concurrent use. +type Budget struct { + mu sync.Mutex + limit int64 + reserved int64 + waiters []*waiter +} + +type waiter struct { + bytes int64 + ch chan struct{} +} + +// NewBudget creates a Budget with the given byte ceiling. limitBytes <= 0 +// means unlimited (Acquire never blocks or fails for capacity reasons). +func NewBudget(limitBytes int64) *Budget { + return &Budget{limit: limitBytes} +} + +// Lease represents a granted, growable/shrinkable reservation against a +// Budget. It is safe for concurrent use. +type Lease struct { + b *Budget + mu sync.Mutex + bytes int64 + released bool +} + +// Acquire reserves bytes from the budget, waiting (FIFO, honoring ctx) if +// the budget is currently full. It returns ErrLocalCapacity if the queue is +// full or the wait exceeds its timeout, or ctx.Err() if ctx is cancelled +// first. +func (b *Budget) Acquire(ctx context.Context, bytes int64) (*Lease, error) { + if bytes <= 0 { + return nil, fmt.Errorf("gate: budget reservation must be positive, got %d", bytes) + } + if b.limit > 0 && bytes > b.limit { + return nil, fmt.Errorf("gate: reservation %d exceeds budget limit %d", bytes, b.limit) + } + if err := ctx.Err(); err != nil { + return nil, err + } + + b.mu.Lock() + if b.limit <= 0 || (len(b.waiters) == 0 && b.reserved+bytes <= b.limit) { + b.reserved += bytes + b.mu.Unlock() + return &Lease{b: b, bytes: bytes}, nil + } + if len(b.waiters) >= maxQueueDepth { + b.mu.Unlock() + return nil, ErrLocalCapacity + } + w := &waiter{bytes: bytes, ch: make(chan struct{})} + b.waiters = append(b.waiters, w) + b.mu.Unlock() + + timeout := defaultAcquireTimeout + if deadline, ok := ctx.Deadline(); ok { + if remaining := time.Until(deadline); remaining < timeout { + timeout = remaining + } + } + timer := time.NewTimer(timeout) + defer timer.Stop() + + select { + case <-w.ch: + return &Lease{b: b, bytes: bytes}, nil + case <-ctx.Done(): + if b.removeWaiter(w) { + return nil, ctx.Err() + } + // Lost the race with drain(): the waiter was already granted. + <-w.ch + return &Lease{b: b, bytes: bytes}, nil + case <-timer.C: + if b.removeWaiter(w) { + return nil, ErrLocalCapacity + } + <-w.ch + return &Lease{b: b, bytes: bytes}, nil + } +} + +func (b *Budget) removeWaiter(w *waiter) bool { + b.mu.Lock() + defer b.mu.Unlock() + for i, x := range b.waiters { + if x == w { + b.waiters = append(b.waiters[:i], b.waiters[i+1:]...) + return true + } + } + return false +} + +// drain grants queued waiters in strict FIFO order: a waiter that does not +// currently fit blocks the whole queue behind it (matching the TS budget, +// which does not let smaller later waiters jump ahead). +func (b *Budget) drain() { + b.mu.Lock() + defer b.mu.Unlock() + for len(b.waiters) > 0 { + head := b.waiters[0] + if b.limit > 0 && b.reserved+head.bytes > b.limit { + return + } + b.reserved += head.bytes + b.waiters = b.waiters[1:] + close(head.ch) + } +} + +// ReservedBytes returns the lease's currently reserved size. +func (l *Lease) ReservedBytes() int64 { + l.mu.Lock() + defer l.mu.Unlock() + return l.bytes +} + +// Grow attempts to grow the lease to bytes total (a no-op, returning true, +// if bytes <= current size). It fails without blocking if the budget has no +// room. +func (l *Lease) Grow(bytes int64) bool { + l.mu.Lock() + defer l.mu.Unlock() + if l.released { + return false + } + if bytes <= l.bytes { + return true + } + delta := bytes - l.bytes + l.b.mu.Lock() + if l.b.limit > 0 && l.b.reserved+delta > l.b.limit { + l.b.mu.Unlock() + return false + } + l.b.reserved += delta + l.b.mu.Unlock() + l.bytes = bytes + return true +} + +// ShrinkTo reduces the lease to bytes total, releasing the difference back +// to the budget and waking any waiters it can now satisfy. It is a no-op if +// bytes >= the current size. +func (l *Lease) ShrinkTo(bytes int64) { + l.mu.Lock() + if l.released || bytes >= l.bytes { + l.mu.Unlock() + return + } + delta := l.bytes - bytes + l.bytes = bytes + l.mu.Unlock() + + l.b.mu.Lock() + l.b.reserved -= delta + l.b.mu.Unlock() + l.b.drain() +} + +// Release returns the lease's entire reservation to the budget. It is +// idempotent. +func (l *Lease) Release() { + l.mu.Lock() + if l.released { + l.mu.Unlock() + return + } + l.released = true + bytes := l.bytes + l.bytes = 0 + l.mu.Unlock() + + l.b.mu.Lock() + l.b.reserved -= bytes + l.b.mu.Unlock() + l.b.drain() +} + +// Snapshot reports the budget's current reservation and queue depth +// (primarily for tests/diagnostics). +func (b *Budget) Snapshot() (reservedBytes int64, waiting int, limit int64) { + b.mu.Lock() + defer b.mu.Unlock() + return b.reserved, len(b.waiters), b.limit +} diff --git a/edge/internal/gate/classifier.go b/edge/internal/gate/classifier.go new file mode 100644 index 0000000000..c228c6843f --- /dev/null +++ b/edge/internal/gate/classifier.go @@ -0,0 +1,804 @@ +// Package gate is a Go port of the TypeScript stream content gate and +// protocol observer: +// - src/app/v1/_lib/proxy/stream-gate/frame-classifier.ts +// - src/app/v1/_lib/proxy/stream-gate/stream-content-gate.ts +// - src/app/v1/_lib/proxy/stream-gate/stream-protocol-observer.ts +// - src/app/v1/_lib/proxy/stream-gate/prebuffer-budget.ts (simplified) +// +// See doc comments on individual types for behavioral notes and documented +// deviations from the TypeScript source. +package gate + +import ( + "encoding/json" + "io" + "strconv" + "strings" +) + +// Family mirrors ProtocolFamily in frame-classifier.ts. +type Family string + +const ( + FamilyAnthropic Family = "anthropic" + FamilyOpenAIChat Family = "openai-chat" + FamilyOpenAIResponses Family = "openai-responses" + FamilyGemini Family = "gemini" +) + +// Verdict mirrors FrameVerdict in frame-classifier.ts. +type Verdict string + +const ( + VerdictContent Verdict = "content" + VerdictError Verdict = "error" + VerdictMalformed Verdict = "malformed" + VerdictTerminal Verdict = "terminal" + VerdictNeutral Verdict = "neutral" +) + +// Classification is the result of classifying one complete SSE frame. +type Classification struct { + Verdict Verdict + // AcceptTerminal is true only for openai-responses frames whose terminal + // kind is a clean "completed" (status=="completed", no non-empty error) + // or "incomplete" completion: these are protocol-legitimate terminations + // the gate should commit/pass through rather than treat as empty_stream. + AcceptTerminal bool + // IsEcho reports whether this frame is a provider "request echo" frame + // (see isRequestEchoFrame in the TS source): such frames' bytes are + // excluded from the gate's prebuffer byte-cap accounting. + IsEcho bool + // TerminalKind is "complete", "incomplete", or "" (no terminal signal in + // this frame). It can be set even when Verdict is "content" (e.g. a + // Responses response.completed frame that also carries compaction + // content), mirroring classifyStructuredTerminalKind being evaluated + // independently of the verdict in the TS observer. + TerminalKind string +} + +type valueMatch struct { + path string + values []string +} + +type frameRule struct { + eventTypes []string + anyPaths []string + valueMatches []valueMatch +} + +type streamSignal struct { + contentRules []frameRule + errorRules []frameRule + terminalRules []frameRule + terminalEvents []string + doneSentinel string +} + +// streamSignals is a direct port of STREAM_SIGNALS in frame-classifier.ts. +var streamSignals = map[Family]streamSignal{ + FamilyAnthropic: { + contentRules: []frameRule{ + { + eventTypes: []string{"content_block_delta"}, + anyPaths: []string{ + "delta.text", + "delta.partial_json", + "delta.thinking", + "delta.signature", + "delta.citation", + }, + }, + { + eventTypes: []string{"content_block_start"}, + anyPaths: []string{ + "content_block.data", + "content_block.content", + "content_block.file_id", + "content_block.fileId", + "content_block.url", + "content_block.result", + }, + valueMatches: []valueMatch{ + { + path: "content_block.type", + values: []string{ + "redacted_thinking", + "web_search_tool_result", + "web_fetch_tool_result", + "code_execution_tool_result", + "bash_code_execution_tool_result", + "text_editor_code_execution_tool_result", + "tool_search_tool_result", + "mcp_tool_result", + "container_upload", + }, + }, + }, + }, + }, + errorRules: []frameRule{ + {eventTypes: []string{"error", "response.error"}}, + {anyPaths: []string{"error"}}, + }, + terminalEvents: []string{"message_stop"}, + }, + FamilyOpenAIChat: { + contentRules: []frameRule{ + { + anyPaths: []string{ + "choices.#.delta.content", + "choices.#.delta.reasoning_content", + "choices.#.delta.tool_calls.#.function.arguments", + "choices.#.delta.function_call.arguments", + "choices.#.delta.refusal", + "choices.#.delta.audio.data", + "choices.#.delta.audio.transcript", + }, + }, + }, + errorRules: []frameRule{ + {anyPaths: []string{"error"}}, + }, + doneSentinel: "[DONE]", + }, + FamilyOpenAIResponses: { + contentRules: []frameRule{ + { + eventTypes: []string{ + "response.output_text.delta", + "response.refusal.delta", + "response.reasoning_text.delta", + "response.reasoning_summary_text.delta", + "response.audio.delta", + "response.audio.transcript.delta", + "response.function_call_arguments.delta", + "response.custom_tool_call_input.delta", + "response.code_interpreter_call_code.delta", + "response.mcp_call_arguments.delta", + }, + anyPaths: []string{"delta"}, + }, + { + eventTypes: []string{"response.image_generation_call.partial_image"}, + anyPaths: []string{"partial_image_b64"}, + }, + { + eventTypes: []string{ + "response.output_text.done", + "response.reasoning_text.done", + "response.reasoning_summary_text.done", + }, + anyPaths: []string{"text"}, + }, + { + eventTypes: []string{"response.audio.transcript.done"}, + anyPaths: []string{"transcript", "text"}, + }, + { + eventTypes: []string{"response.refusal.done"}, + anyPaths: []string{"refusal"}, + }, + { + eventTypes: []string{"response.function_call_arguments.done", "response.mcp_call_arguments.done"}, + anyPaths: []string{"arguments"}, + }, + { + eventTypes: []string{"response.custom_tool_call_input.done"}, + anyPaths: []string{"input"}, + }, + { + eventTypes: []string{"response.code_interpreter_call_code.done"}, + anyPaths: []string{"code"}, + }, + { + eventTypes: []string{"response.output_item.added", "response.output_item.done"}, + anyPaths: []string{ + "item.content.#.text", + "item.summary.#.text", + "item.arguments", + "item.input", + "item.action", + "item.queries", + "item.query", + "item.code", + "item.command", + "item.operation", + }, + }, + }, + errorRules: []frameRule{ + {eventTypes: []string{"error", "response.error"}}, + {eventTypes: []string{"response.failed"}}, + {anyPaths: []string{"error", "response.error"}}, + }, + terminalEvents: []string{"response.completed", "response.incomplete", "response.done"}, + }, + FamilyGemini: { + contentRules: []frameRule{ + { + anyPaths: []string{ + "candidates.#.content.parts.#.text", + "candidates.#.content.parts.#.inlineData.data", + "candidates.#.content.parts.#.fileData.fileUri", + "candidates.#.content.parts.#.functionCall.name", + "candidates.#.content.parts.#.functionResponse.name", + "candidates.#.content.parts.#.executableCode.code", + "candidates.#.content.parts.#.codeExecutionResult.output", + }, + }, + }, + errorRules: []frameRule{ + {anyPaths: []string{"error"}}, + {anyPaths: []string{"promptFeedback.blockReason"}}, + { + valueMatches: []valueMatch{ + { + path: "candidates.#.finishReason", + values: []string{ + "SAFETY", "RECITATION", "LANGUAGE", "BLOCKLIST", "PROHIBITED_CONTENT", + "SPII", "MALFORMED_FUNCTION_CALL", "IMAGE_SAFETY", "UNEXPECTED_TOOL_CALL", + "IMAGE_PROHIBITED_CONTENT", "NO_IMAGE", "IMAGE_RECITATION", "IMAGE_OTHER", "OTHER", + }, + }, + }, + }, + }, + terminalRules: []frameRule{ + {anyPaths: []string{"candidates.#.finishReason"}}, + }, + }, +} + +// FamilyForProviderType mirrors mapProviderTypeToFamily. +func FamilyForProviderType(providerType string) (Family, bool) { + switch providerType { + case "claude", "claude-auth": + return FamilyAnthropic, true + case "codex": + return FamilyOpenAIResponses, true + case "openai-compatible": + return FamilyOpenAIChat, true + case "gemini", "gemini-cli": + return FamilyGemini, true + default: + return "", false + } +} + +var requestEchoEvents = map[Family]map[string]bool{ + FamilyOpenAIResponses: { + "response.created": true, + "response.in_progress": true, + "response.queued": true, + }, +} + +// IsRequestEchoFrame mirrors isRequestEchoFrame. +// +// Deviation: the TS head-sniff slices UTF-16 code units 0..64; this slices +// bytes 0..64, which may fall mid-codepoint for non-ASCII input. Since the +// sniffed marker (`"type":"..."`) is pure ASCII this does not change results +// in practice. +func IsRequestEchoFrame(family Family, eventName *string, data string) bool { + events, ok := requestEchoEvents[family] + if !ok { + return false + } + effective := "" + if eventName != nil { + effective = strings.TrimSpace(*eventName) + } + if effective != "" { + return events[effective] + } + head := data + if len(head) > 64 { + head = head[:64] + } + for event := range events { + if strings.Contains(head, `"type":"`+event+`"`) { + return true + } + } + return false +} + +// undefinedMarker distinguishes "path did not resolve" (JS undefined) from a +// resolved JSON null, matching resolvePath's use of `undefined` in TS. +type undefinedType struct{} + +var undefinedMarker undefinedType + +// ClassifyFrame mirrors classifyFrame + classifyStructuredFrame + +// classifyStructuredTerminalKind + the openai-responses "acceptTerminal" +// computation from frame-probe.ts, evaluated together in one pass since the +// Go API returns them as a single Classification. +// +// Like the TS classifier, this never panics outward: any unexpected failure +// is treated as a neutral classification (fail-open, matching the TS +// try/catch around classifyFrameInner). +func ClassifyFrame(family Family, eventName *string, data string) (result Classification) { + defer func() { + if recover() != nil { + result = Classification{Verdict: VerdictNeutral} + } + }() + return classifyFrameInner(family, eventName, data) +} + +func classifyFrameInner(family Family, eventName *string, data string) Classification { + echo := IsRequestEchoFrame(family, eventName, data) + signal := streamSignals[family] + trimmed := strings.TrimSpace(data) + + if trimmed != "" && signal.doneSentinel != "" && trimmed == signal.doneSentinel { + tk := classifyTerminalKindFallback(family, eventName, nil) + return Classification{Verdict: VerdictTerminal, IsEcho: echo, TerminalKind: tk} + } + if trimmed == "" { + return Classification{Verdict: VerdictNeutral, IsEcho: echo} + } + first := trimmed[0] + if first != '{' && first != '[' { + return Classification{Verdict: VerdictMalformed, IsEcho: echo} + } + parsed, ok := decodeSingleJSON(trimmed) + if !ok || parsed == nil { + return Classification{Verdict: VerdictMalformed, IsEcho: echo} + } + + verdict := classifyStructuredFrame(family, eventName, parsed) + // Mirrors the observer's composition: classifyTerminalKind's + // event-name-only fallback (used when verdict is "terminal" but no + // structural match applies, e.g. a Gemini/Anthropic terminalEvents hit) + // combined with -- and overridden by -- the structural + // classifyStructuredTerminalKind, which can independently detect a + // terminal signal even on a "content" verdict frame (e.g. a Responses + // response.completed carrying compaction content). + terminalKind := "" + if verdict == VerdictTerminal { + terminalKind = classifyTerminalKindFallback(family, eventName, parsed) + } + if structured := classifyStructuredTerminalKind(family, eventName, parsed); structured != "" { + terminalKind = structured + } + accept := computeAcceptTerminal(family, eventName, parsed) + return Classification{Verdict: verdict, IsEcho: echo, TerminalKind: terminalKind, AcceptTerminal: accept} +} + +// classifyTerminalKindFallback mirrors the exported TS classifyTerminalKind +// (family, eventName, parsed?): when a structural match is available it +// wins; otherwise every family except openai-responses treats any +// verdict=="terminal" frame as a clean completion (there is no separate +// "incomplete" signal outside openai-responses), and openai-responses falls +// back to a bare event-name check. +func classifyTerminalKindFallback(family Family, eventName *string, parsed any) string { + if parsed != nil { + if structured := classifyStructuredTerminalKind(family, eventName, parsed); structured != "" { + return structured + } + } + if family != FamilyOpenAIResponses { + return "complete" + } + effective := "" + if eventName != nil { + effective = strings.TrimSpace(*eventName) + } + if (effective == "" || effective == "message") && parsed != nil { + if obj, ok := parsed.(map[string]any); ok { + if t, ok := obj["type"].(string); ok { + effective = t + } + } + } + switch effective { + case "response.incomplete": + return "incomplete" + case "response.completed", "response.done": + return "complete" + default: + return "" + } +} + +// decodeSingleJSON parses exactly one JSON value from s, failing if any +// non-whitespace trailing content follows -- matching JSON.parse, which +// throws on trailing garbage. +func decodeSingleJSON(s string) (any, bool) { + dec := json.NewDecoder(strings.NewReader(s)) + var v any + if err := dec.Decode(&v); err != nil { + return nil, false + } + var extra any + if err := dec.Decode(&extra); err != io.EOF { + return nil, false + } + return v, true +} + +// classifyStructuredFrame mirrors classifyStructuredFrame: it classifies the +// parsed frame, and for Gemini, if the outer classification is neutral, +// unwraps a `{response: {...}}` envelope and retries once. +func classifyStructuredFrame(family Family, eventName *string, parsed any) Verdict { + signal := streamSignals[family] + outer := classifyParsedFrame(family, signal, eventName, parsed) + if outer != VerdictNeutral || family != FamilyGemini { + return outer + } + if _, isArray := parsed.([]any); isArray { + return outer + } + obj, ok := parsed.(map[string]any) + if !ok { + return outer + } + resp, ok := obj["response"] + if !ok || resp == nil { + return outer + } + respObj, ok := resp.(map[string]any) + if !ok { + return outer + } + return classifyParsedFrame(family, signal, eventName, respObj) +} + +func effectiveEventType(eventName *string, parsed any) string { + effective := "" + if eventName != nil { + effective = strings.TrimSpace(*eventName) + } + if effective == "" { + if obj, ok := parsed.(map[string]any); ok { + if t, ok := obj["type"].(string); ok { + effective = t + } + } + } + return effective +} + +func classifyParsedFrame(family Family, signal streamSignal, eventName *string, parsed any) Verdict { + effective := effectiveEventType(eventName, parsed) + + for _, rule := range signal.errorRules { + if frameRuleMatches(rule, effective, parsed) { + return VerdictError + } + } + if family == FamilyOpenAIResponses && isResponsesCompactionContent(effective, parsed) { + return VerdictContent + } + for _, rule := range signal.contentRules { + if frameRuleMatches(rule, effective, parsed) { + return VerdictContent + } + } + for _, rule := range signal.terminalRules { + if frameRuleMatches(rule, effective, parsed) { + return VerdictTerminal + } + } + if effective != "" { + for _, e := range signal.terminalEvents { + if e == effective { + return VerdictTerminal + } + } + } + return VerdictNeutral +} + +// classifyStructuredTerminalKind mirrors classifyStructuredTerminalKind. +// Returns "complete", "incomplete", or "". +func classifyStructuredTerminalKind(family Family, eventName *string, parsed any) string { + effective := "" + if eventName != nil { + effective = strings.TrimSpace(*eventName) + } + if (effective == "" || effective == "message") && parsed != nil { + if _, isArray := parsed.([]any); !isArray { + if obj, ok := parsed.(map[string]any); ok { + if t, ok := obj["type"].(string); ok { + effective = t + } + } + } + } + + if family == FamilyOpenAIResponses { + switch effective { + case "response.incomplete": + return "incomplete" + case "response.completed", "response.done": + return "complete" + default: + return "" + } + } + + signal := streamSignals[family] + if effective != "" { + for _, e := range signal.terminalEvents { + if e == effective { + return "complete" + } + } + } + for _, rule := range signal.terminalRules { + if frameRuleMatches(rule, effective, parsed) { + return "complete" + } + } + return "" +} + +// computeAcceptTerminal replicates frame-probe.ts's "clean"/"incomplete" +// openai-responses acceptTerminal computation (equivalent to +// isCleanResponsesCompletion || isResponsesIncompleteCompletion evaluated +// against an already-parsed frame). +func computeAcceptTerminal(family Family, eventName *string, parsed any) bool { + if family != FamilyOpenAIResponses { + return false + } + obj, ok := parsed.(map[string]any) + if !ok { + return false + } + effective := "" + if eventName != nil { + effective = strings.TrimSpace(*eventName) + } + typ, _ := obj["type"].(string) + resp, hasResp := obj["response"].(map[string]any) + if !hasResp { + return false + } + status, _ := resp["status"].(string) + + clean := (effective == "" || effective == "response.completed") && + typ == "response.completed" && + status == "completed" && + !isNonEmptyValue(resp["error"]) + incomplete := (effective == "" || effective == "response.incomplete") && + typ == "response.incomplete" && + status == "incomplete" + return clean || incomplete +} + +// isResponsesCompactionContent mirrors isResponsesCompactionContent. +func isResponsesCompactionContent(eventType string, parsed any) bool { + if _, isArray := parsed.([]any); isArray { + return false + } + obj, ok := parsed.(map[string]any) + if !ok { + return false + } + if eventType == "response.output_item.done" { + return isNonEmptyCompactionItem(obj["item"]) + } + if eventType != "response.completed" { + return false + } + resp, ok := obj["response"].(map[string]any) + if !ok { + return false + } + output, ok := resp["output"].([]any) + if !ok { + return false + } + for _, item := range output { + if isNonEmptyCompactionItem(item) { + return true + } + } + return false +} + +func isNonEmptyCompactionItem(item any) bool { + obj, ok := item.(map[string]any) + if !ok { + return false + } + if t, _ := obj["type"].(string); t != "compaction" { + return false + } + ec, ok := obj["encrypted_content"].(string) + return ok && ec != "" +} + +// frameRuleMatches mirrors frameRuleMatches: AND semantics across the rule's +// clauses; an empty rule never matches. +func frameRuleMatches(rule frameRule, eventType string, parsed any) bool { + if len(rule.eventTypes) > 0 && !containsStr(rule.eventTypes, eventType) { + return false + } + if len(rule.anyPaths) > 0 { + hit := false + for _, path := range rule.anyPaths { + if isNonEmptyValue(resolvePath(parsed, path)) { + hit = true + break + } + } + if !hit { + return false + } + } + for _, match := range rule.valueMatches { + if !valueMatchHits(match, parsed) { + return false + } + } + return len(rule.eventTypes) > 0 || len(rule.anyPaths) > 0 || len(rule.valueMatches) > 0 +} + +func valueMatchHits(match valueMatch, parsed any) bool { + if match.path == "" || len(match.values) == 0 { + return false + } + resolved := resolvePath(parsed, match.path) + if resolved == undefinedMarker { + return false + } + var candidates []any + if arr, ok := resolved.([]any); ok { + candidates = arr + } else { + candidates = []any{resolved} + } + for _, c := range candidates { + switch v := c.(type) { + case string: + if containsStr(match.values, v) { + return true + } + case bool: + if containsStr(match.values, jsBoolString(v)) { + return true + } + case float64: + if containsStr(match.values, jsNumberString(v)) { + return true + } + } + } + return false +} + +// resolvePath mirrors resolvePath/resolveSegments: gjson-style dotted path +// resolution with `#` mapping/flattening over arrays. Returns undefinedMarker +// when the path does not resolve. +func resolvePath(node any, path string) any { + return resolveSegments(node, strings.Split(path, "."), 0) +} + +func resolveSegments(node any, segments []string, index int) any { + if index == len(segments) { + return node + } + segment := segments[index] + if segment == "#" { + arr, ok := node.([]any) + if !ok { + return undefinedMarker + } + hasNestedHash := false + for _, s := range segments[index+1:] { + if s == "#" { + hasNestedHash = true + break + } + } + collected := make([]any, 0, len(arr)) + for _, item := range arr { + resolved := resolveSegments(item, segments, index+1) + if resolved == undefinedMarker { + continue + } + if sub, isArr := resolved.([]any); isArr && hasNestedHash { + collected = append(collected, sub...) + } else { + collected = append(collected, resolved) + } + } + return collected + } + if node == nil { + return undefinedMarker + } + obj, ok := node.(map[string]any) + if !ok { + return undefinedMarker + } + child, exists := obj[segment] + if !exists { + return undefinedMarker + } + return resolveSegments(child, segments, index+1) +} + +// isNonEmptyValue mirrors isNonEmptyValue's gjson-style "non-empty" test. +func isNonEmptyValue(value any) bool { + if value == nil || value == undefinedMarker { + return false + } + switch v := value.(type) { + case bool: + return v + case string: + return v != "" + case float64: + return true + case json.Number: + return true + case []any: + for _, item := range v { + if isNonEmptyValue(item) { + return true + } + } + return false + case map[string]any: + return len(v) > 0 + default: + return false + } +} + +func containsStr(list []string, s string) bool { + for _, v := range list { + if v == s { + return true + } + } + return false +} + +func jsBoolString(b bool) string { + if b { + return "true" + } + return "false" +} + +// jsNumberString approximates ECMA-262's Number::toString for the finite, +// non-huge/non-tiny values realistically found in provider JSON (ids, +// counts). It is not a complete implementation of the spec algorithm, but +// none of the current STREAM_SIGNALS valueMatches rules compare numbers +// (all candidate lists are strings), so this path is exercised only +// defensively. +func jsNumberString(f float64) string { + if f == 0 { + return "0" + } + abs := f + if abs < 0 { + abs = -abs + } + if abs >= 1e21 || abs < 1e-6 { + s := strconv.FormatFloat(f, 'e', -1, 64) + // Go: "1e+21" / "1e-07"; JS: "1e+21" / "1e-7" (no zero-padded exponent). + if idx := strings.IndexByte(s, 'e'); idx >= 0 { + mantissa, exp := s[:idx], s[idx+1:] + sign := "+" + if len(exp) > 0 && (exp[0] == '+' || exp[0] == '-') { + sign = string(exp[0]) + exp = exp[1:] + } + exp = strings.TrimLeft(exp, "0") + if exp == "" { + exp = "0" + } + return mantissa + "e" + sign + exp + } + return s + } + return strconv.FormatFloat(f, 'f', -1, 64) +} diff --git a/edge/internal/gate/classifier_fixtures_test.go b/edge/internal/gate/classifier_fixtures_test.go new file mode 100644 index 0000000000..b99aaaa1e6 --- /dev/null +++ b/edge/internal/gate/classifier_fixtures_test.go @@ -0,0 +1,87 @@ +package gate + +import ( + "encoding/json" + "os" + "testing" +) + +func loadFixture(t *testing.T, name string, v any) { + t.Helper() + path := "../../../tests/fixtures/edge/gate/" + name + data, err := os.ReadFile(path) + if err != nil { + t.Fatalf("read fixture %s: %v", path, err) + } + if err := json.Unmarshal(data, v); err != nil { + t.Fatalf("parse fixture %s: %v", path, err) + } +} + +type classifierCaseJSON struct { + Family string `json:"family"` + EventName *string `json:"eventName"` + Data string `json:"data"` + Expected struct { + Verdict string `json:"verdict"` + AcceptTerminal bool `json:"acceptTerminal"` + IsEcho bool `json:"isEcho"` + TerminalKind string `json:"terminalKind"` + } `json:"expected"` +} + +func TestClassifierFixtures(t *testing.T) { + var cases []classifierCaseJSON + loadFixture(t, "classifier.json", &cases) + if len(cases) == 0 { + t.Fatal("no fixture cases loaded") + } + + for i, c := range cases { + c := c + got := ClassifyFrame(Family(c.Family), c.EventName, c.Data) + if string(got.Verdict) != c.Expected.Verdict { + t.Errorf("case[%d] family=%s event=%v data=%q: verdict = %s, want %s", + i, c.Family, ptrStr(c.EventName), c.Data, got.Verdict, c.Expected.Verdict) + } + if got.AcceptTerminal != c.Expected.AcceptTerminal { + t.Errorf("case[%d] family=%s event=%v data=%q: acceptTerminal = %v, want %v", + i, c.Family, ptrStr(c.EventName), c.Data, got.AcceptTerminal, c.Expected.AcceptTerminal) + } + if got.IsEcho != c.Expected.IsEcho { + t.Errorf("case[%d] family=%s event=%v data=%q: isEcho = %v, want %v", + i, c.Family, ptrStr(c.EventName), c.Data, got.IsEcho, c.Expected.IsEcho) + } + if got.TerminalKind != c.Expected.TerminalKind { + t.Errorf("case[%d] family=%s event=%v data=%q: terminalKind = %q, want %q", + i, c.Family, ptrStr(c.EventName), c.Data, got.TerminalKind, c.Expected.TerminalKind) + } + } +} + +func TestFamilyForProviderType(t *testing.T) { + cases := map[string]Family{ + "claude": FamilyAnthropic, + "claude-auth": FamilyAnthropic, + "codex": FamilyOpenAIResponses, + "openai-compatible": FamilyOpenAIChat, + "gemini": FamilyGemini, + "gemini-cli": FamilyGemini, + } + for in, want := range cases { + got, ok := FamilyForProviderType(in) + if !ok || got != want { + t.Errorf("FamilyForProviderType(%q) = %q, %v; want %q, true", in, got, ok, want) + } + } + if _, ok := FamilyForProviderType("unknown-provider"); ok { + t.Error("FamilyForProviderType(unknown) should return ok=false") + } +} + +func ptrStr(s *string) string { + if s == nil { + return "" + } + return *s +} diff --git a/edge/internal/gate/observer.go b/edge/internal/gate/observer.go new file mode 100644 index 0000000000..db314f9bf4 --- /dev/null +++ b/edge/internal/gate/observer.go @@ -0,0 +1,147 @@ +package gate + +import ( + "errors" + + "github.com/ding113/claude-code-hub/edge/internal/sse" +) + +// observerMaxBufferBytes mirrors STREAM_PROTOCOL_OBSERVER_MAX_BUFFER_CHARACTERS +// (10 MiB) -- counted in bytes here, see package sse's doc comment. +const observerMaxBufferBytes = 10 * 1024 * 1024 + +const defaultObserverPrebufferBytes = 10 * 1024 * 1024 + +// ObservationFailure mirrors StreamProtocolFailure. +type ObservationFailure struct { + Verdict string + EventName *string + AfterContent bool + SawMalformed bool +} + +// Observation mirrors StreamProtocolObservation. +type Observation struct { + SawContent bool + SawTerminal bool + SawIncomplete bool + ObservationIncomplete bool + Failure *ObservationFailure +} + +// Observer is a Go port of createStreamProtocolObserver: a fail-open, +// non-buffering (bounded) observer over the bytes actually sent to the +// client, used to detect protocol failures/incompleteness after the gate +// has already committed and started streaming. +// +// Deviation: the TS exemption for the observer's own buffer-limit check +// additionally sniffs the pending frame's data head (see package sse's doc +// comment on NewParser); here the exemption is event-name only. +type Observer struct { + family Family + parser *sse.Parser + obs Observation + finished bool + disabled bool +} + +// NewObserver creates an Observer for family. prebufferByteCap should match +// the gate's configured byte cap (used only to size the echo exemption, via +// IsRequestEchoFrame's event-name check); <= 0 uses a default. +func NewObserver(family Family, prebufferByteCap int) *Observer { + if prebufferByteCap <= 0 { + prebufferByteCap = defaultObserverPrebufferBytes + } + o := &Observer{family: family} + o.parser = sse.NewParser(observerMaxBufferBytes, func(pending *string) bool { + return IsRequestEchoFrame(family, pending, "") + }) + return o +} + +func (o *Observer) record(eventName *string, data string) { + cls := ClassifyFrame(o.family, eventName, data) + if cls.Verdict == VerdictContent { + o.obs.SawContent = true + } + switch cls.TerminalKind { + case "incomplete": + o.obs.SawIncomplete = true + case "complete": + o.obs.SawTerminal = true + } + if cls.Verdict != VerdictError && cls.Verdict != VerdictMalformed { + return + } + if o.obs.Failure == nil { + o.obs.Failure = &ObservationFailure{ + Verdict: string(cls.Verdict), + EventName: eventName, + AfterContent: o.obs.SawContent, + } + return + } + if cls.Verdict == VerdictError && o.obs.Failure.Verdict == "malformed" { + o.obs.Failure = &ObservationFailure{ + Verdict: "error", + EventName: eventName, + AfterContent: o.obs.SawContent, + SawMalformed: true, + } + } else if cls.Verdict == VerdictMalformed && o.obs.Failure.Verdict == "error" { + o.obs.Failure.SawMalformed = true + } +} + +func (o *Observer) disableIncomplete() { + o.disabled = true + o.obs.ObservationIncomplete = true +} + +// Observe feeds one chunk of client-facing bytes into the observer. It +// returns the current failure (nil if none) after processing the chunk. +// Parser resource-limit errors disable further observation (fail-open) but +// never fabricate a malformed/error verdict. +func (o *Observer) Observe(chunk []byte) *ObservationFailure { + if o.finished || o.disabled || len(chunk) == 0 { + return o.obs.Failure + } + frames, err := o.parser.Push(chunk) + if err != nil { + if errors.Is(err, sse.ErrBufferLimit) { + o.disableIncomplete() + return o.obs.Failure + } + o.disableIncomplete() + return o.obs.Failure + } + for _, f := range frames { + o.record(f.EventName, f.Data) + } + return o.obs.Failure +} + +// Finish flushes any trailing unterminated frame and returns the final +// observation. Safe to call multiple times (idempotent after the first +// call). +func (o *Observer) Finish() Observation { + if !o.finished { + o.finished = true + if !o.disabled { + frames, err := o.parser.Finish() + if err != nil { + o.disableIncomplete() + } else { + for _, f := range frames { + o.record(f.EventName, f.Data) + } + } + } + } + result := o.obs + if o.obs.Failure != nil { + cp := *o.obs.Failure + result.Failure = &cp + } + return result +} diff --git a/edge/internal/gate/observer_fixtures_test.go b/edge/internal/gate/observer_fixtures_test.go new file mode 100644 index 0000000000..a2685fc5bc --- /dev/null +++ b/edge/internal/gate/observer_fixtures_test.go @@ -0,0 +1,78 @@ +package gate + +import "testing" + +type observerCaseJSON struct { + Name string `json:"name"` + Family string `json:"family"` + Chunks []string `json:"chunks"` + Expected struct { + SawContent bool `json:"sawContent"` + SawTerminal bool `json:"sawTerminal"` + SawIncomplete bool `json:"sawIncomplete"` + ObservationIncomplete bool `json:"observationIncomplete"` + Failure *struct { + Verdict string `json:"verdict"` + EventName *string `json:"eventName"` + AfterContent bool `json:"afterContent"` + SawMalformed bool `json:"sawMalformed"` + } `json:"failure"` + } `json:"expected"` +} + +func TestObserverFixtures(t *testing.T) { + var cases []observerCaseJSON + loadFixture(t, "observer.json", &cases) + if len(cases) == 0 { + t.Fatal("no fixture cases loaded") + } + + for _, c := range cases { + c := c + t.Run(c.Name, func(t *testing.T) { + o := NewObserver(Family(c.Family), 0) + for _, chunk := range c.Chunks { + o.Observe([]byte(chunk)) + } + got := o.Finish() + + if got.SawContent != c.Expected.SawContent { + t.Errorf("SawContent = %v, want %v", got.SawContent, c.Expected.SawContent) + } + if got.SawTerminal != c.Expected.SawTerminal { + t.Errorf("SawTerminal = %v, want %v", got.SawTerminal, c.Expected.SawTerminal) + } + if got.SawIncomplete != c.Expected.SawIncomplete { + t.Errorf("SawIncomplete = %v, want %v", got.SawIncomplete, c.Expected.SawIncomplete) + } + if got.ObservationIncomplete != c.Expected.ObservationIncomplete { + t.Errorf("ObservationIncomplete = %v, want %v", got.ObservationIncomplete, c.Expected.ObservationIncomplete) + } + if (got.Failure == nil) != (c.Expected.Failure == nil) { + t.Fatalf("Failure presence mismatch: got %v, want %v", got.Failure, c.Expected.Failure) + } + if got.Failure != nil { + wf := c.Expected.Failure + if got.Failure.Verdict != wf.Verdict { + t.Errorf("Failure.Verdict = %q, want %q", got.Failure.Verdict, wf.Verdict) + } + if !eqPtrStr(got.Failure.EventName, wf.EventName) { + t.Errorf("Failure.EventName = %v, want %v", ptrStr(got.Failure.EventName), ptrStr(wf.EventName)) + } + if got.Failure.AfterContent != wf.AfterContent { + t.Errorf("Failure.AfterContent = %v, want %v", got.Failure.AfterContent, wf.AfterContent) + } + if got.Failure.SawMalformed != wf.SawMalformed { + t.Errorf("Failure.SawMalformed = %v, want %v", got.Failure.SawMalformed, wf.SawMalformed) + } + } + }) + } +} + +func eqPtrStr(a, b *string) bool { + if (a == nil) != (b == nil) { + return false + } + return a == nil || *a == *b +} diff --git a/edge/internal/gate/run.go b/edge/internal/gate/run.go new file mode 100644 index 0000000000..1d678921c4 --- /dev/null +++ b/edge/internal/gate/run.go @@ -0,0 +1,361 @@ +package gate + +import ( + "context" + "errors" + "io" + "strings" + "time" + + "github.com/ding113/claude-code-hub/edge/internal/sse" +) + +// readChunkBytes is the size of the buffer used for each upstream Read call. +const readChunkBytes = 32 * 1024 + +// Options configures Run. See the package-level doc comment and Run's doc +// comment for the algorithm this ports (runStreamContentGate in +// stream-content-gate.ts). +type Options struct { + Family Family + EventCap int + ByteCap int + IdleTimeout time.Duration + CaptureCommitMarker bool + Budget *Budget + OnFirstByte func() +} + +// Failure mirrors the fields of StreamPrecommitError's construction detail +// plus its `reason`. +type Failure struct { + Reason string + FrameData string + InferenceText string + TerminalBeforeContent bool + FramesSeen int + BufferedBytes int + EchoExcludedBytes int +} + +// CommitMarker mirrors StreamGateCommitMarker. +type CommitMarker struct { + FrameIndex int + ChunkIndex int + EventName *string + BufferedBytes int + EchoExcludedBytes int +} + +// Result mirrors StreamGateResult, extended with Go-specific fields (ReadErr, +// ReaderDone renamed nothing -- see field docs). +type Result struct { + Committed bool + Prefix [][]byte + ReaderDone bool + Failure *Failure + CommitMarker *CommitMarker + FramesSeen int + ReadErr error +} + +type readOutcome struct { + n int + err error +} + +// Run executes the stream content gate's precommit loop over r: it buffers +// upstream bytes, classifying each completed SSE frame, until either a +// content frame commits the prefix, an error/malformed/disallowed-terminal +// frame fails the attempt, or a resource limit (event cap, byte cap, idle +// timeout, budget exhaustion) is hit. +// +// ctx cancellation (e.g. a first-byte timer elsewhere, or the caller +// aborting) stops the loop and is reported via Result.ReadErr; Run does not +// close or cancel r itself -- the caller remains responsible for r's +// lifecycle (closing the body / cancelling whatever produces r is what +// unblocks a Read call that is in flight when Run returns early). +// +// Goroutine note: each upstream Read is issued from its own goroutine so it +// can race against the idle timer and ctx.Done(). In the normal path (a read +// completes before any timeout/cancellation) that goroutine has already +// exited by the time Run reads its result. In the early-return paths (idle +// timeout, ctx cancellation), the in-flight Read's goroutine is left running +// until the underlying Read call itself returns (e.g. because the caller +// closed/cancelled whatever r wraps); its result is then discarded by the +// GC'd, unreferenced channel. Run does not leak a goroutine that runs +// forever on its own -- it always leaks at most until r.Read unblocks. +func Run(ctx context.Context, r io.Reader, opts Options) Result { + var ( + framesSeen int + chunkIndex int + bufferedBytes int + echoExcludedBytes int + prefix [][]byte + firstByteSeen bool + ) + + var lease *Lease + if opts.Budget != nil { + reservation := int64(opts.ByteCap) + if reservation <= 0 { + reservation = readChunkBytes + } + l, err := opts.Budget.Acquire(ctx, reservation) + if err != nil { + if errors.Is(err, ErrLocalCapacity) { + return Result{Failure: &Failure{Reason: "local_capacity"}} + } + return Result{ReadErr: err} + } + lease = l + } + releaseLease := func() { + if lease != nil { + lease.Release() + lease = nil + } + } + + exemption := func(pending *string) bool { + return IsRequestEchoFrame(opts.Family, pending, "") + } + parserLimit := opts.ByteCap * 2 + if parserLimit <= 0 { + parserLimit = 0 // unlimited + } + parser := sse.NewParser(parserLimit, exemption) + + fail := func(reason string, frameData string, terminalBeforeContent bool) Result { + releaseLease() + f := &Failure{ + Reason: reason, + TerminalBeforeContent: terminalBeforeContent, + FramesSeen: framesSeen, + BufferedBytes: bufferedBytes, + EchoExcludedBytes: echoExcludedBytes, + } + if frameData != "" { + f.FrameData = truncateBytes(frameData, 2000) + if reason == "gate_error" { + f.InferenceText = truncateBytes(strings.TrimLeft(frameData, " \t\n\r\v\f"), 64*1024) + } + } + return Result{Committed: false, Failure: f, FramesSeen: framesSeen} + } + + commit := func(eventName *string, readerDone bool) Result { + var marker *CommitMarker + if opts.CaptureCommitMarker { + marker = &CommitMarker{ + FrameIndex: framesSeen, + ChunkIndex: chunkIndex, + EventName: eventName, + BufferedBytes: bufferedBytes, + EchoExcludedBytes: echoExcludedBytes, + } + } + releaseLease() + return Result{ + Committed: true, + Prefix: prefix, + ReaderDone: readerDone, + FramesSeen: framesSeen, + CommitMarker: marker, + } + } + + exceedsByteCap := func() bool { + if opts.ByteCap <= 0 { + return false + } + excluded := echoExcludedBytes + if excluded > opts.ByteCap { + excluded = opts.ByteCap + } + return bufferedBytes-excluded > opts.ByteCap + } + + // processFrames classifies newly parsed frames, returning a non-nil + // *Result when the loop must stop (commit or failure). + processFrames := func(frames []sse.Frame) *Result { + for _, fr := range frames { + framesSeen++ + cls := ClassifyFrame(opts.Family, fr.EventName, fr.Data) + switch cls.Verdict { + case VerdictContent: + if exceedsByteCap() { + res := fail("prebuffer_overflow", "", false) + return &res + } + res := commit(fr.EventName, false) + return &res + case VerdictError: + res := fail("gate_error", fr.Data, false) + return &res + case VerdictMalformed: + res := fail("decode_error", fr.Data, false) + return &res + case VerdictTerminal: + if opts.Family == FamilyOpenAIResponses && cls.AcceptTerminal { + if exceedsByteCap() { + res := fail("prebuffer_overflow", "", false) + return &res + } + res := commit(fr.EventName, false) + return &res + } + res := fail("empty_stream", fr.Data, true) + return &res + default: // neutral + if cls.IsEcho { + echoExcludedBytes += len(fr.Data) + } + if opts.EventCap > 0 && framesSeen > opts.EventCap { + res := fail("prebuffer_overflow", "", false) + return &res + } + } + } + if exceedsByteCap() { + res := fail("prebuffer_overflow", "", false) + return &res + } + return nil + } + + buf := make([]byte, readChunkBytes) + reqCh := make(chan readOutcome, 1) + + for { + go func() { + n, err := r.Read(buf) + reqCh <- readOutcome{n, err} + }() + + var out readOutcome + if opts.IdleTimeout > 0 { + timer := time.NewTimer(opts.IdleTimeout) + select { + case out = <-reqCh: + timer.Stop() + case <-timer.C: + return fail("idle_timeout", "", false) + case <-ctx.Done(): + timer.Stop() + releaseLease() + return Result{ReadErr: ctx.Err(), FramesSeen: framesSeen} + } + } else { + select { + case out = <-reqCh: + case <-ctx.Done(): + releaseLease() + return Result{ReadErr: ctx.Err(), FramesSeen: framesSeen} + } + } + + if out.n > 0 { + chunk := make([]byte, out.n) + copy(chunk, buf[:out.n]) + + if !firstByteSeen { + firstByteSeen = true + if opts.OnFirstByte != nil { + opts.OnFirstByte() + } + } + chunkIndex++ + + if opts.ByteCap > 0 && len(chunk) > opts.ByteCap*2-bufferedBytes { + return fail("prebuffer_overflow", "", false) + } + prefix = append(prefix, chunk) + bufferedBytes += len(chunk) + + frames, perr := parser.Push(chunk) + if perr != nil { + if errors.Is(perr, sse.ErrBufferLimit) { + return fail("prebuffer_overflow", "", false) + } + releaseLease() + return Result{ReadErr: perr, FramesSeen: framesSeen} + } + if res := processFrames(frames); res != nil { + return *res + } + } + + if out.err != nil { + if out.err == io.EOF { + frames, ferr := parser.Finish() + if ferr != nil { + if errors.Is(ferr, sse.ErrBufferLimit) { + return fail("prebuffer_overflow", "", false) + } + releaseLease() + return Result{ReadErr: ferr, FramesSeen: framesSeen} + } + sawTerminal, result := processTrailingFrames(frames, opts, &framesSeen, commit, fail) + if result != nil { + return *result + } + return fail("empty_stream", "", sawTerminal) + } + releaseLease() + return Result{ReadErr: out.err, FramesSeen: framesSeen} + } + // out.n == 0 && out.err == nil: spurious empty read; loop again. + } +} + +// processTrailingFrames mirrors the EOF-flush branch of runStreamContentGate: +// it walks the frames flushed by parser.Finish(), stopping at the first +// commit/failure decision, and separately tracks whether a (non-accepted) +// terminal frame was seen for the eventual empty_stream's +// terminalBeforeContent flag. +func processTrailingFrames( + frames []sse.Frame, + opts Options, + framesSeen *int, + commit func(eventName *string, readerDone bool) Result, + fail func(reason string, frameData string, terminalBeforeContent bool) Result, +) (sawTerminal bool, result *Result) { + for _, fr := range frames { + *framesSeen++ + cls := ClassifyFrame(opts.Family, fr.EventName, fr.Data) + switch cls.Verdict { + case VerdictContent: + res := commit(fr.EventName, true) + return sawTerminal, &res + case VerdictError: + res := fail("gate_error", fr.Data, false) + return sawTerminal, &res + case VerdictMalformed: + res := fail("decode_error", fr.Data, false) + return sawTerminal, &res + case VerdictTerminal: + if opts.Family == FamilyOpenAIResponses && cls.AcceptTerminal { + res := commit(fr.EventName, true) + return sawTerminal, &res + } + sawTerminal = true + default: + if opts.EventCap > 0 && *framesSeen > opts.EventCap { + res := fail("prebuffer_overflow", "", false) + return sawTerminal, &res + } + } + } + return sawTerminal, nil +} + +// truncateBytes returns s truncated to at most n bytes. Deviation: TS caps +// these previews in UTF-16 code units; this caps in bytes (see package sse's +// doc comment for the same tradeoff). +func truncateBytes(s string, n int) string { + if len(s) <= n { + return s + } + return s[:n] +} diff --git a/edge/internal/gate/run_fixtures_test.go b/edge/internal/gate/run_fixtures_test.go new file mode 100644 index 0000000000..33784766e4 --- /dev/null +++ b/edge/internal/gate/run_fixtures_test.go @@ -0,0 +1,148 @@ +package gate + +import ( + "bytes" + "context" + "io" + "strings" + "testing" +) + +type gateCaseJSON struct { + Name string `json:"name"` + Family string `json:"family"` + Chunks []string `json:"chunks"` + EventCap int `json:"eventCap"` + ByteCap int `json:"byteCap"` + Expected struct { + Committed bool `json:"committed"` + Reason string `json:"reason"` + FramesSeen int `json:"framesSeen"` + PrefixBytes int `json:"prefixBytes"` + ReaderDone bool `json:"readerDone"` + TerminalBeforeContent bool `json:"terminalBeforeContent"` + EchoExcludedBytes int `json:"echoExcludedBytes"` + CommitMarker *struct { + FrameIndex int `json:"frameIndex"` + ChunkIndex int `json:"chunkIndex"` + EventName *string `json:"eventName"` + BufferedBytes int `json:"bufferedBytes"` + EchoExcludedBytes int `json:"echoExcludedBytes"` + } `json:"commitMarker"` + } `json:"expected"` +} + +// chunkedReader replays a fixed sequence of byte chunks, one per Read call, +// then returns io.EOF, mirroring the fixture's ReadableStream-per-chunk +// producer. +type chunkedReader struct { + chunks [][]byte + idx int +} + +func newChunkedReader(chunks []string) *chunkedReader { + cr := &chunkedReader{} + for _, c := range chunks { + cr.chunks = append(cr.chunks, []byte(c)) + } + return cr +} + +func (r *chunkedReader) Read(p []byte) (int, error) { + if r.idx >= len(r.chunks) { + return 0, io.EOF + } + chunk := r.chunks[r.idx] + r.idx++ + n := copy(p, chunk) + if n < len(chunk) { + // Should not happen with our 32KiB read buffer and small fixture + // chunks, but guard against silent truncation. + panic("chunkedReader: read buffer too small for fixture chunk") + } + return n, nil +} + +func TestGateCasesFixtures(t *testing.T) { + var cases []gateCaseJSON + loadFixture(t, "gate-cases.json", &cases) + if len(cases) == 0 { + t.Fatal("no fixture cases loaded") + } + + for _, c := range cases { + c := c + t.Run(c.Name, func(t *testing.T) { + r := newChunkedReader(c.Chunks) + res := Run(context.Background(), r, Options{ + Family: Family(c.Family), + EventCap: c.EventCap, + ByteCap: c.ByteCap, + CaptureCommitMarker: true, + }) + + if res.ReadErr != nil { + t.Fatalf("unexpected ReadErr: %v", res.ReadErr) + } + if res.Committed != c.Expected.Committed { + t.Fatalf("Committed = %v, want %v (failure=%+v)", res.Committed, c.Expected.Committed, res.Failure) + } + + if !c.Expected.Committed { + if res.Failure == nil { + t.Fatal("expected Failure, got nil") + } + if res.Failure.Reason != c.Expected.Reason { + t.Errorf("Reason = %q, want %q", res.Failure.Reason, c.Expected.Reason) + } + if res.Failure.TerminalBeforeContent != c.Expected.TerminalBeforeContent { + t.Errorf("TerminalBeforeContent = %v, want %v", res.Failure.TerminalBeforeContent, c.Expected.TerminalBeforeContent) + } + return + } + + var prefixBytes int + for _, chunk := range res.Prefix { + prefixBytes += len(chunk) + } + if prefixBytes != c.Expected.PrefixBytes { + t.Errorf("prefixBytes = %d, want %d", prefixBytes, c.Expected.PrefixBytes) + } + if res.ReaderDone != c.Expected.ReaderDone { + t.Errorf("ReaderDone = %v, want %v", res.ReaderDone, c.Expected.ReaderDone) + } + if c.Expected.CommitMarker != nil { + if res.CommitMarker == nil { + t.Fatal("expected CommitMarker, got nil") + } + wm := c.Expected.CommitMarker + if res.CommitMarker.FrameIndex != wm.FrameIndex { + t.Errorf("CommitMarker.FrameIndex = %d, want %d", res.CommitMarker.FrameIndex, wm.FrameIndex) + } + if res.CommitMarker.ChunkIndex != wm.ChunkIndex { + t.Errorf("CommitMarker.ChunkIndex = %d, want %d", res.CommitMarker.ChunkIndex, wm.ChunkIndex) + } + if !eqPtrStr(res.CommitMarker.EventName, wm.EventName) { + t.Errorf("CommitMarker.EventName = %v, want %v", ptrStr(res.CommitMarker.EventName), ptrStr(wm.EventName)) + } + if res.CommitMarker.BufferedBytes != wm.BufferedBytes { + t.Errorf("CommitMarker.BufferedBytes = %d, want %d", res.CommitMarker.BufferedBytes, wm.BufferedBytes) + } + if res.CommitMarker.EchoExcludedBytes != wm.EchoExcludedBytes { + t.Errorf("CommitMarker.EchoExcludedBytes = %d, want %d", res.CommitMarker.EchoExcludedBytes, wm.EchoExcludedBytes) + } + } + + // Verify the prefix bytes are exactly the concatenation of the + // committed chunks in order (sanity check beyond byte count). + var buf bytes.Buffer + for _, chunk := range res.Prefix { + buf.Write(chunk) + } + joined := strings.Join(c.Chunks, "") + if !strings.HasPrefix(joined, buf.String()) { + t.Errorf("prefix is not a prefix of the concatenated input chunks") + } + }) + } +} diff --git a/edge/internal/gate/run_test.go b/edge/internal/gate/run_test.go new file mode 100644 index 0000000000..db2a0a0fdf --- /dev/null +++ b/edge/internal/gate/run_test.go @@ -0,0 +1,195 @@ +package gate + +import ( + "context" + "errors" + "io" + "sync/atomic" + "testing" + "time" +) + +// blockingReader never returns from Read until unblocked, then reports EOF. +type blockingReader struct { + unblock chan struct{} +} + +func newBlockingReader() *blockingReader { + return &blockingReader{unblock: make(chan struct{})} +} + +func (r *blockingReader) Read(p []byte) (int, error) { + <-r.unblock + return 0, io.EOF +} + +func (r *blockingReader) Release() { + close(r.unblock) +} + +func TestRunIdleTimeout(t *testing.T) { + r := newBlockingReader() + defer r.Release() // unblock the leaked goroutine so the test process can exit cleanly + + res := Run(context.Background(), r, Options{ + Family: FamilyAnthropic, + EventCap: 64, + ByteCap: 1024, + IdleTimeout: 20 * time.Millisecond, + }) + + if res.Committed { + t.Fatal("expected failure, got committed") + } + if res.Failure == nil || res.Failure.Reason != "idle_timeout" { + t.Fatalf("expected idle_timeout failure, got %+v (readErr=%v)", res.Failure, res.ReadErr) + } +} + +func TestRunIdleTimeoutDoesNotFireWhileChunksArrive(t *testing.T) { + // A reader that trickles bytes in well within the idle timeout should + // commit normally rather than timing out. + pr, pw := io.Pipe() + go func() { + defer pw.Close() + frames := []string{ + "event: ping\ndata: {\"type\":\"ping\"}\n\n", + "event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"delta\":{\"text\":\"hi\"}}\n\n", + } + for _, f := range frames { + _, _ = pw.Write([]byte(f)) + time.Sleep(5 * time.Millisecond) + } + }() + + res := Run(context.Background(), pr, Options{ + Family: FamilyAnthropic, + EventCap: 64, + ByteCap: 4096, + IdleTimeout: 200 * time.Millisecond, + }) + if !res.Committed { + t.Fatalf("expected commit, got failure=%+v readErr=%v", res.Failure, res.ReadErr) + } +} + +func TestRunContextCancellation(t *testing.T) { + r := newBlockingReader() + defer r.Release() + + ctx, cancel := context.WithCancel(context.Background()) + done := make(chan Result, 1) + go func() { + done <- Run(ctx, r, Options{Family: FamilyAnthropic, EventCap: 64, ByteCap: 1024}) + }() + + time.Sleep(20 * time.Millisecond) + cancel() + + select { + case res := <-done: + if res.Committed { + t.Fatal("expected failure, got committed") + } + if res.ReadErr == nil { + t.Fatal("expected ReadErr to be set on ctx cancellation") + } + if !errors.Is(res.ReadErr, context.Canceled) { + t.Fatalf("expected context.Canceled, got %v", res.ReadErr) + } + case <-time.After(2 * time.Second): + t.Fatal("Run did not return after ctx cancellation") + } +} + +func TestRunOnFirstByteCalledOnce(t *testing.T) { + body := "event: ping\ndata: {\"type\":\"ping\"}\n\n" + + "event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"delta\":{\"text\":\"hi\"}}\n\n" + r := newChunkedReader([]string{"e", "vent: ping\n", body[12:]}) + + var calls int32 + res := Run(context.Background(), r, Options{ + Family: FamilyAnthropic, + EventCap: 64, + ByteCap: 4096, + OnFirstByte: func() { + atomic.AddInt32(&calls, 1) + }, + }) + if !res.Committed { + t.Fatalf("expected commit, got failure=%+v", res.Failure) + } + if got := atomic.LoadInt32(&calls); got != 1 { + t.Fatalf("OnFirstByte called %d times, want 1", got) + } +} + +func TestRunBudgetExhaustionYieldsLocalCapacity(t *testing.T) { + budget := NewBudget(1024) + // Exhaust the budget with a held lease (limit is large enough for Run's + // own reservation to be valid in principle -- it should queue, not be + // rejected outright for exceeding the limit). + held, err := budget.Acquire(context.Background(), 1024) + if err != nil { + t.Fatalf("Acquire: %v", err) + } + defer held.Release() + + r := newChunkedReader([]string{"event: ping\ndata: {\"type\":\"ping\"}\n\n"}) + + ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond) + defer cancel() + + res := Run(ctx, r, Options{ + Family: FamilyAnthropic, + EventCap: 64, + ByteCap: 256, + Budget: budget, + }) + + if res.Committed { + t.Fatal("expected failure, got committed") + } + if res.Failure == nil || res.Failure.Reason != "local_capacity" { + t.Fatalf("expected local_capacity failure, got failure=%+v readErr=%v", res.Failure, res.ReadErr) + } +} + +func TestRunBudgetGrantedAfterRelease(t *testing.T) { + budget := NewBudget(4096) + held, err := budget.Acquire(context.Background(), 4096) + if err != nil { + t.Fatalf("Acquire: %v", err) + } + + r := newChunkedReader([]string{ + "event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"delta\":{\"text\":\"hi\"}}\n\n", + }) + + done := make(chan Result, 1) + go func() { + done <- Run(context.Background(), r, Options{ + Family: FamilyAnthropic, + EventCap: 64, + ByteCap: 1024, + Budget: budget, + }) + }() + + time.Sleep(20 * time.Millisecond) + held.Release() + + select { + case res := <-done: + if !res.Committed { + t.Fatalf("expected commit, got failure=%+v readErr=%v", res.Failure, res.ReadErr) + } + case <-time.After(2 * time.Second): + t.Fatal("Run did not return after budget release") + } + + reserved, waiting, _ := budget.Snapshot() + if reserved != 0 || waiting != 0 { + t.Fatalf("expected budget fully released, got reserved=%d waiting=%d", reserved, waiting) + } +} diff --git a/edge/internal/ojson/fixtures_test.go b/edge/internal/ojson/fixtures_test.go new file mode 100644 index 0000000000..7eb15a1a6f --- /dev/null +++ b/edge/internal/ojson/fixtures_test.go @@ -0,0 +1,72 @@ +package ojson + +import ( + "encoding/json" + "os" + "strconv" + "testing" +) + +const fixtureDir = "../../../tests/fixtures/edge/body" + +func readFixture(t *testing.T, name string, v interface{}) { + t.Helper() + data, err := os.ReadFile(fixtureDir + "/" + name) + if err != nil { + t.Fatalf("read fixture %s: %v", name, err) + } + if err := json.Unmarshal(data, v); err != nil { + t.Fatalf("unmarshal fixture %s: %v", name, err) + } +} + +type stableCase struct { + Name string `json:"name"` + Value string `json:"value"` + Expected string `json:"expected"` +} + +func TestStableStringifyFixtures(t *testing.T) { + var cases []stableCase + readFixture(t, "stable-stringify.json", &cases) + if len(cases) == 0 { + t.Fatal("no fixture cases loaded") + } + for _, c := range cases { + t.Run(c.Name, func(t *testing.T) { + v, err := Parse([]byte(c.Value), 0) + if err != nil { + t.Fatalf("parse input: %v", err) + } + got := StableStringify(v) + if got != c.Expected { + t.Fatalf("StableStringify mismatch\n got: %s\nwant: %s", got, c.Expected) + } + }) + } +} + +type jsNumberCase struct { + Literal string `json:"literal"` + Expected string `json:"expected"` +} + +func TestFormatJSNumberFixtures(t *testing.T) { + var cases []jsNumberCase + readFixture(t, "js-number.json", &cases) + if len(cases) == 0 { + t.Fatal("no fixture cases loaded") + } + for _, c := range cases { + t.Run(c.Literal, func(t *testing.T) { + f, err := strconv.ParseFloat(c.Literal, 64) + if err != nil { + t.Fatalf("parse literal %q: %v", c.Literal, err) + } + got := FormatJSNumber(f) + if got != c.Expected { + t.Fatalf("FormatJSNumber(%s) = %s, want %s", c.Literal, got, c.Expected) + } + }) + } +} diff --git a/edge/internal/ojson/jsnumber.go b/edge/internal/ojson/jsnumber.go new file mode 100644 index 0000000000..32fde6b150 --- /dev/null +++ b/edge/internal/ojson/jsnumber.go @@ -0,0 +1,101 @@ +package ojson + +import ( + "math" + "strconv" + "strings" +) + +// FormatJSNumber formats f exactly the way JavaScript's Number.prototype.toString +// (and, for finite numbers, JSON.stringify) does: the ECMA-262 Number::toString +// algorithm. -0 formats as "0". NaN and +/-Infinity are not valid JSON numbers; +// they are formatted as "NaN", "Infinity", "-Infinity" respectively for +// completeness but callers should not feed them into JSON output. +func FormatJSNumber(f float64) string { + if math.IsNaN(f) { + return "NaN" + } + if math.IsInf(f, 1) { + return "Infinity" + } + if math.IsInf(f, -1) { + return "-Infinity" + } + if f == 0 { + return "0" // drops sign, matches (-0).toString() === "0" + } + + neg := f < 0 + x := f + if neg { + x = -x + } + + digits, n := shortestDigits(x) + k := len(digits) + + var sb strings.Builder + if neg { + sb.WriteByte('-') + } + + switch { + case k <= n && n <= 21: + sb.WriteString(digits) + for i := 0; i < n-k; i++ { + sb.WriteByte('0') + } + case 0 < n && n <= 21: + sb.WriteString(digits[:n]) + sb.WriteByte('.') + sb.WriteString(digits[n:]) + case -6 < n && n <= 0: + sb.WriteString("0.") + for i := 0; i < -n; i++ { + sb.WriteByte('0') + } + sb.WriteString(digits) + default: + if k == 1 { + sb.WriteString(digits) + } else { + sb.WriteString(digits[:1]) + sb.WriteByte('.') + sb.WriteString(digits[1:]) + } + sb.WriteByte('e') + exp := n - 1 + if exp >= 0 { + sb.WriteByte('+') + } else { + sb.WriteByte('-') + exp = -exp + } + sb.WriteString(strconv.Itoa(exp)) + } + + return sb.String() +} + +// shortestDigits returns the shortest round-trip decimal digit string for +// x > 0 (no leading/trailing zeros) and n such that 10^(n-1) <= x < 10^n. +func shortestDigits(x float64) (digits string, n int) { + formatted := strconv.FormatFloat(x, 'e', -1, 64) + // formatted looks like "d[.ddd]e±dd" + eIdx := strings.IndexByte(formatted, 'e') + mantissa := formatted[:eIdx] + expPart := formatted[eIdx+1:] + exp, err := strconv.Atoi(expPart) + if err != nil { + // unreachable for valid strconv output + exp = 0 + } + dotIdx := strings.IndexByte(mantissa, '.') + if dotIdx == -1 { + digits = mantissa + } else { + digits = mantissa[:dotIdx] + mantissa[dotIdx+1:] + } + n = exp + 1 + return digits, n +} diff --git a/edge/internal/ojson/stablestringify.go b/edge/internal/ojson/stablestringify.go new file mode 100644 index 0000000000..75331c11e5 --- /dev/null +++ b/edge/internal/ojson/stablestringify.go @@ -0,0 +1,89 @@ +package ojson + +import ( + "bytes" + "sort" + "unicode/utf16" +) + +// StableStringify ports src/lib/request-identity.ts stableStringify: object +// keys are sorted by UTF-16 code unit order (matching JS string comparison), +// arrays keep their order, and numbers are formatted via the float64 JS +// Number::toString algorithm (the value already went through JSON.parse in +// the JS reference, so any non-canonical literal - "1.0", "1e2", etc. - is +// normalized). No whitespace is emitted. +func StableStringify(v *Value) string { + var buf bytes.Buffer + writeStable(&buf, v) + return buf.String() +} + +func writeStable(buf *bytes.Buffer, v *Value) { + if v == nil { + buf.WriteString("null") + return + } + switch v.kind { + case KindNull: + buf.WriteString("null") + case KindBool: + if v.b { + buf.WriteString("true") + } else { + buf.WriteString("false") + } + case KindNumber: + f, err := v.Float64() + if err != nil { + buf.WriteString("null") + return + } + buf.WriteString(FormatJSNumber(f)) + case KindString: + writeJSONString(buf, v.str) + case KindArray: + buf.WriteByte('[') + for i, item := range v.arr { + if i > 0 { + buf.WriteByte(',') + } + writeStable(buf, item) + } + buf.WriteByte(']') + case KindObject: + buf.WriteByte('{') + if v.obj != nil { + keys := make([]string, len(v.obj.keys)) + copy(keys, v.obj.keys) + sort.Slice(keys, func(i, j int) bool { return LessUTF16(keys[i], keys[j]) }) + for i, k := range keys { + if i > 0 { + buf.WriteByte(',') + } + writeJSONString(buf, k) + buf.WriteByte(':') + val, _ := v.obj.get(k) + writeStable(buf, val) + } + } + buf.WriteByte('}') + } +} + +// LessUTF16 compares a and b the way JavaScript's `<` operator on strings +// does: lexicographic comparison of UTF-16 code units (not Unicode code +// points), so astral characters compare as their surrogate-pair units. +func LessUTF16(a, b string) bool { + ua := utf16.Encode([]rune(a)) + ub := utf16.Encode([]rune(b)) + n := len(ua) + if len(ub) < n { + n = len(ub) + } + for i := 0; i < n; i++ { + if ua[i] != ub[i] { + return ua[i] < ub[i] + } + } + return len(ua) < len(ub) +} diff --git a/edge/internal/ojson/value.go b/edge/internal/ojson/value.go new file mode 100644 index 0000000000..e955d57e37 --- /dev/null +++ b/edge/internal/ojson/value.go @@ -0,0 +1,446 @@ +// Package ojson implements an order-preserving JSON value tree, mirroring the +// semantics JavaScript's JSON.parse/JSON.stringify give when applied to a +// plain object: object key insertion order is preserved (duplicate keys keep +// the position of their first occurrence and the value of their last +// occurrence), and numeric literals are read as raw text so callers can +// choose whether to preserve them verbatim or re-derive a JS-compatible +// float64 form. +package ojson + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "strconv" + "unicode/utf8" +) + +// Kind identifies the JSON type held by a Value. +type Kind int + +const ( + KindNull Kind = iota + KindBool + KindNumber + KindString + KindArray + KindObject +) + +// DefaultMaxDepth is used when callers do not need a specific limit. +const DefaultMaxDepth = 512 + +// Value is an immutable-shaped JSON tree node. Zero value is JSON null. +type Value struct { + kind Kind + b bool + num string // raw numeric literal, exactly as it appeared in the source + str string + arr []*Value + obj *object +} + +// object is an order-preserving string -> *Value map. +type object struct { + keys []string + m map[string]*Value +} + +func newObject() *object { + return &object{m: make(map[string]*Value)} +} + +func (o *object) set(key string, v *Value) { + if _, ok := o.m[key]; !ok { + o.keys = append(o.keys, key) + } + o.m[key] = v +} + +func (o *object) get(key string) (*Value, bool) { + v, ok := o.m[key] + return v, ok +} + +func (o *object) delete(key string) { + if _, ok := o.m[key]; !ok { + return + } + delete(o.m, key) + for i, k := range o.keys { + if k == key { + o.keys = append(o.keys[:i], o.keys[i+1:]...) + break + } + } +} + +// ---- constructors ---- + +func NewNull() *Value { return &Value{kind: KindNull} } + +func NewBool(b bool) *Value { return &Value{kind: KindBool, b: b} } + +func NewString(s string) *Value { return &Value{kind: KindString, str: s} } + +// NewNumberLiteral builds a number Value from a raw JSON numeric literal. +// The literal is not validated; callers are expected to pass valid JSON +// number text (e.g. from json.Number). +func NewNumberLiteral(raw string) *Value { return &Value{kind: KindNumber, num: raw} } + +// NewNumberFromFloat builds a number Value using the JS Number::toString +// textual form of f. +func NewNumberFromFloat(f float64) *Value { + return &Value{kind: KindNumber, num: FormatJSNumber(f)} +} + +func NewArray() *Value { return &Value{kind: KindArray} } + +func NewObject() *Value { return &Value{kind: KindObject, obj: newObject()} } + +// ---- accessors ---- + +func (v *Value) Kind() Kind { + if v == nil { + return KindNull + } + return v.kind +} + +func (v *Value) IsNull() bool { return v == nil || v.kind == KindNull } +func (v *Value) IsBool() bool { return v != nil && v.kind == KindBool } +func (v *Value) IsNumber() bool { return v != nil && v.kind == KindNumber } +func (v *Value) IsString() bool { return v != nil && v.kind == KindString } +func (v *Value) IsArray() bool { return v != nil && v.kind == KindArray } +func (v *Value) IsObject() bool { return v != nil && v.kind == KindObject } + +func (v *Value) Bool() bool { + if v == nil { + return false + } + return v.b +} + +// Number returns the raw numeric literal text. +func (v *Value) Number() string { + if v == nil { + return "" + } + return v.num +} + +// Float64 parses the numeric literal as float64. +func (v *Value) Float64() (float64, error) { + if v == nil || v.kind != KindNumber { + return 0, fmt.Errorf("ojson: not a number") + } + return strconv.ParseFloat(v.num, 64) +} + +func (v *Value) String() string { + if v == nil { + return "" + } + return v.str +} + +func (v *Value) ArrayItems() []*Value { + if v == nil || v.kind != KindArray { + return nil + } + return v.arr +} + +func (v *Value) ArrayLen() int { + if v == nil || v.kind != KindArray { + return 0 + } + return len(v.arr) +} + +func (v *Value) ArrayGet(i int) *Value { + if v == nil || v.kind != KindArray || i < 0 || i >= len(v.arr) { + return nil + } + return v.arr[i] +} + +func (v *Value) ArrayAppend(item *Value) { + if v == nil || v.kind != KindArray { + return + } + v.arr = append(v.arr, item) +} + +// ObjectKeys returns object keys in insertion order. Nil for non-objects. +func (v *Value) ObjectKeys() []string { + if v == nil || v.kind != KindObject || v.obj == nil { + return nil + } + return v.obj.keys +} + +func (v *Value) ObjectLen() int { + if v == nil || v.kind != KindObject || v.obj == nil { + return 0 + } + return len(v.obj.keys) +} + +// ObjectGet returns the value for key and whether it was present. +func (v *Value) ObjectGet(key string) (*Value, bool) { + if v == nil || v.kind != KindObject || v.obj == nil { + return nil, false + } + return v.obj.get(key) +} + +// ObjectHas reports whether key is an own key of the object. +func (v *Value) ObjectHas(key string) bool { + _, ok := v.ObjectGet(key) + return ok +} + +// ObjectSet sets key to val. An existing key keeps its position; a new key +// is appended at the end (matches JS assignment semantics). +func (v *Value) ObjectSet(key string, val *Value) { + if v == nil || v.kind != KindObject { + return + } + if v.obj == nil { + v.obj = newObject() + } + v.obj.set(key, val) +} + +// ObjectDelete removes key if present. +func (v *Value) ObjectDelete(key string) { + if v == nil || v.kind != KindObject || v.obj == nil { + return + } + v.obj.delete(key) +} + +// Clone deep-copies the value. +func (v *Value) Clone() *Value { + if v == nil { + return nil + } + switch v.kind { + case KindArray: + items := make([]*Value, len(v.arr)) + for i, item := range v.arr { + items[i] = item.Clone() + } + return &Value{kind: KindArray, arr: items} + case KindObject: + out := &Value{kind: KindObject, obj: newObject()} + if v.obj != nil { + for _, k := range v.obj.keys { + val, _ := v.obj.get(k) + out.obj.set(k, val.Clone()) + } + } + return out + default: + cp := *v + return &cp + } +} + +// ---- parsing ---- + +// Parse decodes data into an order-preserving Value tree. maxDepth <= 0 uses +// DefaultMaxDepth. Duplicate object keys: the position of the first +// occurrence is kept, the value of the last occurrence wins (matches +// JSON.parse assignment semantics). +func Parse(data []byte, maxDepth int) (*Value, error) { + if maxDepth <= 0 { + maxDepth = DefaultMaxDepth + } + dec := json.NewDecoder(bytes.NewReader(data)) + dec.UseNumber() + + tok, err := dec.Token() + if err != nil { + return nil, err + } + v, err := buildFromToken(dec, tok, 1, maxDepth) + if err != nil { + return nil, err + } + // Reject trailing garbage after the top-level value. + if _, err := dec.Token(); err != io.EOF { + if err == nil { + return nil, fmt.Errorf("ojson: unexpected trailing data") + } + return nil, err + } + return v, nil +} + +func parseValue(dec *json.Decoder, depth, maxDepth int) (*Value, error) { + tok, err := dec.Token() + if err != nil { + return nil, err + } + return buildFromToken(dec, tok, depth, maxDepth) +} + +func buildFromToken(dec *json.Decoder, tok json.Token, depth, maxDepth int) (*Value, error) { + switch t := tok.(type) { + case json.Delim: + if depth > maxDepth { + return nil, fmt.Errorf("ojson: max depth %d exceeded", maxDepth) + } + switch t { + case '{': + obj := newObject() + for dec.More() { + keyTok, err := dec.Token() + if err != nil { + return nil, err + } + key, ok := keyTok.(string) + if !ok { + return nil, fmt.Errorf("ojson: expected object key") + } + val, err := parseValue(dec, depth+1, maxDepth) + if err != nil { + return nil, err + } + obj.set(key, val) + } + if _, err := dec.Token(); err != nil { // consume '}' + return nil, err + } + return &Value{kind: KindObject, obj: obj}, nil + case '[': + var arr []*Value + for dec.More() { + val, err := parseValue(dec, depth+1, maxDepth) + if err != nil { + return nil, err + } + arr = append(arr, val) + } + if _, err := dec.Token(); err != nil { // consume ']' + return nil, err + } + return &Value{kind: KindArray, arr: arr}, nil + default: + return nil, fmt.Errorf("ojson: unexpected delimiter %v", t) + } + case bool: + return &Value{kind: KindBool, b: t}, nil + case json.Number: + return &Value{kind: KindNumber, num: string(t)}, nil + case string: + return &Value{kind: KindString, str: t}, nil + case nil: + return &Value{kind: KindNull}, nil + default: + return nil, fmt.Errorf("ojson: unexpected token %T", tok) + } +} + +// ---- serialization ---- + +// Marshal serializes the value compactly (no whitespace), preserving object +// key order and original numeric literals, and escaping strings the way +// JSON.stringify does. +func (v *Value) Marshal() []byte { + var buf bytes.Buffer + v.writeTo(&buf) + return buf.Bytes() +} + +func (v *Value) writeTo(buf *bytes.Buffer) { + if v == nil { + buf.WriteString("null") + return + } + switch v.kind { + case KindNull: + buf.WriteString("null") + case KindBool: + if v.b { + buf.WriteString("true") + } else { + buf.WriteString("false") + } + case KindNumber: + if v.num == "" { + buf.WriteString("0") + } else { + buf.WriteString(v.num) + } + case KindString: + writeJSONString(buf, v.str) + case KindArray: + buf.WriteByte('[') + for i, item := range v.arr { + if i > 0 { + buf.WriteByte(',') + } + item.writeTo(buf) + } + buf.WriteByte(']') + case KindObject: + buf.WriteByte('{') + if v.obj != nil { + for i, k := range v.obj.keys { + if i > 0 { + buf.WriteByte(',') + } + writeJSONString(buf, k) + buf.WriteByte(':') + val, _ := v.obj.get(k) + val.writeTo(buf) + } + } + buf.WriteByte('}') + } +} + +// writeJSONString escapes s exactly the way JSON.stringify does: escape `"`, +// `\`, and control characters (<0x20) using the short escapes where +// available (\b \f \n \r \t) or \u00XX otherwise. `<`, `>`, `&` and +// non-ASCII characters are emitted verbatim. Invalid UTF-8 bytes are +// replaced with U+FFFD. +func writeJSONString(buf *bytes.Buffer, s string) { + buf.WriteByte('"') + for i := 0; i < len(s); { + r, size := utf8.DecodeRuneInString(s[i:]) + if r == utf8.RuneError && size == 1 { + buf.WriteRune(utf8.RuneError) + i++ + continue + } + switch r { + case '"': + buf.WriteString(`\"`) + case '\\': + buf.WriteString(`\\`) + case '\b': + buf.WriteString(`\b`) + case '\f': + buf.WriteString(`\f`) + case '\n': + buf.WriteString(`\n`) + case '\r': + buf.WriteString(`\r`) + case '\t': + buf.WriteString(`\t`) + default: + if r < 0x20 { + fmt.Fprintf(buf, `\u%04x`, r) + } else { + buf.WriteRune(r) + } + } + i += size + } + buf.WriteByte('"') +} diff --git a/edge/internal/ojson/value_test.go b/edge/internal/ojson/value_test.go new file mode 100644 index 0000000000..a052d66609 --- /dev/null +++ b/edge/internal/ojson/value_test.go @@ -0,0 +1,95 @@ +package ojson + +import "testing" + +func TestParseMarshalRoundTripPreservesLiterals(t *testing.T) { + input := `{"b":1,"a":2.50000000000000000,"c":1e21,"nested":{"z":1,"y":2}}` + v, err := Parse([]byte(input), 0) + if err != nil { + t.Fatalf("parse: %v", err) + } + got := string(v.Marshal()) + if got != input { + t.Fatalf("marshal mismatch\n got: %s\nwant: %s", got, input) + } +} + +func TestParseDuplicateKeysLastValueFirstPosition(t *testing.T) { + v, err := Parse([]byte(`{"a":1,"b":2,"a":3}`), 0) + if err != nil { + t.Fatalf("parse: %v", err) + } + keys := v.ObjectKeys() + if len(keys) != 2 || keys[0] != "a" || keys[1] != "b" { + t.Fatalf("unexpected key order: %v", keys) + } + val, ok := v.ObjectGet("a") + if !ok || val.Number() != "3" { + t.Fatalf("expected a=3, got %v", val) + } + if string(v.Marshal()) != `{"a":3,"b":2}` { + t.Fatalf("unexpected marshal: %s", v.Marshal()) + } +} + +func TestObjectSetKeepsPositionOnExistingKey(t *testing.T) { + v, _ := Parse([]byte(`{"a":1,"b":2,"c":3}`), 0) + v.ObjectSet("b", NewNumberLiteral("99")) + if string(v.Marshal()) != `{"a":1,"b":99,"c":3}` { + t.Fatalf("unexpected marshal: %s", v.Marshal()) + } + v.ObjectSet("d", NewNumberLiteral("4")) + if string(v.Marshal()) != `{"a":1,"b":99,"c":3,"d":4}` { + t.Fatalf("unexpected marshal after append: %s", v.Marshal()) + } + v.ObjectDelete("a") + if string(v.Marshal()) != `{"b":99,"c":3,"d":4}` { + t.Fatalf("unexpected marshal after delete: %s", v.Marshal()) + } +} + +func TestCloneIsIndependent(t *testing.T) { + v, _ := Parse([]byte(`{"a":[1,2,{"x":1}]}`), 0) + clone := v.Clone() + arr, _ := v.ObjectGet("a") + arr.ArrayAppend(NewNumberLiteral("3")) + cloneArr, _ := clone.ObjectGet("a") + if cloneArr.ArrayLen() != 3 { + t.Fatalf("expected clone array to stay at 3 items, got %d", cloneArr.ArrayLen()) + } +} + +func TestParseDepthLimit(t *testing.T) { + nested := "" + for i := 0; i < 20; i++ { + nested += `{"a":` + } + nested += "1" + for i := 0; i < 20; i++ { + nested += "}" + } + if _, err := Parse([]byte(nested), 5); err == nil { + t.Fatal("expected depth-limit error") + } + if _, err := Parse([]byte(nested), 50); err != nil { + t.Fatalf("expected success with generous depth limit: %v", err) + } +} + +func TestMarshalStringEscaping(t *testing.T) { + v := NewString("a\"b\\c\nd\teg&h\x01i") + got := string(v.Marshal()) + want := `"a\"b\\c\nd\teg&h\u0001i"` + if got != want { + t.Fatalf("got %s, want %s", got, want) + } +} + +func TestMarshalUnicodeNotEscaped(t *testing.T) { + v := NewString("中文\U0001F600") + got := string(v.Marshal()) + want := "\"中文\U0001F600\"" + if got != want { + t.Fatalf("got %s, want %s", got, want) + } +} diff --git a/edge/internal/report/outbox.go b/edge/internal/report/outbox.go new file mode 100644 index 0000000000..a4f65a8bcf --- /dev/null +++ b/edge/internal/report/outbox.go @@ -0,0 +1,192 @@ +package report + +import ( + "bufio" + "bytes" + "encoding/json" + "errors" + "fmt" + "os" + "path/filepath" + "sort" + "strings" + "sync" + "time" +) + +const ( + outboxPrefix = "complete-" + outboxSuffix = ".jsonl" + rotateAtBytes = 64 << 20 +) + +// Outbox is an append-only on-disk queue of completion reports that could not be +// delivered. Each line is one serialized CompleteRequest. +type Outbox struct { + dir string + maxBytes int64 + mu sync.Mutex + current string +} + +func NewOutbox(dir string, maxBytes int64) (*Outbox, error) { + if err := os.MkdirAll(dir, 0o700); err != nil { + return nil, err + } + return &Outbox{dir: dir, maxBytes: maxBytes}, nil +} + +func (o *Outbox) files() ([]string, error) { + entries, err := os.ReadDir(o.dir) + if err != nil { + return nil, err + } + var names []string + for _, entry := range entries { + name := entry.Name() + if !entry.IsDir() && strings.HasPrefix(name, outboxPrefix) && strings.HasSuffix(name, outboxSuffix) { + names = append(names, filepath.Join(o.dir, name)) + } + } + sort.Strings(names) + return names, nil +} + +// PendingBytes returns the total size of queued reports. +func (o *Outbox) PendingBytes() int64 { + o.mu.Lock() + defer o.mu.Unlock() + names, err := o.files() + if err != nil { + return 0 + } + var total int64 + for _, name := range names { + if info, statErr := os.Stat(name); statErr == nil { + total += info.Size() + } + } + return total +} + +// Append durably queues one report, dropping the oldest files when over budget. +func (o *Outbox) Append(report []byte) error { + if bytes.ContainsRune(report, '\n') { + return errors.New("outbox report must be a single line") + } + o.mu.Lock() + defer o.mu.Unlock() + + if o.current != "" { + if info, err := os.Stat(o.current); err != nil || info.Size() >= rotateAtBytes { + o.current = "" + } + } + if o.current == "" { + o.current = filepath.Join(o.dir, fmt.Sprintf("%s%020d%s", outboxPrefix, time.Now().UnixNano(), outboxSuffix)) + } + file, err := os.OpenFile(o.current, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o600) + if err != nil { + return err + } + if _, err = file.Write(append(report, '\n')); err == nil { + err = file.Sync() + } + closeErr := file.Close() + if err == nil { + err = closeErr + } + if err != nil { + return err + } + o.enforceBudgetLocked() + return nil +} + +func (o *Outbox) enforceBudgetLocked() { + names, err := o.files() + if err != nil { + return + } + var total int64 + sizes := make([]int64, len(names)) + for index, name := range names { + if info, statErr := os.Stat(name); statErr == nil { + sizes[index] = info.Size() + total += info.Size() + } + } + for index := 0; total > o.maxBytes && index < len(names)-1; index++ { + if os.Remove(names[index]) == nil { + total -= sizes[index] + } + } +} + +// Drain sends queued reports oldest first. It stops at the first delivery error, +// keeping the undelivered remainder on disk. send returning ErrDrop discards a line. +func (o *Outbox) Drain(send func(json.RawMessage) error) (delivered int, err error) { + o.mu.Lock() + names, err := o.files() + o.current = "" + o.mu.Unlock() + if err != nil { + return 0, err + } + for _, name := range names { + count, drainErr := o.drainFile(name, send) + delivered += count + if drainErr != nil { + return delivered, drainErr + } + } + return delivered, nil +} + +// ErrDrop tells Drain to discard a report that can never be delivered. +var ErrDrop = errors.New("drop report") + +func (o *Outbox) drainFile(name string, send func(json.RawMessage) error) (int, error) { + o.mu.Lock() + data, err := os.ReadFile(name) + o.mu.Unlock() + if err != nil { + return 0, err + } + scanner := bufio.NewScanner(bytes.NewReader(data)) + scanner.Buffer(make([]byte, 0, 64<<10), 16<<20) + var remaining [][]byte + delivered := 0 + var sendErr error + for scanner.Scan() { + line := append([]byte(nil), scanner.Bytes()...) + if len(bytes.TrimSpace(line)) == 0 { + continue + } + if sendErr != nil { + remaining = append(remaining, line) + continue + } + if err := send(line); err != nil && !errors.Is(err, ErrDrop) { + sendErr = err + remaining = append(remaining, line) + continue + } + delivered++ + } + + o.mu.Lock() + defer o.mu.Unlock() + if len(remaining) == 0 { + _ = os.Remove(name) + return delivered, sendErr + } + tmp := name + ".tmp" + if err := os.WriteFile(tmp, append(bytes.Join(remaining, []byte("\n")), '\n'), 0o600); err != nil { + return delivered, err + } + if err := os.Rename(tmp, name); err != nil { + return delivered, err + } + return delivered, sendErr +} diff --git a/edge/internal/report/report_test.go b/edge/internal/report/report_test.go new file mode 100644 index 0000000000..149f300b04 --- /dev/null +++ b/edge/internal/report/report_test.go @@ -0,0 +1,171 @@ +package report + +import ( + "context" + "encoding/json" + "errors" + "io" + "log/slog" + "net/http" + "net/http/httptest" + "net/url" + "os" + "path/filepath" + "sync" + "sync/atomic" + "testing" + "time" + + "github.com/ding113/claude-code-hub/edge/internal/contract" + "github.com/ding113/claude-code-hub/edge/internal/control" +) + +func TestOutboxAppendDrainAndPartialFailure(t *testing.T) { + outbox, err := NewOutbox(t.TempDir(), 1<<20) + if err != nil { + t.Fatal(err) + } + for _, line := range []string{`{"a":1}`, `{"a":2}`, `{"a":3}`} { + if err := outbox.Append([]byte(line)); err != nil { + t.Fatal(err) + } + } + if outbox.PendingBytes() == 0 { + t.Fatal("expected pending bytes") + } + if err := outbox.Append([]byte("a\nb")); err == nil { + t.Fatal("multi-line reports must be rejected") + } + + var seen []string + delivered, err := outbox.Drain(func(line json.RawMessage) error { + seen = append(seen, string(line)) + if string(line) == `{"a":2}` { + return errors.New("down") + } + return nil + }) + if err == nil || delivered != 1 { + t.Fatalf("expected partial drain, got %d %v", delivered, err) + } + delivered, err = outbox.Drain(func(line json.RawMessage) error { + seen = append(seen, string(line)) + if string(line) == `{"a":3}` { + return ErrDrop + } + return nil + }) + if err != nil || delivered != 2 { + t.Fatalf("second drain: %d %v", delivered, err) + } + if outbox.PendingBytes() != 0 { + t.Fatal("outbox should be empty") + } + want := []string{`{"a":1}`, `{"a":2}`, `{"a":2}`, `{"a":3}`} + if len(seen) != len(want) { + t.Fatalf("seen %v", seen) + } + for index := range want { + if seen[index] != want[index] { + t.Fatalf("seen %v", seen) + } + } +} + +func TestOutboxBudgetDropsOldestFiles(t *testing.T) { + dir := t.TempDir() + outbox, _ := NewOutbox(dir, 10) + _ = outbox.Append([]byte(`{"first":true}`)) + outbox.current = "" // force a new file + time.Sleep(time.Millisecond) + _ = outbox.Append([]byte(`{"second":true}`)) + entries, _ := os.ReadDir(dir) + if len(entries) != 1 { + t.Fatalf("expected oldest file dropped, have %d files", len(entries)) + } + data, _ := os.ReadFile(filepath.Join(dir, entries[0].Name())) + if string(data) != "{\"second\":true}\n" { + t.Fatalf("unexpected surviving content %q", data) + } +} + +type recorder struct { + mu sync.Mutex + statuses []int + bodies []string + calls atomic.Int32 +} + +func controlServer(t *testing.T, rec *recorder) (*control.Client, func()) { + t.Helper() + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + body, _ := io.ReadAll(r.Body) + index := int(rec.calls.Add(1)) - 1 + rec.mu.Lock() + rec.bodies = append(rec.bodies, string(body)) + status := http.StatusOK + if index < len(rec.statuses) { + status = rec.statuses[index] + } + rec.mu.Unlock() + w.WriteHeader(status) + if status == http.StatusConflict { + _, _ = w.Write([]byte(`{"error":{"code":"stale_step","message":"stale_step"}}`)) + return + } + _, _ = w.Write([]byte(`{"ok":true}`)) + })) + base, _ := url.Parse(srv.URL) + return control.New(control.Options{BaseURL: base, Secret: "s", EdgeID: "e", CompleteTimeout: time.Second}), srv.Close +} + +func testLogger() *slog.Logger { return slog.New(slog.NewTextHandler(io.Discard, nil)) } + +func TestReporterRetriesThenSucceeds(t *testing.T) { + rec := &recorder{statuses: []int{500, 502, 200}} + client, closeServer := controlServer(t, rec) + defer closeServer() + reporter := NewReporter(client, nil, testLogger()) + reporter.sleep = func(context.Context, time.Duration) bool { return true } + reporter.Complete(context.Background(), &contract.CompleteRequest{RequestID: 5, EdgeToken: "t", Losers: []contract.LoserResult{}}) + reporter.Wait() + if rec.calls.Load() != 3 { + t.Fatalf("expected 3 attempts, got %d", rec.calls.Load()) + } +} + +func TestReporterSpoolsAfterRetriesAndDrainsLater(t *testing.T) { + rec := &recorder{statuses: []int{500, 500, 500, 500, 500, 500}} + client, closeServer := controlServer(t, rec) + defer closeServer() + outbox, _ := NewOutbox(t.TempDir(), 1<<20) + reporter := NewReporter(client, outbox, testLogger()) + reporter.sleep = func(context.Context, time.Duration) bool { return true } + reporter.Complete(context.Background(), &contract.CompleteRequest{RequestID: 6, EdgeToken: "t", Losers: []contract.LoserResult{}}) + reporter.Wait() + if outbox.PendingBytes() == 0 { + t.Fatal("report should be spooled") + } + delivered, err := reporter.DrainOutbox(context.Background()) + if err != nil || delivered != 1 { + t.Fatalf("drain: %d %v", delivered, err) + } + var sent contract.CompleteRequest + _ = json.Unmarshal([]byte(rec.bodies[len(rec.bodies)-1]), &sent) + if sent.RequestID != 6 { + t.Fatalf("unexpected replayed body %s", rec.bodies[len(rec.bodies)-1]) + } +} + +func TestReporterDropsStaleReports(t *testing.T) { + rec := &recorder{statuses: []int{409}} + client, closeServer := controlServer(t, rec) + defer closeServer() + outbox, _ := NewOutbox(t.TempDir(), 1<<20) + reporter := NewReporter(client, outbox, testLogger()) + reporter.Complete(context.Background(), &contract.CompleteRequest{RequestID: 7, EdgeToken: "t", Losers: []contract.LoserResult{}}) + reporter.Wait() + if rec.calls.Load() != 1 || outbox.PendingBytes() != 0 { + t.Fatalf("stale report must not be retried or spooled: calls=%d", rec.calls.Load()) + } +} diff --git a/edge/internal/report/reporter.go b/edge/internal/report/reporter.go new file mode 100644 index 0000000000..87debe444c --- /dev/null +++ b/edge/internal/report/reporter.go @@ -0,0 +1,128 @@ +// Package report delivers completion reports and heartbeats to the control plane. +package report + +import ( + "context" + "encoding/json" + "log/slog" + "sync" + "time" + + "github.com/ding113/claude-code-hub/edge/internal/contract" + "github.com/ding113/claude-code-hub/edge/internal/control" +) + +var retryDelays = []time.Duration{time.Second, 2 * time.Second, 4 * time.Second, 8 * time.Second, 16 * time.Second} + +type Reporter struct { + client *control.Client + outbox *Outbox + logger *slog.Logger + wg sync.WaitGroup + // sleep is replaceable in tests. + sleep func(context.Context, time.Duration) bool +} + +func NewReporter(client *control.Client, outbox *Outbox, logger *slog.Logger) *Reporter { + return &Reporter{client: client, outbox: outbox, logger: logger, sleep: sleepCtx} +} + +func sleepCtx(ctx context.Context, delay time.Duration) bool { + timer := time.NewTimer(delay) + defer timer.Stop() + select { + case <-ctx.Done(): + return false + case <-timer.C: + return true + } +} + +// Complete delivers the report in the background: retries with backoff, then spools +// it to the outbox. ctx bounds the retry phase (process shutdown). +func (r *Reporter) Complete(ctx context.Context, request *contract.CompleteRequest) { + body, err := json.Marshal(request) + if err != nil { + r.logger.Error("failed to encode completion report", "requestId", request.RequestID, "error", err) + return + } + r.wg.Add(1) + go func() { + defer r.wg.Done() + r.deliver(ctx, request.RequestID, body) + }() +} + +func (r *Reporter) deliver(ctx context.Context, requestID int64, body json.RawMessage) { + for attempt := 0; ; attempt++ { + err := r.client.CompleteRaw(context.WithoutCancel(ctx), body) + if err == nil { + return + } + if control.IsStale(err) { + r.logger.Warn("completion report rejected as stale", "requestId", requestID, "error", err) + return + } + if attempt >= len(retryDelays) || !r.sleep(ctx, retryDelays[attempt]) { + break + } + } + if r.outbox == nil { + r.logger.Error("completion report lost: control plane unreachable and no outbox", "requestId", requestID) + return + } + if err := r.outbox.Append(body); err != nil { + r.logger.Error("failed to spool completion report", "requestId", requestID, "error", err) + return + } + r.logger.Warn("completion report spooled to outbox", "requestId", requestID) +} + +// DrainOutbox replays spooled reports; stale rejections are dropped. +func (r *Reporter) DrainOutbox(ctx context.Context) (int, error) { + if r.outbox == nil { + return 0, nil + } + return r.outbox.Drain(func(line json.RawMessage) error { + err := r.client.CompleteRaw(ctx, line) + if err != nil && control.IsStale(err) { + return ErrDrop + } + return err + }) +} + +// RunOutboxDrainer drains the outbox periodically until ctx is done. +func (r *Reporter) RunOutboxDrainer(ctx context.Context, interval time.Duration) { + ticker := time.NewTicker(interval) + defer ticker.Stop() + for { + if delivered, err := r.DrainOutbox(ctx); delivered > 0 || err != nil { + r.logger.Info("outbox drain", "delivered", delivered, "error", errString(err)) + } + select { + case <-ctx.Done(): + return + case <-ticker.C: + } + } +} + +// Wait blocks until in-flight deliveries have finished or been spooled. +func (r *Reporter) Wait() { r.wg.Wait() } + +// Heartbeat sends one heartbeat; failures are logged at debug level only. +func (r *Reporter) Heartbeat(ctx context.Context, request *contract.HeartbeatRequest) error { + err := r.client.Heartbeat(ctx, request) + if err != nil { + r.logger.Debug("heartbeat failed", "requestId", request.RequestID, "error", err) + } + return err +} + +func errString(err error) string { + if err == nil { + return "" + } + return err.Error() +} diff --git a/edge/internal/route/route.go b/edge/internal/route/route.go new file mode 100644 index 0000000000..3f6f0cc333 --- /dev/null +++ b/edge/internal/route/route.go @@ -0,0 +1,81 @@ +// Package route decides which requests the edge may execute itself. +// +// Mirrors src/app/v1/_lib/unprefixed-v1-alias.ts (alias mapping) followed by +// src/app/v1/_lib/proxy/endpoint-paths.ts normalizeEndpointPath. Only POST +// /v1/messages, /v1/responses and /v1/chat/completions (or their unprefixed +// aliases) are executed at the edge; every other request is reverse-proxied to +// the control plane untouched. +package route + +import "strings" + +type Decision int + +const ( + Delegate Decision = iota + Execute +) + +// executablePaths are the normalized endpoints the edge executes itself. +var executablePaths = map[string]bool{ + "/v1/messages": true, + "/v1/responses": true, + "/v1/chat/completions": true, +} + +var unprefixedAliases = []string{"/chat/completions", "/responses", "/models", "/messages"} + +func stripQuery(path string) string { + if index := strings.IndexByte(path, '?'); index >= 0 { + return path[:index] + } + return path +} + +// MapUnprefixed ports mapUnprefixedV1Path: case-sensitive alias match on the +// path without query and without one trailing slash. +func MapUnprefixed(path string) string { + pathname := stripQuery(path) + if pathname == "" { + pathname = "/" + } + if len(pathname) > 1 && strings.HasSuffix(pathname, "/") { + pathname = strings.TrimSuffix(pathname, "/") + } + for _, alias := range unprefixedAliases { + if pathname == alias || strings.HasPrefix(pathname, alias+"/") { + return "/v1" + pathname + } + } + return pathname +} + +// Normalize ports normalizeEndpointPath: strip query and trailing slash, lowercase. +func Normalize(path string) string { + pathname := stripQuery(path) + if len(pathname) > 1 && strings.HasSuffix(pathname, "/") { + pathname = strings.TrimSuffix(pathname, "/") + } + return strings.ToLower(pathname) +} + +// CanonicalRequestPath returns the alias-mapped path with the original query, the +// form the control plane sees after rewriteUnprefixedV1Request. +func CanonicalRequestPath(rawPath string) string { + mapped := MapUnprefixed(rawPath) + if index := strings.IndexByte(rawPath, '?'); index >= 0 { + return mapped + rawPath[index:] + } + return mapped +} + +// Decide returns Execute only for POST requests to an executable endpoint. +func Decide(method, rawPath string) Decision { + if method != "POST" { + return Delegate + } + if executablePaths[Normalize(MapUnprefixed(rawPath))] { + return Execute + } + return Delegate +} diff --git a/edge/internal/route/route_test.go b/edge/internal/route/route_test.go new file mode 100644 index 0000000000..9c41fea1ab --- /dev/null +++ b/edge/internal/route/route_test.go @@ -0,0 +1,52 @@ +package route + +import "testing" + +func TestDecide(t *testing.T) { + cases := []struct { + method, path string + want Decision + }{ + {"POST", "/v1/messages", Execute}, + {"POST", "/v1/messages/", Execute}, + {"POST", "/V1/Messages", Execute}, + {"POST", "/v1/messages?beta=true", Execute}, + {"POST", "/messages", Execute}, + {"POST", "/messages/", Execute}, + {"POST", "/Messages", Delegate}, + {"POST", "/v1/messages/count_tokens", Delegate}, + {"POST", "/messages/count_tokens", Delegate}, + {"POST", "/v1/chat/completions", Execute}, + {"POST", "/chat/completions", Execute}, + {"POST", "/v1/responses", Execute}, + {"POST", "/responses?x=1", Execute}, + {"GET", "/v1/responses", Delegate}, + {"POST", "/v1/responses/compact", Delegate}, + {"POST", "/v1/embeddings", Delegate}, + {"GET", "/v1/messages", Delegate}, + {"GET", "/v1/models", Delegate}, + {"POST", "/v1/messages//", Execute}, + {"POST", "", Delegate}, + } + for _, tc := range cases { + if got := Decide(tc.method, tc.path); got != tc.want { + t.Errorf("Decide(%q, %q) = %v, want %v", tc.method, tc.path, got, tc.want) + } + } +} + +func TestCanonicalRequestPath(t *testing.T) { + cases := map[string]string{ + "/messages?beta=true": "/v1/messages?beta=true", + "/v1/messages": "/v1/messages", + "/messages/": "/v1/messages", + "/chat/completions?x": "/v1/chat/completions?x", + "/other/path": "/other/path", + "/v1/messages/?a=1&b": "/v1/messages?a=1&b", + } + for input, want := range cases { + if got := CanonicalRequestPath(input); got != want { + t.Errorf("CanonicalRequestPath(%q) = %q, want %q", input, got, want) + } + } +} diff --git a/edge/internal/server/execution.go b/edge/internal/server/execution.go new file mode 100644 index 0000000000..cb0642b6e7 --- /dev/null +++ b/edge/internal/server/execution.go @@ -0,0 +1,578 @@ +package server + +import ( + "context" + "errors" + "io" + "net/http" + "strings" + "sync" + "sync/atomic" + "time" + + "github.com/ding113/claude-code-hub/edge/internal/bodyops" + "github.com/ding113/claude-code-hub/edge/internal/contract" + "github.com/ding113/claude-code-hub/edge/internal/delegate" + "github.com/ding113/claude-code-hub/edge/internal/detect" + "github.com/ding113/claude-code-hub/edge/internal/fixer" + "github.com/ding113/claude-code-hub/edge/internal/ojson" + "github.com/ding113/claude-code-hub/edge/internal/upstream" +) + +var ( + errFirstByteTimeout = errors.New("first byte timeout") + errTotalTimeout = errors.New("non-streaming total timeout") + errClientAborted = errors.New("client aborted before commit") +) + +// Response headers never relayed to the client (recomputed by net/http or stripped +// by the local response fixer). +var skippedResponseHeaders = map[string]bool{ + "connection": true, "keep-alive": true, "transfer-encoding": true, "te": true, + "upgrade": true, "content-length": true, "content-encoding": true, + "proxy-authenticate": true, "trailer": true, "x-cch-response-fixer": true, +} + +type execution struct { + h *Handler + w http.ResponseWriter + r *http.Request + raw []byte + body *ojson.Value + requestID int64 + edgeToken string + startedAt time.Time + + mu sync.Mutex + bytesForwarded int64 +} + +// attemptResult is what a single attempt produced before commit. +type attemptResult struct { + committed bool + // event is set when the attempt failed (or needs control-plane review) before commit. + event *contract.NextEvent + // verdict is set when the control plane already answered for this attempt + // (suspect 2xx review) and the outer loop must act on it. + verdict *contract.NextResponse +} + +func (e *execution) run(first *contract.ExecutionStep) { + h := e.h + h.metrics.inflight.Add(1) + defer h.metrics.inflight.Add(-1) + + heartbeatCtx, stopHeartbeat := context.WithCancel(h.baseCtx) + defer stopHeartbeat() + go e.heartbeatLoop(heartbeatCtx, first.Reporting.HeartbeatIntervalMs) + + if first.Hedge != nil { + e.runHedge(first) + return + } + + step := first + for step != nil { + if step.DelayMs > 0 && !sleepCtx(e.r.Context(), time.Duration(step.DelayMs)*time.Millisecond) { + e.reportClientAbort(step) + return + } + h.metrics.attempts.Add("started", 1) + result := e.attempt(step) + if result.committed { + return + } + next := result.verdict + if next == nil { + if result.event == nil { + return + } + var err error + next, err = e.next(step, result.event) + if err != nil { + h.logger.Warn("next failed", "requestId", e.requestID, "stepId", step.StepID, "error", err) + if e.r.Context().Err() == nil { + delegate.WriteError(e.w, http.StatusBadGateway, "edge control plane unavailable", "bad_gateway_error") + } + return + } + } + switch next.Action { + case "retry", "launch": + step = next.Step + case "fail": + if e.r.Context().Err() == nil { + writeFailResponse(e.w, next.Response) + } + return + case "delegate": + h.delegator.Serve(e.w, e.r, e.raw, "control_plane_delegate") + return + default: + h.logger.Warn("unexpected next action", "action", next.Action) + delegate.WriteError(e.w, http.StatusBadGateway, "invalid control plane response", "bad_gateway_error") + return + } + } +} + +func (e *execution) next(step *contract.ExecutionStep, event *contract.NextEvent) (*contract.NextResponse, error) { + // The client may already be gone; the control plane must still settle the request. + ctx := context.WithoutCancel(e.r.Context()) + return e.h.control.Next(ctx, &contract.NextRequest{ + RequestID: e.requestID, + EdgeToken: e.edgeToken, + StepID: step.StepID, + Event: *event, + }) +} + +func (e *execution) heartbeatLoop(ctx context.Context, intervalMs int64) { + if intervalMs <= 0 { + intervalMs = 15_000 + } + ticker := time.NewTicker(time.Duration(intervalMs) * time.Millisecond) + defer ticker.Stop() + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + e.mu.Lock() + forwarded := e.bytesForwarded + e.mu.Unlock() + _ = e.h.reporter.Heartbeat(ctx, &contract.HeartbeatRequest{ + RequestID: e.requestID, + EdgeToken: e.edgeToken, + BytesForwarded: forwarded, + }) + } + } +} + +func (e *execution) reportClientAbort(step *contract.ExecutionStep) { + now := time.Now().UnixMilli() + _, _ = e.next(step, &contract.NextEvent{ + Type: "failure", + Failure: &contract.AttemptFailure{Kind: "client_abort"}, + Timing: &contract.AttemptTiming{EndedAtMs: now}, + }) +} + +// attemptTimers tracks dispatch and first-byte instants for health attribution. +type attemptTimers struct { + mu sync.Mutex + dispatchedAt time.Time + firstByteAt time.Time +} + +func (t *attemptTimers) markDispatched() { + t.mu.Lock() + if t.dispatchedAt.IsZero() { + t.dispatchedAt = time.Now() + } + t.mu.Unlock() +} + +func (t *attemptTimers) markFirstByte() { + t.mu.Lock() + if t.firstByteAt.IsZero() { + t.firstByteAt = time.Now() + } + t.mu.Unlock() +} + +func (t *attemptTimers) snapshot() (dispatched, firstByte time.Time) { + t.mu.Lock() + defer t.mu.Unlock() + return t.dispatchedAt, t.firstByteAt +} + +func msPtr(at time.Time) *int64 { + if at.IsZero() { + return nil + } + value := at.UnixMilli() + return &value +} + +func (t *attemptTimers) timing() *contract.AttemptTiming { + dispatched, firstByte := t.snapshot() + now := time.Now() + elapsed := int64(0) + if !dispatched.IsZero() { + elapsed = now.Sub(dispatched).Milliseconds() + } + return &contract.AttemptTiming{ + DispatchedAtMs: msPtr(dispatched), + FirstByteAtMs: msPtr(firstByte), + EndedAtMs: now.UnixMilli(), + HealthElapsedMs: elapsed, + } +} + +func (e *execution) failureEvent(failure contract.AttemptFailure, timers *attemptTimers, opResults *contract.OpResults) *contract.NextEvent { + dispatched, firstByte := timers.snapshot() + e.h.metrics.attempts.Add("failed_"+failure.Kind, 1) + return &contract.NextEvent{ + Type: "failure", + Failure: &failure, + Dispatched: !dispatched.IsZero(), + FirstByteSeen: !firstByte.IsZero(), + Timing: timers.timing(), + OpResults: opResults, + } +} + +// classifyAttemptError maps an attempt error to a failure, honoring which of our +// own timers (or the client) cancelled the attempt. +func (e *execution) classifyAttemptError(ctx context.Context, err error, gateActive bool) contract.AttemptFailure { + if e.r.Context().Err() != nil || errors.Is(context.Cause(ctx), errClientAborted) { + return contract.AttemptFailure{Kind: "client_abort"} + } + switch cause := context.Cause(ctx); { + case errors.Is(cause, errFirstByteTimeout): + if gateActive { + return contract.AttemptFailure{Kind: "timeout", TimeoutType: "streaming_first_valid_content"} + } + return contract.AttemptFailure{Kind: "timeout", TimeoutType: "streaming_first_byte"} + case errors.Is(cause, errTotalTimeout): + return contract.AttemptFailure{Kind: "timeout", TimeoutType: "non_streaming_total"} + } + code, message := upstream.ClassifyError(err) + e.h.metrics.upstreamErrors.Add(code, 1) + return contract.AttemptFailure{Kind: "transport", Code: code, Message: message} +} + +func hasSignatureRectifier(ops []contract.BodyOp) bool { + for _, op := range ops { + if op.Op == contract.OpApplyThinkingSignatureRectifier { + return true + } + } + return false +} + +func nonEmptyOpResults(results contract.OpResults) *contract.OpResults { + if results.BillingHeader == nil && results.ThinkingSignature == nil { + return nil + } + return &results +} + +// attemptRun owns the per-attempt context and timers of one upstream attempt. +type attemptRun struct { + step *contract.ExecutionStep + timers *attemptTimers + ctx context.Context + cancel context.CancelCauseFunc + committed atomic.Bool + opResults *contract.OpResults + + firstByteTimer *time.Timer + cleanups []func() +} + +// newAttemptRun detaches the attempt from the client context: the upstream request +// must survive a client disconnect after commit (usage is still drained for +// billing). With watchClient the client cancels the attempt only before commit; +// hedge attempts leave client handling to the hedge loop. +func (e *execution) newAttemptRun(step *contract.ExecutionStep, watchClient bool) *attemptRun { + ctx, cancel := context.WithCancelCause(context.WithoutCancel(e.r.Context())) + run := &attemptRun{step: step, timers: &attemptTimers{}, ctx: ctx, cancel: cancel} + if watchClient { + stop := context.AfterFunc(e.r.Context(), func() { + if !run.committed.Load() { + cancel(errClientAborted) + } + }) + run.cleanups = append(run.cleanups, func() { stop() }) + } + if step.IsStreaming && step.Timeouts.FirstByteMs > 0 { + run.firstByteTimer = time.AfterFunc(time.Duration(step.Timeouts.FirstByteMs)*time.Millisecond, func() { + cancel(errFirstByteTimeout) + }) + } + if !step.IsStreaming && step.Timeouts.NonStreamTotalMs > 0 { + totalTimer := time.AfterFunc(time.Duration(step.Timeouts.NonStreamTotalMs)*time.Millisecond, func() { + cancel(errTotalTimeout) + }) + run.cleanups = append(run.cleanups, func() { totalTimer.Stop() }) + } + return run +} + +func (run *attemptRun) stopFirstByteTimer() { + if run.firstByteTimer != nil { + run.firstByteTimer.Stop() + } +} + +func (run *attemptRun) markCommitted() { run.committed.Store(true) } + +func (run *attemptRun) close() { + run.stopFirstByteTimer() + for _, cleanup := range run.cleanups { + cleanup() + } + run.cancel(nil) +} + +// attempt runs one serial attempt to its end (commit or pre-commit failure). +func (e *execution) attempt(step *contract.ExecutionStep) attemptResult { + run := e.newAttemptRun(step, true) + defer run.close() + prepared, result := e.prepare(run, false) + if prepared == nil { + return result + } + return e.commitStream(run, prepared, nil) +} + +// prepare runs an attempt up to (but excluding) the commit point. It returns a +// prepared stream ready to commit, or the attempt result when the attempt failed +// before commit (or, for serial non-stream responses, already committed). In +// hedge mode every 2xx response takes the stream path, like the local hedge +// runner which reads the first chunk / runs the gate regardless of content type. +func (e *execution) prepare(run *attemptRun, hedge bool) (*preparedStream, attemptResult) { + step := run.step + timers := run.timers + + transformed, opResultsValue, err := bodyops.Apply(e.body, step.BodyOps) + if err != nil { + return nil, attemptResult{event: e.failureEvent(contract.AttemptFailure{Kind: "invalid_step", Message: err.Error()}, timers, nil)} + } + opResults := nonEmptyOpResults(opResultsValue) + run.opResults = opResults + if hasSignatureRectifier(step.BodyOps) && opResultsValue.ThinkingSignature != nil && + !opResultsValue.ThinkingSignature.Applied { + return nil, attemptResult{event: &contract.NextEvent{Type: "rectifier_not_applicable", OpResults: opResults}} + } + requestBody := transformed.Marshal() + + result, err := e.h.upstream.Do(run.ctx, step, requestBody, timers.markDispatched) + if err != nil { + return nil, attemptResult{event: e.failureEvent(e.classifyAttemptError(run.ctx, err, false), timers, opResults)} + } + resp := result.Response + release := true + defer func() { + // Owned by the prepared stream / committed response; otherwise release it here. + if release && resp.Body != nil { + _ = resp.Body.Close() + } + }() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + bodyText, truncated := readErrorBody(resp.Body, step.Reporting.MaxErrorBodyBytes) + timers.markFirstByte() + return nil, attemptResult{event: e.failureEvent(contract.AttemptFailure{ + Kind: "upstream_status", + Status: resp.StatusCode, + StatusText: http.StatusText(resp.StatusCode), + Headers: headerPairs(resp.Header), + BodyText: bodyText, + BodyTruncated: truncated, + }, timers, opResults)} + } + + forced := forceStreamHandling(step, resp) + if hedge || forced || isEventStream(resp) { + prepared, failed := e.prepareStream(run, resp, forced) + if prepared != nil { + release = false + } + return prepared, failed + } + return nil, e.handleNonStream(run.ctx, step, resp, timers, run.markCommitted, opResults) +} + +func isEventStream(resp *http.Response) bool { + return strings.Contains(strings.ToLower(resp.Header.Get("Content-Type")), "text/event-stream") +} + +// forceStreamHandling mirrors shouldForceCodexResponsesStreamHandling: a codex +// streaming request whose 2xx body is neither SSE, HTML nor JSON is still +// relayed as a stream. +func forceStreamHandling(step *contract.ExecutionStep, resp *http.Response) bool { + if !step.ForceStreamHandling || resp.Body == nil || resp.StatusCode < 200 || resp.StatusCode >= 300 { + return false + } + contentType := resp.Header.Get("Content-Type") + mediaType := strings.ToLower(strings.TrimSpace(strings.SplitN(contentType, ";", 2)[0])) + switch mediaType { + case "text/event-stream", "text/html", "application/xhtml+xml": + return false + } + return !isJSONMediaType(mediaType) +} + +// isJSONMediaType mirrors isJsonResponseContentType. +func isJSONMediaType(mediaType string) bool { + return mediaType == "application/json" || strings.HasSuffix(mediaType, "+json") +} + +func readErrorBody(body io.Reader, limit int) (string, bool) { + if limit <= 0 { + limit = 64 << 10 + } + data, _ := io.ReadAll(io.LimitReader(body, int64(limit)+1)) + truncated := len(data) > limit + if truncated { + data = data[:limit] + } + return strings.ToValidUTF8(string(data), "�"), truncated +} + +func headerPairs(header http.Header) []contract.HeaderPair { + pairs := make([]contract.HeaderPair, 0, len(header)) + for name, values := range header { + lower := strings.ToLower(name) + for _, value := range values { + pairs = append(pairs, contract.HeaderPair{lower, value}) + } + } + return pairs +} + +func (e *execution) writeResponseHeaders(resp *http.Response) { + header := e.w.Header() + for name, values := range resp.Header { + if skippedResponseHeaders[strings.ToLower(name)] { + continue + } + for _, value := range values { + header.Add(name, value) + } + } + e.w.WriteHeader(resp.StatusCode) +} + +func (e *execution) handleNonStream(ctx context.Context, step *contract.ExecutionStep, resp *http.Response, timers *attemptTimers, markCommitted func(), opResults *contract.OpResults) attemptResult { + if resp.ContentLength == 0 { + return attemptResult{event: e.failureEvent(contract.AttemptFailure{Kind: "empty_response", Reason: "empty_body"}, timers, opResults)} + } + data, err := io.ReadAll(io.LimitReader(resp.Body, e.h.cfg.MaxBodyBytes+1)) + if err != nil { + return attemptResult{event: e.failureEvent(e.classifyAttemptError(ctx, err, false), timers, opResults)} + } + if len(data) > 0 { + timers.markFirstByte() + } + if reason := detect.EmptyReason(data); reason != "" && resp.ContentLength < 0 { + return attemptResult{event: e.failureEvent(contract.AttemptFailure{Kind: "empty_response", Reason: reason}, timers, opResults)} + } + if detect.SuspectNonStream(data, resp.Header.Get("Content-Type")) { + limit := step.Reporting.MaxErrorBodyBytes + text := string(data) + truncated := false + if limit > 0 && len(text) > limit { + text = text[:limit] + truncated = true + } + verdict, err := e.next(step, &contract.NextEvent{ + Type: "suspect_2xx", + Status: resp.StatusCode, + Headers: headerPairs(resp.Header), + BodyText: strings.ToValidUTF8(text, "�"), + BodyTruncated: truncated, + OpResults: opResults, + }) + if err != nil { + e.h.logger.Warn("suspect review failed", "requestId", e.requestID, "error", err) + if e.r.Context().Err() == nil { + delegate.WriteError(e.w, http.StatusBadGateway, "edge control plane unavailable", "bad_gateway_error") + } + return attemptResult{committed: true} + } + if verdict.Action != "commit" { + return attemptResult{verdict: verdict} + } + } + + var fixerAudit *contract.FixerAudit + clientBody := data + if step.Fixer.Enabled { + fixed, audit := fixer.FixNonStream(data, fixerConfig(step)) + clientBody = fixed + if audit.Hit { + fixerAudit = &audit + } + // ResponseFixer.processNonStream ends with normalizeResponseOutput (Responses only). + if clientFormat(step) == contract.FormatResponse && + isJSONMediaType(strings.ToLower(strings.TrimSpace(strings.SplitN(resp.Header.Get("Content-Type"), ";", 2)[0]))) { + if normalized, changed := fixer.NormalizeResponseOutput(clientBody); changed { + clientBody = normalized + } + } + } + + markCommitted() + e.writeResponseHeaders(resp) + written, _ := e.w.Write(clientBody) + e.addForwarded(int64(written)) + + dispatched, firstByte := timers.snapshot() + // The local response handler settles on the fixed body it relays to the client. + storedLimit := step.Reporting.MaxNonStreamBodyBytes + storedText := string(clientBody) + storedTruncated := false + if storedLimit > 0 && len(storedText) > storedLimit { + storedText = storedText[:storedLimit] + storedTruncated = true + } + e.h.reporter.Complete(e.h.baseCtx, &contract.CompleteRequest{ + RequestID: e.requestID, + EdgeToken: e.edgeToken, + Winner: contract.WinnerResult{ + StepID: step.StepID, + UpstreamStatus: resp.StatusCode, + ResponseHeaders: headerPairs(resp.Header), + IsStreaming: false, + StreamEndedNormally: true, + FirstByteSeen: !firstByte.IsZero(), + NonStreamBody: &contract.NonStreamBody{Text: strings.ToValidUTF8(storedText, "�"), Truncated: storedTruncated}, + Fixer: fixerAudit, + Timing: contract.WinnerTiming{ + DispatchedAtMs: dispatched.UnixMilli(), + FirstByteAtMs: msPtr(firstByte), + EndedAtMs: time.Now().UnixMilli(), + HealthElapsedMs: time.Since(dispatched).Milliseconds(), + }, + BytesToClient: int64(written), + OpResults: opResults, + }, + Losers: []contract.LoserResult{}, + }) + e.h.metrics.attempts.Add("committed_non_stream", 1) + return attemptResult{committed: true} +} + +func fixerConfig(step *contract.ExecutionStep) fixer.Config { + return fixer.Config{ + FixTruncatedJSON: step.Fixer.FixTruncatedJSON, + FixSseFormat: step.Fixer.FixSseFormat, + FixEncoding: step.Fixer.FixEncoding, + MaxJSONDepth: step.Fixer.MaxJSONDepth, + MaxFixSize: step.Fixer.MaxFixSize, + Format: clientFormat(step), + } +} + +func (e *execution) addForwarded(bytes int64) { + e.mu.Lock() + e.bytesForwarded += bytes + e.mu.Unlock() + e.h.metrics.bytesToClient.Add(bytes) +} + +func sleepCtx(ctx context.Context, delay time.Duration) bool { + timer := time.NewTimer(delay) + defer timer.Stop() + select { + case <-ctx.Done(): + return false + case <-timer.C: + return true + } +} diff --git a/edge/internal/server/formats_test.go b/edge/internal/server/formats_test.go new file mode 100644 index 0000000000..c948bdeb8b --- /dev/null +++ b/edge/internal/server/formats_test.go @@ -0,0 +1,133 @@ +package server + +import ( + "io" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/ding113/claude-code-hub/edge/internal/contract" +) + +const responsesStream = "event: response.created\ndata: {\"type\":\"response.created\",\"response\":{\"id\":\"resp_1\",\"object\":\"response\",\"model\":\"gpt-5-codex\",\"status\":\"in_progress\",\"usage\":null}}\n\n" + + "event: response.output_text.delta\ndata: {\"type\":\"response.output_text.delta\",\"item_id\":\"m1\",\"delta\":\"hello\"}\n\n" + + "event: response.completed\ndata: {\"type\":\"response.completed\",\"response\":{\"id\":\"resp_1\",\"object\":\"response\",\"model\":\"gpt-5-codex\",\"status\":\"completed\",\"prompt_cache_key\":\"pck\",\"usage\":{\"input_tokens\":10,\"output_tokens\":4,\"total_tokens\":14}}}\n\n" + +const chatStream = "data: {\"id\":\"c1\",\"object\":\"chat.completion.chunk\",\"model\":\"gpt-4.1\",\"choices\":[{\"index\":0,\"delta\":{\"role\":\"assistant\",\"content\":\"\"}}]}\n\n" + + "data: {\"id\":\"c1\",\"object\":\"chat.completion.chunk\",\"model\":\"gpt-4.1\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"hi\"}}]}\n\n" + + "data: {\"id\":\"c1\",\"object\":\"chat.completion.chunk\",\"model\":\"gpt-4.1\",\"choices\":[{\"index\":0,\"delta\":{},\"finish_reason\":\"stop\"}]}\n\n" + + "data: {\"id\":\"c1\",\"object\":\"chat.completion.chunk\",\"model\":\"gpt-4.1\",\"choices\":[],\"usage\":{\"prompt_tokens\":5,\"completion_tokens\":2,\"total_tokens\":7}}\n\n" + + "data: [DONE]\n\n" + +func streamingUpstream(t *testing.T, contentType, stream string, gotBody *string) *httptest.Server { + t.Helper() + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + data, _ := io.ReadAll(r.Body) + if gotBody != nil { + *gotBody = string(data) + } + w.Header().Set("Content-Type", contentType) + flusher := w.(http.Flusher) + for _, frame := range strings.SplitAfter(stream, "\n\n") { + _, _ = w.Write([]byte(frame)) + flusher.Flush() + } + })) + t.Cleanup(srv.Close) + return srv +} + +func TestResponsesForcedStreamIsRelayedAsSSE(t *testing.T) { + var upstreamBody string + upstreamSrv := streamingUpstream(t, "application/octet-stream", responsesStream, &upstreamBody) + step := baseStep(upstreamSrv.URL+"/v1/responses", true) + step.Provider.Type = "codex" + step.ClientFormat = contract.FormatResponse + step.ForceStreamHandling = true + step.BodyOps = []contract.BodyOp{{Op: contract.OpNormalizeResponseInput}} + + control := &fakeControl{decide: executeDecision(step)} + h := newHarness(t, control) + resp := h.post(t, "/responses", `{"model":"gpt-5-codex","stream":true,"input":"hi"}`, nil) + defer resp.Body.Close() + body, _ := io.ReadAll(resp.Body) + if resp.StatusCode != 200 || string(body) != responsesStream { + t.Fatalf("status=%d body=%q", resp.StatusCode, body) + } + if resp.Header.Get("Content-Type") != "text/event-stream; charset=utf-8" { + t.Fatalf("content type %q", resp.Header.Get("Content-Type")) + } + if upstreamBody != `{"model":"gpt-5-codex","stream":true,"input":[{"role":"user","content":[{"type":"input_text","text":"hi"}]}]}` { + t.Fatalf("input not normalized: %s", upstreamBody) + } + digest := control.digests[0] + if digest.Format != contract.FormatResponse || digest.InputCount == nil || *digest.InputCount != 1 { + t.Fatalf("unexpected digest format=%s input=%v", digest.Format, digest.InputCount) + } + + winner := h.reporter.waitComplete(t).Winner + if !strings.Contains(winner.CompactSSE, "response.completed") || strings.Contains(winner.CompactSSE, "output_text.delta") { + t.Fatalf("unexpected compact sse %q", winner.CompactSSE) + } + if winner.Protocol == nil || !winner.Protocol.SawTerminal || winner.Fixer != nil { + t.Fatalf("unexpected observation %+v fixer %+v", winner.Protocol, winner.Fixer) + } +} + +func TestChatCompletionsStreamCompactKeepsUsageAndDone(t *testing.T) { + upstreamSrv := streamingUpstream(t, "text/event-stream", chatStream, nil) + step := baseStep(upstreamSrv.URL+"/v1/chat/completions", true) + step.Provider.Type = "openai-compatible" + step.ClientFormat = contract.FormatOpenAI + step.BodyOps = []contract.BodyOp{{Op: contract.OpSetTopLevel, Key: "stream_options", Value: []byte(`{"include_usage":true}`)}} + + control := &fakeControl{decide: executeDecision(step)} + h := newHarness(t, control) + resp := h.post(t, "/v1/chat/completions", `{"model":"gpt-4.1","stream":true,"messages":[{"role":"user","content":"hi"}]}`, nil) + defer resp.Body.Close() + body, _ := io.ReadAll(resp.Body) + if resp.StatusCode != 200 || string(body) != chatStream { + t.Fatalf("status=%d body=%q", resp.StatusCode, body) + } + if digest := control.digests[0]; digest.Format != contract.FormatOpenAI { + t.Fatalf("digest format %s", digest.Format) + } + winner := h.reporter.waitComplete(t).Winner + for _, want := range []string{`"completion_tokens":2`, "[DONE]", `"finish_reason":"stop"`} { + if !strings.Contains(winner.CompactSSE, want) { + t.Fatalf("compact sse missing %s: %q", want, winner.CompactSSE) + } + } + if strings.Contains(winner.CompactSSE, "event:") { + t.Fatalf("chat compact must not synthesize event lines: %q", winner.CompactSSE) + } + if winner.Protocol == nil || !winner.Protocol.SawTerminal { + t.Fatalf("protocol %+v", winner.Protocol) + } +} + +func TestResponsesNonStreamOutputIsNormalized(t *testing.T) { + upstreamSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(`{"id":"resp_1","object":"response","output":null,"usage":{"input_tokens":3,"output_tokens":1}}`)) + })) + defer upstreamSrv.Close() + step := baseStep(upstreamSrv.URL+"/v1/responses", false) + step.Provider.Type = "codex" + step.ClientFormat = contract.FormatResponse + step.BodyOps = nil + + h := newHarness(t, &fakeControl{decide: executeDecision(step)}) + resp := h.post(t, "/v1/responses", `{"model":"gpt-5-codex","input":[]}`, nil) + defer resp.Body.Close() + body, _ := io.ReadAll(resp.Body) + want := `{"id":"resp_1","object":"response","output":[],"usage":{"input_tokens":3,"output_tokens":1}}` + if string(body) != want { + t.Fatalf("client body %s", body) + } + winner := h.reporter.waitComplete(t).Winner + if winner.NonStreamBody == nil || winner.NonStreamBody.Text != want { + t.Fatalf("reported body %+v", winner.NonStreamBody) + } +} diff --git a/edge/internal/server/handler.go b/edge/internal/server/handler.go new file mode 100644 index 0000000000..025a21cc51 --- /dev/null +++ b/edge/internal/server/handler.go @@ -0,0 +1,320 @@ +// Package server implements the edge executor HTTP front end. +// +// Request flow (see docs in src/app/v1/_lib/edge/contract.ts): +// +// static route -> not POST /v1/messages | /v1/responses | /v1/chat/completions: +// reverse proxy to the control plane +// read + decode body -> digest -> POST /decide +// delegate -> reverse proxy the original bytes +// fail -> relay the prepared error response +// execute -> run ExecutionSteps until one commits: +// serial: pre-commit failures -> POST /next (retry | fail | delegate) +// hedge: attempts race in parallel; first-byte thresholds and +// failures -> POST /next (launch | retry | wait | none | fail), +// the first attempt ready commits, losers are cancelled or +// drained for billing (see hedge.go) +// commit -> stream to the client, heartbeat, POST /complete +// +// Invariants: no byte reaches the client before commit; /next is only called +// before commit; /complete is sent at most once per request. +package server + +import ( + "context" + "errors" + "io" + "log/slog" + "net" + "net/http" + "strings" + "sync/atomic" + "time" + + "github.com/ding113/claude-code-hub/edge/internal/codec" + "github.com/ding113/claude-code-hub/edge/internal/config" + "github.com/ding113/claude-code-hub/edge/internal/contract" + "github.com/ding113/claude-code-hub/edge/internal/delegate" + "github.com/ding113/claude-code-hub/edge/internal/digest" + "github.com/ding113/claude-code-hub/edge/internal/gate" + "github.com/ding113/claude-code-hub/edge/internal/ojson" + "github.com/ding113/claude-code-hub/edge/internal/route" + "github.com/ding113/claude-code-hub/edge/internal/upstream" +) + +// Controller abstracts the control plane client (replaceable in tests). +type Controller interface { + Decide(ctx context.Context, digest *contract.RequestDigest) (*contract.DecideResponse, error) + Next(ctx context.Context, request *contract.NextRequest) (*contract.NextResponse, error) +} + +// Reporter abstracts completion/heartbeat delivery (replaceable in tests). +type Reporter interface { + Complete(ctx context.Context, request *contract.CompleteRequest) + Heartbeat(ctx context.Context, request *contract.HeartbeatRequest) error +} + +type Handler struct { + cfg *config.Config + control Controller + reporter Reporter + delegator *delegate.Delegator + upstream *upstream.Client + budget *gate.Budget + logger *slog.Logger + sem chan struct{} + requestSeq atomic.Uint64 + metrics *Metrics + // baseCtx outlives individual requests (used for background reporting). + baseCtx context.Context +} + +type Deps struct { + Config *config.Config + Control Controller + Reporter Reporter + Delegator *delegate.Delegator + Upstream *upstream.Client + Budget *gate.Budget + Logger *slog.Logger + Metrics *Metrics + BaseCtx context.Context +} + +func NewHandler(deps Deps) *Handler { + metrics := deps.Metrics + if metrics == nil { + metrics = NewMetrics() + } + baseCtx := deps.BaseCtx + if baseCtx == nil { + baseCtx = context.Background() + } + return &Handler{ + cfg: deps.Config, + control: deps.Control, + reporter: deps.Reporter, + delegator: deps.Delegator, + upstream: deps.Upstream, + budget: deps.Budget, + logger: deps.Logger, + sem: make(chan struct{}, deps.Config.MaxConcurrency), + metrics: metrics, + baseCtx: baseCtx, + } +} + +// ServeHTTP is the entry point for client traffic. +func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + stripInternalHeaders(r.Header) + + if route.Decide(r.Method, r.URL.RequestURI()) != route.Execute { + h.metrics.requests.Add("static_delegate", 1) + h.delegator.Serve(w, r, nil, "static_route") + return + } + + select { + case h.sem <- struct{}{}: + defer func() { <-h.sem }() + default: + h.metrics.requests.Add("overflow", 1) + if h.cfg.OverflowToDelegate { + h.delegator.Serve(w, r, nil, "edge_overloaded") + return + } + delegate.WriteError(w, http.StatusServiceUnavailable, "edge executor overloaded", "service_unavailable_error") + return + } + + receivedAt := time.Now() + raw, err := readBody(r.Body, h.cfg.MaxCompressedBodyBytes) + if err != nil { + if errors.Is(err, errBodyTooLarge) { + delegate.WriteError(w, http.StatusRequestEntityTooLarge, "Request body exceeds the maximum allowed size.", "invalid_request_error") + return + } + h.logger.Debug("failed to read request body", "error", err) + return + } + + decoded, err := codec.Decode(raw, r.Header.Get("Content-Encoding"), h.cfg.MaxBodyBytes, h.cfg.MaxCompressedBodyBytes) + if err != nil { + h.metrics.requests.Add("delegate_codec", 1) + h.delegator.Serve(w, r, raw, "body_encoding") + return + } + + body, parseErr := ojson.Parse(decoded.Body, h.cfg.MaxJSONDepth) + if parseErr != nil || body == nil || !body.IsObject() { + h.metrics.requests.Add("delegate_parse", 1) + h.delegator.Serve(w, r, raw, "body_parse") + return + } + + digestHeaders := h.digestHeaders(r, decoded.Decoded) + requestDigest := digest.Build(digest.Input{ + EdgeID: h.cfg.EdgeID, + EdgeRequestID: h.nextRequestID(), + ReceivedAtMs: receivedAt.UnixMilli(), + Method: r.Method, + Path: route.CanonicalRequestPath(r.URL.RequestURI()), + Headers: digestHeaders, + ClientIP: h.clientIPPtr(r), + Body: body, + BodyBytes: len(decoded.Body), + }) + + decideStarted := time.Now() + decision, err := h.control.Decide(r.Context(), &requestDigest) + h.metrics.observeDecide(time.Since(decideStarted), err) + if err != nil { + h.logger.Warn("decide failed", "error", err) + h.metrics.requests.Add("control_unreachable", 1) + if h.cfg.FallbackToDelegate { + h.delegator.Serve(w, r, raw, "control_unreachable") + return + } + delegate.WriteError(w, http.StatusServiceUnavailable, "edge control plane unavailable", "service_unavailable_error") + return + } + + switch decision.Action { + case "delegate": + h.metrics.requests.Add("delegate", 1) + h.delegator.Serve(w, r, raw, decision.Reason) + case "fail": + h.metrics.requests.Add("fail", 1) + writeFailResponse(w, decision.Response) + case "execute": + if decision.Step == nil { + h.delegator.Serve(w, r, raw, "invalid_decide_response") + return + } + h.metrics.requests.Add("execute", 1) + exec := &execution{ + h: h, + w: w, + r: r, + raw: raw, + body: body, + requestID: decision.RequestID, + edgeToken: decision.EdgeToken, + startedAt: receivedAt, + } + exec.run(decision.Step) + default: + h.delegator.Serve(w, r, raw, "unknown_decide_action") + } +} + +func (h *Handler) nextRequestID() string { + return h.cfg.EdgeID + "-" + uint64String(uint64(time.Now().UnixNano())) + "-" + uint64String(h.requestSeq.Add(1)) +} + +// digestHeaders returns the client headers as the control plane should see them. +// Forwarding headers are replaced with the client IP observed here (the edge is +// the first hop unless EDGE_TRUSTED_PROXY_CIDRS says otherwise), and +// content-encoding is dropped when the edge already decoded the body. +func (h *Handler) digestHeaders(r *http.Request, decoded bool) []contract.HeaderPair { + trusted := h.isTrustedPeer(r) + pairs := make([]contract.HeaderPair, 0, len(r.Header)+2) + for name, values := range r.Header { + lower := strings.ToLower(name) + if !trusted && (lower == "x-forwarded-for" || lower == "x-real-ip" || lower == "forwarded") { + continue + } + if decoded && lower == "content-encoding" { + continue + } + for _, value := range values { + pairs = append(pairs, contract.HeaderPair{lower, value}) + } + } + if !trusted { + if ip := peerIP(r); ip != "" { + pairs = append(pairs, contract.HeaderPair{"x-forwarded-for", ip}, contract.HeaderPair{"x-real-ip", ip}) + } + } + return pairs +} + +func (h *Handler) isTrustedPeer(r *http.Request) bool { + if len(h.cfg.TrustedProxyCIDRs) == 0 { + return false + } + ip := net.ParseIP(peerIP(r)) + if ip == nil { + return false + } + for _, cidr := range h.cfg.TrustedProxyCIDRs { + if _, network, err := net.ParseCIDR(cidr); err == nil && network.Contains(ip) { + return true + } + } + return false +} + +func (h *Handler) clientIPPtr(r *http.Request) *string { + ip := peerIP(r) + if ip == "" { + return nil + } + return &ip +} + +func peerIP(r *http.Request) string { + host, _, err := net.SplitHostPort(r.RemoteAddr) + if err != nil { + return r.RemoteAddr + } + return host +} + +func stripInternalHeaders(header http.Header) { + for name := range header { + if strings.HasPrefix(strings.ToLower(name), "x-cch-") { + header.Del(name) + } + } +} + +var errBodyTooLarge = errors.New("request body too large") + +func readBody(body io.ReadCloser, limit int64) ([]byte, error) { + defer body.Close() + data, err := io.ReadAll(io.LimitReader(body, limit+1)) + if err != nil { + return nil, err + } + if int64(len(data)) > limit { + return nil, errBodyTooLarge + } + return data, nil +} + +func writeFailResponse(w http.ResponseWriter, response *contract.FailResponse) { + if response == nil { + delegate.WriteError(w, http.StatusBadGateway, "invalid control plane response", "bad_gateway_error") + return + } + header := w.Header() + for _, pair := range response.Headers { + header.Add(pair[0], pair[1]) + } + w.WriteHeader(response.Status) + _, _ = io.WriteString(w, response.BodyText) +} + +func uint64String(value uint64) string { + if value == 0 { + return "0" + } + var buf [20]byte + index := len(buf) + for value > 0 { + index-- + buf[index] = byte('0' + value%10) + value /= 10 + } + return string(buf[index:]) +} diff --git a/edge/internal/server/hedge.go b/edge/internal/server/hedge.go new file mode 100644 index 0000000000..700e061bf5 --- /dev/null +++ b/edge/internal/server/hedge.go @@ -0,0 +1,449 @@ +package server + +import ( + "context" + "errors" + "io" + "net/http" + "sync" + "time" + + "github.com/ding113/claude-code-hub/edge/internal/capture" + "github.com/ding113/claude-code-hub/edge/internal/contract" + "github.com/ding113/claude-code-hub/edge/internal/delegate" +) + +// Legacy hedge execution (TS: ProxyForwarder.sendStreamingWithHedge, driven step by +// step by src/app/v1/_lib/edge/hedge-coordinator.ts). +// +// Every attempt runs its pre-commit phase on its own goroutine. The hedge loop turns +// first-byte thresholds and pre-commit failures into /next calls, starts the steps the +// control plane launches, and commits the first attempt that becomes ready. Losers are +// cancelled, or drained for billing evidence when the step asks for it, and reported +// with the winner's completion. After the commit no /next call is made. + +var errHedgeLoser = errors.New("hedge loser") + +// loserBillingMaxFrameBytes mirrors LOSER_BILLING_MAX_FRAME_BYTES. +const loserBillingMaxFrameBytes = 1 << 20 + +type hedgeRole int + +const ( + roleRacing hedgeRole = iota + roleWinner + roleLoserDrain + roleCancelled +) + +type hedgeAttempt struct { + run *attemptRun + + mu sync.Mutex + role hedgeRole + finished bool + prepared *preparedStream + status int + // loserResult receives exactly one result once the attempt is a loser. + loserResult chan contract.LoserResult + threshold *time.Timer +} + +func (a *hedgeAttempt) step() *contract.ExecutionStep { return a.run.step } + +type hedgeEventKind int + +const ( + hedgeReady hedgeEventKind = iota + hedgeFailed + hedgeThreshold + hedgeNextResponse +) + +type hedgeEvent struct { + kind hedgeEventKind + attempt *hedgeAttempt + event *contract.NextEvent + // hedgeNextResponse + failureOrigin bool + response *contract.NextResponse + err error +} + +type hedgeLoop struct { + e *execution + events chan hedgeEvent + done chan struct{} + attempts []*hedgeAttempt + racing map[*hedgeAttempt]bool + ready []*hedgeAttempt + // pendingFailures counts failure /next calls in flight. A ready attempt is not + // committed while one is pending: the control plane may be settling the request. + pendingFailures int +} + +func (e *execution) runHedge(first *contract.ExecutionStep) { + loop := &hedgeLoop{ + e: e, + events: make(chan hedgeEvent, 64), + done: make(chan struct{}), + racing: map[*hedgeAttempt]bool{}, + } + defer close(loop.done) + loop.start(first) + + clientDone := e.r.Context().Done() + for { + if loop.pendingFailures == 0 && len(loop.ready) > 0 { + loop.commit(loop.ready[0]) + return + } + if len(loop.racing) == 0 && len(loop.ready) == 0 && loop.pendingFailures == 0 { + // The control plane answered "wait" with nothing left in flight here. + e.h.logger.Warn("hedge has no attempt in flight", "requestId", e.requestID) + delegate.WriteError(e.w, http.StatusBadGateway, "edge hedge state mismatch", "bad_gateway_error") + return + } + + select { + case ev := <-loop.events: + if loop.handle(ev) { + return + } + case <-clientDone: + loop.clientAbort() + return + } + } +} + +func (l *hedgeLoop) send(ev hedgeEvent) { + select { + case l.events <- ev: + case <-l.done: + } +} + +func (l *hedgeLoop) start(step *contract.ExecutionStep) { + e := l.e + attempt := &hedgeAttempt{ + run: e.newAttemptRun(step, false), + loserResult: make(chan contract.LoserResult, 1), + } + l.attempts = append(l.attempts, attempt) + l.racing[attempt] = true + e.h.metrics.attempts.Add("started", 1) + + if step.Hedge != nil && step.Hedge.ThresholdMs > 0 { + attempt.threshold = time.AfterFunc(time.Duration(step.Hedge.ThresholdMs)*time.Millisecond, func() { + attempt.mu.Lock() + racing := attempt.role == roleRacing && !attempt.finished + attempt.mu.Unlock() + if racing { + l.send(hedgeEvent{kind: hedgeThreshold, attempt: attempt}) + } + }) + } + + go func() { + run := attempt.run + if step.DelayMs > 0 && !sleepCtx(run.ctx, time.Duration(step.DelayMs)*time.Millisecond) { + attempt.finish(nil, attemptResult{event: e.failureEvent(e.classifyAttemptError(run.ctx, context.Cause(run.ctx), false), run.timers, nil)}, l) + return + } + prepared, result := e.prepare(run, true) + if prepared != nil { + attempt.mu.Lock() + attempt.status = prepared.resp.StatusCode + attempt.mu.Unlock() + } + attempt.finish(prepared, result, l) + }() +} + +// finish hands a finished pre-commit phase to the loop (still racing) or resolves +// the attempt as a loser (the winner was committed meanwhile). +func (a *hedgeAttempt) finish(prepared *preparedStream, result attemptResult, l *hedgeLoop) { + a.mu.Lock() + a.finished = true + if a.threshold != nil { + a.threshold.Stop() + } + role := a.role + if role == roleRacing && prepared != nil { + a.prepared = prepared + } + a.mu.Unlock() + + switch role { + case roleRacing: + if prepared != nil { + l.send(hedgeEvent{kind: hedgeReady, attempt: a}) + return + } + a.run.close() + event := result.event + if event == nil { + event = l.e.failureEvent(contract.AttemptFailure{Kind: "invalid_step", Message: "attempt ended without result"}, a.run.timers, nil) + } + l.send(hedgeEvent{kind: hedgeFailed, attempt: a, event: event}) + case roleLoserDrain: + if prepared == nil { + a.run.close() + a.loserResult <- a.emptyLoserResult() + return + } + a.loserResult <- l.e.drainLoser(a, prepared) + default: + if prepared != nil { + prepared.close() + } + a.run.close() + a.loserResult <- a.emptyLoserResult() + } +} + +func (a *hedgeAttempt) emptyLoserResult() contract.LoserResult { + a.mu.Lock() + status := a.status + a.mu.Unlock() + return contract.LoserResult{StepID: a.step().StepID, UpstreamStatus: status, EndedAtMs: time.Now().UnixMilli()} +} + +// handle processes one loop event; it returns true when the request is finished. +func (l *hedgeLoop) handle(ev hedgeEvent) bool { + e := l.e + switch ev.kind { + case hedgeReady: + delete(l.racing, ev.attempt) + l.ready = append(l.ready, ev.attempt) + case hedgeFailed: + delete(l.racing, ev.attempt) + l.pendingFailures++ + l.callNext(ev.attempt, ev.event, true) + case hedgeThreshold: + if !l.racing[ev.attempt] { + return false + } + l.callNext(ev.attempt, &contract.NextEvent{Type: "hedge_threshold"}, false) + case hedgeNextResponse: + if ev.failureOrigin { + l.pendingFailures-- + } + if ev.err != nil { + e.h.logger.Warn("hedge next failed", "requestId", e.requestID, "stepId", ev.attempt.step().StepID, "error", ev.err) + return false + } + switch ev.response.Action { + case "retry", "launch": + if ev.response.Step != nil && len(l.ready) == 0 { + l.start(ev.response.Step) + } + case "fail": + l.cancelAll(errHedgeLoser) + if e.r.Context().Err() == nil { + writeFailResponse(e.w, ev.response.Response) + } + return true + case "delegate": + if len(l.racing) == 0 && len(l.ready) == 0 && l.pendingFailures == 0 { + e.h.delegator.Serve(e.w, e.r, e.raw, "control_plane_delegate") + return true + } + } + } + return false +} + +func (l *hedgeLoop) callNext(attempt *hedgeAttempt, event *contract.NextEvent, failureOrigin bool) { + go func() { + response, err := l.e.next(attempt.step(), event) + l.send(hedgeEvent{kind: hedgeNextResponse, attempt: attempt, failureOrigin: failureOrigin, response: response, err: err}) + }() +} + +// cancelAll cancels every attempt that has not finished and releases ready ones. +func (l *hedgeLoop) cancelAll(cause error) { + for _, attempt := range l.attempts { + attempt.mu.Lock() + prepared := attempt.prepared + attempt.prepared = nil + if !attempt.finished { + attempt.role = roleCancelled + } + attempt.mu.Unlock() + if prepared != nil { + prepared.close() + } + attempt.run.cancel(cause) + } +} + +// clientAbort reports a pre-commit client disconnect once, with the timings of all +// in-flight attempts for first-byte health attribution. +func (l *hedgeLoop) clientAbort() { + e := l.e + inflight := make([]*hedgeAttempt, 0, len(l.racing)+len(l.ready)) + for _, attempt := range l.attempts { + if l.racing[attempt] { + inflight = append(inflight, attempt) + } + } + inflight = append(inflight, l.ready...) + l.cancelAll(errClientAborted) + if len(inflight) == 0 { + return + } + reporter := inflight[0] + event := e.failureEvent(contract.AttemptFailure{Kind: "client_abort"}, reporter.run.timers, nil) + for _, peer := range inflight[1:] { + timing := peer.run.timers.timing() + dispatched, firstByte := peer.run.timers.snapshot() + event.Peers = append(event.Peers, contract.PeerTiming{ + StepID: peer.step().StepID, + Dispatched: !dispatched.IsZero(), + FirstByteSeen: !firstByte.IsZero(), + HealthElapsedMs: timing.HealthElapsedMs, + }) + } + if _, err := e.next(reporter.step(), event); err != nil { + e.h.logger.Warn("hedge client abort report failed", "requestId", e.requestID, "error", err) + } +} + +// commit streams the winner and resolves every other attempt as a loser. +func (l *hedgeLoop) commit(winner *hedgeAttempt) { + e := l.e + winner.mu.Lock() + winner.role = roleWinner + prepared := winner.prepared + winner.prepared = nil + winner.mu.Unlock() + + var losers []*hedgeAttempt + var drainDeadline time.Duration + for _, attempt := range l.attempts { + if attempt == winner { + continue + } + attempt.mu.Lock() + if attempt.finished && attempt.prepared == nil { + // Failed before the commit: already reported through /next. + attempt.mu.Unlock() + continue + } + bill := attempt.step().Hedge != nil && attempt.step().Hedge.BillLosers + if bill { + attempt.role = roleLoserDrain + if limit := time.Duration(attempt.step().Hedge.LoserDrainMs) * time.Millisecond; limit > drainDeadline { + drainDeadline = limit + } + } else { + attempt.role = roleCancelled + } + ready := attempt.prepared + attempt.prepared = nil + attempt.mu.Unlock() + losers = append(losers, attempt) + + switch { + case ready != nil && bill: + go func(attempt *hedgeAttempt, ready *preparedStream) { + attempt.loserResult <- e.drainLoser(attempt, ready) + }(attempt, ready) + case ready != nil: + ready.close() + attempt.run.close() + attempt.loserResult <- attempt.emptyLoserResult() + case !bill: + attempt.run.cancel(errHedgeLoser) + } + } + if drainDeadline > 0 { + // A loser still in its pre-commit phase gets the same bounded window. + timer := time.AfterFunc(drainDeadline, func() { + for _, loser := range losers { + loser.run.cancel(errHedgeLoser) + } + }) + defer timer.Stop() + } + + collect := func() []contract.LoserResult { + results := make([]contract.LoserResult, 0, len(losers)) + wait := time.After(drainDeadline + 5*time.Second) + for _, loser := range losers { + select { + case result := <-loser.loserResult: + results = append(results, result) + case <-wait: + results = append(results, loser.emptyLoserResult()) + } + } + return results + } + e.h.metrics.attempts.Add("hedge_committed", 1) + e.commitStream(winner.run, prepared, collect) + winner.run.close() +} + +// drainLoser mirrors drainLoserBillingEvidence: read the loser to its end (or until +// the metering evidence is complete / the drain deadline passes) and return the +// bounded metering text for billing. +func (e *execution) drainLoser(attempt *hedgeAttempt, prepared *preparedStream) contract.LoserResult { + step := attempt.step() + defer attempt.run.close() + defer prepared.close() + + observer := capture.NewMeteringObserver(providerFormat(step.Provider.Type), loserBillingMaxFrameBytes) + stop := false + observe := func(data []byte) { + if stop || len(data) == 0 { + return + } + errorSeen, drainComplete := observer.Observe(data) + stop = errorSeen || drainComplete + } + for _, part := range prepared.prefix { + observe(part) + } + + endedNaturally := prepared.readerDone + if !endedNaturally && !stop { + limit := time.Duration(step.Hedge.LoserDrainMs) * time.Millisecond + if limit <= 0 { + limit = time.Second + } + deadline := time.NewTimer(limit) + defer deadline.Stop() + read: + for !stop { + select { + case next, ok := <-prepared.reader.ch: + if !ok { + endedNaturally = true + break read + } + observe(next.data) + if next.err != nil { + endedNaturally = errors.Is(next.err, io.EOF) + break read + } + case <-deadline.C: + break read + case <-attempt.run.ctx.Done(): + break read + } + } + } + + result := observer.Finish() + terminalSeen := result.TerminalSeen && (result.ProtocolFailure == nil || result.ProtocolFailure.Verdict != "error") + return contract.LoserResult{ + StepID: step.StepID, + UpstreamStatus: prepared.resp.StatusCode, + DrainComplete: endedNaturally || terminalSeen, + MeteringText: result.Text, + EndedAtMs: time.Now().UnixMilli(), + } +} diff --git a/edge/internal/server/hedge_test.go b/edge/internal/server/hedge_test.go new file mode 100644 index 0000000000..851f5949db --- /dev/null +++ b/edge/internal/server/hedge_test.go @@ -0,0 +1,245 @@ +package server + +import ( + "context" + "io" + "net/http" + "net/http/httptest" + "strings" + "sync/atomic" + "testing" + "time" + + "github.com/ding113/claude-code-hub/edge/internal/contract" +) + +func hedgeStep(stepID, upstreamURL string, providerID int64, billLosers bool) *contract.ExecutionStep { + step := baseStep(upstreamURL, true) + step.StepID = stepID + step.Provider.ID = providerID + step.Hedge = &contract.StepHedge{ThresholdMs: 60, MaxInFlight: 2, BillLosers: billLosers, LoserDrainMs: 2000} + return step +} + +// sseUpstream serves sseStream after delay; cancelled records a client-side abort. +func sseUpstream(t *testing.T, delay time.Duration, cancelled *atomic.Bool) *httptest.Server { + t.Helper() + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, _ = io.ReadAll(r.Body) + select { + case <-time.After(delay): + case <-r.Context().Done(): + if cancelled != nil { + cancelled.Store(true) + } + return + } + w.Header().Set("Content-Type", "text/event-stream") + flusher := w.(http.Flusher) + for _, frame := range strings.SplitAfter(sseStream, "\n\n") { + if _, err := w.Write([]byte(frame)); err != nil { + if cancelled != nil { + cancelled.Store(true) + } + return + } + flusher.Flush() + } + })) + t.Cleanup(srv.Close) + return srv +} + +func TestHedgeThresholdLaunchesAlternativeAndCancelsLoser(t *testing.T) { + var slowCancelled atomic.Bool + slow := sseUpstream(t, 2*time.Second, &slowCancelled) + fast := sseUpstream(t, 0, nil) + + control := &fakeControl{ + decide: executeDecision(hedgeStep("1:h1:1", slow.URL, 1, false)), + next: func(request *contract.NextRequest) (*contract.NextResponse, error) { + if request.Event.Type == "hedge_threshold" { + return &contract.NextResponse{Action: "launch", Step: hedgeStep("1:h2:1", fast.URL, 2, false)}, nil + } + return &contract.NextResponse{Action: "wait"}, nil + }, + } + h := newHarness(t, control) + resp := h.post(t, "/v1/messages", `{"model":"m","stream":true,"messages":[{"role":"user","content":"hi"}]}`, nil) + defer resp.Body.Close() + body, _ := io.ReadAll(resp.Body) + if resp.StatusCode != 200 || string(body) != sseStream { + t.Fatalf("status=%d body=%q", resp.StatusCode, body) + } + + report := h.reporter.waitComplete(t) + if report.Winner.StepID != "1:h2:1" { + t.Fatalf("winner %s", report.Winner.StepID) + } + if len(report.Losers) != 1 || report.Losers[0].StepID != "1:h1:1" || report.Losers[0].MeteringText != "" { + t.Fatalf("unexpected losers %+v", report.Losers) + } + nexts := control.nextRequests() + if len(nexts) != 1 || nexts[0].Event.Type != "hedge_threshold" || nexts[0].StepID != "1:h1:1" { + t.Fatalf("unexpected next calls %+v", nexts) + } + deadline := time.Now().Add(2 * time.Second) + for !slowCancelled.Load() && time.Now().Before(deadline) { + time.Sleep(10 * time.Millisecond) + } + if !slowCancelled.Load() { + t.Fatal("loser upstream request was not cancelled") + } +} + +func TestHedgeLoserIsDrainedForBilling(t *testing.T) { + slow := sseUpstream(t, 250*time.Millisecond, nil) + fast := sseUpstream(t, 0, nil) + + control := &fakeControl{ + decide: executeDecision(hedgeStep("1:h1:1", slow.URL, 1, true)), + next: func(request *contract.NextRequest) (*contract.NextResponse, error) { + if request.Event.Type == "hedge_threshold" { + return &contract.NextResponse{Action: "launch", Step: hedgeStep("1:h2:1", fast.URL, 2, true)}, nil + } + return &contract.NextResponse{Action: "wait"}, nil + }, + } + h := newHarness(t, control) + resp := h.post(t, "/v1/messages", `{"model":"m","stream":true,"messages":[{"role":"user","content":"hi"}]}`, nil) + _, _ = io.ReadAll(resp.Body) + resp.Body.Close() + + report := h.reporter.waitComplete(t) + if report.Winner.StepID != "1:h2:1" || len(report.Losers) != 1 { + t.Fatalf("unexpected report %+v", report) + } + loser := report.Losers[0] + if loser.UpstreamStatus != 200 || !loser.DrainComplete { + t.Fatalf("loser not drained: %+v", loser) + } + if !strings.Contains(loser.MeteringText, `"output_tokens":7`) { + t.Fatalf("loser metering text missing usage: %q", loser.MeteringText) + } +} + +func TestHedgeFailureWaitsForPeer(t *testing.T) { + slow := sseUpstream(t, 200*time.Millisecond, nil) + failing := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusInternalServerError) + _, _ = w.Write([]byte(`{"error":{"message":"boom"}}`)) + })) + defer failing.Close() + + control := &fakeControl{ + decide: executeDecision(hedgeStep("1:h1:1", slow.URL, 1, false)), + next: func(request *contract.NextRequest) (*contract.NextResponse, error) { + switch request.Event.Type { + case "hedge_threshold": + return &contract.NextResponse{Action: "launch", Step: hedgeStep("1:h2:1", failing.URL, 2, false)}, nil + case "failure": + return &contract.NextResponse{Action: "wait"}, nil + } + return &contract.NextResponse{Action: "none"}, nil + }, + } + h := newHarness(t, control) + resp := h.post(t, "/v1/messages", `{"model":"m","stream":true,"messages":[{"role":"user","content":"hi"}]}`, nil) + body, _ := io.ReadAll(resp.Body) + resp.Body.Close() + if string(body) != sseStream { + t.Fatalf("body %q", body) + } + report := h.reporter.waitComplete(t) + if report.Winner.StepID != "1:h1:1" || len(report.Losers) != 0 { + t.Fatalf("unexpected report winner=%s losers=%+v", report.Winner.StepID, report.Losers) + } + nexts := control.nextRequests() + if len(nexts) != 2 || nexts[1].Event.Type != "failure" || nexts[1].StepID != "1:h2:1" || + nexts[1].Event.Failure.Status != 500 { + t.Fatalf("unexpected next calls %+v", nexts) + } +} + +func TestHedgeFailCancelsEveryAttempt(t *testing.T) { + var slowCancelled atomic.Bool + slow := sseUpstream(t, 2*time.Second, &slowCancelled) + failing := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusBadRequest) + _, _ = w.Write([]byte(`{"error":{"message":"bad"}}`)) + })) + defer failing.Close() + + control := &fakeControl{ + decide: executeDecision(hedgeStep("1:h1:1", slow.URL, 1, false)), + next: func(request *contract.NextRequest) (*contract.NextResponse, error) { + if request.Event.Type == "hedge_threshold" { + return &contract.NextResponse{Action: "launch", Step: hedgeStep("1:h2:1", failing.URL, 2, false)}, nil + } + return &contract.NextResponse{Action: "fail", Response: &contract.FailResponse{ + Status: 400, Headers: []contract.HeaderPair{{"content-type", "application/json"}}, BodyText: `{"error":"bad"}`, + }}, nil + }, + } + h := newHarness(t, control) + resp := h.post(t, "/v1/messages", `{"model":"m","stream":true,"messages":[{"role":"user","content":"hi"}]}`, nil) + body, _ := io.ReadAll(resp.Body) + resp.Body.Close() + if resp.StatusCode != 400 || string(body) != `{"error":"bad"}` { + t.Fatalf("status=%d body=%q", resp.StatusCode, body) + } + deadline := time.Now().Add(2 * time.Second) + for !slowCancelled.Load() && time.Now().Before(deadline) { + time.Sleep(10 * time.Millisecond) + } + if !slowCancelled.Load() { + t.Fatal("in-flight attempt was not cancelled") + } +} + +func TestHedgeClientAbortReportsPeers(t *testing.T) { + hang := sseUpstream(t, 5*time.Second, nil) + abortSeen := make(chan *contract.NextRequest, 1) + var launched atomic.Bool + control := &fakeControl{ + decide: executeDecision(hedgeStep("1:h1:1", hang.URL, 1, false)), + next: func(request *contract.NextRequest) (*contract.NextResponse, error) { + if request.Event.Type == "hedge_threshold" { + // The control plane stops launching at maxInFlight (2). + if launched.Swap(true) { + return &contract.NextResponse{Action: "none"}, nil + } + return &contract.NextResponse{Action: "launch", Step: hedgeStep("1:h2:1", hang.URL, 2, false)}, nil + } + if request.Event.Failure != nil && request.Event.Failure.Kind == "client_abort" { + abortSeen <- request + } + return &contract.NextResponse{Action: "fail", Response: &contract.FailResponse{Status: 499, BodyText: "{}"}}, nil + }, + } + h := newHarness(t, control) + ctx, cancel := context.WithCancel(context.Background()) + req, _ := http.NewRequestWithContext(ctx, http.MethodPost, h.edge.URL+"/v1/messages", + strings.NewReader(`{"model":"m","stream":true,"messages":[{"role":"user","content":"hi"}]}`)) + go func() { + // Wait until both attempts are in flight (threshold 60ms, launch answered). + time.Sleep(300 * time.Millisecond) + cancel() + }() + if resp, err := http.DefaultClient.Do(req); err == nil { + resp.Body.Close() + } + + select { + case request := <-abortSeen: + if len(request.Event.Peers) != 1 || !request.Event.Peers[0].Dispatched || request.Event.Peers[0].FirstByteSeen { + t.Fatalf("unexpected peers %+v", request.Event.Peers) + } + if request.Event.Peers[0].StepID == request.StepID { + t.Fatalf("reporter listed as its own peer: %s", request.StepID) + } + case <-time.After(3 * time.Second): + t.Fatal("client abort was not reported") + } +} diff --git a/edge/internal/server/metrics.go b/edge/internal/server/metrics.go new file mode 100644 index 0000000000..1148e2796c --- /dev/null +++ b/edge/internal/server/metrics.go @@ -0,0 +1,57 @@ +package server + +import ( + "expvar" + "sync" + "time" +) + +// Metrics are exported as JSON on /-/edge/metrics (expvar format). +type Metrics struct { + requests *expvar.Map + attempts *expvar.Map + upstreamErrors *expvar.Map + decideCalls *expvar.Map + decideLatency *expvar.Float + inflight *expvar.Int + bytesToClient *expvar.Int +} + +var ( + metricsOnce sync.Once + sharedStats *Metrics +) + +func newMap(name string) *expvar.Map { + if existing := expvar.Get(name); existing != nil { + if typed, ok := existing.(*expvar.Map); ok { + return typed + } + } + return expvar.NewMap(name) +} + +// NewMetrics returns the process-wide metrics registry. +func NewMetrics() *Metrics { + metricsOnce.Do(func() { + sharedStats = &Metrics{ + requests: newMap("edge_requests"), + attempts: newMap("edge_attempts"), + upstreamErrors: newMap("edge_upstream_errors"), + decideCalls: newMap("edge_decide_calls"), + decideLatency: expvar.NewFloat("edge_decide_last_latency_ms"), + inflight: expvar.NewInt("edge_inflight"), + bytesToClient: expvar.NewInt("edge_bytes_to_client"), + } + }) + return sharedStats +} + +func (m *Metrics) observeDecide(latency time.Duration, err error) { + m.decideLatency.Set(float64(latency.Microseconds()) / 1000) + if err != nil { + m.decideCalls.Add("error", 1) + return + } + m.decideCalls.Add("ok", 1) +} diff --git a/edge/internal/server/server.go b/edge/internal/server/server.go new file mode 100644 index 0000000000..5f713b08e4 --- /dev/null +++ b/edge/internal/server/server.go @@ -0,0 +1,87 @@ +package server + +import ( + "context" + "expvar" + "net/http" + "sync/atomic" + "time" +) + +const ( + healthPath = "/-/edge/healthz" + readyPath = "/-/edge/readyz" + metricsPath = "/-/edge/metrics" +) + +// ReadinessProbe reports whether the edge should receive traffic. +type ReadinessProbe struct { + ready atomic.Bool + draining atomic.Bool + lastControlOK atomic.Int64 + outboxBytes func() int64 + outboxMaxReady int64 +} + +func NewReadinessProbe(outboxBytes func() int64, outboxMaxReady int64) *ReadinessProbe { + return &ReadinessProbe{outboxBytes: outboxBytes, outboxMaxReady: outboxMaxReady} +} + +func (p *ReadinessProbe) MarkControlOK() { + p.lastControlOK.Store(time.Now().UnixMilli()) + p.ready.Store(true) +} +func (p *ReadinessProbe) MarkDraining() { p.draining.Store(true) } +func (p *ReadinessProbe) IsDraining() bool { return p.draining.Load() } + +func (p *ReadinessProbe) isReady() bool { + if p.draining.Load() || !p.ready.Load() { + return false + } + if time.Since(time.UnixMilli(p.lastControlOK.Load())) > time.Minute { + return false + } + if p.outboxBytes != nil && p.outboxMaxReady > 0 && p.outboxBytes() > p.outboxMaxReady { + return false + } + return true +} + +// Mux routes operational endpoints and hands everything else to the proxy handler. +func Mux(handler http.Handler, probe *ReadinessProbe) http.Handler { + mux := http.NewServeMux() + mux.HandleFunc(healthPath, func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte("ok")) + }) + mux.HandleFunc(readyPath, func(w http.ResponseWriter, _ *http.Request) { + if probe.isReady() { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte("ready")) + return + } + w.WriteHeader(http.StatusServiceUnavailable) + _, _ = w.Write([]byte("not ready")) + }) + mux.Handle(metricsPath, expvar.Handler()) + mux.Handle("/", handler) + return mux +} + +// RunControlProbe keeps readiness in sync with control-plane reachability. +func RunControlProbe(ctx context.Context, probe *ReadinessProbe, check func(context.Context) error, interval time.Duration) { + ticker := time.NewTicker(interval) + defer ticker.Stop() + for { + checkCtx, cancel := context.WithTimeout(ctx, 5*time.Second) + if check(checkCtx) == nil { + probe.MarkControlOK() + } + cancel() + select { + case <-ctx.Done(): + return + case <-ticker.C: + } + } +} diff --git a/edge/internal/server/server_test.go b/edge/internal/server/server_test.go new file mode 100644 index 0000000000..182196c48b --- /dev/null +++ b/edge/internal/server/server_test.go @@ -0,0 +1,521 @@ +package server + +import ( + "bytes" + "compress/gzip" + "context" + "encoding/json" + "fmt" + "io" + "log/slog" + "net/http" + "net/http/httptest" + "net/url" + "strings" + "sync" + "testing" + "time" + + "github.com/ding113/claude-code-hub/edge/internal/config" + "github.com/ding113/claude-code-hub/edge/internal/contract" + "github.com/ding113/claude-code-hub/edge/internal/delegate" + "github.com/ding113/claude-code-hub/edge/internal/gate" + "github.com/ding113/claude-code-hub/edge/internal/upstream" +) + +// fakeControl scripts decide/next responses and records what the edge sent. +type fakeControl struct { + mu sync.Mutex + decide func(*contract.RequestDigest) (*contract.DecideResponse, error) + next func(*contract.NextRequest) (*contract.NextResponse, error) + digests []*contract.RequestDigest + nexts []*contract.NextRequest +} + +func (f *fakeControl) Decide(_ context.Context, digest *contract.RequestDigest) (*contract.DecideResponse, error) { + f.mu.Lock() + f.digests = append(f.digests, digest) + f.mu.Unlock() + return f.decide(digest) +} + +func (f *fakeControl) Next(_ context.Context, request *contract.NextRequest) (*contract.NextResponse, error) { + f.mu.Lock() + f.nexts = append(f.nexts, request) + f.mu.Unlock() + if f.next == nil { + return &contract.NextResponse{Action: "fail", Response: &contract.FailResponse{Status: 502, BodyText: "{}"}}, nil + } + return f.next(request) +} + +func (f *fakeControl) nextRequests() []*contract.NextRequest { + f.mu.Lock() + defer f.mu.Unlock() + return append([]*contract.NextRequest(nil), f.nexts...) +} + +type fakeReporter struct { + mu sync.Mutex + completes []*contract.CompleteRequest + heartbeats int + done chan struct{} +} + +func newFakeReporter() *fakeReporter { return &fakeReporter{done: make(chan struct{}, 16)} } + +func (f *fakeReporter) Complete(_ context.Context, request *contract.CompleteRequest) { + f.mu.Lock() + f.completes = append(f.completes, request) + f.mu.Unlock() + f.done <- struct{}{} +} + +func (f *fakeReporter) Heartbeat(context.Context, *contract.HeartbeatRequest) error { + f.mu.Lock() + f.heartbeats++ + f.mu.Unlock() + return nil +} + +func (f *fakeReporter) waitComplete(t *testing.T) *contract.CompleteRequest { + t.Helper() + select { + case <-f.done: + case <-time.After(5 * time.Second): + t.Fatal("no completion report") + } + f.mu.Lock() + defer f.mu.Unlock() + return f.completes[len(f.completes)-1] +} + +type harness struct { + control *fakeControl + reporter *fakeReporter + edge *httptest.Server + delegated chan *http.Request +} + +func newHarness(t *testing.T, control *fakeControl) *harness { + t.Helper() + delegated := make(chan *http.Request, 8) + delegateSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + body, _ := io.ReadAll(r.Body) + r.Body = io.NopCloser(bytes.NewReader(body)) + delegated <- r + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(`{"delegated":true,"body":` + fmt.Sprintf("%q", string(body)) + `}`)) + })) + t.Cleanup(delegateSrv.Close) + delegateURL, _ := url.Parse(delegateSrv.URL) + logger := slog.New(slog.NewTextHandler(io.Discard, nil)) + cfg := &config.Config{ + EdgeID: "edge-test", + MaxBodyBytes: 10 << 20, + MaxCompressedBodyBytes: 10 << 20, + MaxJSONDepth: 512, + MaxConcurrency: 16, + OverflowToDelegate: true, + FallbackToDelegate: true, + } + reporter := newFakeReporter() + handler := NewHandler(Deps{ + Config: cfg, + Control: control, + Reporter: reporter, + Delegator: delegate.New(delegateURL, logger), + Upstream: upstream.NewClient(), + Budget: gate.NewBudget(64 << 20), + Logger: logger, + }) + edge := httptest.NewServer(handler) + t.Cleanup(edge.Close) + return &harness{control: control, reporter: reporter, edge: edge, delegated: delegated} +} + +func (h *harness) post(t *testing.T, path, body string, headers map[string]string) *http.Response { + t.Helper() + req, _ := http.NewRequest(http.MethodPost, h.edge.URL+path, strings.NewReader(body)) + req.Header.Set("Content-Type", "application/json") + for name, value := range headers { + req.Header.Set(name, value) + } + resp, err := http.DefaultClient.Do(req) + if err != nil { + t.Fatal(err) + } + return resp +} + +func baseStep(upstreamURL string, streaming bool) *contract.ExecutionStep { + return &contract.ExecutionStep{ + StepID: "1:1:1", + AttemptNumber: 1, + TotalProvidersAttempted: 1, + AttemptKind: "normal", + Provider: contract.StepProvider{ID: 9, Name: "p9", Type: "claude"}, + Method: "POST", + URL: upstreamURL, + Headers: []contract.HeaderPair{ + {"content-type", "application/json"}, + {"x-api-key", "sk-upstream"}, + }, + BodyOps: []contract.BodyOp{ + {Op: contract.OpSetTopLevel, Key: "model", Value: json.RawMessage(`"claude-redirected"`)}, + {Op: contract.OpStripPrivateParams}, + }, + IsStreaming: streaming, + Timeouts: contract.StepTimeouts{ConnectMs: 2000, HeadersMs: 5000, BodyMs: 5000}, + Gate: contract.StepGate{Mode: "enforce", EventCap: 64, ByteCap: 1 << 20, CaptureCommitMarker: true}, + Fixer: contract.StepFixer{Enabled: true, FixTruncatedJSON: true, FixSseFormat: true, FixEncoding: true, MaxJSONDepth: 200, MaxFixSize: 1 << 20}, + Reporting: contract.StepReporting{ + MaxCompactBytes: 256 << 10, MaxHeadBytes: 1 << 20, MaxNonStreamBodyBytes: 8 << 20, + MaxErrorBodyBytes: 64 << 10, HeartbeatIntervalMs: 60_000, + }, + ClientAbortDrainMs: 2000, + } +} + +func executeDecision(step *contract.ExecutionStep) func(*contract.RequestDigest) (*contract.DecideResponse, error) { + return func(*contract.RequestDigest) (*contract.DecideResponse, error) { + return &contract.DecideResponse{Action: "execute", RequestID: 1, EdgeToken: "tok", Step: step}, nil + } +} + +const sseStream = "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"id\":\"m\",\"model\":\"claude-x\",\"usage\":{\"input_tokens\":5,\"output_tokens\":1}}}\n\n" + + "event: ping\ndata: {\"type\":\"ping\"}\n\n" + + "event: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"text\",\"text\":\"\"}}\n\n" + + "event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"hello\"}}\n\n" + + "event: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0}\n\n" + + "event: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"end_turn\"},\"usage\":{\"output_tokens\":7}}\n\n" + + "event: message_stop\ndata: {\"type\":\"message_stop\"}\n\n" + +func TestStaticRoutesAreDelegated(t *testing.T) { + h := newHarness(t, &fakeControl{}) + resp := h.post(t, "/v1/embeddings", `{"x":1}`, map[string]string{"X-Cch-Internal": "spoof"}) + defer resp.Body.Close() + if resp.Header.Get(delegate.ReasonHeader) != "static_route" { + t.Fatalf("expected static delegate, headers %v", resp.Header) + } + forwarded := <-h.delegated + if forwarded.URL.Path != "/v1/embeddings" || forwarded.Header.Get("X-Cch-Internal") != "" { + t.Fatalf("unexpected forwarded request %s %v", forwarded.URL.Path, forwarded.Header) + } + if len(h.control.digests) != 0 { + t.Fatal("decide must not be called for static routes") + } +} + +func TestDecideDelegateReplaysOriginalBytes(t *testing.T) { + control := &fakeControl{decide: func(*contract.RequestDigest) (*contract.DecideResponse, error) { + return &contract.DecideResponse{Action: "delegate", Reason: "hedge_pending"}, nil + }} + h := newHarness(t, control) + var gz bytes.Buffer + zw := gzip.NewWriter(&gz) + _, _ = zw.Write([]byte(`{"model":"m","stream":true,"messages":[{"role":"user","content":"hi"}]}`)) + _ = zw.Close() + req, _ := http.NewRequest(http.MethodPost, h.edge.URL+"/messages?beta=true", bytes.NewReader(gz.Bytes())) + req.Header.Set("Content-Encoding", "gzip") + req.Header.Set("X-Forwarded-For", "6.6.6.6") + resp, err := http.DefaultClient.Do(req) + if err != nil { + t.Fatal(err) + } + defer resp.Body.Close() + if resp.Header.Get(delegate.ReasonHeader) != "hedge_pending" { + t.Fatalf("headers %v", resp.Header) + } + forwarded := <-h.delegated + body, _ := io.ReadAll(forwarded.Body) + if !bytes.Equal(body, gz.Bytes()) || forwarded.Header.Get("Content-Encoding") != "gzip" { + t.Fatal("delegate must forward the original compressed bytes") + } + if strings.Contains(forwarded.Header.Get("X-Forwarded-For"), "6.6.6.6") { + t.Fatal("client supplied X-Forwarded-For must not reach the control plane") + } + + digest := control.digests[0] + if digest.Path != "/v1/messages?beta=true" || digest.MessagesCount == nil || *digest.MessagesCount != 1 { + t.Fatalf("unexpected digest path=%s count=%v", digest.Path, digest.MessagesCount) + } + for _, pair := range digest.Headers { + if pair[0] == "content-encoding" { + t.Fatal("decoded body must not advertise content-encoding in the digest") + } + if pair[0] == "x-forwarded-for" && pair[1] == "6.6.6.6" { + t.Fatal("spoofed forwarding header leaked into the digest") + } + } +} + +func TestDecideFailIsRelayedVerbatim(t *testing.T) { + control := &fakeControl{decide: func(*contract.RequestDigest) (*contract.DecideResponse, error) { + return &contract.DecideResponse{Action: "fail", Response: &contract.FailResponse{ + Status: 401, + Headers: []contract.HeaderPair{{"content-type", "application/json; charset=utf-8"}}, + BodyText: `{"error":{"message":"bad key","type":"authentication_error","code":"authentication_error"}}`, + }}, nil + }} + h := newHarness(t, control) + resp := h.post(t, "/v1/messages", `{"model":"m","messages":[]}`, nil) + defer resp.Body.Close() + body, _ := io.ReadAll(resp.Body) + if resp.StatusCode != 401 || string(body) != `{"error":{"message":"bad key","type":"authentication_error","code":"authentication_error"}}` { + t.Fatalf("status=%d body=%s", resp.StatusCode, body) + } +} + +func TestControlPlaneOutageFallsBackToDelegate(t *testing.T) { + control := &fakeControl{decide: func(*contract.RequestDigest) (*contract.DecideResponse, error) { + return nil, fmt.Errorf("connection refused") + }} + h := newHarness(t, control) + resp := h.post(t, "/v1/messages", `{"model":"m","messages":[]}`, nil) + defer resp.Body.Close() + if resp.Header.Get(delegate.ReasonHeader) != "control_unreachable" { + t.Fatalf("headers %v", resp.Header) + } +} + +func TestStreamingExecutionCommitsAndReports(t *testing.T) { + var upstreamBody string + upstreamSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + data, _ := io.ReadAll(r.Body) + upstreamBody = string(data) + w.Header().Set("Content-Type", "text/event-stream") + w.Header().Set("Request-Id", "req_up") + flusher := w.(http.Flusher) + for _, frame := range strings.SplitAfter(sseStream, "\n\n") { + _, _ = w.Write([]byte(frame)) + flusher.Flush() + } + })) + defer upstreamSrv.Close() + + control := &fakeControl{decide: executeDecision(baseStep(upstreamSrv.URL+"/v1/messages", true))} + h := newHarness(t, control) + resp := h.post(t, "/v1/messages", `{"model":"claude-sonnet","stream":true,"_debug":1,"messages":[{"role":"user","content":"hi"}]}`, nil) + defer resp.Body.Close() + body, _ := io.ReadAll(resp.Body) + if resp.StatusCode != 200 || string(body) != sseStream { + t.Fatalf("status=%d body=%q", resp.StatusCode, body) + } + if resp.Header.Get("Request-Id") != "req_up" { + t.Fatalf("upstream headers not relayed: %v", resp.Header) + } + if upstreamBody != `{"model":"claude-redirected","stream":true,"messages":[{"role":"user","content":"hi"}]}` { + t.Fatalf("body ops not applied: %s", upstreamBody) + } + + report := h.reporter.waitComplete(t) + winner := report.Winner + if !winner.IsStreaming || !winner.StreamEndedNormally || winner.ClientAborted || winner.UpstreamStatus != 200 { + t.Fatalf("unexpected winner %+v", winner) + } + if !strings.Contains(winner.CompactSSE, "message_start") || !strings.Contains(winner.CompactSSE, "\"output_tokens\":7") { + t.Fatalf("compact sse missing usage frames: %q", winner.CompactSSE) + } + if strings.Contains(winner.CompactSSE, "text_delta") { + t.Fatalf("compact sse must drop content frames: %q", winner.CompactSSE) + } + if winner.GateCommit == nil || winner.Protocol == nil || !winner.Protocol.SawTerminal { + t.Fatalf("missing gate/protocol observation: %+v %+v", winner.GateCommit, winner.Protocol) + } + if winner.Timing.FirstByteAtMs == nil || winner.Timing.FirstTokenAtMs == nil { + t.Fatal("missing timing marks") + } + if winner.BytesToClient != int64(len(sseStream)) { + t.Fatalf("bytes to client %d", winner.BytesToClient) + } +} + +func TestPreCommitFailureRetriesViaNext(t *testing.T) { + var calls int + var mu sync.Mutex + upstreamSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + mu.Lock() + calls++ + attempt := calls + mu.Unlock() + if attempt == 1 { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(529) + _, _ = w.Write([]byte(`{"type":"error","error":{"type":"overloaded_error","message":"Overloaded"}}`)) + return + } + w.Header().Set("Content-Type", "text/event-stream") + // Error frame before content: the gate must reject it with zero client bytes. + if attempt == 2 { + _, _ = w.Write([]byte("event: error\ndata: {\"type\":\"error\",\"error\":{\"type\":\"api_error\",\"message\":\"boom\"}}\n\n")) + return + } + _, _ = w.Write([]byte(sseStream)) + })) + defer upstreamSrv.Close() + + first := baseStep(upstreamSrv.URL, true) + control := &fakeControl{decide: executeDecision(first)} + control.next = func(request *contract.NextRequest) (*contract.NextResponse, error) { + retry := baseStep(upstreamSrv.URL, true) + retry.StepID = fmt.Sprintf("1:1:%d", len(control.nextRequests())+1) + return &contract.NextResponse{Action: "retry", Step: retry}, nil + } + h := newHarness(t, control) + resp := h.post(t, "/v1/messages", `{"model":"m","stream":true,"messages":[]}`, nil) + defer resp.Body.Close() + body, _ := io.ReadAll(resp.Body) + if string(body) != sseStream { + t.Fatalf("client must only see the committed stream, got %q", body) + } + nexts := control.nextRequests() + if len(nexts) != 2 { + t.Fatalf("expected 2 next calls, got %d", len(nexts)) + } + upstreamFailure := nexts[0].Event.Failure + if upstreamFailure.Kind != "upstream_status" || upstreamFailure.Status != 529 || !strings.Contains(upstreamFailure.BodyText, "Overloaded") { + t.Fatalf("unexpected first failure %+v", upstreamFailure) + } + gateFailure := nexts[1].Event.Failure + if gateFailure.Kind != "gate" || gateFailure.Reason != "gate_error" || !strings.Contains(gateFailure.FrameData, "boom") { + t.Fatalf("unexpected gate failure %+v", gateFailure) + } + if !nexts[0].Event.Dispatched { + t.Fatal("dispatch flag missing") + } + report := h.reporter.waitComplete(t) + if report.Winner.StepID != "1:1:3" { + t.Fatalf("winner step %s", report.Winner.StepID) + } +} + +func TestNextFailIsRelayed(t *testing.T) { + upstreamSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(400) + _, _ = w.Write([]byte(`{"error":{"message":"prompt too long"}}`)) + })) + defer upstreamSrv.Close() + control := &fakeControl{decide: executeDecision(baseStep(upstreamSrv.URL, false))} + control.next = func(*contract.NextRequest) (*contract.NextResponse, error) { + return &contract.NextResponse{Action: "fail", Response: &contract.FailResponse{ + Status: 400, Headers: []contract.HeaderPair{{"content-type", "application/json"}}, BodyText: `{"error":{"message":"prompt too long (cch_session_id: s)"}}`, + }}, nil + } + h := newHarness(t, control) + resp := h.post(t, "/v1/messages", `{"model":"m","messages":[]}`, nil) + defer resp.Body.Close() + body, _ := io.ReadAll(resp.Body) + if resp.StatusCode != 400 || !strings.Contains(string(body), "cch_session_id") { + t.Fatalf("status=%d body=%s", resp.StatusCode, body) + } +} + +func TestFirstByteTimeoutReportsTimeout(t *testing.T) { + release := make(chan struct{}) + upstreamSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/event-stream") + w.WriteHeader(200) + w.(http.Flusher).Flush() + select { + case <-release: + case <-r.Context().Done(): + } + })) + defer upstreamSrv.Close() + defer close(release) + step := baseStep(upstreamSrv.URL, true) + step.Timeouts.FirstByteMs = 100 + step.Gate.Mode = "off" + control := &fakeControl{decide: executeDecision(step)} + h := newHarness(t, control) + resp := h.post(t, "/v1/messages", `{"model":"m","stream":true,"messages":[]}`, nil) + defer resp.Body.Close() + _, _ = io.ReadAll(resp.Body) + nexts := control.nextRequests() + if len(nexts) != 1 || nexts[0].Event.Failure.Kind != "timeout" || nexts[0].Event.Failure.TimeoutType != "streaming_first_byte" { + t.Fatalf("unexpected events %+v", nexts) + } +} + +func TestNonStreamExecutionAndSuspectReview(t *testing.T) { + responses := []string{ + "Cloudflare error", + `{"type":"message","content":[{"type":"text","text":"ok"}],"usage":{"input_tokens":1,"output_tokens":2}}`, + } + var index int + var mu sync.Mutex + upstreamSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + mu.Lock() + body := responses[index] + index++ + mu.Unlock() + if strings.HasPrefix(body, "<") { + w.Header().Set("Content-Type", "text/html") + } else { + w.Header().Set("Content-Type", "application/json") + } + _, _ = w.Write([]byte(body)) + })) + defer upstreamSrv.Close() + + control := &fakeControl{decide: executeDecision(baseStep(upstreamSrv.URL, false))} + control.next = func(request *contract.NextRequest) (*contract.NextResponse, error) { + if request.Event.Type != "suspect_2xx" { + t.Errorf("unexpected event %s", request.Event.Type) + } + retry := baseStep(upstreamSrv.URL, false) + retry.StepID = "1:1:2" + return &contract.NextResponse{Action: "retry", Step: retry}, nil + } + h := newHarness(t, control) + resp := h.post(t, "/v1/messages", `{"model":"m","messages":[]}`, nil) + defer resp.Body.Close() + body, _ := io.ReadAll(resp.Body) + if string(body) != responses[1] { + t.Fatalf("body %s", body) + } + report := h.reporter.waitComplete(t) + if report.Winner.IsStreaming || report.Winner.NonStreamBody == nil || report.Winner.NonStreamBody.Text != responses[1] { + t.Fatalf("unexpected report %+v", report.Winner) + } +} + +func TestClientDisconnectAfterCommitDrainsForUsage(t *testing.T) { + head := "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"model\":\"m\",\"usage\":{\"input_tokens\":3,\"output_tokens\":1}}}\n\n" + + "event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"a\"}}\n\n" + tail := "event: message_delta\ndata: {\"type\":\"message_delta\",\"usage\":{\"output_tokens\":9}}\n\n" + + "event: message_stop\ndata: {\"type\":\"message_stop\"}\n\n" + clientGone := make(chan struct{}) + upstreamSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/event-stream") + _, _ = w.Write([]byte(head)) + w.(http.Flusher).Flush() + <-clientGone + time.Sleep(50 * time.Millisecond) + _, _ = w.Write([]byte(tail)) + })) + defer upstreamSrv.Close() + + control := &fakeControl{decide: executeDecision(baseStep(upstreamSrv.URL, true))} + h := newHarness(t, control) + ctx, cancel := context.WithCancel(context.Background()) + req, _ := http.NewRequestWithContext(ctx, http.MethodPost, h.edge.URL+"/v1/messages", strings.NewReader(`{"model":"m","stream":true,"messages":[]}`)) + resp, err := http.DefaultClient.Do(req) + if err != nil { + t.Fatal(err) + } + buf := make([]byte, 16) + _, _ = resp.Body.Read(buf) + cancel() + _ = resp.Body.Close() + close(clientGone) + + report := h.reporter.waitComplete(t) + if !report.Winner.ClientAborted { + t.Fatalf("expected client abort, got %+v", report.Winner) + } + if !strings.Contains(report.Winner.CompactSSE, "\"output_tokens\":9") { + t.Fatalf("drain must capture final usage, got %q", report.Winner.CompactSSE) + } +} diff --git a/edge/internal/server/stream.go b/edge/internal/server/stream.go new file mode 100644 index 0000000000..43fd5eb925 --- /dev/null +++ b/edge/internal/server/stream.go @@ -0,0 +1,527 @@ +package server + +import ( + "context" + "errors" + "io" + "net/http" + "time" + + "github.com/ding113/claude-code-hub/edge/internal/capture" + "github.com/ding113/claude-code-hub/edge/internal/contract" + "github.com/ding113/claude-code-hub/edge/internal/fixer" + "github.com/ding113/claude-code-hub/edge/internal/gate" +) + +const readChunkSize = 32 << 10 + +type chunk struct { + data []byte + err error +} + +// chunkReader reads the upstream body on a goroutine so reads can race timers. +// Closing the body unblocks a pending read. +type chunkReader struct { + ch chan chunk + stop chan struct{} +} + +func newChunkReader(body io.Reader) *chunkReader { + reader := &chunkReader{ch: make(chan chunk, 1), stop: make(chan struct{})} + go func() { + defer close(reader.ch) + for { + buf := make([]byte, readChunkSize) + n, err := body.Read(buf) + var data []byte + if n > 0 { + data = buf[:n] + } + select { + case reader.ch <- chunk{data: data, err: err}: + case <-reader.stop: + return + } + if err != nil { + return + } + } + }() + return reader +} + +func (c *chunkReader) close() { close(c.stop) } + +// readerFromChunks adapts prefix chunks + a chunkReader into an io.Reader for the gate. +type gateInput struct { + reader *chunkReader + buf []byte + err error +} + +func (g *gateInput) Read(p []byte) (int, error) { + if len(g.buf) > 0 { + n := copy(p, g.buf) + g.buf = g.buf[n:] + return n, nil + } + if g.err != nil { + return 0, g.err + } + next, ok := <-g.reader.ch + if !ok { + return 0, io.EOF + } + if next.err != nil { + g.err = next.err + } + n := copy(p, next.data) + g.buf = next.data[n:] + if n == 0 && g.err != nil { + return 0, g.err + } + return n, nil +} + +type streamOutcome struct { + endedNormally bool + clientAborted bool + abortReason *string +} + +func strPtr(value string) *string { return &value } + +// preparedStream is an attempt that passed its pre-commit phase (gate commit or +// first non-empty chunk) and can be committed to the client. +type preparedStream struct { + resp *http.Response + reader *chunkReader + prefix [][]byte + readerDone bool + commitMarker *contract.GateCommit + firstTokenAt time.Time + forcedStream bool + protocolFamily gate.Family +} + +func (p *preparedStream) close() { + p.reader.close() + _ = p.resp.Body.Close() +} + +// clientFormat returns the step's client format (claude when unset). +func clientFormat(step *contract.ExecutionStep) string { + switch step.ClientFormat { + case contract.FormatResponse, contract.FormatOpenAI: + return step.ClientFormat + } + return contract.FormatClaude +} + +// providerFormat mirrors mapProviderTypeToClientFormat (used for hedge loser metering). +func providerFormat(providerType string) string { + switch providerType { + case "codex": + return contract.FormatResponse + case "openai-compatible": + return contract.FormatOpenAI + } + return contract.FormatClaude +} + +func stepFamily(step *contract.ExecutionStep) gate.Family { + if family, ok := gate.FamilyForProviderType(step.Provider.Type); ok { + return family + } + return gate.FamilyAnthropic +} + +// prepareStream runs the precommit gate (enforce mode, not high concurrency) or +// waits for the first non-empty chunk. On failure the body is released by the caller. +func (e *execution) prepareStream(run *attemptRun, resp *http.Response, forced bool) (*preparedStream, attemptResult) { + step := run.step + timers := run.timers + ctx := run.ctx + reader := newChunkReader(resp.Body) + + prepared := &preparedStream{resp: resp, reader: reader, forcedStream: forced, protocolFamily: stepFamily(step)} + gateStarted := time.Now() + precommit := step.Gate.Mode == "enforce" && !step.Gate.HighConcurrency + + if precommit { + input := &gateInput{reader: reader} + result := gate.Run(ctx, input, gate.Options{ + Family: prepared.protocolFamily, + EventCap: step.Gate.EventCap, + ByteCap: step.Gate.ByteCap, + IdleTimeout: time.Duration(step.Gate.IdleMs) * time.Millisecond, + CaptureCommitMarker: step.Gate.CaptureCommitMarker, + Budget: e.h.budget, + OnFirstByte: func() { + timers.markFirstByte() + run.stopFirstByteTimer() + }, + }) + if !result.Committed { + reader.close() + return nil, attemptResult{event: e.failureEvent(e.gateFailure(ctx, result), timers, run.opResults)} + } + prepared.prefix = result.Prefix + prepared.readerDone = result.ReaderDone + if result.CommitMarker != nil { + marker := result.CommitMarker + prepared.commitMarker = &contract.GateCommit{ + FrameIndex: marker.FrameIndex, + ChunkIndex: marker.ChunkIndex, + EventName: marker.EventName, + BufferedBytes: marker.BufferedBytes, + EchoExcludedBytes: marker.EchoExcludedBytes, + GateWaitMs: time.Since(gateStarted).Milliseconds(), + } + } + // Bytes the gate pulled from the reader but did not consume stay in input.buf. + if len(input.buf) > 0 { + prepared.prefix = append(prepared.prefix, input.buf) + input.buf = nil + } + if input.err != nil { + prepared.readerDone = true + } + } else { + first, ok := e.readFirstChunk(ctx, reader, step) + if !ok.committed { + reader.close() + return nil, ok + } + timers.markFirstByte() + run.stopFirstByteTimer() + prepared.prefix = [][]byte{first.data} + prepared.readerDone = first.err != nil + } + prepared.firstTokenAt = time.Now() + return prepared, attemptResult{} +} + +// commitStream relays a prepared attempt to the client and reports completion. +// losers (hedge mode) is called after the winner finished and returns the hedge +// loser outcomes to include in the completion report. +func (e *execution) commitStream(run *attemptRun, prepared *preparedStream, losers func() []contract.LoserResult) attemptResult { + step := run.step + timers := run.timers + resp := prepared.resp + reader := prepared.reader + defer prepared.close() + + // COMMIT POINT: from here on nothing may fail over. + run.markCommitted() + run.stopFirstByteTimer() + e.h.metrics.attempts.Add("committed_stream", 1) + if prepared.forcedStream { + resp.Header.Set("Content-Type", "text/event-stream; charset=utf-8") + } + e.writeResponseHeaders(resp) + flusher, _ := e.w.(http.Flusher) + if flusher != nil { + flusher.Flush() + } + + format := clientFormat(step) + observer := gate.NewObserver(prepared.protocolFamily, step.Gate.ByteCap) + compact := capture.NewCompactCaptureForFormat(format, step.Reporting.MaxCompactBytes) + metering := capture.NewMeteringObserver(format, 0) + var streamFixer *fixer.StreamFixer + // Forced codex streams bypass the response fixer (ProxyResponseHandler.dispatch). + if step.Fixer.Enabled && !prepared.forcedStream { + streamFixer = fixer.NewStreamFixer(fixerConfig(step)) + } + + var bytesToClient int64 + clientGone := false + meteringDone := false + deliver := func(data []byte) { + if len(data) == 0 { + return + } + if errorSeen, drainComplete := metering.Observe(data); errorSeen || drainComplete { + meteringDone = true + } + out := data + if streamFixer != nil { + out = streamFixer.Write(data) + } + if len(out) == 0 || clientGone { + return + } + observer.Observe(out) + compact.Observe(out) + if _, err := e.w.Write(out); err != nil { + clientGone = true + return + } + bytesToClient += int64(len(out)) + e.addForwarded(int64(len(out))) + if flusher != nil { + flusher.Flush() + } + } + + for _, part := range prepared.prefix { + deliver(part) + } + + outcome := e.relay(step, reader, prepared.readerDone, deliver, &clientGone, &meteringDone, metering) + + if streamFixer != nil && !clientGone { + if tail := streamFixer.Flush(); len(tail) > 0 { + observer.Observe(tail) + compact.Observe(tail) + if _, err := e.w.Write(tail); err == nil { + bytesToClient += int64(len(tail)) + e.addForwarded(int64(len(tail))) + } + if flusher != nil { + flusher.Flush() + } + } + } + + compactText, compactTruncated, eventCount := compact.Result() + observation := observer.Finish() + protocol := &contract.ProtocolObservation{ + SawContent: observation.SawContent, + SawTerminal: observation.SawTerminal, + SawIncomplete: observation.SawIncomplete, + ObservationIncomplete: observation.ObservationIncomplete, + } + if observation.Failure != nil { + protocol.Failure = &contract.ProtocolFailure{ + Verdict: observation.Failure.Verdict, + EventName: observation.Failure.EventName, + AfterContent: observation.Failure.AfterContent, + SawMalformed: observation.Failure.SawMalformed, + } + } + if outcome.clientAborted { + // Mirrors the local client-abort path: settle from the bounded metering evidence. + metered := metering.Finish() + compactText = metered.Text + protocol = &contract.ProtocolObservation{ + SawContent: metered.SawContent, + SawTerminal: metered.TerminalSeen, + SawIncomplete: metered.IncompleteSeen, + ObservationIncomplete: metered.SkippedOversizedFrames > 0, + Failure: metered.ProtocolFailure, + } + } + + var fixerAudit *contract.FixerAudit + if streamFixer != nil { + if audit := streamFixer.Audit(); audit.Hit { + fixerAudit = &audit + } + } + + loserResults := []contract.LoserResult{} + if losers != nil { + loserResults = losers() + } + + dispatched, firstByte := timers.snapshot() + e.h.reporter.Complete(e.h.baseCtx, &contract.CompleteRequest{ + RequestID: e.requestID, + EdgeToken: e.edgeToken, + Winner: contract.WinnerResult{ + StepID: step.StepID, + UpstreamStatus: resp.StatusCode, + ResponseHeaders: headerPairs(resp.Header), + IsStreaming: true, + StreamEndedNormally: outcome.endedNormally, + ClientAborted: outcome.clientAborted, + AbortReason: outcome.abortReason, + FirstByteSeen: !firstByte.IsZero(), + SSEEventCount: eventCount, + CompactSSE: compactText, + CompactTruncated: compactTruncated, + Protocol: protocol, + GateCommit: prepared.commitMarker, + Fixer: fixerAudit, + Timing: contract.WinnerTiming{ + DispatchedAtMs: dispatched.UnixMilli(), + FirstByteAtMs: msPtr(firstByte), + FirstTokenAtMs: msPtr(prepared.firstTokenAt), + EndedAtMs: time.Now().UnixMilli(), + HealthElapsedMs: time.Since(dispatched).Milliseconds(), + }, + BytesToClient: bytesToClient, + OpResults: run.opResults, + }, + Losers: loserResults, + }) + return attemptResult{committed: true} +} + +// readFirstChunk waits for the first non-empty chunk when the precommit gate is off. +func (e *execution) readFirstChunk(ctx context.Context, reader *chunkReader, step *contract.ExecutionStep) (chunk, attemptResult) { + for { + select { + case <-ctx.Done(): + return chunk{}, attemptResult{event: e.failureEventFromCtx(ctx, step)} + case next, ok := <-reader.ch: + if !ok { + return chunk{}, attemptResult{event: e.emptyStreamEvent()} + } + if len(next.data) > 0 { + return next, attemptResult{committed: true} + } + if next.err != nil { + if errors.Is(next.err, io.EOF) { + return chunk{}, attemptResult{event: e.emptyStreamEvent()} + } + failure := e.classifyAttemptError(ctx, next.err, false) + return chunk{}, attemptResult{event: &contract.NextEvent{ + Type: "failure", Failure: &failure, Dispatched: true, + Timing: &contract.AttemptTiming{EndedAtMs: time.Now().UnixMilli()}, + }} + } + } + } +} + +func (e *execution) emptyStreamEvent() *contract.NextEvent { + return &contract.NextEvent{ + Type: "failure", + Failure: &contract.AttemptFailure{Kind: "empty_response", Reason: "empty_body"}, + Dispatched: true, + Timing: &contract.AttemptTiming{EndedAtMs: time.Now().UnixMilli()}, + } +} + +func (e *execution) failureEventFromCtx(ctx context.Context, step *contract.ExecutionStep) *contract.NextEvent { + failure := e.classifyAttemptError(ctx, context.Cause(ctx), false) + return &contract.NextEvent{ + Type: "failure", Failure: &failure, Dispatched: true, + Timing: &contract.AttemptTiming{EndedAtMs: time.Now().UnixMilli()}, + } +} + +func (e *execution) gateFailure(ctx context.Context, result gate.Result) contract.AttemptFailure { + if result.Failure == nil { + err := result.ReadErr + if err == nil { + err = context.Cause(ctx) + } + if err == nil { + err = io.ErrUnexpectedEOF + } + return e.classifyAttemptError(ctx, err, true) + } + failure := result.Failure + switch failure.Reason { + case "idle_timeout": + return contract.AttemptFailure{Kind: "timeout", TimeoutType: "streaming_idle"} + case "local_capacity": + return contract.AttemptFailure{Kind: "local_capacity", Message: "edge prebuffer capacity exhausted"} + } + return contract.AttemptFailure{ + Kind: "gate", + Reason: failure.Reason, + FrameData: failure.FrameData, + InferenceText: failure.InferenceText, + TerminalBeforeContent: failure.TerminalBeforeContent, + FramesSeen: failure.FramesSeen, + BufferedBytes: failure.BufferedBytes, + EchoExcludedBytes: failure.EchoExcludedBytes, + } +} + +// relay copies the rest of the upstream stream to the client after commit, +// enforcing the idle / body inactivity timeouts and draining upstream for usage +// evidence after a client disconnect. +func (e *execution) relay( + step *contract.ExecutionStep, + reader *chunkReader, + readerDone bool, + deliver func([]byte), + clientGone *bool, + meteringDone *bool, + metering *capture.MeteringObserver, +) streamOutcome { + if readerDone { + return streamOutcome{endedNormally: true} + } + idle := time.Duration(step.Timeouts.IdleMs) * time.Millisecond + bodyTimeout := time.Duration(step.Timeouts.BodyMs) * time.Millisecond + inactivity := idle + reason := contract.AbortStreamIdleTimeout + if inactivity <= 0 || (bodyTimeout > 0 && bodyTimeout < inactivity) { + inactivity = bodyTimeout + if idle <= 0 || bodyTimeout < idle { + reason = contract.AbortStreamResponseTimeout + } + } + + clientDone := e.r.Context().Done() + var drainDeadline <-chan time.Time + for { + var timeout <-chan time.Time + var timer *time.Timer + if inactivity > 0 { + timer = time.NewTimer(inactivity) + timeout = timer.C + } + select { + case next, ok := <-reader.ch: + if timer != nil { + timer.Stop() + } + if !ok { + return e.endOfStream(clientGone, nil) + } + if len(next.data) > 0 { + deliver(next.data) + // After a client disconnect, stop draining once usage evidence is complete. + if *clientGone && *meteringDone { + return streamOutcome{clientAborted: true} + } + } + if next.err != nil { + return e.endOfStream(clientGone, next.err) + } + case <-timeout: + if *clientGone { + return streamOutcome{clientAborted: true, abortReason: strPtr(contract.AbortClientAborted)} + } + return streamOutcome{abortReason: strPtr(reason)} + case <-clientDone: + if timer != nil { + timer.Stop() + } + clientDone = nil + *clientGone = true + metering.SwitchToDetachedMode() + if *meteringDone { + return streamOutcome{clientAborted: true} + } + drainDeadline = time.After(time.Duration(step.ClientAbortDrainMs) * time.Millisecond) + case <-drainDeadline: + if timer != nil { + timer.Stop() + } + return streamOutcome{clientAborted: true} + } + } +} + +func (e *execution) endOfStream(clientGone *bool, err error) streamOutcome { + endedNormally := err == nil || errors.Is(err, io.EOF) + if *clientGone { + // Matches the local pump: upstream EOF during the post-disconnect drain still + // counts as a normal end, the client abort flag carries the disconnect. + return streamOutcome{endedNormally: endedNormally, clientAborted: true} + } + if endedNormally { + return streamOutcome{endedNormally: true} + } + return streamOutcome{abortReason: strPtr(contract.AbortStreamUpstreamAborted)} +} diff --git a/edge/internal/sse/fixtures_test.go b/edge/internal/sse/fixtures_test.go new file mode 100644 index 0000000000..6242d1a880 --- /dev/null +++ b/edge/internal/sse/fixtures_test.go @@ -0,0 +1,184 @@ +package sse + +import ( + "encoding/base64" + "encoding/json" + "os" + "testing" +) + +type frameJSON struct { + EventName *string `json:"eventName"` + Data string `json:"data"` +} + +type sseFrameCaseJSON struct { + Name string `json:"name"` + Chunks []string `json:"chunks"` + Binary bool `json:"binary"` + MaxBufferedBytes *int `json:"maxBufferedBytes"` + ExemptionEventName *string `json:"exemptionEventName"` + ExpectedFrames []frameJSON `json:"expectedFrames"` + ExpectedError bool `json:"expectedError"` +} + +// chunkBytes decodes one fixture chunk to raw wire bytes: base64 when the +// case is marked binary (required for mid-UTF-8-codepoint splits, see the +// generator's doc comment on SseFrameCase.binary), plain UTF-8 otherwise. +func (c sseFrameCaseJSON) chunkBytes(chunk string) []byte { + if !c.Binary { + return []byte(chunk) + } + b, err := base64.StdEncoding.DecodeString(chunk) + if err != nil { + panic("fixture: invalid base64 chunk: " + err.Error()) + } + return b +} + +func loadFixture(t *testing.T, name string, v any) { + t.Helper() + path := "../../../tests/fixtures/edge/gate/" + name + data, err := os.ReadFile(path) + if err != nil { + t.Fatalf("read fixture %s: %v", path, err) + } + if err := json.Unmarshal(data, v); err != nil { + t.Fatalf("parse fixture %s: %v", path, err) + } +} + +func TestSseFramesFixtures(t *testing.T) { + var cases []sseFrameCaseJSON + loadFixture(t, "sse-frames.json", &cases) + if len(cases) == 0 { + t.Fatal("no fixture cases loaded") + } + + for _, c := range cases { + c := c + t.Run(c.Name, func(t *testing.T) { + maxBuffered := 0 + if c.MaxBufferedBytes != nil { + maxBuffered = *c.MaxBufferedBytes + } + var exemption func(*string) bool + if c.ExemptionEventName != nil { + want := *c.ExemptionEventName + exemption = func(pending *string) bool { + return pending != nil && *pending == want + } + } + p := NewParser(maxBuffered, exemption) + + var frames []Frame + var gotErr error + for _, chunk := range c.Chunks { + fs, err := p.Push(c.chunkBytes(chunk)) + if err != nil { + gotErr = err + break + } + frames = append(frames, fs...) + } + if gotErr == nil { + fs, err := p.Finish() + if err != nil { + gotErr = err + } else { + frames = append(frames, fs...) + } + } + + if c.ExpectedError { + if gotErr == nil { + t.Fatalf("expected buffer-limit error, got frames=%v", frames) + } + return + } + if gotErr != nil { + t.Fatalf("unexpected error: %v", gotErr) + } + assertFramesEqual(t, c.ExpectedFrames, frames) + }) + } +} + +func assertFramesEqual(t *testing.T, expected []frameJSON, actual []Frame) { + t.Helper() + if len(expected) != len(actual) { + t.Fatalf("frame count mismatch: want %d, got %d\nwant=%+v\ngot=%+v", len(expected), len(actual), expected, actual) + } + for i := range expected { + we, wd := expected[i].EventName, expected[i].Data + ge, gd := actual[i].EventName, actual[i].Data + if (we == nil) != (ge == nil) || (we != nil && ge != nil && *we != *ge) { + t.Fatalf("frame[%d] eventName mismatch: want %v, got %v", i, ptrStr(we), ptrStr(ge)) + } + if wd != gd { + t.Fatalf("frame[%d] data mismatch: want %q, got %q", i, wd, gd) + } + } +} + +func ptrStr(s *string) string { + if s == nil { + return "" + } + return *s +} + +// TestSplitInvariance is a Go-only companion to the fixtures: it verifies +// that splitting a representative body at every possible byte offset +// produces identical frames, exercising the CR/LF/UTF-8-boundary handling +// beyond what the fixture's fixed split points cover. +func TestSplitInvariance(t *testing.T) { + body := "event: content_block_delta\r\n" + + `data: {"type":"content_block_delta","delta":{"text":"你好"}}` + "\r\n\r\n" + + ": comment\n" + + "data: part1\ndata: part2\n\n" + + "event: message_stop\ndata: {}\n\n" + + "data: [DONE]\n\n" + bytes := []byte(body) + + expected, err := ParseBody(bytes) + if err != nil { + t.Fatalf("ParseBody: %v", err) + } + + for split := 1; split < len(bytes); split++ { + p := NewParser(0, nil) + var frames []Frame + fs, err := p.Push(bytes[:split]) + if err != nil { + t.Fatalf("split=%d push1: %v", split, err) + } + frames = append(frames, fs...) + fs, err = p.Push(bytes[split:]) + if err != nil { + t.Fatalf("split=%d push2: %v", split, err) + } + frames = append(frames, fs...) + fs, err = p.Finish() + if err != nil { + t.Fatalf("split=%d finish: %v", split, err) + } + frames = append(frames, fs...) + + if len(frames) != len(expected) { + t.Fatalf("split=%d: frame count mismatch: want %d got %d", split, len(expected), len(frames)) + } + for i := range expected { + if !eqPtr(expected[i].EventName, frames[i].EventName) || expected[i].Data != frames[i].Data { + t.Fatalf("split=%d frame[%d] mismatch: want %+v got %+v", split, i, expected[i], frames[i]) + } + } + } +} + +func eqPtr(a, b *string) bool { + if (a == nil) != (b == nil) { + return false + } + return a == nil || *a == *b +} diff --git a/edge/internal/sse/parser.go b/edge/internal/sse/parser.go new file mode 100644 index 0000000000..25d3a41936 --- /dev/null +++ b/edge/internal/sse/parser.go @@ -0,0 +1,325 @@ +// Package sse implements an incremental Server-Sent-Events frame parser. +// +// It is a Go port of src/app/v1/_lib/proxy/stream-gate/sse-frames.ts +// (SseFrameParser). It reassembles arbitrarily-split network chunks into +// complete SSE frames (event name + data payload), tolerating: +// - arbitrary network splits (frame / line / byte boundaries) +// - LF and CRLF line endings, including a CR landing at the very end of a +// chunk (the following LF may arrive in the next chunk) +// - comment lines (leading ':'), multi-line "data:", and ignored +// "id:"/"retry:"/unknown fields +// - bare `{`/`[` prefixed JSON lines (no "data:" prefix), one frame per line +// +// Frame boundary semantics match the TypeScript implementation: a blank line +// dispatches the current frame; an event with no data line produces no frame +// but still resets the pending event name. +// +// Deviation from the TypeScript source (documented per task instructions): +// - The TypeScript buffer-limit exemption inspects both the pending event +// name and a short prefix of the pending data ("dataHead") to decide +// whether an oversized-but-recognized frame (e.g. a large request-echo +// frame) is allowed past the configured limit, subject to its own, +// separate hard cap. The Go API mandated for this port only receives the +// pending event name (see NewParser), so the data-head sniffing and the +// secondary hard cap are not reproduced here: a matching exemption fully +// exempts the frame from the byte limit (bounded only by whatever the +// caller does with the resulting Frame.Data downstream, e.g. package +// gate's own byte-cap accounting). +// - Byte counts are counted in bytes (UTF-8), not UTF-16 code units as in +// TypeScript's `string.length`. For content dominated by ASCII framing +// bytes (which is the common case for SSE field names) this rarely +// matters; multi-byte payload characters will count as more "bytes" in +// Go than "characters" in TS. +package sse + +import ( + "bytes" + "errors" +) + +// Frame is a single parsed SSE frame. +type Frame struct { + // EventName is the SSE "event:" field value (trimmed), or nil when the + // frame had no event field (or was a bare JSON line). + EventName *string + // Data is the frame's payload: multi-line "data:" fields are joined with + // "\n"; a bare JSON line's Data is that line, trimmed. + Data string + // Raw holds the exact bytes of the frame as they appeared on the wire, + // including the terminating blank line when one was present (bare JSON + // lines and EOF-terminated trailing frames have no blank line, so Raw + // ends at the line's own terminator, or at the end of input). + Raw []byte +} + +// ErrBufferLimit is returned by Push/Finish when the retained, not-yet +// dispatched buffer exceeds the parser's configured limit. +var ErrBufferLimit = errors.New("sse: buffered data exceeded configured limit") + +// Parser incrementally parses SSE frames from a byte stream. It is not safe +// for concurrent use. +type Parser struct { + maxBufferedBytes int + exemption func(pendingEvent *string) bool + + lineBuf []byte + skipLeadingLf bool + currentEvent *string + + dataBuf []byte + dataLineCount int + + rawBuf []byte +} + +// NewParser creates a Parser. +// +// maxBufferedBytes bounds the retained (not-yet-dispatched) line/event/data +// buffers; <= 0 means unlimited (matching the TS default of no +// maxBufferedCharacters option). +// +// exemption, when non-nil, is consulted when the limit would otherwise be +// exceeded: it receives the pending event name (as it would be attributed to +// the in-flight frame) and, if it returns true, the limit check is skipped +// for that update. See the package doc for how this differs from the +// TypeScript implementation's exemption, which also inspects data content. +func NewParser(maxBufferedBytes int, exemption func(pendingEvent *string) bool) *Parser { + return &Parser{maxBufferedBytes: maxBufferedBytes, exemption: exemption} +} + +// Push feeds a network chunk into the parser, returning any frames completed +// by it (possibly none). +func (p *Parser) Push(chunk []byte) ([]Frame, error) { + var frames []Frame + if err := p.consume(chunk, &frames); err != nil { + return nil, err + } + return frames, nil +} + +// Finish signals end of input: it flushes a trailing unterminated line (if +// any) as one final line, and dispatches a trailing frame if one is pending. +func (p *Parser) Finish() ([]Frame, error) { + var frames []Frame + p.skipLeadingLf = false + if len(p.lineBuf) > 0 { + if err := p.handleLine(&frames); err != nil { + return nil, err + } + } + if err := p.flush(&frames); err != nil { + return nil, err + } + return frames, nil +} + +func (p *Parser) consume(data []byte, out *[]Frame) error { + start := 0 + if p.skipLeadingLf { + p.skipLeadingLf = false + if len(data) == 0 { + return nil + } + if data[0] == '\n' { + p.rawBuf = append(p.rawBuf, data[0]) + start = 1 + } + } + + for i := start; i < len(data); i++ { + c := data[i] + if c != '\n' && c != '\r' { + continue + } + p.lineBuf = append(p.lineBuf, data[start:i]...) + p.rawBuf = append(p.rawBuf, data[start:i+1]...) + + if err := p.assertBufferLimitForCompletedLine(); err != nil { + return err + } + if err := p.handleLine(out); err != nil { + return err + } + + if c == '\r' { + if i+1 < len(data) && data[i+1] == '\n' { + p.rawBuf = append(p.rawBuf, data[i+1]) + i++ + } else if i == len(data)-1 { + p.skipLeadingLf = true + } + } + start = i + 1 + } + + p.lineBuf = append(p.lineBuf, data[start:]...) + p.rawBuf = append(p.rawBuf, data[start:]...) + return p.assertBufferLimitDefault() +} + +// takeLine consumes and clears the current line buffer. +func (p *Parser) takeLine() []byte { + line := p.lineBuf + p.lineBuf = nil + return line +} + +func (p *Parser) handleLine(out *[]Frame) error { + line := p.takeLine() + if len(line) == 0 { + return p.flush(out) + } + if line[0] == ':' { + return nil // SSE comment + } + if hasPrefix(line, "event:") { + v := trimSpace(string(line[6:])) + p.currentEvent = &v + return p.assertBufferLimitDefault() + } + if hasPrefix(line, "data:") { + d := stripOneLeadingSpace(line[5:]) + if p.dataLineCount > 0 { + p.dataBuf = append(p.dataBuf, '\n') + } + p.dataBuf = append(p.dataBuf, d...) + p.dataLineCount++ + return p.assertBufferLimitDefault() + } + + candidate := bytes.TrimSpace(line) + if p.currentEvent == nil && p.dataLineCount == 0 && len(candidate) > 0 && + (candidate[0] == '{' || candidate[0] == '[') { + raw := p.takeRaw() + *out = append(*out, Frame{EventName: nil, Data: string(candidate), Raw: raw}) + return nil + } + // id: / retry: / unknown fields: ignored. + return nil +} + +func (p *Parser) flush(out *[]Frame) error { + event := p.currentEvent + p.currentEvent = nil + if p.dataLineCount == 0 { + return nil + } + data := string(p.dataBuf) + p.dataBuf = nil + p.dataLineCount = 0 + raw := p.takeRaw() + *out = append(*out, Frame{EventName: event, Data: data, Raw: raw}) + return nil +} + +func (p *Parser) takeRaw() []byte { + raw := p.rawBuf + p.rawBuf = nil + return raw +} + +func (p *Parser) resetRetainedState() { + p.lineBuf = nil + p.currentEvent = nil + p.dataBuf = nil + p.dataLineCount = 0 + p.skipLeadingLf = false + p.rawBuf = nil +} + +// assertBufferLimitDefault mirrors SseFrameParser#assertBufferLimit() called +// with no arguments: it charges the full current line buffer (there is no +// completed-line prefix to discount) plus the pending event and data +// buffers. +func (p *Parser) assertBufferLimitDefault() error { + return p.assertBufferLimit(len(p.lineBuf), false, p.currentEvent) +} + +// assertBufferLimitForCompletedLine mirrors the TS call site that runs right +// after a full line has been appended to the line buffer but before it is +// classified/consumed: SSE field syntax ("data:"/"event:" plus at most one +// separating space) is excluded from the count, and if the completed line is +// itself an "event:" field it will replace (not add to) the currently +// tracked event name. +func (p *Parser) assertBufferLimitForCompletedLine() error { + line := p.lineBuf + ignored := 0 + replaces := false + candidate := p.currentEvent + switch { + case hasPrefix(line, "data:"): + ignored = 5 + if len(line) > 5 && line[5] == ' ' { + ignored = 6 + } + case hasPrefix(line, "event:"): + ignored = 6 + if len(line) > 6 && line[6] == ' ' { + ignored = 7 + } + replaces = true + v := trimSpace(string(line[6:])) + candidate = &v + } + return p.assertBufferLimit(ignored, replaces, candidate) +} + +func (p *Parser) assertBufferLimit(ignoredLineBytes int, replacesCurrentEvent bool, candidateEvent *string) error { + if p.maxBufferedBytes <= 0 { + return nil + } + lineLen := len(p.lineBuf) - ignoredLineBytes + if lineLen < 0 { + lineLen = 0 + } + eventLen := 0 + if !replacesCurrentEvent && p.currentEvent != nil { + eventLen = len(*p.currentEvent) + } + buffered := lineLen + eventLen + len(p.dataBuf) + if buffered <= p.maxBufferedBytes { + return nil + } + if p.exemption != nil && p.exemption(candidateEvent) { + return nil + } + p.resetRetainedState() + return ErrBufferLimit +} + +func hasPrefix(b []byte, prefix string) bool { + return len(b) >= len(prefix) && string(b[:len(prefix)]) == prefix +} + +func trimSpace(s string) string { + return string(bytes.TrimSpace([]byte(s))) +} + +// stripOneLeadingSpace removes at most one leading space/whitespace byte, +// mirroring TS `line.slice(5).replace(/^\s/, "")` (JS \s strips a single +// leading whitespace character, not just a literal space). +func stripOneLeadingSpace(b []byte) []byte { + if len(b) == 0 { + return b + } + switch b[0] { + case ' ', '\t', '\n', '\r', '\f', '\v': + return b[1:] + } + return b +} + +// ParseBody parses a complete SSE body in one shot (equivalent of TS +// parseSseBody). +func ParseBody(body []byte) ([]Frame, error) { + p := NewParser(0, nil) + frames, err := p.Push(body) + if err != nil { + return nil, err + } + tail, err := p.Finish() + if err != nil { + return nil, err + } + return append(frames, tail...), nil +} diff --git a/edge/internal/upstream/upstream.go b/edge/internal/upstream/upstream.go new file mode 100644 index 0000000000..da130abb2c --- /dev/null +++ b/edge/internal/upstream/upstream.go @@ -0,0 +1,320 @@ +// Package upstream performs one upstream HTTP attempt described by an ExecutionStep. +// +// Mirrors ProxyForwarder.doForwardPrepared transport behavior: headers are sent +// exactly as planned by the control plane, redirects are not followed, a gzip +// response body is decoded transparently (content-encoding / content-length are +// dropped), outbound proxies (http, https, socks5) may fall back to a direct +// connection, and HTTP/2 protocol failures are retried once over HTTP/1.1. +package upstream + +import ( + "bytes" + "compress/gzip" + "context" + "crypto/tls" + "errors" + "io" + "net" + "net/http" + "net/url" + "strings" + "sync" + "time" + + "github.com/ding113/claude-code-hub/edge/internal/contract" +) + +// hop-by-hop and framing headers the transport computes itself. +var skippedRequestHeaders = map[string]bool{ + "connection": true, "keep-alive": true, "transfer-encoding": true, "te": true, + "upgrade": true, "content-length": true, "proxy-connection": true, "host": true, +} + +type transportKey struct { + proxyURL string + http2 bool + connect time.Duration + headers time.Duration + direct bool +} + +// Client caches transports per (proxy, protocol, timeout) combination. +type Client struct { + mu sync.Mutex + transports map[transportKey]*http.Transport + h2Quarantine sync.Map // host -> time.Time until which HTTP/2 is avoided +} + +func NewClient() *Client { + return &Client{transports: make(map[transportKey]*http.Transport)} +} + +func (c *Client) transport(key transportKey) (*http.Transport, error) { + c.mu.Lock() + defer c.mu.Unlock() + if existing, ok := c.transports[key]; ok { + return existing, nil + } + dialer := &net.Dialer{Timeout: key.connect, KeepAlive: 30 * time.Second} + transport := &http.Transport{ + DialContext: dialer.DialContext, + TLSHandshakeTimeout: 15 * time.Second, + ResponseHeaderTimeout: key.headers, + DisableCompression: true, + MaxIdleConns: 512, + MaxIdleConnsPerHost: 64, + IdleConnTimeout: 90 * time.Second, + ForceAttemptHTTP2: key.http2, + } + if !key.http2 { + transport.TLSNextProto = map[string]func(string, *tls.Conn) http.RoundTripper{} + } + if key.proxyURL != "" && !key.direct { + proxyURL, err := url.Parse(key.proxyURL) + if err != nil { + return nil, err + } + transport.Proxy = http.ProxyURL(proxyURL) + } + c.transports[key] = transport + return transport, nil +} + +// Result of a single round trip. +type Result struct { + Response *http.Response + HTTP2Fallback bool + ProxyFallback bool +} + +// Do sends the request. The response body is owned by the caller. +func (c *Client) Do(ctx context.Context, step *contract.ExecutionStep, body []byte, onDispatch func()) (*Result, error) { + proxy := "" + if step.Transport.ProxyURL != nil { + proxy = *step.Transport.ProxyURL + } + key := transportKey{ + proxyURL: proxy, + http2: step.Transport.HTTP2, + connect: time.Duration(step.Timeouts.ConnectMs) * time.Millisecond, + headers: time.Duration(step.Timeouts.HeadersMs) * time.Millisecond, + } + host := hostOf(step.URL) + if key.http2 && c.isQuarantined(host) { + key.http2 = false + } + + result := &Result{} + resp, err := c.roundTrip(ctx, key, step, body, onDispatch) + if err != nil && key.http2 && IsHTTP2Error(err) && ctx.Err() == nil { + c.h2Quarantine.Store(host, time.Now().Add(10*time.Minute)) + key.http2 = false + result.HTTP2Fallback = true + resp, err = c.roundTrip(ctx, key, step, body, nil) + } + if err != nil && proxy != "" && step.Transport.ProxyFallbackToDirect && isProxyConnectError(err) && ctx.Err() == nil { + key.direct = true + result.ProxyFallback = true + resp, err = c.roundTrip(ctx, key, step, body, nil) + } + if err != nil { + return result, err + } + decodeGzip(resp) + result.Response = resp + return result, nil +} + +func (c *Client) isQuarantined(host string) bool { + value, ok := c.h2Quarantine.Load(host) + if !ok { + return false + } + if time.Now().After(value.(time.Time)) { + c.h2Quarantine.Delete(host) + return false + } + return true +} + +func (c *Client) roundTrip(ctx context.Context, key transportKey, step *contract.ExecutionStep, body []byte, onDispatch func()) (*http.Response, error) { + transport, err := c.transport(key) + if err != nil { + return nil, err + } + req, err := http.NewRequestWithContext(ctx, step.Method, step.URL, bytes.NewReader(body)) + if err != nil { + return nil, err + } + req.ContentLength = int64(len(body)) + req.Header = make(http.Header, len(step.Headers)) + for _, pair := range step.Headers { + name := strings.ToLower(pair[0]) + if name == "host" { + req.Host = pair[1] + continue + } + if skippedRequestHeaders[name] { + continue + } + // Keep the header name exactly as planned (no canonicalization). + req.Header[pair[0]] = append(req.Header[pair[0]], pair[1]) + } + if onDispatch != nil { + onDispatch() + } + return transport.RoundTrip(req) +} + +func hostOf(raw string) string { + parsed, err := url.Parse(raw) + if err != nil { + return raw + } + return parsed.Host +} + +type gzipBody struct { + reader *gzip.Reader + raw io.ReadCloser +} + +func (g *gzipBody) Read(p []byte) (int, error) { + n, err := g.reader.Read(p) + // Tolerate a truncated gzip trailer like zlib Z_SYNC_FLUSH: keep decoded bytes. + if errors.Is(err, io.ErrUnexpectedEOF) { + return n, io.EOF + } + return n, err +} + +func (g *gzipBody) Close() error { return g.raw.Close() } + +type lazyGzipBody struct { + raw io.ReadCloser + body io.ReadCloser + err error + opened bool +} + +func (l *lazyGzipBody) Read(p []byte) (int, error) { + if !l.opened { + l.opened = true + reader, err := gzip.NewReader(l.raw) + if err != nil { + if errors.Is(err, io.EOF) { + l.err = io.EOF + } else { + l.err = err + } + } else { + l.body = &gzipBody{reader: reader, raw: l.raw} + } + } + if l.err != nil { + return 0, l.err + } + return l.body.Read(p) +} + +func (l *lazyGzipBody) Close() error { return l.raw.Close() } + +func decodeGzip(resp *http.Response) { + encoding := strings.ToLower(strings.TrimSpace(resp.Header.Get("Content-Encoding"))) + if encoding != "gzip" && encoding != "x-gzip" { + return + } + resp.Body = &lazyGzipBody{raw: resp.Body} + resp.Header.Del("Content-Encoding") + resp.Header.Del("Content-Length") + resp.ContentLength = -1 +} + +// IsHTTP2Error reports HTTP/2 protocol level failures (ports errors.ts isHttp2Error). +func IsHTTP2Error(err error) bool { + if err == nil { + return false + } + message := err.Error() + for _, marker := range []string{ + "http2:", "HTTP/2", "PROTOCOL_ERROR", "REFUSED_STREAM", "INTERNAL_ERROR", + "GOAWAY", "stream error", "ERR_HTTP2", + } { + if strings.Contains(message, marker) { + return true + } + } + return false +} + +func isProxyConnectError(err error) bool { + message := err.Error() + if strings.Contains(message, "proxyconnect") || strings.Contains(message, "socks connect") { + return true + } + var opErr *net.OpError + return errors.As(err, &opErr) && opErr.Op == "dial" +} + +// ClassifyError maps a transport error to the undici-style code the control plane +// categorizes (errors.ts isTransportError). Timeouts caused by the caller's own +// timers must be detected by the caller before calling this. +func ClassifyError(err error) (code string, message string) { + message = redact(err.Error()) + var dnsErr *net.DNSError + if errors.As(err, &dnsErr) { + if dnsErr.IsNotFound { + return "ENOTFOUND", message + } + return "EAI_AGAIN", message + } + if IsHTTP2Error(err) { + return "ERR_HTTP2_STREAM_ERROR", message + } + lower := strings.ToLower(err.Error()) + switch { + case strings.Contains(lower, "timeout awaiting response headers"): + return "UND_ERR_HEADERS_TIMEOUT", message + case strings.Contains(lower, "connection refused"): + return "ECONNREFUSED", message + case strings.Contains(lower, "connection reset"), strings.Contains(lower, "broken pipe"): + return "ECONNRESET", message + case strings.Contains(lower, "tls:"), strings.Contains(lower, "x509:"), strings.Contains(lower, "certificate"): + return "EPROTO", message + case errors.Is(err, io.ErrUnexpectedEOF), strings.Contains(lower, "unexpected eof"), + strings.Contains(lower, "server closed"), errors.Is(err, io.EOF): + return "UND_ERR_SOCKET", "other side closed: " + message + } + var netErr net.Error + if errors.As(err, &netErr) && netErr.Timeout() { + var opErr *net.OpError + if errors.As(err, &opErr) && opErr.Op == "dial" { + return "UND_ERR_CONNECT_TIMEOUT", message + } + return "ETIMEDOUT", message + } + return "UNKNOWN", message +} + +// redact strips userinfo and query strings from URLs embedded in error text. +func redact(message string) string { + fields := strings.Fields(message) + for index, field := range fields { + trimmed := strings.Trim(field, `"':,`) + if !strings.Contains(trimmed, "://") { + continue + } + parsed, err := url.Parse(trimmed) + if err != nil { + continue + } + parsed.User = nil + parsed.RawQuery = "" + fields[index] = strings.Replace(field, trimmed, parsed.String(), 1) + } + result := strings.Join(fields, " ") + if len(result) > 1024 { + result = result[:1024] + } + return result +} diff --git a/edge/internal/upstream/upstream_test.go b/edge/internal/upstream/upstream_test.go new file mode 100644 index 0000000000..02d0190e58 --- /dev/null +++ b/edge/internal/upstream/upstream_test.go @@ -0,0 +1,167 @@ +package upstream + +import ( + "bytes" + "compress/gzip" + "context" + "errors" + "io" + "net" + "net/http" + "net/http/httptest" + "strings" + "testing" + "time" + + "github.com/ding113/claude-code-hub/edge/internal/contract" +) + +func step(url string, headers ...contract.HeaderPair) *contract.ExecutionStep { + return &contract.ExecutionStep{ + Method: "POST", + URL: url, + Headers: headers, + Timeouts: contract.StepTimeouts{ConnectMs: 2000, HeadersMs: 5000}, + } +} + +func TestDoSendsPlannedHeadersAndBody(t *testing.T) { + var gotHost, gotAuth, gotLength, gotBody string + var rawHeaderNames []string + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + gotHost = r.Host + gotAuth = r.Header.Get("Authorization") + gotLength = r.Header.Get("Content-Length") + for name := range r.Header { + rawHeaderNames = append(rawHeaderNames, name) + } + body, _ := io.ReadAll(r.Body) + gotBody = string(body) + w.WriteHeader(201) + })) + defer srv.Close() + + client := NewClient() + dispatched := false + result, err := client.Do(context.Background(), step(srv.URL+"/v1/messages?beta=true", + contract.HeaderPair{"host", "api.example.com"}, + contract.HeaderPair{"Authorization", "Bearer k"}, + contract.HeaderPair{"content-length", "999"}, + contract.HeaderPair{"transfer-encoding", "chunked"}, + contract.HeaderPair{"anthropic-version", "2023-06-01"}, + ), []byte(`{"a":1}`), func() { dispatched = true }) + if err != nil { + t.Fatal(err) + } + defer result.Response.Body.Close() + if !dispatched || result.Response.StatusCode != 201 { + t.Fatalf("unexpected result %+v", result) + } + if gotHost != "api.example.com" || gotAuth != "Bearer k" || gotBody != `{"a":1}` || gotLength != "7" { + t.Fatalf("host=%q auth=%q body=%q length=%q", gotHost, gotAuth, gotBody, gotLength) + } +} + +func TestDoDecodesGzipResponses(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var buf bytes.Buffer + zw := gzip.NewWriter(&buf) + _, _ = zw.Write([]byte("hello gzip")) + _ = zw.Close() + w.Header().Set("Content-Encoding", "gzip") + _, _ = w.Write(buf.Bytes()) + })) + defer srv.Close() + result, err := NewClient().Do(context.Background(), step(srv.URL), nil, nil) + if err != nil { + t.Fatal(err) + } + body, _ := io.ReadAll(result.Response.Body) + if string(body) != "hello gzip" || result.Response.Header.Get("Content-Encoding") != "" { + t.Fatalf("body=%q headers=%v", body, result.Response.Header) + } +} + +func TestDoDoesNotFollowRedirects(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.Redirect(w, r, "/elsewhere", http.StatusFound) + })) + defer srv.Close() + result, err := NewClient().Do(context.Background(), step(srv.URL), nil, nil) + if err != nil { + t.Fatal(err) + } + if result.Response.StatusCode != http.StatusFound { + t.Fatalf("status %d", result.Response.StatusCode) + } +} + +func TestProxyFallbackToDirect(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, _ = w.Write([]byte("direct")) + })) + defer srv.Close() + listener, _ := net.Listen("tcp", "127.0.0.1:0") + deadProxy := "http://" + listener.Addr().String() + _ = listener.Close() + + s := step(srv.URL) + s.Transport.ProxyURL = &deadProxy + s.Transport.ProxyFallbackToDirect = true + result, err := NewClient().Do(context.Background(), s, nil, nil) + if err != nil { + t.Fatal(err) + } + body, _ := io.ReadAll(result.Response.Body) + if !result.ProxyFallback || string(body) != "direct" { + t.Fatalf("fallback=%v body=%q", result.ProxyFallback, body) + } + + s.Transport.ProxyFallbackToDirect = false + if _, err := NewClient().Do(context.Background(), s, nil, nil); err == nil { + t.Fatal("expected proxy failure without fallback") + } +} + +func TestClassifyError(t *testing.T) { + listener, _ := net.Listen("tcp", "127.0.0.1:0") + addr := listener.Addr().String() + _ = listener.Close() + _, err := NewClient().Do(context.Background(), step("http://"+addr), nil, nil) + if code, _ := ClassifyError(err); code != "ECONNREFUSED" { + t.Fatalf("refused: %s (%v)", code, err) + } + + cases := map[string]error{ + "ENOTFOUND": &net.DNSError{Err: "no such host", Name: "x.invalid", IsNotFound: true}, + "EAI_AGAIN": &net.DNSError{Err: "temporary", Name: "x", IsTemporary: true}, + "UND_ERR_HEADERS_TIMEOUT": errors.New("net/http: timeout awaiting response headers"), + "ECONNRESET": errors.New("read tcp: connection reset by peer"), + "UND_ERR_SOCKET": io.ErrUnexpectedEOF, + "ERR_HTTP2_STREAM_ERROR": errors.New("http2: stream error: PROTOCOL_ERROR"), + "EPROTO": errors.New("tls: handshake failure"), + "UNKNOWN": errors.New("something else"), + } + for want, input := range cases { + if got, _ := ClassifyError(input); got != want { + t.Errorf("ClassifyError(%v) = %s, want %s", input, got, want) + } + } +} + +func TestHeadersTimeoutAndRedaction(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + time.Sleep(300 * time.Millisecond) + })) + defer srv.Close() + s := step(srv.URL + "/path?key=secret") + s.Timeouts.HeadersMs = 50 + _, err := NewClient().Do(context.Background(), s, nil, nil) + code, message := ClassifyError(err) + if code != "UND_ERR_HEADERS_TIMEOUT" { + t.Fatalf("code %s (%v)", code, err) + } + if strings.Contains(message, "secret") { + t.Fatalf("message not redacted: %s", message) + } +} diff --git a/messages/en/dashboard.json b/messages/en/dashboard.json index bf2da3c97e..8e6bfefa38 100644 --- a/messages/en/dashboard.json +++ b/messages/en/dashboard.json @@ -1020,6 +1020,7 @@ "providers": "Providers", "auditLogs": "Audit logs", "documentation": "Docs", + "models": "Models", "systemSettings": "Settings", "feedback": "Feedback", "login": "Login", @@ -2279,7 +2280,8 @@ "gh-action": "GitHub Action", "codex-cli-core": "CLI / TUI", "desktop": "Desktop", - "exec": "Exec" + "exec": "Exec", + "python-sdk": "Python SDK" }, "nSelected": "{count} selected" }, diff --git a/messages/en/index.ts b/messages/en/index.ts index 97aeb7c2f2..c7b0c9b4bb 100644 --- a/messages/en/index.ts +++ b/messages/en/index.ts @@ -8,6 +8,7 @@ import errors from "./errors.json"; import forms from "./forms.json"; import internal from "./internal.json"; import ipDetails from "./ipDetails.json"; +import modelCatalog from "./modelCatalog.json"; import myUsage from "./myUsage.json"; import notifications from "./notifications.json"; import providerChain from "./provider-chain.json"; @@ -29,6 +30,7 @@ export default { errors, forms, ipDetails, + modelCatalog, notifications, "provider-chain": providerChain, providers, diff --git a/messages/en/modelCatalog.json b/messages/en/modelCatalog.json new file mode 100644 index 0000000000..23abcfd00a --- /dev/null +++ b/messages/en/modelCatalog.json @@ -0,0 +1,50 @@ +{ + "pageTitle": "Model Catalog", + "pageDescription": "Models your API key can use, the protocols each one is served through, and their limits, prices and capabilities.", + "generatedAt": "Generated at {time}. Refreshes at most once a minute.", + "searchPlaceholder": "Search models", + "tabs": { + "byModel": "By model", + "byProtocol": "By protocol" + }, + "columns": { + "model": "Model", + "protocols": "Protocols", + "contextWindow": "Context", + "maxOutput": "Max output", + "inputPrice": "Input", + "outputPrice": "Output", + "cacheRead": "Cache read", + "cacheWrite": "Cache write", + "capabilities": "Capabilities", + "modalities": "Modalities", + "knowledgeCutoff": "Knowledge cutoff" + }, + "capabilities": { + "vision": "Vision", + "functionCalling": "Tools", + "reasoning": "Reasoning", + "pdfInput": "PDF", + "promptCaching": "Prompt caching" + }, + "modalities": { + "input": "In", + "output": "Out" + }, + "status": { + "deprecated": "Deprecated" + }, + "priceUnit": "Prices are in USD per 1M tokens, base tier.", + "noPriceData": "No metadata", + "empty": "No models match.", + "protocolEmpty": "No models available through this protocol.", + "protocolModelsCount": "{count, plural, one {# model} other {# models}}", + "machineReadable": { + "title": "For agents and scripts", + "description": "Call these URLs with your API key as a Bearer token. The default response is JSON. Add ?format=md for Markdown that LLMs can read directly.", + "copy": "Copy URL" + }, + "notes": { + "title": "Notes from the administrator" + } +} diff --git a/messages/en/myUsage.json b/messages/en/myUsage.json index 58a82954b3..8a9c065c95 100644 --- a/messages/en/myUsage.json +++ b/messages/en/myUsage.json @@ -3,6 +3,7 @@ "title": "My Usage", "welcome": "Welcome, {name}", "documentation": "Usage Docs", + "modelCatalog": "Model Catalog", "logout": "Logout", "keyLabel": "Key", "userLabel": "User", diff --git a/messages/en/provider-chain.json b/messages/en/provider-chain.json index c7e1b10389..e5180f4dc8 100644 --- a/messages/en/provider-chain.json +++ b/messages/en/provider-chain.json @@ -82,18 +82,22 @@ "format_type_mismatch": "Format Type Mismatch", "type_mismatch": "Type Mismatch", "model_not_allowed": "Model Not Allowed", + "prefix_mismatch": "Provider Prefix Mismatch", "model_not_supported": "Model Not Supported", "group_mismatch": "Group Mismatch", "health_check_failed": "Health Check Failed", "endpoint_circuit_open": "Endpoint Circuit Open", "endpoint_disabled": "Endpoint Disabled", - "client_restriction": "Client Restriction" + "client_restriction": "Client Restriction", + "quota_low": "Upstream Quota Low", + "quota_exhausted": "Upstream Quota Exhausted" }, "filterDetails": { "vendor_type_circuit_open": "Vendor-type temporarily circuit-broken", "circuit_open": "Circuit breaker open", "circuit_half_open": "Circuit breaker half-open", "rate_limited": "Rate limited", + "upstream_balance_exhausted": "Upstream balance exhausted", "provider_client_restriction": "Provider skipped due to client restriction", "session_reuse_client_restriction": "Session reuse rejected: client restriction", "blocklist_hit": "Blocked by pattern: {pattern}", diff --git a/messages/en/settings/config.json b/messages/en/settings/config.json index 4a3fe3ceab..ee35b16f51 100644 --- a/messages/en/settings/config.json +++ b/messages/en/settings/config.json @@ -59,9 +59,12 @@ "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.", + "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. Quota leases refresh from the database at most every 30 seconds, the total spending limit cache lasts 15 minutes, and idle availability projection polling slows down. Forwarding, rectifiers, fake-200 detection, billing, and quota enforcement remain unchanged, but Sessions debugging details may be reduced or delayed and quota and dashboard figures update with more lag.", "enableMemoryAdmission": "Enable Memory Admission", "enableMemoryAdmissionDesc": "When enabled, request bodies and the stream buffer before first content are admitted against this machine's available memory. When the budget runs short they are written to disk, and if that is not enough CCH returns a local 429 asking the client to retry later. When disabled, this data stays in memory without queuing and no local 429 is returned.", + "agentCatalogNotes": "Model catalog notes for agents", + "agentCatalogNotesDesc": "Markdown appended to the model catalog page (/models) and the LLM-readable endpoint (/v1/models/catalog). Visible to every user. Use it to tell agents which models to prefer and any usage limits. Leave empty to hide.", + "agentCatalogNotesPlaceholder": "For example:\n## Model selection\n- Prefer claude-sonnet-4-5 for coding tasks\n- Use gemini-2.5-pro for very long contexts", "enableResponseFixer": "Enable Response Fixer", "enableResponseFixerDesc": "Automatically repairs common upstream response issues (encoding, SSE, truncated JSON). Enabled by default.", "enableThinkingSignatureRectifier": "Enable Thinking Signature Rectifier", @@ -126,6 +129,13 @@ "legacyHedgeMaxInFlightInvalid": "Legacy hedge concurrency must be an integer from 1 to 4.", "discoveryEnabled": "Enable bounded provider Discovery", "discoveryEnabledDesc": "When enabled, cold-start streaming requests probe multiple providers within a bounded window and keep at most one fallback. It is disabled by default.", + "upstreamQuotaSchedulingEnabled": "Upstream quota scheduling", + "upstreamQuotaSchedulingEnabledDesc": "Periodically query the remaining Coding Plan quota of Kimi, Zhipu GLM, MiniMax and OpenCode Go providers. New sessions are not scheduled to providers whose remaining quota is below the threshold; existing sessions keep their provider until the quota is fully exhausted. Upstream 402 or insufficient-balance responses pause the provider immediately.", + "upstreamQuotaThresholdPercent": "Minimum remaining quota for new sessions (%)", + "upstreamQuotaThresholdPercentDesc": "Don't schedule new sessions to providers whose remaining quota in any active window (5h, weekly or monthly) is lower than this percentage. Can be overridden per provider.", + "upstreamQuotaProbeIntervalMinutes": "Quota probe interval (minutes)", + "upstreamQuotaProbeIntervalMinutesDesc": "How often each provider's quota is queried. Snapshots older than 3 intervals are ignored, and a reactive pause lasts 2 intervals.", + "upstreamQuotaSettingsInvalid": "Upstream quota threshold must be 1-99 and the probe interval must be 1-1440 minutes.", "discoveryConcurrency": "Discovery initial concurrency", "maxDiscoveryRounds": "Discovery maximum rounds", "discoverySlaMs": "Discovery SLA (milliseconds)", @@ -150,6 +160,7 @@ "description": "Configure lease refresh interval and slice percentages for rate limit checks. The lease mechanism reduces DB query pressure while maintaining rate limit accuracy.", "dbRefreshInterval": "DB Refresh Interval (seconds)", "dbRefreshIntervalDesc": "Interval for refreshing quota usage from the database (1-300 seconds)", + "dbRefreshIntervalHighConcurrencyFloor": "High concurrency mode is on: the effective refresh interval is at least {seconds}s to reduce database aggregation queries.", "leasePercent5h": "5-Hour Window Lease Percentage", "leasePercent5hDesc": "Percentage of 5-hour limit for each lease slice (0-1)", "leasePercentDaily": "Daily Window Lease Percentage", @@ -169,6 +180,8 @@ }, "affinityIgnoreClientSessionId": "Ignore Client Session ID", "affinityIgnoreClientSessionIdDesc": "When enabled, fingerprintable requests are forced to use longest-prefix affinity for provider stickiness (skipping client Session ID binding); non-fingerprintable requests still use session reuse. Default on.", + "edgeExecutionEnabled": "Edge Executor", + "edgeExecutionEnabledDesc": "When enabled, remote edge executors can take over upstream calls for eligible /v1/messages requests (Claude-format providers). Authentication, rate limiting, provider routing, retries and billing are still decided here. Requires CCH_EDGE_SHARED_SECRET.", "streamGateMode": "Stream Content Gate", "streamGateModeDesc": "Controls the first-valid-content gate for ordinary requests: enabled mode fails over on error frames or empty streams, shadow mode only records divergence, and off mode forwards directly. Shadow and off modes deliver the first upstream byte immediately, including for replay owners; high-concurrency mode is treated as off. Default enabled.", "streamGateModeOptions": { diff --git a/messages/en/settings/notifications.json b/messages/en/settings/notifications.json index 569b2c3d6e..8de63bd578 100644 --- a/messages/en/settings/notifications.json +++ b/messages/en/settings/notifications.json @@ -54,6 +54,15 @@ "windowMode": "Window", "windowModeAuto": "Auto" }, + "clientProblem": { + "countThreshold": "General error count threshold", + "cyberCountThreshold": "Cyber risk count threshold", + "cyberWindowMinutes": "Cyber risk window (minutes)", + "description": "Digest timeouts, server errors, and cyber risk seen by clients (excluding 499). Two independent thresholds: count or time window, whichever hits first.", + "enable": "Enable client problem alerts", + "title": "Client problem alerts", + "windowMinutes": "General error window (minutes)" + }, "dailyLeaderboard": { "description": "Send daily scheduled user consumption Top N leaderboard", "enable": "Enable Daily Leaderboard", @@ -89,7 +98,10 @@ "on": "On", "legacyModeDescription": "You are using legacy single-URL notifications. Create a push target to switch to multi-target mode.", "legacyModeTitle": "Legacy Mode", - "title": "Notification Master Switch" + "title": "Notification Master Switch", + "titlePrefix": "Alert title prefix", + "titlePrefixHelp": "Distinguishes instances. Prepended as [PROXY] to alert titles. Leave empty for no prefix. A webhook target proxy wins; otherwise OUTBOUND_PROXY_URL is used; PROXY/HTTPS_PROXY applies only when that is unset.", + "titlePrefixPlaceholder": "PROXY" }, "targetDialog": { "createTitle": "Add Push Target", diff --git a/messages/en/settings/providers/batchEdit.json b/messages/en/settings/providers/batchEdit.json index 3ac96dff03..f2da24f6a9 100644 --- a/messages/en/settings/providers/batchEdit.json +++ b/messages/en/settings/providers/batchEdit.json @@ -55,7 +55,8 @@ "modelRedirects": "Model Redirects", "allowedModels": "Allowed Models", "thinkingBudget": "Thinking Budget", - "adaptiveThinking": "Adaptive Thinking" + "adaptiveThinking": "Adaptive Thinking", + "reasoningEffortRules": "Reasoning Effort Rules" }, "errors": { "nothingToApply": "There are no changes to apply.", diff --git a/messages/en/settings/providers/form/matchTester.json b/messages/en/settings/providers/form/matchTester.json index 4ab09e3994..234b6f25d4 100644 --- a/messages/en/settings/providers/form/matchTester.json +++ b/messages/en/settings/providers/form/matchTester.json @@ -15,5 +15,6 @@ "matchType": "Match Type", "source": "Source Pattern", "target": "Redirect Target", - "pattern": "Allowlist Pattern" + "pattern": "Allowlist Pattern", + "prefixStripped": "Provider prefix removed. Rules are matched against {model}" } diff --git a/messages/en/settings/providers/form/sections.json b/messages/en/settings/providers/form/sections.json index 939d6d4138..4623c377d7 100644 --- a/messages/en/settings/providers/form/sections.json +++ b/messages/en/settings/providers/form/sections.json @@ -176,6 +176,38 @@ "otherLimits": "Other Limits", "windowReset": "Window Reset Behavior" }, + "upstreamQuota": { + "title": "Upstream Quota", + "desc": "Query the provider's Coding Plan quota and stop scheduling new sessions to it when the remaining quota is low. Takes effect when upstream quota scheduling is enabled in system settings.", + "probeType": { + "label": "Quota source", + "desc": "Choose which Coding Plan quota endpoint to query, or turn probing off for this provider.", + "autoResolved": "Auto-detected from the provider URL: {type}", + "options": { + "auto": "Auto-detect from URL", + "none": "Disabled", + "kimi-coding": "Kimi Coding Plan", + "zhipu-coding": "Zhipu GLM Coding Plan", + "minimax-coding": "MiniMax Coding Plan", + "opencode-go": "OpenCode Go" + } + }, + "threshold": { + "label": "Minimum remaining quota (%)", + "desc": "Overrides the system threshold for this provider. Leave empty to use the system setting.", + "placeholder": "System default" + }, + "zhipuOrganization": { + "label": "Zhipu organization ID", + "desc": "Team plan only. Sent as the bigmodel-organization header.", + "placeholder": "org-..." + }, + "zhipuProject": { + "label": "Zhipu project ID", + "desc": "Team plan only. Sent as the bigmodel-project header.", + "placeholder": "proj_..." + } + }, "routing": { "cacheTtl": { "desc": "Force prompt cache TTL; only affects requests with cache_control.", @@ -205,6 +237,86 @@ "invalidTemplate": "Invalid header template. Supported: '{{header.Name}}', '{{session.id}}', '{{session.client_id}}'" } }, + "effortRules": { + "title": "Conditional Reasoning Effort Rules", + "description": "Rules are evaluated in order. The first matching rule determines the override effort. If no rule matches, no override is applied.", + "noRules": "No rules configured. Add a rule to enable conditional effort overrides.", + "addRule": "Add Rule", + "removeRule": "Remove rule", + "moveUp": "Move up", + "moveDown": "Move down", + "ruleLabel": "Rule {number}", + "targetEffort": "Target effort", + "selectTarget": "Select target effort", + "originalModelCondition": "When original model...", + "executionModelCondition": "When execution model...", + "removeCondition": "remove", + "addCondition": "add", + "matchModes": { + "exact": "exact", + "prefix": "prefix", + "suffix": "suffix", + "contains": "contains", + "regex": "regex" + }, + "patternPlaceholder": "e.g. claude-opus-4-6", + "originalEffortCondition": "When original effort...", + "effortMode": { + "any": "Any (omit condition)", + "missing": "Missing (explicit null)", + "specific": "Specific value" + }, + "originalEffortPlaceholder": "e.g. medium", + "maxRulesReached": "Maximum {max} rules reached", + "effortValues": { + "none": "none", + "minimal": "minimal", + "low": "low", + "medium": "medium", + "high": "high", + "xhigh": "xhigh", + "max": "max" + } + }, + "serviceTierRules": { + "title": "Conditional Service Tier Rules", + "description": "Rules are evaluated in order. The first matching rule decides the upstream service_tier. If no rule matches, the client value is passed through unchanged.", + "noRules": "No rules configured. Add a rule to enable conditional service tier overrides.", + "addRule": "Add Rule", + "removeRule": "Remove rule", + "moveUp": "Move up", + "moveDown": "Move down", + "ruleLabel": "Rule {number}", + "targetServiceTier": "Target service tier", + "selectTarget": "Select target service tier", + "originalModelCondition": "When original model...", + "executionModelCondition": "When execution model...", + "removeCondition": "remove", + "addCondition": "add", + "matchModes": { + "exact": "exact", + "prefix": "prefix", + "suffix": "suffix", + "contains": "contains", + "regex": "regex" + }, + "patternPlaceholder": "e.g. gpt-5-codex", + "originalServiceTierCondition": "When original service tier...", + "serviceTierMode": { + "any": "Any (omit condition)", + "missing": "Missing (client sent none)", + "specific": "Specific value" + }, + "originalServiceTierPlaceholder": "e.g. priority", + "maxRulesReached": "Maximum {max} rules reached", + "serviceTierValues": { + "auto": "auto (project default)", + "default": "default (standard)", + "flex": "flex (best-effort)", + "priority": "priority (fast)", + "unset": "Unset (remove service_tier)" + } + }, "codexOverrides": { "title": "Codex Parameter Overrides", "desc": "Override Codex (Responses API) request parameters at the provider level", @@ -227,7 +339,7 @@ } }, "serviceTier": { - "help": "Controls the OpenAI service_tier for Codex (Responses API) requests. \"priority\" corresponds to fast mode. \"inherit\" follows the client request; other values force override service_tier. Unsupported tiers may cause upstream errors.", + "help": "Controls the OpenAI service_tier for Codex (Responses API) requests. When enabled, rules are matched by original model, execution model and the client's service_tier; the first match wins. \"priority\" corresponds to fast mode. \"Unset\" removes service_tier from the upstream request even if the client sent priority; with the \"Requested Service Tier\" billing source such requests are billed at the standard rate. Unsupported tiers may cause upstream errors.", "label": "Service Tier Override", "options": { "auto": "auto (project default)", @@ -378,7 +490,8 @@ "gh-action": "GitHub Action", "codex-cli-core": "CLI / TUI", "desktop": "Desktop", - "exec": "Exec" + "exec": "Exec", + "python-sdk": "Python SDK" }, "nSelected": "{count} selected" }, @@ -392,6 +505,17 @@ "label": "Skip session reuse", "desc": "When enabled, the next request will ignore this provider's sticky binding and rerun priority and health checks. Useful when a cheap higher-priority provider should be retried as soon as it recovers." }, + "overwriteResponseModel": { + "label": "Overwrite response model ID", + "desc": "When enabled, the model field returned to clients is always the requested model ID, not the upstream or redirected model." + }, + "providerPrefix": { + "label": "Provider prefix", + "desc": "Optional. When set, only model IDs carrying this prefix reach this provider, e.g. prefix openai requires openai/gpt-5. The prefix is removed before forwarding upstream.", + "placeholder": "e.g. openai", + "help": "Trailing slashes are normalized to a single /. Prefix matching is case-insensitive. Model redirect and allowlist rules are written against the model name without the prefix. Model list endpoints show this provider's models with the prefix.", + "preview": "Only models starting with {prefix} match, e.g. {example}. {prefix} is removed before forwarding upstream." + }, "options": { "title": "Options", "desc": "Additional provider options and overrides" diff --git a/messages/en/settings/providers/list.json b/messages/en/settings/providers/list.json index 8e39dab8ce..e7c0804171 100644 --- a/messages/en/settings/providers/list.json +++ b/messages/en/settings/providers/list.json @@ -2,7 +2,45 @@ "cancelButton": "Cancel", "circuitBroken": "Circuit Broken", "keyCircuitBroken": "Key Circuit", + "providerPrefixBadge": "Provider prefix: {prefix}", "endpointCircuitBroken": "Endpoint Circuit", + "upstreamQuota": { + "title": "Upstream quota", + "statusOk": "Quota {percent}%", + "statusLow": "Quota low {percent}%", + "statusExhausted": "Out of quota", + "statusUnknown": "Quota unknown", + "windows": { + "5h": "5-hour window", + "weekly": "Weekly", + "monthly": "Monthly", + "rolling": "Rolling window" + }, + "used": "{percent}% used", + "resetsIn": "Resets in {duration}", + "plan": "Plan: {plan}", + "noData": "No quota data yet. The next probe will fetch it.", + "stale": "Quota data is stale and is ignored for scheduling.", + "reactivePaused": "Paused after an insufficient-balance response, {duration} left", + "credentialInvalid": "The quota endpoint rejected the API key.", + "lastError": "Last error: {error}", + "threshold": "New sessions need at least {percent}% remaining", + "updatedAgo": "Updated {duration} ago", + "refresh": "Refresh quota", + "refreshSuccess": "Quota refreshed", + "refreshFailed": "Failed to refresh quota", + "durationDaysHours": "{days}d {hours}h", + "durationHoursMinutes": "{hours}h {minutes}m", + "durationMinutes": "{minutes}m", + "probeTypes": { + "auto": "Auto", + "none": "None", + "kimi-coding": "Kimi Coding Plan", + "zhipu-coding": "Zhipu GLM Coding Plan", + "minimax-coding": "MiniMax Coding Plan", + "opencode-go": "OpenCode Go" + } + }, "clipboardUnavailable": "Clipboard access is blocked in this environment. Select and copy the key manually.", "confirmDeleteMessage": "Are you sure you want to delete provider \"{name}\"? This action cannot be undone.", "confirmDeleteTitle": "Confirm Delete Provider?", diff --git a/messages/ja/dashboard.json b/messages/ja/dashboard.json index 9538eb55f8..66566262b5 100644 --- a/messages/ja/dashboard.json +++ b/messages/ja/dashboard.json @@ -1018,6 +1018,7 @@ "providers": "プロバイダー管理", "auditLogs": "監査ログ", "documentation": "ドキュ", + "models": "モデル", "systemSettings": "設定", "feedback": "フィードバック", "login": "ログイン", @@ -2215,7 +2216,8 @@ "gh-action": "GitHub Action", "codex-cli-core": "CLI / TUI", "desktop": "Desktop", - "exec": "Exec" + "exec": "Exec", + "python-sdk": "Python SDK" }, "nSelected": "{count} 件選択" }, diff --git a/messages/ja/index.ts b/messages/ja/index.ts index 97aeb7c2f2..c7b0c9b4bb 100644 --- a/messages/ja/index.ts +++ b/messages/ja/index.ts @@ -8,6 +8,7 @@ import errors from "./errors.json"; import forms from "./forms.json"; import internal from "./internal.json"; import ipDetails from "./ipDetails.json"; +import modelCatalog from "./modelCatalog.json"; import myUsage from "./myUsage.json"; import notifications from "./notifications.json"; import providerChain from "./provider-chain.json"; @@ -29,6 +30,7 @@ export default { errors, forms, ipDetails, + modelCatalog, notifications, "provider-chain": providerChain, providers, diff --git a/messages/ja/modelCatalog.json b/messages/ja/modelCatalog.json new file mode 100644 index 0000000000..d5e7946bc5 --- /dev/null +++ b/messages/ja/modelCatalog.json @@ -0,0 +1,50 @@ +{ + "pageTitle": "モデルカタログ", + "pageDescription": "この API キーで利用できるモデル、各モデルが対応する呼び出しプロトコル、コンテキスト長・価格・機能の一覧です。", + "generatedAt": "{time} に生成。更新は最大で 1 分に 1 回です。", + "searchPlaceholder": "モデルを検索", + "tabs": { + "byModel": "モデル別", + "byProtocol": "プロトコル別" + }, + "columns": { + "model": "モデル", + "protocols": "プロトコル", + "contextWindow": "コンテキスト", + "maxOutput": "最大出力", + "inputPrice": "入力", + "outputPrice": "出力", + "cacheRead": "キャッシュ読み取り", + "cacheWrite": "キャッシュ書き込み", + "capabilities": "機能", + "modalities": "モダリティ", + "knowledgeCutoff": "知識カットオフ" + }, + "capabilities": { + "vision": "画像認識", + "functionCalling": "ツール", + "reasoning": "推論", + "pdfInput": "PDF", + "promptCaching": "プロンプトキャッシュ" + }, + "modalities": { + "input": "入力", + "output": "出力" + }, + "status": { + "deprecated": "非推奨" + }, + "priceUnit": "価格は 100 万トークンあたりの USD(基本料金)です。", + "noPriceData": "メタデータなし", + "empty": "一致するモデルはありません。", + "protocolEmpty": "このプロトコルで利用できるモデルはありません。", + "protocolModelsCount": "{count} モデル", + "machineReadable": { + "title": "エージェント・スクリプト向け", + "description": "API キーを Bearer トークンとして以下の URL を呼び出してください。既定は JSON で、?format=md を付けると LLM がそのまま読める Markdown を返します。", + "copy": "URL をコピー" + }, + "notes": { + "title": "管理者からのメモ" + } +} diff --git a/messages/ja/myUsage.json b/messages/ja/myUsage.json index 823b78b21b..7f1ce134d4 100644 --- a/messages/ja/myUsage.json +++ b/messages/ja/myUsage.json @@ -3,6 +3,7 @@ "title": "マイ利用状況", "welcome": "ようこそ、{name}さん", "documentation": "利用ドキュメント", + "modelCatalog": "モデルカタログ", "logout": "ログアウト", "keyLabel": "キー", "userLabel": "ユーザー", diff --git a/messages/ja/provider-chain.json b/messages/ja/provider-chain.json index eaa31b5752..12b3f116ed 100644 --- a/messages/ja/provider-chain.json +++ b/messages/ja/provider-chain.json @@ -82,18 +82,22 @@ "format_type_mismatch": "フォーマット不一致", "type_mismatch": "タイプ不一致", "model_not_allowed": "モデル不許可", + "prefix_mismatch": "プレフィックス不一致", "model_not_supported": "モデル非対応", "group_mismatch": "グループ不一致", "health_check_failed": "ヘルスチェック失敗", "endpoint_circuit_open": "エンドポイントサーキットオープン", "endpoint_disabled": "エンドポイント無効", - "client_restriction": "クライアント制限" + "client_restriction": "クライアント制限", + "quota_low": "上流クォータ残少", + "quota_exhausted": "上流クォータ切れ" }, "filterDetails": { "vendor_type_circuit_open": "ベンダータイプ一時サーキットブレイク", "circuit_open": "サーキットブレーカーオープン", "circuit_half_open": "サーキットブレーカーハーフオープン", "rate_limited": "レート制限", + "upstream_balance_exhausted": "上流の残高不足", "provider_client_restriction": "クライアント制限によりプロバイダーをスキップ", "session_reuse_client_restriction": "Session reuse rejected: client restriction", "blocklist_hit": "Blocked by pattern: {pattern}", diff --git a/messages/ja/settings/config.json b/messages/ja/settings/config.json index fb2f241427..0c28e91f6a 100644 --- a/messages/ja/settings/config.json +++ b/messages/ja/settings/config.json @@ -59,9 +59,12 @@ "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 のデバッグ詳細は減少または遅延する場合があります。", + "enableHighConcurrencyModeDesc": "有効にすると、高 RPM 時の CPU / IO 負荷を下げるため、Redis の一部デバッグスナップショットとリアルタイム Session 観測書き込みを停止します。クォータリースの DB 更新間隔は最低 30 秒、総利用上限のキャッシュは 15 分に延長され、可用性プロジェクションのアイドル時ポーリングも緩やかになります。転送、整流、fake 200 検知、課金、制限処理は維持されますが、Sessions のデバッグ詳細は減少または遅延し、クォータやダッシュボードの数値の反映も遅くなる場合があります。", "enableMemoryAdmission": "メモリ受付制御を有効化", "enableMemoryAdmissionDesc": "有効にすると、リクエスト本文とストリームの最初のコンテンツまでのバッファを、このマシンの空きメモリに基づく枠で受け付けます。枠が足りない場合はディスクに一時保存し、それでも足りない場合はローカル 429 を返してクライアントに再試行を促します。無効の場合、これらのデータはすべてメモリ上に保持され、待機もローカル 429 も発生しません。", + "agentCatalogNotes": "モデルカタログの補足ドキュメント", + "agentCatalogNotesDesc": "Markdown 形式。モデルカタログページ(/models)と LLM 向けエンドポイント(/v1/models/catalog)の末尾に追加され、すべてのユーザーに表示されます。エージェント向けのモデル選択の指針や利用制限の説明に使えます。空欄の場合は表示されません。", + "agentCatalogNotesPlaceholder": "例:\n## モデル選択の指針\n- コーディングには claude-sonnet-4-5 を優先\n- 非常に長いコンテキストには gemini-2.5-pro を使用", "enableResponseFixer": "レスポンス整流を有効化", "enableResponseFixerDesc": "上流応答の一般的な形式問題(エンコーディング、SSE、途切れた JSON)を自動修復します(既定で有効)。", "enableThinkingSignatureRectifier": "thinking 署名整流を有効化", @@ -126,6 +129,13 @@ "legacyHedgeMaxInFlightInvalid": "Legacy hedge の同時実行数は 1 から 4 の整数で指定してください。", "discoveryEnabled": "制限付き Provider Discovery を有効化", "discoveryEnabledDesc": "有効にすると、コールドスタートのストリーミングリクエストで複数 Provider を制限時間内に探索し、フォールバックを最大 1 つ保持します。既定はオフです。", + "upstreamQuotaSchedulingEnabled": "上流クォータによるスケジューリング", + "upstreamQuotaSchedulingEnabledDesc": "Kimi、Zhipu GLM、MiniMax、OpenCode Go の Coding Plan 残りクォータを定期的に取得します。残りクォータがしきい値を下回る Provider には新しいセッションを割り当てません。既存セッションはクォータが完全に尽きるまで同じ Provider を使い続けます。上流が 402 または残高不足を返した場合は、その Provider を即座に一時停止します。", + "upstreamQuotaThresholdPercent": "新規セッションに必要な最小残りクォータ(%)", + "upstreamQuotaThresholdPercentDesc": "有効なウィンドウ(5 時間、週次、月次)のいずれかで残りクォータがこの割合を下回る Provider には、新しいセッションを割り当てません。Provider ごとに上書きできます。", + "upstreamQuotaProbeIntervalMinutes": "クォータ取得間隔(分)", + "upstreamQuotaProbeIntervalMinutesDesc": "各 Provider のクォータを取得する頻度です。3 間隔より古いスナップショットは無視され、自動一時停止は 2 間隔続きます。", + "upstreamQuotaSettingsInvalid": "上流クォータのしきい値は 1-99、取得間隔は 1-1440 分である必要があります。", "discoveryConcurrency": "Discovery 初期並列数", "maxDiscoveryRounds": "Discovery 最大ラウンド数", "discoverySlaMs": "Discovery SLA(ミリ秒)", @@ -150,6 +160,7 @@ "description": "レート制限チェック時のリース更新間隔とスライス比率を設定します。リース機構はDBクエリの負荷を軽減しながら、レート制限の精度を維持します。", "dbRefreshInterval": "DB更新間隔(秒)", "dbRefreshIntervalDesc": "データベースからクォータ使用量を更新する間隔(1-300秒)", + "dbRefreshIntervalHighConcurrencyFloor": "高並列モードが有効です:DB 集計クエリを減らすため、実際の更新間隔は {seconds} 秒以上になります。", "leasePercent5h": "5時間ウィンドウリース比率", "leasePercent5hDesc": "各リーススライスの5時間制限に対する比率(0-1)", "leasePercentDaily": "日次ウィンドウリース比率", @@ -169,6 +180,8 @@ }, "affinityIgnoreClientSessionId": "クライアント Session ID を無視", "affinityIgnoreClientSessionIdDesc": "有効にすると、フィンガープリント可能なリクエストは最長プレフィックス親和性によるプロバイダー固定を強制します(クライアント Session ID バインディングをスキップ)。フィンガープリント不可能なリクエストは従来どおりセッション再利用を使用します。デフォルトで有効。", + "edgeExecutionEnabled": "Edge エグゼキューター", + "edgeExecutionEnabledDesc": "有効にすると、リモートの Edge エグゼキューターが条件を満たす /v1/messages リクエスト(Claude 形式のプロバイダー)の上流呼び出しを担当します。認証、レート制限、ルーティング、リトライ、課金は引き続きこのサービスで決定されます。CCH_EDGE_SHARED_SECRET の設定が必要です。", "streamGateMode": "ストリーム内容ゲート", "streamGateModeDesc": "通常リクエストの最初の有効なコンテンツフレームゲートを制御します。有効時はエラーフレームや空ストリームでプロバイダーを切り替え、シャドウモードは判定乖離のみを記録し、オフ時はそのまま転送します。シャドウモードとオフ時は Replay owner を含めて最初のバイトを即座に転送し、高並行モードはオフと同じ扱いになります。デフォルトで有効。", "streamGateModeOptions": { diff --git a/messages/ja/settings/notifications.json b/messages/ja/settings/notifications.json index 10c239446c..fc42f2c088 100644 --- a/messages/ja/settings/notifications.json +++ b/messages/ja/settings/notifications.json @@ -54,6 +54,15 @@ "windowMode": "ウィンドウ", "windowModeAuto": "自動" }, + "clientProblem": { + "countThreshold": "一般エラー件数の閾値", + "cyberCountThreshold": "Cyber risk 件数の閾値", + "cyberWindowMinutes": "Cyber risk の時間窓(分)", + "description": "クライアントがタイムアウト、サーバーエラー、または cyber risk に遭遇したときにまとめて通知します(499 は除外)。2 つの閾値は独立し、件数または時間窓の先に達した方で送信します。", + "enable": "クライアント障害アラートを有効にする", + "title": "クライアント障害アラート", + "windowMinutes": "一般エラーの時間窓(分)" + }, "dailyLeaderboard": { "description": "毎日定時でユーザー消費トップNランキングを送信", "enable": "日次ランキングを有効にする", @@ -89,7 +98,10 @@ "on": "オン", "legacyModeDescription": "現在は旧来の単一URL通知設定を使用しています。プッシュ先を作成するとマルチターゲットモードに切り替わります。", "legacyModeTitle": "互換モード", - "title": "通知マスタースイッチ" + "title": "通知マスタースイッチ", + "titlePrefix": "アラートタイトル接頭辞", + "titlePrefixHelp": "インスタンス識別用。タイトル先頭に [PROXY] のように付きます。空なら付与しません。Webhook はターゲットのプロキシが優先です。未設定なら OUTBOUND_PROXY_URL、それも無いときだけ PROXY/HTTPS_PROXY を使います。", + "titlePrefixPlaceholder": "PROXY" }, "targetDialog": { "createTitle": "プッシュ先を追加", diff --git a/messages/ja/settings/providers/batchEdit.json b/messages/ja/settings/providers/batchEdit.json index 63eaa167c9..f84f40934c 100644 --- a/messages/ja/settings/providers/batchEdit.json +++ b/messages/ja/settings/providers/batchEdit.json @@ -55,7 +55,8 @@ "modelRedirects": "モデルリダイレクト", "allowedModels": "許可モデル", "thinkingBudget": "思考バジェット", - "adaptiveThinking": "アダプティブ思考" + "adaptiveThinking": "アダプティブ思考", + "reasoningEffortRules": "条件付き推論強度ルール" }, "errors": { "nothingToApply": "適用する変更がありません。", diff --git a/messages/ja/settings/providers/form/matchTester.json b/messages/ja/settings/providers/form/matchTester.json index 96ed2bca26..11a4513679 100644 --- a/messages/ja/settings/providers/form/matchTester.json +++ b/messages/ja/settings/providers/form/matchTester.json @@ -15,5 +15,6 @@ "matchType": "一致方式", "source": "元モデルパターン", "target": "リダイレクト先", - "pattern": "許可パターン" + "pattern": "許可パターン", + "prefixStripped": "プロバイダープレフィックスを除去し、{model} でルールを照合しました" } diff --git a/messages/ja/settings/providers/form/sections.json b/messages/ja/settings/providers/form/sections.json index 1d665f9201..960873d37e 100644 --- a/messages/ja/settings/providers/form/sections.json +++ b/messages/ja/settings/providers/form/sections.json @@ -176,6 +176,38 @@ "otherLimits": "その他の制限", "windowReset": "ウィンドウのリセット方式" }, + "upstreamQuota": { + "title": "上流クォータ", + "desc": "Provider の Coding Plan 残りクォータを取得し、残りが少ない場合は新しいセッションを割り当てません。システム設定で上流クォータによるスケジューリングを有効にすると反映されます。", + "probeType": { + "label": "クォータ取得元", + "desc": "取得する Coding Plan クォータのエンドポイントを選択するか、この Provider の取得を無効にします。", + "autoResolved": "Provider の URL から自動判定:{type}", + "options": { + "auto": "URL から自動判定", + "none": "無効", + "kimi-coding": "Kimi Coding Plan", + "zhipu-coding": "Zhipu GLM Coding Plan", + "minimax-coding": "MiniMax Coding Plan", + "opencode-go": "OpenCode Go" + } + }, + "threshold": { + "label": "最小残りクォータ(%)", + "desc": "この Provider についてシステムのしきい値を上書きします。空欄の場合はシステム設定を使用します。", + "placeholder": "システム既定" + }, + "zhipuOrganization": { + "label": "Zhipu 組織 ID", + "desc": "チームプランのみ。bigmodel-organization ヘッダーとして送信します。", + "placeholder": "org-..." + }, + "zhipuProject": { + "label": "Zhipu プロジェクト ID", + "desc": "チームプランのみ。bigmodel-project ヘッダーとして送信します。", + "placeholder": "proj_..." + } + }, "routing": { "cacheTtl": { "desc": "プロンプトキャッシュのTTLを強制設定。cache_controlを含むリクエストにのみ適用されます。", @@ -205,6 +237,86 @@ "invalidTemplate": "ヘッダーテンプレートが無効です。対応: '{{header.Name}}'、'{{session.id}}'、'{{session.client_id}}'" } }, + "effortRules": { + "title": "条件付き推論努力ルール", + "description": "ルールは順番に評価されます。最初に一致したルールがオーバーライド努力を決定します。一致するルールがない場合、オーバーライドは適用されません。", + "noRules": "ルールが設定されていません。ルールを追加して条件付き努力オーバーライドを有効にします。", + "addRule": "ルールを追加", + "removeRule": "ルールを削除", + "moveUp": "上に移動", + "moveDown": "下に移動", + "ruleLabel": "ルール {number}", + "targetEffort": "ターゲット努力", + "selectTarget": "ターゲット努力を選択", + "originalModelCondition": "元のモデルが一致する場合...", + "executionModelCondition": "実行モデルが一致する場合...", + "removeCondition": "削除", + "addCondition": "追加", + "matchModes": { + "exact": "完全一致", + "prefix": "前方一致", + "suffix": "後方一致", + "contains": "部分一致", + "regex": "正規表現" + }, + "patternPlaceholder": "例: claude-opus-4-6", + "originalEffortCondition": "元の努力が一致する場合...", + "effortMode": { + "any": "すべて(条件を省略)", + "missing": "未設定(明示的な null)", + "specific": "特定の値" + }, + "originalEffortPlaceholder": "例: medium", + "maxRulesReached": "最大 {max} パスのルールに達しました", + "effortValues": { + "none": "none", + "minimal": "minimal", + "low": "low", + "medium": "medium", + "high": "high", + "xhigh": "xhigh", + "max": "max" + } + }, + "serviceTierRules": { + "title": "条件付き Service Tier ルール", + "description": "ルールは順番に評価されます。最初に一致したルールが上流の service_tier を決定します。一致するルールがない場合、クライアントの値をそのまま渡します。", + "noRules": "ルールが設定されていません。ルールを追加して条件付き Service Tier オーバーライドを有効にします。", + "addRule": "ルールを追加", + "removeRule": "ルールを削除", + "moveUp": "上に移動", + "moveDown": "下に移動", + "ruleLabel": "ルール {number}", + "targetServiceTier": "対象 Service Tier", + "selectTarget": "対象 Service Tier を選択", + "originalModelCondition": "元のモデルが一致する場合...", + "executionModelCondition": "実行モデルが一致する場合...", + "removeCondition": "削除", + "addCondition": "追加", + "matchModes": { + "exact": "完全一致", + "prefix": "前方一致", + "suffix": "後方一致", + "contains": "部分一致", + "regex": "正規表現" + }, + "patternPlaceholder": "例: gpt-5-codex", + "originalServiceTierCondition": "元の Service Tier が一致する場合...", + "serviceTierMode": { + "any": "すべて(条件を省略)", + "missing": "未設定(クライアント未送信)", + "specific": "特定の値" + }, + "originalServiceTierPlaceholder": "例: priority", + "maxRulesReached": "最大 {max} パスのルールに達しました", + "serviceTierValues": { + "auto": "auto(プロジェクト既定)", + "default": "default(標準)", + "flex": "flex(ベストエフォート)", + "priority": "priority(fast)", + "unset": "クリア(service_tier を削除)" + } + }, "codexOverrides": { "title": "Codex パラメータオーバーライド", "desc": "プロバイダーレベルで Codex (Responses API) リクエストパラメータをオーバーライド", @@ -227,7 +339,7 @@ } }, "serviceTier": { - "help": "Codex(Responses API)リクエストの OpenAI service_tier を制御します。priority は fast モードに相当します。「クライアントに従う」は service_tier を上書きせず、その他の値は強制上書きします。上流が未対応の tier ではエラーになる場合があります。", + "help": "Codex(Responses API)リクエストの OpenAI service_tier を制御します。有効にすると、元のモデル、実行モデル、クライアントの service_tier でルールを照合し、最初に一致したルールが適用されます。priority は fast モードに相当します。「クリア」はクライアントが priority を送信していても上流リクエストから service_tier を削除します。課金ソースが Requested Service Tier の場合、これらのリクエストは標準料金で課金されます。上流が未対応の tier ではエラーになる場合があります。", "label": "Service Tier オーバーライド", "options": { "auto": "auto(プロジェクト既定)", @@ -378,7 +490,8 @@ "gh-action": "GitHub Action", "codex-cli-core": "CLI / TUI", "desktop": "Desktop", - "exec": "Exec" + "exec": "Exec", + "python-sdk": "Python SDK" }, "nSelected": "{count} 件選択" }, @@ -392,6 +505,17 @@ "label": "セッション再利用をスキップ", "desc": "有効にすると、次回リクエストではこのプロバイダーの sticky binding を再利用せず、優先度とヘルスチェックをやり直します。安価な高優先度プロバイダーの復旧後に早く戻したい場合に適しています。" }, + "overwriteResponseModel": { + "label": "応答のモデル ID を上書き", + "desc": "有効にすると、クライアントへ返す model フィールドは常にユーザーがリクエストしたモデル ID になり、上流の実モデルやリダイレクト後のモデルは見えません。" + }, + "providerPrefix": { + "label": "プロバイダープレフィックス", + "desc": "任意。設定すると、このプレフィックス付きのモデル ID のみがこのプロバイダーに振り分けられます(例:プレフィックス openai の場合は openai/gpt-5)。上流へ転送する前にプレフィックスは除去されます。", + "placeholder": "例:openai", + "help": "末尾の / は 1 つの / に正規化されます。プレフィックスの照合は大文字小文字を区別しません。モデルリダイレクトと許可モデルのルールはプレフィックスを除いたモデル名で記述します。モデル一覧 API ではこのプロバイダーのモデルがプレフィックス付きで表示されます。", + "preview": "{prefix} で始まるモデルのみ一致します(例:{example})。上流への転送時に {prefix} は除去されます。" + }, "options": { "title": "オプション", "desc": "追加のプロバイダーオプションとオーバーライド" diff --git a/messages/ja/settings/providers/list.json b/messages/ja/settings/providers/list.json index 3a80c9c6e8..75b6a02c7d 100644 --- a/messages/ja/settings/providers/list.json +++ b/messages/ja/settings/providers/list.json @@ -2,7 +2,45 @@ "cancelButton": "キャンセル", "circuitBroken": "遮断中", "keyCircuitBroken": "キー遮断", + "providerPrefixBadge": "プロバイダープレフィックス:{prefix}", "endpointCircuitBroken": "エンドポイント遮断", + "upstreamQuota": { + "title": "上流クォータ", + "statusOk": "クォータ {percent}%", + "statusLow": "クォータ残少 {percent}%", + "statusExhausted": "クォータ切れ", + "statusUnknown": "クォータ不明", + "windows": { + "5h": "5 時間ウィンドウ", + "weekly": "週次", + "monthly": "月次", + "rolling": "ローリングウィンドウ" + }, + "used": "{percent}% 使用", + "resetsIn": "{duration} 後にリセット", + "plan": "プラン:{plan}", + "noData": "クォータデータはまだありません。次回の取得で更新されます。", + "stale": "クォータデータが古いため、スケジューリングでは無視されます。", + "reactivePaused": "残高不足の応答により一時停止中、残り {duration}", + "credentialInvalid": "クォータエンドポイントが API キーを拒否しました。", + "lastError": "直近のエラー:{error}", + "threshold": "新規セッションには {percent}% 以上の残りが必要です", + "updatedAgo": "{duration} 前に更新", + "refresh": "クォータを更新", + "refreshSuccess": "クォータを更新しました", + "refreshFailed": "クォータの更新に失敗しました", + "durationDaysHours": "{days}日 {hours}時間", + "durationHoursMinutes": "{hours}時間 {minutes}分", + "durationMinutes": "{minutes}分", + "probeTypes": { + "auto": "自動", + "none": "なし", + "kimi-coding": "Kimi Coding Plan", + "zhipu-coding": "Zhipu GLM Coding Plan", + "minimax-coding": "MiniMax Coding Plan", + "opencode-go": "OpenCode Go" + } + }, "clipboardUnavailable": "この環境ではクリップボードを使用できません。手動でコピーしてください。", "confirmDeleteMessage": "プロバイダー \"{name}\" を削除してもよろしいですか?この操作は元に戻せません。", "confirmDeleteTitle": "プロバイダーの削除を確認しますか?", diff --git a/messages/ru/dashboard.json b/messages/ru/dashboard.json index 84ea81eb90..c8dcbfd0fb 100644 --- a/messages/ru/dashboard.json +++ b/messages/ru/dashboard.json @@ -1021,6 +1021,7 @@ "providers": "Поставщики", "auditLogs": "Журнал аудита", "documentation": "Доки", + "models": "Модели", "systemSettings": "Настройки", "feedback": "Обратная связь", "login": "Вход", @@ -2263,7 +2264,8 @@ "gh-action": "GitHub Action", "codex-cli-core": "CLI / TUI", "desktop": "Desktop", - "exec": "Exec" + "exec": "Exec", + "python-sdk": "Python SDK" }, "nSelected": "Выбрано: {count}" }, diff --git a/messages/ru/index.ts b/messages/ru/index.ts index 97aeb7c2f2..c7b0c9b4bb 100644 --- a/messages/ru/index.ts +++ b/messages/ru/index.ts @@ -8,6 +8,7 @@ import errors from "./errors.json"; import forms from "./forms.json"; import internal from "./internal.json"; import ipDetails from "./ipDetails.json"; +import modelCatalog from "./modelCatalog.json"; import myUsage from "./myUsage.json"; import notifications from "./notifications.json"; import providerChain from "./provider-chain.json"; @@ -29,6 +30,7 @@ export default { errors, forms, ipDetails, + modelCatalog, notifications, "provider-chain": providerChain, providers, diff --git a/messages/ru/modelCatalog.json b/messages/ru/modelCatalog.json new file mode 100644 index 0000000000..9933df1d7e --- /dev/null +++ b/messages/ru/modelCatalog.json @@ -0,0 +1,50 @@ +{ + "pageTitle": "Каталог моделей", + "pageDescription": "Модели, доступные вашему API-ключу, протоколы, через которые их можно вызывать, а также лимиты, цены и возможности.", + "generatedAt": "Сформировано {time}. Обновляется не чаще раза в минуту.", + "searchPlaceholder": "Поиск моделей", + "tabs": { + "byModel": "По моделям", + "byProtocol": "По протоколам" + }, + "columns": { + "model": "Модель", + "protocols": "Протоколы", + "contextWindow": "Контекст", + "maxOutput": "Макс. вывод", + "inputPrice": "Ввод", + "outputPrice": "Вывод", + "cacheRead": "Чтение кэша", + "cacheWrite": "Запись кэша", + "capabilities": "Возможности", + "modalities": "Модальности", + "knowledgeCutoff": "Срез знаний" + }, + "capabilities": { + "vision": "Зрение", + "functionCalling": "Инструменты", + "reasoning": "Рассуждение", + "pdfInput": "PDF", + "promptCaching": "Кэш промптов" + }, + "modalities": { + "input": "Вход", + "output": "Выход" + }, + "status": { + "deprecated": "Устарела" + }, + "priceUnit": "Цены в USD за 1 млн токенов, базовый тариф.", + "noPriceData": "Нет метаданных", + "empty": "Подходящих моделей нет.", + "protocolEmpty": "Через этот протокол модели недоступны.", + "protocolModelsCount": "{count, plural, one {# модель} few {# модели} many {# моделей} other {# модели}}", + "machineReadable": { + "title": "Для агентов и скриптов", + "description": "Вызывайте эти URL с вашим API-ключом в виде Bearer-токена. По умолчанию возвращается JSON. Добавьте ?format=md, чтобы получить Markdown, который LLM читает напрямую.", + "copy": "Копировать URL" + }, + "notes": { + "title": "Заметки администратора" + } +} diff --git a/messages/ru/myUsage.json b/messages/ru/myUsage.json index 7d58f72620..3cfb073c00 100644 --- a/messages/ru/myUsage.json +++ b/messages/ru/myUsage.json @@ -3,6 +3,7 @@ "title": "Мои расходы", "welcome": "Добро пожаловать, {name}", "documentation": "Документация", + "modelCatalog": "Каталог моделей", "logout": "Выйти", "keyLabel": "Ключ", "userLabel": "Пользователь", diff --git a/messages/ru/provider-chain.json b/messages/ru/provider-chain.json index 08e432461e..55434bcdbe 100644 --- a/messages/ru/provider-chain.json +++ b/messages/ru/provider-chain.json @@ -82,18 +82,22 @@ "format_type_mismatch": "Несоответствие формата", "type_mismatch": "Несоответствие типа", "model_not_allowed": "Модель не разрешена", + "prefix_mismatch": "Префикс не совпадает", "model_not_supported": "Модель не поддерживается", "group_mismatch": "Несоответствие группы", "health_check_failed": "Проверка состояния не пройдена", "endpoint_circuit_open": "Автомат конечной точки открыт", "endpoint_disabled": "Эндпоинт отключен", - "client_restriction": "Ограничение клиента" + "client_restriction": "Ограничение клиента", + "quota_low": "Мало квоты провайдера", + "quota_exhausted": "Квота провайдера исчерпана" }, "filterDetails": { "vendor_type_circuit_open": "Временное размыкание типа поставщика", "circuit_open": "Размыкатель открыт", "circuit_half_open": "Размыкатель полуоткрыт", "rate_limited": "Ограничение скорости", + "upstream_balance_exhausted": "Недостаточно средств у провайдера", "provider_client_restriction": "Провайдер пропущен из-за ограничения клиента", "session_reuse_client_restriction": "Session reuse rejected: client restriction", "blocklist_hit": "Blocked by pattern: {pattern}", diff --git a/messages/ru/settings/config.json b/messages/ru/settings/config.json index 9d1ef0fcfe..0c42f5f1dd 100644 --- a/messages/ru/settings/config.json +++ b/messages/ru/settings/config.json @@ -59,9 +59,12 @@ "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 может уменьшиться или запаздывать.", + "enableHighConcurrencyModeDesc": "Если включено, CCH отключит часть Redis-снимков для отладки и записи real-time Session-наблюдения, чтобы снизить нагрузку на CPU и IO при высоком RPM. Лизинг квот обновляется из БД не чаще раза в 30 секунд, кэш общего лимита расходов живёт 15 минут, а опрос проекции доступности в простое замедляется. Пересылка, rectifier-логика, обнаружение fake 200, биллинг и лимиты сохраняются, но детализация отладки в Sessions может уменьшиться или запаздывать, а данные квот и дашбордов обновляются с большей задержкой.", "enableMemoryAdmission": "Включить контроль памяти", "enableMemoryAdmissionDesc": "Если включено, тела запросов и буфер потока до первого содержимого принимаются в пределах доступной памяти этой машины. При нехватке бюджета данные временно записываются на диск, а если и этого мало, CCH возвращает локальный 429 с просьбой повторить запрос позже. Если выключено, эти данные целиком хранятся в памяти, без очереди и без локального 429.", + "agentCatalogNotes": "Заметки каталога моделей для агентов", + "agentCatalogNotesDesc": "Markdown, который добавляется в конец страницы каталога моделей (/models) и LLM-ориентированного эндпоинта (/v1/models/catalog). Виден всем пользователям. Используйте его, чтобы подсказать агентам, какие модели выбирать и какие действуют ограничения. Оставьте пустым, чтобы скрыть.", + "agentCatalogNotesPlaceholder": "Например:\n## Выбор модели\n- Для программирования предпочитайте claude-sonnet-4-5\n- Для очень длинного контекста используйте gemini-2.5-pro", "enableResponseFixer": "Включить исправление ответов", "enableResponseFixerDesc": "Автоматически исправляет распространённые проблемы ответа у провайдеров (кодировка, SSE, обрезанный JSON). Включено по умолчанию.", "enableThinkingSignatureRectifier": "Включить исправление thinking-signature", @@ -126,6 +129,13 @@ "legacyHedgeMaxInFlightInvalid": "Параллельность legacy hedge должна быть целым числом от 1 до 4.", "discoveryEnabled": "Включить ограниченное обнаружение провайдеров", "discoveryEnabledDesc": "При включении потоковые запросы холодного старта проверяют несколько провайдеров в ограниченном окне и сохраняют не более одного резервного. По умолчанию выключено.", + "upstreamQuotaSchedulingEnabled": "Планирование по квоте провайдера", + "upstreamQuotaSchedulingEnabledDesc": "Периодически запрашивает остаток квоты Coding Plan у провайдеров Kimi, Zhipu GLM, MiniMax и OpenCode Go. Новые сессии не направляются к провайдерам, у которых остаток ниже порога; существующие сессии остаются на своём провайдере, пока квота не будет исчерпана полностью. Ответы 402 или «недостаточно средств» от провайдера сразу приостанавливают его.", + "upstreamQuotaThresholdPercent": "Минимальный остаток квоты для новых сессий (%)", + "upstreamQuotaThresholdPercentDesc": "Не направлять новые сессии к провайдерам, у которых остаток квоты в любом активном окне (5 ч, неделя или месяц) ниже этого процента. Можно переопределить для каждого провайдера.", + "upstreamQuotaProbeIntervalMinutes": "Интервал проверки квоты (минуты)", + "upstreamQuotaProbeIntervalMinutesDesc": "Как часто запрашивается квота каждого провайдера. Снимки старше 3 интервалов игнорируются, а автоматическая пауза длится 2 интервала.", + "upstreamQuotaSettingsInvalid": "Порог квоты должен быть от 1 до 99, а интервал проверки от 1 до 1440 минут.", "discoveryConcurrency": "Начальная параллельность Discovery", "maxDiscoveryRounds": "Максимальное число раундов Discovery", "discoverySlaMs": "SLA Discovery (миллисекунды)", @@ -150,6 +160,7 @@ "description": "Настройка интервала обновления аренды и процентов среза для проверки лимитов. Механизм аренды снижает нагрузку на БД, сохраняя точность лимитов.", "dbRefreshInterval": "Интервал обновления БД (секунды)", "dbRefreshIntervalDesc": "Интервал обновления использования квоты из базы данных (1-300 секунд)", + "dbRefreshIntervalHighConcurrencyFloor": "Режим высокой нагрузки включён: фактический интервал обновления не меньше {seconds} с, чтобы сократить агрегирующие запросы к БД.", "leasePercent5h": "Процент аренды 5-часового окна", "leasePercent5hDesc": "Процент 5-часового лимита для каждого среза аренды (0-1)", "leasePercentDaily": "Процент аренды дневного окна", @@ -169,6 +180,8 @@ }, "affinityIgnoreClientSessionId": "Игнорировать клиентский Session ID", "affinityIgnoreClientSessionIdDesc": "Если включено, запросы с отпечатком принудительно используют аффинность по самому длинному префиксу для закрепления за провайдером (пропуская привязку по клиентскому Session ID); запросы без отпечатка по-прежнему используют переиспользование сессии. По умолчанию включено.", + "edgeExecutionEnabled": "Edge-исполнитель", + "edgeExecutionEnabledDesc": "Если включено, удалённые Edge-исполнители могут выполнять вызовы к провайдерам для подходящих запросов /v1/messages (провайдеры формата Claude). Аутентификация, ограничение частоты, маршрутизация, повторы и тарификация по-прежнему решаются этим сервисом. Требуется CCH_EDGE_SHARED_SECRET.", "streamGateMode": "Шлюз потокового контента", "streamGateModeDesc": "Управляет шлюзом первого валидного кадра для обычных запросов: включенный режим переключает провайдера при ошибках или пустом потоке, теневой режим только фиксирует расхождения, а выключенный режим пересылает ответ напрямую. В теневом и выключенном режимах первый байт отправляется клиенту сразу, в том числе для replay owner; режим высокой конкурентности приравнивается к выключенному. По умолчанию включено.", "streamGateModeOptions": { diff --git a/messages/ru/settings/notifications.json b/messages/ru/settings/notifications.json index d656cbb340..4837d376fe 100644 --- a/messages/ru/settings/notifications.json +++ b/messages/ru/settings/notifications.json @@ -54,6 +54,15 @@ "windowMode": "Окно", "windowModeAuto": "Авто" }, + "clientProblem": { + "countThreshold": "Порог числа обычных ошибок", + "cyberCountThreshold": "Порог числа Cyber risk", + "cyberWindowMinutes": "Окно Cyber risk (минуты)", + "description": "Сводка таймаутов, ошибок сервера и cyber risk, видимых клиенту (без 499). Два независимых порога: число событий или окно времени — срабатывает то, что раньше.", + "enable": "Включить оповещения о клиентских сбоях", + "title": "Оповещения о клиентских сбоях", + "windowMinutes": "Окно обычных ошибок (минуты)" + }, "dailyLeaderboard": { "description": "Ежедневная отправка рейтинга топ N пользователей по потреблению", "enable": "Включить ежедневный рейтинг", @@ -89,7 +98,10 @@ "on": "Вкл", "legacyModeDescription": "Сейчас используется устаревшая схема уведомлений с одним URL. Создайте цель отправки, чтобы перейти на режим с несколькими целями.", "legacyModeTitle": "Режим совместимости", - "title": "Главный переключатель уведомлений" + "title": "Главный переключатель уведомлений", + "titlePrefix": "Префикс заголовка оповещения", + "titlePrefixHelp": "Чтобы отличить инстансы. Добавляется как [PROXY] к заголовку. Пустое значение — без префикса. Прокси цели webhook важнее; иначе OUTBOUND_PROXY_URL; PROXY/HTTPS_PROXY используется только если он не задан.", + "titlePrefixPlaceholder": "PROXY" }, "targetDialog": { "createTitle": "Добавить цель", diff --git a/messages/ru/settings/providers/batchEdit.json b/messages/ru/settings/providers/batchEdit.json index e8b1163c9d..7bc5d33d40 100644 --- a/messages/ru/settings/providers/batchEdit.json +++ b/messages/ru/settings/providers/batchEdit.json @@ -55,7 +55,8 @@ "modelRedirects": "Перенаправление моделей", "allowedModels": "Разрешенные модели", "thinkingBudget": "Бюджет размышлений", - "adaptiveThinking": "Адаптивное мышление" + "adaptiveThinking": "Адаптивное мышление", + "reasoningEffortRules": "Условные правила усилий рассуждений" }, "errors": { "nothingToApply": "Нет изменений для применения.", diff --git a/messages/ru/settings/providers/form/matchTester.json b/messages/ru/settings/providers/form/matchTester.json index 07b356996c..400b753691 100644 --- a/messages/ru/settings/providers/form/matchTester.json +++ b/messages/ru/settings/providers/form/matchTester.json @@ -15,5 +15,6 @@ "matchType": "Тип совпадения", "source": "Исходный шаблон", "target": "Целевая модель", - "pattern": "Шаблон allowlist" + "pattern": "Шаблон allowlist", + "prefixStripped": "Префикс провайдера удалён. Правила сопоставляются с {model}" } diff --git a/messages/ru/settings/providers/form/sections.json b/messages/ru/settings/providers/form/sections.json index 89217b35f5..188d4e991c 100644 --- a/messages/ru/settings/providers/form/sections.json +++ b/messages/ru/settings/providers/form/sections.json @@ -176,6 +176,38 @@ "otherLimits": "Другие ограничения", "windowReset": "Поведение сброса окон" }, + "upstreamQuota": { + "title": "Квота провайдера", + "desc": "Запрашивает остаток квоты Coding Plan у провайдера и перестаёт направлять к нему новые сессии, когда остаток мал. Работает, если в системных настройках включено планирование по квоте провайдера.", + "probeType": { + "label": "Источник квоты", + "desc": "Выберите, какой эндпоинт квоты Coding Plan опрашивать, или отключите проверку для этого провайдера.", + "autoResolved": "Определено автоматически по URL провайдера: {type}", + "options": { + "auto": "Определять по URL", + "none": "Отключено", + "kimi-coding": "Kimi Coding Plan", + "zhipu-coding": "Zhipu GLM Coding Plan", + "minimax-coding": "MiniMax Coding Plan", + "opencode-go": "OpenCode Go" + } + }, + "threshold": { + "label": "Минимальный остаток квоты (%)", + "desc": "Переопределяет системный порог для этого провайдера. Оставьте пустым, чтобы использовать системную настройку.", + "placeholder": "Системное значение" + }, + "zhipuOrganization": { + "label": "ID организации Zhipu", + "desc": "Только для командного плана. Передаётся в заголовке bigmodel-organization.", + "placeholder": "org-..." + }, + "zhipuProject": { + "label": "ID проекта Zhipu", + "desc": "Только для командного плана. Передаётся в заголовке bigmodel-project.", + "placeholder": "proj_..." + } + }, "routing": { "cacheTtl": { "desc": "Принудительно задать TTL кэша промптов; влияет только на запросы с cache_control.", @@ -205,6 +237,86 @@ "invalidTemplate": "Некорректный шаблон заголовка. Поддерживаются '{{header.Name}}', '{{session.id}}', '{{session.client_id}}'" } }, + "effortRules": { + "title": "Условные правила усилий рассуждений", + "description": "Правила проверяются по порядку. Первое подходящее правило определяет целевое усилие. Если ни одно правило не подходит, переопределение не применяется.", + "noRules": "Правила не настроены. Добавьте правило для включения условных переопределений усилий.", + "addRule": "Добавить правило", + "removeRule": "Удалить правило", + "moveUp": "Переместить вверх", + "moveDown": "Переместить вниз", + "ruleLabel": "Правило {number}", + "targetEffort": "Целевое усилие", + "selectTarget": "Выберите целевое усилие", + "originalModelCondition": "Когда исходная модель...", + "executionModelCondition": "Когда модель выполнения...", + "removeCondition": "убрать", + "addCondition": "добавить", + "matchModes": { + "exact": "точное совпадение", + "prefix": "начало строки", + "suffix": "конец строки", + "contains": "содержит", + "regex": "регулярное выражение" + }, + "patternPlaceholder": "например, claude-opus-4-6", + "originalEffortCondition": "Когда исходное усилие...", + "effortMode": { + "any": "Любое (опустить условие)", + "missing": "Отсутствует (явный null)", + "specific": "Конкретное значение" + }, + "originalEffortPlaceholder": "например, medium", + "maxRulesReached": "Достигнут максимум из {max} правил", + "effortValues": { + "none": "none", + "minimal": "minimal", + "low": "low", + "medium": "medium", + "high": "high", + "xhigh": "xhigh", + "max": "max" + } + }, + "serviceTierRules": { + "title": "Условные правила Service Tier", + "description": "Правила проверяются по порядку. Первое подходящее правило определяет service_tier для upstream. Если ни одно правило не подходит, значение клиента передается без изменений.", + "noRules": "Правила не настроены. Добавьте правило для включения условных переопределений Service Tier.", + "addRule": "Добавить правило", + "removeRule": "Удалить правило", + "moveUp": "Переместить вверх", + "moveDown": "Переместить вниз", + "ruleLabel": "Правило {number}", + "targetServiceTier": "Целевой Service Tier", + "selectTarget": "Выберите целевой Service Tier", + "originalModelCondition": "Когда исходная модель...", + "executionModelCondition": "Когда модель выполнения...", + "removeCondition": "убрать", + "addCondition": "добавить", + "matchModes": { + "exact": "точное совпадение", + "prefix": "начало строки", + "suffix": "конец строки", + "contains": "содержит", + "regex": "регулярное выражение" + }, + "patternPlaceholder": "например, gpt-5-codex", + "originalServiceTierCondition": "Когда исходный Service Tier...", + "serviceTierMode": { + "any": "Любой (опустить условие)", + "missing": "Отсутствует (клиент не передал)", + "specific": "Конкретное значение" + }, + "originalServiceTierPlaceholder": "например, priority", + "maxRulesReached": "Достигнут максимум из {max} правил", + "serviceTierValues": { + "auto": "auto (по умолчанию проекта)", + "default": "default (стандартный)", + "flex": "flex (best-effort)", + "priority": "priority (fast)", + "unset": "Сбросить (удалить service_tier)" + } + }, "codexOverrides": { "title": "Переопределение параметров Codex", "desc": "Переопределение параметров запросов Codex (Responses API) на уровне провайдера", @@ -227,7 +339,7 @@ } }, "serviceTier": { - "help": "Управляет OpenAI service_tier для запросов Codex (Responses API). Значение priority соответствует fast mode. Режим \"следовать клиенту\" не переписывает service_tier, остальные значения принудительно его переопределяют. Неподдерживаемый tier может вызвать ошибку upstream.", + "help": "Управляет OpenAI service_tier для запросов Codex (Responses API). При включении правила сопоставляются по исходной модели, модели выполнения и service_tier клиента; применяется первое совпадение. Значение priority соответствует fast mode. \"Сбросить\" удаляет service_tier из запроса к upstream, даже если клиент передал priority; при источнике тарификации Requested Service Tier такие запросы тарифицируются по стандартной ставке. Неподдерживаемый tier может вызвать ошибку upstream.", "label": "Переопределение Service Tier", "options": { "auto": "auto (по умолчанию проекта)", @@ -378,7 +490,8 @@ "gh-action": "GitHub Action", "codex-cli-core": "CLI / TUI", "desktop": "Desktop", - "exec": "Exec" + "exec": "Exec", + "python-sdk": "Python SDK" }, "nSelected": "Выбрано: {count}" }, @@ -392,6 +505,17 @@ "label": "Не использовать повторно сеанс", "desc": "Если включено, следующий запрос проигнорирует sticky-привязку к этому провайдеру и заново выполнит проверку приоритета и здоровья. Подходит, когда после восстановления нужно как можно скорее вернуться на более дешёвый провайдер с высоким приоритетом." }, + "overwriteResponseModel": { + "label": "Подменять model ID в ответе", + "desc": "Если включено, в ответе клиенту поле model всегда будет запрошенным идентификатором модели, а не фактической или перенаправленной моделью апстрима." + }, + "providerPrefix": { + "label": "Префикс провайдера", + "desc": "Необязательно. Если задан, к этому провайдеру попадают только ID моделей с этим префиксом, например для префикса openai нужен openai/gpt-5. Перед отправкой upstream префикс удаляется.", + "placeholder": "например: openai", + "help": "Завершающие символы / нормализуются до одного /. Сравнение префикса не зависит от регистра. Правила redirect и списка разрешённых моделей пишутся для имени модели без префикса. Эндпоинты списка моделей показывают модели этого провайдера с префиксом.", + "preview": "Совпадают только модели, начинающиеся с {prefix}, например {example}. При отправке upstream {prefix} удаляется." + }, "options": { "title": "Параметры", "desc": "Дополнительные параметры и переопределения провайдера" diff --git a/messages/ru/settings/providers/list.json b/messages/ru/settings/providers/list.json index 1265c8bd59..119756b4c7 100644 --- a/messages/ru/settings/providers/list.json +++ b/messages/ru/settings/providers/list.json @@ -2,7 +2,45 @@ "cancelButton": "Отмена", "circuitBroken": "Разорвано", "keyCircuitBroken": "Разрыв ключа", + "providerPrefixBadge": "Префикс провайдера: {prefix}", "endpointCircuitBroken": "Разрыв endpoint", + "upstreamQuota": { + "title": "Квота провайдера", + "statusOk": "Квота {percent}%", + "statusLow": "Мало квоты {percent}%", + "statusExhausted": "Квота исчерпана", + "statusUnknown": "Квота неизвестна", + "windows": { + "5h": "Окно 5 ч", + "weekly": "Неделя", + "monthly": "Месяц", + "rolling": "Скользящее окно" + }, + "used": "Использовано {percent}%", + "resetsIn": "Сброс через {duration}", + "plan": "План: {plan}", + "noData": "Данных о квоте пока нет. Они появятся после следующей проверки.", + "stale": "Данные о квоте устарели и не учитываются при планировании.", + "reactivePaused": "Приостановлен из-за ответа о нехватке средств, осталось {duration}", + "credentialInvalid": "Эндпоинт квоты отклонил API-ключ.", + "lastError": "Последняя ошибка: {error}", + "threshold": "Для новых сессий нужно не менее {percent}% остатка", + "updatedAgo": "Обновлено {duration} назад", + "refresh": "Обновить квоту", + "refreshSuccess": "Квота обновлена", + "refreshFailed": "Не удалось обновить квоту", + "durationDaysHours": "{days} д {hours} ч", + "durationHoursMinutes": "{hours} ч {minutes} мин", + "durationMinutes": "{minutes} мин", + "probeTypes": { + "auto": "Авто", + "none": "Нет", + "kimi-coding": "Kimi Coding Plan", + "zhipu-coding": "Zhipu GLM Coding Plan", + "minimax-coding": "MiniMax Coding Plan", + "opencode-go": "OpenCode Go" + } + }, "clipboardUnavailable": "Буфер обмена недоступен в этой среде. Скопируйте ключ вручную.", "confirmDeleteMessage": "Вы уверены, что хотите удалить провайдера \"{name}\"? Это действие нельзя отменить.", "confirmDeleteTitle": "Подтвердить удаление провайдера?", diff --git a/messages/zh-CN/dashboard.json b/messages/zh-CN/dashboard.json index d8f17dd50d..07a891f0f5 100644 --- a/messages/zh-CN/dashboard.json +++ b/messages/zh-CN/dashboard.json @@ -1020,6 +1020,7 @@ "providers": "供应商管理", "auditLogs": "审计日志", "documentation": "文档", + "models": "模型目录", "systemSettings": "系统设置", "feedback": "反馈问题", "login": "登录", @@ -2238,7 +2239,8 @@ "gh-action": "GitHub Action", "codex-cli-core": "CLI / TUI", "desktop": "Desktop", - "exec": "Exec" + "exec": "Exec", + "python-sdk": "Python SDK" }, "nSelected": "已选 {count} 项" }, diff --git a/messages/zh-CN/index.ts b/messages/zh-CN/index.ts index 97aeb7c2f2..c7b0c9b4bb 100644 --- a/messages/zh-CN/index.ts +++ b/messages/zh-CN/index.ts @@ -8,6 +8,7 @@ import errors from "./errors.json"; import forms from "./forms.json"; import internal from "./internal.json"; import ipDetails from "./ipDetails.json"; +import modelCatalog from "./modelCatalog.json"; import myUsage from "./myUsage.json"; import notifications from "./notifications.json"; import providerChain from "./provider-chain.json"; @@ -29,6 +30,7 @@ export default { errors, forms, ipDetails, + modelCatalog, notifications, "provider-chain": providerChain, providers, diff --git a/messages/zh-CN/modelCatalog.json b/messages/zh-CN/modelCatalog.json new file mode 100644 index 0000000000..f31fbe248a --- /dev/null +++ b/messages/zh-CN/modelCatalog.json @@ -0,0 +1,50 @@ +{ + "pageTitle": "模型目录", + "pageDescription": "当前 API Key 可用的模型、每个模型支持的调用协议,以及上下文、价格与能力信息。", + "generatedAt": "生成于 {time},最多每分钟刷新一次。", + "searchPlaceholder": "搜索模型", + "tabs": { + "byModel": "按模型", + "byProtocol": "按协议" + }, + "columns": { + "model": "模型", + "protocols": "协议", + "contextWindow": "上下文", + "maxOutput": "最大输出", + "inputPrice": "输入", + "outputPrice": "输出", + "cacheRead": "缓存读取", + "cacheWrite": "缓存写入", + "capabilities": "能力", + "modalities": "模态", + "knowledgeCutoff": "知识截止" + }, + "capabilities": { + "vision": "视觉", + "functionCalling": "工具调用", + "reasoning": "推理", + "pdfInput": "PDF", + "promptCaching": "提示缓存" + }, + "modalities": { + "input": "输入", + "output": "输出" + }, + "status": { + "deprecated": "已弃用" + }, + "priceUnit": "价格单位为 USD / 每百万 tokens(基础档位)。", + "noPriceData": "暂无元数据", + "empty": "没有匹配的模型。", + "protocolEmpty": "该协议下没有可用模型。", + "protocolModelsCount": "{count} 个模型", + "machineReadable": { + "title": "供 agent 与脚本使用", + "description": "使用你的 API Key 作为 Bearer token 请求以下地址。默认返回 JSON,追加 ?format=md 可获得 LLM 可直接阅读的 Markdown。", + "copy": "复制地址" + }, + "notes": { + "title": "管理员说明" + } +} diff --git a/messages/zh-CN/myUsage.json b/messages/zh-CN/myUsage.json index 472c2318f9..200b7512ad 100644 --- a/messages/zh-CN/myUsage.json +++ b/messages/zh-CN/myUsage.json @@ -3,6 +3,7 @@ "title": "我的用量", "welcome": "欢迎,{name}", "documentation": "使用文档", + "modelCatalog": "模型目录", "logout": "退出登录", "keyLabel": "密钥", "userLabel": "用户", diff --git a/messages/zh-CN/provider-chain.json b/messages/zh-CN/provider-chain.json index aceea0a507..9cee913e42 100644 --- a/messages/zh-CN/provider-chain.json +++ b/messages/zh-CN/provider-chain.json @@ -82,18 +82,22 @@ "format_type_mismatch": "请求格式不兼容", "type_mismatch": "类型不匹配", "model_not_allowed": "模型不允许", + "prefix_mismatch": "前缀不匹配", "model_not_supported": "不支持该模型", "group_mismatch": "分组不匹配", "health_check_failed": "健康检查失败", "endpoint_circuit_open": "端点已熔断", "endpoint_disabled": "端点已禁用", - "client_restriction": "客户端限制" + "client_restriction": "客户端限制", + "quota_low": "上游额度不足", + "quota_exhausted": "上游额度耗尽" }, "filterDetails": { "vendor_type_circuit_open": "供应商类型临时熔断", "circuit_open": "熔断器打开", "circuit_half_open": "熔断器半开", "rate_limited": "速率限制", + "upstream_balance_exhausted": "上游余额不足", "provider_client_restriction": "供应商因客户端限制被跳过", "session_reuse_client_restriction": "Session reuse rejected: client restriction", "blocklist_hit": "Blocked by pattern: {pattern}", diff --git a/messages/zh-CN/settings/config.json b/messages/zh-CN/settings/config.json index a51f53c59d..dfa9cb0a18 100644 --- a/messages/zh-CN/settings/config.json +++ b/messages/zh-CN/settings/config.json @@ -53,6 +53,13 @@ "legacyHedgeMaxInFlightInvalid": "Legacy hedge 并发数必须是 1 到 4 之间的整数。", "discoveryEnabled": "启用有界供应商 Discovery", "discoveryEnabledDesc": "启用后,冷启动流式请求会在限定窗口内探测多个供应商,并且最多保留一个保底请求。默认关闭。", + "upstreamQuotaSchedulingEnabled": "上游额度调度", + "upstreamQuotaSchedulingEnabledDesc": "定期查询 Kimi、智谱 GLM、MiniMax、OpenCode Go 供应商的 Coding Plan 剩余额度。新会话不会调度到剩余额度低于阈值的供应商;已有会话继续使用原供应商,直到额度彻底耗尽。上游返回 402 或余额不足时立即暂停该供应商。", + "upstreamQuotaThresholdPercent": "新会话最低剩余额度(%)", + "upstreamQuotaThresholdPercentDesc": "任一有效窗口(5 小时、每周或每月)的剩余额度低于该百分比时,不再向该供应商调度新会话。可在供应商中单独覆盖。", + "upstreamQuotaProbeIntervalMinutes": "额度探测间隔(分钟)", + "upstreamQuotaProbeIntervalMinutesDesc": "每个供应商的额度查询频率。超过 3 个间隔的快照视为过期,被动暂停持续 2 个间隔。", + "upstreamQuotaSettingsInvalid": "上游额度阈值须为 1-99,探测间隔须为 1-1440 分钟。", "discoveryConcurrency": "Discovery 首轮并发数", "maxDiscoveryRounds": "Discovery 最大轮数", "discoverySlaMs": "Discovery SLA(毫秒)", @@ -74,9 +81,12 @@ "enableOpenaiResponsesWebsocket": "启用 OpenAI Responses WebSocket", "enableOpenaiResponsesWebsocketDesc": "启用后,当客户端以 WebSocket 连接 /v1/responses 且选中 Codex 类型供应商时,CCH 会尝试与上游建立 WebSocket。若上游不支持或握手失败,将优雅降级到普通 HTTP Responses,客户端 WebSocket 保持打开;降级不计入熔断。非 WebSocket 客户端与非 Codex 供应商行为不变。", "enableHighConcurrencyMode": "启用高并发模式", - "enableHighConcurrencyModeDesc": "开启后,将关闭部分 Redis 调试快照与实时会话观测写入,以降低高并发下的 CPU 与 IO 开销。不会影响转发、整流、fake 200 检测、计费与限额,但 Sessions 调试详情会减少或延后。", + "enableHighConcurrencyModeDesc": "开启后,将关闭部分 Redis 调试快照与实时会话观测写入,以降低高并发下的 CPU 与 IO 开销;配额租约的数据库刷新间隔至少为 30 秒,总消费限额缓存延长至 15 分钟,可用性投影空闲轮询放缓。不会影响转发、整流、fake 200 检测、计费与限额,但 Sessions 调试详情会减少或延后,配额与看板数据的更新会有更长延迟。", "enableMemoryAdmission": "启用内存准入", "enableMemoryAdmissionDesc": "开启后,请求正文与流式首内容缓冲按本机可用内存分配额度,额度不足时暂存到磁盘,仍然不足时返回本地 429 并提示客户端稍后重试。关闭时这些数据全部保留在内存中,不排队,也不返回本地 429。", + "agentCatalogNotes": "模型目录附加文档", + "agentCatalogNotesDesc": "Markdown 格式,附加在模型目录页面(/models)与 LLM 可读接口(/v1/models/catalog)的末尾,所有用户可见。可用于向 agent 说明模型选择建议、使用限制等。留空则不显示。", + "agentCatalogNotesPlaceholder": "例如:\n## 模型选择建议\n- 编码任务优先使用 claude-sonnet-4-5\n- 超长上下文请使用 gemini-2.5-pro", "interceptAnthropicWarmupRequests": "拦截 Warmup 请求(Anthropic)", "interceptAnthropicWarmupRequestsDesc": "开启后,识别到 Claude Code 的 Warmup 探测请求将由 CCH 直接抢答短响应,避免访问上游供应商;该请求会记录在日志中,但不计费、不限流、不计入统计。", "enableThinkingSignatureRectifier": "启用 thinking 签名整流器", @@ -110,6 +120,8 @@ "cacheEffectivenessEnabledDesc": "模拟最长前缀匹配的缓存命中率(理论 vs 实际),仅用于观测,不影响路由。保存前跟随环境变量 ENABLE_CACHE_EFFECTIVENESS,默认开启。", "affinityIgnoreClientSessionId": "忽略客户端 Session ID", "affinityIgnoreClientSessionIdDesc": "开启后,可指纹化的请求强制使用最长前缀亲和做供应商粘性(跳过客户端 Session ID 绑定);不可指纹化的请求仍走会话复用。默认开启。", + "edgeExecutionEnabled": "Edge 执行器", + "edgeExecutionEnabledDesc": "开启后,远端 Edge 执行器可接管符合条件的 /v1/messages 请求(Claude 格式供应商)的上游调用;鉴权、限流、选路、重试与计费仍由本服务决策。需要配置 CCH_EDGE_SHARED_SECRET。", "fakeStreaming": { "title": "Fake 流式输出白名单", "description": "针对图像 / 视频等长耗时同步生成场景(容易超过 Cloudflare 120 秒无响应体超时),CCH 会先返回 SSE 心跳保活,并在内部串行调用上游供应商,仅在最终拿到非空结果时回写最终响应。命中白名单的模型会启用 fake streaming;列表为空表示完全禁用。", @@ -180,6 +192,7 @@ "description": "配置限额检查时的租约刷新间隔和切片比例。租约机制用于减少 DB 查询压力,同时保持限额精度。", "dbRefreshInterval": "DB 刷新间隔(秒)", "dbRefreshIntervalDesc": "从数据库刷新配额使用量的间隔时间(1-300 秒)", + "dbRefreshIntervalHighConcurrencyFloor": "高并发模式已开启:实际刷新间隔不低于 {seconds} 秒,以减少数据库聚合查询。", "leasePercent5h": "5 小时窗口租约比例", "leasePercent5hDesc": "每次租约切片占 5 小时限额的比例(0-1)", "leasePercentDaily": "每日窗口租约比例", diff --git a/messages/zh-CN/settings/notifications.json b/messages/zh-CN/settings/notifications.json index 9d7bcb2c8c..4cf4c62b98 100644 --- a/messages/zh-CN/settings/notifications.json +++ b/messages/zh-CN/settings/notifications.json @@ -8,7 +8,10 @@ "on": "已开启", "off": "已关闭", "legacyModeTitle": "兼容模式", - "legacyModeDescription": "当前使用旧版单 URL 推送配置。创建推送目标后将自动切换到多目标模式。" + "legacyModeDescription": "当前使用旧版单 URL 推送配置。创建推送目标后将自动切换到多目标模式。", + "titlePrefix": "告警标题前缀", + "titlePrefixPlaceholder": "PROXY", + "titlePrefixHelp": "用于区分实例,发送时加在标题前,例如 [PROXY]。留空则不加前缀。Webhook 目标填写了代理时用目标代理;否则用 OUTBOUND_PROXY_URL;都没有时才用 PROXY/HTTPS_PROXY。" }, "targets": { "title": "推送目标", @@ -146,6 +149,15 @@ "minEligibleTokens": "最小可命中 Tokens", "topN": "Top N" }, + "clientProblem": { + "title": "客户端故障告警", + "description": "客户端遇到超时、服务端错误或 cyber risk 时汇总推送(不含 499)。两组阈值独立:累计条数或时间窗口,先到先发。", + "enable": "启用客户端故障告警", + "countThreshold": "一般错误条数阈值", + "windowMinutes": "一般错误时间窗口(分钟)", + "cyberCountThreshold": "Cyber risk 条数阈值", + "cyberWindowMinutes": "Cyber risk 时间窗口(分钟)" + }, "form": { "save": "保存设置", "saving": "保存中...", diff --git a/messages/zh-CN/settings/providers/batchEdit.json b/messages/zh-CN/settings/providers/batchEdit.json index 781b4438ec..76c35a28fd 100644 --- a/messages/zh-CN/settings/providers/batchEdit.json +++ b/messages/zh-CN/settings/providers/batchEdit.json @@ -55,7 +55,8 @@ "modelRedirects": "模型重定向", "allowedModels": "允许的模型", "thinkingBudget": "思考预算", - "adaptiveThinking": "自适应思考" + "adaptiveThinking": "自适应思考", + "reasoningEffortRules": "条件推理强度规则" }, "errors": { "nothingToApply": "没有可应用的变更。", diff --git a/messages/zh-CN/settings/providers/form/matchTester.json b/messages/zh-CN/settings/providers/form/matchTester.json index 484e42697c..a6a041d821 100644 --- a/messages/zh-CN/settings/providers/form/matchTester.json +++ b/messages/zh-CN/settings/providers/form/matchTester.json @@ -15,5 +15,6 @@ "matchType": "匹配方式", "source": "源模型模式", "target": "重定向目标", - "pattern": "白名单模式" + "pattern": "白名单模式", + "prefixStripped": "已去掉供应商前缀,规则按 {model} 匹配" } diff --git a/messages/zh-CN/settings/providers/form/sections.json b/messages/zh-CN/settings/providers/form/sections.json index 85230f7f45..ab8f251878 100644 --- a/messages/zh-CN/settings/providers/form/sections.json +++ b/messages/zh-CN/settings/providers/form/sections.json @@ -44,6 +44,17 @@ "label": "不遵循会话复用", "desc": "开启后,下次请求不会继续复用当前供应商的 sticky 绑定,而是重新走优先级与健康检查。适合高优先级便宜渠道恢复后尽快切回的场景。" }, + "overwriteResponseModel": { + "label": "复写响应模型 ID", + "desc": "开启后,返回给客户端的 model 字段始终是用户请求的模型 ID,而不是上游实际返回或重定向后的模型。" + }, + "providerPrefix": { + "label": "供应商前缀", + "desc": "可选。配置后只有带此前缀的模型 ID 才能命中此供应商,例如前缀 openai 时需请求 openai/gpt-5;转发上游前会自动去掉前缀。", + "placeholder": "例如:openai", + "help": "末尾的 / 会被自动规范化为单个 /。前缀匹配不区分大小写;模型重定向与模型白名单规则按去掉前缀后的模型名编写;模型列表接口会以带前缀的 ID 展示此供应商的模型。", + "preview": "仅匹配以 {prefix} 开头的模型,例如 {example};转发上游时会去掉 {prefix}。" + }, "options": { "title": "选项", "desc": "附加供应商选项和覆写设置" @@ -89,7 +100,8 @@ "gh-action": "GitHub Action", "codex-cli-core": "CLI / TUI", "desktop": "Desktop", - "exec": "Exec" + "exec": "Exec", + "python-sdk": "Python SDK" }, "nSelected": "已选 {count} 项" }, @@ -161,6 +173,86 @@ "invalidTemplate": "动态模板语法无效。支持 '{{header.Name}}'、'{{session.id}}'、'{{session.client_id}}'" } }, + "effortRules": { + "title": "条件推理强度规则", + "description": "规则按顺序评估,第一条匹配的规则决定覆写强度。如果没有匹配的规则,则不应用覆写。", + "noRules": "未配置规则。添加规则以启用条件强度覆写。", + "addRule": "添加规则", + "removeRule": "删除规则", + "moveUp": "上移", + "moveDown": "下移", + "ruleLabel": "规则 {number}", + "targetEffort": "目标强度", + "selectTarget": "选择目标强度", + "originalModelCondition": "当原始模型匹配时...", + "executionModelCondition": "当执行模型匹配时...", + "removeCondition": "移除", + "addCondition": "添加", + "matchModes": { + "exact": "完全匹配", + "prefix": "前缀匹配", + "suffix": "后缀匹配", + "contains": "包含匹配", + "regex": "正则表达式" + }, + "patternPlaceholder": "例如 claude-opus-4-6", + "originalEffortCondition": "当原始强度匹配时...", + "effortMode": { + "any": "任意(省略条件)", + "missing": "缺失(显式 null)", + "specific": "特定值" + }, + "originalEffortPlaceholder": "例如 medium", + "maxRulesReached": "已达到最大 {max} 条规则", + "effortValues": { + "none": "none", + "minimal": "minimal", + "low": "low", + "medium": "medium", + "high": "high", + "xhigh": "xhigh", + "max": "max" + } + }, + "serviceTierRules": { + "title": "条件 Service Tier 规则", + "description": "规则按顺序评估,第一条匹配的规则决定上游 service_tier。如果没有匹配的规则,则原样透传客户端的值。", + "noRules": "未配置规则。添加规则以启用条件 Service Tier 覆写。", + "addRule": "添加规则", + "removeRule": "删除规则", + "moveUp": "上移", + "moveDown": "下移", + "ruleLabel": "规则 {number}", + "targetServiceTier": "目标 Service Tier", + "selectTarget": "选择目标 Service Tier", + "originalModelCondition": "当原始模型匹配时...", + "executionModelCondition": "当执行模型匹配时...", + "removeCondition": "移除", + "addCondition": "添加", + "matchModes": { + "exact": "完全匹配", + "prefix": "前缀匹配", + "suffix": "后缀匹配", + "contains": "包含匹配", + "regex": "正则表达式" + }, + "patternPlaceholder": "例如 gpt-5-codex", + "originalServiceTierCondition": "当原始 Service Tier 匹配时...", + "serviceTierMode": { + "any": "任意(省略条件)", + "missing": "缺失(客户端未传)", + "specific": "特定值" + }, + "originalServiceTierPlaceholder": "例如 priority", + "maxRulesReached": "已达到最大 {max} 条规则", + "serviceTierValues": { + "auto": "auto(项目默认)", + "default": "default(标准档)", + "flex": "flex(弹性)", + "priority": "priority(fast)", + "unset": "清除(移除 service_tier)" + } + }, "codexOverrides": { "title": "Codex 参数覆写", "desc": "在供应商级别覆写 Codex (Responses API) 请求参数", @@ -217,7 +309,7 @@ }, "serviceTier": { "label": "Service Tier 覆写", - "help": "控制 Codex(Responses API)请求的 OpenAI service_tier。priority 对应 fast 模式;选择“跟随客户端”表示不改写 service_tier,其他值则强制覆写。若上游不支持所选档位,可能返回错误。", + "help": "控制 Codex(Responses API)请求的 OpenAI service_tier。启用后按原始模型、执行模型和客户端 service_tier 匹配规则,第一条命中的规则生效。priority 对应 fast 模式;“清除”会从上游请求中移除 service_tier,即使客户端传了 priority;在计费来源为 Requested Service Tier 时此类请求按标准档计费。若上游不支持所选档位,可能返回错误。", "options": { "inherit": "不覆写(跟随客户端)", "auto": "auto(项目默认)", @@ -359,6 +451,38 @@ "otherLimits": "其他限制", "windowReset": "窗口重置方式" }, + "upstreamQuota": { + "title": "上游额度", + "desc": "查询供应商的 Coding Plan 剩余额度,额度不足时不再向其调度新会话。需在系统设置中开启上游额度调度后生效。", + "probeType": { + "label": "额度来源", + "desc": "选择要查询的 Coding Plan 额度接口,或为该供应商关闭探测。", + "autoResolved": "根据供应商 URL 自动识别为:{type}", + "options": { + "auto": "按 URL 自动识别", + "none": "关闭", + "kimi-coding": "Kimi Coding Plan", + "zhipu-coding": "智谱 GLM Coding Plan", + "minimax-coding": "MiniMax Coding Plan", + "opencode-go": "OpenCode Go" + } + }, + "threshold": { + "label": "最低剩余额度(%)", + "desc": "覆盖系统设置中的阈值。留空则使用系统设置。", + "placeholder": "系统默认" + }, + "zhipuOrganization": { + "label": "智谱组织 ID", + "desc": "仅团队版需要,以 bigmodel-organization 请求头发送。", + "placeholder": "org-..." + }, + "zhipuProject": { + "label": "智谱项目 ID", + "desc": "仅团队版需要,以 bigmodel-project 请求头发送。", + "placeholder": "proj_..." + } + }, "circuitBreaker": { "title": "熔断器配置", "summary": "{failureThreshold} 次失败 / {openDuration} 分钟熔断 / {successThreshold} 次成功恢复 / 每个供应商最多 {maxRetryAttempts} 次尝试", diff --git a/messages/zh-CN/settings/providers/list.json b/messages/zh-CN/settings/providers/list.json index dcb54db1f4..a7c71a765b 100644 --- a/messages/zh-CN/settings/providers/list.json +++ b/messages/zh-CN/settings/providers/list.json @@ -6,7 +6,45 @@ "todayUsageCount": "{count} 次", "circuitBroken": "熔断中", "keyCircuitBroken": "Key 熔断", + "providerPrefixBadge": "供应商前缀:{prefix}", "endpointCircuitBroken": "端点熔断", + "upstreamQuota": { + "title": "上游额度", + "statusOk": "额度 {percent}%", + "statusLow": "额度低 {percent}%", + "statusExhausted": "额度耗尽", + "statusUnknown": "额度未知", + "windows": { + "5h": "5 小时窗口", + "weekly": "每周", + "monthly": "每月", + "rolling": "滚动窗口" + }, + "used": "已用 {percent}%", + "resetsIn": "{duration} 后重置", + "plan": "套餐:{plan}", + "noData": "暂无额度数据,下次探测时获取。", + "stale": "额度数据已过期,调度时忽略。", + "reactivePaused": "因余额不足响应已暂停,剩余 {duration}", + "credentialInvalid": "额度接口拒绝了该 API Key。", + "lastError": "最近错误:{error}", + "threshold": "新会话要求剩余额度至少 {percent}%", + "updatedAgo": "{duration} 前更新", + "refresh": "刷新额度", + "refreshSuccess": "额度已刷新", + "refreshFailed": "刷新额度失败", + "durationDaysHours": "{days} 天 {hours} 小时", + "durationHoursMinutes": "{hours} 小时 {minutes} 分", + "durationMinutes": "{minutes} 分钟", + "probeTypes": { + "auto": "自动", + "none": "无", + "kimi-coding": "Kimi Coding Plan", + "zhipu-coding": "智谱 GLM Coding Plan", + "minimax-coding": "MiniMax Coding Plan", + "opencode-go": "OpenCode Go" + } + }, "officialWebsite": "官网", "viewFullKey": "查看完整 API Key", "viewFullKeyDesc": "请妥善保管,不要泄露给他人", diff --git a/messages/zh-TW/dashboard.json b/messages/zh-TW/dashboard.json index 8100afbfb8..ec737d4dbe 100644 --- a/messages/zh-TW/dashboard.json +++ b/messages/zh-TW/dashboard.json @@ -1018,6 +1018,7 @@ "providers": "供應商管理", "auditLogs": "稽核日誌", "documentation": "文件", + "models": "模型目錄", "systemSettings": "系統設定", "feedback": "意見回饋", "login": "登入", @@ -2223,7 +2224,8 @@ "gh-action": "GitHub Action", "codex-cli-core": "CLI / TUI", "desktop": "Desktop", - "exec": "Exec" + "exec": "Exec", + "python-sdk": "Python SDK" }, "nSelected": "已選 {count} 項" }, diff --git a/messages/zh-TW/index.ts b/messages/zh-TW/index.ts index 97aeb7c2f2..c7b0c9b4bb 100644 --- a/messages/zh-TW/index.ts +++ b/messages/zh-TW/index.ts @@ -8,6 +8,7 @@ import errors from "./errors.json"; import forms from "./forms.json"; import internal from "./internal.json"; import ipDetails from "./ipDetails.json"; +import modelCatalog from "./modelCatalog.json"; import myUsage from "./myUsage.json"; import notifications from "./notifications.json"; import providerChain from "./provider-chain.json"; @@ -29,6 +30,7 @@ export default { errors, forms, ipDetails, + modelCatalog, notifications, "provider-chain": providerChain, providers, diff --git a/messages/zh-TW/modelCatalog.json b/messages/zh-TW/modelCatalog.json new file mode 100644 index 0000000000..3104a7f841 --- /dev/null +++ b/messages/zh-TW/modelCatalog.json @@ -0,0 +1,50 @@ +{ + "pageTitle": "模型目錄", + "pageDescription": "目前 API Key 可用的模型、每個模型支援的呼叫協定,以及上下文、價格與能力資訊。", + "generatedAt": "產生於 {time},最多每分鐘更新一次。", + "searchPlaceholder": "搜尋模型", + "tabs": { + "byModel": "依模型", + "byProtocol": "依協定" + }, + "columns": { + "model": "模型", + "protocols": "協定", + "contextWindow": "上下文", + "maxOutput": "最大輸出", + "inputPrice": "輸入", + "outputPrice": "輸出", + "cacheRead": "快取讀取", + "cacheWrite": "快取寫入", + "capabilities": "能力", + "modalities": "模態", + "knowledgeCutoff": "知識截止" + }, + "capabilities": { + "vision": "視覺", + "functionCalling": "工具呼叫", + "reasoning": "推理", + "pdfInput": "PDF", + "promptCaching": "提示快取" + }, + "modalities": { + "input": "輸入", + "output": "輸出" + }, + "status": { + "deprecated": "已棄用" + }, + "priceUnit": "價格單位為 USD / 每百萬 tokens(基礎檔位)。", + "noPriceData": "暫無中繼資料", + "empty": "沒有符合的模型。", + "protocolEmpty": "此協定下沒有可用模型。", + "protocolModelsCount": "{count} 個模型", + "machineReadable": { + "title": "供 agent 與腳本使用", + "description": "使用你的 API Key 作為 Bearer token 請求以下位址。預設回傳 JSON,加上 ?format=md 可取得 LLM 可直接閱讀的 Markdown。", + "copy": "複製位址" + }, + "notes": { + "title": "管理員說明" + } +} diff --git a/messages/zh-TW/myUsage.json b/messages/zh-TW/myUsage.json index 952f05acbb..b52987f0dd 100644 --- a/messages/zh-TW/myUsage.json +++ b/messages/zh-TW/myUsage.json @@ -3,6 +3,7 @@ "title": "我的使用量", "welcome": "歡迎,{name}", "documentation": "使用說明", + "modelCatalog": "模型目錄", "logout": "登出", "keyLabel": "金鑰", "userLabel": "使用者", diff --git a/messages/zh-TW/provider-chain.json b/messages/zh-TW/provider-chain.json index 038df8b95b..fad70d155f 100644 --- a/messages/zh-TW/provider-chain.json +++ b/messages/zh-TW/provider-chain.json @@ -82,18 +82,22 @@ "format_type_mismatch": "請求格式不相容", "type_mismatch": "類型不匹配", "model_not_allowed": "模型不允許", + "prefix_mismatch": "前綴不符", "model_not_supported": "不支援該模型", "group_mismatch": "分組不匹配", "health_check_failed": "健康檢查失敗", "endpoint_circuit_open": "端點已熔斷", "endpoint_disabled": "端點已停用", - "client_restriction": "客戶端限制" + "client_restriction": "客戶端限制", + "quota_low": "上游額度不足", + "quota_exhausted": "上游額度耗盡" }, "filterDetails": { "vendor_type_circuit_open": "供應商類型臨時熔斷", "circuit_open": "熔斷器打開", "circuit_half_open": "熔斷器半開", "rate_limited": "速率限制", + "upstream_balance_exhausted": "上游餘額不足", "provider_client_restriction": "供應商因客戶端限制被跳過", "session_reuse_client_restriction": "Session reuse rejected: client restriction", "blocklist_hit": "Blocked by pattern: {pattern}", diff --git a/messages/zh-TW/settings/config.json b/messages/zh-TW/settings/config.json index 8322cd7824..9e6645cd3f 100644 --- a/messages/zh-TW/settings/config.json +++ b/messages/zh-TW/settings/config.json @@ -59,9 +59,12 @@ "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 除錯詳情會減少或延後。", + "enableHighConcurrencyModeDesc": "開啟後,將關閉部分 Redis 除錯快照與即時 Session 觀測寫入,以降低高並發下的 CPU 與 IO 開銷;配額租約的資料庫刷新間隔至少為 30 秒,總消費限額快取延長至 15 分鐘,可用性投影閒置輪詢放緩。轉發、整流、fake 200 偵測、計費與限額不受影響,但 Sessions 除錯詳情會減少或延後,配額與看板資料的更新會有更長延遲。", "enableMemoryAdmission": "啟用記憶體准入", "enableMemoryAdmissionDesc": "開啟後,請求本文與串流首內容緩衝依本機可用記憶體分配額度,額度不足時暫存到磁碟,仍然不足時回傳本地 429 並提示用戶端稍後重試。關閉時這些資料全部保留在記憶體中,不排隊,也不回傳本地 429。", + "agentCatalogNotes": "模型目錄附加文件", + "agentCatalogNotesDesc": "Markdown 格式,附加在模型目錄頁面(/models)與 LLM 可讀介面(/v1/models/catalog)的末尾,所有使用者可見。可用於向 agent 說明模型選擇建議、使用限制等。留空則不顯示。", + "agentCatalogNotesPlaceholder": "例如:\n## 模型選擇建議\n- 程式設計任務優先使用 claude-sonnet-4-5\n- 超長上下文請使用 gemini-2.5-pro", "enableResponseFixer": "啟用回應整流", "enableResponseFixerDesc": "自動修復上游回應中常見的編碼、SSE 與 JSON 格式問題(預設開啟)。", "enableThinkingSignatureRectifier": "啟用 thinking 簽名整流器", @@ -126,6 +129,13 @@ "legacyHedgeMaxInFlightInvalid": "Legacy hedge 並發數必須是 1 到 4 之間的整數。", "discoveryEnabled": "啟用有界供應商 Discovery", "discoveryEnabledDesc": "啟用後,冷啟動串流請求會在限定視窗內探測多個供應商,並且最多保留一個保底請求。預設關閉。", + "upstreamQuotaSchedulingEnabled": "上游額度調度", + "upstreamQuotaSchedulingEnabledDesc": "定期查詢 Kimi、智譜 GLM、MiniMax、OpenCode Go 供應商的 Coding Plan 剩餘額度。新會話不會調度到剩餘額度低於閾值的供應商;既有會話繼續使用原供應商,直到額度完全耗盡。上游回傳 402 或餘額不足時立即暫停該供應商。", + "upstreamQuotaThresholdPercent": "新會話最低剩餘額度(%)", + "upstreamQuotaThresholdPercentDesc": "任一有效視窗(5 小時、每週或每月)的剩餘額度低於此百分比時,不再向該供應商調度新會話。可在供應商中單獨覆寫。", + "upstreamQuotaProbeIntervalMinutes": "額度探測間隔(分鐘)", + "upstreamQuotaProbeIntervalMinutesDesc": "每個供應商的額度查詢頻率。超過 3 個間隔的快照視為過期,被動暫停持續 2 個間隔。", + "upstreamQuotaSettingsInvalid": "上游額度閾值須為 1-99,探測間隔須為 1-1440 分鐘。", "discoveryConcurrency": "Discovery 首輪並發數", "maxDiscoveryRounds": "Discovery 最大輪數", "discoverySlaMs": "Discovery 每輪 SLA(毫秒)", @@ -150,6 +160,7 @@ "description": "設定限額檢查時的租約刷新間隔和切片比例。租約機制用於減少 DB 查詢壓力,同時保持限額精度。", "dbRefreshInterval": "DB 刷新間隔(秒)", "dbRefreshIntervalDesc": "從資料庫刷新配額使用量的間隔時間(1-300 秒)", + "dbRefreshIntervalHighConcurrencyFloor": "高並發模式已開啟:實際刷新間隔不低於 {seconds} 秒,以減少資料庫聚合查詢。", "leasePercent5h": "5 小時窗口租約比例", "leasePercent5hDesc": "每次租約切片佔 5 小時限額的比例(0-1)", "leasePercentDaily": "每日窗口租約比例", @@ -169,6 +180,8 @@ }, "affinityIgnoreClientSessionId": "忽略用戶端 Session ID", "affinityIgnoreClientSessionIdDesc": "開啟後,可指紋化的請求強制使用最長前綴親和做供應商黏性(跳過用戶端 Session ID 綁定);不可指紋化的請求仍走會話複用。預設開啟。", + "edgeExecutionEnabled": "Edge 執行器", + "edgeExecutionEnabledDesc": "開啟後,遠端 Edge 執行器可接管符合條件的 /v1/messages 請求(Claude 格式供應商)的上游呼叫;驗證、限流、選路、重試與計費仍由本服務決策。需要設定 CCH_EDGE_SHARED_SECRET。", "streamGateMode": "串流內容閘控", "streamGateModeDesc": "控制一般請求的首個有效內容影格閘控:啟用時遇到錯誤影格或空串流會自動切換供應商重試,影子模式僅旁路統計判定分歧,關閉時直接轉發。影子模式與關閉時首位元組立即透傳給客戶端,Replay owner 也不例外;開啟高併發模式等同於關閉。預設啟用。", "streamGateModeOptions": { diff --git a/messages/zh-TW/settings/notifications.json b/messages/zh-TW/settings/notifications.json index 8295c72e0d..0aa7c1c362 100644 --- a/messages/zh-TW/settings/notifications.json +++ b/messages/zh-TW/settings/notifications.json @@ -54,6 +54,15 @@ "windowMode": "視窗", "windowModeAuto": "自動" }, + "clientProblem": { + "countThreshold": "一般錯誤筆數閾值", + "cyberCountThreshold": "Cyber risk 筆數閾值", + "cyberWindowMinutes": "Cyber risk 時間窗口(分鐘)", + "description": "用戶端遇到逾時、伺服器錯誤或 cyber risk 時彙總推送(不含 499)。兩組閾值獨立:累計筆數或時間窗口,先到先發。", + "enable": "啟用用戶端故障告警", + "title": "用戶端故障告警", + "windowMinutes": "一般錯誤時間窗口(分鐘)" + }, "dailyLeaderboard": { "description": "每天定時發送使用者消費 Top N 排行榜", "enable": "啟用每日排行榜", @@ -89,7 +98,10 @@ "on": "已開啟", "legacyModeDescription": "目前使用舊版單一 URL 推送設定。建立推送目標後將自動切換為多目標模式。", "legacyModeTitle": "相容模式", - "title": "通知總開關" + "title": "通知總開關", + "titlePrefix": "告警標題前綴", + "titlePrefixHelp": "用於區分實例,發送時加在標題前,例如 [PROXY]。留空則不加前綴。Webhook 目標填了代理時用目標代理;否則用 OUTBOUND_PROXY_URL;都沒有時才用 PROXY/HTTPS_PROXY。", + "titlePrefixPlaceholder": "PROXY" }, "targetDialog": { "createTitle": "新增推送目標", diff --git a/messages/zh-TW/settings/providers/batchEdit.json b/messages/zh-TW/settings/providers/batchEdit.json index e8cdb4e0ab..ca990cb65f 100644 --- a/messages/zh-TW/settings/providers/batchEdit.json +++ b/messages/zh-TW/settings/providers/batchEdit.json @@ -55,7 +55,8 @@ "modelRedirects": "模型重新導向", "allowedModels": "允許的模型", "thinkingBudget": "思考預算", - "adaptiveThinking": "自適應思考" + "adaptiveThinking": "自適應思考", + "reasoningEffortRules": "條件推理強度規則" }, "errors": { "nothingToApply": "沒有可套用的變更。", diff --git a/messages/zh-TW/settings/providers/form/matchTester.json b/messages/zh-TW/settings/providers/form/matchTester.json index 7ce801836c..b1fefb87d1 100644 --- a/messages/zh-TW/settings/providers/form/matchTester.json +++ b/messages/zh-TW/settings/providers/form/matchTester.json @@ -15,5 +15,6 @@ "matchType": "匹配方式", "source": "來源模型模式", "target": "重定向目標", - "pattern": "白名單模式" + "pattern": "白名單模式", + "prefixStripped": "已去掉供應商前綴,規則以 {model} 比對" } diff --git a/messages/zh-TW/settings/providers/form/sections.json b/messages/zh-TW/settings/providers/form/sections.json index 2f2960740c..1712f40d75 100644 --- a/messages/zh-TW/settings/providers/form/sections.json +++ b/messages/zh-TW/settings/providers/form/sections.json @@ -176,6 +176,38 @@ "otherLimits": "其他限制", "windowReset": "視窗重設方式" }, + "upstreamQuota": { + "title": "上游額度", + "desc": "查詢供應商的 Coding Plan 剩餘額度,額度不足時不再向其調度新會話。需在系統設定中開啟上游額度調度後生效。", + "probeType": { + "label": "額度來源", + "desc": "選擇要查詢的 Coding Plan 額度介面,或為該供應商關閉探測。", + "autoResolved": "依供應商 URL 自動識別為:{type}", + "options": { + "auto": "依 URL 自動識別", + "none": "關閉", + "kimi-coding": "Kimi Coding Plan", + "zhipu-coding": "智譜 GLM Coding Plan", + "minimax-coding": "MiniMax Coding Plan", + "opencode-go": "OpenCode Go" + } + }, + "threshold": { + "label": "最低剩餘額度(%)", + "desc": "覆寫系統設定中的閾值。留空則使用系統設定。", + "placeholder": "系統預設" + }, + "zhipuOrganization": { + "label": "智譜組織 ID", + "desc": "僅團隊版需要,以 bigmodel-organization 請求標頭傳送。", + "placeholder": "org-..." + }, + "zhipuProject": { + "label": "智譜專案 ID", + "desc": "僅團隊版需要,以 bigmodel-project 請求標頭傳送。", + "placeholder": "proj_..." + } + }, "routing": { "cacheTtl": { "desc": "強制設定 prompt cache TTL;僅影響包含 cache_control 的請求。", @@ -205,6 +237,86 @@ "invalidTemplate": "動態模板語法無效。支援 '{{header.Name}}'、'{{session.id}}'、'{{session.client_id}}'" } }, + "effortRules": { + "title": "條件推理強度規則", + "description": "規則依序評估,第一條匹配的規則決定覆寫強度。如果沒有匹配的規則,則不套用覆寫。", + "noRules": "未設定規則。新增規則以啟用條件強度覆寫。", + "addRule": "新增規則", + "removeRule": "刪除規則", + "moveUp": "上移", + "moveDown": "下移", + "ruleLabel": "規則 {number}", + "targetEffort": "目標強度", + "selectTarget": "選擇目標強度", + "originalModelCondition": "當原始模型匹配時...", + "executionModelCondition": "當執行模型匹配時...", + "removeCondition": "移除", + "addCondition": "新增", + "matchModes": { + "exact": "完全匹配", + "prefix": "前綴匹配", + "suffix": "後綴匹配", + "contains": "包含匹配", + "regex": "正規表達式" + }, + "patternPlaceholder": "例如 claude-opus-4-6", + "originalEffortCondition": "當原始強度匹配時...", + "effortMode": { + "any": "任意(省略條件)", + "missing": "缺失(明確 null)", + "specific": "特定值" + }, + "originalEffortPlaceholder": "例如 medium", + "maxRulesReached": "已達到最大 {max} 條規則", + "effortValues": { + "none": "none", + "minimal": "minimal", + "low": "low", + "medium": "medium", + "high": "high", + "xhigh": "xhigh", + "max": "max" + } + }, + "serviceTierRules": { + "title": "條件 Service Tier 規則", + "description": "規則依序評估,第一條匹配的規則決定上游 service_tier。如果沒有匹配的規則,則原樣透傳客戶端的值。", + "noRules": "未設定規則。新增規則以啟用條件 Service Tier 覆寫。", + "addRule": "新增規則", + "removeRule": "刪除規則", + "moveUp": "上移", + "moveDown": "下移", + "ruleLabel": "規則 {number}", + "targetServiceTier": "目標 Service Tier", + "selectTarget": "選擇目標 Service Tier", + "originalModelCondition": "當原始模型匹配時...", + "executionModelCondition": "當執行模型匹配時...", + "removeCondition": "移除", + "addCondition": "新增", + "matchModes": { + "exact": "完全匹配", + "prefix": "前綴匹配", + "suffix": "後綴匹配", + "contains": "包含匹配", + "regex": "正規表達式" + }, + "patternPlaceholder": "例如 gpt-5-codex", + "originalServiceTierCondition": "當原始 Service Tier 匹配時...", + "serviceTierMode": { + "any": "任意(省略條件)", + "missing": "缺失(客戶端未傳)", + "specific": "特定值" + }, + "originalServiceTierPlaceholder": "例如 priority", + "maxRulesReached": "已達到最大 {max} 條規則", + "serviceTierValues": { + "auto": "auto(專案預設)", + "default": "default(標準檔)", + "flex": "flex(彈性)", + "priority": "priority(fast)", + "unset": "清除(移除 service_tier)" + } + }, "codexOverrides": { "title": "Codex 參數覆寫", "desc": "在供應商級別覆寫 Codex (Responses API) 請求參數", @@ -227,7 +339,7 @@ } }, "serviceTier": { - "help": "控制 Codex(Responses API)請求的 OpenAI service_tier。priority 對應 fast 模式;「跟隨客戶端」不改寫 service_tier,其他值會強制覆寫。若上游不支援所選檔位,可能返回錯誤。", + "help": "控制 Codex(Responses API)請求的 OpenAI service_tier。啟用後依原始模型、執行模型與客戶端 service_tier 匹配規則,第一條命中的規則生效。priority 對應 fast 模式;「清除」會從上游請求中移除 service_tier,即使客戶端傳了 priority;在計費來源為 Requested Service Tier 時此類請求按標準檔計費。若上游不支援所選檔位,可能返回錯誤。", "label": "Service Tier 覆寫", "options": { "auto": "auto(專案預設)", @@ -378,7 +490,8 @@ "gh-action": "GitHub Action", "codex-cli-core": "CLI / TUI", "desktop": "Desktop", - "exec": "Exec" + "exec": "Exec", + "python-sdk": "Python SDK" }, "nSelected": "已選 {count} 項" }, @@ -392,6 +505,17 @@ "label": "不遵循 Session 復用", "desc": "啟用後,下次請求不會繼續復用目前供應商的 sticky 綁定,而會重新走優先級與健康檢查。適合高優先級便宜通道恢復後盡快切回的場景。" }, + "overwriteResponseModel": { + "label": "覆寫回應模型 ID", + "desc": "啟用後,回傳給用戶端的 model 欄位一律是使用者請求的模型 ID,而不是上游實際回傳或重新導向後的模型。" + }, + "providerPrefix": { + "label": "供應商前綴", + "desc": "選填。設定後只有帶此前綴的模型 ID 才能命中此供應商,例如前綴 openai 時需請求 openai/gpt-5;轉發上游前會自動去掉前綴。", + "placeholder": "例如:openai", + "help": "結尾的 / 會自動規範化為單一 /。前綴比對不區分大小寫;模型重新導向與模型白名單規則以去掉前綴後的模型名稱撰寫;模型列表介面會以帶前綴的 ID 顯示此供應商的模型。", + "preview": "僅比對以 {prefix} 開頭的模型,例如 {example};轉發上游時會去掉 {prefix}。" + }, "options": { "title": "選項", "desc": "附加供應商選項和覆寫設定" diff --git a/messages/zh-TW/settings/providers/list.json b/messages/zh-TW/settings/providers/list.json index 00dbcff388..d5f0b655cd 100644 --- a/messages/zh-TW/settings/providers/list.json +++ b/messages/zh-TW/settings/providers/list.json @@ -2,7 +2,45 @@ "cancelButton": "關閉", "circuitBroken": "熔斷中", "keyCircuitBroken": "Key 熔斷", + "providerPrefixBadge": "供應商前綴:{prefix}", "endpointCircuitBroken": "端點熔斷", + "upstreamQuota": { + "title": "上游額度", + "statusOk": "額度 {percent}%", + "statusLow": "額度低 {percent}%", + "statusExhausted": "額度耗盡", + "statusUnknown": "額度未知", + "windows": { + "5h": "5 小時視窗", + "weekly": "每週", + "monthly": "每個月", + "rolling": "滾動視窗" + }, + "used": "已使用 {percent}%", + "resetsIn": "{duration} 後重置", + "plan": "方案:{plan}", + "noData": "尚無額度資料,下次探測時取得。", + "stale": "額度資料已過期,調度時忽略。", + "reactivePaused": "因餘額不足回應已暫停,剩餘 {duration}", + "credentialInvalid": "額度介面拒絕了此 API Key。", + "lastError": "最近錯誤:{error}", + "threshold": "新會話要求剩餘額度至少 {percent}%", + "updatedAgo": "{duration} 前已更新", + "refresh": "重新整理額度", + "refreshSuccess": "額度已更新", + "refreshFailed": "更新額度失敗", + "durationDaysHours": "{days} 天 {hours} 小時", + "durationHoursMinutes": "{hours} 小時 {minutes} 分", + "durationMinutes": "{minutes} 分鐘", + "probeTypes": { + "auto": "自動", + "none": "無", + "kimi-coding": "Kimi Coding Plan", + "zhipu-coding": "智譜 GLM Coding Plan", + "minimax-coding": "MiniMax Coding Plan", + "opencode-go": "OpenCode Go" + } + }, "clipboardUnavailable": "目前環境無法使用剪貼簿,請手動選取複製。", "confirmDeleteMessage": "確定要刪除供應商 \"{name}\" 嗎?此操作無法撤銷。", "confirmDeleteTitle": "確認刪除供應商?", diff --git a/package.json b/package.json index f07cc35642..9c9b3db6a7 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "test:coverage:include-session-id-in-errors": "vitest run --config tests/configs/include-session-id-in-errors.config.mts --coverage --configLoader bundle", "test:coverage:usage-logs-sessionid-search": "vitest run --config tests/configs/usage-logs-sessionid-search.config.mts --coverage --configLoader bundle", "test:coverage:session-binding": "vitest run --config tests/configs/session-binding.config.mts --coverage --configLoader bundle", + "test:coverage:edge-control-plane": "vitest run --config tests/configs/edge-control-plane.config.mts --coverage --configLoader bundle", "test:ci": "vitest run --configLoader bundle --reporter=default --reporter=junit --outputFile.junit=reports/vitest-junit.xml", "test:v1": "vitest run --config tests/configs/v1.config.mts --coverage --reporter=verbose --configLoader bundle && bun scripts/check-v1-critical-coverage.ts", "openapi:generate": "bun scripts/generate-v1-types.ts", @@ -100,6 +101,7 @@ "framer-motion": "^12.43.0", "hono": "^4.12.33", "html2canvas": "^1.4.1", + "https-proxy-agent": "^7.0.6", "ioredis": "^6.0.0", "jspdf": "^4.2.1", "lucide-react": "^1.28.0", @@ -110,14 +112,19 @@ "pino": "^10.3.1", "pino-pretty": "^13.1.3", "postgres": "^3.4.9", + "prom-client": "^15.1.3", "react": "^19.2.8", "react-day-picker": "^10.0.1", "react-dom": "^19.2.8", "react-hook-form": "^7.84.0", + "react-markdown": "^10.1.0", "react-syntax-highlighter": "^16.1.1", "recharts": "^3.10.1", + "rehype-sanitize": "^6.0.0", "safe-regex": "^2.1.1", "server-only": "^0.0.1", + "socks": "^2.8.7", + "socks-proxy-agent": "^8", "sonner": "^2.0.7", "tailwind-merge": "^3.6.0", "timeago.js": "^4.0.2", diff --git a/scripts/edge-fixtures/body.ts b/scripts/edge-fixtures/body.ts new file mode 100644 index 0000000000..9094d48e3e --- /dev/null +++ b/scripts/edge-fixtures/body.ts @@ -0,0 +1,980 @@ +/** + * Generates the shared TS <-> Go edge-executor fixtures under + * tests/fixtures/edge/body/ by calling the *real* TS reference + * implementations (buildRequestDigest, applyBodyOps, stableStringify, + * detectUpstreamErrorFromSseOrJsonText). The Go packages under + * edge/internal/{ojson,digest,bodyops,detect} are ported against these + * fixtures and must reproduce the recorded output exactly. + * + * Run from the repo root: `bun scripts/edge-fixtures/body.ts` + * + * Notes on loading the real TS modules from a standalone bun script: + * - digest.ts pulls in session.ts / session-manager.ts, which transitively + * import a module tagged `server-only`. Next.js strips that import via a + * bundler alias; bun has no such alias, so we register a Bun.plugin that + * replaces `server-only` with an empty module before dynamically importing + * the reference modules (dynamic import so the plugin is registered first; + * a static top-level import would resolve before this file's body runs). + * - `@/` path aliases resolve fine under bun (tsconfig `paths` is honored), + * so we use them directly instead of relative imports. + */ +import { plugin } from "bun"; +import { mkdir, writeFile } from "node:fs/promises"; +import path from "node:path"; + +plugin({ + name: "stub-server-only", + setup(build) { + build.module("server-only", () => ({ contents: "export {};", loader: "js" })); + }, +}); + +const { buildRequestDigest } = await import("@/app/v1/_lib/edge/digest"); +const { applyBodyOps } = await import("@/app/v1/_lib/edge/body-ops"); +const { stableStringify } = await import("@/lib/request-identity"); +const { detectUpstreamErrorFromSseOrJsonText } = await import( + "@/lib/utils/upstream-error-detection" +); +type BodyOp = import("@/app/v1/_lib/edge/contract").BodyOp; + +const OUT_DIR = path.resolve(import.meta.dir, "../../tests/fixtures/edge/body"); + +async function writeJSON(name: string, data: unknown): Promise { + await mkdir(OUT_DIR, { recursive: true }); + await writeFile(path.join(OUT_DIR, name), `${JSON.stringify(data, null, 2)}\n`, "utf8"); + console.log(`wrote ${name}`); +} + +// =================================================================== +// digest.json +// =================================================================== + +interface DigestCase { + name: string; + body: string; // raw JSON text, preserves exact numeric literals etc. + path: string; + method?: string; + headers?: [string, string][]; + clientIp?: string | null; + expected: unknown; // RequestDigest minus receivedAtMs/edgeId/edgeRequestId +} + +function claudeBody(messages: unknown[], overrides: Record = {}): string { + return JSON.stringify({ + model: "claude-sonnet-4-5", + system: "You are a helpful assistant.", + tools: [ + { + name: "read_file", + description: "Read a file", + input_schema: { type: "object", properties: { path: { type: "string" } } }, + }, + { + name: "bash", + description: "Run a command", + input_schema: { type: "object", properties: { cmd: { type: "string" } } }, + }, + ], + messages, + ...overrides, + }); +} + +const U1 = { role: "user", content: "hello" }; +const A1 = { role: "assistant", content: [{ type: "text", text: "hi there" }] }; +const U2 = { role: "user", content: "next question" }; + +function buildDigest(bodyText: string, path = "/v1/messages", method = "POST", headers: [string, string][] = [], clientIp: string | null = null) { + const digest = buildRequestDigest({ + edgeId: "edge-fixture", + edgeRequestId: "req-fixture", + receivedAtMs: 1_700_000_000_000, + method, + path, + headers, + clientIp, + body: JSON.parse(bodyText), + bodyBytes: Buffer.byteLength(bodyText, "utf8"), + }); + const { receivedAtMs: _r, edgeId: _e, edgeRequestId: _i, ...expected } = digest; + return expected; +} + +const digestCases: DigestCase[] = []; + +function addDigest(name: string, bodyText: string, opts: Partial = {}) { + const p = opts.path ?? "/v1/messages"; + const method = opts.method ?? "POST"; + const headers = opts.headers ?? []; + const clientIp = opts.clientIp ?? null; + digestCases.push({ + name, + body: bodyText, + path: p, + method, + headers, + clientIp, + expected: buildDigest(bodyText, p, method, headers, clientIp), + }); +} + +// --- basic determinism / structure --- +addDigest("claude_basic_chain", claudeBody([U1, A1, U2])); +addDigest("claude_prefix_extended", claudeBody([U1, A1, U2, { role: "user", content: "more" }])); +addDigest("claude_empty_messages", claudeBody([])); +addDigest("claude_missing_system_and_tools", JSON.stringify({ messages: [U1] })); +addDigest("claude_empty_content_array_skipped", claudeBody([{ role: "user", content: [] }, U1])); + +// --- volatile key stripping --- +addDigest( + "claude_tool_use_id_stripped", + claudeBody([ + U1, + { + role: "assistant", + content: [{ type: "tool_use", id: "toolu_aaa", name: "read_file", input: { path: "a.ts" } }], + }, + { role: "user", content: [{ type: "tool_result", tool_use_id: "toolu_aaa", content: "file body" }] }, + ]) +); +addDigest( + "claude_thinking_signature_not_hashed", + claudeBody([ + U1, + { role: "assistant", content: [{ type: "thinking", thinking: "step by step", signature: "sig-one" }] }, + ]) +); +addDigest( + "claude_unknown_block_type", + claudeBody([ + U1, + { + role: "assistant", + content: [{ type: "server_tool_use", id: "srvtoolu_1", name: "web_search", payload: { q: "x" } }], + }, + ]) +); + +// --- cache_control boundaries --- +addDigest( + "claude_cache_control_boundary", + claudeBody([ + U1, + { role: "user", content: [{ type: "text", text: "long context", cache_control: { type: "ephemeral" } }] }, + ]) +); + +// --- window truncation (MAX_AFFINITY_WINDOW = 64) --- +addDigest( + "claude_window_truncation_70_messages", + claudeBody( + Array.from({ length: 70 }, (_, i) => ({ + role: i % 2 === 0 ? "user" : "assistant", + content: `message ${i}`, + })) + ) +); + +// --- media digests --- +addDigest( + "claude_media_image_base64", + claudeBody([ + { + role: "user", + content: [ + { type: "image", source: { type: "base64", media_type: "image/png", data: "AAAABBBB" } }, + { type: "text", text: "describe" }, + ], + }, + ]) +); +addDigest( + "claude_media_document_url", + claudeBody([ + { role: "user", content: [{ type: "document", source: { media_type: "application/pdf", url: "https://a.example/x.pdf" } }] }, + ]) +); +addDigest( + "claude_media_no_source", + claudeBody([ + { role: "assistant", content: [{ type: "redacted_thinking", data: "opaque" }, { type: "image", source: null }] }, + ]) +); + +// --- tool ordering --- +addDigest( + "claude_tools_reverse_order", + claudeBody([U1], { + tools: [ + { name: "bash", description: "Run a command", input_schema: { type: "object", properties: { cmd: { type: "string" } } } }, + { name: "read_file", description: "Read a file", input_schema: { type: "object", properties: { path: { type: "string" } } } }, + ], + }) +); + +// --- system as content-block array --- +addDigest( + "claude_system_block_array", + claudeBody([null, U1, { role: "assistant", content: ["raw string block", null] }], { + system: [{ type: "text", text: "block system" }, "loose text"], + }) +); + +// --- unicode keys / private params --- +addDigest( + "unicode_keys_and_private_params", + JSON.stringify({ + model: "claude-sonnet-4-5", + messages: [ + { role: "user", content: "你好😀 unicode text" }, + { role: "assistant", content: [{ type: "text", text: "reply" }], _internal: { _secret: "x" } }, + ], + metadata: { user_id: "u1", _private: true, nested: { _alsoPrivate: 1 } }, + }) +); + +// --- topLevel number literal fidelity (compared canonically on the Go side) --- +addDigest( + "top_level_number_literals", + JSON.stringify({ + model: "claude-sonnet-4-5", + max_tokens: 4096, + messages: [U1], + metadata: { + n_exp: 1e21, + n_neg_zero: -0, + n_one_point_zero: 1.0, + n_big_int: 12345678901234567890, + n_small: 0.000001, + n_tiny_exp: 1e-7, + }, + }) +); + +// --- probe requests --- +addDigest("probe_foo", JSON.stringify({ messages: [{ role: "user", content: "foo" }] })); +addDigest("probe_count_uppercase_trimmed", JSON.stringify({ messages: [{ role: "user", content: " COUNT " }] })); +addDigest("not_probe_two_messages", JSON.stringify({ messages: [{ role: "user", content: "foo" }, { role: "user", content: "bar" }] })); +addDigest("not_probe_content_array", JSON.stringify({ messages: [{ role: "user", content: [{ type: "text", text: "foo" }] }] })); +addDigest("not_probe_wrong_text", JSON.stringify({ messages: [{ role: "user", content: "hello" }] })); + +// --- warmup requests --- +addDigest( + "warmup_valid", + JSON.stringify({ + messages: [ + { role: "user", content: [{ type: "text", text: "Warmup", cache_control: { type: "ephemeral" } }] }, + ], + }), + { path: "/v1/messages" } +); +addDigest( + "warmup_wrong_path", + JSON.stringify({ + messages: [ + { role: "user", content: [{ type: "text", text: "Warmup", cache_control: { type: "ephemeral" } }] }, + ], + }), + { path: "/v1/messages/count_tokens" } +); +addDigest( + "warmup_missing_cache_control", + JSON.stringify({ messages: [{ role: "user", content: [{ type: "text", text: "Warmup" }] }] }), + { path: "/v1/messages" } +); +addDigest( + "warmup_case_insensitive_trimmed", + JSON.stringify({ + messages: [ + { role: "user", content: [{ type: "text", text: " warmup ", cache_control: { type: "ephemeral" } }] }, + ], + }), + { path: "/v1/messages?beta=true" } +); + +// --- messagesHash --- +addDigest("messages_hash_string_content", JSON.stringify({ messages: [{ role: "user", content: "hi there" }] })); +addDigest( + "messages_hash_array_text_blocks", + JSON.stringify({ + messages: [{ role: "user", content: [{ type: "text", text: "part1" }, { type: "image", source: {} }, { type: "text", text: "part2" }] }], + }) +); +addDigest( + "messages_hash_uses_first_three_only", + JSON.stringify({ + messages: [ + { role: "user", content: "m1" }, + { role: "assistant", content: "m2" }, + { role: "user", content: "m3" }, + { role: "assistant", content: "m4 (ignored)" }, + ], + }) +); +addDigest("messages_hash_no_extractable_content", JSON.stringify({ messages: [{ role: "user", content: 42 }] })); +addDigest("messages_hash_empty_messages", JSON.stringify({ messages: [] })); + +// =================================================================== +// response format (/v1/responses): responseInputRectify, messagesCount/ +// inputCount, codexInitialTextHash, fingerprint (extractResponses) +// =================================================================== + +function responseBody(overrides: Record = {}): Record { + return { + model: "gpt-5-codex", + instructions: "You are a coding assistant.", + tools: [ + { + type: "function", + name: "get_weather", + description: "Get the current weather", + parameters: { type: "object", properties: { city: { type: "string" } } }, + }, + ], + ...overrides, + }; +} + +addDigest( + "response_input_string", + JSON.stringify(responseBody({ input: "hello world" })), + { path: "/v1/responses" } +); +addDigest( + "response_input_empty_string", + JSON.stringify(responseBody({ input: "" })), + { path: "/v1/responses" } +); +addDigest( + "response_input_object_message", + JSON.stringify( + responseBody({ input: { role: "user", content: [{ type: "input_text", text: "hi there" }] } }) + ), + { path: "/v1/responses" } +); +addDigest( + "response_input_object_tool_output", + JSON.stringify( + responseBody({ input: { type: "function_call_output", call_id: "call_1", output: "42" } }) + ), + { path: "/v1/responses" } +); +addDigest( + "response_input_object_unrecognized_passthrough", + JSON.stringify(responseBody({ input: { foo: "bar" } })), + { path: "/v1/responses" } +); +addDigest( + "response_input_null_passthrough", + JSON.stringify(responseBody({ input: null })), + { path: "/v1/responses" } +); +addDigest( + "response_input_array_full_conversation", + JSON.stringify( + responseBody({ + input: [ + { type: "message", role: "user", content: [{ type: "input_text", text: "what's the weather in NYC?" }] }, + { type: "function_call", call_id: "call_1", name: "get_weather", arguments: '{"city":"NYC"}' }, + { type: "function_call_output", call_id: "call_1", output: "sunny, 72F" }, + { + type: "reasoning", + summary: [{ type: "summary_text", text: "Checking the weather tool result." }], + }, + { type: "message", role: "assistant", content: [{ type: "output_text", text: "It's sunny, 72F." }] }, + ], + }) + ), + { path: "/v1/responses" } +); +addDigest( + "response_input_array_reasoning_falls_back_to_content", + JSON.stringify( + responseBody({ + input: [{ type: "reasoning", content: [{ type: "reasoning_text", text: "fallback text" }] }], + }) + ), + { path: "/v1/responses" } +); +addDigest( + "response_input_array_unknown_item_type", + JSON.stringify(responseBody({ input: [{ type: "item_reference", id: "ref_1" }] })), + { path: "/v1/responses" } +); +addDigest( + "response_codex_initial_text_hash", + JSON.stringify( + responseBody({ + input: [ + { type: "message", role: "user", content: [{ type: "input_text", text: "first turn text" }] }, + { type: "message", role: "assistant", content: [{ type: "output_text", text: "reply" }] }, + ], + }) + ), + { path: "/v1/responses" } +); + +// --- remote compaction v2 --- +addDigest( + "remote_compaction_v2_array_item", + JSON.stringify(responseBody({ input: [{ type: "compaction_trigger", session_id: "s1" }] })), + { path: "/v1/responses" } +); +addDigest( + "remote_compaction_v2_single_object_shorthand", + JSON.stringify(responseBody({ input: { type: "compaction_trigger", session_id: "s1" } })), + { path: "/v1/responses" } +); +addDigest( + "remote_compaction_v2_not_triggered", + JSON.stringify(responseBody({ input: [{ type: "message", role: "user", content: "hi" }] })), + { path: "/v1/responses" } +); +addDigest( + "remote_compaction_v2_wrong_path_not_triggered", + JSON.stringify({ messages: [U1] }), + { path: "/v1/messages" } +); + +// =================================================================== +// openai format (/v1/chat/completions): fingerprint (extractOpenAIChat) +// =================================================================== + +addDigest( + "openai_chat_system_user_assistant_tool", + JSON.stringify({ + model: "gpt-4o", + messages: [ + { role: "system", content: "You are a helpful assistant." }, + { role: "user", content: "What's the weather in NYC?" }, + { + role: "assistant", + content: null, + tool_calls: [ + { id: "call_abc", type: "function", function: { name: "get_weather", arguments: '{"city":"NYC"}' } }, + ], + }, + { role: "tool", tool_call_id: "call_abc", content: "sunny, 72F" }, + { role: "assistant", content: "It's sunny and 72F in NYC." }, + ], + tools: [ + { + type: "function", + function: { + name: "get_weather", + description: "Get the current weather", + parameters: { type: "object", properties: { city: { type: "string" } } }, + }, + }, + ], + stream_options: { include_usage: true }, + }), + { path: "/v1/chat/completions" } +); +addDigest( + "openai_chat_developer_leading_message", + JSON.stringify({ + model: "gpt-4o", + messages: [ + { role: "developer", content: "Follow house style." }, + { role: "user", content: "hi" }, + ], + }), + { path: "/v1/chat/completions" } +); +addDigest( + "openai_chat_multimodal_content_array", + JSON.stringify({ + model: "gpt-4o", + messages: [ + { + role: "user", + content: [ + { type: "text", text: "describe this" }, + { type: "image_url", image_url: { url: "https://a.example/x.png" } }, + ], + }, + ], + }), + { path: "/v1/chat/completions" } +); +addDigest( + "openai_chat_probe_like", + JSON.stringify({ model: "gpt-4o", messages: [{ role: "user", content: "foo" }] }), + { path: "/v1/chat/completions" } +); + +await writeJSON("digest.json", digestCases); + +// =================================================================== +// stable-stringify.json +// =================================================================== + +interface StableCase { + name: string; + value: string; // JSON text of the input value + expected: string; +} + +const stableCases: StableCase[] = []; +function addStable(name: string, valueText: string) { + stableCases.push({ name, value: valueText, expected: stableStringify(JSON.parse(valueText)) }); +} + +addStable("null_value", "null"); +addStable("bool_true", "true"); +addStable("bool_false", "false"); +addStable("empty_object", "{}"); +addStable("empty_array", "[]"); +addStable("simple_object_key_order", JSON.stringify({ b: 1, a: 2, c: 3 })); +addStable("nested_object_and_array", JSON.stringify({ z: [1, 2, { y: 1, x: 2 }], a: { nested: true } })); +addStable("string_escapes", JSON.stringify({ s: "quote\" backslash\\ tab\tnewline\ncontrol\u0001" })); +addStable("unicode_and_emoji", JSON.stringify({ "中文": 1, emoji: "😀", plain: "z" })); +addStable( + "utf16_key_ordering_astral_vs_bmp", + JSON.stringify({ "￿": 1, "😀hi": 2, a: 3, "": 4 }) +); +addStable("number_variants", JSON.stringify({ a: 1.0, b: -0, c: 1e21, d: 0.000001, e: 1e-7, f: 100 })); +addStable("array_of_mixed", JSON.stringify([1, "two", true, null, { k: "v" }, [1, 2]])); +addStable("big_integer_literal_precision", JSON.stringify({ big: 12345678901234567890 })); + +await writeJSON("stable-stringify.json", stableCases); + +// =================================================================== +// js-number.json +// =================================================================== + +interface JSNumberCase { + literal: string; + expected: string; +} + +const numberLiterals = [ + "0", + "-0", + "1", + "-1", + "100", + "1.5", + "1.0", + "0.1", + "0.000001", + "1e-6", + "1e-7", + "2.5e-10", + "123.456", + "-123.456", + "1e20", + "1e21", + "1e22", + "1e100", + "9007199254740993", + "12345678901234567890", + "3.14159265358979", + "5e-7", + "9.999999999999999e20", + "1234567890123456789012345", + "0.00001234", +]; + +const jsNumberCases: JSNumberCase[] = numberLiterals.map((literal) => ({ + literal, + expected: Number(literal).toString(), +})); + +await writeJSON("js-number.json", jsNumberCases); + +// =================================================================== +// bodyops.json +// =================================================================== + +interface BodyOpsCase { + name: string; + body: string; // raw JSON text + ops: BodyOp[]; + expectedBody: string; // TS JSON.stringify(result.message) output text + expectedOpResults: unknown; +} + +const bodyOpsCases: BodyOpsCase[] = []; +function addBodyOps(name: string, bodyText: string, ops: BodyOp[]) { + const { message, opResults } = applyBodyOps(JSON.parse(bodyText), ops); + bodyOpsCases.push({ + name, + body: bodyText, + ops, + expectedBody: JSON.stringify(message), + expectedOpResults: opResults, + }); +} + +// --- set_top_level / delete_top_level (order semantics) --- +addBodyOps( + "set_top_level_existing_key_keeps_position", + JSON.stringify({ model: "old-model", max_tokens: 100, stream: true }), + [{ op: "set_top_level", key: "model", value: "new-model" }] +); +addBodyOps("set_top_level_new_key_appended", JSON.stringify({ model: "m", stream: true }), [ + { op: "set_top_level", key: "max_tokens", value: 2048 }, +]); +addBodyOps( + "delete_top_level_existing", + JSON.stringify({ model: "m", max_tokens: 100, stream: true }), + [{ op: "delete_top_level", key: "max_tokens" }] +); +addBodyOps("delete_top_level_missing_noop", JSON.stringify({ model: "m" }), [ + { op: "delete_top_level", key: "max_tokens" }, +]); +addBodyOps( + "set_top_level_object_value", + JSON.stringify({ model: "m" }), + [{ op: "set_top_level", key: "metadata", value: { user_id: "u1", nested: { a: 1 } } }] +); + +// --- remove_system_billing_header (billing-header-rectifier.test.ts) --- +addBodyOps( + "billing_header_array_single_block", + JSON.stringify({ + system: [{ type: "text", text: "x-anthropic-billing-header: cc_version=2.1.36; cc_entrypoint=cli; cch=1;" }], + }), + [{ op: "remove_system_billing_header" }] +); +addBodyOps( + "billing_header_array_multiple_blocks", + JSON.stringify({ + system: [ + { type: "text", text: "x-anthropic-billing-header: cc_version=2.1.36;" }, + { type: "text", text: "x-anthropic-billing-header: cc_entrypoint=cli;" }, + ], + }), + [{ op: "remove_system_billing_header" }] +); +addBodyOps( + "billing_header_array_no_match", + JSON.stringify({ + system: [ + { type: "text", text: "You are a helpful assistant." }, + { type: "text", text: "Follow instructions carefully." }, + ], + }), + [{ op: "remove_system_billing_header" }] +); +addBodyOps( + "billing_header_array_mixed_with_prompts", + JSON.stringify({ + system: [ + { type: "text", text: "You are a helpful assistant." }, + { type: "text", text: "x-anthropic-billing-header: cc_version=2.1.36; cch=1;" }, + { type: "text", text: "Follow instructions carefully." }, + ], + }), + [{ op: "remove_system_billing_header" }] +); +addBodyOps( + "billing_header_plain_string_match", + JSON.stringify({ system: "x-anthropic-billing-header: cc_version=2.1.36;" }), + [{ op: "remove_system_billing_header" }] +); +addBodyOps("billing_header_plain_string_no_match", JSON.stringify({ system: "You are a helpful assistant." }), [ + { op: "remove_system_billing_header" }, +]); +addBodyOps("billing_header_missing_system", JSON.stringify({ model: "claude-3" }), [ + { op: "remove_system_billing_header" }, +]); +addBodyOps("billing_header_null_system", JSON.stringify({ system: null }), [ + { op: "remove_system_billing_header" }, +]); +addBodyOps( + "billing_header_mid_string_not_removed", + JSON.stringify({ + system: [{ type: "text", text: "Some preamble text x-anthropic-billing-header: cc_version=2.1.36;" }], + }), + [{ op: "remove_system_billing_header" }] +); +addBodyOps( + "billing_header_case_insensitive", + JSON.stringify({ system: [{ type: "text", text: "X-Anthropic-Billing-Header: cc_version=2.1.36;" }] }), + [{ op: "remove_system_billing_header" }] +); +addBodyOps( + "billing_header_various_formats", + JSON.stringify({ + system: [ + { type: "text", text: "x-anthropic-billing-header:cc_version=2.1.36" }, + { type: "text", text: " x-anthropic-billing-header: cc_version=2.2.0; cc_entrypoint=vscode;" }, + { type: "text", text: "x-anthropic-billing-header: " }, + ], + }), + [{ op: "remove_system_billing_header" }] +); +addBodyOps( + "billing_header_non_text_blocks_preserved", + JSON.stringify({ + system: [ + { type: "image", source: { type: "base64" } }, + { type: "text", text: "x-anthropic-billing-header: val" }, + { type: "text", text: "Keep this" }, + ], + }), + [{ op: "remove_system_billing_header" }] +); + +// --- set_cache_control_ttl (forwarder.ts applyCacheTtlOverrideToMessage) --- +addBodyOps( + "cache_ttl_system_array_ephemeral_1h", + JSON.stringify({ system: [{ type: "text", text: "sys", cache_control: { type: "ephemeral" } }] }), + [{ op: "set_cache_control_ttl", ttl: "1h" }] +); +addBodyOps( + "cache_ttl_messages_content_ephemeral_5m", + JSON.stringify({ + messages: [{ role: "user", content: [{ type: "text", text: "ctx", cache_control: { type: "ephemeral" } }] }], + }), + [{ op: "set_cache_control_ttl", ttl: "5m" }] +); +addBodyOps( + "cache_ttl_non_ephemeral_skipped", + JSON.stringify({ system: [{ type: "text", text: "sys", cache_control: { type: "persistent" } }] }), + [{ op: "set_cache_control_ttl", ttl: "1h" }] +); +addBodyOps( + "cache_ttl_no_cache_control_noop", + JSON.stringify({ system: [{ type: "text", text: "sys" }], messages: [{ role: "user", content: "hi" }] }), + [{ op: "set_cache_control_ttl", ttl: "1h" }] +); +addBodyOps( + "cache_ttl_overwrites_existing_ttl", + JSON.stringify({ + system: [{ type: "text", text: "sys", cache_control: { type: "ephemeral", ttl: "5m" } }], + }), + [{ op: "set_cache_control_ttl", ttl: "1h" }] +); + +// --- apply_thinking_signature_rectifier (thinking-signature-rectifier.test.ts) --- +addBodyOps( + "thinking_removes_thinking_and_signature_fields", + JSON.stringify({ + model: "claude-test", + messages: [ + { + role: "assistant", + content: [ + { type: "thinking", thinking: "t", signature: "sig_thinking" }, + { type: "text", text: "hello", signature: "sig_text_should_remove" }, + { type: "tool_use", id: "toolu_1", name: "WebSearch", input: { query: "q" }, signature: "sig_tool_should_remove" }, + { type: "redacted_thinking", data: "r", signature: "sig_redacted" }, + ], + }, + { role: "user", content: [{ type: "text", text: "hi" }] }, + ], + }), + [{ op: "apply_thinking_signature_rectifier" }] +); +addBodyOps("thinking_no_messages_noop", JSON.stringify({ model: "claude-test" }), [ + { op: "apply_thinking_signature_rectifier" }, +]); +addBodyOps( + "thinking_removes_top_level_when_tool_use_prefix_missing", + JSON.stringify({ + model: "claude-test", + thinking: { type: "enabled", budget_tokens: 1024 }, + messages: [ + { role: "assistant", content: [{ type: "tool_use", id: "toolu_1", name: "WebSearch", input: { query: "q" } }] }, + { role: "user", content: [{ type: "tool_result", tool_use_id: "toolu_1", content: "ok" }] }, + ], + }), + [{ op: "apply_thinking_signature_rectifier" }] +); +addBodyOps( + "thinking_missing_signature_field_removes_and_clears_top_level", + JSON.stringify({ + model: "claude-3-5-sonnet-20241022", + thinking: { type: "enabled", budget_tokens: 10000 }, + messages: [ + { + role: "assistant", + content: [ + { type: "thinking", thinking: "Let me analyze this..." }, + { type: "tool_use", id: "toolu_01", name: "WebSearch", input: { query: "test" } }, + ], + }, + { role: "user", content: [{ type: "tool_result", tool_use_id: "toolu_01", content: "search result" }] }, + ], + }), + [{ op: "apply_thinking_signature_rectifier" }] +); +addBodyOps( + "thinking_starts_with_thinking_block_top_level_kept", + JSON.stringify({ + model: "claude-test", + thinking: { type: "enabled", budget_tokens: 1024 }, + messages: [ + { + role: "assistant", + content: [ + { type: "thinking", thinking: "ok", signature: "sig" }, + { type: "tool_use", id: "toolu_1", name: "WebSearch", input: { query: "q" } }, + ], + }, + ], + }), + [{ op: "apply_thinking_signature_rectifier" }] +); + +// --- strip_private_params (forwarder.ts filterPrivateParameters) --- +addBodyOps( + "strip_private_params_nested", + JSON.stringify({ + model: "m", + _debug: true, + metadata: { user_id: "u1", _trace: "x" }, + messages: [{ role: "user", content: "hi", _extra: 1 }], + }), + [{ op: "strip_private_params" }] +); +addBodyOps( + "strip_private_params_arrays", + JSON.stringify({ items: [{ a: 1, _b: 2 }, { _c: 3, d: 4 }] }), + [{ op: "strip_private_params" }] +); +addBodyOps("strip_private_params_none_present", JSON.stringify({ model: "m", max_tokens: 10 }), [ + { op: "strip_private_params" }, +]); + +// --- op chaining --- +addBodyOps( + "chain_set_then_strip_then_ttl", + JSON.stringify({ + model: "old", + _junk: 1, + system: [{ type: "text", text: "sys", cache_control: { type: "ephemeral" } }], + }), + [ + { op: "set_top_level", key: "model", value: "new-model" }, + { op: "strip_private_params" }, + { op: "set_cache_control_ttl", ttl: "1h" }, + ] +); + +// --- normalize_response_input (response-input-rectifier.test.ts) --- +addBodyOps("normalize_response_input_string", JSON.stringify({ input: "hello" }), [ + { op: "normalize_response_input" }, +]); +addBodyOps("normalize_response_input_empty_string", JSON.stringify({ input: "" }), [ + { op: "normalize_response_input" }, +]); +addBodyOps( + "normalize_response_input_object", + JSON.stringify({ input: { role: "user", content: [{ type: "input_text", text: "hi" }] } }), + [{ op: "normalize_response_input" }] +); +addBodyOps( + "normalize_response_input_object_unrecognized_passthrough", + JSON.stringify({ input: { foo: "bar" } }), + [{ op: "normalize_response_input" }] +); +addBodyOps( + "normalize_response_input_array_passthrough", + JSON.stringify({ input: [{ type: "message", role: "user", content: [] }] }), + [{ op: "normalize_response_input" }] +); +addBodyOps("normalize_response_input_absent_noop", JSON.stringify({ model: "gpt-5-codex" }), [ + { op: "normalize_response_input" }, +]); +addBodyOps("normalize_response_input_null_passthrough", JSON.stringify({ input: null }), [ + { op: "normalize_response_input" }, +]); + +// --- normalize_response_input combined with set_top_level on responses-only keys --- +addBodyOps( + "normalize_response_input_with_set_top_level_reasoning", + JSON.stringify({ input: "hi", reasoning: { effort: "low" } }), + [ + { op: "set_top_level", key: "reasoning", value: { effort: "high" } }, + { op: "normalize_response_input" }, + ] +); +addBodyOps( + "normalize_response_input_with_set_top_level_text", + JSON.stringify({ input: "hi" }), + [ + { op: "set_top_level", key: "text", value: { format: { type: "text" } } }, + { op: "normalize_response_input" }, + ] +); +addBodyOps( + "normalize_response_input_with_set_top_level_parallel_tool_calls", + JSON.stringify({ input: { role: "user", content: [{ type: "input_text", text: "hi" }] } }), + [ + { op: "set_top_level", key: "parallel_tool_calls", value: false }, + { op: "normalize_response_input" }, + ] +); +addBodyOps( + "normalize_response_input_with_set_top_level_service_tier", + JSON.stringify({ input: "" }), + [ + { op: "set_top_level", key: "service_tier", value: "priority" }, + { op: "normalize_response_input" }, + ] +); +addBodyOps( + "normalize_response_input_with_set_top_level_stream_options", + JSON.stringify({ input: [{ type: "message", role: "user", content: [] }] }), + [ + { op: "set_top_level", key: "stream_options", value: { include_usage: true } }, + { op: "normalize_response_input" }, + ] +); + +await writeJSON("bodyops.json", bodyOpsCases); + +// =================================================================== +// detect.json +// =================================================================== + +interface DetectCase { + name: string; + bodyText: string; + expected: { isError: boolean; code?: string }; +} + +const detectCases: DetectCase[] = []; +function addDetect(name: string, bodyText: string) { + const detected = detectUpstreamErrorFromSseOrJsonText(bodyText, { maxJsonCharsForMessageCheck: 0 }); + detectCases.push({ + name, + bodyText, + expected: detected.isError ? { isError: true, code: detected.code } : { isError: false }, + }); +} + +addDetect("empty_body", ""); +addDetect("whitespace_only_body", " \n\t "); +addDetect("html_doctype", "502 Bad Gateway"); +addDetect("html_tag_only", "error"); +addDetect("not_html_angle_bracket_text", "not really html"); +addDetect("json_error_string_non_empty", JSON.stringify({ error: "invalid api key" })); +addDetect("json_error_object_with_message", JSON.stringify({ error: { message: "rate limited", type: "rate_limit_error" } })); +addDetect("json_error_empty_string_not_error", JSON.stringify({ error: "" })); +addDetect("json_error_null_not_error", JSON.stringify({ error: null })); +addDetect("json_no_error_field", JSON.stringify({ type: "message", content: [{ type: "text", text: "hi" }] })); +addDetect( + "json_message_keyword_not_checked_because_max_chars_zero", + JSON.stringify({ message: "an error occurred processing your request" }) +); +addDetect( + "openai_responses_failed_status", + JSON.stringify({ id: "resp_123", object: "response", status: "failed", error: { message: "server error", code: "server_error" } }) +); +addDetect( + "openai_responses_failed_event_type", + JSON.stringify({ type: "response.failed", response: { id: "resp_456", status: "failed", error: "boom" } }) +); +addDetect("openai_responses_completed_not_error", JSON.stringify({ id: "resp_789", object: "response", status: "completed" })); +addDetect("plain_array_not_error", JSON.stringify([1, 2, 3])); +addDetect("malformed_json_not_error", "{not valid json"); + +await writeJSON("detect.json", detectCases); + +console.log("done"); diff --git a/scripts/edge-fixtures/fixer-capture.ts b/scripts/edge-fixtures/fixer-capture.ts new file mode 100644 index 0000000000..31d95d6d8b --- /dev/null +++ b/scripts/edge-fixtures/fixer-capture.ts @@ -0,0 +1,1739 @@ +// Generates deterministic JSON fixtures for the Go ports of: +// - src/app/v1/_lib/proxy/response-fixer/* (edge/internal/fixer) +// - src/app/v1/_lib/proxy/client-abort-metering.ts (edge/internal/capture) +// - a new "compact SSE capture" concept (edge/internal/capture/compact.go) +// +// Run from repo root: `bun scripts/edge-fixtures/fixer-capture.ts` +// +// Import notes (see final report for details): +// - `@/` path aliases do not resolve under plain `bun run`, so this file +// uses relative imports. +// - response-handler.ts pulls in `server-only` (via its module graph) and +// cannot be imported from a plain script. `parseUsageFromResponseText` +// (and its helper `extractUsageMetrics`) are therefore reimplemented +// below as a Claude-SSE-focused subset sufficient for the synthetic +// streams used in compact.json -- NOT a byte-for-byte port of the full +// (Gemini/OpenAI-aware) original. `parseSSEData`, +// `resolveAnthropicStreamActualResponseModel`, and +// `extractThinkingSignatureModelFromStream` import cleanly and are used +// for real. + +import { EncodingFixer } from "../../src/app/v1/_lib/proxy/response-fixer/encoding-fixer"; +import { SseFixer } from "../../src/app/v1/_lib/proxy/response-fixer/sse-fixer"; +import { JsonFixer } from "../../src/app/v1/_lib/proxy/response-fixer/json-fixer"; +import { + createClientAbortMeteringObserver, + type ClientAbortMeteringObserver, +} from "../../src/app/v1/_lib/proxy/client-abort-metering"; +import { parseSSEData } from "../../src/lib/utils/sse"; +import { resolveAnthropicStreamActualResponseModel } from "../../src/app/v1/_lib/proxy/anthropic-actual-response-model"; + +const OUT_DIR = "tests/fixtures/edge"; +const enc = new TextEncoder(); +const dec = new TextDecoder(); + +function b64(bytes: Uint8Array): string { + return Buffer.from(bytes).toString("base64"); +} + +async function writeJSON(relPath: string, data: unknown): Promise { + const path = `${OUT_DIR}/${relPath}`; + await Bun.write(path, `${JSON.stringify(data, null, 2)}\n`); + console.log(`wrote ${path}`); +} + +// --------------------------------------------------------------------------- +// encoding.json +// --------------------------------------------------------------------------- + +type EncodingCase = { + name: string; + inputB64: string; + expectedDataB64: string; + expectedApplied: boolean; + expectedDetails: string | null; +}; + +function buildEncodingCases(): EncodingCase[] { + const cases: EncodingCase[] = []; + const run = (name: string, input: Uint8Array) => { + const res = new EncodingFixer().fix(input); + cases.push({ + name, + inputB64: b64(input), + expectedDataB64: b64(res.data), + expectedApplied: res.applied, + expectedDetails: res.details ?? null, + }); + }; + + run("valid_utf8_passthrough", enc.encode("Hello 世界")); + run("utf8_bom_stripped", new Uint8Array([0xef, 0xbb, 0xbf, ...enc.encode("Hello")])); + run("utf16_bom_stripped", new Uint8Array([0xff, 0xfe, 0x41, 0x00])); + run("null_bytes_stripped", new Uint8Array([0x48, 0x65, 0x00, 0x6c, 0x6c, 0x6f])); + run("invalid_utf8_lossy_fixed", new Uint8Array([0xc3, 0x28, 0x61])); + + // Extra WHATWG-decoder edge cases (maximal-subpart replacement semantics). + run("overlong_lead_then_valid_continuation", new Uint8Array([0xe0, 0x80, 0x80, 0x61])); // overlong encoding + run("truncated_multibyte_at_end", new Uint8Array([0x61, 0xe2, 0x82])); // "a" + incomplete 3-byte seq + run("lone_continuation_byte", new Uint8Array([0x61, 0x80, 0x62])); // "a" + stray continuation + "b" + run("surrogate_range_rejected", new Uint8Array([0xed, 0xa0, 0x80])); // encodes U+D800 (surrogate), invalid in UTF-8 + run("f4_upper_boundary", new Uint8Array([0xf4, 0x90, 0x80, 0x80])); // > U+10FFFF, invalid continuation for F4 + run("empty_input", new Uint8Array([])); + + return cases; +} + +// --------------------------------------------------------------------------- +// sse.json +// --------------------------------------------------------------------------- + +type SseCase = { + name: string; + inputB64: string; + expectedDataB64: string; + expectedApplied: boolean; +}; + +function buildSseCases(): SseCase[] { + const cases: SseCase[] = []; + const run = (name: string, input: string) => { + const res = new SseFixer().fix(enc.encode(input)); + cases.push({ + name, + inputB64: b64(enc.encode(input)), + expectedDataB64: b64(res.data), + expectedApplied: res.applied, + }); + }; + + run("valid_sse_passthrough", 'data: {"test": true}\n'); + run("missing_space_after_data_colon", 'data:{"test": true}\n'); + run("long_data_line", `data:${"a".repeat(100_000)}\n`); + run("bare_json_object_wrapped", '{"content": "hello"}\n'); + run("bare_json_array_wrapped", '[{"delta": {}}]\n'); + run("done_sentinel_wrapped", "[DONE]\n"); + run("comment_line_preserved", ": this is a comment\ndata: test\n"); + run("event_field_space_fixed", "event:message\ndata: test\n"); + run("id_field_space_fixed", "id:123\ndata: test\n"); + run("retry_field_space_fixed", "retry:1000\ndata: test\n"); + run("crlf_normalized", "data: test\r\ndata: test2\r\n"); + run("cr_only_normalized", "data: test\rdata: test2\r"); + run("data_capitalized_fixed", 'Data:{"test": true}\n'); + run("data_upper_fixed", 'DATA:{"test": true}\n'); + run("data_space_before_colon_fixed", 'data :{"test": true}\n'); + run("consecutive_blank_lines_collapsed", "data: test\n\n\n\ndata: test2\n"); + run("multiline_data_preserved", "data: line1\ndata: line2\n\n"); + + return cases; +} + +// --------------------------------------------------------------------------- +// json.json +// --------------------------------------------------------------------------- + +type JsonCase = { + name: string; + inputB64: string; + maxDepth: number; + maxSize: number; + expectedApplied: boolean; + expectedDetails: string | null; + // The repaired bytes are only meaningfully comparable via re-parse (the + // TS tests only assert JSON.parse succeeds / matches an object), so we + // record the parsed-back value (or null if parsing should fail / applied + // is false and we just compare raw bytes). + expectedDataB64: string; + expectAppliedDataParsesTo?: unknown; +}; + +function buildJsonCases(): JsonCase[] { + const cases: JsonCase[] = []; + const run = ( + name: string, + input: string, + maxDepth: number, + maxSize: number, + expectAppliedDataParsesTo?: unknown + ) => { + const fixer = new JsonFixer({ maxDepth, maxSize }); + const res = fixer.fix(enc.encode(input)); + cases.push({ + name, + inputB64: b64(enc.encode(input)), + maxDepth, + maxSize, + expectedApplied: res.applied, + expectedDetails: res.details ?? null, + expectedDataB64: b64(res.data), + expectAppliedDataParsesTo, + }); + }; + + run("valid_json_passthrough", '{"a":1}', 200, 1024 * 1024); + run("unclosed_object", '{"key":"value"', 200, 1024 * 1024, { key: "value" }); + run("unclosed_array", "[1, 2, 3", 200, 1024 * 1024, [1, 2, 3]); + run("unclosed_string", '{"key":"val', 200, 1024 * 1024, { key: "val" }); + run("trailing_comma_object", '{"a": 1,}', 200, 1024 * 1024, { a: 1 }); + run("trailing_comma_array", "[1, 2,]", 200, 1024 * 1024, [1, 2]); + run("missing_value_after_colon", '{"key":', 200, 1024 * 1024, { key: null }); + run("nested_unclosed", '{"outer": {"inner": [1, 2', 200, 1024 * 1024, { + outer: { inner: [1, 2] }, + }); + run("exceeds_max_depth_unchanged", '{"a":{"b":{"c":{"d":', 3, 1024 * 1024); + run("exceeds_max_size_unchanged", '{"key":"very long value"}', 200, 10); + + return cases; +} + +// --------------------------------------------------------------------------- +// nonstream.json -- FixNonStream(body, cfg): encoding -> json, whole body. +// --------------------------------------------------------------------------- + +type NonStreamCase = { + name: string; + inputB64: string; + cfg: { fixEncoding: boolean; fixTruncatedJson: boolean; maxJsonDepth: number; maxFixSize: number }; + expectedEncodingApplied: boolean; + expectedJsonApplied: boolean; + expectedHit: boolean; + expectedDataParsesAsJson: boolean; + expectedDataB64ForNonJsonCase: string | null; +}; + +function buildNonStreamCases(): NonStreamCase[] { + const cfg = { fixEncoding: true, fixTruncatedJson: true, maxJsonDepth: 200, maxFixSize: 1024 * 1024 }; + + const run = (name: string, input: Uint8Array): NonStreamCase => { + let data: Uint8Array = input; + let encodingApplied = false; + let jsonApplied = false; + + const encRes = new EncodingFixer().fix(data); + if (encRes.applied) { + encodingApplied = true; + data = encRes.data; + } + const jsonFixer = new JsonFixer({ maxDepth: cfg.maxJsonDepth, maxSize: cfg.maxFixSize }); + const jsonRes = jsonFixer.fix(data); + if (jsonRes.applied) { + jsonApplied = true; + data = jsonRes.data; + } + + let parsesAsJson = false; + try { + JSON.parse(dec.decode(data)); + parsesAsJson = true; + } catch { + parsesAsJson = false; + } + + return { + name, + inputB64: b64(input), + cfg, + expectedEncodingApplied: encodingApplied, + expectedJsonApplied: jsonApplied, + expectedHit: encodingApplied || jsonApplied, + expectedDataParsesAsJson: parsesAsJson, + expectedDataB64ForNonJsonCase: parsesAsJson ? null : b64(data), + }; + }; + + return [ + run("valid_json_body", enc.encode('{"ok":true}')), + run("truncated_json_with_bom", new Uint8Array([0xef, 0xbb, 0xbf, ...enc.encode('{"a":1,"b":')])), + run( + "invalid_utf8_and_truncated", + new Uint8Array([0x7b, 0x22, 0x61, 0x22, 0x3a, 0x22, 0xc3, 0x28]) // {"a":" + ), + run("not_json_passthrough", enc.encode("plain text body")), + ]; +} + +// --------------------------------------------------------------------------- +// stream.json -- drives EncodingFixer -> SseFixer -> per-line JsonFixer +// through a reimplementation of index.ts's ChunkBuffer buffering, split +// across several chunk patterns. (See header comment: driving the real +// ResponseFixer.processStream would require a ProxySession/session-manager/ +// DB stub; this replicates the equivalent fixer sequence using the real +// fixer classes plus a from-spec reimplementation of ChunkBuffer.) +// --------------------------------------------------------------------------- + +const LF = 0x0a; +const CR = 0x0d; + +class ChunkBufferSim { + private chunks: Uint8Array[] = []; + private head = 0; + private headOffset = 0; + private total = 0; + private processableEnd = 0; + private pendingCR = false; + + get length(): number { + return this.total; + } + + push(chunk: Uint8Array): void { + if (chunk.length === 0) return; + const prevTotal = this.total; + this.chunks.push(chunk); + this.total += chunk.length; + + if (this.pendingCR) { + this.processableEnd = chunk[0] === LF ? prevTotal + 1 : prevTotal; + this.pendingCR = false; + } + + for (let i = 0; i < chunk.length; i += 1) { + const b = chunk[i]; + if (b === LF) { + this.processableEnd = prevTotal + i + 1; + continue; + } + if (b !== CR) continue; + if (i + 1 < chunk.length) { + if (chunk[i + 1] !== LF) this.processableEnd = prevTotal + i + 1; + continue; + } + this.pendingCR = true; + } + } + + findProcessableEnd(): number { + if (this.total === 0) return 0; + if (this.pendingCR) return 0; + return this.processableEnd; + } + + take(size: number): Uint8Array { + if (size <= 0) return new Uint8Array(0); + const out = new Uint8Array(size); + let outOffset = 0; + while (outOffset < size) { + const chunk = this.chunks[this.head]; + const available = chunk.length - this.headOffset; + const toCopy = Math.min(available, size - outOffset); + out.set(chunk.subarray(this.headOffset, this.headOffset + toCopy), outOffset); + outOffset += toCopy; + this.headOffset += toCopy; + this.total -= toCopy; + if (this.headOffset >= chunk.length) { + this.head += 1; + this.headOffset = 0; + } + } + if (this.head > 64) { + this.chunks.splice(0, this.head); + this.head = 0; + } + this.processableEnd = Math.max(0, this.processableEnd - size); + return out; + } + + drain(): Uint8Array { + const out = this.take(this.total); + this.chunks = []; + this.head = 0; + this.headOffset = 0; + this.total = 0; + this.processableEnd = 0; + this.pendingCR = false; + return out; + } +} + +const SSE_DATA_PREFIX_WITH_SPACE = enc.encode("data: "); + +function fixMaybeDataJsonLine( + line: Uint8Array, + jsonFixer: JsonFixer +): { line: Uint8Array; applied: boolean } { + const prefix = enc.encode("data:"); + if (line.length < prefix.length) return { line, applied: false }; + for (let i = 0; i < prefix.length; i += 1) { + if (line[i] !== prefix[i]) return { line, applied: false }; + } + let payloadStart = prefix.length; + if (payloadStart < line.length && line[payloadStart] === 0x20) payloadStart += 1; + const payload = line.subarray(payloadStart); + const res = jsonFixer.fix(payload); + if (!res.applied) return { line, applied: false }; + const out = new Uint8Array(SSE_DATA_PREFIX_WITH_SPACE.length + res.data.length); + out.set(SSE_DATA_PREFIX_WITH_SPACE, 0); + out.set(res.data, SSE_DATA_PREFIX_WITH_SPACE.length); + return { line: out, applied: true }; +} + +function fixSseJsonLines(data: Uint8Array, jsonFixer: JsonFixer): Uint8Array { + let lineStart = 0; + const parts: Uint8Array[] = []; + for (let i = 0; i < data.length; i += 1) { + if (data[i] !== LF) continue; + const line = data.subarray(lineStart, i); + const fixed = fixMaybeDataJsonLine(line, jsonFixer); + parts.push(fixed.applied ? fixed.line : line); + parts.push(new Uint8Array([LF])); + lineStart = i + 1; + } + if (lineStart < data.length) { + const line = data.subarray(lineStart); + const fixed = fixMaybeDataJsonLine(line, jsonFixer); + parts.push(fixed.applied ? fixed.line : line); + } + let total = 0; + for (const p of parts) total += p.length; + const out = new Uint8Array(total); + let off = 0; + for (const p of parts) { + out.set(p, off); + off += p.length; + } + return out; +} + +type StreamFixtureConfig = { + fixEncoding: boolean; + fixSseFormat: boolean; + fixTruncatedJson: boolean; + maxJsonDepth: number; + maxFixSize: number; + // Not part of ResponseFixerConfig: mirrors session.originalFormat, only + // consulted by filterInertResponsesChatCompletionChunks ("response" only). + format?: string; +}; + +// Local reimplementation of ResponseFixer.filterInertResponsesChatCompletionChunks +// (response-fixer/index.ts) -- avoids importing index.ts, which pulls in +// SessionManager/session (see file header note on why response-handler.ts +// et al. cannot be imported from a plain bun script). +function hasMeaningfulValueLocal(value: unknown): boolean { + if (value == null) return false; + if (typeof value === "string") return value.length > 0; + if (Array.isArray(value)) return value.length > 0; + if (value && typeof value === "object") return Object.keys(value).length > 0; + return true; +} + +function isInertChatCompletionChoiceLocal(choice: unknown): boolean { + if (!choice || typeof choice !== "object") return false; + const c = choice as Record; + if (c.finish_reason != null) return false; + const delta = c.delta; + if (!delta || typeof delta !== "object") return true; + for (const [key, value] of Object.entries(delta as Record)) { + if (key === "role") continue; + if (hasMeaningfulValueLocal(value)) return false; + } + return true; +} + +function isInertChatCompletionChunkPayloadLocal(payload: unknown): boolean { + if (!payload || typeof payload !== "object") return false; + const p = payload as Record; + if (p.object !== "chat.completion.chunk") return false; + if (hasMeaningfulValueLocal(p.usage)) return false; + const choices = p.choices; + if (!Array.isArray(choices) || choices.length === 0) return false; + return choices.every(isInertChatCompletionChoiceLocal); +} + +function filterInertResponsesChatCompletionChunksLocal( + format: string | undefined, + data: Uint8Array +): { data: Uint8Array; applied: boolean } { + if (format !== "response") return { data, applied: false }; + const text = dec.decode(data); + if (!text.includes('"chat.completion.chunk"')) return { data, applied: false }; + + const lines = text.split("\n"); + const out: string[] = []; + let applied = false; + let skipNextBlank = false; + + for (let i = 0; i < lines.length; i += 1) { + const line = lines[i]; + const hasLineBreak = i < lines.length - 1; + + if (skipNextBlank && (line === "" || line === "\r")) { + skipNextBlank = false; + continue; + } + skipNextBlank = false; + + let isInert = false; + if (line.startsWith("data:")) { + let payloadText = line.slice(5); + if (payloadText.startsWith(" ")) payloadText = payloadText.slice(1); + if (payloadText.startsWith("{")) { + try { + isInert = isInertChatCompletionChunkPayloadLocal(JSON.parse(payloadText)); + } catch { + isInert = false; + } + } + } + + if (isInert) { + applied = true; + skipNextBlank = true; + continue; + } + + out.push(line); + if (hasLineBreak) out.push("\n"); + } + + if (!applied) return { data, applied: false }; + return { data: enc.encode(out.join("")), applied: true }; +} + +function runStreamPipeline(chunks: Uint8Array[], cfg: StreamFixtureConfig): Uint8Array { + const encodingFixer = cfg.fixEncoding ? new EncodingFixer() : null; + const sseFixer = cfg.fixSseFormat ? new SseFixer() : null; + const jsonFixer = cfg.fixTruncatedJson ? new JsonFixer({ maxDepth: cfg.maxJsonDepth, maxSize: cfg.maxFixSize }) : null; + + const apply = (input: Uint8Array): Uint8Array => { + let data = input; + if (encodingFixer) { + const res = encodingFixer.fix(data); + if (res.applied) data = res.data; + } + if (sseFixer) { + const res = sseFixer.fix(data); + if (res.applied) data = res.data; + } + if (jsonFixer) { + data = fixSseJsonLines(data, jsonFixer); + } + const filtered = filterInertResponsesChatCompletionChunksLocal(cfg.format, data); + if (filtered.applied) data = filtered.data; + return data; + }; + + const buffer = new ChunkBufferSim(); + let passthrough = false; + const outParts: Uint8Array[] = []; + + for (const chunk of chunks) { + if (passthrough) { + outParts.push(chunk); + continue; + } + if (buffer.length + chunk.length > cfg.maxFixSize) { + passthrough = true; + outParts.push(buffer.drain()); + outParts.push(chunk); + continue; + } + buffer.push(chunk); + const end = buffer.findProcessableEnd(); + if (end <= 0) continue; + outParts.push(apply(buffer.take(end))); + } + if (buffer.length > 0) { + outParts.push(apply(buffer.drain())); + } + + let total = 0; + for (const p of outParts) total += p.length; + const out = new Uint8Array(total); + let off = 0; + for (const p of outParts) { + out.set(p, off); + off += p.length; + } + return out; +} + +function splitBytes(data: Uint8Array, pattern: "single" | "byte" | number[]): Uint8Array[] { + if (pattern === "single") return [data]; + if (pattern === "byte") return Array.from(data).map((b) => new Uint8Array([b])); + const out: Uint8Array[] = []; + let offset = 0; + for (const size of pattern) { + if (offset >= data.length) break; + out.push(data.subarray(offset, Math.min(offset + size, data.length))); + offset += size; + } + if (offset < data.length) out.push(data.subarray(offset)); + return out; +} + +type StreamCase = { + name: string; + fullInputB64: string; + splitPattern: string; + chunksB64: string[]; + cfg: StreamFixtureConfig; + expectedOutputB64: string; +}; + +function buildStreamCases(): StreamCase[] { + const cfg: StreamFixtureConfig = { + fixEncoding: true, + fixSseFormat: true, + fixTruncatedJson: true, + maxJsonDepth: 200, + maxFixSize: 1024 * 1024, + }; + + const sseText = + 'event: message_start\ndata: {"type":"message_start","message":{"model":"claude-x"}}\n\n' + + 'event: content_block_delta\ndata:{"type":"content_block_delta","delta":{"text":"hi"}}\n\n' + + 'event: message_stop\ndata: {"type":"message_stop"\n\n'; // truncated json on purpose + + const utf8Payload = "Hello 世界 😀"; // includes emoji (surrogate pair in UTF-16, 4-byte UTF-8) + const mixedText = `data: {"content":"${utf8Payload}"}\n\n`; + + // Bytes with an invalid UTF-8 sequence embedded mid-stream, and a BOM at + // the very start (covers "split mid multi-byte sequence"). + const invalidUtf8Bytes = new Uint8Array([ + ...enc.encode("data: {\"a\":\""), + 0xc3, + 0x28, // invalid 2-byte lead + ...enc.encode('"}\n\n'), + ]); + + const cases: StreamCase[] = []; + const add = ( + name: string, + full: Uint8Array, + splitPattern: string, + chunks: Uint8Array[], + caseCfg: StreamFixtureConfig = cfg + ) => { + const expected = runStreamPipeline(chunks, caseCfg); + cases.push({ + name, + fullInputB64: b64(full), + splitPattern, + chunksB64: chunks.map(b64), + cfg: caseCfg, + expectedOutputB64: b64(expected), + }); + }; + + { + const full = enc.encode(sseText); + add("sse_single_chunk", full, "single", splitBytes(full, "single")); + add("sse_byte_at_a_time", full, "byte", splitBytes(full, "byte")); + add("sse_split_mid_line", full, "custom_mid_line", splitBytes(full, [10, 25, 9999])); + add("sse_split_arbitrary_sizes", full, "custom_arbitrary", splitBytes(full, [3, 7, 13, 21, 5, 9999])); + } + { + const full = enc.encode(mixedText); + // Split precisely inside the 4-byte emoji UTF-8 sequence. + const emojiByteOffset = enc.encode('data: {"content":"Hello 世界 "').length; + add( + "utf8_split_mid_multibyte", + full, + "custom_mid_utf8", + splitBytes(full, [emojiByteOffset + 1, 9999]) + ); + add("utf8_split_mid_multibyte_2", full, "custom_mid_utf8_2", splitBytes(full, [emojiByteOffset + 2, 9999])); + } + { + add("invalid_utf8_split", invalidUtf8Bytes, "custom_invalid_utf8", splitBytes(invalidUtf8Bytes, [14, 9999])); + } + { + // CRLF split exactly at the CR/LF boundary across chunks. + const full = enc.encode('data: test\r\ndata: test2\r\n'); + add("crlf_split_at_boundary", full, "custom_crlf", splitBytes(full, [11, 9999])); + } + + { + // response format: an inert chat.completion.chunk frame interleaved + // with Responses-shaped SSE events must be dropped, split across chunk + // boundaries. + const responseCfg: StreamFixtureConfig = { ...cfg, format: "response" }; + const inertChunk = + 'data: {"id":"c1","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"role":"assistant"},"finish_reason":null}]}\n\n'; + const respCreated = + 'event: response.created\ndata: {"type":"response.created","response":{"id":"resp_1","status":"in_progress"}}\n\n'; + const respCompleted = + 'event: response.completed\ndata: {"type":"response.completed","response":{"id":"resp_1","status":"completed"}}\n\n'; + const full = enc.encode(respCreated + inertChunk + respCompleted); + add( + "response_format_drops_inert_chat_completion_chunk_single", + full, + "single", + splitBytes(full, "single"), + responseCfg + ); + add( + "response_format_drops_inert_chat_completion_chunk_byte", + full, + "byte", + splitBytes(full, "byte"), + responseCfg + ); + add( + "response_format_drops_inert_chat_completion_chunk_split", + full, + "custom_mid_line", + splitBytes(full, [40, 90, 9999]), + responseCfg + ); + + // A non-inert chat.completion.chunk (has content) must be kept. + const nonInertChunk = + 'data: {"id":"c1","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"hi"},"finish_reason":null}]}\n\n'; + const full2 = enc.encode(respCreated + nonInertChunk + respCompleted); + add( + "response_format_keeps_non_inert_chat_completion_chunk", + full2, + "single", + splitBytes(full2, "single"), + responseCfg + ); + + // Non-"response" formats must never apply the filter, even if the + // marker string is present. + const full3 = enc.encode(respCreated + inertChunk + respCompleted); + add( + "claude_format_does_not_filter_chat_completion_chunk", + full3, + "single", + splitBytes(full3, "single"), + cfg + ); + } + + return cases; +} + +// --------------------------------------------------------------------------- +// metering.json -- 18 scenarios ported from client-abort-metering.test.ts +// --------------------------------------------------------------------------- + +type ObserveCall = { + chunkB64: string; + errorSeen: boolean; + drainComplete: boolean; +}; + +type MeteringCase = { + name: string; + format: string; + attachedMaxFrameBytes: number | null; + // Sequence of actions: either an "observe" (with the chunk + expected + // return) or a "detach" marker. + actions: Array<{ kind: "observe"; chunkB64: string; errorSeen: boolean; drainComplete: boolean } | { kind: "detach" }>; + finish: { + text: string; + sawContent: boolean; + terminalSeen: boolean; + incompleteSeen: boolean; + retainedBytes: number; + skippedOversizedFrames: number; + protocolFailure: { afterContent: boolean; verdict: string; eventName: string | null } | null; + }; + // For the one test that calls finish() twice, the second snapshot. + finishAgain?: MeteringCase["finish"]; +}[]; + +function buildMeteringCases() { + const cases: Array<{ + name: string; + format: string; + attachedMaxFrameBytes: number | null; + actions: Array< + | { kind: "observe"; chunkB64: string; errorSeen: boolean; drainComplete: boolean } + | { kind: "detach" } + >; + finishAfterActions: number; + finish: { + text: string; + sawContent: boolean; + terminalSeen: boolean; + incompleteSeen: boolean; + retainedBytes: number; + skippedOversizedFrames: number; + protocolFailure: { afterContent: boolean; verdict: string; eventName: string | null } | null; + }; + finishAgain?: { + text: string; + sawContent: boolean; + terminalSeen: boolean; + incompleteSeen: boolean; + retainedBytes: number; + skippedOversizedFrames: number; + protocolFailure: { afterContent: boolean; verdict: string; eventName: string | null } | null; + }; + }> = []; + + function makeObserver(format: string, attachedMaxFrameBytes: number | null): ClientAbortMeteringObserver { + return attachedMaxFrameBytes != null + ? createClientAbortMeteringObserver(format as any, { attachedMaxFrameBytes }) + : createClientAbortMeteringObserver(format as any); + } + + function snapshotOf(observer: ClientAbortMeteringObserver) { + const s = observer.finish(); + return { + text: s.text, + sawContent: s.sawContent, + terminalSeen: s.terminalSeen, + incompleteSeen: s.incompleteSeen, + retainedBytes: s.retainedBytes, + skippedOversizedFrames: s.skippedOversizedFrames, + protocolFailure: s.protocolFailure + ? { + afterContent: s.protocolFailure.afterContent, + verdict: s.protocolFailure.verdict, + eventName: s.protocolFailure.eventName, + } + : null, + }; + } + + function run( + name: string, + format: string, + attachedMaxFrameBytes: number | null, + steps: Array<{ chunk: Uint8Array } | { detach: true } | { finishAndContinue: true }> + ) { + const observer = makeObserver(format, attachedMaxFrameBytes); + const actions: Array< + | { kind: "observe"; chunkB64: string; errorSeen: boolean; drainComplete: boolean } + | { kind: "detach" } + > = []; + let finishAgain: MeteringCase[number]["finish"] | undefined; + + for (const step of steps) { + if ("detach" in step) { + observer.switchToDetachedMode(); + actions.push({ kind: "detach" }); + continue; + } + if ("finishAndContinue" in step) { + // handled by caller after loop via extra observe calls post-finish + continue; + } + const result = observer.observe(step.chunk); + actions.push({ + kind: "observe", + chunkB64: b64(step.chunk), + errorSeen: result.errorSeen, + drainComplete: result.drainComplete, + }); + } + + const finish = snapshotOf(observer); + cases.push({ + name, + format, + attachedMaxFrameBytes, + actions, + finishAfterActions: actions.length, + finish, + finishAgain, + }); + } + + // 1. keeps only compact Responses accounting evidence + run("responses_compact_accounting_evidence", "response", null, [ + { + chunk: enc.encode( + `event: response.output_text.delta\ndata: ${JSON.stringify({ + type: "response.output_text.delta", + delta: "x".repeat(32 * 1024), + })}\n\n` + ), + }, + { + chunk: enc.encode( + `event: response.completed\ndata: ${JSON.stringify({ + type: "response.completed", + response: { + id: "resp_1", + model: "gpt-test", + output: [{ content: [{ text: "discard me" }] }], + usage: { input_tokens: 10, output_tokens: 5 }, + }, + })}\n\n` + ), + }, + ]); + + // 2. retains Claude initial and terminal usage until message_stop + run("claude_initial_and_terminal_usage", "claude", null, [ + { + chunk: enc.encode( + `event: message_start\ndata: ${JSON.stringify({ + type: "message_start", + message: { model: "claude-test", usage: { input_tokens: 20, output_tokens: 1 } }, + })}\n\n` + ), + }, + { + chunk: enc.encode( + `event: message_delta\ndata: ${JSON.stringify({ + type: "message_delta", + usage: { output_tokens: 7 }, + })}\n\n` + ), + }, + { chunk: enc.encode(`event: message_stop\ndata: {"type":"message_stop"}\n\n`) }, + ]); + + // 3. skips an oversized content frame and resumes at the next frame boundary + run("skips_oversized_content_frame", "response", null, [ + { + chunk: enc.encode( + `event: response.output_text.delta\ndata: ${JSON.stringify({ + type: "response.output_text.delta", + delta: "x".repeat(70 * 1024), + })}\n\nevent: response.completed\ndata: ${JSON.stringify({ + type: "response.completed", + response: { usage: { input_tokens: 10, output_tokens: 5 } }, + })}\n\n` + ), + }, + ]); + + // 4. stops metering at an OpenAI done marker without requiring usage + run("openai_done_marker", "openai", null, [{ chunk: enc.encode("data: [DONE]\n\n") }]); + + // 5. treats a valid Responses terminal without usage as complete + run("responses_terminal_without_usage", "response", null, [ + { + chunk: enc.encode( + 'event: response.completed\ndata: {"type":"response.completed","response":{"status":"completed"}}\n\n' + ), + }, + ]); + + // 6. does not treat standard nullable Responses error fields as protocol failures + run("responses_nullable_error_not_failure", "response", null, [ + { + chunk: enc.encode( + 'event: response.completed\ndata: {"type":"response.completed","response":{"status":"completed","error":null,"usage":{"input_tokens":4,"output_tokens":2}}}\n\n' + ), + }, + ]); + + // 7. retains zero-valued usage without making it a completion requirement + run("zero_valued_usage_retained", "response", null, [ + { + chunk: enc.encode( + 'event: response.completed\ndata: {"type":"response.completed","response":{"usage":{"input_tokens":0,"output_tokens":0}}}\n\n' + ), + }, + ]); + + // 8. does not treat initial Claude usage as a completed stream + run("claude_initial_usage_not_terminal", "claude", null, [ + { + chunk: enc.encode( + 'event: message_start\ndata: {"type":"message_start","message":{"usage":{"input_tokens":12,"output_tokens":1}}}\n\n' + ), + }, + ]); + + // 9. combines an OpenAI usage chunk with a later done marker across arbitrary splits + { + const text = `data: ${JSON.stringify({ + id: "chatcmpl_1", + choices: [], + usage: { prompt_tokens: 12, completion_tokens: 4 }, + })}\r\n\r\ndata: [DONE]\r\n\r\n`; + const bytes = enc.encode(text); + const steps: Array<{ chunk: Uint8Array }> = []; + for (let offset = 0; offset < bytes.length; offset += 7) { + steps.push({ chunk: bytes.subarray(offset, offset + 7) }); + } + run("openai_usage_then_done_across_splits", "openai", null, steps); + } + + // 10. waits for OpenAI usage after finish_reason before ending the drain + run("openai_waits_for_usage_after_finish_reason", "openai", null, [ + { chunk: enc.encode('data: {"choices":[{"finish_reason":"stop"}],"usage":null}\n\n') }, + { + chunk: enc.encode( + 'data: {"choices":[],"usage":{"prompt_tokens":12,"completion_tokens":4}}\n\n' + ), + }, + ]); + + // 11. uses the last Gemini NDJSON usage and finishReason as terminal evidence + run("gemini_last_ndjson_usage_and_finish", "gemini", null, [ + { + chunk: enc.encode( + `${JSON.stringify({ + candidates: [{ content: { parts: [{ text: "discard" }] } }], + usageMetadata: { promptTokenCount: 10, candidatesTokenCount: 1 }, + })}\n` + ), + }, + { + chunk: enc.encode( + `${JSON.stringify({ + candidates: [{ finishReason: "STOP" }], + usageMetadata: { promptTokenCount: 10, candidatesTokenCount: 8 }, + })}\n` + ), + }, + ]); + + // 12. retains compact protocol errors without retaining content + run("compact_protocol_errors_no_content", "response", null, [ + { + chunk: enc.encode( + `event: error\ndata: ${JSON.stringify({ + type: "response.error", + error: { code: "upstream_failed", message: "failure" }, + debug: "x".repeat(32 * 1024), + })}\n\n` + ), + }, + ]); + + // 13. compacts extended usage, metadata, cache, and signature evidence + run("compacts_extended_usage_metadata_signature", "response", null, [ + { + chunk: enc.encode( + `event: response.in_progress\ndata: ${JSON.stringify({ + id: "resp_extended", + model: "gpt-extended", + prompt_cache_key: "cache-key", + service_tier: "priority", + status: "in_progress", + type: "response.in_progress", + message: { id: "message-1", model: "gpt-message", usage: { input_tokens: 1 } }, + delta: { + type: "signature_delta", + stop_reason: "end_turn", + signature: "signed-model", + usage: { output_tokens: 2 }, + }, + usage: { + input_tokens: 10, + output_tokens: 3, + cache_creation_input_tokens: 2, + cache_creation_5m_input_tokens: 1, + cache_creation_1h_input_tokens: 1, + cache_read_input_tokens: 4, + input_tokens_details: { cached_tokens: 4, cache_write_tokens: 2 }, + prompt_tokens_details: { cached_tokens: 4, cache_write_tokens: 2 }, + cache_creation: { ephemeral_5m_input_tokens: 1, ephemeral_1h_input_tokens: 1 }, + candidatesTokensDetails: [null, {}, { modality: "TEXT", tokenCount: 2 }, { tokenCount: 1 }], + promptTokensDetails: [{ modality: "IMAGE", tokenCount: 3 }], + }, + usageMetadata: { promptTokenCount: 10, candidatesTokenCount: 3 }, + choices: [null, {}, { finish_reason: "stop" }], + candidates: [null, {}, { finishReason: "STOP" }], + ignored: "not retained", + })}\n\n` + ), + }, + { + chunk: enc.encode( + `event: response.completed\ndata: ${JSON.stringify({ + type: "response.completed", + response: { + id: "resp_extended", + model: "gpt-extended", + service_tier: "priority", + usage: { input_tokens: 10, output_tokens: 3 }, + }, + })}\n\n` + ), + }, + ]); + + // 14. handles comments, multi-line data, bare JSON tails, and malformed frames + run("comments_multiline_bare_json_malformed", "gemini-cli", null, [ + { chunk: new Uint8Array() }, + { + chunk: enc.encode( + ': keepalive\rretry: 1000\revent: message\rdata: {"usageMetadata":\rdata: {"promptTokenCount":10,"candidatesTokenCount":2}}\r\r' + ), + }, + { chunk: enc.encode("data: true\n\n") }, + { chunk: enc.encode("data: not-json\n\n") }, + { chunk: enc.encode("data: still-not-json\n\n") }, + { + chunk: enc.encode( + JSON.stringify({ + candidates: [{ finishReason: "STOP" }], + usageMetadata: { promptTokenCount: 10, candidatesTokenCount: 2 }, + }) + ), + }, + ]); + + // 15. recovers after an oversized bare JSON line and ignores post-finish input + // (two finish() calls; the second must equal the first) + { + const observer = createClientAbortMeteringObserver("gemini" as any); + const actions: MeteringCase[number]["actions"] = []; + const obs = (chunk: Uint8Array) => { + const r = observer.observe(chunk); + actions.push({ kind: "observe", chunkB64: b64(chunk), errorSeen: r.errorSeen, drainComplete: r.drainComplete }); + }; + obs(enc.encode(`{"ignored":"${"x".repeat(70 * 1024)}"}\n`)); + obs( + enc.encode( + `${JSON.stringify({ + candidates: [{ finishReason: "STOP" }], + usageMetadata: { promptTokenCount: 4, candidatesTokenCount: 2 }, + })}` + ) + ); + const first = snapshotOf(observer); + const finishAfterActions = actions.length; + obs(enc.encode('{"error":true}\n')); + const second = snapshotOf(observer); + cases.push({ + name: "recovers_after_oversized_bare_json_ignores_post_finish", + format: "gemini", + attachedMaxFrameBytes: null, + actions, + finishAfterActions, + finish: first, + finishAgain: second, + }); + } + + // 16. does not fabricate completion from an oversized unvalidated terminal frame + run("no_fabricated_completion_oversized_terminal", "response", null, [ + { + chunk: enc.encode( + 'event: response.completed\ndata: {"type":"response.completed","padding":"' + "x".repeat(70 * 1024) + ), + }, + { chunk: enc.encode('"}\n\n') }, + ]); + + // 17. recovers at the next NDJSON line after tightening an in-flight frame + run("recovers_at_next_ndjson_line_after_tightening", "gemini", 128 * 1024, [ + { + chunk: enc.encode(`{"candidates":[{"content":{"parts":[{"text":"${"x".repeat(70 * 1024)}`), + }, + { detach: true }, + { + chunk: enc.encode( + '"}]}}]}\n{"candidates":[{"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":7,"candidatesTokenCount":3}}\n' + ), + }, + ]); + + // 18/19. it.each -- Responses incomplete (event-named and bare-data variants) + run("responses_incomplete_with_event_name", "response", null, [ + { + chunk: enc.encode( + 'event: response.incomplete\ndata: {"type":"response.incomplete","response":{"status":"incomplete"}}\n\n' + ), + }, + ]); + run("responses_incomplete_bare_data", "response", null, [ + { + chunk: enc.encode('data: {"type":"response.incomplete","response":{"status":"incomplete"}}\n\n'), + }, + ]); + + // 20. records protocol failure after content independently from completion + run("protocol_failure_after_content", "response", null, [ + { + chunk: enc.encode( + 'event: response.output_text.delta\ndata: {"type":"response.output_text.delta","delta":"ok"}\n\n' + ), + }, + { + chunk: enc.encode( + 'event: response.failed\ndata: {"type":"response.failed","response":{"status":"failed"}}\n\n' + ), + }, + ]); + + return cases; +} + +// --------------------------------------------------------------------------- +// compact.json -- synthetic Anthropic SSE streams + independently-derived +// expected compact output, cross-checked against usage/model extraction. +// --------------------------------------------------------------------------- + +// Minimal Claude-focused reimplementation of extractUsageMetrics (see file +// header: the real one lives in response-handler.ts, which cannot be +// imported standalone). Only handles the fields our synthetic fixtures use. +function extractUsageMetricsSimplified(value: unknown): Record | null { + if (!value || typeof value !== "object") return null; + const usage = value as Record; + const result: Record = {}; + let hasAny = false; + for (const field of [ + "input_tokens", + "output_tokens", + "cache_creation_input_tokens", + "cache_creation_5m_input_tokens", + "cache_creation_1h_input_tokens", + "cache_read_input_tokens", + ]) { + const v = usage[field]; + if (typeof v === "number") { + result[field] = v; + hasAny = true; + } + } + return hasAny ? result : null; +} + +// Minimal Claude-SSE-focused reimplementation of parseUsageFromResponseText's +// message_start/message_delta merge logic (see file header for why). +function parseUsageFromResponseTextSimplified(sseText: string): Record | null { + const events = parseSSEData(sseText); + let messageStartUsage: Record | null = null; + let messageDeltaUsage: Record | null = null; + const merge = (base: Record | null, patch: Record) => + base ? { ...base, ...patch } : { ...patch }; + + for (const event of events) { + if (typeof event.data !== "object" || !event.data) continue; + const data = event.data as Record; + if (event.event === "message_start") { + let usageValue: unknown = null; + if (data.message && typeof data.message === "object") { + usageValue = (data.message as Record).usage; + } + if (!usageValue) usageValue = data.usage; + const extracted = extractUsageMetricsSimplified(usageValue); + if (extracted) messageStartUsage = merge(messageStartUsage, extracted); + } + if (event.event === "message_delta") { + let usageValue: unknown = data.usage; + if (!usageValue && data.delta && typeof data.delta === "object") { + usageValue = (data.delta as Record).usage; + } + const extracted = extractUsageMetricsSimplified(usageValue); + if (extracted) messageDeltaUsage = merge(messageDeltaUsage, extracted); + } + } + + if (messageDeltaUsage && messageStartUsage) return merge(messageStartUsage, messageDeltaUsage); + return messageDeltaUsage ?? messageStartUsage; +} + +// Independent (script-local) reimplementation of the compact-capture spec, +// used ONLY to derive the expected fixture output -- deliberately written +// without reference to the Go implementation, from the spec in the task +// description, so it serves as a genuine cross-check rather than an oracle +// copy. +function deriveExpectedCompact( + fullSse: string, + maxBytes: number +): { text: string; truncated: boolean; eventCount: number } { + type Frame = { event: string | null; data: string }; + const frames: Frame[] = []; + let curEvent: string | null = null; + let curData: string[] = []; + const flush = () => { + if (curData.length === 0) { + curEvent = null; + return; + } + frames.push({ event: curEvent, data: curData.join("\n") }); + curEvent = null; + curData = []; + }; + for (const rawLine of fullSse.split("\n")) { + const line = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine; + if (line === "") { + flush(); + continue; + } + if (line.startsWith(":")) continue; + if (line.startsWith("event:")) { + curEvent = line.slice(6).trim(); + continue; + } + if (line.startsWith("data:")) { + let v = line.slice(5); + if (v.startsWith(" ")) v = v.slice(1); + curData.push(v); + } + } + flush(); + + let messageStartKept = false; + let messageStopKept = false; + let signatureDeltaKept = 0; + let out = ""; + let truncated = false; + let overBudget = false; + + for (const frame of frames) { + let name = frame.event ?? ""; + if (!name) { + try { + const parsed = JSON.parse(frame.data); + if (parsed && typeof parsed === "object" && typeof parsed.type === "string") { + name = parsed.type; + } + } catch { + // ignore + } + } + + let keep = false; + let priority = false; + if (name === "message_start" && !messageStartKept) { + keep = true; + messageStartKept = true; + } else if (name === "message_delta") { + keep = true; + priority = true; + } else if (name === "message_stop" && !messageStopKept) { + keep = true; + messageStopKept = true; + } else if (name === "error") { + keep = true; + priority = true; + } else if (name === "content_block_delta" && frame.data.includes("signature_delta") && signatureDeltaKept < 4) { + keep = true; + signatureDeltaKept += 1; + } + + if (!keep) continue; + const emitted = `event: ${name}\ndata: ${frame.data}\n\n`; + + if (!priority) { + if (overBudget) continue; + if (out.length + emitted.length > maxBytes) { + overBudget = true; + truncated = true; + continue; + } + out += emitted; + continue; + } + + if (out.length + emitted.length > maxBytes) truncated = true; + out += emitted; + } + + return { text: out, truncated, eventCount: frames.length }; +} + +function sseFrame(event: string, data: unknown): string { + return `event: ${event}\ndata: ${JSON.stringify(data)}\n\n`; +} + +type CompactStreamSpec = { name: string; maxBytes: number; full: string }; + +function buildCompactStreamSpecs(): CompactStreamSpec[] { + const specs: CompactStreamSpec[] = []; + + // 1. text-only + { + let s = sseFrame("message_start", { + type: "message_start", + message: { id: "msg_1", model: "claude-text", usage: { input_tokens: 10, output_tokens: 1 } }, + }); + s += sseFrame("content_block_start", { type: "content_block_start", index: 0, content_block: { type: "text", text: "" } }); + s += sseFrame("content_block_delta", { type: "content_block_delta", index: 0, delta: { type: "text_delta", text: "Hello" } }); + s += sseFrame("content_block_delta", { type: "content_block_delta", index: 0, delta: { type: "text_delta", text: " world" } }); + s += sseFrame("content_block_stop", { type: "content_block_stop", index: 0 }); + s += sseFrame("message_delta", { type: "message_delta", delta: { stop_reason: "end_turn" }, usage: { output_tokens: 8 } }); + s += sseFrame("message_stop", { type: "message_stop" }); + specs.push({ name: "text_only", maxBytes: 64 * 1024, full: s }); + } + + // 2. thinking with signature_delta + { + let s = sseFrame("message_start", { + type: "message_start", + message: { id: "msg_2", model: "claude-think", usage: { input_tokens: 20, output_tokens: 1 } }, + }); + s += sseFrame("content_block_start", { type: "content_block_start", index: 0, content_block: { type: "thinking", thinking: "" } }); + s += sseFrame("content_block_delta", { type: "content_block_delta", index: 0, delta: { type: "thinking_delta", thinking: "Let me think..." } }); + s += sseFrame("content_block_delta", { type: "content_block_delta", index: 0, delta: { type: "signature_delta", signature: "sig-aaaa" } }); + s += sseFrame("content_block_stop", { type: "content_block_stop", index: 0 }); + s += sseFrame("content_block_start", { type: "content_block_start", index: 1, content_block: { type: "text", text: "" } }); + s += sseFrame("content_block_delta", { type: "content_block_delta", index: 1, delta: { type: "text_delta", text: "Answer." } }); + s += sseFrame("content_block_stop", { type: "content_block_stop", index: 1 }); + s += sseFrame("message_delta", { type: "message_delta", delta: { stop_reason: "end_turn" }, usage: { output_tokens: 42 } }); + s += sseFrame("message_stop", { type: "message_stop" }); + specs.push({ name: "thinking_with_signature", maxBytes: 64 * 1024, full: s }); + } + + // 3. tool_use + { + let s = sseFrame("message_start", { + type: "message_start", + message: { id: "msg_3", model: "claude-tool", usage: { input_tokens: 15, output_tokens: 1 } }, + }); + s += sseFrame("content_block_start", { + type: "content_block_start", + index: 0, + content_block: { type: "tool_use", id: "toolu_1", name: "get_weather", input: {} }, + }); + s += sseFrame("content_block_delta", { type: "content_block_delta", index: 0, delta: { type: "input_json_delta", partial_json: '{"city":' } }); + s += sseFrame("content_block_delta", { type: "content_block_delta", index: 0, delta: { type: "input_json_delta", partial_json: '"SF"}' } }); + s += sseFrame("content_block_stop", { type: "content_block_stop", index: 0 }); + s += sseFrame("message_delta", { type: "message_delta", delta: { stop_reason: "tool_use" }, usage: { output_tokens: 20 } }); + s += sseFrame("message_stop", { type: "message_stop" }); + specs.push({ name: "tool_use", maxBytes: 64 * 1024, full: s }); + } + + // 4. ping-heavy + { + let s = sseFrame("message_start", { + type: "message_start", + message: { id: "msg_4", model: "claude-ping", usage: { input_tokens: 5, output_tokens: 1 } }, + }); + for (let i = 0; i < 20; i += 1) s += sseFrame("ping", { type: "ping" }); + s += sseFrame("content_block_start", { type: "content_block_start", index: 0, content_block: { type: "text", text: "" } }); + s += sseFrame("content_block_delta", { type: "content_block_delta", index: 0, delta: { type: "text_delta", text: "ok" } }); + s += sseFrame("content_block_stop", { type: "content_block_stop", index: 0 }); + s += sseFrame("message_delta", { type: "message_delta", delta: { stop_reason: "end_turn" }, usage: { output_tokens: 3 } }); + s += sseFrame("message_stop", { type: "message_stop" }); + specs.push({ name: "ping_heavy", maxBytes: 64 * 1024, full: s }); + } + + // 5. error mid-stream + { + let s = sseFrame("message_start", { + type: "message_start", + message: { id: "msg_5", model: "claude-err", usage: { input_tokens: 8, output_tokens: 1 } }, + }); + s += sseFrame("content_block_start", { type: "content_block_start", index: 0, content_block: { type: "text", text: "" } }); + s += sseFrame("content_block_delta", { type: "content_block_delta", index: 0, delta: { type: "text_delta", text: "partial" } }); + s += sseFrame("error", { type: "error", error: { type: "overloaded_error", message: "Overloaded" } }); + specs.push({ name: "error_mid_stream", maxBytes: 64 * 1024, full: s }); + } + + // 6. many signature_delta frames (verify cap of 4) + { + let s = sseFrame("message_start", { + type: "message_start", + message: { id: "msg_6", model: "claude-sig-many", usage: { input_tokens: 9, output_tokens: 1 } }, + }); + s += sseFrame("content_block_start", { type: "content_block_start", index: 0, content_block: { type: "thinking", thinking: "" } }); + for (let i = 0; i < 7; i += 1) { + s += sseFrame("content_block_delta", { type: "content_block_delta", index: 0, delta: { type: "signature_delta", signature: `sig-${i}` } }); + } + s += sseFrame("content_block_stop", { type: "content_block_stop", index: 0 }); + s += sseFrame("message_delta", { type: "message_delta", delta: { stop_reason: "end_turn" }, usage: { output_tokens: 11 } }); + s += sseFrame("message_stop", { type: "message_stop" }); + specs.push({ name: "many_signature_deltas_capped", maxBytes: 64 * 1024, full: s }); + } + + // 7. tight byte budget forcing truncation of non-priority frames but + // still keeping message_delta. + { + let s = sseFrame("message_start", { + type: "message_start", + message: { id: "msg_7", model: "claude-tight", usage: { input_tokens: 30, output_tokens: 1 } }, + }); + s += sseFrame("content_block_start", { type: "content_block_start", index: 0, content_block: { type: "text", text: "" } }); + for (let i = 0; i < 5; i += 1) { + s += sseFrame("content_block_delta", { type: "content_block_delta", index: 0, delta: { type: "text_delta", text: "chunk of filler text ".repeat(20) } }); + } + s += sseFrame("content_block_stop", { type: "content_block_stop", index: 0 }); + s += sseFrame("message_delta", { type: "message_delta", delta: { stop_reason: "end_turn" }, usage: { output_tokens: 99 } }); + s += sseFrame("message_stop", { type: "message_stop" }); + specs.push({ name: "tight_budget_still_keeps_message_delta", maxBytes: 200, full: s }); + } + + return specs; +} + +type CompactCase = { + name: string; + maxBytes: number; + fullSseB64: string; + expectedText: string; + expectedTruncated: boolean; + expectedEventCount: number; + fullUsage: Record | null; + compactUsage: Record | null; + usageMatches: boolean; + fullActualModel: { actualResponseModel: string | null; source: string | null }; + compactActualModel: { actualResponseModel: string | null; source: string | null }; + actualModelMatches: boolean; +}; + +function buildCompactCases(): CompactCase[] { + const specs = buildCompactStreamSpecs(); + const cases: CompactCase[] = []; + + for (const spec of specs) { + const derived = deriveExpectedCompact(spec.full, spec.maxBytes); + + const fullUsage = parseUsageFromResponseTextSimplified(spec.full); + const compactUsage = parseUsageFromResponseTextSimplified(derived.text); + const usageMatches = JSON.stringify(fullUsage) === JSON.stringify(compactUsage); + + const resolveParams = { + providerType: "claude" as const, + requestedModel: "claude-3-5-sonnet-20241022", + thinkingEnabled: spec.full.includes("signature_delta"), + }; + const fullModel = resolveAnthropicStreamActualResponseModel({ + ...resolveParams, + responseStreamText: spec.full, + }); + const compactModel = resolveAnthropicStreamActualResponseModel({ + ...resolveParams, + responseStreamText: derived.text, + }); + const actualModelMatches = + fullModel.actualResponseModel === compactModel.actualResponseModel && + fullModel.source === compactModel.source; + + cases.push({ + name: spec.name, + maxBytes: spec.maxBytes, + fullSseB64: b64(enc.encode(spec.full)), + expectedText: derived.text, + expectedTruncated: derived.truncated, + expectedEventCount: derived.eventCount, + fullUsage, + compactUsage, + usageMatches, + fullActualModel: fullModel, + compactActualModel: compactModel, + actualModelMatches, + }); + + if (!usageMatches) { + throw new Error(`compact fixture "${spec.name}": usage mismatch between full and compact stream`); + } + if (!actualModelMatches) { + throw new Error(`compact fixture "${spec.name}": actual-response-model mismatch between full and compact stream`); + } + } + + return cases; +} + +// --------------------------------------------------------------------------- +// response-normalize.json -- normalizeResponseOutputPayload +// (response-output-normalizer.ts), Responses-API non-stream body normalization. +// +// Local, faithful port (not a "simplified" subset): the real function is +// pure and self-contained (no server-only/session imports), so this is a +// straight copy of its logic rather than a reduced reimplementation. +// --------------------------------------------------------------------------- + +function isRecordLocal(value: unknown): value is Record { + return typeof value === "object" && value !== null && !Array.isArray(value); +} + +function stringifyArgumentsLocal(value: unknown): string { + if (value == null) return "{}"; + if (typeof value === "string") return value; + try { + return JSON.stringify(value) ?? "{}"; + } catch { + return String(value); + } +} + +function normalizeFunctionArgumentsLocal( + target: Record, + key: string, + fixes: string[], + path: string +): void { + if (!(key in target)) return; + const value = target[key]; + const normalized = stringifyArgumentsLocal(value); + if (normalized === value) return; + target[key] = normalized; + fixes.push(`${path}.${key}`); +} + +function normalizeContentPartLocal(part: unknown, fixes: string[], path: string): void { + if (!isRecordLocal(part)) return; + if ("text" in part && part.text === null) { + part.text = ""; + fixes.push(`${path}.text`); + } + if ("annotations" in part && part.annotations === null) { + part.annotations = []; + fixes.push(`${path}.annotations`); + } + if ("logprobs" in part && part.logprobs === null) { + part.logprobs = []; + fixes.push(`${path}.logprobs`); + } +} + +function normalizeToolCallLocal(toolCall: unknown, fixes: string[], path: string): void { + if (!isRecordLocal(toolCall)) return; + const nestedFunction = toolCall.function; + if (isRecordLocal(nestedFunction)) { + normalizeFunctionArgumentsLocal(nestedFunction, "arguments", fixes, `${path}.function`); + } +} + +function normalizeOutputItemLocal(item: unknown, fixes: string[], path: string): void { + if (!isRecordLocal(item)) return; + if ("content" in item) { + if (item.content === null) { + item.content = []; + fixes.push(`${path}.content`); + } else if (Array.isArray(item.content)) { + item.content.forEach((part, index) => { + normalizeContentPartLocal(part, fixes, `${path}.content[${index}]`); + }); + } + } + if ("summary" in item && item.summary === null) { + item.summary = []; + fixes.push(`${path}.summary`); + } + normalizeFunctionArgumentsLocal(item, "arguments", fixes, path); + const nestedFunction = item.function; + if (isRecordLocal(nestedFunction)) { + normalizeFunctionArgumentsLocal(nestedFunction, "arguments", fixes, `${path}.function`); + } + if (Array.isArray(item.tool_calls)) { + item.tool_calls.forEach((toolCall, index) => { + normalizeToolCallLocal(toolCall, fixes, `${path}.tool_calls[${index}]`); + }); + } +} + +function normalizeResponseOutputPayloadLocal(payload: unknown): { + payload: unknown; + applied: boolean; + fixes: string[]; +} { + const fixes: string[] = []; + if (!isRecordLocal(payload) || payload.object !== "response") { + return { payload, applied: false, fixes }; + } + if ("output" in payload) { + if (payload.output === null) { + payload.output = []; + fixes.push("output"); + } else if (Array.isArray(payload.output)) { + payload.output.forEach((item, index) => { + normalizeOutputItemLocal(item, fixes, `output[${index}]`); + }); + } + } + if ("tools" in payload && payload.tools === null) { + payload.tools = []; + fixes.push("tools"); + } + return { payload, applied: fixes.length > 0, fixes }; +} + +type ResponseNormalizeCase = { + name: string; + inputJson: string; + expectedApplied: boolean; + expectedOutputJson: string; + expectedFixes: string[]; +}; + +function buildResponseNormalizeCases(): ResponseNormalizeCase[] { + const cases: ResponseNormalizeCase[] = []; + const run = (name: string, input: unknown) => { + const inputJson = JSON.stringify(input); + // Re-parse so mutation doesn't affect a shared literal across cases. + const res = normalizeResponseOutputPayloadLocal(JSON.parse(inputJson)); + cases.push({ + name, + inputJson, + expectedApplied: res.applied, + expectedOutputJson: JSON.stringify(res.payload), + expectedFixes: res.fixes, + }); + }; + + run("not_a_response_object_untouched", { object: "chat.completion", output: null }); + run("no_object_field_untouched", { output: null }); + + run("null_output_becomes_empty_array", { object: "response", output: null }); + run("null_tools_becomes_empty_array", { object: "response", tools: null }); + run("both_null_output_and_tools", { object: "response", output: null, tools: null }); + + run("output_item_null_content_becomes_array", { + object: "response", + output: [{ type: "message", content: null }], + }); + run("output_item_content_part_null_text", { + object: "response", + output: [{ type: "message", content: [{ type: "output_text", text: null }] }], + }); + run("output_item_content_part_null_annotations_and_logprobs", { + object: "response", + output: [ + { + type: "message", + content: [{ type: "output_text", text: "ok", annotations: null, logprobs: null }], + }, + ], + }); + run("output_item_null_summary_becomes_array", { + object: "response", + output: [{ type: "reasoning", summary: null }], + }); + run("output_item_object_arguments_stringified", { + object: "response", + output: [{ type: "function_call", name: "f", arguments: { a: 1, b: "x" } }], + }); + run("output_item_null_arguments_stringified_to_empty_object", { + object: "response", + output: [{ type: "function_call", name: "f", arguments: null }], + }); + run("output_item_string_arguments_untouched", { + object: "response", + output: [{ type: "function_call", name: "f", arguments: '{"a":1}' }], + }); + run("output_item_nested_function_arguments_stringified", { + object: "response", + output: [{ type: "custom", function: { name: "f", arguments: [1, 2, 3] } }], + }); + run("output_item_tool_calls_function_arguments_stringified", { + object: "response", + output: [ + { + type: "message", + tool_calls: [{ id: "call_1", function: { name: "f", arguments: { x: true } } }], + }, + ], + }); + run("output_array_with_multiple_items_mixed", { + object: "response", + output: [ + { type: "message", content: null }, + { type: "reasoning", summary: null }, + { type: "message", content: [{ type: "output_text", text: "already set" }] }, + ], + tools: null, + }); + run("fully_clean_payload_no_fixes", { + object: "response", + output: [{ type: "message", content: [{ type: "output_text", text: "hi" }] }], + tools: [{ type: "function", name: "f" }], + }); + + return cases; +} + +async function main() { + await writeJSON("fixer/encoding.json", buildEncodingCases()); + await writeJSON("fixer/sse.json", buildSseCases()); + await writeJSON("fixer/json.json", buildJsonCases()); + await writeJSON("fixer/nonstream.json", buildNonStreamCases()); + await writeJSON("fixer/stream.json", buildStreamCases()); + await writeJSON("fixer/response-normalize.json", buildResponseNormalizeCases()); + await writeJSON("capture/metering.json", buildMeteringCases()); + await writeJSON("capture/compact.json", buildCompactCases()); + console.log("done"); +} + +main().catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/scripts/edge-fixtures/gate.ts b/scripts/edge-fixtures/gate.ts new file mode 100644 index 0000000000..11346f59a0 --- /dev/null +++ b/scripts/edge-fixtures/gate.ts @@ -0,0 +1,801 @@ +/** + * Generates JSON fixtures for the Go port of the stream content gate + * (edge/internal/sse, edge/internal/gate) by driving the REAL TypeScript + * implementations. Run with: + * + * bun scripts/edge-fixtures/gate.ts + * + * from the repo root. Output is deterministic: re-running produces byte + * identical files (no timestamps/randomness in the emitted JSON; the one + * pseudo-random case below uses a fixed seed). + */ + +import { mkdirSync, writeFileSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { + classifyFrame, + isRequestEchoFrame, + isCleanResponsesCompletion, + isResponsesIncompleteCompletion, + classifyStructuredTerminalKind, + classifyTerminalKind, + type ProtocolFamily, +} from "../../src/app/v1/_lib/proxy/stream-gate/frame-classifier"; +import { + parseSseBody, + SseFrameBufferLimitError, + SseFrameParser, +} from "../../src/app/v1/_lib/proxy/stream-gate/sse-frames"; +import { + concatChunks, + runStreamContentGate, + StreamPrecommitError, +} from "../../src/app/v1/_lib/proxy/stream-gate/stream-content-gate"; +import { createStreamProtocolObserver } from "../../src/app/v1/_lib/proxy/stream-gate/stream-protocol-observer"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const OUT_DIR = join(__dirname, "../../tests/fixtures/edge/gate"); +mkdirSync(OUT_DIR, { recursive: true }); + +function writeJson(name: string, value: unknown): void { + const path = join(OUT_DIR, name); + writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`, "utf8"); + console.log(`wrote ${path}`); +} + +const encoder = new TextEncoder(); + +// --------------------------------------------------------------------------- +// sse-frames.json +// --------------------------------------------------------------------------- + +interface SseFrameCase { + name: string; + chunks: string[]; + // When set, `chunks` are base64-encoded raw bytes instead of plain UTF-8 + // text. This is required for chunk boundaries that land mid-UTF-8-codepoint: + // round-tripping such a chunk through a plain JSON string (decode as text, + // even partially) would corrupt it, since a partial codepoint decodes to a + // replacement character. Byte-split cases must therefore preserve the + // exact wire bytes end to end. + binary?: boolean; + maxBufferedBytes?: number; + exemptionEventName?: string; + expectedFrames?: { eventName: string | null; data: string }[]; + expectedError?: boolean; +} + +function encodeChunk(c: SseFrameCase, chunk: string): Uint8Array { + return c.binary ? Uint8Array.from(Buffer.from(chunk, "base64")) : encoder.encode(chunk); +} + +function runSseCase(c: SseFrameCase): { frames?: { eventName: string | null; data: string }[]; error?: boolean } { + const options: ConstructorParameters[0] = {}; + if (c.maxBufferedBytes !== undefined) options.maxBufferedCharacters = c.maxBufferedBytes; + if (c.exemptionEventName !== undefined) { + options.bufferLimitExemption = { + maxBufferedCharacters: Number.MAX_SAFE_INTEGER, + matches: (eventName) => eventName === c.exemptionEventName, + }; + } + const parser = new SseFrameParser(options); + try { + const frames = c.chunks.flatMap((chunk) => parser.push(encodeChunk(c, chunk))); + frames.push(...parser.finish()); + return { frames }; + } catch (error) { + if (error instanceof SseFrameBufferLimitError) return { error: true }; + throw error; + } +} + +const sseFrameCases: SseFrameCase[] = [ + { + name: "simple event stream", + chunks: ['event: message_start\ndata: {"a":1}\n\ndata: [DONE]\n\n'], + }, + { + name: "joins multi-line data with newline", + chunks: ["data: line1\ndata: line2\n\n"], + }, + { + name: "CRLF line endings", + chunks: ["event: ping\r\ndata: {}\r\n\r\n"], + }, + { + name: "skips comment lines and id/retry fields", + chunks: [": keep-alive\nid: 42\nretry: 500\ndata: x\n\n"], + }, + { + name: "newline-delimited raw JSON frames", + chunks: ['{"candidates":[]}\n{"error":{"message":"failed"}}\n'], + }, + { + name: "event without data emits no frame and resets event name", + chunks: ["event: orphan\n\ndata: y\n\n"], + }, + { + name: "trailing frame without terminating blank line at EOF", + chunks: ['event: e\ndata: {"z":1}'], + }, + { + name: "strips exactly one leading space after data:", + chunks: ["data: two-spaces\n\ndata:none\n\n"], + }, + { + name: "CRLF split across chunk boundary", + chunks: ["data: a\r", "\ndata: b\r\n\r\n"], + }, + { + name: "unterminated line exceeding retained buffer throws", + chunks: [`data: ${"x".repeat(20)}`], + maxBufferedBytes: 16, + expectedError: true, + }, + { + name: "completed oversized unknown line throws", + chunks: [`unknown: ${"x".repeat(20)}\n`], + maxBufferedBytes: 16, + expectedError: true, + }, + { + name: "counts accumulated data lines before dispatch", + chunks: ["data: 12345\ndata: 67890\n"], + maxBufferedBytes: 10, + expectedError: true, + }, + { + name: "does not count an event name after a later event field replaces it", + chunks: ["event: 1234567890\nevent: abcdefghij\ndata: x\n\n"], + maxBufferedBytes: 11, + }, + { + name: "keeps unlimited behavior when no limit configured", + chunks: [`data: ${"x".repeat(1024 * 1024 + 1)}\n\n`], + }, + { + name: "event-name-only buffer exemption allows an oversized recognized frame", + chunks: [`event: response.created\ndata: ${"x".repeat(200)}\n\n`], + maxBufferedBytes: 16, + exemptionEventName: "response.created", + }, + { + name: "exemption does not apply to a non-matching event", + chunks: [`event: other\ndata: ${"x".repeat(200)}\n\n`], + maxBufferedBytes: 16, + exemptionEventName: "response.created", + expectedError: true, + }, +]; + +interface ByteSplitCase { + name: string; + body: string; + splits: number[]; // byte offsets to split at (two chunks each), plus "every-byte" flag handled separately + everyByte?: boolean; +} + +const byteSplitCases: ByteSplitCase[] = [ + { name: "utf8 codepoint split", body: "data: 中文内容\n\n", splits: [8] }, + { + name: "byte-split invariance: arbitrary points", + body: + 'event: content_block_delta\r\ndata: {"type":"content_block_delta","delta":{"text":"你好"}}\r\n\r\n' + + ": comment\n" + + "data: part1\ndata: part2\n\n" + + "event: message_stop\ndata: {}\n\n" + + "data: [DONE]\n\n", + splits: [], + everyByte: true, + }, + { + name: "byte-split invariance: byte-by-byte, trailing unterminated frame", + body: 'event: e1\ndata: {"a":"中"}\n\ndata: tail', + splits: [], + everyByte: true, + }, +]; + +function framesToJson(frames: { eventName: string | null; data: string }[]) { + return frames.map((f) => ({ eventName: f.eventName, data: f.data })); +} + +const sseFrameFixture: unknown[] = []; + +for (const c of sseFrameCases) { + const outcome = runSseCase(c); + sseFrameFixture.push({ + name: c.name, + chunks: c.chunks, + binary: c.binary === true, + maxBufferedBytes: c.maxBufferedBytes ?? null, + exemptionEventName: c.exemptionEventName ?? null, + expectedFrames: outcome.frames ? framesToJson(outcome.frames) : null, + expectedError: outcome.error === true, + }); +} + +for (const bc of byteSplitCases) { + const bytes = encoder.encode(bc.body); + const expected = framesToJson(parseSseBody(bc.body)); + const splitPoints = bc.everyByte + ? Array.from({ length: bytes.length - 1 }, (_, i) => i + 1) + : bc.splits; + for (const splitAt of splitPoints) { + const parser = new SseFrameParser(); + const frames = [ + ...parser.push(bytes.slice(0, splitAt)), + ...parser.push(bytes.slice(splitAt)), + ...parser.finish(), + ]; + const actual = framesToJson(frames); + if (JSON.stringify(actual) !== JSON.stringify(expected)) { + throw new Error(`byte-split mismatch for "${bc.name}" at split=${splitAt}`); + } + } + const sampleSplits = bc.everyByte + ? [splitPoints[0], splitPoints[Math.floor(splitPoints.length / 2)], splitPoints[splitPoints.length - 1]] + : splitPoints; + for (const splitAt of sampleSplits) { + sseFrameFixture.push({ + name: `${bc.name} (byte-split invariant, split=${splitAt})`, + chunks: [ + Buffer.from(bytes.slice(0, splitAt)).toString("base64"), + Buffer.from(bytes.slice(splitAt)).toString("base64"), + ], + binary: true, + maxBufferedBytes: null, + exemptionEventName: null, + expectedFrames: expected, + expectedError: false, + }); + } +} + +writeJson("sse-frames.json", sseFrameFixture); + +// --------------------------------------------------------------------------- +// classifier.json +// --------------------------------------------------------------------------- + +const families: ProtocolFamily[] = ["anthropic", "openai-chat", "openai-responses", "gemini"]; + +const classifierSamples: { eventName: string | null; data: string }[] = [ + { eventName: "message_start", data: '{"type":"message_start","message":{"id":"m1"}}' }, + { + eventName: "content_block_delta", + data: '{"type":"content_block_delta","delta":{"type":"text_delta","text":"hello"}}', + }, + { + eventName: "content_block_start", + data: '{"type":"content_block_start","content_block":{"type":"tool_use","name":"lookup"}}', + }, + { + eventName: "content_block_start", + data: '{"type":"content_block_start","content_block":{"type":"redacted_thinking"}}', + }, + { eventName: "message_stop", data: '{"type":"message_stop"}' }, + { eventName: "error", data: '{"type":"error","error":{"type":"overloaded_error","message":"overloaded"}}' }, + { eventName: null, data: '{"error":{"message":"failed"}}' }, + { eventName: null, data: '{"choices":[{"delta":{"content":"hi"}}]}' }, + { eventName: null, data: '{"choices":[{"delta":{"reasoning_content":"thinking"}}]}' }, + { eventName: null, data: "[DONE]" }, + { + eventName: "response.output_text.delta", + data: '{"type":"response.output_text.delta","delta":"ok"}', + }, + { + eventName: "response.output_text.delta", + data: '{"type":"response.output_text.delta","delta":"ok","error":{"message":"failed"}}', + }, + { + eventName: "response.completed", + data: '{"type":"response.completed","response":{"status":"completed","output":[]}}', + }, + { + eventName: "response.completed", + data: '{"type":"response.completed","response":{"status":"completed","output":[{"type":"compaction","encrypted_content":"opaque"}]}}', + }, + { + eventName: "response.completed", + data: '{"type":"response.completed","response":{"status":"completed","output":[{"type":"compaction"},{"encrypted_content":"opaque"}]}}', + }, + { + eventName: "response.output_item.done", + data: '{"type":"response.output_item.done","item":{"type":"compaction","encrypted_content":"opaque-state"}}', + }, + { + eventName: "response.incomplete", + data: '{"type":"response.incomplete","response":{"status":"incomplete"}}', + }, + { + eventName: "response.completed", + data: '{"type":"response.completed","response":{"status":"failed","output":[],"error":null}}', + }, + { + eventName: "response.completed", + data: '{"type":"response.completed","response":{"status":"completed","output":[],"error":{"code":"server_error"}}}', + }, + { eventName: "response.created", data: '{"type":"response.created","response":{"instructions":"echo"}}' }, + { eventName: null, data: '{"type":"response.created","response":{"instructions":"echo"}}' }, + { eventName: "response.failed", data: '{"type":"response.failed","response":{"status":"failed"}}' }, + { + eventName: null, + data: '{"candidates":[{"content":{"parts":[{"text":"hi"}]}}]}', + }, + { eventName: null, data: '{"candidates":[{"finishReason":"STOP"}]}' }, + { eventName: null, data: '{"candidates":[{"finishReason":"SAFETY"}]}' }, + { eventName: null, data: '{"usageMetadata":{"totalTokenCount":1}}' }, + { eventName: null, data: '{"promptFeedback":{"blockReason":"SAFETY"}}' }, + { + eventName: null, + data: '{"response":{"candidates":[{"content":{"parts":[{"text":"yes"}]},"finishReason":"SAFETY"}]}}', + }, + { eventName: null, data: "{}" }, + { eventName: null, data: "[]" }, + { eventName: null, data: "null" }, + { eventName: null, data: "" }, + { eventName: null, data: " " }, + { eventName: null, data: "{broken}" }, + { eventName: null, data: "{not-json}" }, + { eventName: null, data: '{"error":false,"error":null}' }, + { eventName: null, data: '{"error":0}' }, + { eventName: null, data: '{"error":[false,null,"",[]]}' }, + { eventName: null, data: '{"error":[{}]}' }, + { eventName: null, data: '{"error":1e+2}' }, + { eventName: null, data: '{"error":01}' }, + { eventName: null, data: '{"error":1.}' }, + { eventName: null, data: '{"error":1e}' }, + { eventName: null, data: '{"error":true,}' }, + { eventName: null, data: '{"choices":{"#":{"delta":{"content":"wrong object wildcard"}}}}' }, + { + eventName: null, + data: + '{"unused":' + "[".repeat(50) + "0" + "]".repeat(50) + ',"choices":[{"delta":{"content":"ok"}}]}', + }, + { eventName: null, data: '{"choices":[{"delta":{"content":""}},{"delta":{"tool_calls":[{"function":{"arguments":"{}"}}]}}]}' }, +]; + +// terminalKind mirrors the exact composition used by +// createStreamProtocolObserver's `record` function: a verdict-gated, +// event-name-only base (classifyTerminalKind, no parsed argument), then +// overridden by the structural classifyStructuredTerminalKind when a parsed +// JSON object/array is available and it returns non-null. +function computeTerminalKind( + family: ProtocolFamily, + eventName: string | null, + data: string, + verdict: string +): string { + const trimmed = data.trim(); + let parsed: unknown = null; + if (trimmed.startsWith("{") || trimmed.startsWith("[")) { + try { + const value = JSON.parse(trimmed); + if (value !== null && typeof value === "object") parsed = value; + } catch { + parsed = null; + } + } + const base = verdict === "terminal" ? classifyTerminalKind(family, eventName) : null; + const kind = parsed !== null ? (classifyStructuredTerminalKind(family, eventName, parsed as object) ?? base) : base; + return kind ?? ""; +} + +const classifierFixture: unknown[] = []; +for (const family of families) { + for (const sample of classifierSamples) { + const verdict = classifyFrame(family, sample.eventName, sample.data); + const acceptTerminal = + family === "openai-responses" && + (isCleanResponsesCompletion(sample.eventName, sample.data) || + isResponsesIncompleteCompletion(sample.eventName, sample.data)); + classifierFixture.push({ + family, + eventName: sample.eventName, + data: sample.data, + expected: { + verdict, + acceptTerminal, + isEcho: isRequestEchoFrame(family, sample.eventName, sample.data), + terminalKind: computeTerminalKind(family, sample.eventName, sample.data, verdict), + }, + }); + } +} + +// Deterministic pseudo-random differential samples (mirrors +// stream-gate-frame-probe.test.ts's "随机协议载荷差分" generator). +{ + let seed = 1473; + const random = (n: number) => { + seed = (Math.imul(seed, 1664525) + 1013904223) >>> 0; + return seed % n; + }; + const values: unknown[] = [null, false, true, 0, "", "x", {}, [], [null], { x: null }, "SAFETY", "compaction"]; + for (let i = 0; i < 200; i++) { + const choose = () => values[random(values.length)]; + const data = JSON.stringify({ + type: ["response.completed", "response.output_item.done", "content_block_delta"][random(3)], + error: choose(), + delta: { text: choose() }, + choices: [{ delta: { content: choose() } }], + item: { type: choose(), encrypted_content: choose() }, + candidates: [{ finishReason: choose(), content: { parts: [{ text: choose() }] } }], + response: { output: [{ type: choose() }, { encrypted_content: choose() }] }, + }); + for (const family of families) { + const verdict = classifyFrame(family, null, data); + const acceptTerminal = + family === "openai-responses" && + (isCleanResponsesCompletion(null, data) || isResponsesIncompleteCompletion(null, data)); + classifierFixture.push({ + family, + eventName: null, + data, + expected: { + verdict, + acceptTerminal, + isEcho: isRequestEchoFrame(family, null, data), + terminalKind: computeTerminalKind(family, null, data, verdict), + }, + }); + } + } +} + +writeJson("classifier.json", classifierFixture); + +// --------------------------------------------------------------------------- +// observer.json +// --------------------------------------------------------------------------- + +interface ObserverCase { + name: string; + family: ProtocolFamily; + chunks: string[]; // observed in order via observer.observe() +} + +const observerCases: ObserverCase[] = [ + { + name: "compaction content also counts as terminal completion", + family: "openai-responses", + chunks: [ + 'event: response.completed\ndata: {"type":"response.completed","response":{"status":"completed","output":[{"type":"compaction","encrypted_content":"opaque"}]}}\n\n', + ], + }, + { + name: "gemini content with finishReason in same frame", + family: "gemini", + chunks: ['{"candidates":[{"content":{"parts":[{"text":"done"}]},"finishReason":"STOP"}]}\n'], + }, + { + name: "content then failure then terminal, EOF flush", + family: "openai-responses", + chunks: [ + 'event: response.output_text.delta\ndata: {"type":"response.output_text.delta","delta":"ok"}\n\n', + 'event: response.failed\ndata: {"type":"response.failed","response":{"status":"failed"}}\n\n', + 'event: response.completed\ndata: {"type":"response.completed","response":{"status":"completed"}}', + ], + }, + { + name: "malformed terminal-ish frame recorded as terminal failure", + family: "anthropic", + chunks: [ + 'event: content_block_delta\ndata: {"type":"content_block_delta","delta":{"text":"ok"}}\n\n', + "event: message_stop\ndata: {not-json}\n\n", + ], + }, + { + name: "explicit error overrides earlier malformed, keeps sawMalformed", + family: "openai-responses", + chunks: [ + 'event: response.output_text.delta\ndata: {"type":"response.output_text.delta","delta":"ok"}\n\n', + "event: response.in_progress\ndata: not-json\n\n", + 'event: response.failed\ndata: {"type":"response.failed","response":{"status":"failed"}}\n\n', + ], + }, + { + name: "tool metadata alone is not content", + family: "anthropic", + chunks: [ + 'event: content_block_start\ndata: {"type":"content_block_start","content_block":{"type":"tool_use","name":"lookup"}}\n\n', + 'event: message_stop\ndata: {"type":"message_stop"}\n\n', + ], + }, + { + name: "content without terminal", + family: "openai-chat", + chunks: ['data: {"choices":[{"delta":{"content":"partial"}}]}\n\n'], + }, + { + name: "openai-chat DONE is success terminal", + family: "openai-chat", + chunks: ['data: {"choices":[{"delta":{"content":"ok"}}]}\n\n', "data: [DONE]\n\n"], + }, + { + name: "responses incomplete via event name", + family: "openai-responses", + chunks: ['event: response.incomplete\ndata: {"type":"response.incomplete","response":{"status":"incomplete"}}\n\n'], + }, + { + name: "responses incomplete via bare data", + family: "openai-responses", + chunks: ['data: {"type":"response.incomplete","response":{"status":"incomplete"}}\n\n'], + }, + { + name: "finish flushes malformed frame with no terminating blank line", + family: "openai-responses", + chunks: ["event: response.completed\ndata: not-json"], + }, +]; + +const observerFixture: unknown[] = []; +for (const c of observerCases) { + const observer = createStreamProtocolObserver(c.family); + for (const chunk of c.chunks) observer.observe(encoder.encode(chunk)); + const result = observer.finish(); + observerFixture.push({ + name: c.name, + family: c.family, + chunks: c.chunks, + expected: result, + }); +} + +writeJson("observer.json", observerFixture); + +// --------------------------------------------------------------------------- +// gate-cases.json +// --------------------------------------------------------------------------- + +function readerFromChunks(chunks: string[]): ReadableStreamDefaultReader { + let index = 0; + const stream = new ReadableStream({ + pull(controller) { + if (index >= chunks.length) { + controller.close(); + return; + } + controller.enqueue(encoder.encode(chunks[index++])); + }, + }); + return stream.getReader(); +} + +interface GateCase { + name: string; + family: ProtocolFamily; + chunks: string[]; + eventCap?: number; + byteCap?: number; +} + +const PING = 'event: ping\ndata: {"type":"ping"}\n\n'; +const MESSAGE_START = 'event: message_start\ndata: {"type":"message_start","message":{"id":"m1"}}\n\n'; +const TEXT_DELTA = + 'event: content_block_delta\ndata: {"type":"content_block_delta","delta":{"type":"text_delta","text":"hello"}}\n\n'; +const ERROR_FRAME = + 'event: error\ndata: {"type":"error","error":{"type":"overloaded_error","message":"overloaded"}}\n\n'; +const MESSAGE_STOP = 'event: message_stop\ndata: {"type":"message_stop"}\n\n'; + +const gateCases: GateCase[] = [ + { name: "commits on first content frame", family: "anthropic", chunks: [PING, MESSAGE_START, TEXT_DELTA, MESSAGE_STOP] }, + { name: "fails over on error before content", family: "anthropic", chunks: [PING, ERROR_FRAME, TEXT_DELTA] }, + { name: "fails on malformed frame", family: "anthropic", chunks: [PING, "data: {broken json\n\n"] }, + { name: "terminal before content is empty stream", family: "anthropic", chunks: [PING, MESSAGE_STOP] }, + { name: "EOF without content is empty stream", family: "anthropic", chunks: [PING, MESSAGE_START] }, + { name: "fully empty stream", family: "anthropic", chunks: [] }, + { + name: "commits trailing content frame without terminating blank line", + family: "anthropic", + chunks: ['data: {"type":"content_block_delta","delta":{"text":"tail"}}'], + }, + { + name: "event cap overflow across many frames", + family: "anthropic", + chunks: Array.from({ length: 20 }, () => PING), + eventCap: 10, + }, + { + name: "event cap overflow within a single chunk", + family: "anthropic", + chunks: [Array.from({ length: 20 }, () => PING).join("")], + eventCap: 10, + }, + { + name: "commits right at event cap boundary", + family: "anthropic", + chunks: [PING + PING + PING + TEXT_DELTA], + eventCap: 3, + }, + { + name: "byte cap overflow", + family: "anthropic", + chunks: [ + `event: ping\ndata: {"type":"ping","pad":"${"x".repeat(4000)}"}\n\n`, + `event: ping\ndata: {"type":"ping","pad":"${"x".repeat(4000)}"}\n\n`, + `event: ping\ndata: {"type":"ping","pad":"${"x".repeat(4000)}"}\n\n`, + ], + byteCap: 8000, + }, + { + name: "byte-fragmented prefix, one byte per chunk", + family: "anthropic", + chunks: [...encoder.encode(PING + MESSAGE_START + TEXT_DELTA)].map((byte) => String.fromCharCode(byte)), + }, + { + name: "openai-chat DONE-only stream is empty", + family: "openai-chat", + chunks: ["data: [DONE]\n\n"], + }, + { + name: "openai-chat in-stream error fails over", + family: "openai-chat", + chunks: [ + 'data: {"choices":[{"delta":{"role":"assistant"}}]}\n\n', + 'data: {"error":{"message":"rate limited","code":429}}\n\n', + ], + }, + { + name: "openai-chat reasoning_content commits", + family: "openai-chat", + chunks: ['data: {"choices":[{"delta":{"reasoning_content":"reasoning step 0"}}]}\n\n'], + }, + { + name: "openai-responses commits compaction item before completed", + family: "openai-responses", + chunks: [ + 'event: response.output_item.done\ndata: {"type":"response.output_item.done","item":{"type":"compaction","encrypted_content":"opaque-state"}}\n\n', + 'event: response.completed\ndata: {"type":"response.completed","response":{"status":"completed"}}\n\n', + ], + }, + { + name: "openai-responses commits compaction carried only by response.completed", + family: "openai-responses", + chunks: [ + 'event: response.completed\ndata: {"type":"response.completed","response":{"status":"completed","output":[{"type":"compaction","encrypted_content":"opaque-state"}]}}\n\n', + ], + }, + { + name: "gemini usage-only chunks buffer until content commits", + family: "gemini", + chunks: [ + 'data: {"usageMetadata":{"totalTokenCount":1}}\n\n', + 'data: {"candidates":[{"content":{"parts":[{"text":"hi"}]}}]}\n\n', + ], + }, + { + name: "openai-responses transparent empty completion (legal silent success)", + family: "openai-responses", + chunks: [ + 'event: response.created\ndata: {"type":"response.created","response":{"id":"resp_empty","status":"in_progress","output":[]}}\n\n', + 'event: response.output_text.done\ndata: {"type":"response.output_text.done","text":""}\n\n', + 'event: response.completed\ndata: {"type":"response.completed","response":{"id":"resp_empty","status":"completed","output":[],"error":null}}\n\n', + ], + }, + { + name: "openai-responses no-trailing-blank-line success completion at EOF", + family: "openai-responses", + chunks: [ + 'event: response.completed\ndata: {"type":"response.completed","response":{"id":"resp_empty","status":"completed","output":[],"error":null}}', + ], + }, + { + name: "openai-responses explicit incomplete terminal is a legal completion", + family: "openai-responses", + chunks: [ + 'event: response.incomplete\ndata: {"type":"response.incomplete","response":{"id":"resp_incomplete","status":"incomplete","incomplete_details":{"reason":"max_output_tokens"},"usage":{"input_tokens":8,"output_tokens":4}}}\n\n', + ], + }, + { + name: "openai-responses rejects failed completion", + family: "openai-responses", + chunks: [ + 'event: response.completed\ndata: {"type":"response.completed","response":{"id":"resp_bad","status":"failed","output":[],"error":null}}\n\n', + ], + }, + { + name: "openai-responses rejects completed-with-error", + family: "openai-responses", + chunks: [ + 'event: response.completed\ndata: {"type":"response.completed","response":{"id":"resp_bad","status":"completed","output":[],"error":{"code":"server_error"}}}\n\n', + ], + }, +]; + +// Request-echo byte-cap exclusion cases (openai-responses) +{ + const bigPayload = "x".repeat(4096); + const echoFrame = `event: response.created\ndata: {"type":"response.created","response":{"instructions":"${bigPayload}"}}\n\n`; + const responsesDelta = + 'event: response.output_text.delta\ndata: {"type":"response.output_text.delta","delta":"hi"}\n\n'; + gateCases.push({ + name: "echo frame excluded from byte cap, commits", + family: "openai-responses", + chunks: [echoFrame, responsesDelta], + byteCap: 4096, + }); + gateCases.push({ + name: "echo exemption caps at prebufferByteCap, still overflows", + family: "openai-responses", + chunks: [echoFrame, responsesDelta], + byteCap: 1024, + }); + const bigNeutral = `event: response.output_item.added\ndata: {"type":"response.output_item.added","item":"${bigPayload}"}\n\n`; + gateCases.push({ + name: "oversized non-echo neutral frame still overflows", + family: "openai-responses", + chunks: [bigNeutral, responsesDelta], + byteCap: 1024, + }); +} + +interface GateExpected { + committed: boolean; + reason?: string; + framesSeen: number; + prefixBytes: number; + readerDone?: boolean; + terminalBeforeContent?: boolean; + echoExcludedBytes?: number; + commitMarker?: { + frameIndex: number; + chunkIndex: number; + eventName: string | null; + bufferedBytes: number; + echoExcludedBytes: number; + } | null; +} + +const gateCaseFixture: unknown[] = []; +for (const c of gateCases) { + const reader = readerFromChunks(c.chunks); + const result = await runStreamContentGate(reader, { + family: c.family, + providerId: 1, + providerName: "fixture-provider", + prebufferEventCap: c.eventCap ?? 64, + prebufferByteCap: c.byteCap ?? 256 * 1024, + captureCommitMarker: true, + }); + + let expected: GateExpected; + if (result.committed) { + const merged = concatChunks(result.prefixChunks); + const prefixBytes = merged ? merged.byteLength : 0; + expected = { + committed: true, + framesSeen: result.framesSeen, + prefixBytes, + readerDone: result.readerDone, + commitMarker: result.commitMarker, + }; + } else { + const error = result.error; + if (!(error instanceof StreamPrecommitError)) { + throw new Error(`case "${c.name}": expected StreamPrecommitError, got ${String(error)}`); + } + expected = { + committed: false, + reason: error.gateReason, + framesSeen: 0, + prefixBytes: 0, + terminalBeforeContent: error.terminalBeforeContent, + }; + } + + gateCaseFixture.push({ + name: c.name, + family: c.family, + chunks: c.chunks, + eventCap: c.eventCap ?? 64, + byteCap: c.byteCap ?? 256 * 1024, + expected, + }); +} + +writeJson("gate-cases.json", gateCaseFixture); + +console.log("done"); diff --git a/server-lib/listen-prefix.js b/server-lib/listen-prefix.js new file mode 100644 index 0000000000..4fd701964a --- /dev/null +++ b/server-lib/listen-prefix.js @@ -0,0 +1,198 @@ +// Shared helper for the optional extra listen prefix (PROXY_LISTEN_PREFIX). +// +// Purpose: let deployments mount the proxy API under one or more additional +// path prefixes (e.g. `/gateway/v1/messages` alongside `/v1/messages`) when a +// shared domain routes by path. The prefix is stripped at the entry layer so +// every downstream component (Hono apps, guards, converters, session, billing) +// keeps seeing the canonical path. +// +// Two entry layers use this module: +// - server.js (prod via cluster.js, and `bun run dev:server`): rewrites +// `req.url` before handing to Next, and accepts prefixed WebSocket upgrades. +// - src/proxy.ts (Next middleware; the only layer under plain `next dev`): +// NextResponse.rewrite() to the stripped path. +// +// Kept dependency-free CJS so server.js can require it directly while the +// TypeScript side imports it through src/lib/listen-prefix.ts (same pattern as +// server-lib/spool-directory.js and server-lib/memory-governor.js). + +"use strict"; + +// Canonical proxy path roots. A prefixed path only gets rewritten when the +// remainder after the prefix is one of these (exact, or followed by `/`). +// KEEP IN SYNC with UNPREFIXED_V1_ALIASES in src/app/v1/_lib/unprefixed-v1-alias.ts +// (plus the two versioned Hono basePaths); the unit test asserts no drift. +const PROXY_PATH_ROOTS = [ + "/v1", + "/v1beta", + "/chat/completions", + "/responses", + "/models", + "/messages", +]; + +// A prefix whose first segment is one of these would shadow an existing route: +// management API, proxy roots, Next internals, app pages, or an i18n locale. +// KEEP IN SYNC with routing.locales (src/i18n/config.ts) and the alias list +// above; the unit test cross-checks both. +const RESERVED_FIRST_SEGMENTS = new Set([ + "api", + "v1", + "v1beta", + "_next", + "favicon.ico", + "login", + "status", + "usage-doc", + "dashboard", + "chat", + "responses", + "models", + "messages", + "zh-CN", + "zh-TW", + "en", + "ja", + "ru", +]); + +const SEGMENT_PATTERN = /^[A-Za-z0-9._~-]+$/; +const MAX_PREFIX_LENGTH = 128; + +/** + * Parse the raw PROXY_LISTEN_PREFIX value into normalized prefixes. + * + * Accepts a comma-separated list. Each entry gets a leading slash if missing + * and has trailing slashes stripped. Matching is case-sensitive and operates + * on raw (undecoded) pathnames, so percent-encoded prefixes are rejected. + * + * Never throws: callers decide how to surface `error`. + * + * @param {unknown} raw + * @returns {{ prefixes: string[], error: string | null }} + */ +function parseListenPrefixes(raw) { + if (typeof raw !== "string" || raw.trim() === "") { + return { prefixes: [], error: null }; + } + + const prefixes = []; + for (const entry of raw.split(",")) { + const trimmed = entry.trim(); + if (trimmed === "") continue; + + const withSlash = trimmed.startsWith("/") ? trimmed : `/${trimmed}`; + let normalized = withSlash; + while (normalized.length > 1 && normalized.endsWith("/")) { + normalized = normalized.slice(0, -1); + } + + if (normalized === "" || normalized === "/") { + return { prefixes: [], error: `"${trimmed}" must contain at least one path segment` }; + } + if (normalized.length > MAX_PREFIX_LENGTH) { + return { prefixes: [], error: `"${trimmed}" exceeds ${MAX_PREFIX_LENGTH} characters` }; + } + + const segments = normalized.slice(1).split("/"); + for (const segment of segments) { + if (segment === "") { + return { prefixes: [], error: `"${trimmed}" contains an empty path segment` }; + } + if (segment === "." || segment === "..") { + return { prefixes: [], error: `"${trimmed}" contains a relative path segment` }; + } + if (!SEGMENT_PATTERN.test(segment)) { + return { + prefixes: [], + error: `"${trimmed}" contains an invalid character in segment "${segment}" (allowed: A-Z a-z 0-9 . _ ~ -)`, + }; + } + } + + const firstSegment = segments[0]; + if (RESERVED_FIRST_SEGMENTS.has(firstSegment)) { + return { + prefixes: [], + error: `"${trimmed}" starts with the reserved segment "${firstSegment}"`, + }; + } + + for (const existing of prefixes) { + if (existing === normalized) { + return { prefixes: [], error: `"${trimmed}" is listed more than once` }; + } + if (existing.startsWith(`${normalized}/`) || normalized.startsWith(`${existing}/`)) { + return { + prefixes: [], + error: `"${trimmed}" overlaps with "${existing}"; prefixes must not nest`, + }; + } + } + + prefixes.push(normalized); + } + + return { prefixes, error: null }; +} + +/** + * Strip a configured listen prefix from a pathname. + * + * Returns the canonical remainder (e.g. "/v1/messages", "/responses") when the + * pathname sits under a prefix AND the remainder is a proxy path; otherwise + * null, so the caller leaves the request untouched. + * + * @param {string} pathname raw pathname, no query string + * @param {readonly string[]} prefixes normalized prefixes + * @returns {string | null} + */ +function stripListenPrefix(pathname, prefixes) { + if (typeof pathname !== "string" || !Array.isArray(prefixes) || prefixes.length === 0) { + return null; + } + + for (const prefix of prefixes) { + // Require a segment boundary so `/gatewayx/v1/...` does not match `/gateway`, + // and `pathname === prefix` (nothing underneath) falls through. + if (!pathname.startsWith(`${prefix}/`)) continue; + + const rest = pathname.slice(prefix.length); + for (const root of PROXY_PATH_ROOTS) { + if (rest === root || rest.startsWith(`${root}/`)) { + return rest; + } + } + } + + return null; +} + +/** + * Strip a listen prefix from a raw request URL, preserving the query string + * verbatim (no decoding, so `parse(req.url, true)` keeps its semantics). + * + * @param {unknown} rawUrl + * @param {readonly string[]} prefixes + * @returns {string | null} rewritten "path?query", or null when untouched + */ +function rewriteListenPrefixedUrl(rawUrl, prefixes) { + if (typeof rawUrl !== "string" || rawUrl === "") return null; + + const queryIndex = rawUrl.indexOf("?"); + const pathname = queryIndex === -1 ? rawUrl : rawUrl.slice(0, queryIndex); + const suffix = queryIndex === -1 ? "" : rawUrl.slice(queryIndex); + + const stripped = stripListenPrefix(pathname, prefixes); + if (stripped === null) return null; + + return `${stripped}${suffix}`; +} + +module.exports = { + parseListenPrefixes, + stripListenPrefix, + rewriteListenPrefixedUrl, + PROXY_PATH_ROOTS, + RESERVED_FIRST_SEGMENTS, +}; diff --git a/server.js b/server.js index 53dadb2de6..edae876c88 100644 --- a/server.js +++ b/server.js @@ -19,12 +19,22 @@ // 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. +// +// Listen prefix: when PROXY_LISTEN_PREFIX is configured, this server strips the +// prefix from req.url before handing to Next (so the Next middleware never sees +// it and prefixed bodies avoid the middleware body clone) and accepts prefixed +// WebSocket upgrades such as /gateway/v1/responses. See server-lib/listen-prefix.js. "use strict"; const http = require("node:http"); const { randomUUID } = require("node:crypto"); const { parse } = require("node:url"); +const { + parseListenPrefixes, + rewriteListenPrefixedUrl, + stripListenPrefix, +} = require("./server-lib/listen-prefix"); function isNextDevMode(nodeEnv) { return nodeEnv !== "production"; @@ -42,6 +52,7 @@ const INTERNAL_TUNNEL_HOST = hostname === "0.0.0.0" || hostname === "::" || hostname === "*" ? "127.0.0.1" : hostname; const WS_PATH = "/v1/responses"; +const WS_PATH_UNPREFIXED = "/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"; @@ -1072,10 +1083,21 @@ async function forwardToInternalHttp( }); } -function isResponsesWsUpgrade(req) { +function isResponsesWsUpgrade(req, listenPrefixes = []) { if (!req.url) return false; const parsed = parse(req.url); - return parsed.pathname === WS_PATH; + let pathname = parsed.pathname; + if (pathname && pathname.length > 1 && pathname.endsWith("/")) { + pathname = pathname.slice(0, -1); + } + // PROXY_LISTEN_PREFIX: accept //v1/responses and //responses. + // The internal tunnel always POSTs to the canonical WS_PATH, so only this + // acceptance check needs to know about the prefix. + const stripped = stripListenPrefix(pathname, listenPrefixes); + if (stripped !== null) { + pathname = stripped.length > 1 && stripped.endsWith("/") ? stripped.slice(0, -1) : stripped; + } + return pathname === WS_PATH || pathname === WS_PATH_UNPREFIXED; } function listenServer(server, options) { @@ -1169,9 +1191,22 @@ async function main() { process.env[INTERNAL_SECRET_ENV] = randomUUID(); } - const app = nextFactory({ dev, hostname, port }); + // Next lazily installs its upgrade handler on the first HTTP request's server. + // Bind it to the private listener so it cannot close a Responses socket that + // the public listener has already accepted through our WebSocketServer. + const internalServer = http.createServer(); + const app = nextFactory({ dev, hostname, port, httpServer: internalServer }); const handler = app.getRequestHandler(); await app.prepare(); + // `.env` 在 `node server.js` 路径下由 app.prepare() 加载(cluster.js 则更早用 + // @next/env 加载),因此监听前缀必须在 prepare 之后读取。 + const { prefixes: listenPrefixes, error: listenPrefixError } = parseListenPrefixes( + process.env.PROXY_LISTEN_PREFIX + ); + if (listenPrefixError) { + log("error", "listen_prefix_invalid", { error: `PROXY_LISTEN_PREFIX ${listenPrefixError}` }); + process.exit(1); + } // 基础加载完成后再建立单进程/worker 预算;与 Next bundle 通过 Symbol 共享实例。 const { getMemoryGovernor } = require("./server-lib/memory-governor"); const memoryGovernor = getMemoryGovernor(); @@ -1191,6 +1226,12 @@ async function main() { const requestListener = async (req, res) => { try { + // 剥离监听前缀后再交给 Next:下游只看到规范代理路径,且这些请求不会进入 + // Next middleware(避免正文克隆与 proxyClientMaxBodySize 截断)。 + const rewrittenUrl = rewriteListenPrefixedUrl(req.url, listenPrefixes); + if (rewrittenUrl !== null) { + req.url = rewrittenUrl; + } const parsedUrl = parse(req.url, true); await handler(req, res, parsedUrl); } catch (err) { @@ -1206,7 +1247,7 @@ async function main() { // WebSocket frame 必须重新进入持有客户端连接和持久上游会话的同一个 worker。 // 私有独占 listener 无需经 IPC 复制请求正文即可保证这一不变量。 - const internalServer = http.createServer(requestListener); + internalServer.on("request", requestListener); const internalHttpTarget = await listenOnPrivateLoopback(internalServer); const server = http.createServer(requestListener); @@ -1215,7 +1256,7 @@ async function main() { wss = new WebSocketServer({ noServer: true, maxPayload: WS_MAX_PAYLOAD_BYTES }); server.on("upgrade", (req, socket, head) => { - if (!isResponsesWsUpgrade(req)) { + if (!isResponsesWsUpgrade(req, listenPrefixes)) { socket.destroy(); return; } @@ -1253,6 +1294,7 @@ async function main() { internalTunnelHost: internalHttpTarget.hostname, internalTunnelPort: internalHttpTarget.port, wsEnabled: !!WebSocketServer, + listenPrefixes, workerIndex: process.env.CCH_MULTICORE_WORKER_INDEX ?? null, workerCount: process.env.CCH_MULTICORE_WORKER_COUNT ?? "1", }); @@ -1417,6 +1459,7 @@ module.exports = { listenOnPrivateLoopback, notifyMulticoreReady, registerOrchestratedShutdown, + isResponsesWsUpgrade, WS_MAX_PAYLOAD_BYTES, MAX_PENDING_BYTES, MAX_PENDING_OUTBOUND_BYTES, diff --git a/src/actions/active-sessions.ts b/src/actions/active-sessions.ts index bcc21aa6fd..fb8b58c49a 100644 --- a/src/actions/active-sessions.ts +++ b/src/actions/active-sessions.ts @@ -8,6 +8,7 @@ import { setSessionDetailsCache, } from "@/lib/cache/session-cache"; import { logger } from "@/lib/logger"; +import { resolveDashboardCacheTtlSeconds } from "@/lib/redis/dashboard-cache-ttl"; import { extractAfterRequestMessages, isSessionMessages } from "@/lib/session-detail-snapshots"; import { resolveSessionRequestLocator } from "@/lib/session-request-locator"; import { normalizeRequestSequence } from "@/lib/utils/request-sequence"; @@ -23,6 +24,20 @@ import type { SpecialSetting } from "@/types/special-settings"; import { summarizeTerminateSessionsBatch } from "./active-sessions-utils"; import type { ActionResult } from "./types"; +/** + * Active session list aggregates are expensive (per-session usage_ledger sums) and polled every + * few seconds by several dashboard components; live concurrency counts are still read per call. + */ +const ACTIVE_SESSIONS_CACHE_TTL_SECONDS = 5; +const ACTIVE_SESSIONS_CACHE_TTL_HIGH_CONCURRENCY_SECONDS = 10; + +function resolveActiveSessionsCacheTtlSeconds(): number { + return resolveDashboardCacheTtlSeconds( + ACTIVE_SESSIONS_CACHE_TTL_SECONDS, + ACTIVE_SESSIONS_CACHE_TTL_HIGH_CONCURRENCY_SECONDS + ); +} + type ResolvedSessionIdentity = NonNullable< Awaited> >; @@ -344,7 +359,7 @@ export async function getActiveSessions(): Promise findRecentActivityStream(limit), + }); +} /** * 获取数据大屏的所有实时数据 @@ -96,7 +115,7 @@ export async function getDashboardRealtimeData(): Promise, + getLeaderboardWithCache("daily", settings.currencyDisplay, "provider") as Promise< + ProviderLeaderboardEntry[] + >, getProviderSlots(), - findDailyModelLeaderboard(), + getLeaderboardWithCache("daily", settings.currencyDisplay, "model") as Promise< + ModelLeaderboardEntry[] + >, getUserStatistics("today"), ]); diff --git a/src/actions/dispatch-simulator.ts b/src/actions/dispatch-simulator.ts index 9ed7a174aa..8fb9f0cd50 100644 --- a/src/actions/dispatch-simulator.ts +++ b/src/actions/dispatch-simulator.ts @@ -13,7 +13,8 @@ import { getCircuitState, isCircuitOpen } from "@/lib/circuit-breaker"; import { PROVIDER_GROUP } from "@/lib/constants/provider.constants"; import { logger } from "@/lib/logger"; import { getEndpointFilterStats } from "@/lib/provider-endpoints/endpoint-selector"; -import { getProviderModelRedirectTarget } from "@/lib/provider-model-redirects"; +import { matchesProviderPrefix } from "@/lib/provider-prefix"; +import { resolveUpstreamModel } from "@/lib/provider-upstream-model"; import { RateLimitService } from "@/lib/rate-limit"; import { resolveSystemTimezone } from "@/lib/utils/timezone"; import { isVendorTypeCircuitOpen } from "@/lib/vendor-type-circuit-breaker"; @@ -116,9 +117,7 @@ async function buildPriorityTiers( const enriched = await Promise.all( providers.map(async (provider) => ({ provider, - redirectedModel: modelName - ? getProviderModelRedirectTarget(modelName, provider.modelRedirects) - : null, + redirectedModel: modelName ? resolveUpstreamModel(provider, modelName).model : null, endpointStats: await getEndpointStats(provider), })) ); @@ -271,7 +270,11 @@ export async function simulateDispatchDecisionTree( ) .map((provider) => buildProviderSnapshot(provider, groupFilter, { - details: `model ${normalizedModelName} did not match allowlist`, + details: + provider.providerPrefix && + !matchesProviderPrefix(normalizedModelName, provider.providerPrefix) + ? `model ${normalizedModelName} did not match provider prefix ${provider.providerPrefix}` + : `model ${normalizedModelName} did not match allowlist`, }) ), groupFilter, @@ -376,7 +379,7 @@ export async function simulateDispatchDecisionTree( .map((provider) => buildProviderSnapshot(provider, groupFilter, { redirectedModel: normalizedModelName - ? getProviderModelRedirectTarget(normalizedModelName, provider.modelRedirects) + ? resolveUpstreamModel(provider, normalizedModelName).model : null, }) ), @@ -386,20 +389,26 @@ export async function simulateDispatchDecisionTree( selectedPriorityProviderIds.has(provider.id) ); - const redirectedProviders = currentProviders.map((provider) => - buildProviderSnapshot(provider, groupFilter, { - redirectedModel: normalizedModelName - ? getProviderModelRedirectTarget(normalizedModelName, provider.modelRedirects) - : null, + const redirectedProviders = currentProviders.map((provider) => { + if (normalizedModelName === "") { + return buildProviderSnapshot(provider, groupFilter, { + redirectedModel: null, + details: "no_model_name_provided", + }); + } + + const resolution = resolveUpstreamModel(provider, normalizedModelName); + return buildProviderSnapshot(provider, groupFilter, { + redirectedModel: resolution.model, + ...(resolution.prefixStripped ? { prefixStripped: true } : {}), details: - normalizedModelName === "" - ? "no_model_name_provided" - : getProviderModelRedirectTarget(normalizedModelName, provider.modelRedirects) !== - normalizedModelName - ? "redirect_rule_matched" + resolution.matchedRule && resolution.model !== normalizedModelName + ? "redirect_rule_matched" + : resolution.prefixStripped + ? "provider_prefix_stripped" : "no_redirect_rule_matched", - }) - ); + }); + }); steps.push({ stepName: "modelRedirect", stepIndex: 8, diff --git a/src/actions/notification-bindings.ts b/src/actions/notification-bindings.ts index ab3be2243f..f18adb150b 100644 --- a/src/actions/notification-bindings.ts +++ b/src/actions/notification-bindings.ts @@ -18,6 +18,7 @@ const NotificationTypeSchema = z.enum([ "daily_leaderboard", "cost_alert", "cache_hit_rate_alert", + "client_problem", ]); const BindingInputSchema: z.ZodType = z.object({ diff --git a/src/actions/providers.ts b/src/actions/providers.ts index 436f9278c4..2d56dd7834 100644 --- a/src/actions/providers.ts +++ b/src/actions/providers.ts @@ -34,9 +34,19 @@ import { PROVIDER_MODEL_REDIRECT_RULE_LIST_SCHEMA } from "@/lib/provider-model-r import { normalizeProviderModelRedirectRules } from "@/lib/provider-model-redirects"; import { buildProviderBatchApplyUpdates, + hasLegacyReasoningEffortOverrideFields, + hasLegacyServiceTierOverrideFields, hasProviderBatchPatchChanges, + hasProviderReasoningEffortOverrideRulesField, + hasProviderServiceTierOverrideRulesField, normalizeProviderBatchPatchDraft, PROVIDER_PATCH_ERROR_CODES, + type ProviderBatchApplyUpdatesWithReasoningEffortRules, + type ProviderBatchPatchWithReasoningEffortRules, + validateProviderReasoningEffortOverrideBatch, + validateProviderReasoningEffortOverrideMutation, + validateProviderServiceTierOverrideBatch, + validateProviderServiceTierOverrideMutation, } from "@/lib/provider-patch-contract"; import { executeProviderTest, @@ -46,6 +56,11 @@ import { type TestSubStatus, } from "@/lib/provider-testing"; import { getPresetsForProvider } from "@/lib/provider-testing/presets"; +import { clearUpstreamQuotaState } from "@/lib/provider-upstream-quota/state"; +import { + buildProviderUpstreamQuotaStatusMap, + refreshProviderUpstreamQuotaNow, +} from "@/lib/provider-upstream-quota/status"; import { createProxyAgentForProvider, fetchWithDispatcher, @@ -68,6 +83,7 @@ import { extractZodErrorCode, formatZodError } from "@/lib/utils/zod-i18n"; import { validateProviderUrlForConnectivity } from "@/lib/validation/provider-url"; import { CreateProviderSchema, UpdateProviderSchema } from "@/lib/validation/schemas"; import { restoreProvidersBatch } from "@/repository"; +import type { ProviderWithReasoningEffortOverrideRules } from "@/repository/_shared/transformers"; import { applyProviderBatchOperationIfUnchanged, type BatchProviderUpdates, @@ -108,7 +124,6 @@ import type { CodexServiceTierPreference, CodexTextVerbosityPreference, Provider, - ProviderBatchApplyUpdates, ProviderBatchPatch, ProviderBatchPatchField, ProviderDisplay, @@ -116,7 +131,15 @@ import type { ProviderPatchOperation, ProviderStatisticsMap, ProviderType, + ReasoningEffortOverrideRule, + ServiceTierOverrideRule, } from "@/types/provider"; +import type { + ProviderUpstreamQuotaStatus, + ProviderUpstreamQuotaStatusMap, + UpstreamQuotaProbeOptions, + UpstreamQuotaProbeType, +} from "@/types/upstream-quota"; import type { ActionResult } from "./types"; type AutoSortResult = { @@ -349,6 +372,7 @@ export async function getProviders(): Promise { providerVendorId: provider.providerVendorId, preserveClientIp: provider.preserveClientIp, disableSessionReuse: provider.disableSessionReuse, + overwriteResponseModel: provider.overwriteResponseModel, modelRedirects: provider.modelRedirects, activeTimeStart: provider.activeTimeStart, activeTimeEnd: provider.activeTimeEnd, @@ -357,6 +381,9 @@ export async function getProviders(): Promise { blockedClients: provider.blockedClients, mcpPassthroughType: provider.mcpPassthroughType, mcpPassthroughUrl: provider.mcpPassthroughUrl, + upstreamQuotaProbeType: provider.upstreamQuotaProbeType, + upstreamQuotaThresholdPercent: provider.upstreamQuotaThresholdPercent, + upstreamQuotaProbeOptions: provider.upstreamQuotaProbeOptions, limit5hUsd: provider.limit5hUsd, limit5hResetMode: provider.limit5hResetMode, limitDailyUsd: provider.limitDailyUsd, @@ -379,6 +406,7 @@ export async function getProviders(): Promise { requestTimeoutNonStreamingMs: provider.requestTimeoutNonStreamingMs, websiteUrl: provider.websiteUrl, faviconUrl: provider.faviconUrl, + providerPrefix: provider.providerPrefix ?? null, cacheTtlPreference: provider.cacheTtlPreference, swapCacheTtlBilling: provider.swapCacheTtlBilling, context1mPreference: provider.context1mPreference, @@ -391,6 +419,8 @@ export async function getProviders(): Promise { anthropicMaxTokensPreference: provider.anthropicMaxTokensPreference, anthropicThinkingBudgetPreference: provider.anthropicThinkingBudgetPreference, anthropicAdaptiveThinking: provider.anthropicAdaptiveThinking, + reasoningEffortOverrideRules: provider.reasoningEffortOverrideRules ?? null, + serviceTierOverrideRules: provider.serviceTierOverrideRules ?? null, geminiGoogleSearchPreference: provider.geminiGoogleSearchPreference, tpm: provider.tpm, rpm: provider.rpm, @@ -578,6 +608,8 @@ export async function addProvider(data: { anthropic_max_tokens_preference?: AnthropicMaxTokensPreference | null; anthropic_thinking_budget_preference?: AnthropicThinkingBudgetPreference | null; anthropic_adaptive_thinking?: AnthropicAdaptiveThinkingConfig | null; + reasoning_effort_override_rules?: ReasoningEffortOverrideRule[] | null; + service_tier_override_rules?: ServiceTierOverrideRule[] | null; max_retry_attempts?: number | null; circuit_breaker_failure_threshold?: number; circuit_breaker_open_duration?: number; @@ -589,8 +621,12 @@ export async function addProvider(data: { streaming_idle_timeout_ms?: number; request_timeout_non_streaming_ms?: number; website_url?: string | null; + provider_prefix?: string | null; mcp_passthrough_type?: "none" | "minimax" | "glm" | "custom"; mcp_passthrough_url?: string | null; + upstream_quota_probe_type?: UpstreamQuotaProbeType; + upstream_quota_threshold_percent?: number | null; + upstream_quota_probe_options?: UpstreamQuotaProbeOptions | null; tpm: number | null; rpm: number | null; rpd: number | null; @@ -618,6 +654,26 @@ export async function addProvider(data: { } const validated = CreateProviderSchema.parse(data); + const reasoningValidation = validateProviderReasoningEffortOverrideMutation({ + providerType: validated.provider_type, + hasRulesField: hasProviderReasoningEffortOverrideRulesField(data), + rules: validated.reasoning_effort_override_rules, + hasLegacyFields: hasLegacyReasoningEffortOverrideFields(data), + existingRules: null, + }); + if (!reasoningValidation.ok) { + return { ok: false, error: reasoningValidation.error, errorCode: "INVALID_INPUT" }; + } + const serviceTierValidation = validateProviderServiceTierOverrideMutation({ + providerType: validated.provider_type, + hasRulesField: hasProviderServiceTierOverrideRulesField(data), + rules: validated.service_tier_override_rules, + hasLegacyFields: hasLegacyServiceTierOverrideFields(data), + existingRules: null, + }); + if (!serviceTierValidation.ok) { + return { ok: false, error: serviceTierValidation.error, errorCode: "INVALID_INPUT" }; + } logger.trace("addProvider:validated", { name: validated.name }); // 获取 favicon URL @@ -674,6 +730,7 @@ export async function addProvider(data: { codex_image_generation_preference: validated.codex_image_generation_preference, codex_service_tier_preference: validated.codex_service_tier_preference ?? "inherit", website_url: validated.website_url ?? null, + provider_prefix: validated.provider_prefix ?? null, favicon_url: faviconUrl, tpm: validated.tpm ?? null, rpm: validated.rpm ?? null, @@ -797,6 +854,8 @@ export async function editProvider( anthropic_max_tokens_preference?: AnthropicMaxTokensPreference | null; anthropic_thinking_budget_preference?: AnthropicThinkingBudgetPreference | null; anthropic_adaptive_thinking?: AnthropicAdaptiveThinkingConfig | null; + reasoning_effort_override_rules?: ReasoningEffortOverrideRule[] | null; + service_tier_override_rules?: ServiceTierOverrideRule[] | null; max_retry_attempts?: number | null; circuit_breaker_failure_threshold?: number; circuit_breaker_open_duration?: number; @@ -808,8 +867,12 @@ export async function editProvider( streaming_idle_timeout_ms?: number; request_timeout_non_streaming_ms?: number; website_url?: string | null; + provider_prefix?: string | null; mcp_passthrough_type?: "none" | "minimax" | "glm" | "custom"; mcp_passthrough_url?: string | null; + upstream_quota_probe_type?: UpstreamQuotaProbeType; + upstream_quota_threshold_percent?: number | null; + upstream_quota_probe_options?: UpstreamQuotaProbeOptions | null; tpm?: number | null; rpm?: number | null; rpd?: number | null; @@ -869,6 +932,27 @@ export async function editProvider( return { ok: false, error: "供应商不存在" }; } + const reasoningValidation = validateProviderReasoningEffortOverrideMutation({ + providerType: validated.provider_type ?? currentProvider.providerType, + hasRulesField: hasProviderReasoningEffortOverrideRulesField(data), + rules: validated.reasoning_effort_override_rules, + hasLegacyFields: hasLegacyReasoningEffortOverrideFields(data), + existingRules: currentProvider.reasoningEffortOverrideRules, + }); + if (!reasoningValidation.ok) { + return { ok: false, error: reasoningValidation.error, errorCode: "INVALID_INPUT" }; + } + const serviceTierValidation = validateProviderServiceTierOverrideMutation({ + providerType: validated.provider_type ?? currentProvider.providerType, + hasRulesField: hasProviderServiceTierOverrideRulesField(data), + rules: validated.service_tier_override_rules, + hasLegacyFields: hasLegacyServiceTierOverrideFields(data), + existingRules: currentProvider.serviceTierOverrideRules, + }); + if (!serviceTierValidation.ok) { + return { ok: false, error: serviceTierValidation.error, errorCode: "INVALID_INPUT" }; + } + const preimageFields: Record = {}; for (const [field, nextValue] of Object.entries(payload)) { if (field === "key") { @@ -919,6 +1003,26 @@ export async function editProvider( await SessionManager.terminateStickySessionsForProviders([providerId], "editProvider"); } + // 上游额度快照与凭证/地址/探测配置绑定:任一变化即丢弃旧快照,等待重新探测 + if ( + (payload.key !== undefined && payload.key !== currentProvider.key) || + (payload.url !== undefined && payload.url !== currentProvider.url) || + (payload.upstream_quota_probe_type !== undefined && + payload.upstream_quota_probe_type !== currentProvider.upstreamQuotaProbeType) || + (payload.upstream_quota_probe_options !== undefined && + JSON.stringify(payload.upstream_quota_probe_options ?? null) !== + JSON.stringify(currentProvider.upstreamQuotaProbeOptions ?? null)) + ) { + try { + await clearUpstreamQuotaState(providerId); + } catch (error) { + logger.warn("editProvider:upstream_quota_state_clear_failed", { + providerId, + error: error instanceof Error ? error.message : String(error), + }); + } + } + if ( payload.limit_5h_reset_mode !== undefined && payload.limit_5h_reset_mode !== currentProvider.limit5hResetMode @@ -1043,6 +1147,14 @@ export async function removeProvider( // 清除内存缓存(无论 Redis 是否成功都要执行) clearConfigCache(providerId); await clearProviderState(providerId); + try { + await clearUpstreamQuotaState(providerId); + } catch (error) { + logger.warn("removeProvider:upstream_quota_state_clear_failed", { + providerId, + error: error instanceof Error ? error.message : String(error), + }); + } // 删除 Redis 缓存(非关键路径,失败时记录警告) try { @@ -1299,6 +1411,46 @@ export async function resetProviderCircuit(providerId: number): Promise { + try { + const session = await getSession(); + if (session?.user.role !== "admin") { + return {}; + } + const providers = await findAllProvidersFresh(); + return await buildProviderUpstreamQuotaStatusMap(providers); + } catch (error) { + logger.error("获取上游额度状态失败:", error); + return {}; + } +} + +/** + * 立即探测单个供应商的上游额度(忽略探测间隔) + */ +export async function refreshProviderUpstreamQuota( + providerId: number +): Promise> { + try { + const session = await getSession(); + if (session?.user.role !== "admin") { + return { ok: false, error: "无权限执行此操作" }; + } + const result = await refreshProviderUpstreamQuotaNow(providerId); + if (!result.ok) { + return { ok: false, error: result.error, errorCode: result.errorCode }; + } + return { ok: true, data: result.status }; + } catch (error) { + logger.error("刷新上游额度失败:", error); + const message = error instanceof Error ? error.message : "刷新上游额度失败"; + return { ok: false, error: message }; + } +} + /** * 手动重置供应商“总用量”(用于总消费上限 limit_total_usd) * @@ -1339,6 +1491,11 @@ const PROVIDER_BATCH_PREVIEW_TTL_SECONDS = 60; const PROVIDER_PATCH_UNDO_TTL_SECONDS = 10; const PROVIDER_DELETE_UNDO_TTL_SECONDS = 60; +type ProviderBatchPatchFieldWithReasoningEffortRules = + | ProviderBatchPatchField + | "reasoning_effort_override_rules" + | "service_tier_override_rules"; + const ProviderBatchPatchProviderIdsSchema = z .array(z.number().int().positive()) .min(1) @@ -1379,7 +1536,7 @@ const UndoProviderDeleteSchema = z export interface ProviderBatchPreviewRow { providerId: number; providerName: string; - field: ProviderBatchPatchField; + field: ProviderBatchPatchFieldWithReasoningEffortRules; status: "changed" | "skipped"; before: unknown; after: unknown; @@ -1391,7 +1548,7 @@ export interface PreviewProviderBatchPatchResult { previewRevision: string; previewExpiresAt: string; providerIds: number[]; - changedFields: ProviderBatchPatchField[]; + changedFields: ProviderBatchPatchFieldWithReasoningEffortRules[]; rows: ProviderBatchPreviewRow[]; summary: { providerCount: number; @@ -1440,9 +1597,9 @@ interface ProviderBatchPatchPreviewSnapshot { previewToken: string; previewRevision: string; providerIds: number[]; - patch: ProviderBatchPatch; + patch: ProviderBatchPatchWithReasoningEffortRules; patchSerialized: string; - changedFields: ProviderBatchPatchField[]; + changedFields: ProviderBatchPatchFieldWithReasoningEffortRules[]; rows: ProviderBatchPreviewRow[]; providerTypes: Record; providerEnabled: Record; @@ -1476,7 +1633,10 @@ const providerDeleteUndoStore = new RedisKVStore({ }); type ProviderPatchActionError = Extract; -const SINGLE_EDIT_PREIMAGE_FIELD_TO_PROVIDER_KEY: Record = { +const SINGLE_EDIT_PREIMAGE_FIELD_TO_PROVIDER_KEY: Record< + string, + keyof ProviderWithReasoningEffortOverrideRules +> = { name: "name", url: "url", new_api_access_token: "newApiAccessToken", @@ -1490,6 +1650,8 @@ const SINGLE_EDIT_PREIMAGE_FIELD_TO_PROVIDER_KEY: Record provider_type: "providerType", preserve_client_ip: "preserveClientIp", disable_session_reuse: "disableSessionReuse", + overwrite_response_model: "overwriteResponseModel", + provider_prefix: "providerPrefix", active_time_start: "activeTimeStart", active_time_end: "activeTimeEnd", model_redirects: "modelRedirects", @@ -1517,6 +1679,8 @@ const SINGLE_EDIT_PREIMAGE_FIELD_TO_PROVIDER_KEY: Record anthropic_max_tokens_preference: "anthropicMaxTokensPreference", anthropic_thinking_budget_preference: "anthropicThinkingBudgetPreference", anthropic_adaptive_thinking: "anthropicAdaptiveThinking", + reasoning_effort_override_rules: "reasoningEffortOverrideRules", + service_tier_override_rules: "serviceTierOverrideRules", gemini_google_search_preference: "geminiGoogleSearchPreference", max_retry_attempts: "maxRetryAttempts", circuit_breaker_failure_threshold: "circuitBreakerFailureThreshold", @@ -1532,6 +1696,9 @@ const SINGLE_EDIT_PREIMAGE_FIELD_TO_PROVIDER_KEY: Record favicon_url: "faviconUrl", mcp_passthrough_type: "mcpPassthroughType", mcp_passthrough_url: "mcpPassthroughUrl", + upstream_quota_probe_type: "upstreamQuotaProbeType", + upstream_quota_threshold_percent: "upstreamQuotaThresholdPercent", + upstream_quota_probe_options: "upstreamQuotaProbeOptions", tpm: "tpm", rpm: "rpm", rpd: "rpd", @@ -1563,8 +1730,10 @@ function dedupeProviderIds(providerIds: number[]): number[] { return [...new Set(providerIds)].sort((a, b) => a - b); } -function getChangedPatchFields(patch: ProviderBatchPatch): ProviderBatchPatchField[] { - return (Object.keys(patch) as ProviderBatchPatchField[]).filter( +function getChangedPatchFields( + patch: ProviderBatchPatchWithReasoningEffortRules +): ProviderBatchPatchFieldWithReasoningEffortRules[] { + return (Object.keys(patch) as ProviderBatchPatchFieldWithReasoningEffortRules[]).filter( (field) => patch[field].mode !== "no_change" ); } @@ -1669,7 +1838,7 @@ function buildExpectedProviderBatchPreimages( } function mapApplyUpdatesToRepositoryFormat( - applyUpdates: ProviderBatchApplyUpdates + applyUpdates: ProviderBatchApplyUpdatesWithReasoningEffortRules ): BatchProviderUpdates { const result: BatchProviderUpdates = {}; if (applyUpdates.is_enabled !== undefined) { @@ -1705,12 +1874,21 @@ function mapApplyUpdatesToRepositoryFormat( if (applyUpdates.anthropic_adaptive_thinking !== undefined) { result.anthropicAdaptiveThinking = applyUpdates.anthropic_adaptive_thinking; } + if (applyUpdates.reasoning_effort_override_rules !== undefined) { + result.reasoningEffortOverrideRules = applyUpdates.reasoning_effort_override_rules; + } + if (applyUpdates.service_tier_override_rules !== undefined) { + result.serviceTierOverrideRules = applyUpdates.service_tier_override_rules; + } if (applyUpdates.preserve_client_ip !== undefined) { result.preserveClientIp = applyUpdates.preserve_client_ip; } if (applyUpdates.disable_session_reuse !== undefined) { result.disableSessionReuse = applyUpdates.disable_session_reuse; } + if (applyUpdates.overwrite_response_model !== undefined) { + result.overwriteResponseModel = applyUpdates.overwrite_response_model; + } if (applyUpdates.active_time_start !== undefined) { result.activeTimeStart = applyUpdates.active_time_start; } @@ -1819,10 +1997,19 @@ function mapApplyUpdatesToRepositoryFormat( if (applyUpdates.mcp_passthrough_url !== undefined) { result.mcpPassthroughUrl = applyUpdates.mcp_passthrough_url; } + if (applyUpdates.upstream_quota_probe_type !== undefined) { + result.upstreamQuotaProbeType = applyUpdates.upstream_quota_probe_type; + } + if (applyUpdates.upstream_quota_threshold_percent !== undefined) { + result.upstreamQuotaThresholdPercent = applyUpdates.upstream_quota_threshold_percent; + } return result; } -const PATCH_FIELD_TO_PROVIDER_KEY: Record = { +const PATCH_FIELD_TO_PROVIDER_KEY: Record< + ProviderBatchPatchFieldWithReasoningEffortRules, + keyof ProviderWithReasoningEffortOverrideRules +> = { is_enabled: "isEnabled", priority: "priority", weight: "weight", @@ -1834,8 +2021,11 @@ const PATCH_FIELD_TO_PROVIDER_KEY: Record> = { +const PATCH_FIELD_CLEAR_VALUE: Partial< + Record +> = { allowed_clients: [], blocked_clients: [], anthropic_thinking_budget_preference: "inherit", @@ -1886,26 +2080,29 @@ const PATCH_FIELD_CLEAR_VALUE: Partial> codex_service_tier_preference: "inherit", anthropic_max_tokens_preference: "inherit", gemini_google_search_preference: "inherit", + reasoning_effort_override_rules: null, + service_tier_override_rules: null, mcp_passthrough_type: "none", }; -const CLAUDE_ONLY_FIELDS: ReadonlySet = new Set([ +const CLAUDE_ONLY_FIELDS: ReadonlySet = new Set([ "anthropic_thinking_budget_preference", "anthropic_adaptive_thinking", "anthropic_max_tokens_preference", "context_1m_preference", ]); -const CODEX_ONLY_FIELDS: ReadonlySet = new Set([ +const CODEX_ONLY_FIELDS: ReadonlySet = new Set([ "codex_reasoning_effort_preference", "codex_reasoning_summary_preference", "codex_text_verbosity_preference", "codex_parallel_tool_calls_preference", "codex_image_generation_preference", "codex_service_tier_preference", + "service_tier_override_rules", ]); -const GEMINI_ONLY_FIELDS: ReadonlySet = new Set([ +const GEMINI_ONLY_FIELDS: ReadonlySet = new Set([ "gemini_google_search_preference", ]); @@ -1941,6 +2138,7 @@ const CODEX_ONLY_REPO_KEYS: ReadonlySet = new Set([ "codexParallelToolCallsPreference", "codexImageGenerationPreference", "codexServiceTierPreference", + "serviceTierOverrideRules", ]); const GEMINI_ONLY_REPO_KEYS: ReadonlySet = new Set([ @@ -1965,7 +2163,7 @@ function filterRepositoryUpdatesByProviderType( } function computePreviewAfterValue( - field: ProviderBatchPatchField, + field: ProviderBatchPatchFieldWithReasoningEffortRules, operation: ProviderPatchOperation ): unknown { if (operation.mode === "set") { @@ -1985,9 +2183,9 @@ function computePreviewAfterValue( } function generatePreviewRows( - providers: Provider[], - patch: ProviderBatchPatch, - changedFields: ProviderBatchPatchField[] + providers: ProviderWithReasoningEffortOverrideRules[], + patch: ProviderBatchPatchWithReasoningEffortRules, + changedFields: ProviderBatchPatchFieldWithReasoningEffortRules[] ): ProviderBatchPreviewRow[] { const rows: ProviderBatchPreviewRow[] = []; @@ -2220,6 +2418,20 @@ export async function previewProviderBatchPatch( const allProviders = await findAllProvidersFresh(); const providerIdSet = new Set(providerIds); const matchedProviders = allProviders.filter((p) => providerIdSet.has(p.id)); + const reasoningValidation = validateProviderReasoningEffortOverrideBatch({ + patch: normalizedPatch.data, + providers: matchedProviders, + }); + if (!reasoningValidation.ok) { + return { ok: false, error: reasoningValidation.error, errorCode: "INVALID_INPUT" }; + } + const serviceTierValidation = validateProviderServiceTierOverrideBatch({ + patch: normalizedPatch.data, + providers: matchedProviders, + }); + if (!serviceTierValidation.ok) { + return { ok: false, error: serviceTierValidation.error, errorCode: "INVALID_INPUT" }; + } const rows = generatePreviewRows(matchedProviders, normalizedPatch.data, changedFields); const skipCount = rows.filter((r) => r.status === "skipped").length; @@ -2367,6 +2579,23 @@ export async function applyProviderBatchPatch( }; } + const allProviders = await findAllProvidersFresh(); + const effectiveIdSet = new Set(effectiveProviderIds); + const matchedProviders = allProviders.filter((p) => effectiveIdSet.has(p.id)); + const reasoningValidation = validateProviderReasoningEffortOverrideBatch({ + patch: normalizedPatch.data, + providers: matchedProviders, + }); + if (!reasoningValidation.ok) { + return { ok: false, error: reasoningValidation.error, errorCode: "INVALID_INPUT" }; + } + const serviceTierValidation = validateProviderServiceTierOverrideBatch({ + patch: normalizedPatch.data, + providers: matchedProviders, + }); + if (!serviceTierValidation.ok) { + return { ok: false, error: serviceTierValidation.error, errorCode: "INVALID_INPUT" }; + } const changedFields = getChangedPatchFields(normalizedPatch.data); const preimages = buildExpectedProviderBatchPreimages(snapshot, providerIds); if (!preimages) { @@ -2732,6 +2961,8 @@ export interface BatchUpdateProvidersParams { codex_image_generation_preference?: CodexImageGenerationPreference | null; anthropic_thinking_budget_preference?: AnthropicThinkingBudgetPreference | null; anthropic_adaptive_thinking?: AnthropicAdaptiveThinkingConfig | null; + reasoning_effort_override_rules?: ReasoningEffortOverrideRule[] | null; + service_tier_override_rules?: ServiceTierOverrideRule[] | null; }; } @@ -2754,6 +2985,48 @@ export async function batchUpdateProviders( return { ok: false, error: `单次批量操作最多支持 ${BATCH_OPERATION_MAX_SIZE} 个供应商` }; } + const hasRulesField = hasProviderReasoningEffortOverrideRulesField(updates); + const hasLegacyFields = hasLegacyReasoningEffortOverrideFields(updates); + if (hasRulesField || hasLegacyFields) { + const currentProviders = await findAllProvidersFresh(); + const providerIdSet = new Set(providerIds); + const selectedProviders = currentProviders.filter((provider) => + providerIdSet.has(provider.id) + ); + for (const provider of selectedProviders) { + const reasoningValidation = validateProviderReasoningEffortOverrideMutation({ + providerType: provider.providerType, + hasRulesField, + rules: updates.reasoning_effort_override_rules, + hasLegacyFields, + existingRules: provider.reasoningEffortOverrideRules, + }); + if (!reasoningValidation.ok) { + return { ok: false, error: reasoningValidation.error, errorCode: "INVALID_INPUT" }; + } + } + } + + const hasServiceTierRulesField = hasProviderServiceTierOverrideRulesField(updates); + const hasLegacyServiceTierFields = hasLegacyServiceTierOverrideFields(updates); + if (hasServiceTierRulesField || hasLegacyServiceTierFields) { + const currentProviders = await findAllProvidersFresh(); + const providerIdSet = new Set(providerIds); + for (const provider of currentProviders) { + if (!providerIdSet.has(provider.id)) continue; + const serviceTierValidation = validateProviderServiceTierOverrideMutation({ + providerType: provider.providerType, + hasRulesField: hasServiceTierRulesField, + rules: updates.service_tier_override_rules, + hasLegacyFields: hasLegacyServiceTierFields, + existingRules: provider.serviceTierOverrideRules, + }); + if (!serviceTierValidation.ok) { + return { ok: false, error: serviceTierValidation.error, errorCode: "INVALID_INPUT" }; + } + } + } + const hasUpdates = Object.values(updates).some((v) => v !== undefined); if (!hasUpdates) { return { ok: false, error: "请指定要更新的字段" }; @@ -2841,6 +3114,12 @@ export async function batchUpdateProviders( if (updates.anthropic_adaptive_thinking !== undefined) { repositoryUpdates.anthropicAdaptiveThinking = updates.anthropic_adaptive_thinking; } + if (updates.reasoning_effort_override_rules !== undefined) { + repositoryUpdates.reasoningEffortOverrideRules = updates.reasoning_effort_override_rules; + } + if (updates.service_tier_override_rules !== undefined) { + repositoryUpdates.serviceTierOverrideRules = updates.service_tier_override_rules; + } const updatedCount = await updateProvidersBatch(providerIds, repositoryUpdates); @@ -4343,7 +4622,6 @@ async function executeProviderApiTest( const shouldAttemptDirectRetry = Boolean(proxyConfig?.fallbackToDirect) && PROXY_RETRY_STATUS_CODES.has(response.status); - if (shouldAttemptDirectRetry) { const isCloudflareError = detectCloudflareGatewayError(response); @@ -4767,7 +5045,7 @@ export async function testProviderGemini( try { // 使用 GeminiAuth 获取 token (如果是 json 凭证) - processedApiKey = await GeminiAuth.getAccessToken(data.apiKey); + processedApiKey = await GeminiAuth.getAccessToken(data.apiKey, data.proxyUrl); isJsonCreds = GeminiAuth.isJson(data.apiKey); } catch (e) { // 忽略错误,让后续请求失败 @@ -5174,7 +5452,7 @@ export async function testProviderById( let geminiBearerAuth = false; if (isGeminiType) { try { - apiKey = await GeminiAuth.getAccessToken(provider.key); + apiKey = await GeminiAuth.getAccessToken(provider.key, provider.proxyUrl); geminiBearerAuth = GeminiAuth.isJson(provider.key); } catch (error) { logger.warn("testProviderById: gemini auth preprocess failed", { error, providerId }); @@ -5505,7 +5783,7 @@ async function fetchGeminiModels( let isJsonCreds = false; try { - processedApiKey = await GeminiAuth.getAccessToken(data.apiKey); + processedApiKey = await GeminiAuth.getAccessToken(data.apiKey, data.proxyUrl); isJsonCreds = GeminiAuth.isJson(data.apiKey); } catch (e) { logger.warn("fetchGeminiModels: auth process failed", { error: e }); diff --git a/src/actions/system-config.ts b/src/actions/system-config.ts index 96f53163a8..0ead048df4 100644 --- a/src/actions/system-config.ts +++ b/src/actions/system-config.ts @@ -92,6 +92,9 @@ export async function saveSystemSettings(formData: { stickySlaMs?: number; racingTotalTimeoutMs?: number; stickyTimeoutCooldownMs?: number; + upstreamQuotaSchedulingEnabled?: boolean; + upstreamQuotaThresholdPercent?: number; + upstreamQuotaProbeIntervalMinutes?: number; timezone?: string | null; enableAutoCleanup?: boolean; cleanupRetentionDays?: number; @@ -114,10 +117,12 @@ export async function saveSystemSettings(formData: { fakeStreamingWhitelist?: FakeStreamingWhitelistEntry[]; streamGateMode?: StreamGateSettingMode; affinityIgnoreClientSessionId?: boolean; + edgeExecutionEnabled?: boolean; replayEnabled?: boolean | null; replayCacheTtlMinutes?: number; cacheEffectivenessEnabled?: boolean | null; enableMemoryAdmission?: boolean; + agentCatalogNotes?: string | null; enableCodexSessionIdCompletion?: boolean; enableClaudeMetadataUserIdInjection?: boolean; enableResponseFixer?: boolean; @@ -184,6 +189,9 @@ export async function saveSystemSettings(formData: { stickySlaMs: validated.stickySlaMs, racingTotalTimeoutMs: validated.racingTotalTimeoutMs, stickyTimeoutCooldownMs: validated.stickyTimeoutCooldownMs, + upstreamQuotaSchedulingEnabled: validated.upstreamQuotaSchedulingEnabled, + upstreamQuotaThresholdPercent: validated.upstreamQuotaThresholdPercent, + upstreamQuotaProbeIntervalMinutes: validated.upstreamQuotaProbeIntervalMinutes, timezone: validated.timezone, enableAutoCleanup: validated.enableAutoCleanup, cleanupRetentionDays: validated.cleanupRetentionDays, @@ -207,10 +215,12 @@ export async function saveSystemSettings(formData: { fakeStreamingWhitelist: validated.fakeStreamingWhitelist, streamGateMode: validated.streamGateMode, affinityIgnoreClientSessionId: validated.affinityIgnoreClientSessionId, + edgeExecutionEnabled: validated.edgeExecutionEnabled, replayEnabled: validated.replayEnabled, replayCacheTtlMinutes: validated.replayCacheTtlMinutes, cacheEffectivenessEnabled: validated.cacheEffectivenessEnabled, enableMemoryAdmission: validated.enableMemoryAdmission, + agentCatalogNotes: validated.agentCatalogNotes, enableCodexSessionIdCompletion: validated.enableCodexSessionIdCompletion, enableClaudeMetadataUserIdInjection: validated.enableClaudeMetadataUserIdInjection, enableResponseFixer: validated.enableResponseFixer, diff --git a/src/actions/webhook-targets.ts b/src/actions/webhook-targets.ts index 643dff9679..80969d8122 100644 --- a/src/actions/webhook-targets.ts +++ b/src/actions/webhook-targets.ts @@ -7,7 +7,10 @@ import { logger } from "@/lib/logger"; import { isValidProxyUrl } from "@/lib/proxy-agent"; import { resolveSystemTimezone } from "@/lib/utils/timezone"; import { WebhookNotifier } from "@/lib/webhook"; -import { buildTestMessage } from "@/lib/webhook/templates/test-messages"; +import { + buildClientProblemTestData, + buildTestMessage, +} from "@/lib/webhook/templates/test-messages"; import { getNotificationSettings, updateNotificationSettings } from "@/repository/notifications"; import { createWebhookTarget, @@ -80,6 +83,7 @@ const NotificationTypeSchema = z.enum([ "daily_leaderboard", "cost_alert", "cache_hit_rate_alert", + "client_problem", ]); export type NotificationType = z.infer; @@ -248,6 +252,8 @@ function toJobType(type: NotificationType): NotificationJobType { return "cost-alert"; case "cache_hit_rate_alert": return "cache-hit-rate-alert"; + case "client_problem": + return "client-problem"; } } @@ -329,6 +335,8 @@ function buildTestData(type: NotificationType): unknown { }, generatedAt: new Date().toISOString(), }; + case "client_problem": + return buildClientProblemTestData(); } } @@ -438,12 +446,14 @@ export async function testWebhookTargetAction( const validatedType = NotificationTypeSchema.parse(notificationType); const timezone = await resolveSystemTimezone(); const testMessage = buildTestMessage(toJobType(validatedType), timezone); + const settings = await getNotificationSettings(); const notifier = new WebhookNotifier(target); const result = await notifier.send(testMessage, { notificationType: validatedType, data: buildTestData(validatedType), timezone, + titlePrefix: settings.titlePrefix, }); const latencyMs = Date.now() - start; diff --git a/src/app/[locale]/dashboard/_components/dashboard-header.tsx b/src/app/[locale]/dashboard/_components/dashboard-header.tsx index 637859206a..3ddd3c8d99 100644 --- a/src/app/[locale]/dashboard/_components/dashboard-header.tsx +++ b/src/app/[locale]/dashboard/_components/dashboard-header.tsx @@ -19,6 +19,7 @@ export async function DashboardHeader({ session, locale }: DashboardHeaderProps) const isAdmin = session?.user.role === "admin"; const canUseDashboard = !!session && (isAdmin || session.key.canLoginWebUi); const documentationItem = { href: "/usage-doc", label: t("documentation") }; + const modelsItem = { href: "/models", label: t("models") }; const NAV_ITEMS: (DashboardNavItem & { adminOnly?: boolean })[] = [ { href: "/dashboard", label: t("dashboard") }, @@ -31,12 +32,13 @@ export async function DashboardHeader({ session, locale }: DashboardHeaderProps) : [{ href: "/dashboard/my-quota", label: t("myQuota") }]), { href: "/dashboard/users", label: t("userManagement") }, documentationItem, + modelsItem, { href: "/settings", label: t("systemSettings"), adminOnly: true }, ]; const items = session && !canUseDashboard - ? [documentationItem] + ? [documentationItem, modelsItem] : NAV_ITEMS.filter((item) => !item.adminOnly || isAdmin); return ( 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 aaa0c8244e..9e42bc0a72 100644 --- a/src/app/[locale]/dashboard/_components/user/forms/user-form.tsx +++ b/src/app/[locale]/dashboard/_components/user/forms/user-form.tsx @@ -413,6 +413,7 @@ export function UserForm({ user, onSuccess, currentUser }: UserFormProps) { "codex-cli-core": tUserEdit("subClients.codex-cli-core"), desktop: tUserEdit("subClients.desktop"), exec: tUserEdit("subClients.exec"), + "python-sdk": tUserEdit("subClients.python-sdk"), }, nSelected: tUserEdit("nSelected", { count: "{count}" }), }} diff --git a/src/app/[locale]/dashboard/_components/user/hooks/use-user-translations.ts b/src/app/[locale]/dashboard/_components/user/hooks/use-user-translations.ts index 99538dd5a7..d8d7c3eda8 100644 --- a/src/app/[locale]/dashboard/_components/user/hooks/use-user-translations.ts +++ b/src/app/[locale]/dashboard/_components/user/hooks/use-user-translations.ts @@ -273,6 +273,7 @@ export function useUserTranslations( "codex-cli-core": t("userEditSection.subClients.codex-cli-core"), desktop: t("userEditSection.subClients.desktop"), exec: t("userEditSection.subClients.exec"), + "python-sdk": t("userEditSection.subClients.python-sdk"), }, nSelected: t("userEditSection.nSelected", { count: "{count}" }), limitRules: { diff --git a/src/app/[locale]/dashboard/logs/_components/error-details-dialog/components/SummaryTab.tsx b/src/app/[locale]/dashboard/logs/_components/error-details-dialog/components/SummaryTab.tsx index 61d381c877..b13d3e57ae 100644 --- a/src/app/[locale]/dashboard/logs/_components/error-details-dialog/components/SummaryTab.tsx +++ b/src/app/[locale]/dashboard/logs/_components/error-details-dialog/components/SummaryTab.tsx @@ -20,6 +20,7 @@ import { useTranslations } from "next-intl"; import { useState } from "react"; import { IpDetailsDialog } from "@/app/[locale]/dashboard/_components/ip-details-dialog"; import { IpDisplayTrigger } from "@/app/[locale]/dashboard/_components/ip-display-trigger"; +import { AnthropicEffortBadge } from "@/components/customs/anthropic-effort-badge"; import { ThinkingEffortBadge } from "@/components/customs/thinking-effort-badge"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; @@ -158,6 +159,8 @@ export function SummaryTab({ overridden: t(`${effortMessageKey}.overridden`), } : null; + const EffortBadge = + thinkingEffortInfo?.source === "anthropic" ? AnthropicEffortBadge : ThinkingEffortBadge; const isFake200PostStreamFailure = typeof errorMessage === "string" && errorMessage.startsWith("FAKE_200_"); const fake200Code = @@ -385,7 +388,7 @@ export function SummaryTab({ - @@ -402,7 +405,7 @@ export function SummaryTab({ {effortDisplay.requestedEffort && (
{children}
+ + ), + th: ({ children }) => {children}, + td: ({ children }) => {children}, + hr: () =>
, +}; + +interface AgentNotesProps { + markdown: string | null; + title: string; +} + +/** Admin-authored notes, rendered from Markdown with HTML sanitized. */ +export function AgentNotes({ markdown, title }: AgentNotesProps) { + if (!markdown) return null; + return ( + + + {title} + + + + {markdown} + + + + ); +} diff --git a/src/app/[locale]/models/_components/model-catalog-view.tsx b/src/app/[locale]/models/_components/model-catalog-view.tsx new file mode 100644 index 0000000000..bd5ed7e1bd --- /dev/null +++ b/src/app/[locale]/models/_components/model-catalog-view.tsx @@ -0,0 +1,322 @@ +"use client"; + +import { Check, Copy, Search } from "lucide-react"; +import { useFormatter, useTranslations } from "next-intl"; +import { useMemo, useState } from "react"; +import { ModelVendorIcon } from "@/components/customs/model-vendor-icon"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { Input } from "@/components/ui/input"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import type { CatalogModel, ModelCatalog } from "@/lib/model-catalog/types"; +import { copyToClipboard } from "@/lib/utils/clipboard"; + +const CATALOG_PATH = "/v1/models/catalog"; + +type CapabilityKey = keyof NonNullable; +const CAPABILITY_KEYS: CapabilityKey[] = [ + "vision", + "functionCalling", + "reasoning", + "pdfInput", + "promptCaching", +]; + +function CopyLine({ value, label }: { value: string; label: string }) { + const [copied, setCopied] = useState(false); + return ( +
+ + {value} + + +
+ ); +} + +interface ModelCatalogViewProps { + catalog: ModelCatalog; + origin: string | null; +} + +export function ModelCatalogView({ catalog, origin }: ModelCatalogViewProps) { + const t = useTranslations("modelCatalog"); + const format = useFormatter(); + const [query, setQuery] = useState(""); + + const modelsById = useMemo( + () => new Map(catalog.models.map((model) => [model.id, model])), + [catalog.models] + ); + + const filteredModels = useMemo(() => { + const needle = query.trim().toLowerCase(); + if (!needle) return catalog.models; + return catalog.models.filter((model) => + [model.id, model.displayName, model.vendorName ?? "", ...model.protocols].some((value) => + value.toLowerCase().includes(needle) + ) + ); + }, [catalog.models, query]); + + const visibleIds = useMemo( + () => new Set(filteredModels.map((model) => model.id)), + [filteredModels] + ); + + const formatTokens = (value: number | null) => + value === null ? "-" : format.number(value, { notation: "compact" }); + const formatPrice = (value: number | null | undefined) => + value === null || value === undefined + ? "-" + : format.number(value, { style: "currency", currency: "USD", maximumFractionDigits: 4 }); + + const base = origin ?? ""; + + return ( +
+ + + {t("machineReadable.title")} + {t("machineReadable.description")} + + + + +

+ {t("generatedAt", { + time: format.dateTime(new Date(catalog.generatedAt), { + dateStyle: "medium", + timeStyle: "medium", + }), + })} +

+
+
+ + +
+ + + {t("tabs.byModel")} ({catalog.models.length}) + + {t("tabs.byProtocol")} + +
+ + setQuery(event.target.value)} + placeholder={t("searchPlaceholder")} + aria-label={t("searchPlaceholder")} + className="h-9 pl-8 text-sm" + /> +
+
+ + +

{t("priceUnit")}

+ {filteredModels.length === 0 ? ( +

+ {t("empty")} +

+ ) : ( +
+ + + + {t("columns.model")} + {t("columns.protocols")} + {t("columns.contextWindow")} + {t("columns.maxOutput")} + {t("columns.inputPrice")} + {t("columns.outputPrice")} + {t("columns.cacheRead")} + {t("columns.cacheWrite")} + {t("columns.capabilities")} + {t("columns.modalities")} + {t("columns.knowledgeCutoff")} + + + + {filteredModels.map((model) => ( + + +
+ +
+
+ {model.id} + {model.deprecated && ( + + {t("status.deprecated")} + + )} +
+
+ {[ + model.displayName !== model.id ? model.displayName : null, + model.vendorName, + ] + .filter(Boolean) + .join(" / ")} +
+
+
+
+ +
+ {model.protocols.map((protocol) => ( + + {protocol} + + ))} +
+
+ + {formatTokens(model.contextWindow)} + + + {formatTokens(model.maxOutputTokens)} + + + {formatPrice(model.pricing?.input)} + + + {formatPrice(model.pricing?.output)} + + + {formatPrice(model.pricing?.cacheRead)} + + + {formatPrice(model.pricing?.cacheWrite)} + + + {model.capabilities ? ( +
+ {CAPABILITY_KEYS.filter((key) => model.capabilities?.[key]).map( + (key) => ( + + {t(`capabilities.${key}`)} + + ) + )} +
+ ) : ( + {t("noPriceData")} + )} +
+ + {model.modalities ? ( +
+
+ + {t("modalities.input")}:{" "} + + {model.modalities.input.join(", ") || "-"} +
+
+ + {t("modalities.output")}:{" "} + + {model.modalities.output.join(", ") || "-"} +
+
+ ) : ( + "-" + )} +
+ {model.knowledgeCutoff ?? "-"} +
+ ))} +
+
+
+ )} +
+ + +
+ {catalog.protocols.map((protocol) => { + const visible = protocol.models.filter((id) => visibleIds.has(id)); + return ( + + +
+ {protocol.label} + + {t("protocolModelsCount", { count: protocol.models.length })} + +
+ + {protocol.id} + {protocol.endpointPath} + +
+ + {visible.length === 0 ? ( +

{t("protocolEmpty")}

+ ) : ( +
    + {visible.map((id) => { + const model = modelsById.get(id); + return ( +
  • + + + {id} + +
  • + ); + })} +
+ )} +
+
+ ); + })} +
+
+
+
+ ); +} diff --git a/src/app/[locale]/models/layout.tsx b/src/app/[locale]/models/layout.tsx new file mode 100644 index 0000000000..a8f102e6f2 --- /dev/null +++ b/src/app/[locale]/models/layout.tsx @@ -0,0 +1,44 @@ +import type { Metadata } from "next"; +import { getTranslations } from "next-intl/server"; +import type { ReactNode } from "react"; +import { redirect } from "@/i18n/routing"; +import { getSession } from "@/lib/auth"; +import { DashboardHeader } from "../dashboard/_components/dashboard-header"; + +type ModelsParams = { locale: string }; + +export async function generateMetadata({ + params, +}: { + params: Promise; +}): Promise { + const { locale } = await params; + const t = await getTranslations({ locale, namespace: "modelCatalog" }); + return { title: t("pageTitle"), description: t("pageDescription") }; +} + +/** + * Model catalog layout. Any signed-in session works (admin, web UI key or read-only key); + * the catalog is always scoped to the session's own user and key. + */ +export default async function ModelsLayout({ + children, + params, +}: { + children: ReactNode; + params: Promise; +}) { + const { locale } = await params; + const session = await getSession({ allowReadOnlyAccess: true }); + + if (!session) { + return redirect({ href: "/login?from=/models", locale }); + } + + return ( +
+ +
{children}
+
+ ); +} diff --git a/src/app/[locale]/models/loading.tsx b/src/app/[locale]/models/loading.tsx new file mode 100644 index 0000000000..9ef56d6d08 --- /dev/null +++ b/src/app/[locale]/models/loading.tsx @@ -0,0 +1,18 @@ +import { TableSkeleton } from "@/components/loading/page-skeletons"; +import { Skeleton } from "@/components/ui/skeleton"; + +export default function ModelsLoading() { + return ( +
+
+ + +
+ + +
+ +
+
+ ); +} diff --git a/src/app/[locale]/models/page.tsx b/src/app/[locale]/models/page.tsx new file mode 100644 index 0000000000..faea83b6a3 --- /dev/null +++ b/src/app/[locale]/models/page.tsx @@ -0,0 +1,42 @@ +import { headers } from "next/headers"; +import { getTranslations } from "next-intl/server"; +import { redirect } from "@/i18n/routing"; +import { getSession } from "@/lib/auth"; +import { buildModelCatalog } from "@/lib/model-catalog"; +import { AgentNotes } from "./_components/agent-notes"; +import { ModelCatalogView } from "./_components/model-catalog-view"; + +export const dynamic = "force-dynamic"; + +async function resolveOrigin(): Promise { + const h = await headers(); + const host = h.get("x-forwarded-host")?.split(",")[0]?.trim() || h.get("host"); + if (!host) return null; + const proto = h.get("x-forwarded-proto")?.split(",")[0]?.trim(); + return `${proto === "http" || proto === "https" ? proto : "https"}://${host}`; +} + +export default async function ModelsPage({ params }: { params: Promise<{ locale: string }> }) { + const { locale } = await params; + const session = await getSession({ allowReadOnlyAccess: true }); + if (!session) { + return redirect({ href: "/login?from=/models", locale }); + } + + const [catalog, origin, t] = await Promise.all([ + buildModelCatalog({ user: session.user, key: session.key }), + resolveOrigin(), + getTranslations({ locale, namespace: "modelCatalog" }), + ]); + + return ( +
+
+

{t("pageTitle")}

+

{t("pageDescription")}

+
+ + +
+ ); +} diff --git a/src/app/[locale]/my-usage/_components/my-usage-header.tsx b/src/app/[locale]/my-usage/_components/my-usage-header.tsx index a41dfc0bea..11544f72df 100644 --- a/src/app/[locale]/my-usage/_components/my-usage-header.tsx +++ b/src/app/[locale]/my-usage/_components/my-usage-header.tsx @@ -1,6 +1,6 @@ "use client"; -import { BookOpen, LogOut } from "lucide-react"; +import { BookOpen, Boxes, LogOut } from "lucide-react"; import { useTranslations } from "next-intl"; import { Button } from "@/components/ui/button"; import { Link, useRouter } from "@/i18n/routing"; @@ -52,6 +52,12 @@ export function MyUsageHeader({ onLogout, keyName, userName }: MyUsageHeaderProp {t("documentation")} +