feat(gateway): 消息mode落库(A3) — 持久化真实写入渲染模式
- POST /api/v1/chat 与 ws.chat 上行新增可选 mode(行为模式 design/01 §3/design/16 §5.1) - Web路径经ctx透传本轮mode到编排器持久化(persistUserMessages/persistAssistantReply),缺省回落chat - pendingMsg记录mode,恢复/续接路径沿用发起轮次mode - IM路径(persistToMain/persistIsolated)恒为chat - 新增/扩充测试:REST/WS/orchestrator/channelbridge 覆盖mode写入 - Docs/api-client.md v0.20 同步mode字段
This commit is contained in:
1 parent
fe5dbcddba
commit
b686d5fec8
9 files changed
+199
-41
No files matched your search
@@ -130,11 +130,14 @@ type sessionState struct {
|
||||
// pendingMsg is one queued user turn. It carries its own env so a resume runs
|
||||
// under the same memory-isolation boundary the message arrived with, plus the
|
||||
// client deviceID of the originating turn (design/16 §4.5) so the resumed
|
||||
// assistant reply is tagged with the same client that sent the message.
|
||||
// assistant reply is tagged with the same client that sent the message, and the
|
||||
// turn's behavior mode (design/16 §5.1) so the resumed reply is persisted under
|
||||
// the same mode as the user message that started it.
|
||||
type pendingMsg struct {
|
||||
messages []model.Message
|
||||
env loop.SessionEnv
|
||||
deviceID string
|
||||
mode string
|
||||
at time.Time
|
||||
}
|
||||
|
||||
@@ -219,6 +222,7 @@ func (o *Orchestrator) submitInternal(ctx context.Context, sessionID string, mes
|
||||
o.states[sessionID] = st
|
||||
}
|
||||
deviceID := deviceIDFrom(ctx)
|
||||
msgMode := modeFrom(ctx)
|
||||
if st.mode != ModeIdle {
|
||||
if len(st.pending) >= o.maxPending {
|
||||
mode := st.mode
|
||||
@@ -226,11 +230,11 @@ func (o *Orchestrator) submitInternal(ctx context.Context, sessionID string, mes
|
||||
o.emitState(sessionID, EventPendingFull, map[string]any{"sessionId": sessionID, "mode": mode, "max": o.maxPending})
|
||||
return SubmitResult{PendingFull: true}, nil
|
||||
}
|
||||
st.pending = append(st.pending, pendingMsg{messages: messages, env: env, deviceID: deviceID, at: time.Now()})
|
||||
st.pending = append(st.pending, pendingMsg{messages: messages, env: env, deviceID: deviceID, mode: msgMode, at: time.Now()})
|
||||
depth := len(st.pending)
|
||||
mode := st.mode
|
||||
o.mu.Unlock()
|
||||
o.persistUserMessages(ctx, sessionID, messages, deviceID)
|
||||
o.persistUserMessages(ctx, sessionID, messages, deviceID, msgMode)
|
||||
o.emitState(sessionID, EventPendingAck, map[string]any{"sessionId": sessionID, "mode": mode, "queueDepth": depth})
|
||||
return SubmitResult{Accepted: true, Pending: true, QueueDepth: depth}, nil
|
||||
}
|
||||
@@ -244,32 +248,32 @@ func (o *Orchestrator) submitInternal(ctx context.Context, sessionID string, mes
|
||||
o.mu.Unlock()
|
||||
|
||||
o.emitState(sessionID, EventSessionBusy, map[string]any{"sessionId": sessionID, "mode": ModeThinking})
|
||||
o.persistUserMessages(ctx, sessionID, messages, deviceID)
|
||||
o.persistUserMessages(ctx, sessionID, messages, deviceID, msgMode)
|
||||
|
||||
if sync {
|
||||
res, err := o.run(runCtx, sessionID, messages, env)
|
||||
if err != nil {
|
||||
o.log.Error("orchestrator turn", "err", err, "session", sessionID)
|
||||
}
|
||||
_ = o.completeTurn(sessionID, gen, res, err, deviceID)
|
||||
_ = o.completeTurn(sessionID, gen, res, err, deviceID, msgMode)
|
||||
return SubmitResult{Accepted: true, Immediate: true, Err: err}, res
|
||||
}
|
||||
|
||||
go o.goTurn(sessionID, gen, messages, env, runCtx, cancel, deviceID)
|
||||
go o.goTurn(sessionID, gen, messages, env, runCtx, cancel, deviceID, msgMode)
|
||||
return SubmitResult{Accepted: true, Immediate: true}, nil
|
||||
}
|
||||
|
||||
// goTurn runs a single turn on a background goroutine and, when it finishes,
|
||||
// hands the outcome to completeTurn so the orchestrator can resume the queue.
|
||||
// deviceID tags the persistent assistant reply with the client that sent the turn
|
||||
// (design/16 §4.5).
|
||||
func (o *Orchestrator) goTurn(sessionID string, gen uint64, messages []model.Message, env loop.SessionEnv, ctx context.Context, cancel context.CancelFunc, deviceID string) {
|
||||
// (design/16 §4.5) and mode with the turn's behavior mode (design/16 §5.1).
|
||||
func (o *Orchestrator) goTurn(sessionID string, gen uint64, messages []model.Message, env loop.SessionEnv, ctx context.Context, cancel context.CancelFunc, deviceID, mode string) {
|
||||
defer cancel()
|
||||
res, err := o.run(ctx, sessionID, messages, env)
|
||||
if err != nil && ctx.Err() == nil {
|
||||
o.log.Error("orchestrator turn", "err", err, "session", sessionID)
|
||||
}
|
||||
_ = o.completeTurn(sessionID, gen, res, err, deviceID)
|
||||
_ = o.completeTurn(sessionID, gen, res, err, deviceID, mode)
|
||||
}
|
||||
|
||||
// completeTurn runs after a turn settles (either it finished, errored, or was
|
||||
@@ -278,9 +282,10 @@ func (o *Orchestrator) goTurn(sessionID string, gen uint64, messages []model.Mes
|
||||
// message "remembers" the prior context instead of starting from a blank slate
|
||||
// (design/15 §2.2: incremental, not full replay). A stale generation (an
|
||||
// interrupt or a newer top-level turn superseded this one) is a no-op.
|
||||
// deviceID is the originating turn's client id, used to tag the persisted reply.
|
||||
func (o *Orchestrator) completeTurn(sessionID string, gen uint64, res *loop.Result, runErr error, deviceID string) error {
|
||||
o.persistAssistantReply(sessionID, res, deviceID)
|
||||
// deviceID is the originating turn's client id and mode its behavior mode, both
|
||||
// used to tag the persisted reply (design/16 §4.5, §5.1).
|
||||
func (o *Orchestrator) completeTurn(sessionID string, gen uint64, res *loop.Result, runErr error, deviceID, mode string) error {
|
||||
o.persistAssistantReply(sessionID, res, deviceID, mode)
|
||||
o.mu.Lock()
|
||||
st := o.states[sessionID]
|
||||
if st == nil || st.gen != gen {
|
||||
@@ -305,7 +310,7 @@ func (o *Orchestrator) completeTurn(sessionID string, gen uint64, res *loop.Resu
|
||||
o.mu.Unlock()
|
||||
|
||||
o.emitState(sessionID, EventPendingDrain, map[string]any{"sessionId": sessionID, "mode": ModeThinking, "queueDepth": depth})
|
||||
go o.goTurn(sessionID, gen, resume, env, runCtx, cancel, next.deviceID)
|
||||
go o.goTurn(sessionID, gen, resume, env, runCtx, cancel, next.deviceID, next.mode)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user