growth(day-25): @byok-relay/angular — Angular injectable services for BYOK AI#63
growth(day-25): @byok-relay/angular — Angular injectable services for BYOK AI#63alokit-bot wants to merge 2 commits into
Conversation
packages/solid/src/index.js: four stores with SolidJS signal contract ([getter,setter]): - createByokRelayStore: token registration + key CRUD + logout (localStorage persistence, SolidStart SSR-safe via typeof window guard) - createChatStore: stateful message list, non-streaming, openai/anthropic/groq/mistral/openrouter, systemPrompt + extraParams - createStreamingChatStore: SSE streaming with AbortController, stopStreaming(), streamingContent() live signal, partial-commit on abort - createRelayHealthStore: polls /health on configurable interval, refetch(), destroy() for onCleanup() lifecycle management Signal shim: all stores work without solid-js installed (signals degrade to plain getter/setter pairs for testing and non-Solid environments). 20 smoke tests passing (node test/stores.test.js). packages/solid/README.md: full API docs, SolidJS quick-start, SolidStart SSR guide, all four stores documented, provider table, self-hosting section, related packages table. llms.txt: SolidJS Signals section + npm link. README.md: SolidJS stores section before 'For AI coding agents' with install + streaming example + links to other framework packages. package.json: workspaces added. metrics/daily.jsonl: 2026-07-03 snapshot (stars=52 forks=0 watchers=1 clones=110 views=20). Next for Avi: cd packages/solid && npm publish --access public
… BYOK AI packages/angular/src/index.js: four Angular injectable services: - ByokRelayService (token registration + key CRUD + logout, localStorage persistence, Analog SSR-safe in-memory fallback) - ChatService (stateful message list, non-streaming, openai/anthropic/groq/mistral/openrouter, systemPrompt + extraParams, rollback on error) - StreamingChatService (SSE streaming with AbortController, stopStreaming(), streamingContent() live signal, partial-commit on abort) - RelayHealthService (polls /health, check(deep=true) for upstream ping, destroy() for ngOnDestroy cleanup) Angular signal shim: services use Angular 16+ signal() when @angular/core is detected; plain getter/setter shim otherwise — same reactive API in both cases. provideByokRelay(config): makeEnvironmentProviders() with all four services wired together via deps when @angular/core is installed; plain createByokRelayBundle() otherwise (for testing and standalone use). createByokRelayBundle(config): factory function for standalone / non-Angular usage (Analog islands, unit tests, vanilla JS). 29 smoke tests passing (node test/services.test.js, no DOM, no @angular/core required): - ByokRelayService: 12 tests (register, getOrRegister, storeKey, listKeys, deleteKey, rotateKey, logout, error signals) - ChatService: 5 tests (send, rollback, clear) - StreamingChatService: 4 tests (stream, stopStreaming partial-commit) - RelayHealthService: 5 tests (check, deep check, error, polling lifecycle) - createByokRelayBundle: 2 tests (service wiring) packages/angular/README.md: full API docs, Angular 14+ quick-start, Angular 16+ signals note, Analog SSR guide, provider table, self-hosting section, related packages table. llms.txt: Angular Injectable Services section + npm link. README.md: Angular services section after SolidJS stores. package.json: workspaces already includes packages/*. metrics/daily.jsonl: 2026-07-04 snapshot (stars=52 forks=0 watchers=1 clones=112 views=22). Next for Avi: cd packages/angular && npm publish --access public
📝 WalkthroughWalkthroughAdds two new frontend integration packages, ChangesAngular package
Solid package
Root docs and workspace config
Estimated code review effort: 3 (Moderate) | ~30 minutes Sequence Diagram(s)sequenceDiagram
participant Component
participant ChatService
participant ByokRelayService
participant RelayAPI
Component->>ByokRelayService: getOrRegister(appId)
ByokRelayService->>RelayAPI: POST /users
RelayAPI-->>ByokRelayService: token
Component->>ChatService: sendMessage(content)
ChatService->>RelayAPI: POST /relay/{provider}
RelayAPI-->>ChatService: assistant reply
ChatService-->>Component: updated messages
sequenceDiagram
participant Component
participant StreamingChatStore
participant RelayAPI
Component->>StreamingChatStore: streamMessage(content)
StreamingChatStore->>RelayAPI: POST /relay/{provider} (SSE)
RelayAPI-->>StreamingChatStore: data delta chunks
StreamingChatStore-->>Component: streamingContent updates
StreamingChatStore->>StreamingChatStore: commit assistant message
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 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: 16
🧹 Nitpick comments (1)
packages/solid/src/index.js (1)
57-88: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
createStoreis unused dead code.No store factory in this file calls
createStore, and it isn't exported. Combined with the shim issue above, it's effectively inert. Consider removing it until it's actually wired up, or export it if intended for consumer use.🤖 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/solid/src/index.js` around lines 57 - 88, The createStore helper is currently dead code because nothing in index.js uses it and it is not exported. Remove the unused createStore function and its related shim logic if it is not meant for consumers, or export it from the module if it is intended to be part of the public API; use the createStore symbol and the setState/_subscribe implementation to locate the block.
🤖 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 `@packages/angular/README.md`:
- Around line 204-211: The Google provider row in the Angular README documents a
provider that is not yet functional end-to-end. Update the table entry
referenced by the Angular README so it is removed or clearly marked as coming
soon until the provider path and Gemini-specific handling are implemented in the
Angular client, especially in the provider routing logic and the
ChatService/StreamingChatService flow.
In `@packages/angular/src/index.js`:
- Line 270: `ChatService.sendMessage` and `StreamingChatService.streamMessage`
are reaching into `ByokRelayService` private fields, which breaks encapsulation
and creates fragile dependencies. Update these call sites to use the public
accessors on `this._relay` instead of directly reading `_storage`,
`_storageKey`, or `_relayUrl`; specifically, replace token retrieval with
`token()` and URL access with `relayUrl` so the relay implementation can change
without silently breaking these services.
- Around line 262-268: Add early input validation in ChatService.sendMessage and
StreamingChatService.streamMessage so empty or whitespace-only content is
rejected before any history update or network call. Mirror the Solid store
contract by checking content.trim() and throwing a clear “Message content is
required” error, and keep the existing relay token validation in the same
preflight path so both methods fail fast using the shared
sendMessage/streamMessage flow.
- Around line 507-526: The check() method in the health polling flow is clearing
the last known good value on transient failures. Update the catch block in
check() so it sets _error from err and rethrows, but does not call
_status.set(null); keep the previous status intact and only replace it when
fetch/json succeeds.
- Around line 262-296: The Angular ByokRelayService.sendMessage and
ByokRelayService.streamMessage paths still build Anthropic requests like OpenAI,
so add a provider-specific Anthropic branch mirroring the Solid implementation:
keep systemPrompt as a top-level system field instead of injecting it into
messages, and ensure max_tokens is always set (with a sensible default) before
calling fetch. Update the request body construction in both sendMessage and
streamMessage to use the Anthropic chat payload shape while preserving the
existing OpenAI path for other providers.
- Around line 584-591: The BYOK relay config token is created inside
provideByokRelay() but never made usable, so nothing can inject it. Update
provideByokRelay() to either include RELAY_CONFIG_TOKEN in the providers
returned by makeEnvironmentProviders(...) or export/reuse it at module scope so
consumers can inject BYOK_RELAY_CONFIG. Keep the fix centered on the
provideByokRelay and RELAY_CONFIG_TOKEN symbols so the token is not recreated
and discarded on each call.
- Around line 412-437: The SSE handling in the streaming reader does not stop
the outer read loop when the terminal [DONE] event is seen. Update the logic in
the reader/decoder loop so that the [DONE] signal from the payload parsing
inside the line-processing block causes the surrounding while loop in the
streaming function to exit as well, either by tracking a completion flag or
cancelling the reader; use the existing reader, decoder, leftover, and onChunk
flow to locate the fix.
- Around line 29-36: The Google relay path is currently stored with an unfilled
{model} placeholder, so ChatService and StreamingChatService will build an
invalid URL for provider: 'google'. Update PROVIDER_PATHS and the URL
construction logic in the ChatService/StreamingChatService flow so the model
value is interpolated before the request is sent, or remove the broken google
entry if it is not supported. Use the PROVIDER_PATHS constant and the
ChatService/StreamingChatService path-building code to locate the fix.
- Around line 584-618: provideByokRelay currently assumes
makeEnvironmentProviders is available, which makes the Angular 14 path return an
invalid providers shape via createByokRelayBundle(config). Update
provideByokRelay to either only use makeEnvironmentProviders when Angular 15+ is
guaranteed, or return an Angular 14-compatible provider array/object; use the
provideByokRelay helper and the ByokRelayService, ChatService,
StreamingChatService, and RelayHealthService registrations as the entry point
for the fix.
In `@packages/solid/README.md`:
- Around line 198-208: The README’s Supported providers table incorrectly
advertises Google Gemini as available even though `src/index.js` has no `google`
provider handling in the store logic. Update the provider table in the README to
either remove the `google` row or clearly mark it as coming soon/unsupported
until `createStore`, request handling, and response parsing in `src/index.js`
are implemented for Gemini.
- Around line 30-80: The SolidJS Quick Start example uses <Show> and <For>
without bringing them into scope, so the sample will fail when copied. Update
the README example near App and its imports to include the Solid control-flow
helpers alongside createSignal from solid-js, and ensure the JSX references to
Show and For match those imports so the snippet is directly runnable.
In `@packages/solid/src/index.js`:
- Around line 24-31: `google` is listed as a supported provider in
`PROVIDER_PATHS`, but `sendMessage` and `extractDelta` never handle Gemini’s
request/response shape. Either add proper Google/Gemini support in the
`sendMessage` flow and response parsing (using `contents` and `candidates`), or
remove `google` from `PROVIDER_PATHS` and update the README/provider table so it
is not advertised as supported. Make sure the behavior in the `sendMessage` and
`extractDelta` paths matches the documented provider set.
- Around line 113-121: The SSE parsing in parseSSE is dropping payloads when a
JSON line is split across reader.read() chunks because each chunk is split
independently and malformed fragments are silently skipped. Update parseSSE to
retain a trailing incomplete line buffer between calls, only attempt JSON.parse
on complete data: lines, and flush the buffered remainder once the final chunk
arrives. Use the parseSSE generator and its current chunk/line handling as the
place to add the carry-over logic, and apply the same buffering fix anywhere the
same parser logic is duplicated.
- Around line 42-55: createSignal is always falling back to the plain
getter/setter because the Solid runtime hook is never initialized in this
package. Update the package entry in index.js so it actually uses solid-js
reactivity when available, either by importing the real createSignal/createStore
implementations or by wiring the expected global hook before these helpers run.
Also apply the same fix to createStore so both symbols resolve to Solid’s
reactive APIs instead of the non-reactive fallback.
In `@README.md`:
- Around line 57-78: The Angular example in ChatComponent uses *ngFor without
showing the required template import setup, so it won’t compile as written.
Update the README example to include the proper Angular import for the template
directive in the standalone component case, or note that the component must be
declared in an NgModule that imports CommonModule. Reference the ChatComponent
and its template snippet so the fix is applied where the usage example is shown.
- Around line 28-47: The README example is missing the Solid control-flow
imports needed for the `App` snippet to run as written. Update the example that
uses `createByokRelayStore`, `createStreamingChatStore`, `For`, and `Show` so it
explicitly imports `For` and `Show` from `solid-js` alongside the existing
package import, ensuring the copy-paste example is complete.
---
Nitpick comments:
In `@packages/solid/src/index.js`:
- Around line 57-88: The createStore helper is currently dead code because
nothing in index.js uses it and it is not exported. Remove the unused
createStore function and its related shim logic if it is not meant for
consumers, or export it from the module if it is intended to be part of the
public API; use the createStore symbol and the setState/_subscribe
implementation to locate the block.
🪄 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: 8e7e4713-88ab-4b13-a216-f0da6b3dbeb4
📒 Files selected for processing (11)
README.mdllms.txtpackage.jsonpackages/angular/README.mdpackages/angular/package.jsonpackages/angular/src/index.jspackages/angular/test/services.test.jspackages/solid/README.mdpackages/solid/package.jsonpackages/solid/src/index.jspackages/solid/test/stores.test.js
| | Provider | `provider` value | Models | | ||
| |---|---|---| | ||
| | OpenAI | `openai` | gpt-4o, gpt-4o-mini, o3, … | | ||
| | Anthropic | `anthropic` | claude-opus-4, claude-sonnet-4-5, … | | ||
| | Groq | `groq` | llama3-70b, mixtral-8x7b, … | | ||
| | Mistral | `mistral` | mistral-large, codestral, … | | ||
| | OpenRouter | `openrouter` | any model via openrouter.ai | | ||
| | Google | `google` | gemini-2.5-pro, gemini-2.0-flash, … | |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
"Google" row documents a currently non-functional provider.
Per the PROVIDER_PATHS/URL-building issue flagged in packages/angular/src/index.js (unresolved {model} placeholder, and no Gemini-specific request/response shape handling in ChatService/StreamingChatService), setting provider: 'google' as documented here would not work end-to-end. Consider removing this row (or adding a "coming soon" note) until the implementation lands.
🤖 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/angular/README.md` around lines 204 - 211, The Google provider row
in the Angular README documents a provider that is not yet functional
end-to-end. Update the table entry referenced by the Angular README so it is
removed or clearly marked as coming soon until the provider path and
Gemini-specific handling are implemented in the Angular client, especially in
the provider routing logic and the ChatService/StreamingChatService flow.
| 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.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== packages/angular/src/index.js =="
cat -n packages/angular/src/index.js | sed -n '1,140p'
echo
echo "== Search for PROVIDER_PATHS and URL construction =="
rg -n "PROVIDER_PATHS|/relay/|generateContent|relayUrl" packages/angular/src/index.js packages/solid/src/index.js packages -g '!**/node_modules/**'
echo
echo "== Relevant slices from packages/angular/src/index.js =="
sed -n '250,430p' packages/angular/src/index.js
echo
echo "== Relevant slice from packages/solid/src/index.js =="
sed -n '260,380p' packages/solid/src/index.js
echo
echo "== README provider table mentions =="
rg -n "provider table|google|openrouter|anthropic|mistral|groq|openai" README.md packages -g '!**/node_modules/**'Repository: avikalpg/byok-relay
Length of output: 36267
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Angular chat/stream tests around relay URLs =="
sed -n '220,270p' packages/angular/test/services.test.js
echo
sed -n '270,340p' packages/angular/test/services.test.js
echo
echo "== Angular README route examples =="
sed -n '150,250p' packages/angular/README.md
echo
echo "== Solid README route examples =="
sed -n '150,250p' packages/solid/README.md
echo
echo "== Search for google-specific interpolation or route handling =="
rg -n "models/\\{model\\}:generateContent|generateContent|\\{model\\}|provider: 'google'|provider === 'google'" packages -g '!**/node_modules/**'Repository: avikalpg/byok-relay
Length of output: 11119
Remove the broken Google relay path or interpolate model before building the URL — ChatService and StreamingChatService pass PROVIDER_PATHS.google through unchanged, so provider: 'google' sends /relay/google/models/{model}:generateContent literally and will fail.
🤖 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/angular/src/index.js` around lines 29 - 36, The Google relay path is
currently stored with an unfilled {model} placeholder, so ChatService and
StreamingChatService will build an invalid URL for provider: 'google'. Update
PROVIDER_PATHS and the URL construction logic in the
ChatService/StreamingChatService flow so the model value is interpolated before
the request is sent, or remove the broken google entry if it is not supported.
Use the PROVIDER_PATHS constant and the ChatService/StreamingChatService
path-building code to locate the fix.
| async sendMessage(content, options = {}) { | ||
| const { | ||
| provider = 'openai', | ||
| model = 'gpt-4o-mini', | ||
| systemPrompt, | ||
| ...extra | ||
| } = options; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
No content validation before sending a message.
Unlike the Solid store's sendMessage (packages/solid/src/index.js:297-301), which throws early on empty content, ChatService.sendMessage/StreamingChatService.streamMessage push whatever content is given straight into history and send it, wasting a round trip on empty/whitespace input.
🛡️ Suggested fix
async sendMessage(content, options = {}) {
+ if (!content?.trim()) throw new Error('Message content is required');
const {Also applies to: 366-373
🤖 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/angular/src/index.js` around lines 262 - 268, Add early input
validation in ChatService.sendMessage and StreamingChatService.streamMessage so
empty or whitespace-only content is rejected before any history update or
network call. Mirror the Solid store contract by checking content.trim() and
throwing a clear “Message content is required” error, and keep the existing
relay token validation in the same preflight path so both methods fail fast
using the shared sendMessage/streamMessage flow.
| async sendMessage(content, options = {}) { | ||
| const { | ||
| provider = 'openai', | ||
| model = 'gpt-4o-mini', | ||
| systemPrompt, | ||
| ...extra | ||
| } = options; | ||
|
|
||
| const tok = this._relay._storage.getItem(this._relay._storageKey); | ||
| if (!tok) throw new Error('Not registered. Call ByokRelayService.register() first.'); | ||
|
|
||
| const newUserMsg = { role: 'user', content }; | ||
| this._messages.update((prev) => [...prev, newUserMsg]); | ||
| this._loading.set(true); | ||
| this._error.set(null); | ||
|
|
||
| const history = this._messages.value(); | ||
| const body = { | ||
| model, | ||
| messages: systemPrompt | ||
| ? [{ role: 'system', content: systemPrompt }, ...history] | ||
| : [...history], | ||
| ...extra, | ||
| }; | ||
|
|
||
| try { | ||
| const path = PROVIDER_PATHS[provider] || 'chat/completions'; | ||
| const resp = await fetch( | ||
| `${this._relay._relayUrl}/relay/${provider}/${path}`, | ||
| { | ||
| method: 'POST', | ||
| headers: { 'Content-Type': 'application/json', 'x-relay-token': tok }, | ||
| body: JSON.stringify(body), | ||
| }, | ||
| ); |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Map the relevant files first.
git ls-files 'packages/angular/src/index.js' 'packages/solid/src/index.js' 'packages/angular/test/services.test.js' 'README.md'
echo '--- angular outline ---'
ast-grep outline packages/angular/src/index.js --view expanded || true
echo '--- solid outline ---'
ast-grep outline packages/solid/src/index.js --view expanded || true
echo '--- angular anthropic-related matches ---'
rg -n "anthropic|max_tokens|systemPrompt|role: 'system'|role: \"system\"" packages/angular/src/index.js packages/angular/test/services.test.js README.md packages/solid/src/index.js
echo '--- read angular sendMessage area ---'
sed -n '240,340p' packages/angular/src/index.js
echo '--- read solid anthropic area ---'
sed -n '260,360p' packages/solid/src/index.js
echo '--- read README provider docs ---'
rg -n -A4 -B4 "anthropic|max_tokens|systemPrompt|Messages API|chat/completions" README.mdRepository: avikalpg/byok-relay
Length of output: 16098
Handle Anthropic chat payload separately in Angular (packages/angular/src/index.js:262-296, 366-393). sendMessage and streamMessage still put systemPrompt inside messages and never default max_tokens; Anthropic expects a top-level system string and requires max_tokens, so provider: 'anthropic' requests will fail. Mirror the Solid package’s Anthropic branch here.
🤖 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/angular/src/index.js` around lines 262 - 296, The Angular
ByokRelayService.sendMessage and ByokRelayService.streamMessage paths still
build Anthropic requests like OpenAI, so add a provider-specific Anthropic
branch mirroring the Solid implementation: keep systemPrompt as a top-level
system field instead of injecting it into messages, and ensure max_tokens is
always set (with a sensible default) before calling fetch. Update the request
body construction in both sendMessage and streamMessage to use the Anthropic
chat payload shape while preserving the existing OpenAI path for other
providers.
| ...extra | ||
| } = options; | ||
|
|
||
| const tok = this._relay._storage.getItem(this._relay._storageKey); |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Reaching into ByokRelayService private fields breaks encapsulation.
ChatService.sendMessage and StreamingChatService.streamMessage read this._relay._storage.getItem(this._relay._storageKey) and this._relay._relayUrl directly instead of using public accessors. Any internal rename/refactor of ByokRelayService silently breaks these dependents.
♻️ Suggested fix
get token() { return this._token.value; }
get loading() { return this._loading.value; }
get error() { return this._error.value; }
+ get relayUrl() { return this._relayUrl; }Then replace this._relay._storage.getItem(this._relay._storageKey) with this._relay.token() and this._relay._relayUrl with this._relay.relayUrl in ChatService/StreamingChatService.
Also applies to: 375-376
🤖 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/angular/src/index.js` at line 270, `ChatService.sendMessage` and
`StreamingChatService.streamMessage` are reaching into `ByokRelayService`
private fields, which breaks encapsulation and creates fragile dependencies.
Update these call sites to use the public accessors on `this._relay` instead of
directly reading `_storage`, `_storageKey`, or `_relayUrl`; specifically,
replace token retrieval with `token()` and URL access with `relayUrl` so the
relay implementation can change without silently breaking these services.
| 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
google provider is advertised in the README but never actually implemented — silent failure for users.
PROVIDER_PATHS declares a Gemini-specific path (models/{model}:generateContent) but it's never referenced anywhere in the file — sendMessage/extractDelta only special-case anthropic and otherwise assume an OpenAI-shaped { messages: [...] } request body and choices[0].message.content / choices[0].delta.content response. Google's Gemini API uses a completely different request (contents) and response (candidates[0].content.parts[0].text) shape, so provider: 'google' will send a malformed request and/or fail to parse the response. The README (packages/solid/README.md provider table) documents google as supported, which will mislead users into a broken integration; the PR objectives also only list OpenAI/Anthropic/Groq/Mistral/OpenRouter as supported.
Either implement proper Gemini request/response handling or remove google from PROVIDER_PATHS and the README until it's supported.
Also applies to: 123-130, 281-320, 378-441
🤖 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/solid/src/index.js` around lines 24 - 31, `google` is listed as a
supported provider in `PROVIDER_PATHS`, but `sendMessage` and `extractDelta`
never handle Gemini’s request/response shape. Either add proper Google/Gemini
support in the `sendMessage` flow and response parsing (using `contents` and
`candidates`), or remove `google` from `PROVIDER_PATHS` and update the
README/provider table so it is not advertised as supported. Make sure the
behavior in the `sendMessage` and `extractDelta` paths matches the documented
provider set.
| function createSignal(initial) { | ||
| // Use native SolidJS signals when available (works inside components/stores) | ||
| if (typeof globalThis !== 'undefined' && globalThis.__solidCreateSignal) { | ||
| return globalThis.__solidCreateSignal(initial); | ||
| } | ||
|
|
||
| let value = initial; | ||
| const getter = () => value; | ||
| const setter = (next) => { | ||
| value = typeof next === 'function' ? next(value) : next; | ||
| return value; | ||
| }; | ||
| return [getter, setter]; | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🔴 Critical | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== files ==\n'
git ls-files packages/solid/src/index.js packages/solid/package.json package.json README.md 2>/dev/null || true
printf '\n== index.js ==\n'
sed -n '1,220p' packages/solid/src/index.js
printf '\n== package.json (root, if present) ==\n'
sed -n '1,220p' package.json 2>/dev/null || true
printf '\n== packages/solid/package.json (if present) ==\n'
sed -n '1,220p' packages/solid/package.json 2>/dev/null || true
printf '\n== repo search: __solidCreateSignal ==\n'
rg -n "__solidCreateSignal" . || true
printf '\n== repo search: solid-js references ==\n'
rg -n "\bsolid-js\b|createSignal|createStore" packages README.md package.json .github . 2>/dev/null | sed -n '1,240p'Repository: avikalpg/byok-relay
Length of output: 14064
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== tail of packages/solid/src/index.js ==\n'
tail -n 120 packages/solid/src/index.js
printf '\n== package type fields ==\n'
python3 - <<'PY'
import json, pathlib
for p in [pathlib.Path('package.json'), pathlib.Path('packages/solid/package.json')]:
data = json.loads(p.read_text())
print(p)
print('type:', data.get('type'))
print('main:', data.get('main'))
print('module:', data.get('module'))
print('exports:', data.get('exports'))
PY
printf '\n== README usage around reactivity ==\n'
sed -n '1,80p' packages/solid/README.mdRepository: avikalpg/byok-relay
Length of output: 6220
SolidJS reactivity never engages here
createSignal only checks globalThis.__solidCreateSignal, but nothing in this package sets that hook and solid-js is never imported, so Solid apps always get the plain getter/setter fallback and JSX reads won’t trigger re-renders. Same applies to createStore.
🤖 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/solid/src/index.js` around lines 42 - 55, createSignal is always
falling back to the plain getter/setter because the Solid runtime hook is never
initialized in this package. Update the package entry in index.js so it actually
uses solid-js reactivity when available, either by importing the real
createSignal/createStore implementations or by wiring the expected global hook
before these helpers run. Also apply the same fix to createStore so both symbols
resolve to Solid’s reactive APIs instead of the non-reactive fallback.
| function* parseSSE(chunk) { | ||
| const lines = chunk.split('\n'); | ||
| for (const line of lines) { | ||
| if (!line.startsWith('data:')) continue; | ||
| const data = line.slice(5).trim(); | ||
| if (data === '[DONE]') { yield null; continue; } | ||
| try { yield JSON.parse(data); } catch { /* skip malformed */ } | ||
| } | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
SSE parser doesn't buffer partial lines split across network chunks.
parseSSE splits each raw chunk on \n independently. If a data: {...} JSON payload is split across two reader.read() calls (common with real network streaming), the incomplete line will fail JSON.parse and be silently swallowed by the catch { /* skip malformed */ }, causing dropped text in the streamed response without any error surfaced to the user.
🔧 Proposed fix: carry over an incomplete trailing line buffer
- const reader = res.body.getReader();
- const decoder = new TextDecoder();
- let accumulated = '';
+ const reader = res.body.getReader();
+ const decoder = new TextDecoder();
+ let accumulated = '';
+ let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
- const chunk = decoder.decode(value, { stream: true });
- for (const event of parseSSE(chunk)) {
+ buffer += decoder.decode(value, { stream: true });
+ const lines = buffer.split('\n');
+ buffer = lines.pop(); // keep incomplete trailing line for next read
+ for (const event of parseSSE(lines.join('\n'))) {
const delta = extractDelta(event, provider);
if (delta) {
accumulated += delta;
setStreamingContent(accumulated);
}
}
}Also applies to: 459-471
🤖 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/solid/src/index.js` around lines 113 - 121, The SSE parsing in
parseSSE is dropping payloads when a JSON line is split across reader.read()
chunks because each chunk is split independently and malformed fragments are
silently skipped. Update parseSSE to retain a trailing incomplete line buffer
between calls, only attempt JSON.parse on complete data: lines, and flush the
buffered remainder once the final chunk arrives. Use the parseSSE generator and
its current chunk/line handling as the place to add the carry-over logic, and
apply the same buffering fix anywhere the same parser logic is duplicated.
| ```jsx | ||
| import { createByokRelayStore, createStreamingChatStore } from '@byok-relay/solid'; | ||
|
|
||
| function App() { | ||
| const relay = createByokRelayStore({ appId: 'my-app' }); | ||
| const chat = createStreamingChatStore({ provider: 'openai', model: 'gpt-4o-mini' }); | ||
|
|
||
| async function send(text) { | ||
| if (!relay.token()) await relay.register(); | ||
| await chat.sendMessage(text, relay.token()); | ||
| } | ||
|
|
||
| return ( | ||
| <> | ||
| <For each={chat.messages()}>{msg => <p>{msg.role}: {msg.content}</p>}</For> | ||
| <Show when={chat.streamingContent()}><p>assistant: {chat.streamingContent()}▋</p></Show> | ||
| </> | ||
| ); | ||
| } | ||
| ``` |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Show the README section around the reported lines
sed -n '1,120p' README.md | cat -n
# Look for SolidJS imports/examples elsewhere in the repo
rg -n "from 'solid-js'|from \"solid-js\"|<For>|<Show>" README.md . --glob '!**/node_modules/**'Repository: avikalpg/byok-relay
Length of output: 6095
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Map README structure to inspect the relevant subsection precisely
ast-grep outline README.md --view expandedRepository: avikalpg/byok-relay
Length of output: 181
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Minimal behavioral sanity check: parse the snippet as plain JS/JSX text to confirm imports are absent
python3 - <<'PY'
from pathlib import Path
text = Path('README.md').read_text()
start = text.find("```jsx")
end = text.find("```", start + 1)
snippet = text[start:end]
print("has solid-js import:", "from 'solid-js'" in snippet or 'from "solid-js"' in snippet)
print("has For:", "<For" in snippet)
print("has Show:", "<Show" in snippet)
PYRepository: avikalpg/byok-relay
Length of output: 213
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the README excerpt with line numbers if needed
nl -ba README.md | sed -n '20,60p'Repository: avikalpg/byok-relay
Length of output: 197
Import For and Show from solid-js.
This snippet won’t copy-paste as written because <For> and <Show> are used without imports.
🤖 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 `@README.md` around lines 28 - 47, The README example is missing the Solid
control-flow imports needed for the `App` snippet to run as written. Update the
example that uses `createByokRelayStore`, `createStreamingChatStore`, `For`, and
`Show` so it explicitly imports `For` and `Show` from `solid-js` alongside the
existing package import, ensuring the copy-paste example is complete.
| ```typescript | ||
| import { Component, inject } from '@angular/core'; | ||
| import { ByokRelayService, ChatService, provideByokRelay } from '@byok-relay/angular'; | ||
|
|
||
| // app.config.ts | ||
| export const appConfig = { | ||
| providers: [provideByokRelay({ relayUrl: 'https://relay.byokrelay.com' })], | ||
| }; | ||
|
|
||
| // chat.component.ts | ||
| @Component({ template: ` | ||
| <div *ngFor="let m of chat.messages()">{{ m.role }}: {{ m.content }}</div> | ||
| <button (click)="send('Hello!')">Send</button> | ||
| ` }) | ||
| export class ChatComponent { | ||
| relay = inject(ByokRelayService); | ||
| chat = inject(ChatService); | ||
|
|
||
| async ngOnInit() { await this.relay.getOrRegister('my-app'); } | ||
| async send(text: string) { await this.chat.sendMessage(text); } | ||
| } | ||
| ``` |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate the README section and nearby Angular docs/examples.
grep -n -A40 -B20 "provideByokRelay" README.md || true
# Search for other Angular examples or setup notes in the repo.
rg -n "standalone: true|CommonModule|NgModule|provideByokRelay|ByokRelayService|ChatService|`@angular/core`" README.md . -g '!**/node_modules/**' -g '!**/dist/**' -g '!**/build/**' || trueRepository: avikalpg/byok-relay
Length of output: 16358
README.md:57-78 — Add the Angular template setup
*ngFor needs CommonModule/NgFor in a standalone component, or an NgModule that imports CommonModule. As written, this example won’t compile as-is.
🤖 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 `@README.md` around lines 57 - 78, The Angular example in ChatComponent uses
*ngFor without showing the required template import setup, so it won’t compile
as written. Update the README example to include the proper Angular import for
the template directive in the standalone component case, or note that the
component must be declared in an NgModule that imports CommonModule. Reference
the ChatComponent and its template snippet so the fix is applied where the usage
example is shown.
Summary
Day 25 of the byok-relay growth track: Angular is the last major JS framework without a dedicated byok-relay package. This PR adds
@byok-relay/angular— four injectable services that work in Angular 14+, Angular 16+ (signals), Analog, and standalone JS contexts.What is in this PR
packages/angular/src/index.jsFour services, each a plain JS class that works as an Angular injectable:
ByokRelayService— token registration, key CRUD (store/list/delete/rotate), localStorage persistence, in-memory fallback for Analog SSR, reactive signals for token/loading/error stateChatService— stateful message history, non-streaming, works with openai / anthropic / groq / mistral / openrouter; rolls back user message on errorStreamingChatService— SSE streaming with AbortController;stopStreaming()partial-commits accumulated content;streamingContent()live signal for template bindingRelayHealthService— polls/healthon configurable interval;check(deep=true)for upstream provider ping;destroy()forngOnDestroylifecycleAngular signal shim
@angular/core→ uses nativesignal()→ works in OnPush componentsprovideByokRelay(config)@angular/corepresent → callsmakeEnvironmentProviders()with properdepswiringcreateByokRelayBundle()(test / standalone / vanilla)29 smoke tests (all passing)
Runs in plain Node, no DOM, no
@angular/core:Docs
packages/angular/README.md— full API docs, quick-start, signals note, Analog SSR guide, provider tablellms.txt— Angular Injectable Services section + npm linkREADME.md— Angular services section after SolidJSMetrics (2026-07-04)
stars=52 forks=0 views=22 clones=112
Next for Avi
Summary by CodeRabbit
New Features
Documentation
Tests