Skip to content

Conversation

@yujonglee
Copy link
Contributor

@yujonglee yujonglee commented Dec 5, 2025

Summary

Refactors the API service architecture by reorganizing code into domain-specific directories:

API Changes:

  • Moved integration clients (Stripe, Supabase, OpenRouter, PostHog) into integration/ directory
  • Moved middleware (auth, Sentry, Stripe webhook) into middleware/ directory
  • Split routes into separate files by domain (health, llm, stt, webhook)
  • Moved metrics to root level with domain-specific groupings
  • Improved Sentry span handling by storing spans in Hono context

Desktop Changes:

  • Refactored useLLMConnection hook into modular resolver pattern with resolveLLMConnection and createLanguageModel functions
  • Added useLLMConnectionStatus hook for status-only access

Bug Fixes (from CodeRabbit/Claude review):

  • Fixed streaming metrics being recorded before stream completes (now records after controller.close())
  • Added null safety for sentrySpan using optional chaining
  • Changed empty model string to openrouter/auto for proper OpenRouter routing
  • Fixed hyprnote provider baseUrl to include path suffix (/functions/v1/llm)

Review & Testing Checklist for Human

  • Verify OpenRouter model identifiers (moonshotai/kimi-k2-0905, openai/gpt-5.1-chat, etc.) are valid and accessible
  • Test streaming chat completions to confirm metrics are recorded only after stream completes successfully
  • Test hyprnote provider LLM connection in desktop app to verify the baseUrl is correctly constructed (${VITE_API_URL}/functions/v1/llm)
  • Verify Sentry tracing works correctly with the new middleware pattern (check spans appear in Sentry dashboard)

Recommended test plan:

  1. Run the API locally and test /chat/completions endpoint with both streaming and non-streaming requests
  2. Test the desktop app with hyprnote provider selected to verify LLM calls work
  3. Check Sentry dashboard for proper span attribution after making API requests

Notes

Link to Devin run: https://app.devin.ai/sessions/417a0eb8e7164599b6bade0455eabfc4
Requested by: yujonglee ([email protected]) / @yujonglee

@netlify
Copy link

netlify bot commented Dec 5, 2025

Deploy Preview for hyprnote-storybook ready!

Name Link
🔨 Latest commit fb92040
🔍 Latest deploy log https://app.netlify.com/projects/hyprnote-storybook/deploys/6932b5196d743f000882e85b
😎 Deploy Preview https://deploy-preview-2145--hyprnote-storybook.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 5, 2025

📝 Walkthrough

Walkthrough

Consolidates integrations and middleware, splits routes into modular handlers (health, llm, stt, webhook), introduces an OpenRouter-backed LLM integration, adds a Sentry tracing middleware that stores spans in bindings, centralizes Stripe/Supabase clients, and refactors desktop LLM connection logic into resolver-driven provider handling.

Changes

