Loading workspace insights... Statistics interval
7 days30 daysLatest CI Pipeline Executions
e41edc17 feat(ai, ai-client): add SessionAdapter for durable session support. (#286)
* feat(ai): align start event types with AG-UI
Widen TextMessageStartEvent.role to accept all message roles and add
optional parentMessageId to ToolCallStartEvent.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat(ai): add MessageStreamState type for per-message stream tracking
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat(ai): refactor StreamProcessor to per-message state
Replace single-message instance variables with a Map<string, MessageStreamState>
keyed by messageId. Add explicit handlers for TEXT_MESSAGE_START, TEXT_MESSAGE_END,
and STATE_SNAPSHOT events. Route tool calls via toolCallToMessage mapping.
Maintains backward compat: startAssistantMessage() sets pendingManualMessageId
which TEXT_MESSAGE_START associates with. ensureAssistantMessage() auto-creates
state for streams without TEXT_MESSAGE_START.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat(ai): replace STATE_SNAPSHOT with MESSAGES_SNAPSHOT event
Add MessagesSnapshotEvent as a first-class AG-UI event type for
conversation hydration. Replace the previous STATE_SNAPSHOT handler
(which extracted messages from arbitrary state) with a dedicated
MESSAGES_SNAPSHOT handler that accepts a typed messages array.
- Add MessagesSnapshotEvent type to AGUIEventType and AGUIEvent unions
- Add MESSAGES_SNAPSHOT case in StreamProcessor.processChunk()
- Remove STATE_SNAPSHOT handler (falls through to default no-op)
- Fix onStreamEnd to fire per-message (not only when no active messages remain)
- Fix getActiveAssistantMessageId to return on first reverse match
- Fix ensureAssistantMessage to emit onStreamStart and onMessagesChange
- Add proposal docs for resumeable session support
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat(ai-client): add SessionAdapter interface and createDefaultSession
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat(ai-client): refactor ChatClient to use SessionAdapter subscription model
Replace direct ConnectionAdapter usage in ChatClient with a SessionAdapter-based
subscription loop. When only a ConnectionAdapter is provided, it is wrapped in a
DefaultSessionAdapter internally. This enables persistent session support while
preserving existing timing semantics and backwards compatibility.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(ai-preact): thread option through.
* fix(ai): finalizeStream when RUN_FINISHED.
* fix(ai-client): handle reload during active stream with generation counter
reload() now cancels the active stream (abort controllers, subscription,
processing promise) before starting a new one. A stream generation counter
prevents a superseded stream's async cleanup from clobbering the new
stream's state (abortController, isLoading, processor).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: remove proposal docs.
* fix(ai, ai-client): address stream lifecycle edge cases from PR review
- Guard against double onStreamEnd when RUN_FINISHED arrives before TEXT_MESSAGE_END
- Clear dead waiters on subscribe exit to prevent chunk loss on reconnection
- Reset transient processor state (messageStates, activeMessageIds, etc.) on MESSAGES_SNAPSHOT
- Remove optimistic startAssistantMessage() from streamResponse(); let stream events create the message naturally via TEXT_MESSAGE_START or ensureAssistantMessage()
- Clean up abort listeners on normal waiter resolution to prevent listener accumulation
- Make handleStepFinishedEvent use ensureAssistantMessage() for backward compat with streams that lack TEXT_MESSAGE_START
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(ai-client): fix reload failures from stale stream state and waiter race
Reset processor stream state (prepareAssistantMessage) in streamResponse()
before the subscription loop, preventing stale messageStates from blocking
new assistant message creation on reload.
Rewrite createDefaultSession with per-subscribe queue isolation: each
subscribe() synchronously installs fresh buffer/waiters, drains pre-buffered
chunks via splice(0), and removes async cleanup that raced with new
subscription cycles.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* ci: apply automated fixes
* fix(ai): resolve eslint errors in stream processor
Remove unnecessary `chunk.delta !== undefined` condition (delta is
always a string on TextMessageContentEvent) and remove redundant `!`
non-null assertion inside an already-narrowed `if` block.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(ai-client): resolve eslint errors in chat-client and session-adapter
Fix import ordering: move value import `createDefaultSession` above
type-only imports. Convert shorthand method signatures to function
property style in the SessionAdapter interface.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(ai-client): propagate send() errors to subscribe() consumers
Wrap createDefaultSession's send() in try/catch and push a RUN_ERROR
AG-UI event to the queue before re-throwing, so subscribe() consumers
learn about connection failures through the standard protocol. Also
resolve processingResolve on RUN_ERROR in consumeSubscription (same as
RUN_FINISHED) to prevent hangs.
Tests updated: error assertions now check message content rather than
referential identity, since errors flowing through RUN_ERROR create
new Error instances from the message string.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(ai): map 'tool' role to 'assistant' in message state to fix lookups
The stream processor mapped 'tool' to 'assistant' for UIMessage but
stored the raw 'tool' role in MessageStreamState. This caused
getActiveAssistantMessageId() and getCurrentAssistantMessageId() to miss
tool-role messages, so subsequent stream events couldn't attach to the
existing message. Now the uiRole mapping is applied consistently across
all three cases in handleTextMessageStartEvent.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(ai): normalize chunk.delta to avoid "undefined" string concatenation
When chunk.delta was undefined, the check `chunk.delta !== ''` evaluated
to true, causing "undefined" to be concatenated into nextText. Use
`chunk.delta ?? ''` to normalize before comparison, matching the safe
pattern already used in handleToolCallArgsEvent.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(ai): use || instead of ?? for chunk.delta fallback to satisfy eslint
The no-unnecessary-condition rule flags ?? since TypeScript types delta
as string. Using || preserves runtime safety and matches existing patterns.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(ai): reset stream flags on MESSAGES_SNAPSHOT to avoid stale state
handleMessagesSnapshotEvent was clearing maps but not resetting isDone,
hasError, and finishReason. Use resetStreamState() which handles all of
these, ensuring finalizeStream() sees fresh state after a snapshot.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor(ai-client): finalize connection adapter unification
Squash all post-groundwork changes into a single commit that completes the connection adapter unification, stream lifecycle hardening, and restoration of ai stream snapshot/state behavior.
Co-authored-by: Cursor <cursoragent@cursor.com>
* ci: apply automated fixes
* fix: return booleans from chat client streamResponse
Keep early stream exits aligned with the Promise<boolean> contract so repo-wide type checks pass after the merge from main.
Made-with: Cursor
* chore: add changeset for durable chat updates
Document the patch releases for the durable subscribe/send chat transport, generation client wrappers, and core stream processing changes included on this branch.
Made-with: Cursor
* fix: resolve tool call approval after RUN_FINISHED using toolCallToMessage fallback
After RUN_FINISHED, activeMessageIds is cleared so tool call approval
couldn't find the parent message. Fall back to the toolCallToMessage map
which is populated during TOOL_CALL_START and preserved across finalize.
Also adds missing role field to mock chat scenarios.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Sam Willis <sam.willis@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Jack Herrington <jherr@pobox.com>