Skip to content

feat: add rerank() activity with Cohere and OpenRouter adapters#845

Open
AlemTuzlak wants to merge 7 commits into
mainfrom
worktree-resilient-popping-knuth
Open

feat: add rerank() activity with Cohere and OpenRouter adapters#845
AlemTuzlak wants to merge 7 commits into
mainfrom
worktree-resilient-popping-knuth

Conversation

@AlemTuzlak

@AlemTuzlak AlemTuzlak commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

What

A provider-agnostic rerank() activity in @tanstack/ai for reordering documents by relevance to a query, plus rerank adapters in two provider packages:

  • @tanstack/ai-cohere (new package) — cohereRerank / createCohereRerank, Cohere /v2/rerank over raw fetch.
  • @tanstack/ai-openrouteropenRouterRerank / createOpenRouterRerank, via the @openrouter/sdk rerank method. Supports every OpenRouter rerank model (Cohere rerank-v3.5/4-fast/4-pro, NVIDIA llama-nemotron-rerank, and any future slug).

Highlights

  • Generic over document type — strings or JSON-serializable objects; the original element comes back in the result, fully typed.
  • House-style paritytopN, abortSignal cancellation, observe-only GenerationMiddleware (start/usage/finish/abort/error), rerank:* devtools events. Bills in search units via usage.unitsBilled (OpenRouter also surfaces cost).
  • Cohere uses raw fetch; OpenRouter uses the SDK so non-Cohere models route correctly, with optional provider routing via modelOptions.

Tests & docs

Unit tests for the activity, Cohere adapter (mocked fetch), and OpenRouter adapter (mocked SDK). New Reranking guide + Cohere adapter page; OpenRouter adapter page gains a Reranking section. E2E intentionally skipped in favor of unit tests.

Changesets: minor for @tanstack/ai, @tanstack/ai-event-client, @tanstack/ai-cohere, @tanstack/ai-openrouter.

Summary by CodeRabbit

  • New Features
    • Added provider-agnostic rerank() activity with topN and abortSignal cancellation.
    • Introduced Cohere rerank-only adapter and OpenRouter rerank adapter with relevance-based document reordering and usage reporting (including billed units when available).
    • Added rerank middleware lifecycle hooks and new rerank:* devtools events (start/complete/usage).
  • Bug Fixes
    • Improved validation and error handling for empty inputs, unexpected responses, and failed requests.
  • Documentation
    • Added reranking guides plus Cohere/OpenRouter adapter docs and updated site navigation.
  • Tests
    • Added activity and adapter test coverage for mapping, lifecycle hooks, cancellation, and error cases.

Add a provider-agnostic rerank() activity to @tanstack/ai for reordering
documents by relevance to a query, plus a new rerank-only @tanstack/ai-cohere
provider package.

- rerank() is generic over the document type: strings or JSON-serializable
  objects. Object documents are serialized for the provider and the original
  element is returned in the result, fully typed.
- Supports topN, per-request cancellation via abortSignal, observe-only
  GenerationMiddleware (start/usage/finish/abort/error), and rerank:* devtools
  events. Rerank bills in provider search units, surfaced on usage.unitsBilled.
- @tanstack/ai-cohere ships cohereRerank / createCohereRerank, talking to
  Cohere /v2/rerank over fetch (no SDK) with per-model provider options.
- Unit tests for the activity and the Cohere adapter; docs for the rerank
  guide and the Cohere adapter page.
@coderabbitai

coderabbitai Bot commented Jun 25, 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 rerank support to the AI core, Cohere and OpenRouter adapters, rerank devtools events, and matching documentation, navigation, and tests.

Changes

Rerank feature

