Skip to content

growth(day-21): @byok-relay/react — React hooks for BYOK AI#59

Open
alokit-bot wants to merge 1 commit into
mainfrom
feat/growth-2026-06-30
Open

growth(day-21): @byok-relay/react — React hooks for BYOK AI#59
alokit-bot wants to merge 1 commit into
mainfrom
feat/growth-2026-06-30

Conversation

@alokit-bot

@alokit-bot alokit-bot commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator

Summary

GROWTH_PLAN Day 21@byok-relay/react npm package: React hooks that add drop-in BYOK AI to any React app.

What ships

packages/react/src/index.js — four hooks:

  • useByokRelay — core: relay token registration (auto-stored in localStorage), key storage/deletion/listing, logout
  • useChat — stateful chat with message history, supports openai/anthropic/groq/mistral/openrouter, systemPrompt and extraParams passthrough
  • useStreamingChat — SSE streaming with AbortController cancel, stopStreaming(), streamingContent live string
  • useRelayHealth — polls /health endpoint with refetch()

packages/react/package.json@byok-relay/react v1.0.0, peerDep react >=17, 14 npm keywords, repo + homepage + bugs fields.

packages/react/README.md — full hook API docs, quick-start examples, provider table, self-hosting note, Lovable/Bolt/Next.js/Vite compatibility note.

packages/react/test/hooks.test.js — 17 smoke tests, all passing.

llms.txt — React Hooks section (install + code snippets + npm URL). Also added MCP Server + OpenAPI spec sections.

README.md — React hooks section before "For AI coding agents" — install + three hook examples + link to packages/react.

Why this matters

  • Opens new npm discovery channel: @byok-relay/react alongside existing @byok-relay/mcp
  • Targets the primary audience directly: Lovable/Bolt/Vite/Next.js React apps
  • Zero friction: npm install @byok-relay/react + three lines of JSX
  • Streaming-first: useStreamingChat works with all providers out of the box
  • Natural follow-on to Day 20 (MCP server) — completes the "how do I use this from X" matrix for the two biggest audiences (AI agents via MCP; React devs via hooks)

Tests

17 tests: 17 passed, 0 failed

Metrics (2026-06-30)

stars=51 forks=0 views=18 clones=111

Next for Avi

cd packages/react && npm publish --access public

Summary by CodeRabbit

  • New Features

    • Added a new React hooks package for chat workflows, key storage, and relay health checks.
    • Introduced support for both streaming and non-streaming chat interactions.
    • Added setup guidance for React apps, including install and usage examples.
  • Documentation

    • Expanded the main README with React hooks quick-start information.
    • Added integration guidance for desktop/CLI clients and machine-readable API specs.
    • Published full package documentation for the React hooks API.
  • Tests

    • Added basic validation coverage for the new React package and its exports.

packages/react/src/index.js: four hooks — useByokRelay (token
registration + key CRUD + logout), useChat (stateful message list,
non-streaming, openai/anthropic/groq/mistral/openrouter support),
useStreamingChat (SSE streaming with AbortController, stopStreaming()),
useRelayHealth (polls /health endpoint).

packages/react/package.json: @byok-relay/react v1.0.0, peerDep react
>=17, 14 npm keywords (byok, ai, react, hooks, openai, anthropic, llm,
relay, frontend, lovable, bolt, no-backend, api-key, self-hosted).
packages/react/README.md: full hook API docs, quick-start examples,
provider table, self-hosting note, Lovable/Bolt compatibility note.
packages/react/test/hooks.test.js: 17 smoke tests, all passing.

llms.txt: React Hooks section (npm install, useChat + useStreamingChat
code snippet, hook list, npm URL). MCP Server section + OpenAPI spec
section added for completeness.

README.md: new React hooks section before 'For AI coding agents' —
install snippet, useChat/useStreamingChat/useByokRelay examples, link
to packages/react README.

Opens new npm discovery channel as @byok-relay/react. Targets
Lovable/Bolt/Vite/Next.js React ecosystem directly. Natural extension
of @byok-relay/mcp (Day 20). Next for Avi: npm publish --access public.
@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Introduces the @byok-relay/react npm package with four hooks (useByokRelay, useChat, useStreamingChat, useRelayHealth), a package.json, a Node smoke-test suite, and a full package README. Root README.md and llms.txt are updated with React, MCP, and OpenAPI integration sections.

