mirror of
https://github.com/sipeed/picoclaw.git
synced 2026-07-28 01:27:58 +00:00
639b32703a
* Support streaming * fix: stream pico reasoning updates Route Pico reasoning through the active streamer and hide empty thought placeholders. * fix: harden configured streaming delivery * fix ci * fix split issue
119 lines
3.6 KiB
Go
119 lines
3.6 KiB
Go
// PicoClaw - Ultra-lightweight personal AI agent
|
|
|
|
package agent
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/sipeed/picoclaw/pkg/bus"
|
|
runtimeevents "github.com/sipeed/picoclaw/pkg/events"
|
|
"github.com/sipeed/picoclaw/pkg/providers"
|
|
)
|
|
|
|
// Finalize handles turn finalization, either:
|
|
// - Early return when allResponsesHandled=true (ExecuteTools already finalized)
|
|
// - Normal finalization for allResponsesHandled=false (sets finalContent, saves session, compact)
|
|
func (p *Pipeline) Finalize(
|
|
ctx context.Context,
|
|
turnCtx context.Context,
|
|
ts *turnState,
|
|
exec *turnExecution,
|
|
turnStatus TurnEndStatus,
|
|
finalContent string,
|
|
) (turnResult, error) {
|
|
al := p.al
|
|
|
|
// When allResponsesHandled=true, ExecuteTools already finalized
|
|
// (added handledToolResponseSummary, saved session, set phase to Completed).
|
|
// But still check for hard abort - if requested, abort the turn.
|
|
if exec.allResponsesHandled {
|
|
if ts.hardAbortRequested() {
|
|
return al.abortTurn(ts)
|
|
}
|
|
ts.setPhase(TurnPhaseCompleted)
|
|
return turnResult{
|
|
finalContent: finalContent,
|
|
status: turnStatus,
|
|
followUps: append([]bus.InboundMessage(nil), ts.followUps...),
|
|
}, nil
|
|
}
|
|
|
|
ts.setPhase(TurnPhaseFinalizing)
|
|
ts.setFinalContent(finalContent)
|
|
if !ts.opts.NoHistory {
|
|
finalMsg := providers.Message{
|
|
Role: "assistant",
|
|
Content: finalContent,
|
|
ReasoningContent: responseReasoningContent(exec.response),
|
|
}
|
|
ts.agent.Sessions.AddFullMessage(ts.sessionKey, finalMsg)
|
|
ts.recordPersistedMessage(finalMsg)
|
|
ts.ingestMessage(turnCtx, al, finalMsg)
|
|
if err := ts.agent.Sessions.Save(ts.sessionKey); err != nil {
|
|
al.emitEvent(
|
|
runtimeevents.KindAgentError,
|
|
ts.eventMeta("runTurn", "turn.error"),
|
|
ErrorPayload{
|
|
Stage: "session_save",
|
|
Message: err.Error(),
|
|
},
|
|
)
|
|
cancelConfiguredStreamingLLM(turnCtx, exec)
|
|
return turnResult{status: TurnEndStatusError}, err
|
|
}
|
|
}
|
|
|
|
if ts.opts.EnableSummary {
|
|
al.contextManager.Compact(
|
|
turnCtx,
|
|
&CompactRequest{
|
|
SessionKey: ts.sessionKey,
|
|
Reason: ContextCompressReasonSummarize,
|
|
Budget: ts.agent.ContextWindow,
|
|
},
|
|
)
|
|
}
|
|
|
|
contextUsage := computeContextUsage(ts.agent, ts.sessionKey)
|
|
streamErr := finalizeConfiguredStreamingLLM(turnCtx, ts, exec, finalContent, contextUsage)
|
|
// If streaming never became visible, keep the legacy Pico interim publish path
|
|
// so the final answer is still delivered outside normal SendResponse.
|
|
if ((streamErr != nil && !isConfiguredStreamingVisibleError(streamErr)) || exec.streamingFallback) &&
|
|
!ts.opts.SendResponse && ts.opts.AllowInterimPicoPublish && finalContent != "" {
|
|
agentID, sessionKey, scope := outboundTurnMetadata(
|
|
ts.agent.ID,
|
|
ts.opts.Dispatch.SessionKey,
|
|
ts.opts.Dispatch.SessionScope,
|
|
)
|
|
msg := bus.OutboundMessage{
|
|
Context: outboundContextFromInbound(
|
|
ts.opts.Dispatch.InboundContext,
|
|
ts.opts.Dispatch.Channel(),
|
|
ts.opts.Dispatch.ChatID(),
|
|
ts.opts.Dispatch.ReplyToMessageID(),
|
|
),
|
|
AgentID: agentID,
|
|
SessionKey: sessionKey,
|
|
Scope: scope,
|
|
Content: finalContent,
|
|
ContextUsage: contextUsage,
|
|
}
|
|
markFinalOutbound(&msg)
|
|
_ = al.bus.PublishOutbound(turnCtx, msg)
|
|
}
|
|
if streamErr != nil && isConfiguredStreamingVisibleError(streamErr) {
|
|
ts.setPhase(TurnPhaseCompleted)
|
|
return turnResult{
|
|
finalContent: finalContent,
|
|
status: TurnEndStatusError,
|
|
followUps: append([]bus.InboundMessage(nil), ts.followUps...),
|
|
}, streamErr
|
|
}
|
|
ts.setPhase(TurnPhaseCompleted)
|
|
return turnResult{
|
|
finalContent: finalContent,
|
|
status: turnStatus,
|
|
followUps: append([]bus.InboundMessage(nil), ts.followUps...),
|
|
}, nil
|
|
}
|