Layer / File(s) Summary
Core contracts and flow
packages/ai/src/types.ts, packages/ai/src/activities/rerank/adapter.ts, packages/ai/src/activities/rerank/index.ts, packages/ai/src/activities/index.ts, packages/ai/src/index.ts, packages/ai/src/activities/middleware/types.ts, packages/ai/src/middlewares/otel.ts, packages/ai-event-client/src/index.ts, packages/ai/tests/rerank.test.ts
Adds rerank request/result contracts, adapter abstractions, rerank exports, devtools event types, tracing names, and the rerank activity implementation and tests.
Cohere adapter package
packages/ai-cohere/src/*, packages/ai-cohere/tests/*, packages/ai-cohere/package.json, packages/ai-cohere/tsconfig.json, packages/ai-cohere/vite.config.ts, packages/ai-cohere/README.md, packages/ai-cohere/LICENSE
Adds Cohere rerank model metadata, client and environment helpers, the Cohere adapter implementation, package exports, package wiring, and adapter tests.
OpenRouter adapter package
packages/ai-openrouter/src/*, packages/ai-openrouter/tests/*, .changeset/openrouter-rerank.md, docs/adapters/openrouter.md
Adds OpenRouter rerank model metadata, adapter implementation, package exports, adapter tests, changeset notes, and adapter documentation updates.
Docs and navigation
docs/rerank/rerank.md, docs/adapters/cohere.md, docs/config.json, .changeset/rerank-cohere.md
Adds the reranking guide, Cohere adapter docs, docs navigation entries, and the release changeset.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • TanStack/ai#760: Updates the shared generation middleware and OpenTelemetry plumbing that this PR extends for the new rerank activity.

Suggested reviewers

  • tombeckenham

Poem

I nibbled a query and sniffed out the light,
then hopped through the ranks with a merry delight.
Cohere sang softly, the middleware chimed,
and every fine document landed well-timed.
🐰✨ The burrow is tidy; the top picks feel bright.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description covers the change well, but it does not follow the required template sections and is missing the checklist format. Restructure the PR description into the template sections: 🎯 Changes, ✅ Checklist, and 🚀 Release Impact, and include the required checkboxes.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding rerank activity support with Cohere and OpenRouter adapters.
Docstring Coverage ✅ Passed Docstring coverage is 93.33% which is sufficient. The required threshold is 80.00%.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 worktree-resilient-popping-knuth

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-actions Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

🚀 Changeset Version Preview

4 package(s) bumped directly, 29 bumped as dependents.

🟥 Major bumps

Package Version Reason
@tanstack/ai-cohere 0.0.0 → 1.0.0 Changeset
@tanstack/ai-openrouter 0.15.3 → 1.0.0 Changeset
@tanstack/ai-angular 0.1.11 → 1.0.0 Dependent
@tanstack/ai-anthropic 0.15.10 → 1.0.0 Dependent
@tanstack/ai-code-mode 0.3.1 → 1.0.0 Dependent
@tanstack/ai-code-mode-skills 0.3.4 → 1.0.0 Dependent
@tanstack/ai-elevenlabs 0.2.29 → 1.0.0 Dependent
@tanstack/ai-fal 0.9.6 → 1.0.0 Dependent
@tanstack/ai-gemini 0.18.2 → 1.0.0 Dependent
@tanstack/ai-grok 0.14.4 → 1.0.0 Dependent
@tanstack/ai-groq 0.4.14 → 1.0.0 Dependent
@tanstack/ai-isolate-node 0.1.40 → 1.0.0 Dependent
@tanstack/ai-isolate-quickjs 0.1.40 → 1.0.0 Dependent
@tanstack/ai-ollama 0.8.10 → 1.0.0 Dependent
@tanstack/ai-openai 0.15.8 → 1.0.0 Dependent
@tanstack/ai-preact 0.9.15 → 1.0.0 Dependent
@tanstack/ai-react 0.15.15 → 1.0.0 Dependent
@tanstack/ai-react-ui 0.8.11 → 1.0.0 Dependent
@tanstack/ai-solid 0.13.15 → 1.0.0 Dependent
@tanstack/ai-solid-ui 0.7.10 → 1.0.0 Dependent
@tanstack/ai-svelte 0.13.15 → 1.0.0 Dependent
@tanstack/ai-vue 0.13.15 → 1.0.0 Dependent
@tanstack/openai-base 0.9.4 → 1.0.0 Dependent

🟨 Minor bumps

Package Version Reason
@tanstack/ai 0.37.0 → 0.38.0 Changeset
@tanstack/ai-event-client 0.6.8 → 0.7.0 Changeset

🟩 Patch bumps

Package Version Reason
@tanstack/ai-client 0.18.6 → 0.18.7 Dependent
@tanstack/ai-devtools-core 0.4.18 → 0.4.19 Dependent
@tanstack/ai-isolate-cloudflare 0.2.31 → 0.2.32 Dependent
@tanstack/ai-mcp 0.1.10 → 0.1.11 Dependent
@tanstack/ai-vue-ui 0.2.27 → 0.2.28 Dependent
@tanstack/preact-ai-devtools 0.1.61 → 0.1.62 Dependent
@tanstack/react-ai-devtools 0.2.61 → 0.2.62 Dependent
@tanstack/solid-ai-devtools 0.2.61 → 0.2.62 Dependent

@nx-cloud

nx-cloud Bot commented Jun 25, 2026

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit f0f2fbb

Command Status Duration Result
nx run-many --targets=build --exclude=examples/... ✅ Succeeded 1m 27s View ↗

☁️ Nx Cloud last updated this comment at 2026-06-25 17:06:42 UTC

@pkg-pr-new

pkg-pr-new Bot commented Jun 25, 2026

Copy link
Copy Markdown

Open in StackBlitz

@tanstack/ai

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

@tanstack/ai-angular

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

@tanstack/ai-anthropic

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

@tanstack/ai-client

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

@tanstack/ai-code-mode

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

@tanstack/ai-code-mode-skills

npm i https://pkg.pr.new/TanStack/ai/@tanstack/ai-code-mode-skills@845

@tanstack/ai-cohere

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

@tanstack/ai-devtools-core

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

@tanstack/ai-elevenlabs

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

@tanstack/ai-event-client

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

@tanstack/ai-fal

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

@tanstack/ai-gemini

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

@tanstack/ai-grok

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

@tanstack/ai-groq

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

@tanstack/ai-isolate-cloudflare

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

@tanstack/ai-isolate-node

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

@tanstack/ai-isolate-quickjs

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

@tanstack/ai-mcp

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

@tanstack/ai-ollama

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

@tanstack/ai-openai

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

@tanstack/ai-openrouter

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

@tanstack/ai-preact

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

@tanstack/ai-react

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

@tanstack/ai-react-ui

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

@tanstack/ai-solid

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

@tanstack/ai-solid-ui

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

@tanstack/ai-svelte

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

@tanstack/ai-utils

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

@tanstack/ai-vue

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

@tanstack/ai-vue-ui

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

@tanstack/openai-base

npm i https://pkg.pr.new/TanStack/ai/@tanstack/openai-base@845

@tanstack/preact-ai-devtools

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

@tanstack/react-ai-devtools

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

@tanstack/solid-ai-devtools

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

commit: f0f2fbb

@coderabbitai coderabbitai Bot 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: 4

🧹 Nitpick comments (2)
packages/ai/src/activities/rerank/adapter.ts (1)

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

Optional: parameter order makes the default unusable.

config has a default but precedes the required model, so callers can never omit config to rely on the default. Consider reordering (model first) or making both explicit.

🤖 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/src/activities/rerank/adapter.ts` around lines 78 - 81, The
RerankAdapter constructor currently puts the optional config parameter before
the required model, so the default config cannot actually be omitted by callers.
Update the constructor signature in RerankAdapter to either place model before
config or make both parameters explicit, and make sure any call sites or related
initialization in RerankAdapterConfig usage still pass arguments in the correct
order.
packages/ai-cohere/src/adapters/rerank.ts (1)

117-120: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

JSON parse / shape-validation failures bypass logger.errors().

await response.json() can reject on invalid JSON, and the shape-mismatch path throws too — neither routes through logger.errors(). The upstream RerankOptions contract states adapters must call logger.errors() in catch blocks, so these failures will be silent in adapter logging unlike the fetch and !response.ok paths.

🛠️ Proposed logging on parse/validation failure
-    const json: unknown = await response.json()
-    if (!isCohereRerankResponse(json)) {
-      throw new Error('Cohere rerank response had an unexpected shape')
-    }
+    let json: unknown
+    try {
+      json = await response.json()
+    } catch (error) {
+      logger.errors(`${this.name}.rerank fatal`, {
+        error,
+        source: `${this.name}.rerank`,
+      })
+      throw error
+    }
+    if (!isCohereRerankResponse(json)) {
+      const error = new Error('Cohere rerank response had an unexpected shape')
+      logger.errors(`${this.name}.rerank fatal`, {
+        error,
+        source: `${this.name}.rerank`,
+      })
+      throw error
+    }
🤖 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-cohere/src/adapters/rerank.ts` around lines 117 - 120, The JSON
parse and response-shape validation path in rerank.ts bypasses adapter error
logging, so update the rerank adapter flow around `response.json()` and
`isCohereRerankResponse` to ensure failures are handled in a catch block that
calls `logger.errors()`. Keep the existing fetch and `!response.ok` logging
behavior, but wrap the parse/validation steps so any invalid JSON or unexpected
shape is logged before rethrowing, using the same `RerankOptions` logger
contract.
🤖 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/rerank/rerank.md`:
- Around line 161-165: The server example in rerank.md uses an `as` type
assertion on `body`, which violates the docs guideline and should be removed.
Update the example to type-check without assertions by validating `body` first
with a runtime guard or schema, then access `query`, `documents`, and `topN`
through a narrowed type. Refer to the server example destructuring of `body` in
the rerank docs and keep the sample aligned with the existing request-handling
flow.

In `@packages/ai-cohere/src/utils/client.ts`:
- Around line 23-43: The env lookup in getCohereApiKeyFromEnv currently prefers
window.env and skips process.env whenever window exists, which breaks cases
where window.env is unset but COHERE_API_KEY is still available via process.env.
Update the fallback logic in getCohereApiKeyFromEnv so it first reads window.env
when present, but if that is missing or undefined it then checks process.env
before throwing. Keep the existing error behavior and use the same
COHERE_API_KEY symbol path so the function works in browser-like and
Electron/bundler setups.

In `@packages/ai/src/activities/rerank/adapter.ts`:
- Around line 87-89: The fallback ID generation in generateId() can return an
empty or very short random suffix because substring(7) is applied to a
non-fixed-length Math.random().toString(36) value. Update the generateId()
implementation in the rerank adapter to use a more robust random segment, and
align it with the createId() approach from index.ts by slicing a stable range
such as slice(2, 9) so the tracing/devtools correlation IDs remain sufficiently
unique.

In `@packages/ai/src/activities/rerank/index.ts`:
- Around line 210-215: The rerank mapping in the `ranking` construction is
trusting provider-supplied `r.index` values and masking invalid lookups with a
type cast, so add a validation step in the `rerank` flow before remapping
`documents[r.index]`. If any index is out of range or resolves to `undefined`,
throw a clear error instead of building `ranking` and `rerankedDocuments` with
corrupted entries, and keep the guard close to the `result.ranking.map(...)`
logic so the failure is caught where the provider contract is consumed.

---

Nitpick comments:
In `@packages/ai-cohere/src/adapters/rerank.ts`:
- Around line 117-120: The JSON parse and response-shape validation path in
rerank.ts bypasses adapter error logging, so update the rerank adapter flow
around `response.json()` and `isCohereRerankResponse` to ensure failures are
handled in a catch block that calls `logger.errors()`. Keep the existing fetch
and `!response.ok` logging behavior, but wrap the parse/validation steps so any
invalid JSON or unexpected shape is logged before rethrowing, using the same
`RerankOptions` logger contract.

In `@packages/ai/src/activities/rerank/adapter.ts`:
- Around line 78-81: The RerankAdapter constructor currently puts the optional
config parameter before the required model, so the default config cannot
actually be omitted by callers. Update the constructor signature in
RerankAdapter to either place model before config or make both parameters
explicit, and make sure any call sites or related initialization in
RerankAdapterConfig usage still pass arguments in the correct order.
🪄 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: 24968d99-0670-48fd-ac64-4f4ae084631a

📥 Commits

Reviewing files that changed from the base of the PR and between a6cceba and fe6a81c.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (23)
  • .changeset/rerank-cohere.md
  • docs/adapters/cohere.md
  • docs/config.json
  • docs/rerank/rerank.md
  • packages/ai-cohere/LICENSE
  • packages/ai-cohere/README.md
  • packages/ai-cohere/package.json
  • packages/ai-cohere/src/adapters/rerank.ts
  • packages/ai-cohere/src/index.ts
  • packages/ai-cohere/src/model-meta.ts
  • packages/ai-cohere/src/utils/client.ts
  • packages/ai-cohere/tests/rerank-adapter.test.ts
  • packages/ai-cohere/tsconfig.json
  • packages/ai-cohere/vite.config.ts
  • packages/ai-event-client/src/index.ts
  • packages/ai/src/activities/index.ts
  • packages/ai/src/activities/middleware/types.ts
  • packages/ai/src/activities/rerank/adapter.ts
  • packages/ai/src/activities/rerank/index.ts
  • packages/ai/src/index.ts
  • packages/ai/src/middlewares/otel.ts
  • packages/ai/src/types.ts
  • packages/ai/tests/rerank.test.ts

Comment thread docs/rerank/rerank.md Outdated
Comment thread packages/ai-cohere/src/utils/client.ts
Comment thread packages/ai/src/activities/rerank/adapter.ts
Comment thread packages/ai/src/activities/rerank/index.ts Outdated
Add openRouterRerank / createOpenRouterRerank for reordering documents by
relevance through OpenRouter's unified /v1/rerank endpoint, using the
@openrouter/sdk rerank method.

- Model-agnostic: supports every rerank model OpenRouter offers (Cohere
  rerank-v3.5 / 4-fast / 4-pro, NVIDIA llama-nemotron-rerank, and any future
  slug) via an open model type with known-model autocomplete.
- Maps the SDK response to the shared RerankAdapterResult, including usage
  (searchUnits -> unitsBilled, cost, totalTokens).
- Optional provider-routing preferences via modelOptions.provider; abortSignal
  forwarded through the SDK fetchOptions.
- Unit tests (SDK mocked) and docs (rerank guide + OpenRouter adapter page).
@AlemTuzlak AlemTuzlak changed the title feat: add rerank() activity and @tanstack/ai-cohere adapter feat: add rerank() activity with Cohere and OpenRouter adapters Jun 25, 2026
autofix-ci Bot and others added 2 commits June 25, 2026 16:30
…Title)

The SDK-based OpenRouter adapters use SDKOptions, whose attribution field is
appTitle, not xTitle (xTitle only exists on the legacy raw-fetch client helper).
Fix the rerank section of the OpenRouter adapter docs and the changeset.

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

♻️ Duplicate comments (1)
docs/rerank/rerank.md (1)

189-193: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove the remaining as cast from the server example.

The server sample still uses a type assertion, which breaks the docs rule and makes the example stop type-checking cleanly without casts. Narrow body with a guard/schema instead.

As per coding guidelines, docs/**/*.md: No as type-assertion casts in code samples under docs/.