Cohort / File(s) Change Summary
Integration barrel & adapters
apps/api/src/integration/*
apps/api/src/integration/index.ts, apps/api/src/integration/supabase.ts, apps/api/src/integration/stripe.ts, apps/api/src/integration/openrouter.ts, apps/api/src/integration/posthog.ts
Adds integration barrel and adapter modules: exports supabaseAdmin, centralized stripe, an OpenRouter-backed openai plus getModels, and adjusts posthog env import path.
Middleware reorganization
apps/api/src/middleware/*
apps/api/src/middleware/index.ts, apps/api/src/middleware/load-test-auth.ts, apps/api/src/middleware/sentry.ts, apps/api/src/middleware/supabase.ts, apps/api/src/middleware/stripe.ts
Adds middleware barrel; creates new sentryMiddleware (continues traces, starts HTTP span, stores it in bindings); renames requireSupabaseAuthsupabaseAuthMiddleware; moves Stripe client to integration; adjusts env import paths.
App bindings & entry
apps/api/src/hono-bindings.ts, apps/api/src/index.ts
Extends AppBindings.Variables with sentrySpan: Sentry.Span; replaces per-route auth with consolidated supabaseAuthMiddleware; composes global middleware (sentryMiddleware, logger, bodyLimit) and updated middleware imports.
Routes — modularized
apps/api/src/routes/*
apps/api/src/routes/index.ts, apps/api/src/routes/constants.ts, apps/api/src/routes/health.ts, apps/api/src/routes/llm.ts, apps/api/src/routes/stt.ts, apps/api/src/routes/webhook.ts
Introduces route modules and index: health (GET /health), llm (POST /chat/completions) using OpenRouter with streaming & tool-calling, stt (GET /listen WebSocket), and webhook (POST /stripe) that invokes billing sync. Adds API_TAGS constants and re-exports.
Routing composition update
apps/api/src/routes.ts
Reworks chat completion flow to use getModels/openrouter client, centralizes timing/metrics and error handling, removes nested Sentry span wrappers, and expands API tags.
Billing, listen, metrics
apps/api/src/billing.ts, apps/api/src/listen.ts, apps/api/src/metrics.ts
Updates import paths to integration/ and metrics; reorganizes Metrics into grouped objects (billing, stt, llm) then spreads them into exported Metrics (external API preserved).
Removed legacy Sentry
apps/api/src/sentry/middleware.ts
Deletes previous sentry middleware file (functionality moved to new middleware location).
Desktop LLM connection refactor
apps/desktop/src/hooks/useLLMConnection.ts
Replaces monolithic logic with resolver-driven APIs: resolveLLMConnection, useLLMConnection, useLLMConnectionStatus, useLanguageModel, createLanguageModel, wrapWithThinkingMiddleware; adds provider-specific construction and hosted Hyprnote support.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Client
    participant API as API (Hono)
    participant Sentry as sentryMiddleware
    participant Verify as verifyStripeWebhook
    participant Billing as billing.syncBillingForStripeEvent
    participant Stripe
    participant Supabase as supabaseAdmin

    Client->>API: POST /webhook/stripe (with stripe-signature)
    API->>Sentry: pass through sentryMiddleware (continue trace / start span)
    Sentry->>API: span stored in bindings (sentrySpan)
    API->>Verify: verify signature -> produce stripeEvent
    Verify-->>API: stripeEvent
    API->>Sentry: set span tag stripe.event_type
    API->>Billing: syncBillingForStripeEvent(stripeEvent)
    Billing->>Stripe: fetch/process event data
    Billing->>Supabase: update billing records
    Billing-->>API: success / error
    API->>Sentry: record outcome and status
    API-->>Client: 200 { ok: true } / 500 { error }
Loading
sequenceDiagram
    autonumber
    participant Client
    participant API as API (Hono / /chat/completions)
    participant Sentry as sentryMiddleware
    participant OpenRouter as openrouter.openai
    participant Metrics

    Client->>API: POST /chat/completions (messages, tools, streaming?)
    API->>Sentry: sentryMiddleware attaches span
    API->>API: determine needsToolCalling -> models = getModels(...)
    alt streaming=true
        API->>OpenRouter: stream request (models, messages, tools...)
        OpenRouter-->>API: stream chunks
        API->>Client: SSE stream events (including [DONE])
        API->>Metrics: record upstream latency + streaming metrics
    else streaming=false
        API->>OpenRouter: create completion (models, messages...)
        OpenRouter-->>API: JSON response
        API-->>Client: JSON completion
        API->>Metrics: record upstream latency + completion metrics
    end
    API->>Sentry: annotate span with outcome
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Files that need careful review:
    • apps/api/src/routes/llm.ts — streaming, tool-calling logic, OpenRouter integration, error/metric flows.
    • apps/api/src/middleware/sentry.ts and apps/api/src/hono-bindings.ts — trace continuation, span lifecycle, bindings typing.
    • apps/api/src/routes/webhook.ts and apps/api/src/billing.ts — Stripe verification, error handling, and Supabase billing updates.
    • apps/desktop/src/hooks/useLLMConnection.ts — provider-specific construction, hosted Hyprnote handling, and the new public hooks.

Possibly related PRs

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Refactor apps/api service' directly and clearly describes the main change: refactoring the API service architecture with reorganized code into domain-specific directories.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, covering API reorganization, desktop hook refactoring, bug fixes, testing checklist, and providing context through a Devin run link.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor-api

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 and usage tips.

@netlify
Copy link

netlify bot commented Dec 5, 2025

Deploy Preview for hyprnote ready!

Name Link
🔨 Latest commit fb92040
🔍 Latest deploy log https://app.netlify.com/projects/hyprnote/deploys/6932b519292fbf0008dd7a98
😎 Deploy Preview https://deploy-preview-2145--hyprnote.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (8)
apps/api/src/routes/constants.ts (1)

1-6: Centralized API_TAGS is good; ensure old definitions are removed

Defining API_TAGS in a dedicated constants.ts with as const is a solid move for tag consistency and typing. Just make sure any prior API_TAGS declarations (e.g., in routes.ts) are removed or re‑wired to this one so there’s a single source of truth.

apps/api/src/middleware/index.ts (1)

1-4: Middleware barrel export is helpful; watch exported surface

Consolidating middleware exports here will simplify consumption (import { … } from "./middleware"). Just ensure each of the re-exported modules only exposes the intended middleware symbols so you don’t accidentally widen the public surface or create naming collisions down the line.

apps/api/src/routes/health.ts (1)

1-33: Health route looks solid; consider tightening the response schema

The health router and its OpenAPI metadata are wired correctly, and returning { status: "ok" } with API_TAGS.INTERNAL makes sense.

You could optionally make the schema reflect the concrete response by narrowing it to a literal:

-const HealthResponseSchema = z.object({
-  status: z.string(),
-});
+const HealthResponseSchema = z.object({
+  status: z.literal("ok"),
+});

This keeps clients’ expectations aligned with the actual behavior.

apps/api/src/integration/openrouter.ts (1)

6-10: Consider renaming openai to clarify it's an OpenRouter client.

The variable openai is initialized with the OpenRouter base URL, which could cause confusion. A name like openrouter or openrouterClient would better reflect its purpose.

-export const openai = new PostHogOpenAI({
+export const openrouter = new PostHogOpenAI({
   baseURL: "https://openrouter.ai/api/v1",
   apiKey: env.OPENROUTER_API_KEY,
   posthog,
 });
apps/api/src/routes/stt.ts (1)

62-65: Dynamic import may be unnecessary here.

The lazy import of listenSocketHandler adds runtime overhead on each request. Unless there's a specific code-splitting or circular dependency reason, consider using a static import at the top of the file.

+import { listenSocketHandler } from "../listen";
+
 const WebSocketErrorSchema = z.object({

Then simplify the handler:

   async (c, next) => {
-    const { listenSocketHandler } = await import("../listen");
     return listenSocketHandler(c, next);
   },
apps/api/src/routes/webhook.ts (1)

55-60: Header validation duplicates middleware check.

The verifyStripeWebhook middleware already validates the Stripe-Signature header and returns 400 if missing. This validator adds OpenAPI documentation value but creates redundant validation.

Consider if the header validator is purely for OpenAPI documentation or if it serves another purpose. If only for docs, the schema could be declared without runtime validation since the middleware handles it.

apps/api/src/routes/llm.ts (1)

12-25: Schema may not cover all OpenAI message types.

ChatCompletionMessageSchema only supports system, user, and assistant roles. OpenAI's API also supports tool and function roles for tool call responses. If clients send tool responses, validation will fail.

 const ChatCompletionMessageSchema = z.object({
-  role: z.enum(["system", "user", "assistant"]),
-  content: z.string(),
+  role: z.enum(["system", "user", "assistant", "tool", "function"]),
+  content: z.string().nullable(),
+  tool_call_id: z.string().optional(),
+  name: z.string().optional(),
 });

Alternatively, if full OpenAI compatibility isn't needed, document the supported subset.

apps/desktop/src/hooks/useLLMConnection.ts (1)

52-55: Consider deeper memoization for the connection object.

The conn object is recreated on every render in resolveLLMConnection, which limits the effectiveness of this useMemo. While not incorrect, you could optimize by using useMemo with value-based dependencies (providerId, modelId, baseUrl, apiKey) in resolveLLMConnection to return stable object references when values haven't changed.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 775c138 and 2b1d23a.

📒 Files selected for processing (24)
  • apps/api/src/billing.ts (1 hunks)
  • apps/api/src/hono-bindings.ts (1 hunks)
  • apps/api/src/index.ts (2 hunks)
  • apps/api/src/integration/index.ts (1 hunks)
  • apps/api/src/integration/openrouter.ts (1 hunks)
  • apps/api/src/integration/posthog.ts (1 hunks)
  • apps/api/src/integration/stripe.ts (1 hunks)
  • apps/api/src/integration/supabase.ts (1 hunks)
  • apps/api/src/listen.ts (1 hunks)
  • apps/api/src/metrics.ts (1 hunks)
  • apps/api/src/middleware/index.ts (1 hunks)
  • apps/api/src/middleware/load-test-auth.ts (1 hunks)
  • apps/api/src/middleware/sentry.ts (1 hunks)
  • apps/api/src/middleware/stripe.ts (1 hunks)
  • apps/api/src/middleware/supabase.ts (1 hunks)
  • apps/api/src/routes.ts (3 hunks)
  • apps/api/src/routes/constants.ts (1 hunks)
  • apps/api/src/routes/health.ts (1 hunks)
  • apps/api/src/routes/index.ts (1 hunks)
  • apps/api/src/routes/llm.ts (1 hunks)
  • apps/api/src/routes/stt.ts (1 hunks)
  • apps/api/src/routes/webhook.ts (1 hunks)
  • apps/api/src/sentry/middleware.ts (0 hunks)
  • apps/desktop/src/hooks/useLLMConnection.ts (3 hunks)
💤 Files with no reviewable changes (1)
  • apps/api/src/sentry/middleware.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.ts: Agent implementations should use TypeScript and follow the established architectural patterns defined in the agent framework
Agent communication should use defined message protocols and interfaces

Files:

  • apps/api/src/middleware/index.ts
  • apps/api/src/hono-bindings.ts
  • apps/api/src/routes/constants.ts
  • apps/api/src/integration/supabase.ts
  • apps/api/src/routes/index.ts
  • apps/api/src/routes/stt.ts
  • apps/api/src/middleware/sentry.ts
  • apps/api/src/integration/openrouter.ts
  • apps/api/src/integration/stripe.ts
  • apps/api/src/routes/webhook.ts
  • apps/api/src/routes/health.ts
  • apps/api/src/billing.ts
  • apps/api/src/integration/index.ts
  • apps/api/src/routes/llm.ts
  • apps/api/src/index.ts
  • apps/api/src/metrics.ts
  • apps/api/src/middleware/load-test-auth.ts
  • apps/api/src/listen.ts
  • apps/api/src/integration/posthog.ts
  • apps/api/src/middleware/stripe.ts
  • apps/api/src/middleware/supabase.ts
  • apps/api/src/routes.ts
  • apps/desktop/src/hooks/useLLMConnection.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Avoid creating a bunch of types/interfaces if they are not shared. Especially for function props, just inline them instead.
Never do manual state management for form/mutation. Use useForm (from tanstack-form) and useQuery/useMutation (from tanstack-query) instead for 99% of cases. Avoid patterns like setError.
If there are many classNames with conditional logic, use cn (import from @hypr/utils). It is similar to clsx. Always pass an array and split by logical grouping.
Use motion/react instead of framer-motion.

Files:

  • apps/api/src/middleware/index.ts
  • apps/api/src/hono-bindings.ts
  • apps/api/src/routes/constants.ts
  • apps/api/src/integration/supabase.ts
  • apps/api/src/routes/index.ts
  • apps/api/src/routes/stt.ts
  • apps/api/src/middleware/sentry.ts
  • apps/api/src/integration/openrouter.ts
  • apps/api/src/integration/stripe.ts
  • apps/api/src/routes/webhook.ts
  • apps/api/src/routes/health.ts
  • apps/api/src/billing.ts
  • apps/api/src/integration/index.ts
  • apps/api/src/routes/llm.ts
  • apps/api/src/index.ts
  • apps/api/src/metrics.ts
  • apps/api/src/middleware/load-test-auth.ts
  • apps/api/src/listen.ts
  • apps/api/src/integration/posthog.ts
  • apps/api/src/middleware/stripe.ts
  • apps/api/src/middleware/supabase.ts
  • apps/api/src/routes.ts
  • apps/desktop/src/hooks/useLLMConnection.ts
🧬 Code graph analysis (9)
apps/api/src/routes/constants.ts (2)
apps/api/src/routes.ts (1)
  • API_TAGS (11-16)
apps/api/src/routes/index.ts (1)
  • API_TAGS (9-9)
apps/api/src/routes/index.ts (6)
apps/api/src/routes.ts (1)
  • routes (50-50)
apps/api/src/hono-bindings.ts (1)
  • AppBindings (4-9)
apps/api/src/routes/health.ts (1)
  • health (13-13)
apps/api/src/routes/llm.ts (1)
  • llm (27-27)
apps/api/src/routes/stt.ts (1)
  • stt (14-14)
apps/api/src/routes/webhook.ts (1)
  • webhook (19-19)
apps/api/src/middleware/sentry.ts (1)
apps/api/src/hono-bindings.ts (1)
  • AppBindings (4-9)
apps/api/src/routes/webhook.ts (5)
apps/api/src/hono-bindings.ts (1)
  • AppBindings (4-9)
apps/api/src/routes.ts (1)
  • API_TAGS (11-16)
apps/api/src/routes/constants.ts (1)
  • API_TAGS (1-6)
apps/api/src/billing.ts (1)
  • syncBillingForStripeEvent (75-87)
apps/api/src/metrics.ts (1)
  • Metrics (33-43)
apps/api/src/routes/llm.ts (5)
apps/api/src/hono-bindings.ts (1)
  • AppBindings (4-9)
apps/api/src/routes.ts (1)
  • API_TAGS (11-16)
apps/api/src/routes/constants.ts (1)
  • API_TAGS (1-6)
apps/api/src/integration/openrouter.ts (2)
  • openai (6-10)
  • getModels (21-23)
apps/api/src/metrics.ts (1)
  • Metrics (33-43)
apps/api/src/index.ts (4)
apps/api/src/middleware/load-test-auth.ts (1)
  • loadTestOverride (5-16)
apps/api/src/middleware/supabase.ts (1)
  • supabaseAuthMiddleware (6-27)
apps/api/src/middleware/stripe.ts (1)
  • verifyStripeWebhook (10-38)
apps/api/src/env.ts (1)
  • env (4-25)
apps/api/src/middleware/supabase.ts (1)
apps/web/src/middleware/supabase.ts (1)
  • supabaseAuthMiddleware (12-41)
apps/api/src/routes.ts (3)
apps/api/src/integration/openrouter.ts (2)
  • openai (6-10)
  • getModels (21-23)
apps/api/src/metrics.ts (1)
  • Metrics (33-43)
apps/api/src/billing.ts (1)
  • syncBillingForStripeEvent (75-87)
apps/desktop/src/hooks/useLLMConnection.ts (4)
apps/desktop/src/auth.tsx (1)
  • useAuth (278-286)
apps/desktop/src/billing.tsx (1)
  • useBillingAccess (122-130)
apps/desktop/src/components/settings/ai/stt/shared.tsx (2)
  • ProviderId (24-24)
  • PROVIDERS (189-197)
apps/desktop/src/env.ts (1)
  • env (4-16)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: Redirect rules - hyprnote
  • GitHub Check: Header rules - hyprnote
  • GitHub Check: Pages changed - hyprnote
  • GitHub Check: Redirect rules - hyprnote-storybook
  • GitHub Check: Header rules - hyprnote-storybook
  • GitHub Check: Pages changed - hyprnote-storybook
  • GitHub Check: desktop_ci (linux, depot-ubuntu-24.04-8)
  • GitHub Check: desktop_ci (linux, depot-ubuntu-22.04-8)
  • GitHub Check: desktop_ci (macos, depot-macos-14)
  • GitHub Check: fmt
🔇 Additional comments (33)
apps/api/src/hono-bindings.ts (1)

1-8: Confirm sentrySpan is always initialized before use

Extending AppBindings.Variables with sentrySpan: Sentry.Span is reasonable, but this type shape implies the span is always present whenever accessed via c.get("sentrySpan"). Please double‑check that all code paths that read sentrySpan only do so after it’s set (or consider making it nullable/optional in the bindings if some routes legitimately don’t create a span).

apps/api/src/integration/posthog.ts (1)

3-8: Env import path refactor looks consistent

Switching env to ../env keeps the PostHog wiring intact and aligns with the shared env module used elsewhere. No behavioral changes here as long as ../env exposes the same keys.

apps/api/src/listen.ts (1)

5-52: Metrics import redirection looks correct

Pointing Metrics to "./metrics" matches the metrics consolidation described elsewhere; existing calls to websocketConnected/websocketDisconnected remain unchanged. Just confirm that ./metrics still exposes a Metrics object with these methods to avoid a silent API shape drift.

apps/api/src/middleware/load-test-auth.ts (1)

3-16: Env path refactor keeps middleware behavior intact

Using ../env here is consistent with the new env layout and doesn’t change the override behavior. Middleware still safely falls back to next() when the header or override token doesn’t match.

apps/api/src/billing.ts (1)

4-109: Using shared Stripe/Supabase integrations is a good consolidation

Importing stripe and supabaseAdmin from the integration modules keeps client configuration centralized while preserving the existing billing flow (handled events, customer resolution, and billings upsert). Please just confirm there’s no circular dependency (e.g., integration/stripe importing billing.ts) and that integration/stripe exports a fully configured Stripe client instance.

apps/api/src/integration/index.ts (1)

1-4: Clean barrel export pattern for integration modules.

This consolidates all integration adapters under a single entry point, making imports cleaner throughout the codebase.

apps/api/src/integration/supabase.ts (1)

1-8: Standard Supabase admin client initialization.

The singleton pattern is appropriate for server-side admin operations. The service role key usage is correct for server-to-server communication.

apps/api/src/integration/openrouter.ts (1)

21-23: Clean implementation of model selection helper.

The spread operator ensures callers receive a mutable copy, preventing accidental mutations to the source array. The as const assertion on MODELS provides good type narrowing.

apps/api/src/routes/index.ts (2)

13-16: Route mounting order is not a concern.

The stt router contains only a specific route (GET /listen), not a catch-all or wildcard pattern. The webhook router similarly contains specific routes (e.g., POST /stripe). Reordering the routes as suggested is unnecessary; the current mounting order causes no path conflicts in Hono.

Likely an incorrect or invalid review comment.


9-11: Remove the old apps/api/src/routes.ts file to eliminate confusion.

The codebase now uses apps/api/src/routes/index.ts for route aggregation, but the legacy routes.ts file still exists at the same level. Although imports correctly resolve to the new directory-based structure, the duplicate file creates ambiguity and should be deleted.

⛔ Skipped due to learnings
Learnt from: CR
Repo: fastrepl/hyprnote PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T16:32:23.055Z
Learning: Applies to **/*.{ts,tsx} : Use `motion/react` instead of `framer-motion`.
Learnt from: CR
Repo: fastrepl/hyprnote PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T16:32:19.706Z
Learning: Applies to **/*.{ts,tsx} : Use `motion/react` instead of `framer-motion`.
apps/api/src/integration/stripe.ts (1)

5-7: Consider updating to the latest Stripe API version.

The current version "2025-10-29.clover" is valid and supported. However, a newer Clover release 2025-11-17.clover is available. Review whether upgrading aligns with your release schedule and feature requirements (the November version includes additional refinements over the October release).

Likely an incorrect or invalid review comment.

apps/api/src/index.ts (2)

14-19: LGTM! Clean middleware consolidation.

The consolidated import from ./middleware barrel aligns well with the refactoring goal of centralizing middleware exports.


49-54: LGTM! Middleware ordering is correct.

The loadTestOverride middleware correctly runs before supabaseAuthMiddleware, allowing load test authentication to bypass Supabase auth when OVERRIDE_AUTH is set. The conditional /listen auth in non-development environments is preserved.

apps/api/src/middleware/sentry.ts (1)

6-29: Well-structured Sentry middleware with proper distributed tracing.

The implementation correctly continues traces from upstream services and creates appropriate HTTP server spans. Storing the span in context enables downstream route handlers to add custom attributes.

Consider whether errors thrown by next() should also be captured on the span. Currently, if an error occurs, span.setAttribute("http.status_code", ...) may not execute or may record an incorrect status. The global app.onError handler captures exceptions, but the span won't reflect the error state.

apps/api/src/metrics.ts (1)

3-43: LGTM! Clean metric organization by domain.

The refactoring improves code organization by grouping metrics logically while preserving the external API through object spreading. The billingSync metric appropriately tracks success/failure with the Stripe event type.

apps/api/src/middleware/stripe.ts (1)

5-6: LGTM! Import paths updated for integration consolidation.

The Stripe instance is now properly sourced from the centralized integration/stripe module, aligning with the PR's goal of consolidating integrations.

apps/api/src/routes/stt.ts (2)

9-12: LGTM! Well-defined error schema.

The WebSocketErrorSchema provides a clear structure for error responses with required error and optional detail fields.


16-61: LGTM! Comprehensive OpenAPI documentation.

The route description covers all relevant HTTP status codes for a WebSocket upgrade endpoint, including upstream service errors (502, 504). Security requirement is correctly declared.

apps/api/src/middleware/supabase.ts (1)

6-27: LGTM! Correct Supabase authentication middleware.

The implementation properly creates a per-request Supabase client with the user's JWT, validates the token via getUser(), and stores the user ID in context. Using the anon key with the user's Authorization header enables RLS policies.

apps/api/src/routes/webhook.ts (1)

61-80: LGTM! Well-structured webhook handler with proper observability.

The handler correctly:

  • Retrieves the pre-validated Stripe event from context
  • Attaches event type to Sentry span for tracing
  • Records billing metrics for success/failure
  • Captures exceptions with relevant tags
  • Returns appropriate HTTP responses

Same note as stt.ts: consider static import for ../billing unless code splitting is intentional.

apps/api/src/routes/llm.ts (2)

92-99: Streaming latency metric measures time-to-first-byte, not total duration.

Metrics.upstreamLatency is recorded immediately after openai.chat.completions.create returns for streaming responses. This captures only the initial connection time, not the full stream duration. Consider if this is the intended behavior or if you want end-to-end latency.

If end-to-end latency is desired for streaming, record the metric inside the ReadableStream.start callback after the stream completes:

             controller.enqueue(encoder.encode("data: [DONE]\n\n"));
+            Metrics.upstreamLatency("openrouter", performance.now() - startTime);
             controller.close();

And remove the early metric call at line 98.


139-148: LGTM! Error handling extracts status and re-throws.

The error handler correctly records metrics, extracts HTTP status from OpenAI SDK errors when available, and re-throws to let the global error handler capture it to Sentry.

apps/api/src/routes.ts (2)

303-306: LGTM!

The WebSocket route delegation is clean and the dynamic import pattern is appropriate for lazy loading the socket handler.


97-105: The concern about potential null/undefined access on sentrySpan is incorrect. The sentryMiddleware is registered globally with app.use(sentryMiddleware) at line 24 of apps/api/src/index.ts and runs before all route handlers. The middleware guarantees that c.set("sentrySpan", span) is called within the Sentry.startSpan() callback with a valid span object before await next() allows route handlers to execute. The span value is guaranteed to exist when routes access it.

Likely an incorrect or invalid review comment.

apps/desktop/src/hooks/useLLMConnection.ts (9)

26-50: LGTM! Well-structured type definitions.

The discriminated union for LLMConnectionStatus provides clear, type-safe status tracking with detailed error reasons. The types are appropriately scoped.


57-87: LGTM! Well-composed hook with proper dependencies.

The hook correctly aggregates auth, billing, and store state. The memoization dependencies are complete.


89-92: LGTM! Clean convenience hook.


94-136: LGTM! Proper validation flow with safe type handling.

The type assertion at Line 116 is mitigated by the subsequent validation that checks if the provider exists in PROVIDERS (lines 125-136).


164-194: LGTM! Robust configuration validation.

The fallback chain for baseUrl and conditional apiKey validation handle both configured and default provider settings correctly. The missing array provides clear error feedback.


196-204: LGTM! Clean middleware abstraction.

Centralizing the reasoning extraction middleware ensures consistent behavior across all providers.


206-221: LGTM! Proper provider setup with tracing.

The hyprnote case correctly uses tracedFetch for observability and sets appropriate headers. Note: This assumes the baseUrl from line 157 is correct (see previous comment).


223-264: LGTM! Provider-specific implementations are correct.

Each provider case properly configures the respective SDK with appropriate headers, fetch implementations, and provider-specific options. The consistent use of tauriFetch for desktop environment is appropriate.


266-279: LGTM! Flexible default handling for OpenAI-compatible providers.

The conditional apiKey configuration (lines 272-274) correctly handles providers that don't require authentication, such as local LM Studio or Ollama instances.

- Fix incomplete OpenRouter model identifier (gpt-5.1-chat -> gpt-5.1-chat-latest)
- Fix metrics recorded before streaming completes (move success metric after stream completes)
- Add null safety for sentrySpan in all routes using optional chaining
- Use openrouter/auto instead of empty string for model
- Fix baseUrl for hyprnote provider to include path suffix

Co-Authored-By: yujonglee <[email protected]>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
apps/api/src/routes.ts (1)

72-194: Major code duplication: entire chat completions route duplicated from llm.ts.

The /chat/completions route handler (lines 72-194) is nearly identical to the implementation in apps/api/src/routes/llm.ts. Based on the AI summary, routes/index.ts wires /chat to the llm router, which would make this implementation redundant.

Either:

  1. Remove this route if llm.ts is the intended implementation
  2. Import and mount the llm router here instead of duplicating
+import { llm } from "./routes/llm";
+
 export const routes = new Hono<AppBindings>();
+
+routes.route("/chat", llm);

-routes.post(
-  "/chat/completions",
-  // ... ~120 lines of duplicated code
-);
♻️ Duplicate comments (1)
apps/desktop/src/hooks/useLLMConnection.ts (1)

153-161: Previous baseUrl issue has been addressed.

The baseUrl now correctly includes ${providerDefinition.baseUrl || ""}, which will append the /functions/v1/llm path from the PROVIDERS definition for hyprnote.

🧹 Nitpick comments (5)
apps/api/src/routes/llm.ts (1)

12-15: Consider adding "tool" and "function" roles for complete tool-calling support.

The OpenAI-compatible chat API also supports "tool" and "function" roles for tool response messages. If tool-calling workflows need to support multi-turn conversations with tool results, these roles should be included.

 const ChatCompletionMessageSchema = z.object({
-  role: z.enum(["system", "user", "assistant"]),
+  role: z.enum(["system", "user", "assistant", "tool", "function"]),
   content: z.string(),
 });
apps/api/src/routes.ts (2)

11-16: Duplicate API_TAGS definition detected.

API_TAGS is also defined in apps/api/src/routes/constants.ts (per relevant snippets). This creates duplicate definitions that could drift apart. Consider importing from the constants file instead.

+import { API_TAGS } from "./routes/constants";
+
-export const API_TAGS = {
-  INTERNAL: "internal",
-  APP: "app",
-  WEBHOOK: "webhook",
-  PUBLIC: "public",
-} as const;

22-35: Duplicate schema definitions with llm.ts.

ChatCompletionMessageSchema and ChatCompletionRequestSchema are duplicated in both routes.ts and routes/llm.ts. Extract these to a shared schemas file to maintain a single source of truth.

apps/desktop/src/hooks/useLLMConnection.ts (2)

116-136: Consider moving the type cast after provider validation.

The cast to ProviderId at line 116 occurs before verifying the provider exists in PROVIDERS (line 127). While functionally safe since the error path uses rawProviderId, moving the cast after the validation would better reflect the logical flow.

-  const providerId = rawProviderId as ProviderId;
-
   if (!modelId) {
     return {
       conn: null,
-      status: { status: "pending", reason: "missing_model", providerId },
+      status: { status: "pending", reason: "missing_model", providerId: rawProviderId as ProviderId },
     };
   }

   const providerDefinition = PROVIDERS.find((p) => p.id === rawProviderId);

   if (!providerDefinition) {
     return {
       conn: null,
       status: {
         status: "error",
         reason: "provider_not_found",
         providerId: rawProviderId,
       },
     };
   }
+
+  const providerId = rawProviderId as ProviderId;

210-221: Consider removing redundant Authorization header.

The apiKey option already handles authentication for OpenAI-compatible providers. The explicit Authorization header is redundant unless the server requires a specific header format not provided by the SDK.

       const provider = createOpenAICompatible({
         fetch: tracedFetch,
         name: "hyprnote",
         baseURL: conn.baseUrl,
         apiKey: conn.apiKey,
-        headers: {
-          Authorization: `Bearer ${conn.apiKey}`,
-        },
       });
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2b1d23a and fd58e87.

📒 Files selected for processing (5)
  • apps/api/src/integration/openrouter.ts (1 hunks)
  • apps/api/src/routes.ts (3 hunks)
  • apps/api/src/routes/llm.ts (1 hunks)
  • apps/api/src/routes/webhook.ts (1 hunks)
  • apps/desktop/src/hooks/useLLMConnection.ts (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/api/src/integration/openrouter.ts
  • apps/api/src/routes/webhook.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.ts: Agent implementations should use TypeScript and follow the established architectural patterns defined in the agent framework
Agent communication should use defined message protocols and interfaces

Files:

  • apps/api/src/routes/llm.ts
  • apps/api/src/routes.ts
  • apps/desktop/src/hooks/useLLMConnection.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Avoid creating a bunch of types/interfaces if they are not shared. Especially for function props, just inline them instead.
Never do manual state management for form/mutation. Use useForm (from tanstack-form) and useQuery/useMutation (from tanstack-query) instead for 99% of cases. Avoid patterns like setError.
If there are many classNames with conditional logic, use cn (import from @hypr/utils). It is similar to clsx. Always pass an array and split by logical grouping.
Use motion/react instead of framer-motion.

Files:

  • apps/api/src/routes/llm.ts
  • apps/api/src/routes.ts
  • apps/desktop/src/hooks/useLLMConnection.ts
🧠 Learnings (1)
📚 Learning: 2025-11-24T16:32:13.593Z
Learnt from: CR
Repo: fastrepl/hyprnote PR: 0
File: packages/nango/.cursor/rules/nango.mdc:0-0
Timestamp: 2025-11-24T16:32:13.593Z
Learning: Applies to packages/nango/**/models.ts : Do NOT edit the models.ts file - it is automatically generated at compilation time

