docs(gateway): gateway 剩余文件注释改中文(逐行 1:1,代码零字节改动)
主人 2026-10-03 拍板「注释一律中文」;gateway 至此整包注释中文化完成(本会话共 3 批: 已推库 16 个文件 + 本批剩余文件)。 校验:机械校验(非注释改动行)= 空;剥注释后代码文本逐行比对全等(不一致文件数 0); 全树 go test ./... 58 包 ok / 0 FAIL;交叉编译绿。
This commit is contained in:
1 parent
91e8d7a029
commit
074ffc4ebf
73 files changed
+2529
-2529
No files matched your search
@@ -21,27 +21,27 @@ import (
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/plugins/channels/weixin_ilink"
|
||||
)
|
||||
|
||||
// LLM-provider and channel-lifecycle admin writes (owner-only, audited). The LLM
|
||||
// test performs a real model call; provider "add" records a process-lifetime
|
||||
// entry. Channel open/close drive the real adapter lifecycle; the identity test
|
||||
// is the structural actor × whitelist rule, never prompt text.
|
||||
// LLM provider 与渠道生命周期的管理写操作(仅 owner,带审计)。LLM
|
||||
// 测试会发起真实模型调用;provider「add」记录一条
|
||||
// 进程生命周期内的条目。渠道开/关驱动真实适配器生命周期;身份
|
||||
// 测试走结构化的 actor × 白名单规则,绝不是提示词文本。
|
||||
|
||||
const (
|
||||
// ownerProfilePath is the owner whitelist loaded for the structural identity
|
||||
// test. It mirrors the path wire.go uses for owner recognition.
|
||||
// ownerProfilePath 是为结构化身份测试加载的主人白名单。
|
||||
// 它与 wire.go 用于主人识别的路径一致。
|
||||
ownerProfilePath = "configs/users/owner.yaml"
|
||||
// llmTestTimeout bounds a single LLM probe request.
|
||||
// llmTestTimeout 限定单次 LLM 探测请求。
|
||||
llmTestTimeout = 30 * time.Second
|
||||
// llmProbePrompt is the minimal prompt used to verify a model responds.
|
||||
// llmProbePrompt 是用于验证模型能应答的最小提示词。
|
||||
llmProbePrompt = "Reply with the single word: OK"
|
||||
)
|
||||
|
||||
// llmProviderRequest is the POST /api/v1/admin/llm body for adding/updating a
|
||||
// provider group. Every field is a pointer so the handler can tell "absent"
|
||||
// (preserve the stored value) from "explicitly zero" (overwrite): a partial edit
|
||||
// from the panel must never wipe a field it did not touch. Name is a client-side
|
||||
// display label (not persisted — the models.json schema carries id/provider/
|
||||
// baseUrl/apiKey/model/fallback/multimodal/maxTokens).
|
||||
// llmProviderRequest 是 POST /api/v1/admin/llm 的请求体,用于添加/更新
|
||||
// provider 分组。每个字段都是指针,使 handler 能区分「缺失」
|
||||
// (保留已存值)与「显式零值」(覆盖):来自面板的局部
|
||||
// 编辑绝不清除它未触碰的字段。Name 是客户端侧
|
||||
// 显示标签(不持久化 —— models.json 结构含 id/provider/
|
||||
// baseUrl/apiKey/model/fallback/multimodal/maxTokens)。
|
||||
type llmProviderRequest struct {
|
||||
ID *string `json:"id"`
|
||||
Name *string `json:"name"`
|
||||
@@ -51,16 +51,16 @@ type llmProviderRequest struct {
|
||||
Model *string `json:"model"`
|
||||
Multimodal *bool `json:"multimodal"`
|
||||
Fallback *string `json:"fallback"`
|
||||
// MaxTokens is the group's own token budget. Absent preserves the stored
|
||||
// budget (0 = unset for a new group). Negative is rejected.
|
||||
// MaxTokens 是该分组自身的 token 预算。缺失则保留已存
|
||||
// 预算(新分组 0 = 未设置)。负值被拒绝。
|
||||
MaxTokens *int `json:"maxTokens"`
|
||||
}
|
||||
|
||||
// adminAddLlmProvider saves a provider group to models.json (read-modify-write,
|
||||
// promoting it to the active group) and hot-reloads the running LLM stack so the
|
||||
// new provider/model takes effect without a restart (design/04 §3.1). A field the
|
||||
// client omits keeps its stored value, so a partial edit never resets a budget or
|
||||
// key. The API key is persisted to the gitignored file but never returned/logged.
|
||||
// adminAddLlmProvider 把 provider 分组保存到 models.json(read-modify-write,
|
||||
// 并将其提升为活动分组),再热重载运行中的 LLM 栈,使
|
||||
// 新的 provider/model 无需重启即生效(design/04 §3.1)。客户端省略的
|
||||
// 字段保持其已存值,因此局部编辑绝不复位预算或
|
||||
// key。API key 被持久化进被 gitignore 的文件,但绝不返回/记日志。
|
||||
func (s *Server) adminAddLlmProvider(w http.ResponseWriter, r *http.Request) {
|
||||
var req llmProviderRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
@@ -83,8 +83,8 @@ func (s *Server) adminAddLlmProvider(w http.ResponseWriter, r *http.Request) {
|
||||
writeError(w, apperr.Wrap(apperr.ErrBadRequest, "bad_request", "%v", err))
|
||||
return
|
||||
}
|
||||
// Promote the saved group to the active (index 0) slot, then persist and
|
||||
// hot-reload so Current() reflects the new provider/model immediately.
|
||||
// 把已保存的分组提升到活动(下标 0)槽位,然后持久化并
|
||||
// 热重载,使 Current() 立即反映新的 provider/model。
|
||||
groups := runtimeconfig.Activate(s.llm.Groups(), group)
|
||||
if err := runtimeconfig.SaveModels(s.llm.Path(), groups); err != nil {
|
||||
s.auditClaims(r, "llm.addProvider", "error")
|
||||
@@ -117,9 +117,9 @@ func (s *Server) adminAddLlmProvider(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
// mergeLlmProvider applies a partial provider patch over the stored groups: a
|
||||
// field present in the request wins, an absent field keeps the stored value (or
|
||||
// its default for a new group). It returns a validated group.
|
||||
// mergeLlmProvider 把局部 provider 补丁施加到已存分组之上:
|
||||
// 请求中出现的字段胜出,缺失字段保留已存值(新分组
|
||||
// 则用其默认值)。它返回经过校验的分组。
|
||||
func mergeLlmProvider(groups []runtimeconfig.Group, req llmProviderRequest) (runtimeconfig.Group, error) {
|
||||
id := ""
|
||||
if req.ID != nil {
|
||||
@@ -166,7 +166,7 @@ func mergeLlmProvider(groups []runtimeconfig.Group, req llmProviderRequest) (run
|
||||
return g, nil
|
||||
}
|
||||
|
||||
// llmTestRequest is the POST /api/v1/admin/llm/test body.
|
||||
// llmTestRequest 是 POST /api/v1/admin/llm/test 的请求体。
|
||||
type llmTestRequest struct {
|
||||
Provider string `json:"provider"`
|
||||
BaseURL string `json:"baseUrl"`
|
||||
@@ -175,8 +175,8 @@ type llmTestRequest struct {
|
||||
Prompt string `json:"prompt"`
|
||||
}
|
||||
|
||||
// adminTestLlm performs a real completion against the supplied provider/model
|
||||
// endpoint and reports the result — a genuine round-trip, not a placeholder.
|
||||
// adminTestLlm 针对提供的 provider/model 端点执行一次真实补全
|
||||
// 并报告结果 —— 真实的往返,而不是占位。
|
||||
func (s *Server) adminTestLlm(w http.ResponseWriter, r *http.Request) {
|
||||
var req llmTestRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
@@ -217,12 +217,12 @@ func (s *Server) adminTestLlm(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(w, http.StatusOK, map[string]any{"ok": true, "provider": provider, "model": resp.Model, "reply": resp.Content, "tokens": resp.TotalTokens})
|
||||
}
|
||||
|
||||
// channelActionRequest is the POST /api/v1/admin/channels/open|close body.
|
||||
// channelActionRequest 是 POST /api/v1/admin/channels/open|close 的请求体。
|
||||
type channelActionRequest struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// adminChannelOpen connects a registered channel adapter (real lifecycle call).
|
||||
// adminChannelOpen 连接一个已注册渠道适配器(真实的生命周期调用)。
|
||||
func (s *Server) adminChannelOpen(w http.ResponseWriter, r *http.Request) {
|
||||
name, ok := s.channelName(r, w, "channel.open")
|
||||
if !ok {
|
||||
@@ -236,17 +236,17 @@ func (s *Server) adminChannelOpen(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
if err := ch.Connect(r.Context()); err != nil {
|
||||
s.auditClaims(r, "channel.open", "error")
|
||||
// A state a plain reconnect cannot fix must not read as a generic failure:
|
||||
// the weixin-iLink bot token was rejected by iLink (errcode=-14), so the
|
||||
// owner needs a fresh scan-code login. Answer 409 with that instruction
|
||||
// instead of pretending the channel opened (the panel marks it connected).
|
||||
// 单纯重连修不好的状态不得读作通用失败:
|
||||
// weixin-iLink 机器人 token 被 iLink 拒绝(errcode=-14),因此
|
||||
// 主人需要一次新的扫码登录。用该指引返回 409,
|
||||
// 而不是假装渠道已打开(面板会把它标记为已连接)。
|
||||
if errors.Is(err, weixin_ilink.ErrSessionRejected) {
|
||||
writeError(w, apperr.Wrap(apperr.ErrConflict, "session_rejected",
|
||||
"该渠道会话已被 iLink 拒绝(errcode=-14):请用「扫码登录」重新绑定后再连接"))
|
||||
return
|
||||
}
|
||||
// weixin-iLink with no token yet must not hang nor read as a credential
|
||||
// error: the panel routes the owner to the out-of-band 扫码登录 flow.
|
||||
// 尚无 token 的 weixin-iLink 既不能挂起,也不能读作凭据
|
||||
// 错误:面板会把主人导向带外的扫码登录流程。
|
||||
if errors.Is(err, weixin_ilink.ErrNeedsLogin) {
|
||||
writeError(w, apperr.Wrap(apperr.ErrConflict, "needs_login", "该渠道尚未登录:请先用「扫码登录」绑定,再连接"))
|
||||
return
|
||||
@@ -259,7 +259,7 @@ func (s *Server) adminChannelOpen(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(w, http.StatusOK, map[string]any{"name": name, "connected": true})
|
||||
}
|
||||
|
||||
// adminChannelClose disconnects a registered channel adapter (real lifecycle call).
|
||||
// adminChannelClose 断开一个已注册渠道适配器(真实的生命周期调用)。
|
||||
func (s *Server) adminChannelClose(w http.ResponseWriter, r *http.Request) {
|
||||
name, ok := s.channelName(r, w, "channel.close")
|
||||
if !ok {
|
||||
@@ -281,17 +281,17 @@ func (s *Server) adminChannelClose(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(w, http.StatusOK, map[string]any{"name": name, "connected": false})
|
||||
}
|
||||
|
||||
// channelTestRequest is the POST /api/v1/admin/channels/test body: a channel
|
||||
// account id plus the conversation shape, resolved by the structural actor rule.
|
||||
// channelTestRequest 是 POST /api/v1/admin/channels/test 的请求体:一个渠道
|
||||
// 账号 id 加上对话形态,由结构化 actor 规则解析。
|
||||
type channelTestRequest struct {
|
||||
Platform string `json:"platform"`
|
||||
AccountID string `json:"accountId"`
|
||||
IsGroup bool `json:"isGroup"`
|
||||
}
|
||||
|
||||
// adminChannelTest runs the structural identity rule (actor metadata × owner
|
||||
// whitelist), never prompt text. It loads the owner whitelist from disk so a
|
||||
// mismatch degrades to the conservative default (member/stranger).
|
||||
// adminChannelTest 运行结构化身份规则(actor 元数据 × 主人
|
||||
// 白名单),绝不是提示词文本。它从磁盘加载主人白名单,
|
||||
// 因此不匹配时退化为保守默认(member/stranger)。
|
||||
func (s *Server) adminChannelTest(w http.ResponseWriter, r *http.Request) {
|
||||
var req channelTestRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
@@ -327,8 +327,8 @@ func (s *Server) adminChannelTest(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
// channelName decodes the {name} body for open/close routes, writing a 400 on
|
||||
// failure. It reports uniqueness of the decode via ok.
|
||||
// channelName 为 open/close 路由解码 {name} 请求体,失败时写出 400。
|
||||
// 它通过 ok 报告解码的唯一性。
|
||||
func (s *Server) channelName(r *http.Request, w http.ResponseWriter, op string) (string, bool) {
|
||||
var req channelActionRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
@@ -343,11 +343,11 @@ func (s *Server) channelName(r *http.Request, w http.ResponseWriter, op string)
|
||||
return req.Name, true
|
||||
}
|
||||
|
||||
// findChannel returns the registered channel adapter named name, if any. An exact
|
||||
// adapter name ("kook:conf0") wins; a bare platform name ("kook") that is not an
|
||||
// exact name still resolves to that platform's first registered account, so the
|
||||
// panel/CLI "connect kook" reaches the token-bearing account even when it is named
|
||||
// "kook:conf0" (the empty-token bare placeholder is never registered).
|
||||
// findChannel 返回名为 name 的已注册渠道适配器(若有)。精确的
|
||||
// 适配器名("kook:conf0")胜出;非精确名的裸平台名("kook")
|
||||
// 仍会解析到该平台首个已注册账号,因此面板/CLI 的
|
||||
// "connect kook" 能抵达携带 token 的账号,即使它名为
|
||||
// "kook:conf0"(空 token 的裸占位符从不注册)。
|
||||
func findChannel(caps CapabilityStore, name string) (sdk.Channel, bool) {
|
||||
if caps == nil {
|
||||
return nil, false
|
||||
@@ -371,8 +371,8 @@ func findChannel(caps CapabilityStore, name string) (sdk.Channel, bool) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// loadOwnerProfile loads the owner whitelist from disk, returning nil on any
|
||||
// failure so the identity test degrades to the conservative deny.
|
||||
// loadOwnerProfile 从磁盘加载主人白名单,任何失败都返回
|
||||
// nil,因此身份测试退化为保守的拒绝。
|
||||
func loadOwnerProfile() *user.OwnerProfile {
|
||||
p, err := user.LoadOwnerProfile(ownerProfilePath)
|
||||
if err != nil {
|
||||
@@ -381,7 +381,7 @@ func loadOwnerProfile() *user.OwnerProfile {
|
||||
return p
|
||||
}
|
||||
|
||||
// describeIdentity renders the structural verdict for the identity test.
|
||||
// describeIdentity 为身份测试渲染结构化判定结果。
|
||||
func describeIdentity(a adapter.Actor, _ *user.OwnerProfile) string {
|
||||
switch {
|
||||
case a.IsGroupChat():
|
||||
|
||||
Reference in new issue
Block a user