Files
heicode-mananger/heicode/controller/heicode_available_models.go
T
chenchenandClaude Opus 4.8 79e0e8b7d1 feat(heicode): 新增 /api/heicode/available-models,客户端模型列表收口 (#25)
客户端需要 Manager 下发用户可用模型作唯一来源(里程碑04 收尾)。

- GET /api/heicode/available-models(挂 UserOrV2DeviceAuth,与 /api/heicode/self 同
  设备配对鉴权)。
- 服务端解析:user.Group → service.GetUserUsableGroups → model.GetGroupEnabledModels
  (与 GetUserModels 同源),去重排序。
- 客户端安全形状 {model_id, display_name, default}:绝不下发 channelId/base_url/
  api_key/provider_type/单价(buildAvailableModelItems 纯函数 + 测试断言不泄露)。
- default 标记取 defaultAgentModelID()。

可选字段 capabilities/context_window/cost_tier 暂不下发(HM 无可靠来源,避免臆造;
客户端按可选处理)。etag/updated_at 可作后续。

Fixes #25

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 15:51:21 +08:00

64 lines
1.9 KiB
Go

package controller
import (
"sort"
"github.com/gin-gonic/gin"
"github.com/heicode/manager/common"
"github.com/heicode/manager/model"
"github.com/heicode/manager/service"
)
// buildAvailableModelItems maps resolved model names to the client-safe catalog
// shape for #25: only model_id / display_name / default. It deliberately omits
// channelId / base_url / api_key_ref / provider_type / per-token price — those
// stay server-side (Manager). Pure (testable).
func buildAvailableModelItems(modelNames []string, defaultModel string) []gin.H {
items := make([]gin.H, 0, len(modelNames))
for _, name := range modelNames {
items = append(items, gin.H{
"model_id": name,
"display_name": name,
"default": name == defaultModel,
})
}
return items
}
// HeicodeAvailableModels: GET /api/heicode/available-models (issue #25).
//
// The single source of truth for the desktop client's model list: the models
// the LOGGED-IN user may use, resolved server-side from the user's usable groups
// → group-enabled models (same resolution as GetUserModels). Bound to the user's
// own group/subscription; channelId / base_url / api_key / provider type / price
// are NEVER exposed. Same auth as /api/heicode/self (UserOrV2DeviceAuth / device
// pairing). Client uses this as the only model source (no local presets).
func HeicodeAvailableModels(c *gin.Context) {
userID := c.GetInt("id")
if userID <= 0 {
common.ApiErrorMsg(c, "authentication required")
return
}
user, err := model.GetUserCache(userID)
if err != nil {
common.ApiError(c, err)
return
}
seen := map[string]bool{}
var names []string
for group := range service.GetUserUsableGroups(user.Group) {
for _, m := range model.GetGroupEnabledModels(group) {
if !seen[m] {
seen[m] = true
names = append(names, m)
}
}
}
sort.Strings(names)
common.ApiSuccess(c, gin.H{
"available_models": buildAvailableModelItems(names, defaultAgentModelID()),
})
}