mirror of
https://github.com/patriceckhart/zot.git
synced 2026-06-26 21:36:31 +02:00
fix: keep transcript and cumulative cost across cross-provider /model swap
applyModelSelection's cross-provider branch built a fresh agent via BuildAgentFor and assigned it to i.agent without copying anything from the old one, so the user perceived /model anthropic->openai (or vice versa) as a hard reset of the entire conversation. same-provider swaps already worked because they only mutated agent.Model in place. now the cross-provider path snapshots agent.Messages() and agent.Cost() before constructing the replacement, then SetMessages + SeedCost on the new agent so the transcript and the cumulative dollar meter both carry across. added core.Agent.SeedCost(provider.Usage) for the cost transfer. messages already round-trip cleanly because tool names are normalised to lowercase in the in-memory provider.Message representation; the oauth-only Read/Write/Edit/Bash rename happens on the wire at request build time, not in the transcript. verified end-to-end via zot rpc: turn 1 (opus): "remember teal" -> "ok" set_model: opus -> sonnet turn 2 (sonnet): "what color did i say?" -> "teal" both same-provider (opus<->sonnet) and cross-provider (anthropic<->openai) paths exercised.
This commit is contained in:
parent
3ff6d9e6b7
commit
5a2cfb525e
2 changed files with 35 additions and 0 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue