Commit Graph
1928 Commits
Author SHA1 Message Date
erio f968e1bd95 refactor: extract computeTokenBreakdown to deduplicate billing logic
- calculateTokenCost reduced from 80 to 15 lines
- calculateCostInternal reduced from 91 to 15 lines
- Shared logic in computeTokenBreakdown + computeCacheCreationCost
- Unified rateMultiplier <= 0 protection in both paths
2026-04-02 02:33:05 +08:00
erio ed01e0dcb0 refactor: use structured error responses in channel handler
Replace response.BadRequest with response.ErrorFrom + infraerrors.BadRequest
to provide machine-readable reason codes (VALIDATION_ERROR, INVALID_CHANNEL_ID,
MISSING_PARAMETER) for frontend i18n support.
2026-04-02 02:32:39 +08:00
erio a745a83521 refactor: split buildCache into sub-functions, reduce nesting 5→2
- Extract newEmptyChannelCache() factory to deduplicate map init
- Extract expandPricingToCache() for model pricing expansion
- Extract expandMappingToCache() for model mapping expansion
- buildCache reduced from 110 to 50 lines
2026-04-02 02:28:32 +08:00
erio 198010f114 refactor: replace magic strings with named constants
- PricingSourceChannel/LiteLLM/Fallback for resolver source
- MediaTypeImage/Video/Prompt for result.MediaType
- Reuse BillingModeToken/BillingModeImage for billing mode
- Reuse BillingModelSourceChannelMapped/PlatformAnthropic in handler
2026-04-02 02:22:15 +08:00
erio 95d9b2d1d0 fix: billing mode display follows cost calculation result
Instead of hardcoding BillingMode="image" when ImageCount>0,
let cost.BillingMode (set by CalculateCostUnified/CalculateImageCost)
take priority. This ensures channel token pricing shows "token" mode.
2026-04-02 01:34:19 +08:00
erio ab74dcbf9d debug: add image billing resolve logging for troubleshooting 2026-04-02 01:09:49 +08:00
erio e9586ac085 fix: validate prices must be >= 0, remove debug logs 2026-04-02 00:52:19 +08:00
erio 5fb1340681 chore: remove debug logs for candidatesTokensDetails investigation 2026-04-02 00:48:36 +08:00
erio 435f88d984 debug: print v1internal raw payload for non-stream path 2026-04-02 00:29:44 +08:00
erio 194bde4651 debug: check candidatesTokensDetails in v1internal wrapper 2026-04-02 00:24:35 +08:00
erio e772c6ba2d debug: log candidatesTokensDetails presence in extractGeminiUsage 2026-04-02 00:17:38 +08:00
erio 4a24921ca9 feat: channel token pricing takes priority over per-image billing
When ImageCount > 0, check if channel has token pricing configured:
- YES (source=channel, mode=token) → use token billing with image_output_tokens
- NO → fall back to CalculateImageCost (original per-image billing)

This allows channels to configure $/MTok pricing for image generation
models while maintaining backward compatibility for setups without
channel pricing.
2026-04-01 23:29:01 +08:00
erio 5f48e903bf fix: address audit findings - cache sync, validation, consistency
- clearCreditsExhausted: sync Redis scheduler cache after DB update
- Image billing mode UI: write to per_request_price instead of image_output_price
- OpenAI RecordUsage: use BillingModelSourceRequested constant, add s.cfg nil guard
- Fix i18n key path: admin.channels.perRequestPriceRequired → admin.channels.form.perRequestPriceRequired
2026-04-01 23:13:58 +08:00
erio bace10ff61 test: add unit tests for channel platform matching, interval validation, credits check
- TestIsPlatformPricingMatch: 12 cases covering all platform combinations
- TestMatchingPlatforms: 4 cases for platform expansion
- TestGetChannelModelPricing_AntigravityCrossPlatform: antigravity sees anthropic pricing
- TestGetChannelModelPricing_AnthropicCannotSeeAntigravityPricing: no reverse leakage
- TestResolveChannelMapping_AntigravityCrossPlatform: antigravity uses anthropic mapping
- TestFilterValidIntervals: 8 cases for empty interval filtering
- TestHasEnoughCredits: 10 cases for credits balance threshold logic
- Extract hasEnoughCredits() pure function for testability
2026-04-01 22:41:39 +08:00
erio 8b179af2e4 fix: validate empty intervals + antigravity platform pricing match
- Backend: reject intervals with all-null price fields on save
- Backend: filterValidIntervals skips empty intervals in pricing resolver
- Frontend: red border + asterisk on empty interval rows
- Backend: antigravity groups now match anthropic/gemini channel pricing
2026-04-01 22:03:00 +08:00
erio 4d8683a115 fix: antigravity groups now match anthropic/gemini channel pricing
Antigravity platform serves both Claude and Gemini models, but channel
pricing/mapping is configured under Anthropic/Gemini tabs. The cache
builder was using strict platform equality, causing antigravity groups
to miss all channel pricing entries, resulting in $0 billing.

