feat(server): notify mcp-server billing_provider=newapi after Agnet user sync

Implements Heicode's choice (b) for §7.7.1 ②: in syncLocalUserFromAgnet,
fire-and-forget PUT mcp-server's internal /api/auth/internal/billing-provider
{email, billing_provider:"newapi"} so mcp-server's User table billing_provider
column lands as 'newapi' for users that came in via Heicode Manager (vs the
default 'litellm' for native taijiagent users).

- Goroutine: never blocks login on this side-effect; mcp-server endpoint is
  idempotent so retries from repeat logins are harmless.
- Token via env MCP_SERVER_INTERNAL_TOKEN (K8s/compose secret); empty env
  silently skips (dev-friendly).
- Reuses agnetHTTPClient + common.Marshal + common.GetUUID per repo
  conventions.

Spec: docs/Heicode-对接进度与待办.md §7.8.1.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-08 18:36:24 +08:00
co-authored by Claude Opus 4.7
parent 2f75588ed1
commit fc2c811e93
@@ -174,6 +174,61 @@ func fetchAgnetRefresh(baseURL, refreshToken string) (access string, refresh str
return env.Data.Token, env.Data.RefreshToken, nil
}
// markBillingProviderNewapi notifies mcp-server that this user's billing
// provider should be set to "newapi" (per Heicode 决策 7.7.1 ②).
//
// Fire-and-forget: 失败不影响登录主流程;mcp-server 那边也允许重复调用
// (内部端点对相同 email 幂等)。详见 Heicode-对接进度与待办.md §7.8.1。
//
// Token 通过 K8s secret / docker compose env `MCP_SERVER_INTERNAL_TOKEN`
// 注入;未配置时跳过(开发环境兼容)。
func markBillingProviderNewapi(email string) {
tok := strings.TrimSpace(os.Getenv("MCP_SERVER_INTERNAL_TOKEN"))
if tok == "" {
return
}
email = strings.TrimSpace(email)
if email == "" {
return
}
go func(email, tok string) {
body, err := common.Marshal(map[string]string{
"email": email,
"billing_provider": "newapi",
})
if err != nil {
common.SysLog("markBillingProviderNewapi: marshal failed: " + err.Error())
return
}
baseURL := defaultHeicodeAuthBaseURL()
req, err := http.NewRequest(
http.MethodPut,
baseURL+"/api/auth/internal/billing-provider",
strings.NewReader(string(body)),
)
if err != nil {
common.SysLog("markBillingProviderNewapi: req build failed: " + err.Error())
return
}
req.Header.Set("Authorization", "Bearer "+tok)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-Request-Id", common.GetUUID())
res, err := agnetHTTPClient().Do(req)
if err != nil {
common.SysLog("markBillingProviderNewapi: send failed: " + err.Error())
return
}
defer res.Body.Close()
if res.StatusCode >= 400 {
b, _ := io.ReadAll(io.LimitReader(res.Body, 1<<14))
common.SysLog(fmt.Sprintf(
"markBillingProviderNewapi: %d %s — %s",
res.StatusCode, email, strings.TrimSpace(string(b)),
))
}
}(email, tok)
}
func syncLocalUserFromAgnet(me agnetMeEnvelope) (*model.User, error) {
email := strings.TrimSpace(me.Data.Email)
if email == "" {
@@ -242,6 +297,9 @@ func syncLocalUserFromAgnet(me agnetMeEnvelope) (*model.User, error) {
}
}
// Notify mcp-server billing_provider=newapi (fire-and-forget; goroutine).
markBillingProviderNewapi(user.Email)
return &user, nil
}