Skip to content

feat: add headless BYOK via defineByok - #906

Open
tombeckenham wants to merge 4 commits into
mainfrom
byok-package
Open

feat: add headless BYOK via defineByok#906
tombeckenham wants to merge 4 commits into
mainfrom
byok-package

Conversation

@tombeckenham

@tombeckenhamtombeckenham commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Changes

This PR rewrites BYOK as a headless client. There is no @tanstack/ai-byok package. There is no packaged key dialog.

Users define a keyring, pass it into useChat or useGeneration, and store keys in their own UI. Keys travel in x-byok-* headers only. The key never goes in the JSON body.

  • defineByok in @tanstack/ai-client/byok — create one ByokClient for the app
  • byok on hooks — pass it into useChat, useGeneration, and the other generation hooks (React, Preact, Solid, Vue, Svelte, Angular)
  • useByok(client) — live snapshot for your own form (last four characters, lock state, missing-key prompt)
  • Headersx-byok-<provider> on each POST. runContext.headers carries them. The key is not in the body or the message history
  • Relay@tanstack/ai/byok (getByokOrEnvKey, byokMissing, scrubSecrets, maskKey). The header wins. If it is empty, the relay can fall back to env
  • StoragedefaultByokStorage() uses a passkey when the browser supports it (WebAuthn PRF → HKDF → AES-GCM). If it does not, keys stay in session memory. After refresh, saved keys are locked until unlock()
  • No UI package — call byok.update(provider, value) from your own UI. This library does not ship ByokKeyDialog or ByokProvider
  • DocsBYOK guide and API pages for ai, ai-client, and each framework
  • Examplets-react-chat has an app-owned key form and a relay that reads the header
  • E2Etesting/e2e/tests/byok.spec.ts covers header transport and byokMissing

Usage

import{defineByok,defaultByokStorage}from'@tanstack/ai-client/byok'exportconstbyok=defineByok({storage: defaultByokStorage()})
const{ sendMessage }=useChat({connection: fetchServerSentEvents('/api/chat'),
byok,forwardedProps: {provider: 'openai',model: 'gpt-5.5'},})
import{byokMissing,getByokOrEnvKey}from'@tanstack/ai/byok'constapiKey=getByokOrEnvKey(request,'openai',['OPENAI_API_KEY'])if(!apiKey)returnbyokMissing('openai')

Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested this code locally with pnpm run test:pr.

Targeted tests that did run: @tanstack/ai BYOK unit tests, @tanstack/ai-client BYOK unit tests, @tanstack/ai-react BYOK hook tests, and testing/e2e/tests/byok.spec.ts.

Release Impact

  • This change affects published code, and I have generated a changeset.
  • This change is docs/CI/dev-only (no release).

@coderabbitai

coderabbitaiBot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR adds @tanstack/ai-byok, providing client keyrings, passkey storage, BYOK headers, missing-key handling, React key management, OpenRouter PKCE login, stateless server helpers, example-app integration, and end-to-end tests.

Changes

Core BYOK package