Add isPlatformPricingMatch() to treat antigravity as superset of
anthropic+gemini for pricing and mapping cache indexing.
2026-04-01 21:47:47 +08:00
erio eff0ceb14a fix: add cost nil guard to Anthropic/Antigravity RecordUsage paths
- Apply same nil-pointer protection as OpenAI path
- Remove unused accessToken/proxyURL params from checkAccountCredits
2026-04-01 17:13:31 +08:00
erio 327a4fb248 fix: golangci-lint test assertion and gofmt 2026-04-01 16:39:38 +08:00
erio 6af5b9ff9c fix: resolve golangci-lint issues
- Fix errcheck: defer rows.Close() with nolint
- Fix errcheck: type assertion with ok check in channel cache
- Fix staticcheck ST1005: lowercase error string
- Fix staticcheck SA5011: nil check cost before use in openai gateway
- Fix gofmt: format chatcompletions_to_responses.go
2026-04-01 16:30:47 +08:00
erio a1216b8839 feat: image output token billing, channel-mapped billing source, credits balance precheck
- Parse candidatesTokensDetails from Gemini API to separate image/text output tokens
- Add image_output_tokens and image_output_cost to usage_log (migration 089)
- Support per-image-token pricing via output_cost_per_image_token from model pricing data
- Channel pricing ImageOutputPrice override works in token billing mode
- Auto-fill image_output_price in channel pricing form from model defaults
- Add "channel_mapped" billing model source as new default (migration 088)
- Bills by model name after channel mapping, before account mapping
- Fix channel cache error TTL sign error (115s → 5s)
- Fix Update channel only invalidating new groups, not removed groups
- Fix frontend model_mapping clearing sending undefined instead of {}
- Credits balance precheck via shared AccountUsageService cache before injection
- Skip credits injection for accounts with insufficient balance
- Don't mark credits exhausted for "exhausted your capacity on this model" 429s
2026-04-01 15:08:57 +08:00
erio 47266e03e6 fix(antigravity): 积分注入后仍 429 直接标记 credits exhausted
旧逻辑中 shouldMarkCreditsExhausted 因 parseAntigravitySmartRetryInfo
能解析出 retry info 就 return false,导致 free 账号注入积分后上游返回
429 也永远不标记 exhausted,形成无限重试死循环。

修复:429 直接标记 exhausted,其他 4xx 保留关键词匹配(如 403 积分不足)。
2026-04-01 02:40:52 +08:00
erio 7abc6e02f4 feat(channel): 渠道管理全链路集成 — 模型映射、定价、限制、用量统计
- 渠道模型映射:支持精确匹配和通配符映射,按平台隔离
- 渠道模型定价:支持 token/按次/图片三种计费模式,区间分层定价
- 模型限制:渠道可限制仅允许定价列表中的模型
- 计费模型来源:支持 requested/upstream 两种计费模型选择
- 用量统计:usage_logs 新增 channel_id/model_mapping_chain/billing_tier/billing_mode 字段
- Dashboard 支持 model_source 维度(requested/upstream/mapping)查看模型统计
- 全部 gateway handler 统一接入 ResolveChannelMappingAndRestrict
- 修复测试:同步 SoraGenerationRepository 接口、SQL INSERT 参数、scan 字段
2026-04-01 01:51:19 +08:00
erio 9df5d90e78 fix(channel): 模型限制以原始请求模型检查定价列表,而非映射后模型
开启 restrict_models 时,应用原始模型名查定价列表;
定价列表未命中即拒绝,不因通配符映射而绕过限制。
2026-03-31 20:38:02 +08:00
erio adcf6328b4 fix(openai): 非 OAuth 账号 Chat Completions 也提取 system 消息并注入 instructions
GPT-5/Codex 等模型要求请求中必须包含 instructions 字段。
之前 extractSystemMessagesFromInput + applyInstructions 仅在
OAuth 路径执行,API Key 账号走 Chat Completions 时 system 消息
保留在 input 中但缺少 instructions,导致上游返回 400。

