Commit Graph

3263 Commits

Author SHA1 Message Date
Wesley Liddick
dc09b367dc Merge pull request #2143 from alfadb/fix/openai-apikey-cc-default-routing
修复:APIKey 账户上游不支持 OpenAI Responses API 时的 Chat Completions 路由回退
2026-05-03 22:58:26 +08:00
shaw
0b84d12dbb fix: correct affiliate audit record sources 2026-05-03 22:12:57 +08:00
Wesley Liddick
76e2503d5e Merge pull request #2169 from lyen1688/feat/admin-affiliate-records
feat: 新增管理后台邀请返利记录页面
2026-05-03 21:10:33 +08:00
lyen1688
3ab40269b4 完善返利转入余额历史显示 2026-05-03 20:33:14 +08:00
lyen1688
650ddb2e39 fix: make affiliate record users clickable 2026-05-03 20:33:14 +08:00
lyen1688
0a914e034c fix: include matured affiliate quota in admin overview 2026-05-03 20:33:13 +08:00
lyen1688
6a41cf6a51 feat: add admin affiliate record pages 2026-05-03 20:33:13 +08:00
shaw
47fb38bca1 fix: record zero OpenAI usage logs 2026-05-03 17:43:56 +08:00
shaw
72d5ee4cd1 fix: drain OpenAI compat streams for usage 2026-05-03 17:11:27 +08:00
shaw
b2bdba78dd stabilize image request handling 2026-05-03 14:56:09 +08:00
alfadb
3930bebaf9 Merge branch 'fix/raw-cc-upstream-endpoint-log' into fix/openai-apikey-cc-default-routing 2026-05-02 10:53:11 +08:00
alfadb
e736de1ed9 fix(handler): log correct upstream endpoint for raw CC path
DeriveUpstreamEndpoint hard-codes /v1/responses for PlatformOpenAI,
but APIKey accounts probed to not support Responses API are forwarded
directly to /v1/chat/completions via forwardAsRawChatCompletions.

