Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
124 commits
Select commit Hold shift + click to select a range
db57b16
feat(proxy): make model name matching case-insensitive across whiteli…
kexichan Jul 21, 2026
e9ad468
feat(proxy): filter model list endpoints by user allowedModels
kexichan Jul 21, 2026
2970ed8
feat: Langfuse trace name=model; add admin key validation endpoint
kexichan Jul 24, 2026
161babc
test(proxy): cover provider client restrictions
kexichan Jul 24, 2026
914c404
feat(langfuse): reconstruct final streamed responses
kexichan Jul 30, 2026
e3acd3d
fix(langfuse): send complete streamed outputs
kexichan Jul 30, 2026
7a8ea45
fix(langfuse): harden stream finalization terminal validation
kexichan Jul 30, 2026
113246e
feat(langfuse): prefix trace name with username
kexichan Jul 30, 2026
4ea02c3
feat(proxy): add conditional reasoning effort rule engine
kexichan Jul 31, 2026
5ba55db
feat(providers): persist conditional effort override rules
kexichan Jul 31, 2026
a0db282
feat(repository): wire conditional effort rule persistence
kexichan Jul 31, 2026
d332d83
test(repository): cover conditional effort rule round-trips
kexichan Jul 31, 2026
a221752
test(providers): verify isolated effort-rule migration
kexichan Jul 31, 2026
6c0a4ec
fix(test): bind migration JSONB fixtures with postgres JSON parameters
kexichan Jul 31, 2026
34368ef
feat(codex): evaluate conditional reasoning effort overrides
kexichan Jul 31, 2026
a82788a
feat(anthropic): evaluate conditional reasoning effort overrides
kexichan Jul 31, 2026
150caac
test(anthropic): preserve effort audit consumers
kexichan Jul 31, 2026
0ac0529
test(dashboard): cover Anthropic effort audit display
kexichan Jul 31, 2026
cdbc2d9
feat(api): validate provider effort override rules
kexichan Jul 31, 2026
d27b485
feat(proxy): wire conditional effort rules into forwarding
kexichan Jul 31, 2026
256ef6d
feat(settings): configure conditional reasoning effort rules
kexichan Jul 31, 2026
2254030
feat(settings): configure conditional reasoning effort rules
kexichan Jul 31, 2026
9db9c00
test(settings): raise reasoning effort rule editor coverage above 80 …
kexichan Jul 31, 2026
c935888
test(actions): repair provider batch mocks
kexichan Jul 31, 2026
9f8e4c7
test: repair pre-existing locale switcher and abort drain tests
kexichan Jul 31, 2026
379b207
chore: untrack .omo orchestration state files
kexichan Jul 31, 2026
01113b7
docs: add API key validation workflow
kexichan Aug 3, 2026
188f7c8
Merge tag 'v0.9.1' into sync/upstream-v0.9.1
kexichan Aug 3, 2026
c67fc8b
test(providers): cover batch override validation lookup
kexichan Aug 3, 2026
00fa2db
fix: restore Anthropic effort audit badge and update hedge mocks for …
kexichan Aug 3, 2026
1bc0154
Merge tag 'v0.9.2'
kexichan Aug 5, 2026
ae2d3ae
Merge tag 'v0.9.3'
kexichan Aug 13, 2026
24d56cb
feat(langfuse): document v5 environment and release env vars
kexichan Aug 16, 2026
b8effe4
fix(langfuse): pass environment and release into LangfuseSpanProcessor
kexichan Aug 16, 2026
cd7d43b
fix(langfuse): create observations inside propagateAttributes
kexichan Aug 16, 2026
16ef446
fix(langfuse): name traces as user:shortModel
kexichan Aug 16, 2026
2df8998
test(langfuse): cover user:model names and prefix stripping
kexichan Aug 16, 2026
f367d86
fix(langfuse): pass current model as undefined to display name builder
kexichan Aug 16, 2026
6b86daf
feat(session): expose original client request headers
kexichan Aug 17, 2026
3b2ef63
feat(langfuse): record mashed client headers in client_metadata
kexichan Aug 17, 2026
9a76479
feat(proxy): parse and display OpenAI chat reasoning effort (#1438)
cnzgray Aug 20, 2026
5ce6844
fix(proxy): bound detached stream memory without disabling replay (#1…
AptS-1547 Aug 21, 2026
5e61fcd
fix(responses-ws): encode multiline events as valid SSE (#1435)
ROOOO Aug 21, 2026
67d3e9d
fix(proxy): decode ArrayBuffer bodies for Responses WebSocket (#1432)
ROOOO Aug 21, 2026
9e30a5b
fix(proxy): harden detached stream cleanup (#1440)
AptS-1547 Aug 21, 2026
cca4f55
feat(proxy): disable memory-heavy features under high-concurrency mod…
ding113 Aug 21, 2026
d95699a
chore: sync VERSION file with release v0.9.4 [skip ci]
github-actions[bot] Aug 22, 2026
bb2fae1
feat(metrics): add Prometheus scrape endpoint
kexichan Aug 29, 2026
5f00b38
feat(metrics): emit traffic metrics on proxy completion
kexichan Aug 29, 2026
eedced1
test(metrics): cover catalog, scrape auth, emit, and gauges
kexichan Aug 29, 2026
a9148ff
Merge tag 'v0.9.5'
kexichan Sep 6, 2026
17c8202
fix(proxy): use undici fetch with ProxyAgent on Node 24
kexichan Sep 8, 2026
1fe51a3
feat(providers): header templates, OpenCode Go adapter, response mode…
kexichan Sep 9, 2026
c6cc156
feat(db): add client_problem notification settings and title_prefix
kexichan Sep 9, 2026
b63cc3e
feat(notifications): client-problem alerts with instance title prefix…
kexichan Sep 9, 2026
4543ae0
test(notifications): cover client-problem alerts, title prefix, and P…
kexichan Sep 9, 2026
9bb1ea1
fix(notifications): match OpenAI cyber risk keywords more completely
kexichan Sep 10, 2026
6539877
test(notifications): cover cyber risk keyword classification
kexichan Sep 10, 2026
62a8cd4
docs: note client-problem cyber keyword matching
kexichan Sep 10, 2026
4baab01
feat(notifications): skip already-sent client-problem alert content
kexichan Sep 10, 2026
fe0f98b
docs: document traced standalone systemd deploy size
kexichan Sep 10, 2026
468b024
chore(test): format leftover unit test fixtures
kexichan Sep 10, 2026
9e94f19
perf(proxy): cache settings, provider snapshot, endpoints and prices …
kexichan Sep 14, 2026
203306c
perf(rate-limit): batch quota lease refreshes and single-flight cost …
kexichan Sep 14, 2026
470068f
perf(db): gate ledger projection to finalized rows, batch and retire …
kexichan Sep 14, 2026
0cbec7e
perf(dashboard): share cache-aside logic and cache big-screen/proxy-s…
kexichan Sep 14, 2026
c7db6f7
Merge upstream/dev for memory-aware stream-gate admission
kexichan Sep 15, 2026
3d52a80
feat(langfuse): send full response bodies and compress retained payloads
kexichan Sep 16, 2026
ae611e6
feat(proxy): map unprefixed OpenAI/Anthropic paths onto /v1
kexichan Sep 16, 2026
e233c99
fix(memory): clamp governor limit by real headroom instead of re-disc…
kexichan Sep 19, 2026
e2c9bd5
feat(memory): add tagged lease ledger to governor snapshot
kexichan Sep 19, 2026
c1ba33d
fix(memory): bound request memory lifetime against stuck background o…
kexichan Sep 19, 2026
e00ca0e
fix(proxy): back stream-gate leases with the request lifetime
kexichan Sep 19, 2026
71a7825
feat(proxy): record local capacity 429s in usage logs
kexichan Sep 19, 2026
0c45cfa
docs(memory): document headroom clamp, bounded lifetime and diagnostics
kexichan Sep 19, 2026
94e2ac5
feat(config): add ClickHouse request log sync env schema
kexichan Sep 19, 2026
b5bd5d4
feat(clickhouse): add HTTP client, table DDL and row mapper
kexichan Sep 19, 2026
9ec3f2b
feat(clickhouse): add PG tail sync worker
kexichan Sep 19, 2026
4f07f29
feat(clickhouse): start and stop the sync worker with the app
kexichan Sep 19, 2026
1cabbe6
feat(log-cleanup): fence deletes on ClickHouse sync progress
kexichan Sep 19, 2026
22f67f0
docs(clickhouse): document request log sync
kexichan Sep 19, 2026
2d120a6
fix(memory): do not crash on abort of a locked response stream
kexichan Sep 22, 2026
447f3f2
fix(health): wait for ioredis ready before ping
kexichan Sep 22, 2026
5ef4594
feat(providers): add provider_prefix column and normalization
kexichan Sep 22, 2026
6898117
feat(proxy): route by provider prefix and strip it upstream
kexichan Sep 22, 2026
b9e2874
feat(models): list prefixed model IDs for prefixed providers
kexichan Sep 22, 2026
b138175
feat(dispatch-simulator): preview provider prefix matching and stripping
kexichan Sep 22, 2026
6c1e7a5
feat(api): expose providerPrefix in the v1 providers API
kexichan Sep 22, 2026
0a45d9c
feat(settings): provider prefix field in the provider form
kexichan Sep 22, 2026
e974253
feat(proxy): add shared listen-prefix parser and path stripper
kexichan Sep 23, 2026
d74f6c5
feat(config): validate PROXY_LISTEN_PREFIX at startup
kexichan Sep 23, 2026
0769c88
feat(proxy): rewrite prefixed paths in the Next middleware
kexichan Sep 23, 2026
b727961
feat(server): strip listen prefix in the custom server and WS upgrade
kexichan Sep 23, 2026
7c0bdc4
test(proxy): cover listen prefix composed with the unprefixed aliases
kexichan Sep 23, 2026
d7d3507
docs: document PROXY_LISTEN_PREFIX
kexichan Sep 23, 2026
7075d1f
feat(proxy): add OUTBOUND_PROXY_URL global egress proxy
kexichan Sep 25, 2026
91f8ab8
fix(providers): allow max in conditional Codex effort rules
kexichan Sep 26, 2026
e14a1db
refactor(providers): use toggle for Codex effort rules
kexichan Sep 26, 2026
96bc73f
feat(db): add ClickHouse sync marker column, index and floor table
kexichan Sep 26, 2026
5707d72
fix(clickhouse): replace id cursor with per-row sync marker
kexichan Sep 26, 2026
36d4229
test(clickhouse): cover sync marker and cleanup fence on PostgreSQL
kexichan Sep 26, 2026
dfda6bf
docs(clickhouse): document per-row sync marker, upgrade and gap checks
kexichan Sep 26, 2026
18ae01d
feat(providers): schedule by upstream Coding Plan quota
kexichan Sep 27, 2026
139dc3c
refactor(proxy): extract serial retry decision and add edge digest hints
kexichan Sep 27, 2026
9fcf12d
feat(settings): add edge executor kill switch
kexichan Sep 27, 2026
657f9d8
feat(edge): add control plane API for remote edge executors
kexichan Sep 27, 2026
4ec495a
feat(edge): add Go edge executor
kexichan Sep 27, 2026
2866e28
feat(edge): execute OpenAI Responses/Chat and legacy hedge at the edge
kexichan Sep 27, 2026
b8b4d8a
Merge tag 'v0.9.7'
kexichan Oct 2, 2026
7691187
feat(providers): add endpoint ownership scope calculation
kexichan Oct 3, 2026
db36fc0
docs(clickhouse): document preallocated ID cursor data loss
kexichan Oct 3, 2026
8189921
feat(client-restrictions): detect codex_python_sdk under codex-cli pr…
kexichan Oct 5, 2026
0c13215
feat(ui): add Python SDK sub-client label for codex-cli restrictions
kexichan Oct 5, 2026
9889e7a
refactor(models): extract API key auth and group models by provider type
kexichan Oct 7, 2026
53d8944
feat(price-sync): persist model modalities from the cloud price table
kexichan Oct 7, 2026
53da3bb
feat(settings): add admin-authored agent catalog notes
kexichan Oct 7, 2026
e4a3631
feat(model-catalog): add GET /v1/models/catalog for agents
kexichan Oct 7, 2026
a15f4d5
feat(ui): add model catalog page
kexichan Oct 7, 2026
85e5215
feat(model-price): match price rows case-insensitively
kexichan Oct 7, 2026
7bfda75
feat(db): add service_tier_override_rules column with legacy backfill
kexichan Oct 9, 2026
e626992
refactor(override-rules): extract shared conditional rule matcher
kexichan Oct 9, 2026
d104c0c
feat(proxy): apply conditional service tier rules to Codex requests
kexichan Oct 9, 2026
96e4266
feat(api): expose service_tier_override_rules across actions and v1 API
kexichan Oct 9, 2026
a31ca61
feat(ui): edit conditional service tier rules in the provider form
kexichan Oct 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
3 changes: 3 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
.git
.gitignore

# Go edge executor (built by edge/Dockerfile)
edge

# Node modules and build artifacts
node_modules
.next
Expand Down
81 changes: 81 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 # 建立连接超时(秒)
Expand All @@ -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
Expand Down Expand Up @@ -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 秒)
Expand Down Expand Up @@ -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 <token> 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 状态时,定期探测供应商以实现更快恢复
Expand Down Expand Up @@ -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 运行)
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,8 @@ tmp/
.omx/
.opencode/
bunx-*/

# Go edge executor build output
edge/bin/
edge/coverage.out
.research/
16 changes: 14 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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` 自动注入


---

Expand Down
86 changes: 85 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand All @@ -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/<name>-<hash>` entries are **symlinks** into `../../node_modules/<pkg>` (`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).
6 changes: 5 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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-%,%,$@)
Loading
Loading