Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
b13200d
fix(gateway): forward received chat stream usage
wucm667 Sep 19, 2026
ece1ecc
fix(gateway): normalize streamed Anthropic usage
wucm667 Sep 21, 2026
0416c9e
fix(gateway): avoid ambiguous late-cache input subtraction
wucm667 Sep 23, 2026
13fcfc0
fix(gateway): resolve composite routes for websocket aliases
wucm667 Sep 19, 2026
e878ab7
fix(gateway): constrain account model route ownership
wucm667 Sep 21, 2026
453b796
fix(gateway): enforce composite model ownership in legacy scheduling
wucm667 Sep 23, 2026
e2c35cf
fix(account): prevent whitelist model mapping conflicts
wucm667 Sep 23, 2026
9a62841
chore: sync VERSION to 0.2.9 [skip ci]
github-actions[bot] Sep 28, 2026
bd48b2b
fix(antigravity): keep compat streams alive before first content
wucm667 Sep 28, 2026
aa3a3ea
feat(dashboard): toggle recent usage between tokens and spending
wucm667 Sep 28, 2026
2840cde
feat(frontend): hide unsupported clients for Claude Code-only groups
PMExtra Sep 23, 2026
6a69dd0
feat: add risk control user allowlist
PMExtra Sep 24, 2026
6073704
fix: omit Codex model catalog for OpenAI groups
PMExtra Sep 23, 2026
8490a81
feat: support Claude Sonnet 5.5
StarryKira Sep 28, 2026
2cc459e
style: satisfy Sonnet 5.5 lint checks
StarryKira Sep 28, 2026
6a8f921
feat(claude): expose sanitized native reset credit status
korkin25 Sep 24, 2026
f8984b8
feat(accounts): show Claude reset credit status on demand
korkin25 Sep 24, 2026
48970f6
fix(claude): explicitly close status HTTP response
korkin25 Sep 24, 2026
421da3f
feat(accounts): align Claude reset credit query with Codex reset coun…
william-drakemond Sep 28, 2026
35232c3
fix(accounts): address review of Claude reset credit count
william-drakemond Sep 28, 2026
860782e
fix(claude): harden reset credit projection and cell state (review R2)
william-drakemond Sep 29, 2026
27a9421
fix(anthropic): rewrite tool names in one body pass
wucm667 Sep 29, 2026
dd6cb41
Merge pull request #7683 from StarryKira/codex/sonnet-5-5-upstream
Wei-Shaw Sep 29, 2026
1dca86f
Merge pull request #7701 from wucm667/fix/issue-7699-tool-rewrite
Wei-Shaw Sep 29, 2026
b35a611
Merge pull request #7646 from wucm667/feat/issue-7642-dashboard-spend…
Wei-Shaw Sep 29, 2026
883a53f
Merge pull request #7643 from wucm667/fix/issue-7637-antigravity-comp…
Wei-Shaw Sep 29, 2026
f50b99b
Merge pull request #7542 from wucm667/fix/model-whitelist-mapping-con…
Wei-Shaw Sep 29, 2026
ccb027b
Merge pull request #7378 from wucm667/fix/issue-7368-composite-ws-rou…
Wei-Shaw Sep 29, 2026
1f955af
Merge pull request #7380 from wucm667/fix/issue-7377-forward-stream-u…
Wei-Shaw Sep 29, 2026
052e08e
Merge pull request #7684 from william-drakemond/feat/claude-reset-cre…
Wei-Shaw Sep 29, 2026
41dcfec
Merge pull request #7680 from PMExtra/fix/openai-model-catalog
Wei-Shaw Sep 29, 2026
fe9dadf
Merge pull request #7679 from PMExtra/feat/risk-control-allowlist
Wei-Shaw Sep 29, 2026
2f3fed2
Merge pull request #7678 from PMExtra/feat/claude-code-only-client-tabs
Wei-Shaw Sep 29, 2026
a60a295
chore: sync VERSION to 0.2.10 [skip ci]
github-actions[bot] Sep 29, 2026
6a1dc1e
chore: merge upstream v0.2.10 preserving downstream contracts
Sep 29, 2026
821339e
docs: record the upstream v0.2.10 integration decisions
Sep 29, 2026
cb45094
fix(test): drop the removed includeUsage argument from chat caller specs
Sep 29, 2026
56f0615
style: gofmt the settings update view after the merge
Sep 29, 2026
e72a01f
chore: set VERSION and upstream base to 0.2.10 for the codexrip release
Sep 29, 2026
c399dd6
fix(merge): schedule WS composite aliases on the resolved model; hono…
Sep 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .downstream/upstream-base
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v0.2.9
v0.2.10
46 changes: 46 additions & 0 deletions .downstream/upstream-review-v0.2.10.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Upstream v0.2.10 integration