Add resolveRawCCUpstreamEndpoint which returns /v1/chat/completions
when the account's extra.openai_responses_supported is explicitly false.
2026-05-02 10:31:57 +08:00
alfadb
57099a6af6 fix(openai-gateway): extract reasoning_effort in raw Chat Completions path
The forwardAsRawChatCompletions path (used when APIKey accounts target
upstreams that don't support Responses API, e.g. DeepSeek) was missing
reasoning_effort and service_tier extraction, causing all reasoning
effort values to be silently dropped.

Extract both from the raw Chat Completions body before forwarding, and
propagate them through streamRawChatCompletions / bufferRawChatCompletions
to the OpenAIForwardResult.
2026-05-02 10:22:16 +08:00
alfadb
adf01ac880 fix(openai-gateway): address PR review — probe URL /v1 prefix, Create trigger, tests
Fix four issues flagged by copilot-pull-request-reviewer on PR #2143:

1. Probe URL missing /v1 prefix (openai_apikey_responses_probe.go)
   Replaced bare TrimSuffix + "/responses" with buildOpenAIResponsesURL(),
   which handles bare domain → /v1/responses correctly. Affected:
   - ProbeOpenAIAPIKeyResponsesSupport (probe URL)
   - TestAccount endpoint (apiURL for APIKey accounts)

2. Create endpoint not triggering probe (account_handler.go)
   Capture created account from idempotent closure and call
   scheduleOpenAIResponsesProbe after success, same pattern as
   BatchCreate and Update.

3. Tests (openai_gateway_chat_completions_raw_test.go)
   Added TestBuildOpenAIChatCompletionsURL (7 cases covering
   bare domain, /v1 suffix, trailing slash, third-party domains,
   whitespace) and TestBuildOpenAIResponsesURL_ProbeURL (6 cases
   locking the probe URL construction for bare-domain inputs).

All unit tests pass; go build ./cmd/server/ clean.
2026-04-30 21:46:46 +08:00
alfadb-bot
4d145300c3 fixup! fix(openai-gateway): route APIKey accounts to /v1/chat/completions when upstream lacks Responses API
Address self-review findings:

R7: Use a narrow per-trust-domain header allowlist for CC raw forwarding.
The previously reused openaiAllowedHeaders contains Codex client-only headers
(originator/session_id/x-codex-turn-state/x-codex-turn-metadata/conversation_id)
that must not leak to third-party OpenAI-compatible upstreams (DeepSeek/Kimi/
GLM/Qwen). Strict upstreams may 400 with 'unknown parameter'; lenient ones
silently pollute their request statistics. New openaiCCRawAllowedHeaders only
allows generic HTTP headers (accept-language, user-agent); content-type/
authorization/accept are set explicitly by callers.

R4: Drop the dead includeUsage parameter from streamRawChatCompletions.
The CC pass-through path doesn't need to inspect the client's stream_options
flag — the upstream handles it and we only extract usage when it appears in
chunks. Killing the unused parameter removes a misleading 'parameter read
but discarded' code smell.

Sediment refs:
- pensieve/short-term/maxims/dont-reuse-shared-headers-whitelist-across-different-upstream-trust-domains
- pensieve/short-term/knowledge/openai-gateway-shared-state-quirks
- pensieve/short-term/pipelines/run-when-self-reviewing-forwarder-implementation
2026-04-30 20:16:44 +08:00
alfadb-bot
4e4cc80971 fix(openai-gateway): route APIKey accounts to /v1/chat/completions when upstream lacks Responses API
OpenAI APIKey accounts with base_url pointing to third-party OpenAI-compatible
upstreams (DeepSeek, Kimi, GLM, Qwen, etc.) were failing because the gateway
unconditionally converted Chat Completions requests to Responses format and
forwarded to {base_url}/v1/responses, which only exists on OpenAI's official
endpoint.

Detection-based routing:
- Probe upstream capability on account create/update via a minimal POST to
  /v1/responses; HTTP 404/405 means 'unsupported', any other response means
  'supported'.
- Persist result as accounts.extra.openai_responses_supported (bool).
- ForwardAsChatCompletions branches at function entry: APIKey accounts with
  explicit support=false go through new forwardAsRawChatCompletions which
  passthrough-forwards CC body to /v1/chat/completions without protocol
  conversion.

Default behavior for accounts without the marker preserves the legacy
'always Responses' path — existing OpenAI APIKey accounts that were working
before this change continue to work without modification (the 'reality is
evidence' principle: an account that has been running implies upstream
capability).

Probe is fired async after Create / Update / BatchCreate; failures only log,
never block the admin flow. BulkUpdate omitted (low signal of base_url
changes; can be added if needed).

Implementation:
- New pkg internal/pkg/openai_compat: marker key + ShouldUseResponsesAPI
- New service file openai_apikey_responses_probe.go: probe + persist
- New service file openai_gateway_chat_completions_raw.go: CC pass-through
- Account test endpoint short-circuits with explicit message for
  probed-unsupported accounts (full CC test path is a TODO)

Zero schema changes, zero migrations, zero frontend changes, zero wire
modifications — all wired through existing AccountTestService injection.

Closes: DeepSeek-OpenAI account (id=128) production failure
2026-04-30 19:25:45 +08:00
github-actions[bot]
48912014a1 chore: sync VERSION to 0.1.121 [skip ci] 2026-04-30 06:06:12 +00:00
shaw
9d801595c9 test: 更新管理员设置契约字段 v0.1.121 2026-04-30 13:48:27 +08:00
Wesley Liddick
9c448f89a8 Merge pull request #2118 from DaydreamCoding/fix/restore-pagination-localStorage
fix: 恢复表格分页大小 localStorage 持久化
2026-04-30 13:42:18 +08:00
shaw
73b872998e feat: 添加 Anthropic 缓存 TTL 注入开关 2026-04-30 13:38:22 +08:00
shaw
094e1171ef fix(openai): infer previous response for item references 2026-04-30 12:02:08 +08:00
shaw
733627cf9d fix: improve sticky session scheduling 2026-04-30 11:38:11 +08:00
DaydreamCoding
f084d30d65 fix: 恢复表格分页大小 localStorage 持久化
- usePersistedPageSize: 恢复 localStorage 读写,以系统配置为 fallback
- useTableLoader: handlePageSizeChange 时写入 localStorage
- Pagination.vue: handlePageSizeChange 时写入 localStorage

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-30 10:35:15 +08:00
github-actions[bot]
8ad099baa6 chore: sync VERSION to 0.1.120 [skip ci] 2026-04-29 15:08:59 +00:00
shaw
8bf2a7b88a fix(scheduler): resolve SetSnapshot race conditions and remove usage throttle
Backend: Fix three race conditions in SetSnapshot that caused account
scheduling anomalies and broken sticky sessions:
- Use Lua CAS script for atomic version activation, preventing version
  rollback when concurrent goroutines write snapshots simultaneously
- Add UnlockBucket to release rebuild lock immediately after completion
  instead of waiting 30s TTL expiry
- Replace immediate DEL of old snapshots with 60s EXPIRE grace period,
  preventing readers from hitting empty ZRANGE during version switches

Frontend: Remove serial queue throttle (1-2s delay per request) from
usage loading since backend now uses passive sampling. All usage
requests execute immediately in parallel.
v0.1.120
2026-04-29 22:48:39 +08:00
shaw
40feb86ba4 fix(httputil): add decompression bomb guard and fix errcheck lint 2026-04-29 22:11:45 +08:00
Wesley Liddick
f972a2faf2 Merge pull request #1990 from haha1903/feat/zstd-request-decompression
feat(httputil): decode zstd/gzip/deflate request bodies
2026-04-29 22:08:28 +08:00
Wesley Liddick
55a7fa1e07 Merge pull request #2005 from gaoren002/pr/openai-strip-passthrough-fields
fix(openai): strip unsupported passthrough fields
2026-04-29 21:46:19 +08:00
shaw
5e54d492be fix(lint): check type assertion error in codex transform test
The errcheck linter flagged an unchecked type assertion on
item["type"].(string). Use the two-value form with require.True
to satisfy the linter and fail clearly on unexpected types.
2026-04-29 21:35:18 +08:00
Wesley Liddick
8d6d31545f Merge pull request #2068 from Ogannesson/fix/openai-drop-reasoning-items-from-input
fix(openai): drop reasoning items from /v1/responses input on OAuth path
2026-04-29 21:32:52 +08:00
Wesley Liddick
17ced6b73a Merge pull request #2027 from hansnow/codex/fix-api-key-rate-limit-reset
fix(api-key): reset rate limit usage cache
2026-04-29 21:27:52 +08:00
Wesley Liddick
7f8f3fe0dd Merge pull request #2100 from KnowSky404/fix/codex-cli-edit-resend-tool-continuation
[codex] fix WS continuation inference for explicit tool replay
2026-04-29 21:14:55 +08:00
Wesley Liddick
46f06b2498 Merge pull request #2050 from zvensmoluya/fix/openai-compact-payload-fields
fix(openai): preserve current Codex compact payload fields
2026-04-29 21:03:48 +08:00
shaw
7ce5b83215 chore: remove superpowers docs 2026-04-29 21:00:30 +08:00
Wesley Liddick
27cad10d30 Merge pull request #2030 from KnowSky404/feature/account-bulk-edit-scope-and-compact
feat: support filtered account bulk edit and align compact OpenAI bulk fields
2026-04-29 20:56:43 +08:00
Wesley Liddick
ff6fa0203d Merge pull request #2058 from ivanvolt-labs/fix-responses-function-tool-choice
fix: use Responses-compatible function tool_choice format
2026-04-29 20:43:43 +08:00
KnowSky404
f7c13af11f fix: format ingress continuation test 2026-04-29 18:02:19 +08:00
KnowSky404
28dc34b6a3 fix(openai): avoid inferred WS continuation on explicit tool replay 2026-04-29 17:38:08 +08:00
Wesley Liddick
4d676dddd1 Merge pull request #2066 from alfadb/fix/anthropic-stream-eof-failover
fix(gateway): Anthropic 流式 EOF 失败移交 + SSE error 帧标准化
2026-04-29 17:09:47 +08:00
shaw
93d91e20b9 fix(vertex): audit fixes for Vertex Service Account feature (#1977)
- Security: force token_uri to Google default, preventing SSRF via crafted service account JSON
- Dedup: extract shared getVertexServiceAccountAccessToken() to eliminate ~35 lines of duplication between ClaudeTokenProvider and GeminiTokenProvider
- Fix: apply model mapping + Vertex model ID normalization in forward_as_responses and forward_as_chat_completions paths
- Fix: exclude service_account from AI Studio endpoint selection (Vertex cannot serve generativelanguage.googleapis.com)
- Feature: add model restriction/mapping UI for service_account in EditAccountModal
- Dedup: extract VERTEX_LOCATION_OPTIONS to shared constants
- i18n: replace all hardcoded Chinese strings in Vertex UI with translation keys
2026-04-29 16:53:09 +08:00
Wesley Liddick
63ef23108c Merge pull request #1977 from sholiverlee/vertex
feat: 支持 Vertex Service Account(Anthropic / Gemini)
2026-04-29 15:48:26 +08:00
alfadb
d78478e866 fix(gateway): sanitize stream errors to avoid leaking infrastructure topology
(*net.OpError).Error() concatenates Source/Addr fields, so the previous
disconnectMsg surfaced internal source IP/port and upstream server address
to clients via SSE error frames and UpstreamFailoverError.ResponseBody
(reported by @Wei-Shaw on PR #2066).

- Add sanitizeStreamError that maps known errors (io.ErrUnexpectedEOF,
  context.Canceled, syscall.ECONNRESET/EPIPE/ETIMEDOUT/...) to fixed
  descriptions and falls back to a generic placeholder, with an explicit
  *net.OpError branch that drops Source/Addr fields entirely.
- Use sanitized message in client-facing disconnectMsg; full ev.err is
  still preserved in the existing operator log line for diagnosis.
- Tests cover net.OpError redaction, the failover ResponseBody path, and
  every known sanitized error mapping.
2026-04-29 15:44:54 +08:00
Wesley Liddick
bf43fb4e38 Merge pull request #2044 from VitalyAnkh/fix/openai-image-apikey-versioned-base-url
fix(openai): honor versioned image base URLs
2026-04-29 15:20:14 +08:00
Wesley Liddick
a16c66500f Merge pull request #2090 from touwaeriol/feat/ops-retention-zero
feat(ops): allow retention days = 0 to wipe table on each scheduled cleanup
2026-04-29 15:12:30 +08:00
erio
4b6954f9f0 feat(ops): allow retention days = 0 to wipe table on each scheduled cleanup
Background / 背景

The ops cleanup task currently rejects retention days < 1 in both validate
and normalize, so operators who want minimal-history setups (e.g. high
churn deployments that prefer near-realtime cleanup) cannot express that
intent through the UI. The only options are 1+ days, which keeps at least
24h of history regardless of cron frequency.

ops 清理任务目前在 validate 和 normalize 两处都拒绝小于 1 的保留天数,
让希望尽量不留历史的运维场景(高吞吐部署 + 想用近实时清理)无法通过 UI
表达。最低只能配 1,等于不管 cron 多频繁,至少都会保留 24 小时的历史。

Purpose / 目的

Let admins set retention days to 0, meaning "every scheduled cleanup
run wipes the corresponding table(s) entirely". Combined with a more
frequent cron (e.g. `0 * * * *`) this yields effectively rolling cleanup.

允许管理员把保留天数设为 0,语义为"每次定时清理时把对应表全部清空"。
搭配更频繁的 cron(比如每小时整点)即可获得近似滚动清理的效果。

Changes / 改动内容

Backend

- service/ops_settings.go: validate accepts [0, 365]; normalize only
  refills default 30 when value is < 0 (negative is treated as legacy
  bad data, 0 is honoured)
- service/ops_cleanup_service.go: introduce `opsCleanupPlan(now, days)`
  returning `(cutoff, truncate, ok)`. days==0 returns truncate=true and
  short-circuits to a new `truncateOpsTable` helper that uses
  `TRUNCATE TABLE` (O(1), no WAL, no VACUUM pressure). days>0 keeps
  the existing batched DELETE path unchanged. Empty tables skip
  TRUNCATE to avoid the ACCESS EXCLUSIVE lock entirely
- Extract `isMissingRelationError` helper to dedupe the "table not
  yet created" tolerance shared by both delete and truncate paths
- Add unit tests for `opsCleanupPlan` (three branches) and
  `isMissingRelationError`

后端

- service/ops_settings.go: validate 接受 [0, 365];normalize 仅在 < 0
  时回填默认 30(负数视为脏数据,0 被尊重)
- service/ops_cleanup_service.go: 抽 `opsCleanupPlan(now, days)` 返回
  `(cutoff, truncate, ok)`。days==0 → truncate=true,走新增
  `truncateOpsTable`(TRUNCATE TABLE,O(1),无 WAL、无 VACUUM 压力);
  days>0 仍走原批量 DELETE 路径,行为完全不变。空表跳过 TRUNCATE,
  避免无意义的 ACCESS EXCLUSIVE 锁
- 抽 `isMissingRelationError` helper 复用 delete / truncate 两处的
  "表不存在"宽容判断
- 补 `opsCleanupPlan` 三分支 + `isMissingRelationError` 单元测试

Frontend

- OpsSettingsDialog.vue: validation accepts [0, 365]; input min=0
- i18n (zh/en): hint mentions "0 = wipe all on every cleanup",
  validation message updated to 0-365 range

前端

- OpsSettingsDialog.vue: 校验放宽到 [0, 365],input min 改 0
- i18n(zh/en):hint 补"0 = 每次清理时清空所有",错误提示改 0-365

Trade-offs / 取舍

- TRUNCATE requires ACCESS EXCLUSIVE lock briefly, but ops tables only
  have the cleanup task as a writer, so the lock is invisible to other
  workloads
- Empty-table guard avoids the lock when there is nothing to clean
- Negative values are still treated as legacy bad data and replaced
  with default 30 to preserve compatibility
2026-04-29 15:01:02 +08:00
shaw
da4b078df2 chore: update sponsors 2026-04-29 14:41:35 +08:00
Oganneson
7452fad820 fix(openai): drop reasoning items from /v1/responses input on OAuth path
Closes #1957

The OAuth path forwards client requests to chatgpt.com/backend-api/codex/responses,
where applyCodexOAuthTransform forces store=false (chatgpt.com's codex backend
rejects store=true). Reasoning items emitted under store=false are NEVER
persisted upstream, so any rs_* reference that a client carries forward in a
subsequent input[] array triggers a guaranteed upstream 404:

    Item with id 'rs_...' not found. Items are not persisted when `store` is
    set to false. Try again with `store` set to true, or remove this item
    from your input.

sub2api wraps this as 502 "Upstream request failed" and the conversation
breaks on every multi-turn /v1/responses request that uses reasoning + tools
(reproducible with gpt-5.5; gpt-5.4 happens to dodge it because the upstream
does not emit reasoning items for that model).

Affected clients include any that follow the OpenAI Responses API spec and
replay prior assistant items verbatim — in practice this hit OpenClaw and
similar agent harnesses on every turn ≥2 with tool use.

The fix: in filterCodexInput, drop input items with type == "reasoning"
entirely. The model never reads reasoning summary text from input (only
encrypted_content can carry reasoning context across turns, and chatgpt.com
under store=false does not emit it), so this is a no-op for the model itself
and a clean removal of unreachable upstream lookups.

Scope is intentionally narrow:
  * Only OAuth account requests (account.Type == AccountTypeOAuth) reach
    applyCodexOAuthTransform / filterCodexInput.
  * API-key accounts going to api.openai.com/v1/responses are unaffected
    (store=true works there, rs_* persists, multi-turn already works).
  * Anthropic / Gemini platform groups go through different transforms and
    are unaffected.
  * /v1/chat/completions is unaffected (no reasoning items).
  * item_reference items (different type) are unaffected — only type ==
    "reasoning" is dropped.

Verification:
  * Existing tests pass: go test ./internal/service/ -run Codex|Tool|OAuth
  * New regression test asserts reasoning items are dropped under both
    preserveReferences=true and preserveReferences=false.
  * End-to-end repro on gpt-5.5 multi-turn + tools: pre-patch 502, post-patch
    200. Repro on gpt-5.4 unchanged. Three-turn deep loop on gpt-5.5 passes.
2026-04-28 20:36:50 +08:00
alfadb
4c474616b9 fix(gateway): emit Anthropic-standard SSE error events and failover body
Two follow-ups to PR #2066's failover-wrap fix:

1. Failover ResponseBody (`UpstreamFailoverError.ResponseBody`) was encoded
   as `{"error": "<msg>"}` (string field). `ExtractUpstreamErrorMessage`
   probes for `error.message`, `detail`, or top-level `message` only — so
   `handleFailoverExhausted` and downstream passthrough rules saw an empty
   message, losing the EOF root cause in ops logs. Re-encode as the
   Anthropic standard shape `{"type":"error","error":{"type":"upstream_disconnected","message":"..."}}`.
   (Addresses the inline review comment from copilot-pull-request-reviewer
   on Wei-Shaw/sub2api#2066.)

2. The streaming `event: error` SSE frame for `response_too_large`,
   `stream_read_error`, and `stream_timeout` was non-standard
   (`{"error":"<reason>"}`). Anthropic SDKs (and Claude Code) expect
   `{"type":"error","error":{"type":"...","message":"..."}}` and parse
   `error.type`/`error.message` accordingly. Refactor `sendErrorEvent` to
   take both reason and message, and emit the standard frame so client
   SDKs surface a real diagnostic message instead of a generic stream error.

This does not by itself prevent task interruption on long-stream EOF
(SSE has no resume; client-side retry remains the only complete fix), but
it gives both server-side ops logs and client-side error UIs a meaningful
upstream message so users know the next step is to retry.

Tests updated to assert the new body shape on both branches plus a new
assertion that `ExtractUpstreamErrorMessage` returns a non-empty string.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 20:24:17 +08:00
alfadb
6327573534 fix(gateway): wrap Anthropic stream EOF as failover error before client output
Anthropic streaming path (gateway_service.go) returned a plain error on
upstream SSE read failure, so the handler-level UpstreamFailoverError check
never fired and the client received a bare `stream_read_error` event,
breaking long-running tasks even when no bytes had been written yet.

The most common trigger is HTTP/2 GOAWAY from api.anthropic.com edge
backends doing graceful rotation: Go's http.Transport surfaces this as
`unexpected EOF` and never auto-retries.

Mirror what the OpenAI and antigravity gateways already do: when the read
error happens before any byte has reached the client (`!c.Writer.Written()`),
return `*UpstreamFailoverError{StatusCode: 502, RetryableOnSameAccount: true}`
so the handler can retry on the same or another account. After client
output has begun, SSE has no resume protocol — keep the existing passthrough
behavior.

Tests cover both branches via streamReadCloser-based fixtures.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 19:12:48 +08:00
ivanvolt
04b2866f65 fix: use Responses-compatible function tool_choice format 2026-04-28 16:26:09 +08:00