Applied to files:

  • apps/api/src/routes/llm.ts
🧬 Code graph analysis (3)
apps/api/src/routes/llm.ts (5)
apps/api/src/hono-bindings.ts (1)
  • AppBindings (4-9)
apps/api/src/routes/constants.ts (1)
  • API_TAGS (1-6)
apps/api/src/routes/index.ts (1)
  • API_TAGS (9-9)
apps/api/src/integration/openrouter.ts (2)
  • openai (6-10)
  • getModels (21-23)
apps/api/src/metrics.ts (1)
  • Metrics (33-43)
apps/api/src/routes.ts (3)
apps/api/src/integration/openrouter.ts (2)
  • openai (6-10)
  • getModels (21-23)
apps/api/src/metrics.ts (1)
  • Metrics (33-43)
apps/api/src/billing.ts (1)
  • syncBillingForStripeEvent (75-87)
apps/desktop/src/hooks/useLLMConnection.ts (2)
packages/store/src/schema-internal.ts (1)
  • AIProviderStorage (46-46)
apps/desktop/src/components/settings/ai/stt/shared.tsx (2)
  • ProviderId (24-24)
  • PROVIDERS (189-197)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: desktop_ci (linux, depot-ubuntu-24.04-8)
  • GitHub Check: desktop_ci (linux, depot-ubuntu-22.04-8)
  • GitHub Check: desktop_ci (macos, depot-macos-14)
  • GitHub Check: Redirect rules - hyprnote
  • GitHub Check: Header rules - hyprnote
  • GitHub Check: fmt
  • GitHub Check: Pages changed - hyprnote