🤖 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 `@docs/rerank/rerank.md` around lines 189 - 193, The server example in the
rerank docs still uses a type assertion on body, which should be removed to
match the docs rule. Update the code sample around the request handling in the
rerank server example to narrow body with a guard or schema instead of using an
as cast, and keep the destructuring of query, documents, and topN inside the
validated path.

Source: Coding guidelines

🧹 Nitpick comments (1)
docs/adapters/openrouter.md (1)

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

Use the current Cohere rerank model in the example.

The example still anchors on cohere/rerank-v3.5, even though this section already calls out newer Cohere slugs. Please switch the sample (and the explicit-key variant in the paragraph below) to whatever model-meta.ts marks as the latest Cohere rerank model. As per coding guidelines, "docs/**/*.md: Use the latest model per provider in documentation examples, sourced from each adapter's model-meta.ts`".

🤖 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 `@docs/adapters/openrouter.md` around lines 263 - 275, The OpenRouter rerank
example is still using an outdated Cohere model slug. Update the sample in the
openRouterRerank/createOpenRouterRerank snippet to the latest Cohere rerank
model listed in model-meta.ts, and make sure the explicit-key example in the
explanatory paragraph uses the same current slug.

Source: Coding guidelines

🤖 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/ai-openrouter/tests/rerank-adapter.test.ts`:
- Around line 1-134: This test suite is in the wrong location: the
`OpenRouterRerankAdapter` tests should be colocated with the adapter source
rather than kept under a package-level `tests` folder. Move the
`rerank-adapter.test.ts` suite next to `createOpenRouterRerank` in
`src/adapters/rerank.ts`, keeping the same Vitest coverage and imports, so the
`*.test.ts` convention is followed.

---

Duplicate comments:
In `@docs/rerank/rerank.md`:
- Around line 189-193: The server example in the rerank docs still uses a type
assertion on body, which should be removed to match the docs rule. Update the
code sample around the request handling in the rerank server example to narrow
body with a guard or schema instead of using an as cast, and keep the
destructuring of query, documents, and topN inside the validated path.

---

Nitpick comments:
In `@docs/adapters/openrouter.md`:
- Around line 263-275: The OpenRouter rerank example is still using an outdated
Cohere model slug. Update the sample in the
openRouterRerank/createOpenRouterRerank snippet to the latest Cohere rerank
model listed in model-meta.ts, and make sure the explicit-key example in the
explanatory paragraph uses the same current slug.
🪄 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: b3cac01f-540f-4e5a-9dbe-198bb4005e74

📥 Commits

Reviewing files that changed from the base of the PR and between fe6a81c and a5f1578.

📒 Files selected for processing (8)
  • .changeset/openrouter-rerank.md
  • docs/adapters/openrouter.md
  • docs/config.json
  • docs/rerank/rerank.md
  • packages/ai-openrouter/src/adapters/rerank.ts
  • packages/ai-openrouter/src/index.ts
  • packages/ai-openrouter/src/rerank/rerank-provider-options.ts
  • packages/ai-openrouter/tests/rerank-adapter.test.ts
✅ Files skipped from review due to trivial changes (2)
  • .changeset/openrouter-rerank.md
  • packages/ai-openrouter/src/rerank/rerank-provider-options.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • docs/config.json

Comment on lines +1 to +134
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { rerank } from '@tanstack/ai'
import { createOpenRouterRerank } from '../src/adapters/rerank'

// Mock the OpenRouter SDK so the adapter's `new OpenRouter().rerank.rerank()`
// call resolves to a controlled response. `vi.hoisted` lets the (hoisted)
// vi.mock factory reference the spy.
const { rerankFn } = vi.hoisted(() => ({ rerankFn: vi.fn() }))

vi.mock('@openrouter/sdk', () => ({
// A class so `new OpenRouter(config)` is constructable; each instance exposes
// the spied `rerank.rerank`.
OpenRouter: class {
rerank = { rerank: rerankFn }
},
}))

beforeEach(() => {
rerankFn.mockReset()
})

const documents = ['sunny day at the beach', 'rainy afternoon in the city']

/** A parsed SDK rerank response (camelCase, as the SDK returns it). */
function sdkResponse() {
return {
id: 'or-1',
model: 'cohere/rerank-v3.5',
results: [
{ document: { text: documents[1] }, index: 1, relevanceScore: 0.97 },
{ document: { text: documents[0] }, index: 0, relevanceScore: 0.1 },
],
usage: { searchUnits: 1, cost: 0.002, totalTokens: 20 },
}
}

const adapter = () => createOpenRouterRerank('cohere/rerank-v3.5', 'sk-or-test')

describe('OpenRouterRerankAdapter', () => {
it('calls the SDK rerank with the request body and maps the response', async () => {
rerankFn.mockResolvedValue(sdkResponse())

const result = await rerank({
adapter: adapter(),
query: 'talk about rain',
documents,
topN: 2,
})

expect(rerankFn).toHaveBeenCalledTimes(1)
expect(rerankFn.mock.calls[0]![0]).toEqual({
requestBody: {
model: 'cohere/rerank-v3.5',
query: 'talk about rain',
documents,
topN: 2,
},
})
expect(result.id).toBe('or-1')
expect(result.ranking).toEqual([
{ index: 1, score: 0.97, document: documents[1] },
{ index: 0, score: 0.1, document: documents[0] },
])
})

it('maps SDK usage (searchUnits/cost/totalTokens)', async () => {
rerankFn.mockResolvedValue(sdkResponse())

const result = await rerank({ adapter: adapter(), query: 'q', documents })

expect(result.usage.unitsBilled).toBe(1)
expect(result.usage.cost).toBe(0.002)
expect(result.usage.totalTokens).toBe(20)
})

it('works with a non-Cohere model slug', async () => {
rerankFn.mockResolvedValue({
...sdkResponse(),
model: 'nvidia/llama-nemotron-rerank-vl-1b-v2',
})

await rerank({
adapter: createOpenRouterRerank(
'nvidia/llama-nemotron-rerank-vl-1b-v2',
'sk-or-test',
),
query: 'q',
documents,
})

expect(rerankFn.mock.calls[0]![0].requestBody.model).toBe(
'nvidia/llama-nemotron-rerank-vl-1b-v2',
)
})

it('forwards provider routing preferences into the request body', async () => {
rerankFn.mockResolvedValue(sdkResponse())

await rerank({
adapter: adapter(),
query: 'q',
documents,
modelOptions: { provider: { order: ['cohere'] } },
})

expect(rerankFn.mock.calls[0]![0].requestBody.provider).toEqual({
order: ['cohere'],
})
})

it('forwards the abort signal via fetchOptions', async () => {
rerankFn.mockResolvedValue(sdkResponse())
const controller = new AbortController()

await rerank({
adapter: adapter(),
query: 'q',
documents,
abortSignal: controller.signal,
})

expect(rerankFn.mock.calls[0]![1]).toEqual({
fetchOptions: { signal: controller.signal },
})
})

it('throws when the SDK returns a bare string response', async () => {
rerankFn.mockResolvedValue('error: bad request')

await expect(
rerank({ adapter: adapter(), query: 'q', documents, debug: false }),
).rejects.toThrow('unexpected response')
})
})

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.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Move this test beside the adapter it exercises.

This new suite lives under packages/ai-openrouter/tests/, but the repo rule for *.test.ts files is to colocate unit tests with their source. Please move it next to src/adapters/rerank.ts instead of keeping a separate package-level test folder. As per coding guidelines, "**/*.test.ts: Place unit tests alongside source code in *.test.ts files`".

