growth(day-21): @byok-relay/react — React hooks for BYOK AI#59
growth(day-21): @byok-relay/react — React hooks for BYOK AI#59alokit-bot wants to merge 1 commit into
Conversation
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.
📝 WalkthroughWalkthroughIntroduces the Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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 | 🟠 MajorLicense mismatch: package README says MIT, llms.txt says Apache 2.0.
The root project and
llms.txtstate Apache 2.0, butpackages/react/README.mdstates 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 winAdd error handling to the key storage example.
The
handleSaveasync function lacks try/catch, sostoreKeyfailures will throw unhandled. Also,isRegisteredis 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 | 🔵 TrivialAvoid using array index as React
key.Using
key={i}can cause rendering issues with dynamic lists. SinceuseChatappends messages, consider using a stable identifier if the message shape includes anid, 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 winDrop the
modulefield or publish an ESM entrypackages/reactonly ships a CommonJSsrc/index.js, somoduledoesn’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
📒 Files selected for processing (6)
README.mdllms.txtpackages/react/README.mdpackages/react/package.jsonpackages/react/src/index.jspackages/react/test/hooks.test.js
| ## 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 | ||
|
|
There was a problem hiding this comment.
📐 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 -3Repository: 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.
| ## 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 | | ||
|
|
There was a problem hiding this comment.
📐 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.jsRepository: 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.
| const PROVIDER_PATHS = { | ||
| openai: 'chat/completions', | ||
| anthropic: 'messages', | ||
| google: 'models/{model}:generateContent', | ||
| groq: 'chat/completions', | ||
| mistral: 'chat/completions', | ||
| openrouter: 'chat/completions', | ||
| }; |
There was a problem hiding this comment.
🎯 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 }); |
There was a problem hiding this comment.
🗄️ 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.
| 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]); |
There was a problem hiding this comment.
🎯 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
| 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 */ } | ||
| } |
There was a problem hiding this comment.
🎯 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.
| 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.
| 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]); |
There was a problem hiding this comment.
🎯 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.
Summary
GROWTH_PLAN Day 21 —
@byok-relay/reactnpm 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, logoutuseChat— stateful chat with message history, supports openai/anthropic/groq/mistral/openrouter, systemPrompt and extraParams passthroughuseStreamingChat— SSE streaming with AbortController cancel,stopStreaming(),streamingContentlive stringuseRelayHealth— polls/healthendpoint withrefetch()packages/react/package.json—@byok-relay/reactv1.0.0, peerDepreact >=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 topackages/react.Why this matters
@byok-relay/reactalongside existing@byok-relay/mcpnpm install @byok-relay/react+ three lines of JSXuseStreamingChatworks with all providers out of the boxTests
Metrics (2026-06-30)
stars=51 forks=0 views=18 clones=111
Next for Avi
Summary by CodeRabbit
New Features
Documentation
Tests