🔇 Additional comments (10)
apps/api/src/routes/llm.ts (3)

1-11: LGTM!

Imports are well-organized and correctly reference the consolidated integration modules.


27-76: LGTM!

Route setup is well-structured with proper OpenAPI documentation, validation, and safe optional chaining for Sentry span attributes.


77-150: LGTM! Past review concerns addressed.

Good improvements:

  • Success metric Metrics.chatCompletion(true, 200) is correctly called after controller.close() (line 110)
  • Streaming error path now records failure metric Metrics.chatCompletion(true, 500) (line 112)
  • Using "openrouter/auto" as the primary model follows OpenRouter's documented pattern
apps/api/src/routes.ts (3)

52-70: LGTM!

Simple and clean health check endpoint.


236-256: LGTM! Past review concern addressed.

The span?.setAttribute on line 241 properly handles the case where sentrySpan might be undefined. Error handling is comprehensive with proper Sentry tagging and metric recording.


258-308: LGTM!

WebSocket endpoint is cleanly structured with proper OpenAPI documentation and dynamic import for code splitting.

apps/desktop/src/hooks/useLLMConnection.ts (4)

52-55: LGTM!

Clean hook composition with correct memoization dependency.


70-86: LGTM!

The resolver-driven memoization approach is clean. Minor note: the dependency on auth object (rather than auth?.session) may cause extra recomputation if the auth object reference changes but session remains unchanged, though this is unlikely to be a performance concern in practice.