## Source

- Downstream base: `a3bca48d645f3bcd49720c53e197a43a11282a5e` (`v0.2.9-codexrip.2`, `origin/main`).
- Upstream: stable tag `v0.2.10`, commit `2f3fed2fdb0787141294cec81487a5df30426f7f` (`upstream/main` at `a60a29549f488a854966aaec9541abbe006cac22`).
- Shared ancestor: `4c00df2e0183e2c70b7fa8ba45914205e36aad0c` (upstream `v0.2.9`), which is also the base of the previous integration.
- Upstream delta: 34 commits, 118 paths, +3759/-332. 64 paths overlap downstream changes; 17 of them conflicted textually.
- No upstream schema, migration or dependency changes. No new SQL migration is introduced by this integration.

## Integration decisions

Four high-impact areas were escalated to the owner before any code was written; the owner chose official semantics for all four.

- **Risk-control user allowlist (upstream #7679).** Adopted verbatim: the allowlisted platform users keep full upstream evidence while being excluded from automatic local penalties; default is off and the list is empty. The fork's downstream visibility rules (administrators see complete, unmasked information) are unaffected because the upstream change adds fields rather than hiding any.
- **Claude Sonnet 5.5 support (upstream #7683).** Adopted verbatim, including the upstream effort catalog and the single-pass tool-name rewrite in `gateway_tool_rewrite.go`. Downstream effort accounting, exact date aliases, explicit `none` and downstream `max` semantics remain in place and compose with the upstream catalog.
- **Billing and streamed usage normalization (upstream #7380/#7684).** Adopted: billed figures follow the upstream normalization for streamed Anthropic usage and chat-stream usage forwarding. The one-off value shift this causes in existing figures is accepted by the owner's decision to follow official semantics.
- **Client tabs for Claude Code-only groups (upstream #7678).** Adopted: unsupported clients are hidden for those groups. The fork's OpenCode tab was preserved and the guard composed with it.

## Deliberate downstream deviations kept

- **Capacity stays manifest-derived.** Upstream re-introduced a hard-coded `limit: { context: 1000000, output: 128000 }` for `claude-opus-5-5` and `claude-sonnet-5-5` in the OpenCode config. The fork removed exactly those constants in `862e84bbb` (v0.2.9 capacity unification) so that an OpenCode limit is emitted only from a resolver-tagged manifest row (`custom`, `official`, `upstream`, `registry`) and stays unknown when no evidence exists. The upstream constants are not restored; the upstream spec assertions that required them were rewritten to the fork's contract. The model entries, adaptive thinking options and variant sets are unchanged.
- **The OpenCode tab keeps fetching the Codex model manifest for OpenAI groups.** Upstream #7680 stopped showing and fetching the catalog for OpenAI groups. The fork derives OpenCode capacity from that manifest, so the catalog panel and fetch remain available for the `opencode` tab only. For the Codex CLI tabs on OpenAI groups the upstream behavior is kept exactly: the config emits no `model_catalog_json` reference and no catalog panel is rendered. This preserves the upstream intent while keeping the fork's capacity source.
- The fork's scheduler reporting (`ReportOpenAIAccountScheduleResultForSelection`, selective proxy-failure reporting, retry-exhausted cooldown) and its composite-route target-platform checks are retained alongside the upstream composite route resolution added in this release.
- The Apple-style console theme, the restored admin visibility and the native in-host capabilities are untouched by this integration.

## Conflict resolutions

- `backend/cmd/server/wire_gen.go`: arguments follow the fork's provider signatures plus the new `compositeRouteResolver` and `claudeResetCreditService` providers.
- `backend/internal/handler/openai_gateway_handler.go` (10 hunks): kept the fork's scheduler reporting, selective failure reporting and refusal-recovery cyber input handling; adopted upstream composite route resolution, the log-only cyber gate, the synchronous session-block write before the asynchronous record, and the body-aware blocked-session lookup. `wsRouteModel` (upstream) and `wsRoutingModel`/`wsForwardModel` (fork) coexist: routing data used for scheduler calls is the resolved upstream model (`wsRoutingModel = wsForwardModel`, identical to the old value outside composite groups). The fork's older asynchronous session-block write was removed, so allowlisted (log-only) users never get transcript blocks, as upstream intends.
- Settings union: `CyberPolicyUserAllowlist` was added alongside the fork's existing refusal and alpha-search keys in `settings_view.go`, `setting_parse.go`, `dto/settings.go`, `domain_constants.go` and `setting_handler_update.go`.
- `AccountUsageCell.vue`: adopted upstream's single stable `ClaudeResetCreditsCell` instance that survives the usage response, while keeping the fork's empty-usage text and always-available Grok quota probe.
- `EditAccountModal.vue`, `ModelWhitelistSelector.spec.ts`, `UseKeyModal.vue`: the fork's capacity-sync props were merged with upstream's `model-mappings` prop.

## Validation

- `GOOS=linux GOARCH=amd64 go build ./...` passes.
- `pnpm run typecheck` passes.
- `pnpm run check:i18n` passes.
- 174 focused frontend specs pass, covering every conflicted frontend file plus the new upstream `ClaudeResetCreditsCell` spec.
- `go test -tags=unit ./internal/handler/` passes, including upstream's composite WebSocket alias and cyber allowlist tests. The shared WebSocket harness counts rejected-turn upstream frames with the fork's `frameCount`.
- Not run: production migration rehearsal, real model calls, deploy, or canary. Source integration does not claim a successful deployment or model-quality acceptance.

## Release target

- `v0.2.10-codexrip.1`, fixed downstream image digest, `deploy-preserve`, resource profile `preserve`, natural drain, only the Sub2API container recreated. Final source SHA, PR and check results, image digest and production outcome belong to the operational delivery evidence.
2 changes: 1 addition & 1 deletion backend/cmd/server/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.2.9
0.2.10
5 changes: 3 additions & 2 deletions backend/cmd/server/wire_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions backend/internal/domain/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,8 @@ var DefaultBedrockModelMapping = map[string]string{
"claude-opus-4-1": "us.anthropic.claude-opus-4-1-20250805-v1:0",
"claude-opus-4-20250514": "us.anthropic.claude-opus-4-20250514-v1:0",
// Claude Sonnet
// Sonnet 5.5 is available on bedrock-runtime through Global inference only.
"claude-sonnet-5-5": "global.anthropic.claude-sonnet-5-5",
"claude-sonnet-5": "us.anthropic.claude-sonnet-5-v1",
"claude-sonnet-4-6-thinking": "us.anthropic.claude-sonnet-4-6",
"claude-sonnet-4-6": "us.anthropic.claude-sonnet-4-6",
Expand Down
7 changes: 4 additions & 3 deletions backend/internal/domain/constants_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,10 @@ func TestDefaultBedrockModelMapping_ContainsNewClaudeModels(t *testing.T) {
t.Parallel()

cases := map[string]string{
"claude-fable-5-1": "anthropic.claude-fable-5-1",
"claude-fable-5": "anthropic.claude-fable-5",
"claude-opus-4-8": "us.anthropic.claude-opus-4-8-v1",
"claude-fable-5-1": "anthropic.claude-fable-5-1",
"claude-fable-5": "anthropic.claude-fable-5",
"claude-opus-4-8": "us.anthropic.claude-opus-4-8-v1",
"claude-sonnet-5-5": "global.anthropic.claude-sonnet-5-5",
}
for from, want := range cases {
got, ok := DefaultBedrockModelMapping[from]
Expand Down
1 change: 1 addition & 0 deletions backend/internal/handler/admin/account_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ func NewOAuthHandler(oauthService *service.OAuthService) *OAuthHandler {

// AccountHandler handles admin account management
type AccountHandler struct {
claudeResetCredits claudeResetReader
adminService service.AdminService
oauthService *service.OAuthService
openaiOAuthService *service.OpenAIOAuthService
Expand Down
34 changes: 34 additions & 0 deletions backend/internal/handler/admin/claude_reset_handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package admin

import (
"context"
"github.com/Wei-Shaw/sub2api/internal/pkg/response"
"github.com/Wei-Shaw/sub2api/internal/service"
"github.com/gin-gonic/gin"
"strconv"
)

type claudeResetReader interface {
Query(context.Context, int64) (*service.ClaudeResetCredits, error)
}

func (h *AccountHandler) SetClaudeResetCreditService(s *service.ClaudeResetCreditService) {
h.claudeResetCredits = s
}
func (h *AccountHandler) ClaudeResetCredits(c *gin.Context) {
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
if err != nil || id <= 0 {
response.BadRequest(c, "Invalid account ID")
return
}
if h.claudeResetCredits == nil {
response.Error(c, 503, "Claude reset service unavailable")
return
}
status, err := h.claudeResetCredits.Query(c.Request.Context(), id)
if err != nil {
response.ErrorFrom(c, err)
return
}
response.Success(c, status)
}
6 changes: 5 additions & 1 deletion backend/internal/handler/admin/dashboard_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -500,7 +500,11 @@ func (h *DashboardHandler) GetUserUsageTrend(c *gin.Context) {
limit = 12
}

trend, hit, err := h.getUserUsageTrendCached(c.Request.Context(), startTime, endTime, granularity, limit)
metric := c.DefaultQuery("metric", "tokens")
if metric != "tokens" && metric != "actual_cost" {
metric = "tokens"
}
trend, hit, err := h.getUserUsageTrendCached(c.Request.Context(), startTime, endTime, granularity, limit, metric)
if err != nil {
response.Error(c, 500, "Failed to get user usage trend")
return
Expand Down
17 changes: 17 additions & 0 deletions backend/internal/handler/admin/dashboard_handler_cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ func (r *dashboardUsageRepoCacheProbe) GetUserUsageTrend(
startTime, endTime time.Time,
granularity string,
limit int,
metric string,
) ([]usagestats.UserUsageTrendPoint, error) {
r.usersTrendCalls.Add(1)
return []usagestats.UserUsageTrendPoint{{
Expand Down Expand Up @@ -115,4 +116,20 @@ func TestDashboardHandler_GetUserUsageTrend_UsesCache(t *testing.T) {
require.Equal(t, http.StatusOK, rec2.Code)
require.Equal(t, "hit", rec2.Header().Get("X-Snapshot-Cache"))
require.Equal(t, int32(1), repo.usersTrendCalls.Load())

for _, tc := range []struct {
metric, cache string
calls int32
}{
{"actual_cost", "miss", 2},
{"actual_cost", "hit", 2},
{"tokens", "hit", 2},
} {
req := httptest.NewRequest(http.MethodGet, "/admin/dashboard/users-trend?start_date=2026-03-01&end_date=2026-03-07&granularity=day&limit=8&metric="+tc.metric, nil)
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
require.Equal(t, http.StatusOK, rec.Code)
require.Equal(t, tc.cache, rec.Header().Get("X-Snapshot-Cache"))
require.Equal(t, tc.calls, repo.usersTrendCalls.Load())
}
}
6 changes: 4 additions & 2 deletions backend/internal/handler/admin/dashboard_query_cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ type dashboardEntityTrendCacheKey struct {
EndTime string `json:"end_time"`
Granularity string `json:"granularity"`
Limit int `json:"limit"`
Metric string `json:"metric,omitempty"`
}

func cacheStatusValue(hit bool) string {
Expand Down Expand Up @@ -213,15 +214,16 @@ func (h *DashboardHandler) getAPIKeyUsageTrendCached(ctx context.Context, startT
return trend, hit, err
}

func (h *DashboardHandler) getUserUsageTrendCached(ctx context.Context, startTime, endTime time.Time, granularity string, limit int) ([]usagestats.UserUsageTrendPoint, bool, error) {
func (h *DashboardHandler) getUserUsageTrendCached(ctx context.Context, startTime, endTime time.Time, granularity string, limit int, metric string) ([]usagestats.UserUsageTrendPoint, bool, error) {
key := mustMarshalDashboardCacheKey(dashboardEntityTrendCacheKey{
StartTime: startTime.UTC().Format(time.RFC3339),
EndTime: endTime.UTC().Format(time.RFC3339),
Granularity: granularity,
Limit: limit,
Metric: metric,
})
entry, hit, err := dashboardUsersTrendCache.GetOrLoad(key, func() (any, error) {
return h.dashboardService.GetUserUsageTrend(ctx, startTime, endTime, granularity, limit)
return h.dashboardService.GetUserUsageTrend(ctx, startTime, endTime, granularity, limit, metric)
})
if err != nil {
return nil, hit, err
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ func (h *DashboardHandler) buildSnapshotV2Response(
}

if includeUsersTrend {
usersTrend, _, err := h.getUserUsageTrendCached(ctx, startTime, endTime, granularity, usersTrendLimit)
usersTrend, _, err := h.getUserUsageTrendCached(ctx, startTime, endTime, granularity, usersTrendLimit, "tokens")
if err != nil {
return nil, errors.New("failed to get user usage trend")
}
Expand Down
1 change: 1 addition & 0 deletions backend/internal/handler/admin/setting_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,7 @@ func (h *SettingHandler) GetSettings(c *gin.Context) {
DefaultBalance: settings.DefaultBalance,
RiskControlEnabled: settings.RiskControlEnabled,
CyberSessionBlockEnabled: settings.CyberSessionBlockEnabled,
CyberPolicyUserAllowlist: settings.CyberPolicyUserAllowlist,
CyberSessionBlockTTLSeconds: settings.CyberSessionBlockTTLSeconds,
OpenAIRefusalRecoveryEnabled: settings.OpenAIRefusalRecoveryEnabled,
OpenAICyberFailoverEnabled: settings.OpenAICyberFailoverEnabled,
Expand Down
3 changes: 3 additions & 0 deletions backend/internal/handler/admin/setting_handler_audit.go
Original file line number Diff line number Diff line change
Expand Up @@ -604,6 +604,9 @@ func diffSettings(before *service.SystemSettings, after *service.SystemSettings,
if before.RiskControlEnabled != after.RiskControlEnabled {
changed = append(changed, "risk_control_enabled")
}
if before.CyberPolicyUserAllowlist != after.CyberPolicyUserAllowlist {
changed = append(changed, "cyber_policy_user_allowlist")
}
if before.CyberSessionBlockEnabled != after.CyberSessionBlockEnabled {
changed = append(changed, "cyber_session_block_enabled")
}
Expand Down
Loading
Loading