Skip to content

refactor: add type-safe stream event interface to listenStream#14

Open
whitelonng wants to merge 3 commits into
yejiming:mainfrom
whitelonng:fix/06-listenstream-type-safety
Open

refactor: add type-safe stream event interface to listenStream#14
whitelonng wants to merge 3 commits into
yejiming:mainfrom
whitelonng:fix/06-listenstream-type-safety

Conversation

@whitelonng

Copy link
Copy Markdown
Contributor

Summary

Fixes type safety issue where listenStream used unsafe any for event payloads, providing no type checking for event structure or discriminated unions on eventType.

Changes

Type System

  • Define comprehensive StreamEvent type hierarchy covering all event types:
    • Content events: StreamDeltaEvent, StreamTextEvent
    • Thinking events: StreamThinkingDeltaEvent, StreamThinkingSignatureEvent
    • Context events: StreamContextCompactedEvent (matches SessionContextCompaction schema)
    • Tool events: StreamToolStartEvent, StreamToolDeltaEvent, StreamToolDoneEvent, StreamToolEvent
    • Control events: StreamDoneEvent, StreamErrorEvent
  • Update listenStream signature to use StreamEventWrapper with typed payload
  • All events extend StreamEventBase with common runId, eventType, message fields

Example

Before (unsafe):

listenStream(runId, (event) => {
  if (event.payload.eventType === 'delat') { // typo, no compile error
    console.log(event.payload.text); // wrong field, no error
  }
});

After (type-safe):

listenStream(runId, (event) => {
  if (event.payload.eventType === 'delta') {
    console.log(event.payload.delta); // ✓ correct field, autocompleted
    console.log(event.payload.text);  // ✗ TypeScript error: Property 'text' does not exist
  }
});

Benefits

  • ✅ Event structure validated at compile time
  • ✅ IDE autocomplete shows available fields for each eventType
  • ✅ Discriminated unions prevent accessing wrong fields
  • ✅ Self-documenting: event structure centrally defined
  • ✅ Catches eventType typos at compile time
  • ✅ Type narrowing works correctly with switch/if statements

Test Plan

  • ✅ 186 Rust unit tests pass
  • ✅ TypeScript type check passes
  • All existing listenStream usage sites now have proper type inference
  • Event handlers correctly narrow types based on eventType checks

Breaking Changes

None for runtime behavior. Type-level only: code accessing non-existent event fields will now show TypeScript errors (which is the intended improvement).

Related

Complements PR #13 (appInvoke type safety) by adding type safety to the stream event system.

Type safety issue: listenStream used unsafe 'any' for event payloads, providing
no type checking for event structure or discriminated unions on eventType.

Changes:
- Define comprehensive StreamEvent type hierarchy covering all event types:
  - StreamDeltaEvent: text content deltas
  - StreamThinkingDeltaEvent/StreamThinkingSignatureEvent: thinking blocks
  - StreamContextCompactedEvent: context compaction notifications
  - StreamToolStartEvent/StreamToolDeltaEvent/StreamToolDoneEvent: tool lifecycle
  - StreamToolEvent: tool use/result events
  - StreamDoneEvent/StreamErrorEvent: stream termination
- Update listenStream signature to use StreamEventWrapper type
- Type event payloads as StreamEvent union for discriminated union support
- Match SessionContextCompaction structure for context compaction events

Benefits:
- Event structure is validated at compile time
- IDE autocomplete shows available fields for each eventType
- Discriminated unions prevent accessing wrong fields for event types
- Self-documenting: event structure is centrally defined
- Catches typos in eventType checks at compile time

Example before:
  listenStream(runId, (event) => {
    if (event.payload.eventType === 'delat') { // typo, no error
      console.log(event.payload.text); // wrong field, no error
    }
  });

Example after:
  listenStream(runId, (event) => {
    if (event.payload.eventType === 'delta') {
      console.log(event.payload.delta); // ✓ correct field
      console.log(event.payload.text);  // ✗ type error
    }
  });

Test results:
- ✅ 186 Rust unit tests pass
- ✅ TypeScript type check passes
@whitelonng whitelonng force-pushed the fix/06-listenstream-type-safety branch from 3b529a5 to 49f4ccb Compare June 21, 2026 14:30
The module-level code that auto-extracted tokens was causing unhandled
errors in CI environment during test cleanup. Token extraction is already
handled by getMobileToken() when called, so this redundant code can be
safely removed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant