Skip to content

growth(day-25): @byok-relay/angular — Angular injectable services for BYOK AI#63

Open
alokit-bot wants to merge 2 commits into
mainfrom
feat/growth-2026-07-04
Open

growth(day-25): @byok-relay/angular — Angular injectable services for BYOK AI#63
alokit-bot wants to merge 2 commits into
mainfrom
feat/growth-2026-07-04

Conversation

@alokit-bot

@alokit-bot alokit-bot commented Jul 4, 2026

Copy link
Copy Markdown
Collaborator

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.js

Four 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 state
  • ChatService — stateful message history, non-streaming, works with openai / anthropic / groq / mistral / openrouter; rolls back user message on error
  • StreamingChatService — SSE streaming with AbortController; stopStreaming() partial-commits accumulated content; streamingContent() live signal for template binding
  • RelayHealthService — polls /health on configurable interval; check(deep=true) for upstream provider ping; destroy() for ngOnDestroy lifecycle

Angular signal shim

  • Angular 16+ detected via @angular/core → uses native signal() → works in OnPush components
  • Otherwise → plain getter/setter shim with identical API

provideByokRelay(config)

  • @angular/core present → calls makeEnvironmentProviders() with proper deps wiring
  • Not present → returns a plain createByokRelayBundle() (test / standalone / vanilla)

29 smoke tests (all passing)

Runs in plain Node, no DOM, no @angular/core:

── Results: 29 passed, 0 failed ──

Docs

  • packages/angular/README.md — full API docs, quick-start, signals note, Analog SSR guide, provider table
  • llms.txt — Angular Injectable Services section + npm link
  • README.md — Angular services section after SolidJS

Metrics (2026-07-04)

stars=52 forks=0 views=22 clones=112

Next for Avi

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

Summary by CodeRabbit

  • New Features

    • Added Angular and SolidJS integrations for token management, chat, streaming responses, and relay health monitoring.
    • Introduced framework setup helpers and reusable services/stores for app integration.
    • Added workspace support for managing multiple packages together.
  • Documentation

    • Expanded README and package docs with install steps, quick-start examples, SSR-safe usage, and self-hosting notes for Angular and SolidJS.
    • Updated integration reference material to include the new frontend packages.
  • Tests

    • Added smoke tests covering token flow, chat, streaming, health checks, and polling behavior for both integrations.

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
@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds two new frontend integration packages, @byok-relay/angular and @byok-relay/solid, each providing reactive services/stores for token registration, chat (streaming and non-streaming), and health polling, plus package manifests, smoke tests, and READMEs. Root package.json gains npm workspaces; README.md and llms.txt gain corresponding documentation.

Changes

Angular package

Layer / File(s) Summary
Core services implementation
packages/angular/src/index.js
Adds constants, signal/storage shims, and ByokRelayService, ChatService, StreamingChatService, RelayHealthService classes with createByokRelayBundle/provideByokRelay factories and module exports.
Service tests
packages/angular/test/services.test.js
Adds a fetch/storage/SSE mock test harness and suites covering all four services plus the bundle factory.
Package manifest and README
packages/angular/package.json, packages/angular/README.md
Adds package metadata, exports/peer dependencies, and documentation for installation, service usage, signals fallback, standalone/SSR usage, and self-hosting.

Solid package

Layer / File(s) Summary
Core store implementation
packages/solid/src/index.js
Adds reactivity/storage shims, SSE parsing helpers, and createByokRelayStore, createChatStore, createStreamingChatStore, createRelayHealthStore factories with module exports.
Store tests
packages/solid/test/stores.test.js
Adds a fetch/SSE/storage mock harness and test suites covering all four store factories.
Package manifest and README
packages/solid/package.json, packages/solid/README.md
Adds package metadata, exports/peer dependencies, and documentation for installation, quick start, SSR usage, store APIs, and self-hosting.

Root docs and workspace config

Layer / File(s) Summary
Workspace config and documentation
package.json, README.md, llms.txt
Adds npm workspaces glob for packages/* and documents the new SolidJS and Angular integrations in the root README and llms.txt.

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
Loading
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
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding Angular injectable services for BYOK AI.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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-07-04

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: 16

🧹 Nitpick comments (1)
packages/solid/src/index.js (1)

57-88: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

createStore is 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

📥 Commits

Reviewing files that changed from the base of the PR and between f0e1a43 and 82b8c26.

📒 Files selected for processing (11)
  • README.md
  • llms.txt
  • package.json
  • packages/angular/README.md
  • packages/angular/package.json
  • packages/angular/src/index.js
  • packages/angular/test/services.test.js
  • packages/solid/README.md
  • packages/solid/package.json
  • packages/solid/src/index.js
  • packages/solid/test/stores.test.js

Comment on lines +204 to +211
| 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, … |

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 | 🟡 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.

Comment on lines +29 to +36
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.

🗄️ 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 URLChatService 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.

Comment on lines +262 to +268
async sendMessage(content, options = {}) {
const {
provider = 'openai',
model = 'gpt-4o-mini',
systemPrompt,
...extra
} = options;

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

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 {
As per the upstream contract in `packages/solid/src/index.js:297-301`: if (!content?.trim()) throw new Error('Message content is required'); if (!relayToken) throw new Error('Relay token is required');

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.

Comment on lines +262 to +296
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),
},
);

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

🧩 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.md

Repository: 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);

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 | 🟠 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.

Comment on lines +24 to +31
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

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.

Comment on lines +42 to +55
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];
}

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 | 🔴 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.md

Repository: 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.

Comment on lines +113 to +121
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 */ }
}
}

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

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.

Comment thread README.md
Comment on lines +28 to +47
```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>
</>
);
}
```

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 | 🟡 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 expanded

Repository: 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)
PY

Repository: 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.

Comment thread README.md
Comment on lines +57 to +78
```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); }
}
```

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 | 🟡 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/**' || true

Repository: 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.

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