196-204: LGTM!

Clean middleware wrapper supporting both <think> and <thinking> tag formats for reasoning extraction.


266-277: LGTM!

Good fallback implementation for OpenAI-compatible providers with conditional apiKey handling. The use of tauriFetch for external providers and tracedFetch for the hosted hyprnote provider is a sensible distinction.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (2)
apps/api/src/routes.ts (2)

176-195: Streaming metrics now aligned with stream lifecycle

Moving Metrics.chatCompletion(true, 200) to after the for await loop and controller.close() ensures success is only recorded once the stream has fully completed, and adding Metrics.chatCompletion(true, 500) in the catch gives you a clear streaming‑error path. This addresses the earlier concern about premature success metrics for streaming responses.


273-278: Null‑safe Sentry span attribute for Stripe webhook

Using const span = c.get("sentrySpan"); with span?.setAttribute("stripe.event_type", stripeEvent.type); correctly guards against missing spans and prevents a runtime error in cases where the Sentry middleware hasn’t attached one. Awaiting syncBillingForStripeEvent inside the try also ensures Metrics.billingSync(true, …) only fires on successful completion.

🧹 Nitpick comments (1)
apps/api/src/routes.ts (1)

130-167: Chat completions refactor looks correct; verify extraBody handling and reduce reliance on as cast