Changes

@byok-relay/react package

Layer / File(s) Summary
Package manifest and routing constants
packages/react/package.json, packages/react/src/index.js
New package.json defines metadata, entry points, and React peer dependency. index.js sets the default relay URL, provider-to-path template map, React hook imports, and localStorage safe-access helpers.
Hook implementations
packages/react/src/index.js
Implements useByokRelay (localStorage-backed relay token, key CRUD via relay endpoints), useChat (non-streaming chat with provider-specific request bodies), useStreamingChat (SSE incremental parsing, AbortController cancellation), useRelayHealth (health polling), and internal content-extraction helpers.
Smoke tests
packages/react/test/hooks.test.js
Node-only test script stubs React, patches the module loader, and validates hook exports, localStorage-absent safety, per-provider initialization, health/streaming object construction, and package.json metadata fields.
Documentation
packages/react/README.md, README.md, llms.txt
Full package README with hook API reference, quick-start examples, provider table, self-hosting steps, and environment compatibility. Root README gains a React hooks section; llms.txt adds React, MCP server, and OpenAPI spec integration sections.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • avikalpg

Poem

🐇 A new hook package hops into view,
Keys stored safe, no backend to pursue.
Stream or chat, the relay obeys,
Anthropic, OpenAI — all in the maze.
Tests stub React and pass with a cheer,
@byok-relay/react is finally here! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding the new @byok-relay/react package with React hooks for BYOK AI workflows.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/growth-2026-06-30

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
llms.txt (1)

121-124: 🗄️ Data Integrity & Integration | 🟠 Major

License mismatch: package README says MIT, llms.txt says Apache 2.0.

The root project and llms.txt state Apache 2.0, but packages/react/README.md states MIT. Align the React package license with the root project, or clarify if the React package is intentionally under a different license.

