@perstack/react
v0.0.72
Published
React hooks and utilities for Perstack integration
Downloads
2,476
Readme
@perstack/react
Reusable React hooks and utilities for Perstack integration. This is the shared React library layer that provides framework-agnostic hooks consumed by both TUI and web applications.
Installation
bun add @perstack/reactUsage
useRun
The main hook for managing Perstack run state. It processes events into:
- activities - Accumulated activities from RunEvent (append-only)
- streaming - Current streaming state for real-time display
import { useRun } from "@perstack/react"
function ExpertRunner() {
const { activities, streaming, isComplete, addEvent, appendHistoricalEvents } = useRun()
// Add events from your event source
useEffect(() => {
const eventSource = new EventSource("/api/events")
eventSource.onmessage = (e) => {
addEvent(JSON.parse(e.data))
}
return () => eventSource.close()
}, [addEvent])
return (
<div>
{/* Show streaming content (grouped by run for parallel execution) */}
{Object.entries(streaming.runs).map(([runId, run]) => (
run.isReasoningActive && (
<div key={runId}>[{run.expertKey}] Reasoning: {run.reasoning}</div>
)
))}
{/* Show accumulated activities */}
<ActivityLog activities={activities} />
{isComplete && <div>Run complete!</div>}
</div>
)
}useJobStream
Stream events for a single job. Wraps useRun with automatic stream connection management.
import { useJobStream } from "@perstack/react"
function JobViewer({ jobId }: { jobId: string }) {
const { activities, streaming, latestActivity, isConnected, error } = useJobStream({
jobId,
connect: (jobId, signal) => fetchStream(`/api/jobs/${jobId}/events`, signal),
})
return (
<div>
{isConnected && <span>Connected</span>}
{error && <span>Error: {error.message}</span>}
<ActivityLog activities={activities} />
</div>
)
}useJobStreams
Track multiple jobs simultaneously with lightweight summaries (latest activity only).
import { useJobStreams } from "@perstack/react"
function JobList({ jobIds }: { jobIds: string[] }) {
const states = useJobStreams({
jobs: jobIds.map((id) => ({ id, enabled: true })),
connect: (jobId, signal) => fetchStream(`/api/jobs/${jobId}/events`, signal),
})
return (
<ul>
{jobIds.map((id) => {
const state = states.get(id)
return (
<li key={id}>
{id}: {state?.isConnected ? "Connected" : "Disconnected"}
{state?.latestActivity && ` - ${state.latestActivity.type}`}
</li>
)
})}
</ul>
)
}Utility Functions
For advanced use cases, you can use the utility functions directly:
import {
createInitialActivityProcessState,
processRunEventToActivity,
toolToActivity,
groupActivitiesByRun,
} from "@perstack/react"
// Create processing state
const state = createInitialActivityProcessState()
// Process RunEvent into Activity
const activities = []
processRunEventToActivity(state, event, (activity) => activities.push(activity))
// Group activities by run ID
const grouped = groupActivitiesByRun(activities)API
useRun()
Returns an object with:
activities: Array ofActivityOrGrouprepresenting completed actions (append-only)streaming: CurrentStreamingStatefor real-time displayisComplete: Whether the run is completeeventCount: Total number of processed eventsaddEvent(event): Add a new event to processappendHistoricalEvents(events): Bulk load historical eventsclearStreaming(): Clear streaming state
Note: Activities are append-only and never cleared. This is required for compatibility with Ink's <Static> component.
useJobStream(options)
Streams events for a single job. Parameters:
jobId: Job ID to stream (ornullto disable)connect:StreamConnectorfunction(jobId, signal) => AsyncIterable<PerstackEvent>enabled: Whether to connect (default:true)
Returns:
activities: Array ofActivityOrGroupstreaming: CurrentStreamingStatelatestActivity: Most recent activity (ornull)isConnected: Whether the stream is activeerror: Connection error (ornull)
useJobStreams(options)
Tracks multiple jobs with lightweight summaries. Parameters:
jobs: Array of{ id: string; enabled: boolean }connect:StreamConnectorfunction
Returns a Map<string, JobStreamSummary> where each summary contains:
latestActivity: Most recent activity for the jobisConnected: Whether the stream is active
Types
StreamingState
Real-time streaming state, organized by run ID to support parallel execution:
type PerRunStreamingState = {
expertKey: string
reasoning?: string
runResult?: string
isReasoningActive?: boolean
isRunResultActive?: boolean
}
type StreamingState = {
runs: Record<string, PerRunStreamingState>
}When multiple experts run in parallel (e.g., parallel delegation), each run's streaming content is tracked separately by its runId.
License
Apache-2.0
