Skip to content

[EPIC-D][LOU-D15] useLoushyAgent React hook over the typed event stream - #85

Merged
LinuxDevil merged 2 commits into
mainfrom
lou-d15-react-hook
Oct 1, 2026
Merged

LinuxDevil merged 2 commits into
mainfrom
lou-d15-react-hook

Conversation

@LinuxDevil

Copy link
Copy Markdown
Owner

What

New subpath @loushy/build-ai-agent/react (tsup entry src/react/index.ts, exports["./react"]):

  • useLoushyAgent(source, options?)
    • { agent, sessionId? }: runs in process. It calls agent.stream(), or, when sessionId is set, session.stream() on an agent.session({ id }) that the hook creates once per agent and id, so the conversation carries over between turns.
    • { url, headers?, fetch? }: runs remotely. It POSTs { input } and reads the SSE/NDJSON event stream that the docs/streaming.md server writes.
    • Returns messages (role, text, toolCalls with status/args/result/error), status (idle | streaming | awaiting-approval | error), pendingApproval, error, usage (from run.done), lastEvent, send(), stop(), approve(note?) and reject(note?).
    • stop() and unmount abort the run through an AbortSignal. When send() replaces a run that is still going, the old run's events are dropped.
    • Approvals: in process they go through agent.approvals.resolve(), and a run that pauses again exposes the next approval. Remote: with options.approvalsUrl the hook does POST ${approvalsUrl}/${id} and reads an ApprovalOutcome JSON response. Without it, approve()/reject() do nothing and the caller resolves the approval. This is documented.
  • reduceAgentEvents(state, event | action) is a pure, framework-neutral reducer (src/react/reducer.ts) that holds the logic, so the Vue/Svelte tickets can reuse it. Events from sub-agents only update lastEvent.
  • parseEventStream(response) accepts SSE data: framing and NDJSON. It skips comments, event:/id: lines and unknown events, handles chunk splits and CRLF, and cancels the body on break.
  • react is an optional peer (^18 || ^19). New devDependencies: react@18, @types/react, react-test-renderer@18, @types/react-test-renderer. react-test-renderer was picked because it needs no DOM; the hook test sets a // @vitest-environment node pragma and the global vitest config is unchanged.
  • Docs: new docs/react.md with a component (tsx), a type-checked Node server including the approvals route, an in-process custom hook and a framework-free reducer/parser snippet. It is linked from the README (new "UI bindings" feature line plus a docs table row), docs/api-overview.md and docs/streaming.md. llms files regenerated; CHANGELOG entry under Added.

Why

This closes an Epic D parity gap: eve ships useEveAgent and open-harness ships useOpenHarness, and we had no UI binding over our typed event stream.

Acceptance criteria

  • @loushy/build-ai-agent/react subpath (tsup entry, exports["./react"]). react is an optional peer ^18 || ^19. Dev deps added, with a per-file @vitest-environment pragma and no global vitest config change
  • useLoushyAgent(source, options?) with in-process (agent.stream() / session stream) and remote (POST { input } plus parseEventStream) sources and the full return shape
  • stop() aborts via AbortSignal, and unmount aborts too
  • Approvals: in process via agent.approvals.resolve(). Remote via POST ${approvalsUrl}/${id}, or exposed for the caller to resolve when no URL is given (documented)
  • Pure reduceAgentEvents exported and unit-tested without React
  • Tests: reducer over scripted sequences (text deltas, tool lifecycle, approval to status, run.done to idle plus usage, error). Hook in process with mockModel through createAgent (send, streamed text, idle). Plus sessions, approve/reject, remote SSE, remote approvals, stop/unmount. parseEventStream with SSE and NDJSON. Type test for the subpath's public types
  • docs/react.md with type-checked ts snippets, linked from api-overview and README; llms regenerated; CHANGELOG (Added)
  • No changes to src/execution/, src/createAgent.ts, src/context/, src/flows/, src/providers/

How verified

  • npx tsc --noEmit: passed
  • npm run lint: 0 errors, 433 warnings. src/react adds 0 warnings and no other linted source changed.
  • npm run test:types: 5 files, 23 tests passed. Type Errors: no errors
  • npx vitest run: 143 files passed; 2078 tests passed, 3 skipped. The new src/react suites have 21 tests.
  • npm run build && npm run build --workspace=packages/create-loushy-agent: passed. dist/react/index.mjs is about 9 KB and imports react as an external.
  • npm run docs:verify-snippets -- --skip-build: all 98 snippets type-check against src/, and 6 also run cleanly
  • npm run docs:llms:check: up to date
  • npm run test:coverage: 143 files passed, thresholds met (All files 89.93% statements)
  • npm run fallow: dead code reports "No issues found", 0 above the health threshold. The one existing duplicate clone group, between the Anthropic and OpenAI providers, is unrelated to this change.

🤖 Generated with Claude Code

https://claude.ai/code/session_016bY29ibQgv3MDdjAbUT4YR


Generated by Claude Code

claude added 2 commits October 1, 2026 14:09
New `@loushy/build-ai-agent/react` subpath:
- useLoushyAgent(source, options?): in process ({ agent, sessionId? } via
  agent.stream() / agent.session({ id }).stream()) or remote ({ url, headers?,
  fetch? }: POST { input }, read SSE or NDJSON events). Returns messages,
  status, pendingApproval, error, usage, lastEvent, send, stop, approve,
  reject. stop() and unmount abort via AbortSignal; approvals go through
  agent.approvals.resolve() in process or POST ${approvalsUrl}/${id} remotely.
- reduceAgentEvents(): pure, framework-neutral reducer holding the logic.
- parseEventStream(): SSE/NDJSON response parser.

react (^18 || ^19) is an optional peer; react, @types/react,
react-test-renderer and its types are devDependencies. Docs in
docs/react.md, linked from README and api-overview; llms files regenerated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bY29ibQgv3MDdjAbUT4YR
@LinuxDevil
LinuxDevil merged commit 8baa97c into main Oct 1, 2026
2 checks passed
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.

2 participants