The overall flow (deriving needsToolCalling, using getModels(needsToolCalling), defaulting to openrouter/auto, and centralizing metrics in the shared try/catch) is coherent and matches the OpenRouter auto‑routing intent.

Two small follow‑ups to consider:

  • The createParams object is forced with as Parameters<typeof openai.chat.completions.create>[0]. To keep types honest as the client evolves, you might instead introduce a local alias and assign without a cast:
type ChatCreateParams = Parameters<typeof openai.chat.completions.create>[0];

const createParams: ChatCreateParams = {
  model: "openrouter/auto",
  messages,
  tools,
  tool_choice,
  temperature,
  max_tokens,
};
  • For the second argument, please double‑check that PostHogOpenAI’s .chat.completions.create actually consumes { body: extraBody } the way you expect (i.e., forwarding models and provider through to OpenRouter). If body is not honored in that options object, the OpenRouter‑specific fields (and any loosened restBody fields) may be silently ignored.

Also applies to: 207-225

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c498aed and fb92040.

📒 Files selected for processing (2)
  • apps/api/src/index.ts (2 hunks)
  • apps/api/src/routes.ts (3 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.ts: Agent implementations should use TypeScript and follow the established architectural patterns defined in the agent framework
Agent communication should use defined message protocols and interfaces

Files:

  • apps/api/src/routes.ts
  • apps/api/src/index.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Avoid creating a bunch of types/interfaces if they are not shared. Especially for function props, just inline them instead.
Never do manual state management for form/mutation. Use useForm (from tanstack-form) and useQuery/useMutation (from tanstack-query) instead for 99% of cases. Avoid patterns like setError.
If there are many classNames with conditional logic, use cn (import from @hypr/utils). It is similar to clsx. Always pass an array and split by logical grouping.
Use motion/react instead of framer-motion.

Files:

  • apps/api/src/routes.ts
  • apps/api/src/index.ts
🧬 Code graph analysis (1)
apps/api/src/routes.ts (3)
apps/api/src/integration/openrouter.ts (2)
  • openai (6-10)
  • getModels (21-23)
apps/api/src/metrics.ts (1)
  • Metrics (33-43)
apps/api/src/billing.ts (1)
  • syncBillingForStripeEvent (75-87)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: Redirect rules - hyprnote-storybook
  • GitHub Check: Header rules - hyprnote-storybook
  • GitHub Check: Pages changed - hyprnote-storybook
  • GitHub Check: Redirect rules - hyprnote
  • GitHub Check: Header rules - hyprnote
  • GitHub Check: Pages changed - hyprnote
  • GitHub Check: fmt
  • GitHub Check: desktop_ci (macos, depot-macos-14)
  • GitHub Check: desktop_ci (linux, depot-ubuntu-22.04-8)
  • GitHub Check: desktop_ci (linux, depot-ubuntu-24.04-8)
🔇 Additional comments (3)
apps/api/src/index.ts (2)

14-19: Middleware barrel import keeps the entrypoint clean and aligned with the new layout

Pulling loadTestOverride, sentryMiddleware, supabaseAuthMiddleware, and verifyStripeWebhook from ./middleware matches the refactor goal of consolidating middleware surfaces and makes index.ts much easier to scan. No issues from my side here.


49-55: Auth + load-test coverage is correctly centralized with clean dev bypass

Verification confirms the architecture is sound:

  • No legacy requireSupabaseAuth-style per-route wrappers remain in the codebase—middleware is properly centralized in index.ts.
  • All three endpoints (/chat/completions, /listen, /transcribe) have auth/load-test middleware registered in the correct scope: /chat/completions always authenticated, /listen and /transcribe authenticated only in production.
  • The dev bypass is intentional and documented ("Requires Supabase authentication in production" in route descriptions).
  • No double-auth or inconsistent 401 handling patterns detected.

The implementation matches the PR checklist and is ready to merge.

apps/api/src/routes.ts (1)

8-9: Centralized OpenRouter & metrics imports look good

Using ./integration/openrouter and ./metrics here matches the new integration/metrics surface and keeps this route thin. No issues from a routing or dependency standpoint.

@yujonglee yujonglee merged commit 6420965 into main Dec 5, 2025
17 checks passed
@yujonglee yujonglee deleted the refactor-api branch December 5, 2025 10:52
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.

2 participants