refactor(gateway+model): 拆 gateway/server.go 719→204 + 重构 scanSSE/processFrom 到 ≤80行
- server.go 204 + 新 routes.go(193,路由注册/Handler/withMiddleware)/deps.go(108)/contracts.go(231,17依赖接口)/broadcast.go(78,BroadcastPersisted/CapacityAwaken/Mood); 导出面不变; 74条路由pattern+RBAC门逐条比对一致 - model/openai_stream.go: scanSSE 138→25(parse/apply/finalize 对齐 anthropic_stream); openaiStreamState结构体 - channelbridge/inbound.go: processFrom 112→46(blacklistDrop/inboundTargetFor/runInboundTurn/deliverReply) - 纯行为等价; build/vet/test绿(56包)+win/darwin交叉编译
This commit is contained in:
1 parent
4c3c98d5d4
commit
4ea68d17f3
7 files changed
+844
-654
No files matched your search
@@ -0,0 +1,78 @@
|
||||
package gateway
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/history"
|
||||
)
|
||||
|
||||
// This file owns the gateway's outbound realtime pushes: the persisted-message
|
||||
// frames shared with the channel bridge plus the per-persona state frames the
|
||||
// frontend renders (mood badge, capacity awaken).
|
||||
|
||||
// BroadcastPersisted broadcasts a just-persisted chat message as a WS event so
|
||||
// every browser of the same account appends it without a manual refresh
|
||||
// (design/16 §4.2). It is shared by the orchestrator (Web turns) and the channel
|
||||
// bridge (IM messages), so both paths flow through one frame shape. The event type
|
||||
// is split by session: the main web session and its web sub-sessions broadcast
|
||||
// `message_persisted` for web-chat realtime sync, while any other session id is an
|
||||
// isolated channel adapter session (sess-xxx) and broadcasts `channel_persisted`
|
||||
// for the IM adapter log page — the web chat has no handler for the latter, so it
|
||||
// is naturally ignored there and never pollutes the conversation. Thinking is
|
||||
// stripped to stay consistent with the WS history replay (design/16 §6). It is a
|
||||
// no-op-safe call when the hub has broadcast wired.
|
||||
func (s *Server) BroadcastPersisted(msg history.Message) {
|
||||
wire := toHistoryMessage(msg)
|
||||
wire.Thinking = ""
|
||||
typ := "channel_persisted"
|
||||
if msg.SessionID == history.MainSessionID || strings.HasPrefix(msg.SessionID, history.MainSessionID+":") {
|
||||
typ = "message_persisted"
|
||||
}
|
||||
s.hub.broadcast(wsEvent{Type: typ, SessionID: msg.SessionID, Data: map[string]any{"message": wire}})
|
||||
}
|
||||
|
||||
// wsCapacityAwaken is the WS event type for a capacity state transition
|
||||
// (design/14 §5). Its data field is the frontend AwakenPayload {fromState,toState,tool}.
|
||||
const wsCapacityAwaken = "capacity_awaken"
|
||||
|
||||
// BroadcastCapacityAwaken fans a capacity state transition to the Web UI as a
|
||||
// `capacity_awaken` frame (design/14 §5). sessionID is the conversation that
|
||||
// transitioned, so the client targets the correct session rather than treating the
|
||||
// wake event as global. data matches the frontend AwakenPayload {fromState, toState,
|
||||
// tool}; tool may be empty (no single tool was lit, the client then keeps the prior
|
||||
// tool badge).
|
||||
func (s *Server) BroadcastCapacityAwaken(sessionID, fromState, toState, tool string) {
|
||||
s.hub.broadcast(wsEvent{
|
||||
Type: wsCapacityAwaken,
|
||||
SessionID: sessionID,
|
||||
Data: map[string]any{
|
||||
"fromState": fromState,
|
||||
"toState": toState,
|
||||
"tool": tool,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// wsMoodEvent is the WS event type carrying the persona's mood after a turn
|
||||
// (design/07 §3 下行 `mood`; design/01 §2 情绪状态机). The frontend's existing
|
||||
// `mood` handler (ws.ts) reads data.mood and shows the 心情 badge.
|
||||
const wsMoodEvent = "mood"
|
||||
|
||||
// BroadcastMood fans one emotion change to every Web client as a `mood` frame
|
||||
// (design/07 §3). It is called only when the mood actually changed — the
|
||||
// machinery in the composition root is the gate — so the badge does not flicker
|
||||
// with an unchanged value on every turn. data matches what the frontend `mood`
|
||||
// handler reads ({mood}); affection/personaId ride along as the honest extra
|
||||
// detail (affection is the persona's current 好感度, personaId the persona whose
|
||||
// mood moved) and are ignored by clients that do not render them. The mood is a
|
||||
// persona-level value, not a per-session one, so no sessionId is attached.
|
||||
func (s *Server) BroadcastMood(personaID, mood string, affection int) {
|
||||
s.hub.broadcast(wsEvent{
|
||||
Type: wsMoodEvent,
|
||||
Data: map[string]any{
|
||||
"mood": mood,
|
||||
"personaId": personaID,
|
||||
"affection": affection,
|
||||
},
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user