- MIT — [byok-relay on GitHub](https://github.com/avikalpg/byok-relay)
+ Apache 2.0 — [byok-relay on GitHub](https://github.com/avikalpg/byok-relay)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@llms.txt` around lines 121 - 124, The project license text is inconsistent
across docs, with the React package README saying MIT while the root and
llms.txt indicate Apache 2.0. Update the React package’s license statement in
its README to match the root project, or explicitly document that the React
package is intentionally licensed differently; keep the “License” section in
llms.txt and the package README aligned.
🧹 Nitpick comments (3)
packages/react/README.md (2)

66-89: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add error handling to the key storage example.

The handleSave async function lacks try/catch, so storeKey failures will throw unhandled. Also, isRegistered is destructured but unused — remove it or use it to gate the form.

   const { storeKey } = useByokRelay({ appId: 'my-app' });
   const [key, setKey] = React.useState('');
   const [saved, setSaved] = React.useState(false);
+  const [saveError, setSaveError] = React.useState(null);
 
   async function handleSave() {
+    setSaveError(null);
+    try {
       await storeKey('openai', key);
       setSaved(true);
+    } catch (e) {
+      setSaveError(e.message);
+    }
   }
 
   return (
     <div>
       <input
         type="password"
         placeholder="sk-…"
         value={key}
         onChange={e => setKey(e.target.value)}
       />
       <button onClick={handleSave}>{saved ? '✅ Saved' : 'Save key'}</button>
+      {saveError && <p className="error">{saveError}</p>}
       <p>Your key is encrypted and stored on the relay. It never appears in your code.</p>
     </div>
   );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/react/README.md` around lines 66 - 89, The ApiKeyForm example’s
handleSave currently awaits storeKey without any error handling, so update it to
catch and surface failures instead of letting them go unhandled; also address
the unused isRegistered value from useByokRelay by either removing it from the
destructuring or using it to conditionally render/disable the form. Keep the fix
localized to ApiKeyForm and the handleSave flow.

44-48: 📐 Maintainability & Code Quality | 🔵 Trivial

Avoid using array index as React key.

Using key={i} can cause rendering issues with dynamic lists. Since useChat appends messages, consider using a stable identifier if the message shape includes an id, or document that consumers should generate IDs.

-        {messages.map((m, i) => (
-          <div key={i} className={`msg ${m.role}`}>{m.content}</div>
+        {messages.map((m, i) => (
+          <div key={m.id || i} className={`msg ${m.role}`}>{m.content}</div>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/react/README.md` around lines 44 - 48, The message list example is
using the array index as the React key, which can break rendering when `useChat`
adds or reorders messages. Update the `messages.map(...)` example to use a
stable identifier from the message object, such as `m.id`, if available, and
keep the `msg` rendering logic in sync with that shape. If the message type in
this README does not include an `id`, adjust the example or docs to explicitly
state that consumers should provide stable IDs for each message.
packages/react/package.json (1)

21-25: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Drop the module field or publish an ESM entry packages/react only ships a CommonJS src/index.js, so module doesn’t advertise a separate ESM build. Remove it for now, or point it at a real ESM wrapper/build.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/react/package.json` around lines 21 - 25, The package metadata in
the react package is advertising an ESM entry via the module field even though
src/index.js is the only CommonJS entry. Update the package.json for the react
package by either removing the module field entirely or changing it to point at
a real ESM build/wrapper, and keep the main and exports entries consistent with
the actual shipped format.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@llms.txt`:
- Around line 115-120: The OpenAPI links in llms.txt are broken and currently
point to endpoints that return 404, so update the JSON and YAML spec URLs to the
correct reachable locations used by the OpenAPI server. Make sure the listed
URLs for the OpenAPI spec are valid and fetchable by agents/tools, and verify
both the JSON and YAML entries resolve successfully.

In `@packages/react/README.md`:
- Around line 209-219: The Providers table in the README is missing the `google`
provider entry even though `packages/react/src/index.js` accepts it; update the
table to include a `google` row alongside the existing provider values, using
the same naming/style as the other entries so the documentation matches the
supported options.

In `@packages/react/src/index.js`:
- Around line 21-28: The PROVIDER_PATHS mapping in index.js includes google even
though the request/response flow only supports Anthropic and OpenAI-style
adapters. Remove the google entry from PROVIDER_PATHS, or alternatively add
matching Google-specific request body construction and response extraction in
the same adapter path before keeping it exposed. Use the PROVIDER_PATHS constant
and the provider handling logic around request construction/extraction to locate
the change.
- Line 183: `_useByokRelayInternal`, `useChat`, and `useStreamingChat` are
keeping separate in-memory token state, so a hook can auto-register a second
token before `useByokRelay.register()` or `storeKey()` has written localStorage.
Update `getToken()` in `_useByokRelayInternal` to re-read the persisted relay
token before deciding to register, or move the token source to shared state used
by all hooks so they observe the same token immediately.
- Around line 397-418: The useRelayHealth hook currently only fetches once on
mount, which does not match the polling behavior described by its objective.
Either add real polling inside useRelayHealth by setting up an interval in the
useEffect with proper cleanup, or rename/update the hook’s documentation and
behavior to clearly indicate it is a one-shot health fetch. Keep the refetch
callback and useEffect as the main locations to adjust.
- Around line 188-234: The send flow in sendMessage is building nextMessages
from the captured messages array, which can go stale when requests overlap and
cause lost history; update the chat append/send logic to use the latest message
state at send time instead of a closure-captured snapshot. Refactor sendMessage
and the other send handler mentioned in the same component to read from a shared
ref or a functional state update path so both append the user message and send
the full current history consistently.
- Around line 340-354: The SSE parser in the streaming loop is splitting each
decoded chunk independently in the React index logic, so JSON frames that span
multiple network chunks get dropped as malformed. Update the stream handling
around decoder.decode and _extractStreamDelta to keep a carry-over buffer
between reads, parse complete data: lines from that buffer, and preserve any
trailing partial line for the next chunk. Also make sure [DONE] exits the outer
read loop, not just the per-chunk line loop, so streaming stops immediately when
the sentinel arrives.

---

Outside diff comments:
In `@llms.txt`:
- Around line 121-124: The project license text is inconsistent across docs,
with the React package README saying MIT while the root and llms.txt indicate
Apache 2.0. Update the React package’s license statement in its README to match
the root project, or explicitly document that the React package is intentionally
licensed differently; keep the “License” section in llms.txt and the package
README aligned.

---

Nitpick comments:
In `@packages/react/package.json`:
- Around line 21-25: The package metadata in the react package is advertising an
ESM entry via the module field even though src/index.js is the only CommonJS
entry. Update the package.json for the react package by either removing the
module field entirely or changing it to point at a real ESM build/wrapper, and
keep the main and exports entries consistent with the actual shipped format.

In `@packages/react/README.md`:
- Around line 66-89: The ApiKeyForm example’s handleSave currently awaits
storeKey without any error handling, so update it to catch and surface failures
instead of letting them go unhandled; also address the unused isRegistered value
from useByokRelay by either removing it from the destructuring or using it to
conditionally render/disable the form. Keep the fix localized to ApiKeyForm and
the handleSave flow.
- Around line 44-48: The message list example is using the array index as the
React key, which can break rendering when `useChat` adds or reorders messages.
Update the `messages.map(...)` example to use a stable identifier from the
message object, such as `m.id`, if available, and keep the `msg` rendering logic
in sync with that shape. If the message type in this README does not include an
`id`, adjust the example or docs to explicitly state that consumers should
provide stable IDs for each message.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ae6d2781-c995-4d6b-ac40-6d534c0a27f6

📥 Commits

Reviewing files that changed from the base of the PR and between f0e1a43 and 7c9c297.

📒 Files selected for processing (6)
  • README.md
  • llms.txt
  • packages/react/README.md
  • packages/react/package.json
  • packages/react/src/index.js
  • packages/react/test/hooks.test.js

Comment thread llms.txt
Comment on lines +115 to +120
## OpenAPI spec

Machine-readable API spec (for agents/tools/code-gen):
- JSON: https://relay.byokrelay.com/openapi.json
- YAML: https://relay.byokrelay.com/openapi.yaml

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if OpenAPI endpoints are accessible
curl -sI https://relay.byokrelay.com/openapi.json | head -3
curl -sI https://relay.byokrelay.com/openapi.yaml | head -3

Repository: avikalpg/byok-relay

Length of output: 243


🏁 Script executed:

#!/bin/bash
set -euo pipefail

for url in https://relay.byokrelay.com/openapi.json https://relay.byokrelay.com/openapi.yaml; do
  echo "== $url =="
  curl -sS -D - -o /tmp/body.$$ "$url" | sed -n '1,12p'
  echo "-- body --"
  head -20 /tmp/body.$$
  echo
done
rm -f /tmp/body.$$

Repository: avikalpg/byok-relay

Length of output: 1201


Fix the OpenAPI links https://relay.byokrelay.com/openapi.json and https://relay.byokrelay.com/openapi.yaml return 404s (Cannot GET ...), so agents can’t fetch the spec from llms.txt.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@llms.txt` around lines 115 - 120, The OpenAPI links in llms.txt are broken
and currently point to endpoints that return 404, so update the JSON and YAML
spec URLs to the correct reachable locations used by the OpenAPI server. Make
sure the listed URLs for the OpenAPI spec are valid and fetchable by
agents/tools, and verify both the JSON and YAML entries resolve successfully.

Comment thread packages/react/README.md
Comment on lines +209 to +219
## Providers

| `provider` value | Provider |
|---------|----------|
| `openai` | OpenAI (GPT-4o, GPT-4, …) |
| `anthropic` | Anthropic (Claude 3.5, …) |
| `groq` | Groq |
| `mistral` | Mistral AI |
| `openrouter` | OpenRouter (200+ models) |
| any string | Passed as-is to the relay path |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check PROVIDER_PATHS definition in implementation
rg -A 10 'PROVIDER_PATHS' packages/react/src/index.js

Repository: avikalpg/byok-relay

Length of output: 1193


Add the missing google provider row. packages/react/src/index.js already accepts google, but the README table only lists 5 providers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/react/README.md` around lines 209 - 219, The Providers table in the
README is missing the `google` provider entry even though
`packages/react/src/index.js` accepts it; update the table to include a `google`
row alongside the existing provider values, using the same naming/style as the
other entries so the documentation matches the supported options.

Comment on lines +21 to +28
const PROVIDER_PATHS = {
openai: 'chat/completions',
anthropic: 'messages',
google: 'models/{model}:generateContent',
groq: 'chat/completions',
mistral: 'chat/completions',
openrouter: 'chat/completions',
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Remove google until it has matching adapters.

google is routed in PROVIDER_PATHS, but request construction and response extraction only special-case Anthropic; all other providers use OpenAI-style messages and choices. Either remove this entry or add Google-specific body and extraction logic before exposing it.

Also applies to: 452-464

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/react/src/index.js` around lines 21 - 28, The PROVIDER_PATHS mapping
in index.js includes google even though the request/response flow only supports
Anthropic and OpenAI-style adapters. Remove the google entry from
PROVIDER_PATHS, or alternatively add matching Google-specific request body
construction and response extraction in the same adapter path before keeping it
exposed. Use the PROVIDER_PATHS constant and the provider handling logic around
request construction/extraction to locate the change.

systemPrompt,
extraParams = {},
} = {}) {
const { getToken, error: relayError } = _useByokRelayInternal({ relayUrl, appId });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Share or refresh the relay token across hooks.

useChat/useStreamingChat mount their own internal token state. If they mount before useByokRelay.register()/storeKey() writes localStorage, getToken() still sees its initial null state and registers a second token, so chat can run under a token without the stored key. Re-read storage before auto-registering, or centralize token state.

Minimal direction
-  const getToken = useCallback(async () => token || register(), [token, register]);
+  const getToken = useCallback(async () => {
+    const stored = storageGet(tokenKey);
+    if (stored) {
+      if (stored !== token) setToken(stored);
+      return stored;
+    }
+    return token || register();
+  }, [token, tokenKey, register]);

Also applies to: 269-269, 426-449

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/react/src/index.js` at line 183, `_useByokRelayInternal`, `useChat`,
and `useStreamingChat` are keeping separate in-memory token state, so a hook can
auto-register a second token before `useByokRelay.register()` or `storeKey()`
has written localStorage. Update `getToken()` in `_useByokRelayInternal` to
re-read the persisted relay token before deciding to register, or move the token
source to shared state used by all hooks so they observe the same token
immediately.

Comment on lines +188 to +234
const sendMessage = useCallback(async (content) => {
setError(null);
const userMsg = { role: 'user', content };
const nextMessages = [...messages, userMsg];
setMessages(nextMessages);
setIsLoading(true);

try {
const t = await getToken();
const path = PROVIDER_PATHS[provider] || 'chat/completions';
const resolvedPath = path.replace('{model}', model);

let body;
if (provider === 'anthropic') {
body = {
model,
max_tokens: 1024,
messages: nextMessages,
...(systemPrompt ? { system: systemPrompt } : {}),
...extraParams,
};
} else {
body = {
model,
messages: [
...(systemPrompt ? [{ role: 'system', content: systemPrompt }] : []),
...nextMessages,
],
...extraParams,
};
}

const res = await fetch(`${relayUrl}/relay/${provider}/${resolvedPath}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'x-relay-token': t },
body: JSON.stringify(body),
});

