render typed tool cards and live session status from stream-json
The conversation pane now exploits the structured stream instead of dumping tool input as JSON. ConversationController indexes tool_use by id so a tool_result pairs back to its call and renders the Edit/Write diff or is_error failure in place; per-tool bodies (Bash command+output, Read/Grep file/query) reuse the shared renderers factored out of the permission card. SessionStatus gains cost + contextWindow + rate-limit, read straight off the init/result/rate_limit_event events, so the in-pane status line reflects live state without the config probe. Partial-message streaming is wired behind --include-partial-messages but its event shape is unverified against the live binary and degrades to a no-op if it differs — see T-184. T-168. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -47,16 +47,58 @@ class ConversationController extends ChangeNotifier {
|
||||
final Future<void> Function()? _onDispose;
|
||||
late final StreamSubscription<ConversationItem> _sub;
|
||||
final List<ConversationItem> _items = [];
|
||||
|
||||
/// Index from uuid → position in [_items] for the FIRST item with that
|
||||
/// uuid. Used to upsert partial-message streaming updates in-place (T-168):
|
||||
/// when a partial arrives, the session emits an item with the same uuid as
|
||||
/// the previous partial so the controller replaces rather than appends it.
|
||||
/// Only the first occurrence is indexed — full (non-partial) items that
|
||||
/// share a uuid after a session resume are appended normally (uuid reuse
|
||||
/// across turns is rare; correctness wins over perf there).
|
||||
final Map<String, int> _uuidIndex = {};
|
||||
|
||||
Timer? _notifyTimer;
|
||||
bool _disposed = false;
|
||||
|
||||
/// Items in arrival (transcript) order.
|
||||
List<ConversationItem> get items => List.unmodifiable(_items);
|
||||
|
||||
/// Index from `tool_use_id` to the corresponding [AssistantToolUse] item,
|
||||
/// built as items arrive. Used by the conversation view to render the
|
||||
/// result card in the context of its tool_use (T-168).
|
||||
Map<String, AssistantToolUse> get toolUseById => Map.unmodifiable(_toolUseById);
|
||||
final Map<String, AssistantToolUse> _toolUseById = {};
|
||||
|
||||
bool get isEmpty => _items.isEmpty;
|
||||
|
||||
void _onItem(ConversationItem item) {
|
||||
_items.add(item);
|
||||
// Track AssistantToolUse items by toolUseId for result-card pairing (T-168).
|
||||
if (item is AssistantToolUse) {
|
||||
_toolUseById[item.toolUseId] = item;
|
||||
}
|
||||
// Upsert-by-uuid only for partial-message streaming items (T-168). Partial
|
||||
// items are distinguished by a `partial-<message.id>` uuid prefix assigned
|
||||
// by [StreamJsonSession]. Real transcript items always have distinct uuids
|
||||
// (or at least should not be collapsed even when they collide, since the
|
||||
// transcript records separate turns). This guard prevents test items with
|
||||
// fixed uuids from accidentally replacing each other.
|
||||
if (item.uuid.startsWith('partial-')) {
|
||||
final existing = _uuidIndex[item.uuid];
|
||||
if (existing != null && existing < _items.length) {
|
||||
_items[existing] = item;
|
||||
if (item is AssistantToolUse) _toolUseById[item.toolUseId] = item;
|
||||
// Coalesce-notify path below handles notifications.
|
||||
} else {
|
||||
_uuidIndex[item.uuid] = _items.length;
|
||||
_items.add(item);
|
||||
}
|
||||
} else {
|
||||
// Normal (non-partial) item: always append. Index only if not already
|
||||
// seen (so the first real occurrence wins in the upsert table — there
|
||||
// should be no real collision, but be safe).
|
||||
_uuidIndex.putIfAbsent(item.uuid, () => _items.length);
|
||||
_items.add(item);
|
||||
}
|
||||
// Coalesce notifications: the reader emits a burst (the initial tail
|
||||
// read), and a notify-per-item would thrash the view's rebuild +
|
||||
// auto-scroll. A zero-duration Timer fires only after the microtask
|
||||
|
||||
Reference in New Issue
Block a user