diff --git a/internal/agent/modes/interactive.go b/internal/agent/modes/interactive.go index 0d1b206..82c2fd9 100644 --- a/internal/agent/modes/interactive.go +++ b/internal/agent/modes/interactive.go @@ -1316,6 +1316,17 @@ func (i *Interactive) applyModelSelection(prov, model string) { i.mu.Unlock() return } + // Snapshot the current transcript and cumulative usage BEFORE we + // build the replacement agent so we can hand them off. Without + // this the user perceives the entire session as wiped on a + // cross-provider /model swap. + var carryMsgs []provider.Message + var carryCost provider.Usage + if i.agent != nil { + carryMsgs = i.agent.Messages() + carryCost = i.agent.Cost() + } + ag, p, md, err := i.cfg.BuildAgentFor(m.Provider, m.ID) if err != nil { i.mu.Lock() @@ -1323,12 +1334,27 @@ func (i *Interactive) applyModelSelection(prov, model string) { i.mu.Unlock() return } + + // Replay the transcript and seed the cost on the freshly-built + // agent. Messages travel cleanly between providers because they + // use the same provider.Message shape; tool-call ids are local + // to a turn so cross-provider continuation never confuses the + // new model (it just sees the assistant's reply, no orphan + // tool_use blocks because /model swaps are gated to idle state). + if len(carryMsgs) > 0 { + ag.SetMessages(carryMsgs) + } + ag.SeedCost(carryCost) + i.mu.Lock() i.agent = ag i.cfg.Provider = p i.cfg.Model = md i.statusOK = "switched to " + p + " / " + md i.statusErr = "" + // Render cache keys are width+content based, so the new agent's + // identical messages will reuse the existing entries. Nothing + // to invalidate. i.mu.Unlock() if i.cfg.PersistModel != nil { i.cfg.PersistModel(p, md) diff --git a/internal/core/agent.go b/internal/core/agent.go index 38f23e1..6b9f750 100644 --- a/internal/core/agent.go +++ b/internal/core/agent.go @@ -60,6 +60,15 @@ func (a *Agent) Cost() provider.Usage { return a.cost.Total } +// SeedCost sets the cumulative usage as a baseline before the first +// turn runs. Used when transferring state from another agent (model +// or provider switch) so the running cost meter doesn't reset to 0. +func (a *Agent) SeedCost(u provider.Usage) { + a.mu.Lock() + defer a.mu.Unlock() + a.cost.Total = u +} + // Prompt sends a user message and runs the agent loop until the model // stops or an error occurs. Events are delivered via sink in order. // sink must not block the caller for long; buffer as needed.