EmitStartupInfo emits initial agent, team, and toolset information for immediate sidebar display. When sess is non-nil and contains token data, a TokenUsageEvent is also emitted so that the sidebar can display context usage percentage on session restore.
(ctx context.Context, sess *session.Session, events EventSink)
| 1470 | // When sess is non-nil and contains token data, a TokenUsageEvent is also emitted so that the |
| 1471 | // sidebar can display context usage percentage on session restore. |
| 1472 | func (r *LocalRuntime) EmitStartupInfo(ctx context.Context, sess *session.Session, events EventSink) { |
| 1473 | // Prevent duplicate emissions |
| 1474 | if r.startupInfoEmitted { |
| 1475 | return |
| 1476 | } |
| 1477 | r.startupInfoEmitted = true |
| 1478 | |
| 1479 | a := r.CurrentAgent() |
| 1480 | |
| 1481 | // Helper to send events with context check |
| 1482 | send := func(event Event) bool { |
| 1483 | if ctx.Err() != nil { |
| 1484 | return false |
| 1485 | } |
| 1486 | events.Emit(event) |
| 1487 | return true |
| 1488 | } |
| 1489 | |
| 1490 | // Emit agent and team information immediately for fast sidebar display. |
| 1491 | // Use getEffectiveModelID to account for active fallback cooldowns. |
| 1492 | modelID := r.getEffectiveModelID(ctx, a) |
| 1493 | if !r.emitAgentAndTeamInfo(ctx, a, send) { |
| 1494 | return |
| 1495 | } |
| 1496 | |
| 1497 | // When restoring a session that already has token data, emit a |
| 1498 | // TokenUsageEvent so the sidebar can show the context usage percentage. |
| 1499 | // The context limit comes from the model definition (models.dev), which |
| 1500 | // is a model property — not persisted in the session. |
| 1501 | // |
| 1502 | // Use TotalCost (not OwnCost) because this is a restore/branch context: |
| 1503 | // sub-sessions won't emit their own events, so the parent must include |
| 1504 | // their costs. |
| 1505 | if sess != nil && (sess.InputTokens > 0 || sess.OutputTokens > 0) { |
| 1506 | contextLimit := r.contextLimitForAgentModel(ctx, a, modelID) |
| 1507 | usage := SessionUsage(sess, contextLimit) |
| 1508 | usage.Cost = sess.TotalCost() |
| 1509 | |
| 1510 | // Reconstruct LastMessage from the parent session's last assistant |
| 1511 | // message so that FinishReason (and other per-message fields) are |
| 1512 | // available on session restore. We intentionally iterate |
| 1513 | // sess.Messages (not GetAllMessages) so the result reflects the |
| 1514 | // parent agent's state: this event carries the parent session_id, |
| 1515 | // and sub-agents emit their own token_usage events with their own |
| 1516 | // session_id during live streaming. |
| 1517 | for i := range slices.Backward(sess.Messages) { |
| 1518 | item := &sess.Messages[i] |
| 1519 | if !item.IsMessage() || item.Message.Message.Role != chat.MessageRoleAssistant { |
| 1520 | continue |
| 1521 | } |
| 1522 | msg := &item.Message.Message |
| 1523 | lm := &MessageUsage{ |
| 1524 | Model: msg.Model, |
| 1525 | Cost: msg.Cost, |
| 1526 | FinishReason: msg.FinishReason, |
| 1527 | } |
| 1528 | if msg.Usage != nil { |
| 1529 | lm.Usage = *msg.Usage |