if (!res.ok) {
const errBody = await res.json().catch(() => ({}));
throw new Error(errBody.error || `Request failed (${res.status})`);
}

const data = await res.json();
const assistantContent = _extractContent(data, provider);
const assistantMsg = { role: 'assistant', content: assistantContent };
setMessages(prev => [...prev, assistantMsg]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Avoid building message history from a stale closure.

Both send handlers derive nextMessages from captured messages. Overlapping sends before React re-renders can drop earlier user messages and send incomplete history. Track latest messages in a ref or use a single functional updater path for append/send history.

Also applies to: 281-358

🧰 Tools
🪛 ast-grep (0.44.0)

[warning] 191-191: Avoid using the initial state variable in setState
Context: setMessages(nextMessages)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(setstate-same-var)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/react/src/index.js` around lines 188 - 234, The send flow in
sendMessage is building nextMessages from the captured messages array, which can
go stale when requests overlap and cause lost history; update the chat
append/send logic to use the latest message state at send time instead of a
closure-captured snapshot. Refactor sendMessage and the other send handler
mentioned in the same component to read from a shared ref or a functional state
update path so both append the user message and send the full current history
consistently.

Source: Linters/SAST tools

Comment on lines +340 to +354
const chunk = decoder.decode(value, { stream: true });
const lines = chunk.split('\n');

for (const line of lines) {
if (!line.startsWith('data: ')) continue;
const raw = line.slice(6).trim();
if (raw === '[DONE]') break;
try {
const delta = _extractStreamDelta(JSON.parse(raw), provider);
if (delta) {
full += delta;
setStreamingContent(full);
}
} catch { /* skip malformed chunks */ }
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Buffer SSE lines across chunks.

chunk.split('\n') parses each network chunk independently, so JSON split across chunks is discarded as malformed. Keep a carry-over buffer and exit the outer loop on [DONE].

Parser direction
+      let buffer = '';
+      let finished = false;
       while (true) {
         const { done, value } = await reader.read();
-        if (done) break;
+        if (done) break;
 
-        const chunk = decoder.decode(value, { stream: true });
-        const lines = chunk.split('\n');
+        buffer += decoder.decode(value, { stream: true });
+        const lines = buffer.split(/\r?\n/);
+        buffer = lines.pop() ?? '';
 
         for (const line of lines) {
           if (!line.startsWith('data: ')) continue;
           const raw = line.slice(6).trim();
-          if (raw === '[DONE]') break;
+          if (raw === '[DONE]') { finished = true; break; }
           try {
             const delta = _extractStreamDelta(JSON.parse(raw), provider);
             if (delta) {
               full += delta;
               setStreamingContent(full);
             }
           } catch { /* skip malformed chunks */ }
         }
+        if (finished) break;
       }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const chunk = decoder.decode(value, { stream: true });
const lines = chunk.split('\n');
for (const line of lines) {
if (!line.startsWith('data: ')) continue;
const raw = line.slice(6).trim();
if (raw === '[DONE]') break;
try {
const delta = _extractStreamDelta(JSON.parse(raw), provider);
if (delta) {
full += delta;
setStreamingContent(full);
}
} catch { /* skip malformed chunks */ }
}
let buffer = '';
let finished = false;
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split(/\r?\n/);
buffer = lines.pop() ?? '';
for (const line of lines) {
if (!line.startsWith('data: ')) continue;
const raw = line.slice(6).trim();
if (raw === '[DONE]') { finished = true; break; }
try {
const delta = _extractStreamDelta(JSON.parse(raw), provider);
if (delta) {
full += delta;
setStreamingContent(full);
}
} catch { /* skip malformed chunks */ }
}
if (finished) break;
}
🧰 Tools
🪛 ast-grep (0.44.0)

[warning] 350-350: Avoid using the initial state variable in setState
Context: setStreamingContent(full)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(setstate-same-var)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/react/src/index.js` around lines 340 - 354, The SSE parser in the
streaming loop is splitting each decoded chunk independently in the React index
logic, so JSON frames that span multiple network chunks get dropped as
malformed. Update the stream handling around decoder.decode and
_extractStreamDelta to keep a carry-over buffer between reads, parse complete
data: lines from that buffer, and preserve any trailing partial line for the
next chunk. Also make sure [DONE] exits the outer read loop, not just the
per-chunk line loop, so streaming stops immediately when the sentinel arrives.

Comment on lines +397 to +418
function useRelayHealth({ relayUrl = DEFAULT_RELAY_URL, deep = false } = {}) {
const [data, setData] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const [status, setStatus] = useState(null); // 'ok' | 'error' | null

const refetch = useCallback(async () => {
setIsLoading(true);
try {
const url = deep ? `${relayUrl}/health?deep=1` : `${relayUrl}/health`;
const res = await fetch(url);
const body = await res.json();
setData(body);
setStatus(res.ok ? 'ok' : 'error');
} catch {
setStatus('error');
setData(null);
} finally {
setIsLoading(false);
}
}, [relayUrl, deep]);

useEffect(() => { refetch(); }, [refetch]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Implement actual health polling or rename the behavior.

The hook comment/objective says it polls /health, but the effect only calls refetch() once on mount. Add an interval with cleanup, or document this as one-shot health fetching.

🧰 Tools
🪛 ast-grep (0.44.0)

[warning] 407-407: Avoid using the initial state variable in setState
Context: setData(body)
Note: [CWE-710] Improper Adherence to Coding Standards. Security best practice.

(setstate-same-var)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/react/src/index.js` around lines 397 - 418, The useRelayHealth hook
currently only fetches once on mount, which does not match the polling behavior
described by its objective. Either add real polling inside useRelayHealth by
setting up an interval in the useEffect with proper cleanup, or rename/update
the hook’s documentation and behavior to clearly indicate it is a one-shot
health fetch. Keep the refetch callback and useEffect as the main locations to
adjust.

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