Layer / File(s)Summary
Provider registry and request handling
packages/ai-byok/src/shared/*, packages/ai-byok/src/client/*
Defines provider metadata, headers, storage, validation, connection wrappers, and fetcher support.
Passkey storage and server safeguards
packages/ai-byok/src/client/passkey.ts, packages/ai-byok/src/server/*
Adds encrypted IndexedDB storage, header-only extraction, typed missing-key responses, and secret masking.
React key management and OpenRouter login
packages/ai-byok/src/react/*, packages/ai-byok/src/client/openrouter-pkce.ts
Adds provider state management, key UI, PKCE helpers, and the React PKCE hook.
Package surface, documentation, and tests
packages/ai-byok/package.json, packages/ai-byok/README.md, docs/*, packages/ai-byok/tests/*
Adds package exports, configuration, release metadata, documentation, and unit coverage.

ts-react-chat example integration

Layer / File(s)Summary
Provider mapping and environment status
examples/ts-react-chat/src/lib/byok-config.ts, examples/ts-react-chat/package.json
Maps providers and exposes environment-key availability without returning key values.
Example relay adapter selection
examples/ts-react-chat/src/routes/api.tanchat.ts
Uses request BYOK keys, falls back to environment adapters, and returns byokMissing when unavailable.
Example key dialog and chat wiring
examples/ts-react-chat/src/components/ByokKeyDialog.tsx, examples/ts-react-chat/src/routes/index.tsx
Adds key entry, masking, unlock handling, BYOK-aware SSE requests, and storage initialization.

End-to-end BYOK flow

Layer / File(s)Summary
BYOK routes and adapter forwarding
testing/e2e/src/routes/byok.tsx, testing/e2e/src/routes/api.byok-chat.ts, testing/e2e/src/lib/providers.ts
Adds a BYOK chat page and relay route with header-only key extraction and per-request adapter forwarding.
Generated route registration
testing/e2e/src/routeTree.gen.ts
Registers /byok and /api/byok-chat in generated route and type mappings.
Playwright BYOK validation
testing/e2e/tests/byok.spec.ts, testing/e2e/README.md, testing/e2e/package.json
Verifies header-only transmission, masked keys, successful streaming, and missing-key errors.

Estimated code review effort: 4 (Complex) | ~75 minutes

Possibly related PRs

  • TanStack/ai#502: Modifies the same gemini-interactions adapter setup used by the example’s BYOK adapter selection.

Suggested reviewers:alemtuzlak

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 70.18% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check⚠️ WarningThe description uses the required sections, but it contradicts the changes by denying the new package and packaged React UI.Update the description to reflect the actual @tanstack/ai-byok package, React bindings, storage, server helpers, example, tests, and documentation.
✅ Passed checks (3 passed)
Check nameStatusExplanation
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Title check✅ PassedThe title mentions the BYOK feature but incorrectly identifies it as headless and based on nonexistent defineByok APIs.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch byok-package

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.

@github-actions

github-actionsBot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

🚀 Changeset Version Preview

21 package(s) bumped directly, 25 bumped as dependents.

🟥 Major bumps

PackageVersionReason
@tanstack/ai-angular0.2.3 → 1.0.0Changeset
@tanstack/ai-anthropic0.16.1 → 1.0.0Changeset
@tanstack/ai-bedrock0.1.2 → 1.0.0Changeset
@tanstack/ai-fal0.9.10 → 1.0.0Changeset
@tanstack/ai-gemini0.19.1 → 1.0.0Changeset
@tanstack/ai-grok0.14.7 → 1.0.0Changeset
@tanstack/ai-groq0.5.1 → 1.0.0Changeset
@tanstack/ai-mistral0.2.1 → 1.0.0Changeset
@tanstack/ai-ollama0.8.14 → 1.0.0Changeset
@tanstack/ai-openai0.16.0 → 1.0.0Changeset
@tanstack/ai-openrouter0.15.8 → 1.0.0Changeset
@tanstack/ai-preact0.10.3 → 1.0.0Changeset
@tanstack/ai-react0.16.4 → 1.0.0Changeset
@tanstack/ai-sandbox0.2.2 → 1.0.0Changeset
@tanstack/ai-solid0.14.3 → 1.0.0Changeset
@tanstack/ai-svelte0.14.3 → 1.0.0Changeset
@tanstack/ai-vue0.14.3 → 1.0.0Changeset
@tanstack/openai-base0.9.7 → 1.0.0Changeset
@tanstack/ai-acp0.2.1 → 1.0.0Dependent
@tanstack/ai-claude-code0.2.1 → 1.0.0Dependent
@tanstack/ai-code-mode0.3.6 → 1.0.0Dependent
@tanstack/ai-code-mode-skills0.3.9 → 1.0.0Dependent
@tanstack/ai-codex0.2.1 → 1.0.0Dependent
@tanstack/ai-elevenlabs0.2.32 → 1.0.0Dependent
@tanstack/ai-grok-build0.2.1 → 1.0.0Dependent
@tanstack/ai-isolate-node0.1.45 → 1.0.0Dependent
@tanstack/ai-isolate-quickjs0.1.45 → 1.0.0Dependent
@tanstack/ai-opencode0.2.1 → 1.0.0Dependent
@tanstack/ai-react-ui0.8.13 → 1.0.0Dependent
@tanstack/ai-sandbox-cloudflare0.2.2 → 1.0.0Dependent
@tanstack/ai-sandbox-daytona0.2.0 → 1.0.0Dependent
@tanstack/ai-sandbox-docker0.2.0 → 1.0.0Dependent
@tanstack/ai-sandbox-local-process0.2.0 → 1.0.0Dependent
@tanstack/ai-sandbox-sprites0.2.1 → 1.0.0Dependent
@tanstack/ai-sandbox-vercel0.2.0 → 1.0.0Dependent
@tanstack/ai-solid-ui0.7.12 → 1.0.0Dependent

🟨 Minor bumps

PackageVersionReason
@tanstack/ai0.40.0 → 0.41.0Changeset
@tanstack/ai-byok0.1.0 → 0.2.0Changeset
@tanstack/ai-client0.20.0 → 0.21.0Changeset

🟩 Patch bumps

PackageVersionReason
@tanstack/ai-devtools-core0.4.22 → 0.4.23Dependent
@tanstack/ai-isolate-cloudflare0.2.36 → 0.2.37Dependent
@tanstack/ai-mcp0.2.3 → 0.2.4Dependent
@tanstack/ai-vue-ui0.2.31 → 0.2.32Dependent
@tanstack/preact-ai-devtools0.1.65 → 0.1.66Dependent
@tanstack/react-ai-devtools0.2.65 → 0.2.66Dependent
@tanstack/solid-ai-devtools0.2.65 → 0.2.66Dependent

@nx-cloud

nx-cloudBot commented Jul 7, 2026

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit d88b2e4

CommandStatusDurationResult
nx run-many --targets=build --exclude=examples/...✅ Succeeded1m 51sView ↗

☁️ Nx Cloud last updated this comment at 2026-08-19 05:41:16 UTC

@nx-cloud

nx-cloudBot commented Jul 7, 2026

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit c9d0226

CommandStatusDurationResult
nx run-many --targets=build --exclude=examples/...✅ Succeeded2m 2sView ↗

☁️ Nx Cloud last updated this comment at 2026-07-07 02:09:29 UTC

@pkg-pr-new

pkg-pr-newBot commented Jul 7, 2026

Copy link
Copy Markdown

Open in StackBlitz

@tanstack/ai

npm i https://pkg.pr.new/@tanstack/ai@906

@tanstack/ai-acp

npm i https://pkg.pr.new/@tanstack/ai-acp@906

@tanstack/ai-angular

npm i https://pkg.pr.new/@tanstack/ai-angular@906

@tanstack/ai-anthropic

npm i https://pkg.pr.new/@tanstack/ai-anthropic@906

@tanstack/ai-bedrock

npm i https://pkg.pr.new/@tanstack/ai-bedrock@906

@tanstack/ai-byteplus

npm i https://pkg.pr.new/@tanstack/ai-byteplus@906

@tanstack/ai-claude-code

npm i https://pkg.pr.new/@tanstack/ai-claude-code@906

@tanstack/ai-client

npm i https://pkg.pr.new/@tanstack/ai-client@906

@tanstack/ai-code-mode

npm i https://pkg.pr.new/@tanstack/ai-code-mode@906

@tanstack/ai-code-mode-snippets

npm i https://pkg.pr.new/@tanstack/ai-code-mode-snippets@906

@tanstack/ai-codex

npm i https://pkg.pr.new/@tanstack/ai-codex@906

@tanstack/ai-cohere

npm i https://pkg.pr.new/@tanstack/ai-cohere@906

@tanstack/ai-devtools-core

npm i https://pkg.pr.new/@tanstack/ai-devtools-core@906

@tanstack/ai-durable-stream

npm i https://pkg.pr.new/@tanstack/ai-durable-stream@906

@tanstack/ai-elevenlabs

npm i https://pkg.pr.new/@tanstack/ai-elevenlabs@906

@tanstack/ai-event-client

npm i https://pkg.pr.new/@tanstack/ai-event-client@906

@tanstack/ai-fal

npm i https://pkg.pr.new/@tanstack/ai-fal@906

@tanstack/ai-gemini

npm i https://pkg.pr.new/@tanstack/ai-gemini@906

@tanstack/ai-grok

npm i https://pkg.pr.new/@tanstack/ai-grok@906

@tanstack/ai-grok-build

npm i https://pkg.pr.new/@tanstack/ai-grok-build@906

@tanstack/ai-groq

npm i https://pkg.pr.new/@tanstack/ai-groq@906

@tanstack/ai-isolate-cloudflare

npm i https://pkg.pr.new/@tanstack/ai-isolate-cloudflare@906

@tanstack/ai-isolate-daytona

npm i https://pkg.pr.new/@tanstack/ai-isolate-daytona@906

@tanstack/ai-isolate-node

npm i https://pkg.pr.new/@tanstack/ai-isolate-node@906

@tanstack/ai-isolate-quickjs

npm i https://pkg.pr.new/@tanstack/ai-isolate-quickjs@906

@tanstack/ai-isolate-quickjs-bun

npm i https://pkg.pr.new/@tanstack/ai-isolate-quickjs-bun@906

@tanstack/ai-mcp

npm i https://pkg.pr.new/@tanstack/ai-mcp@906

@tanstack/ai-memory

npm i https://pkg.pr.new/@tanstack/ai-memory@906

@tanstack/ai-mistral

npm i https://pkg.pr.new/@tanstack/ai-mistral@906

@tanstack/ai-ollama

npm i https://pkg.pr.new/@tanstack/ai-ollama@906

@tanstack/ai-openai

npm i https://pkg.pr.new/@tanstack/ai-openai@906

@tanstack/ai-opencode

npm i https://pkg.pr.new/@tanstack/ai-opencode@906

@tanstack/ai-openrouter

npm i https://pkg.pr.new/@tanstack/ai-openrouter@906

@tanstack/ai-perplexity

npm i https://pkg.pr.new/@tanstack/ai-perplexity@906

@tanstack/ai-persistence

npm i https://pkg.pr.new/@tanstack/ai-persistence@906

@tanstack/ai-preact

npm i https://pkg.pr.new/@tanstack/ai-preact@906

@tanstack/ai-react

npm i https://pkg.pr.new/@tanstack/ai-react@906

@tanstack/ai-react-ui

npm i https://pkg.pr.new/@tanstack/ai-react-ui@906

@tanstack/ai-sandbox

npm i https://pkg.pr.new/@tanstack/ai-sandbox@906

@tanstack/ai-sandbox-cloudflare

npm i https://pkg.pr.new/@tanstack/ai-sandbox-cloudflare@906

@tanstack/ai-sandbox-daytona

npm i https://pkg.pr.new/@tanstack/ai-sandbox-daytona@906

@tanstack/ai-sandbox-docker

npm i https://pkg.pr.new/@tanstack/ai-sandbox-docker@906

@tanstack/ai-sandbox-local-process

npm i https://pkg.pr.new/@tanstack/ai-sandbox-local-process@906

@tanstack/ai-sandbox-sprites

npm i https://pkg.pr.new/@tanstack/ai-sandbox-sprites@906

@tanstack/ai-sandbox-vercel

npm i https://pkg.pr.new/@tanstack/ai-sandbox-vercel@906

@tanstack/ai-solid

npm i https://pkg.pr.new/@tanstack/ai-solid@906

@tanstack/ai-solid-ui

npm i https://pkg.pr.new/@tanstack/ai-solid-ui@906

@tanstack/ai-svelte

npm i https://pkg.pr.new/@tanstack/ai-svelte@906

@tanstack/ai-utils

npm i https://pkg.pr.new/@tanstack/ai-utils@906

@tanstack/ai-vercel-gateway

npm i https://pkg.pr.new/@tanstack/ai-vercel-gateway@906

@tanstack/ai-vue

npm i https://pkg.pr.new/@tanstack/ai-vue@906

@tanstack/ai-vue-ui

npm i https://pkg.pr.new/@tanstack/ai-vue-ui@906

@tanstack/openai-base

npm i https://pkg.pr.new/@tanstack/openai-base@906

@tanstack/preact-ai-devtools

npm i https://pkg.pr.new/@tanstack/preact-ai-devtools@906

@tanstack/react-ai-devtools

npm i https://pkg.pr.new/@tanstack/react-ai-devtools@906

@tanstack/solid-ai-devtools

npm i https://pkg.pr.new/@tanstack/solid-ai-devtools@906

commit: d88b2e4

@tombeckenham
tombeckenham marked this pull request as ready for review July 7, 2026 10:41

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 9

🧹 Nitpick comments (8)
packages/ai-byok/tests/react.test.tsx (1)

6-7: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Merge duplicate ../src/index type imports.

Static analysis flags import/no-duplicates for the two separate type-only imports from ../src/index.

🧹 Proposed fix
-import type { Keyring } from '../src/index'-import type { KeyringStorage } from '../src/index'+import type { Keyring, KeyringStorage } from '../src/index'
🤖 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/ai-byok/tests/react.test.tsx` around lines 6 - 7, The React test
file has duplicate type-only imports from the same module, triggering
import/no-duplicates. Update the import section in react.test.tsx to merge the
Keyring and KeyringStorage type imports into a single import statement from
../src/index, keeping the existing type-only form and preserving the referenced
symbols.

Source: Linters/SAST tools

packages/ai-byok/tests/byok.test.ts (1)

1-16: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Fix import order/sort lint errors.

Static analysis flags: byokHeaderName out of alphabetical order in the named import on Line 2, and the type import on Line 3 should be ordered after the ../src/client/passkey import.

🧹 Proposed fix for import ordering
-import { byokFetch, byokHeaders, byokHeaderName, withByok } from '../src/index'-import type { Keyring } from '../src/index'-import {- byokMissing,- getByokKey,- isByokMissingBody,- maskKey,- scrubSecrets,-} from '../src/server'-import { memoryStorage } from '../src/client/storage'-import {- decryptKeyring,- deriveAesKey,- encryptKeyring,-} from '../src/client/passkey'+import { byokFetch, byokHeaderName, byokHeaders, withByok } from '../src/index'+import {+ byokMissing,+ getByokKey,+ isByokMissingBody,+ maskKey,+ scrubSecrets,+} from '../src/server'+import { memoryStorage } from '../src/client/storage'+import {+ decryptKeyring,+ deriveAesKey,+ encryptKeyring,+} from '../src/client/passkey'+import type { Keyring } from '../src/index'
🤖 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/ai-byok/tests/byok.test.ts` around lines 1 - 16, The test file
import ordering is violating lint rules: the named import from byok helpers has
an out-of-order symbol, and the type-only import is placed before the
client/passkey imports. Reorder the imports in byok.test.ts so the named
specifiers in the ../src/index import are alphabetized and the type import for
Keyring is moved to the correct position after the ../src/client/passkey import,
keeping the existing symbols and groupings intact.

Source: Linters/SAST tools

packages/ai-byok/src/client/passkey.ts (1)

299-322: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Guard ensureKey against concurrent ceremonies.

The doc comment promises "exactly one WebAuthn ceremony," but because the cachedKey/cachedMeta assignment is await-gated, two overlapping calls (e.g. a save racing a load, or rapid double-save) both observe an empty cache, both call idbGet, and both run a full ceremony. In the register branch this can mint two passkeys with only the last record persisted, orphaning the other. Memoize the in-flight promise so concurrent callers share one ceremony.

♻️ Sketch
 let cachedKey: CryptoKey | null = null
let cachedMeta: {
credentialId: ArrayBuffer
salt: Uint8Array<ArrayBuffer>
} | null = null
+ let pending: Promise<{+ key: CryptoKey+ credentialId: ArrayBuffer+ salt: Uint8Array<ArrayBuffer>+ }> | null = null
async function ensureKey(): Promise<{
key: CryptoKey
credentialId: ArrayBuffer
salt: Uint8Array<ArrayBuffer>
}> {
if (cachedKey && cachedMeta) {
return { key: cachedKey, ...cachedMeta }
}
- const existing = await idbGet(dbName)- ...- return { key: cachedKey, ...cachedMeta }+ if (!pending) {+ pending = (async () => {+ // ...existing register/unlock logic, sets cachedKey/cachedMeta...+ return { key: cachedKey!, ...cachedMeta! }+ })().finally(() => { pending = null })+ }+ return pending
}
🤖 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/ai-byok/src/client/passkey.ts` around lines 299 - 322, The ensureKey
helper currently allows overlapping calls to start separate WebAuthn ceremonies
because cachedKey/cachedMeta are only populated after awaited work completes.
Update ensureKey to memoize and share a single in-flight promise so concurrent
callers (for example load and save, or rapid double-save) all await the same
ceremony; reuse the existing cachedKey/cachedMeta fast path, but ensure the
first unresolved call to ensureKey owns the idbGet/registerPasskey flow and
later calls return that same promise until it settles.
packages/ai-byok/src/client/validate.ts (1)

22-40: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

No timeout on the validation fetch.

fetch has no AbortSignal/timeout, so a stalled network or slow-to-respond provider endpoint leaves the caller's await (and, per byok-context.tsx, the UI's validating state) hanging indefinitely with no way for the user to recover short of a page reload.

⏱️ Proposed fix
 export async function validateKey(
provider: ProviderId,
key: string,
): Promise<ValidationStatus> {
const config = providerValidateConfig(provider)
if (!config) return 'unsupported'
- const response = await fetch(config.url, {- method: 'GET',- headers: config.headers(key),- })+ const response = await fetch(config.url, {+ method: 'GET',+ headers: config.headers(key),+ signal: AbortSignal.timeout(10_000),+ })
🤖 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/ai-byok/src/client/validate.ts` around lines 22 - 40, The
validateKey() fetch call currently has no timeout, so a slow or stalled provider
can leave the request and validating state hanging forever. Update validateKey()
in validate.ts to use an AbortSignal with a timeout (for example via
AbortController) around the fetch call, and ensure timeout aborts are handled as
a validation failure instead of hanging. Keep the existing
providerValidateConfig(), response handling, and ValidationStatus behavior
intact.
packages/ai-byok/src/server/byok-missing.ts (1)

39-44: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Type guard doesn't fully validate its asserted shape.

isByokMissingBody only checks error.type, not error.provider/error.message, yet it asserts the full ByokMissingBody interface (Line 4-10). Downstream, with-byok.ts calls onMissingKey(body.error.provider) (Line 23) trusting this is a valid ProviderId — an untrusted/malformed 401 body could pass this guard and hand a bad value to the caller's callback.

♻️ Proposed fix to validate provider/message
 import type { ProviderId } from '../shared/providers'
+import { isProviderId } from '../shared/providers'
...
export function isByokMissingBody(value: unknown): value is ByokMissingBody {
if (typeof value !== 'object' || value === null) return false
const { error } = value as { error?: unknown }
if (typeof error !== 'object' || error === null) return false
- return (error as { type?: unknown }).type === 'byok_missing'+ const { type, provider, message } = error as {+ type?: unknown+ provider?: unknown+ message?: unknown+ }+ return (+ type === 'byok_missing' &&+ typeof provider === 'string' &&+ isProviderId(provider) &&+ typeof message === 'string'+ )
}
🤖 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/ai-byok/src/server/byok-missing.ts` around lines 39 - 44,
`isByokMissingBody` currently only verifies `error.type`, so it can accept
malformed bodies while claiming to be `ByokMissingBody`. Update the guard in
`byok-missing.ts` to fully validate the asserted shape by checking
`error.provider` and `error.message` alongside `error.type`, ensuring `provider`
matches a valid `ProviderId`-like string and `message` is a string before
returning true. Keep the checks inside `isByokMissingBody` so `with-byok.ts` can
safely call `onMissingKey(body.error.provider)` without trusting unvalidated
data.
packages/ai-byok/src/react/byok-key-manager.tsx (2)

101-107: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

No confirmation before clearing a saved key.

A single click on "Clear" permanently removes the stored key with no undo/confirm step. Worth a lightweight confirm (or a two-step "Clear?" toggle) to prevent accidental deletion, though the user can always re-enter the key.

🤖 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/ai-byok/src/react/byok-key-manager.tsx` around lines 101 - 107, The
Clear action in the byok-key-manager component triggers immediate key deletion
with no safeguard, so add a lightweight confirmation step before calling
clearKey(provider). Update the button behavior in ByokKeyManager to require a
second explicit confirmation (or a confirm dialog/toggle) before clearing the
saved key, while keeping the existing clearKey provider flow unchanged once
confirmed.

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

Redundant 'masked' in status check.

hasKey already excludes only the 'empty' state, and every non-'empty'KeyStatus variant carries masked (per byok-context.tsx's KeyStatus union). The extra narrowing is harmless but adds noise.

🤖 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/ai-byok/src/react/byok-key-manager.tsx` at line 88, Remove the
redundant masked property check in byok-key-manager.tsx’s render condition: the
hasKey guard already covers all non-empty KeyStatus values, so update the
conditional around the masked UI branch to rely on hasKey alone and keep the
logic aligned with the KeyStatus union defined in byok-context.tsx.
packages/ai-byok/src/react.ts (1)

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

Exporting the raw ByokContext bypasses the useByok safety guard.

Consumers can call useContext(ByokContext) directly and get undefined silently instead of the descriptive error thrown by useByok. Consider keeping ByokContext internal (only exported for testing) and steering public consumers exclusively to useByok.

🤖 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/ai-byok/src/react.ts` at line 4, The public re-export in react.ts is
exposing ByokContext directly, which lets consumers bypass the safety checks in
useByok and receive undefined silently. Remove ByokContext from the public
export surface in react.ts, keep it internal or test-only in react/byok-context,
and ensure consumers are directed to use the useByok hook (and ByokProvider) for
all supported access paths.
🤖 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 `@examples/ts-react-chat/src/routes/api.tanchat.ts`:
- Around line 22-24: The import block in api.tanchat.ts violates the
import/order lint rule because the value import from BYOK config is placed
before the type-only import. Reorder the imports so the type import from
`@tanstack/ai-byok/server` stays grouped correctly and the value import
BYOK_PROVIDER_MAP/byokIdForProvider from `@/lib/byok-config` comes after it; keep
byokMissing and getByokKey with the other server imports and let the import
grouping in the module follow the linter’s expected order.
- Line 260: The generic type parameter name in byokAdapter violates the
TypeScript naming convention rule; rename M to a valid type-parameter identifier
that matches the required pattern, and update any references to that type
parameter within byokAdapter accordingly. Keep the change scoped to the
byokAdapter declaration and its uses so the lint error is resolved without
altering behavior.
In `@examples/ts-react-chat/src/routes/index.tsx`:
- Around line 37-39: The import order in the route module is violating the
import/order lint rule, so adjust the grouped and sorted imports near the top of
the file. Reorder the type-only and value imports involving ProviderId,
ByokKeyDialog, and byokIdForProvider/getEnvKeyStatus to match the project’s
import grouping conventions, then verify the file passes lint (or use the
auto-fix from the linter).
- Around line 877-882: Remove the unnecessary optional chaining in the
platformAuth calculation inside the index route component:
isPasskeyStorageSupported() already guards globalThis.PublicKeyCredential, so
update the PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()
call to use it directly instead of ?. to satisfy no-unnecessary-condition while
keeping the existing runtime behavior unchanged.
In `@packages/ai-byok/src/client/passkey.ts`:
- Around line 57-64: The support check in isPasskeyStorageSupported() is unsafe
because it dereferences navigator.credentials.create directly, so it can throw
when navigator exists but credentials is undefined. Update the guard in this
function to safely probe navigator.credentials with optional chaining or an
explicit credentials existence check before accessing create, matching the safer
PublicKeyCredential probe so the fallback path can run.
In `@packages/ai-byok/src/react/byok-key-manager.tsx`:
- Around line 121-129: The password input placeholder in byok-key-manager.tsx is
using the raw provider id instead of the user-facing provider label, causing
inconsistent copy with the row header. Update the placeholder logic in the BYOK
key input to use the same label source as the header, namely
BYOK_PROVIDERS[provider].label, while preserving the existing Replace key…
behavior when hasKey is true.
- Around line 93-119: The `ByokKeyManager` action handlers are swallowing async
failures from `setKey` and `clearKey`, so rejected promises never reach the
user. Update the `Validate`, `Clear`, and form submit flows in `ByokKeyManager`
to handle rejections explicitly, similar to how `unlock()` surfaces
`unlockError`, and avoid clearing `draft` until `setKey` succeeds. Use the
existing `validateKey`, `clearKey`, and `setKey` entry points to attach error
handling and show the failure state instead of silently ignoring it.
In `@packages/ai-byok/src/shared/providers.ts`:
- Around line 62-70: The Gemini validation note in providers.ts is inaccurate:
the validate block for the gemini provider uses the x-goog-api-key header, not a
query parameter. Update the inline comment near the gemini validate
configuration to describe the actual header-based authentication used by the
validate URL, so the comment matches the behavior of the gemini provider
definition.
In `@testing/e2e/src/routes/byok.tsx`:
- Around line 4-12: The import order in byok.tsx violates the import/order rule
because the type-only `@tanstack/ai-byok/react` import is placed after the local
`@/components/ChatUI` import. Reorder the imports in the byok module so all
`@tanstack/ai-byok/react` imports (including the Keyring/KeyringStorage type
import) come before the local ChatUI import, keeping the existing grouped
structure intact.
---
Nitpick comments:
In `@packages/ai-byok/src/client/passkey.ts`:
- Around line 299-322: The ensureKey helper currently allows overlapping calls
to start separate WebAuthn ceremonies because cachedKey/cachedMeta are only
populated after awaited work completes. Update ensureKey to memoize and share a
single in-flight promise so concurrent callers (for example load and save, or
rapid double-save) all await the same ceremony; reuse the existing
cachedKey/cachedMeta fast path, but ensure the first unresolved call to
ensureKey owns the idbGet/registerPasskey flow and later calls return that same
promise until it settles.
In `@packages/ai-byok/src/client/validate.ts`:
- Around line 22-40: The validateKey() fetch call currently has no timeout, so a
slow or stalled provider can leave the request and validating state hanging
forever. Update validateKey() in validate.ts to use an AbortSignal with a
timeout (for example via AbortController) around the fetch call, and ensure
timeout aborts are handled as a validation failure instead of hanging. Keep the
existing providerValidateConfig(), response handling, and ValidationStatus
behavior intact.
In `@packages/ai-byok/src/react.ts`:
- Line 4: The public re-export in react.ts is exposing ByokContext directly,
which lets consumers bypass the safety checks in useByok and receive undefined
silently. Remove ByokContext from the public export surface in react.ts, keep it
internal or test-only in react/byok-context, and ensure consumers are directed
to use the useByok hook (and ByokProvider) for all supported access paths.
In `@packages/ai-byok/src/react/byok-key-manager.tsx`:
- Around line 101-107: The Clear action in the byok-key-manager component
triggers immediate key deletion with no safeguard, so add a lightweight
confirmation step before calling clearKey(provider). Update the button behavior
in ByokKeyManager to require a second explicit confirmation (or a confirm
dialog/toggle) before clearing the saved key, while keeping the existing
clearKey provider flow unchanged once confirmed.
- Line 88: Remove the redundant masked property check in byok-key-manager.tsx’s
render condition: the hasKey guard already covers all non-empty KeyStatus
values, so update the conditional around the masked UI branch to rely on hasKey
alone and keep the logic aligned with the KeyStatus union defined in
byok-context.tsx.
In `@packages/ai-byok/src/server/byok-missing.ts`:
- Around line 39-44: `isByokMissingBody` currently only verifies `error.type`,
so it can accept malformed bodies while claiming to be `ByokMissingBody`. Update
the guard in `byok-missing.ts` to fully validate the asserted shape by checking
`error.provider` and `error.message` alongside `error.type`, ensuring `provider`
matches a valid `ProviderId`-like string and `message` is a string before
returning true. Keep the checks inside `isByokMissingBody` so `with-byok.ts` can
safely call `onMissingKey(body.error.provider)` without trusting unvalidated
data.
In `@packages/ai-byok/tests/byok.test.ts`:
- Around line 1-16: The test file import ordering is violating lint rules: the
named import from byok helpers has an out-of-order symbol, and the type-only
import is placed before the client/passkey imports. Reorder the imports in
byok.test.ts so the named specifiers in the ../src/index import are alphabetized
and the type import for Keyring is moved to the correct position after the
../src/client/passkey import, keeping the existing symbols and groupings intact.
In `@packages/ai-byok/tests/react.test.tsx`:
- Around line 6-7: The React test file has duplicate type-only imports from the
same module, triggering import/no-duplicates. Update the import section in
react.test.tsx to merge the Keyring and KeyringStorage type imports into a
single import statement from ../src/index, keeping the existing type-only form
and preserving the referenced symbols.
🪄 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: b9c868f3-0974-4289-8a38-e41c277bf284

📥 Commits

Reviewing files that changed from the base of the PR and between e3de949 and cd6251e.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (35)
  • .changeset/byok-package.md
  • examples/ts-react-chat/package.json
  • examples/ts-react-chat/src/components/ByokKeyDialog.tsx
  • examples/ts-react-chat/src/lib/byok-config.ts
  • examples/ts-react-chat/src/routes/api.tanchat.ts
  • examples/ts-react-chat/src/routes/index.tsx
  • knip.json
  • packages/ai-byok/README.md
  • packages/ai-byok/package.json
  • packages/ai-byok/src/client/keyring.ts
  • packages/ai-byok/src/client/passkey.ts
  • packages/ai-byok/src/client/storage.ts
  • packages/ai-byok/src/client/validate.ts
  • packages/ai-byok/src/client/with-byok.ts
  • packages/ai-byok/src/index.ts
  • packages/ai-byok/src/react.ts
  • packages/ai-byok/src/react/byok-context.tsx
  • packages/ai-byok/src/react/byok-key-manager.tsx
  • packages/ai-byok/src/react/use-byok.ts
  • packages/ai-byok/src/server.ts
  • packages/ai-byok/src/server/byok-missing.ts
  • packages/ai-byok/src/server/get-byok-key.ts
  • packages/ai-byok/src/server/scrub.ts
  • packages/ai-byok/src/shared/providers.ts
  • packages/ai-byok/tests/byok.test.ts
  • packages/ai-byok/tests/react.test.tsx
  • packages/ai-byok/tsconfig.json
  • packages/ai-byok/vite.config.ts
  • testing/e2e/README.md
  • testing/e2e/package.json
  • testing/e2e/src/lib/providers.ts
  • testing/e2e/src/routeTree.gen.ts
  • testing/e2e/src/routes/api.byok-chat.ts
  • testing/e2e/src/routes/byok.tsx
  • testing/e2e/tests/byok.spec.ts

Comment threadexamples/ts-react-chat/src/routes/api.tanchat.ts Outdated
Comment threadexamples/ts-react-chat/src/routes/api.tanchat.ts Outdated
Comment threadexamples/ts-react-chat/src/routes/index.tsx Outdated
Comment threadexamples/ts-react-chat/src/routes/index.tsx Outdated
Comment threadpackages/ai-client/src/byok/passkey.ts
Comment threadpackages/ai-byok/src/react/byok-key-manager.tsx Outdated
Comment threadpackages/ai-byok/src/react/byok-key-manager.tsx Outdated
Comment threadpackages/ai-byok/src/shared/providers.ts Outdated
Comment threadtesting/e2e/src/routes/byok.tsx Outdated

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 8

Caution

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

⚠️ Outside diff range comments (2)
examples/ts-react-chat/src/routes/index.tsx (2)

389-392: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Avoid mutating refs during render.

Writing to keysRef.current and statusRef.current during the render phase violates React's pure function rules and can cause state inconsistencies in React 19's Concurrent Mode or Strict Mode. Move these mutations into a useEffect hook to ensure they only execute after a render commits.

🔒️ Proposed fix to synchronize refs safely
- const keysRef = useRef(keys)- keysRef.current = keys- const statusRef = useRef(status)- statusRef.current = status+ const keysRef = useRef(keys)+ const statusRef = useRef(status)++ useEffect(() => {+ keysRef.current = keys+ statusRef.current = status+ }, [keys, status])
🤖 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 `@examples/ts-react-chat/src/routes/index.tsx` around lines 389 - 392, Update
the ref synchronization near keysRef and statusRef so render no longer assigns
to either .current; synchronize both refs inside a useEffect that depends on
keys and status, while preserving their latest committed values for the existing
consumers.

393-424: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Prevent UI flash during initial environment key status fetch.

Initializing envKeyStatus to an empty object {} causes !envKeyStatus[activeByokId] to evaluate to true on the first render before the server response arrives, flashing the "No key..." warning prematurely. Introduce a loaded state to prevent this visual glitch.

✨ Proposed fix to delay `notUsable` evaluation
- const [envKeyStatus, setEnvKeyStatus] = useState<- Partial<Record<ProviderId, boolean>>- >({})- useEffect(() => {- void getEnvKeyStatus().then(setEnvKeyStatus)- }, [])+ const [envKeyStatus, setEnvKeyStatus] = useState<+ Partial<Record<ProviderId, boolean>>+ >({})+ const [isEnvKeyLoaded, setIsEnvKeyLoaded] = useState(false)+ useEffect(() => {+ void getEnvKeyStatus().then((res) => {+ setEnvKeyStatus(res)+ setIsEnvKeyLoaded(true)+ })+ }, [])
// Key dialog, opened either by the toolbar icon or reactively when the relay
// reports a missing key.
const [keyDialog, setKeyDialog] = useState<{
open: boolean
provider: ProviderId | null
}>({ open: false, provider: null })
// The relay returned a byokMissing 401 (no server key, no BYOK key). If we
// already hold that key but it's locked, unlock it; otherwise prompt to add.
const handleMissingKey = useCallback(
(provider: ProviderId) => {
if (statusRef.current[provider]?.state === 'locked') {
void unlock()
} else {
setKeyDialog({ open: true, provider })
}
},
[unlock],
)
const activeByokId = byokIdForProvider(selectedModel.provider)
// The selected model can't run right now if its provider has no server key
// and no decrypted key in the browser.
const notUsable =
- activeByokId != null && !envKeyStatus[activeByokId] && !keys[activeByokId]+ isEnvKeyLoaded &&+ activeByokId != null &&+ !envKeyStatus[activeByokId] &&+ !keys[activeByokId]
🤖 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 `@examples/ts-react-chat/src/routes/index.tsx` around lines 393 - 424, Track
whether the environment key status request has completed alongside envKeyStatus,
updating that flag in the getEnvKeyStatus flow. Update notUsable to remain false
until the status is loaded, then apply the existing provider-key checks so the
“No key” warning does not flash on the initial render.
🧹 Nitpick comments (1)
docs/api/ai-byok.md (1)

275-277: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Specify a language for the fenced code block.

This fenced code block lacks a language identifier, which triggers a markdownlint warning (MD040).

♻️ Proposed fix
-```+```http
x-byok-<provider>: <api-key>
</details>
<details>
<summary>🤖 Prompt for AI Agents</summary>

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

In @docs/api/ai-byok.md around lines 275 - 277, Specify the HTTP language
identifier on the fenced code block containing the x-byok header example by
changing its opening fence to use http.


</details>
<!-- cr-comment:v1:706760bc5f68c00716f954e6 -->
_Source: Linters/SAST tools_
</blockquote></details>
</blockquote></details>
<details>
<summary>🤖 Prompt for all review comments with AI agents</summary>

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 @docs/advanced/byok.md:

  • Around line 377-379: Add a small client-side code snippet in the “Lower-level
    client API” section showing how to consume the server-provided boolean
    environment-key flags and warn the user before selecting an unsupported model.
    Keep the example limited to flag consumption and UI warning behavior, without
    exposing env-key values.
  • Around line 120-129: Update the handleMissingKey callback in Chat to accept
    the existing ProviderId type instead of string, then access status with that
    typed provider directly and remove the as assertion.
  • Line 167: Update the model identifier passed to createOpenaiChat in the BYOK
    documentation example from gpt-5.2 to gpt-5.5, leaving the adapter and apiKey
    usage unchanged.

In @docs/getting-started/overview.md:

  • Around line 104-111: Update the getting-started/overview entry in
    docs/config.json to set updatedAt to 2026-07-16, reflecting the documentation
    change in the overview page.
  • Around line 104-111: Expand the @tanstack/ai-byok section in the overview
    documentation with minimal server and client examples: add a relay endpoint
    showing server-side extraction via getByokKey or byokMissing, and a
    corresponding client snippet demonstrating key transport and consumption through
    the BYOK React bindings. Keep the examples concise and consistent with the
    documented API.

In @packages/ai-byok/src/client/openrouter-pkce.ts:

  • Around line 23-27: Align the PKCE pending state and token exchange parameters
    with the authorization behavior in the OpenRouter PKCE flow. When useS256 is
    false, do not retain or submit a plain codeVerifier unless the authorization URL
    also sends the matching plain challenge; otherwise omit the verifier fields for
    that path. Update the OpenRouterPkcePending handling and related
    authorization/exchange logic while preserving the S256 flow.

In @packages/ai-byok/src/react/use-openrouter-pkce.ts:

  • Around line 35-60: Update the useEffect in the PKCE hook to deduplicate
    concurrent completeOpenRouterPkceFromUrl exchanges across React Strict Mode
    re-runs, using a shared in-flight promise or completion ref. Ensure both effect
    instances reuse the same one-time-code exchange, while cancellation still
    prevents stale instances from updating setKey, setError, or setCompleting.

In @packages/ai-byok/tests/byok.test.ts:

  • Line 1: Relocate the unit tests from the dedicated tests directory into the
    corresponding src directories, placing each test alongside the source it covers;
    split the combined coverage into focused files such as passkey.test.ts and
    openrouter-pkce.test.ts where appropriate, while preserving the existing test
    behavior.

Outside diff comments:
In @examples/ts-react-chat/src/routes/index.tsx:

  • Around line 389-392: Update the ref synchronization near keysRef and statusRef
    so render no longer assigns to either .current; synchronize both refs inside a
    useEffect that depends on keys and status, while preserving their latest
    committed values for the existing consumers.
  • Around line 393-424: Track whether the environment key status request has
    completed alongside envKeyStatus, updating that flag in the getEnvKeyStatus
    flow. Update notUsable to remain false until the status is loaded, then apply
    the existing provider-key checks so the “No key” warning does not flash on the
    initial render.

Nitpick comments:
In @docs/api/ai-byok.md:

  • Around line 275-277: Specify the HTTP language identifier on the fenced code
    block containing the x-byok header example by changing its opening fence to use
    http.

</details>
<details>
<summary>🪄 Autofix (Beta)</summary>
Fix all unresolved CodeRabbit comments on this PR:
- [ ] <!-- {"checkboxId": "4b0d0e0a-96d7-4f10-b296-3a18ea78f0b9"} --> Push a commit to this branch (recommended)
- [ ] <!-- {"checkboxId": "ff5b1114-7d8c-49e6-8ac1-43f82af23a33"} --> Create a new PR with the fixes
</details>
---
<details>
<summary>ℹ️ Review info</summary>
<details>
<summary>⚙️ Run configuration</summary>
**Configuration used**: defaults
**Review profile**: CHILL
**Plan**: Pro
**Run ID**: `50886328-979f-4edb-b72a-6927d656960a`
</details>
<details>
<summary>📥 Commits</summary>
Reviewing files that changed from the base of the PR and between 31a8d7242de37f7129fe902c454923fb2c5291ad and 2eafb8f4f3849f2391c1639afa5b5cc7dab5f1ae.
</details>
<details>
<summary>📒 Files selected for processing (16)</summary>
* `docs/advanced/byok.md`
* `docs/api/ai-byok.md`
* `docs/chat/connection-adapters.md`
* `docs/config.json`
* `docs/getting-started/overview.md`
* `examples/ts-react-chat/src/components/ByokKeyDialog.tsx`
* `examples/ts-react-chat/src/routes/index.tsx`
* `packages/ai-byok/README.md`
* `packages/ai-byok/src/client/openrouter-pkce.ts`
* `packages/ai-byok/src/client/passkey.ts`
* `packages/ai-byok/src/index.ts`
* `packages/ai-byok/src/react.ts`
* `packages/ai-byok/src/react/byok-context.tsx`
* `packages/ai-byok/src/react/byok-key-manager.tsx`
* `packages/ai-byok/src/react/use-openrouter-pkce.ts`
* `packages/ai-byok/tests/byok.test.ts`
</details>
<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (6)</summary>
* packages/ai-byok/src/react/byok-key-manager.tsx
* packages/ai-byok/src/react.ts
* examples/ts-react-chat/src/components/ByokKeyDialog.tsx
* packages/ai-byok/src/react/byok-context.tsx
* packages/ai-byok/src/client/passkey.ts
* packages/ai-byok/README.md
</details>
</details>
<!-- This is an auto-generated comment by CodeRabbit for review status -->

Comment threaddocs/advanced/byok.md Outdated
Comment threaddocs/advanced/byok.md Outdated
Comment threaddocs/advanced/byok.md Outdated
Comment threaddocs/getting-started/overview.md Outdated
Comment threadpackages/ai-byok/src/client/openrouter-pkce.ts
Comment threadpackages/ai-byok/src/react/use-openrouter-pkce.ts Outdated
Comment threadpackages/ai-byok/tests/byok.test.ts Outdated

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 `@docs/api/ai-byok.md`:
- Line 275: Update the header example fenced code block in the AI BYOK
documentation to specify the text language identifier, changing the unlabeled
fence to a text-labeled fence so it satisfies markdownlint MD040.
🪄 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: 27c17c30-1230-4c63-a91d-a8ffec076451

📥 Commits

Reviewing files that changed from the base of the PR and between 2eafb8f and 76f5c7b.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (40)
  • .changeset/byok-package.md
  • docs/advanced/byok.md
  • docs/api/ai-byok.md
  • docs/chat/connection-adapters.md
  • docs/config.json
  • docs/getting-started/overview.md
  • examples/ts-react-chat/package.json
  • examples/ts-react-chat/src/components/ByokKeyDialog.tsx
  • examples/ts-react-chat/src/lib/byok-config.ts
  • examples/ts-react-chat/src/routes/api.tanchat.ts
  • examples/ts-react-chat/src/routes/index.tsx
  • knip.json
  • packages/ai-byok/README.md
  • packages/ai-byok/package.json
  • packages/ai-byok/src/client/keyring.ts
  • packages/ai-byok/src/client/openrouter-pkce.ts
  • packages/ai-byok/src/client/passkey.ts
  • packages/ai-byok/src/client/storage.ts
  • packages/ai-byok/src/client/validate.ts
  • packages/ai-byok/src/client/with-byok.ts
  • packages/ai-byok/src/index.ts
  • packages/ai-byok/src/react.ts
  • packages/ai-byok/src/react/byok-context.tsx
  • packages/ai-byok/src/react/byok-key-manager.tsx
  • packages/ai-byok/src/react/use-byok.ts
  • packages/ai-byok/src/react/use-openrouter-pkce.ts
  • packages/ai-byok/src/server.ts
  • packages/ai-byok/src/server/byok-missing.ts
  • packages/ai-byok/src/server/get-byok-key.ts
  • packages/ai-byok/src/server/scrub.ts
  • packages/ai-byok/src/shared/providers.ts
  • packages/ai-byok/tests/byok.test.ts
  • packages/ai-byok/tests/react.test.tsx
  • packages/ai-byok/tsconfig.json
  • packages/ai-byok/vite.config.ts
  • testing/e2e/README.md
  • testing/e2e/package.json
  • testing/e2e/src/lib/providers.ts
  • testing/e2e/src/routeTree.gen.ts
  • testing/e2e/src/routes/api.byok-chat.ts
🚧 Files skipped from review as they are similar to previous changes (36)
  • packages/ai-byok/src/server/get-byok-key.ts
  • packages/ai-byok/src/react/use-byok.ts
  • packages/ai-byok/package.json
  • packages/ai-byok/tsconfig.json
  • examples/ts-react-chat/src/lib/byok-config.ts
  • docs/config.json
  • docs/chat/connection-adapters.md
  • packages/ai-byok/vite.config.ts
  • knip.json
  • packages/ai-byok/src/server.ts
  • packages/ai-byok/src/index.ts
  • testing/e2e/src/lib/providers.ts
  • packages/ai-byok/src/client/validate.ts
  • packages/ai-byok/src/server/byok-missing.ts
  • packages/ai-byok/src/server/scrub.ts
  • packages/ai-byok/src/client/storage.ts
  • .changeset/byok-package.md
  • testing/e2e/README.md
  • packages/ai-byok/src/client/keyring.ts
  • testing/e2e/package.json
  • testing/e2e/src/routes/api.byok-chat.ts
  • packages/ai-byok/tests/react.test.tsx
  • packages/ai-byok/src/shared/providers.ts
  • packages/ai-byok/src/react.ts
  • packages/ai-byok/README.md
  • examples/ts-react-chat/src/routes/index.tsx
  • testing/e2e/src/routeTree.gen.ts
  • packages/ai-byok/src/react/use-openrouter-pkce.ts
  • packages/ai-byok/src/react/byok-key-manager.tsx
  • examples/ts-react-chat/src/components/ByokKeyDialog.tsx
  • packages/ai-byok/src/react/byok-context.tsx
  • packages/ai-byok/src/client/openrouter-pkce.ts
  • examples/ts-react-chat/src/routes/api.tanchat.ts
  • packages/ai-byok/src/client/passkey.ts
  • packages/ai-byok/src/client/with-byok.ts
  • packages/ai-byok/tests/byok.test.ts

Comment threaddocs/api/ai-byok.md Outdated
@github-actionsgithub-actionsBot added waiting-on: maintainer The ball is in the maintainers’ court merge-conflicts Conflicts with the base branch — needs a rebase waiting-on: author Waiting for the author to respond or update and removed waiting-on: maintainer The ball is in the maintainers’ court waiting-on: author Waiting for the author to respond or update merge-conflicts Conflicts with the base branch — needs a rebase labels Aug 13, 2026
@github-actionsgithub-actionsBot added merge-conflicts Conflicts with the base branch — needs a rebase waiting-on: author Waiting for the author to respond or update waiting-on: maintainer The ball is in the maintainers’ court and removed waiting-on: author Waiting for the author to respond or update merge-conflicts Conflicts with the base branch — needs a rebase waiting-on: maintainer The ball is in the maintainers’ court labels Aug 18, 2026
Users define a keyring, pass it into useChat or useGeneration, and store keys in their own UI. Keys travel in x-byok-* headers only.
@AlemTuzlakAlemTuzlak changed the title feat(byok): add @tanstack/ai-byok bring-your-own-key toolkitfeat: add headless BYOK via defineByokAug 18, 2026
@github-actionsgithub-actionsBot removed the merge-conflicts Conflicts with the base branch — needs a rebase label Aug 18, 2026
* Optional BYOK keyring. On each send the client prepares the resolved
* provider and stamps `x-byok-*` request headers. Keys never go in the body.
*/
byok?: ByokClient

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I'm thinking byok and byokProvider should be on the one field. i.e. byok should be {client, provider}