同时在 ChatCompletionsRequest 和 ResponsesRequest 中增加
Instructions 字段,支持客户端直接传入 instructions 透传到上游。
2026-03-31 20:11:39 +08:00
erio 72b9d198a1 feat(channel): 模型映射源支持通配符匹配
与定价通配符一致,映射源支持 * 后缀通配符(最长前缀优先):
- `*` 匹配所有模型
- `claude-*` 匹配 claude- 开头的模型
- 精确匹配优先于通配符
2026-03-31 19:32:14 +08:00
erio 3840dc8787 fix(channel): 修复 invalidateCache 存入 typed nil 导致 loadCache panic
invalidateCache 存入 (*channelCache)(nil),类型断言 ok=true 但
指针为 nil,后续 cached.loadedAt 导致 nil pointer dereference。
在 loadCache 双重检查处增加 cached != nil 防御。
2026-03-31 18:37:30 +08:00
erio bd17bde19c Merge branch 'feature/channel' into release/custom-0.1.105 2026-03-31 17:23:31 +08:00
erio d058e5c112 fix(channel): 全平台渠道映射覆盖 + 公共函数抽取 + 死代码清理
- 4个缺失handler入口添加渠道映射+限制检查(ChatCompletions/Responses/Gemini)
- 模型限制错误信息优化,区分"模型不可用"和"无账号"
- OpenAI RecordUsage RequestedModel 改用 OriginalModel
- ResolveChannelMappingAndRestrict/ReplaceModelInBody 抽取到 ChannelService 消除跨service重复
- validateNoDuplicateModels 按 platform:model 去重
- 删除 Channel.ResolveMappedModel 死代码和 CalculateCostWithChannel Deprecated方法
- 移除冗余nil检查,抽取 validatePricingBillingMode 公共校验
2026-03-31 15:26:20 +08:00
erio b4514501dd Merge remote-tracking branch 'origin/feature/channel' into release/custom-0.1.105 2026-03-31 02:11:52 +08:00
erio d1791f43f8 refactor(channel): 抽取渠道映射公共函数 + OpenAI映射到body + 空响应修复 + 清理日志
- 抽取 ResolveChannelMappingAndRestrict 统一入口(5处→1个方法)
- 抽取 BuildModelMappingChain 到 ChannelMappingResult 方法(5处→1行调用)
- OpenAI 三入口 Forward 前应用渠道映射到请求体
- OpenAI Responses/Messages 限制检查添加错误响应
- 清理前端 3 处 console.log 调试日志
2026-03-31 02:11:24 +08:00
erio 8741864b79 Merge remote-tracking branch 'origin/feature/channel' into release/custom-0.1.105 2026-03-31 01:34:37 +08:00
erio 946180ef0d fix(channel): 模型限制用映射后模型检查 + 平台开关保留配置不删除
- OpenAI 网关三处 IsModelRestricted 改用 channelMapping.MappedModel
- 前端平台勾选改为 enabled 开关,取消勾选不清空配置数据
- formToAPI/校验只处理 enabled 的平台
2026-03-31 01:34:16 +08:00
erio f27f1254ae Merge remote-tracking branch 'origin/feature/channel' into release/custom-0.1.105 2026-03-31 00:24:22 +08:00
erio a65a8bcd93 feat(channel): 通配符定价匹配 + OpenAI BillingModelSource + 按次价格校验 + 用户端计费模式展示
- 定价查找支持通配符(suffix *),最长前缀优先匹配
- 模型限制(restrict_models)同样支持通配符匹配
- OpenAI 网关接入渠道映射/BillingModelSource/模型限制
- 按次/图片计费模式创建时强制要求价格或层级(前后端)
- 用户使用记录列表增加计费模式 badge 列
2026-03-31 00:23:45 +08:00
erio 7347916f09 Merge remote-tracking branch 'origin/feature/channel' into release/custom-0.1.105 2026-03-30 23:05:04 +08:00
erio 08593ea3e4 fix(billing): 按次计费回退到默认 PerRequestPrice
ResolvedPricing 新增 DefaultPerRequestPrice,当无层级匹配时使用渠道的默认按次价格
2026-03-30 23:04:48 +08:00
erio 9876c05efc merge: feature/channel → release (v0.1.105.15) — 计费迁移到 CalculateCostUnified 2026-03-30 22:59:15 +08:00
erio f0d5163973 feat(billing): 网关计费迁移到 CalculateCostUnified + 模型限制错误统一
- GatewayService/OpenAIGatewayService 注入 ModelPricingResolver
- RecordUsage 从旧路径迁移到 CalculateCostUnified(支持 per_request/image 模式)
- 无渠道时自动回退旧路径,保持原有行为
- 长上下文双倍计费仅在无渠道定价时生效
- CostBreakdown 新增 BillingMode 字段,使用日志记录实际计费模式
- 模型限制错误改为与"无可用账号"相同的 503 响应
2026-03-30 22:58:28 +08:00
erio 54976c77ce merge: feature/channel → release (v0.1.105.14) — 使用记录计费模式 2026-03-30 22:14:24 +08:00
erio 9e848ec2f0 feat(usage): 使用记录增加计费模式字段 — 记录/展示/筛选 token/按次/图片
- DB: usage_logs 表新增 billing_mode VARCHAR(20) 列
- 后端: RecordUsage 写入时根据 image_count 判定计费模式
- 前端: 使用记录表格新增计费模式 badge 列 + 筛选下拉
2026-03-30 22:13:16 +08:00
erio 2f1168a126 Merge remote-tracking branch 'origin/feature/channel' into release/custom-0.1.105
# Conflicts:
#	backend/cmd/server/wire_gen.go
2026-03-30 16:13:52 +08:00
erio c74c6f3f1f feat(channel): 模型价格自动填充 + 默认定价 API
- 新增 GET /admin/channels/model-pricing?model=xxx API
- 从 BillingService 查询 LiteLLM/Fallback 默认定价
- 前端添加模型时自动查询并填充价格($/MTok)
- 仅在所有价格字段为空时才自动填充,不覆盖手动配置
2026-03-30 16:11:49 +08:00
erio b80361e57a Merge remote-tracking branch 'origin/feature/channel' into release/custom-0.1.105 2026-03-30 15:04:48 +08:00
erio c67e5a9cd9 feat(channel): 缓存扁平化 + 网关映射集成 + 计费模式统一 + 模型限制
- 缓存按 (groupID, platform, model) 三维 key 扁平化,避免跨平台同名模型冲突
- buildCache 批量查询 group platform,按平台过滤展开定价和映射
- model_mapping 改为嵌套格式 {platform: {src: dst}}
- channel_model_pricing 新增 platform 列
- 前端按平台维度重构:每个平台独立配置分组/映射/定价
- 迁移 086: platform 列 + model_mapping 嵌套格式迁移
2026-03-30 15:04:30 +08:00
erio 6b68152540 Merge branch 'feature/channel' into release/custom-0.1.105
# Conflicts:
#	backend/cmd/server/VERSION
#	backend/internal/handler/admin/channel_handler.go
#	backend/internal/service/channel.go
2026-03-30 13:27:26 +08:00
erio 19d1a28e23 feat(channel): 缓存扁平化 + 网关映射集成 + 计费模式统一 + 模型限制
- 缓存重构为 O(1) 哈希结构 (pricingByGroupModel, mappingByGroupModel)
- 渠道模型映射接入网关流程 (Forward 前应用, a→b→c 映射链)
- 新增 billing_model_source 配置 (请求模型/最终模型计费)
- usage_logs 新增 channel_id, model_mapping_chain, billing_tier 字段
- 每种计费模式统一支持默认价格 + 区间定价
- 渠道模型限制开关 (restrict_models)
- 分组按平台分类展示 + 彩色图标
- 必填字段红色星号 + 模型映射 UI
- 去除模型通配符支持
2026-03-30 13:26:05 +08:00
erio 151ea48b87 Merge remote-tracking branch 'origin/feature/channel' into release/custom-0.1.105
# Conflicts:
#	backend/internal/handler/admin/channel_handler.go
2026-03-30 02:38:18 +08:00
erio e2f0df78ce feat(channel): 模型映射 + 分组搜索 + 卡片折叠 + 冲突校验
- 渠道模型映射:新增 model_mapping JSONB 字段,在账号映射之前执行
- 分组选择:添加搜索过滤 + 平台图标
- 定价卡片:支持折叠/展开,已有数据默认折叠
- 模型冲突校验:前后端均禁止同一渠道内重复模型
- 迁移 083: channels 表添加 model_mapping 列
2026-03-30 02:36:04 +08:00
erio 755bf44e4d Merge remote-tracking branch 'origin/feature/channel' into release/custom-0.1.105 2026-03-30 02:25:12 +08:00
erio e4814e4c77 feat(channel): 模型标签输入 + $/MTok 价格单位 + 左开右闭区间 + i18n
- 模型输入改为标签列表(输入回车添加,支持粘贴批量导入)
- 价格显示单位改为 $/MTok(每百万 token),提交时自动转换
- Token 模式增加图片输出价格字段(适配 Gemini 图片模型按 token 计费)
- 区间边界改为左开右闭 (min, max],右边界包含
- 默认价格作为未命中区间时的回退价格
- 添加完整中英文 i18n 翻译
2026-03-30 02:24:54 +08:00