🤖 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-openrouter/tests/rerank-adapter.test.ts` around lines 1 - 134,
This test suite is in the wrong location: the `OpenRouterRerankAdapter` tests
should be colocated with the adapter source rather than kept under a
package-level `tests` folder. Move the `rerank-adapter.test.ts` suite next to
`createOpenRouterRerank` in `src/adapters/rerank.ts`, keeping the same Vitest
coverage and imports, so the `*.test.ts` convention is followed.

Source: Coding guidelines

Replace the @openrouter/sdk module mock with a network-layer fetch intercept
driving the real SDK. Module mocking was sensitive to the CI runner's test
isolation and failed there while passing locally; intercepting fetch exercises
the SDK's real request building and zod response parsing and is environment-
independent.
- rerank() activity: guard provider-returned indices and remove the as-cast,
  throwing a clear error on an out-of-range index instead of yielding undefined.
- BaseRerankAdapter.generateId: use slice(2, 9) so the fallback id is never empty.
- ai-cohere env detection: fall back to process.env when window.env is absent
  (bundler/Electron browser builds).
- docs: replace the `as` cast in the rerank server example with a runtime guard.

@coderabbitai coderabbitai Bot 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 `@packages/ai/tests/rerank.test.ts`:
- Around line 231-237: The rerank bounds test currently uses an obviously
invalid ranking index, which does not protect the exact upper-bound edge. Update
the test in rerank.test.ts near the rerank() rejection assertion to use the
first invalid index for the two-document input, so it specifically validates
that the rerank validation rejects index equal to documents.length and catches
off-by-one regressions.
🪄 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: a90eada4-643a-4f65-817d-15cb5afea3ad

📥 Commits

Reviewing files that changed from the base of the PR and between 224c407 and f0f2fbb.

📒 Files selected for processing (5)
  • docs/rerank/rerank.md
  • packages/ai-cohere/src/utils/client.ts
  • packages/ai/src/activities/rerank/adapter.ts
  • packages/ai/src/activities/rerank/index.ts
  • packages/ai/tests/rerank.test.ts
✅ Files skipped from review due to trivial changes (1)
  • docs/rerank/rerank.md
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/ai-cohere/src/utils/client.ts
  • packages/ai/src/activities/rerank/adapter.ts
  • packages/ai/src/activities/rerank/index.ts

Comment on lines +231 to +237
ranking: [{ index: 5, score: 0.9 }],
usage: { ...zeroUsage },
}))

await expect(
rerank({ adapter, query: 'q', documents: ['a', 'b'] }),
).rejects.toThrow('out-of-range')

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use the first invalid index here to catch off-by-one regressions.

index: 5 proves the guard rejects obviously bad values, but it would still pass if the implementation accidentally allowed index === documents.length. Using index: 2 for ['a', 'b'] exercises the exact upper-bound edge this test is meant to protect.

Suggested change
-      ranking: [{ index: 5, score: 0.9 }],
+      ranking: [{ index: 2, score: 0.9 }],
📝 Committable suggestion

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

Suggested change
ranking: [{ index: 5, score: 0.9 }],
usage: { ...zeroUsage },
}))
await expect(
rerank({ adapter, query: 'q', documents: ['a', 'b'] }),
).rejects.toThrow('out-of-range')
ranking: [{ index: 2, score: 0.9 }],
usage: { ...zeroUsage },
}))
await expect(
rerank({ adapter, query: 'q', documents: ['a', 'b'] }),
).rejects.toThrow('out-of-range')
🤖 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/tests/rerank.test.ts` around lines 231 - 237, The rerank bounds
test currently uses an obviously invalid ranking index, which does not protect
the exact upper-bound edge. Update the test in rerank.test.ts near the rerank()
rejection assertion to use the first invalid index for the two-document input,
so it specifically validates that the rerank validation rejects index equal to
documents.length and catches off-by-one regressions.

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