Comment threadpackages/ai/src/byok/providers.ts Outdated
Authorization: `Bearer ${key}`,
})

export const BYOK_PROVIDERS = {

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Not sure about listing providers in the ai package. Did I do that?

Drop the hardcoded BYOK_PROVIDERS catalog. Provider ids are slugs.
Each adapter exports a defineByokProvider object whose id is required
and cannot be optional.
@github-actionsgithub-actionsBot added merge-conflicts Conflicts with the base branch — needs a rebase and removed merge-conflicts Conflicts with the base branch — needs a rebase labels Aug 19, 2026
OpenRouter OAuth mints a user key. completeOpenRouterPkceIntoByok writes
it with the required adapter slug so the relay reads x-byok-openrouter.
@github-actionsgithub-actionsBot added waiting-on: maintainer The ball is in the maintainers’ court and removed waiting-on: author Waiting for the author to respond or update merge-conflicts Conflicts with the base branch — needs a rebase labels Aug 20, 2026
Adapters export env var names on defineByokProvider. Relays call
getByokKey from @tanstack/ai/byok/server so process.env stays out of
the client graph. Remove getByokOrEnvKey.
@github-actionsgithub-actionsBot added merge-conflicts Conflicts with the base branch — needs a rebase waiting-on: author Waiting for the author to respond or update and removed waiting-on: maintainer The ball is in the maintainers’ court labels Aug 20, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merge-conflictsConflicts with the base branch — needs a rebasewaiting-on: authorWaiting for the author to respond or update

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@tombeckenham@jherr@AlemTuzlak