feat: implement API for AI interactions and authentication: - #244
feat: implement API for AI interactions and authentication:#244yashdev9274 wants to merge 1 commit into
Conversation
- Added core API functionality for AI chat and object generation with streaming responses. - Introduced authentication middleware to secure routes and manage user sessions. - Created health check and user management endpoints for improved service monitoring. - Established a structured environment configuration for better deployment practices. - Integrated token budget management to track usage and enforce limits on AI interactions.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Your trial has ended. Reactivate Greptile to resume code reviews.
WalkthroughThe PR adds a Bun/Hono API with authentication, AI provider routing, token budgets, usage tracking, streaming endpoints, and structured generation. It also introduces a typed SDK with Zod schemas and HTTP/NDJSON client methods. ChangesAPI runtime and authentication
AI provider flow
SDK
Estimated code review effort: 4 (Complex) | ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 19
🧹 Nitpick comments (2)
apps/api/src/providers/index.ts (1)
61-77: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicate pricing table/logic — already diverged from
lib/pricing.ts.This local
MODEL_PRICING/computeCostduplicateslib/pricing.tsand has already drifted: missingcacheWriteand the"nvidia/minimax-m3"zero-price entry. Import fromlib/pricing.tsinstead to avoid future price drift.🤖 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 `@apps/api/src/providers/index.ts` around lines 61 - 77, Remove the local MODEL_PRICING table and computeCost implementation in the provider module, and import and reuse the existing pricing helper from lib/pricing.ts. Update callers to use that shared symbol so cache-write pricing and the "nvidia/minimax-m3" entry remain consistent.apps/api/src/routes/ai.ts (1)
10-25: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicated
messagesschema between the two endpoints.The same message-array shape is repeated verbatim in
chatBodySchemaandgenerateObjectBodySchema. Extract a sharedmessageSchemato avoid drift between the two.+const messageSchema = z.object({ + role: z.enum(["system", "user", "assistant"]), + content: z.string(), +}) + const chatBodySchema = z.object({ - messages: z.array(z.object({ - role: z.enum(["system", "user", "assistant"]), - content: z.string(), - })), + messages: z.array(messageSchema), ... }) const generateObjectBodySchema = z.object({ - messages: z.array(z.object({ - role: z.enum(["system", "user", "assistant"]), - content: z.string(), - })), + messages: z.array(messageSchema), ... })Also applies to: 27-38
🤖 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 `@apps/api/src/routes/ai.ts` around lines 10 - 25, Extract the repeated message object definition into a shared messageSchema near chatBodySchema, then reuse it for the messages arrays in both chatBodySchema and generateObjectBodySchema. Preserve the existing role enum and content validation unchanged.
🤖 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 `@apps/api/package.json`:
- Line 10: Replace the no-op "lint" script in package.json with a command that
runs the project's configured linter against the API source files, or make the
script exit nonzero until linting is configured. Ensure bun run lint cannot
succeed without performing real validation.
- Around line 8-9: Update the package.json start script to execute the
dist/index.js artifact generated by the build script, instead of launching
src/index.ts. Keep the existing build command unchanged so production starts
from the compiled output.
In `@apps/api/src/app.ts`:
- Around line 15-17: Update the bodyLimit configuration in apps/api/src/app.ts
lines 15-17 to read and parse SUPERCODE_JSON_BODY_LIMIT, using the parsed value
as maxSize instead of the hardcoded limit. Keep SUPERCODE_JSON_BODY_LIMIT in
apps/api/.env.example line 3 because the runtime configuration will consume it.
- Around line 24-26: Update the app route registration in the app initialization
flow to mount the Better Auth GET/POST wildcard handler from the auth
configuration before authRoute, using the configured /api/auth base path so
GitHub sign-in and callback requests are handled while preserving the existing
custom auth routes.
In `@apps/api/src/lib/auth.ts`:
- Line 18: Update the GitHub OAuth configuration in the auth setup so the
production redirectURI uses serverUrl, matching the Better-Auth baseURL, rather
than clientUrl; preserve the existing callback path and non-production behavior.
- Around line 16-17: Validate GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET before
registering the GitHub provider instead of relying on type assertions. When
GitHub is optional, omit the provider if either value is missing; when required,
fail fast with a clear configuration error, ensuring no provider is registered
with undefined credentials.
In `@apps/api/src/lib/pricing.ts`:
- Around line 1-37: The NVIDIA routed model key is “minimaxai/minimax-m3”, but
MODEL_PRICING only recognizes “minimax-m3”, causing zero-cost lookups. Update
the pricing lookup or shared pricing definitions used by routeToProvider so the
routed NVIDIA key resolves to the Minimax pricing values, while preserving
existing model pricing behavior.
In `@apps/api/src/lib/token-budget.ts`:
- Around line 6-28: Update the UsageEvent query in checkDailyTokenBudget to
filter by the provided userId in addition to the current-day createdAt range, so
all returned totals are scoped to that user. Preserve the existing date
boundaries and token field selection.
In `@apps/api/src/lib/track-usage.ts`:
- Around line 3-11: Add an explicit return type annotation to the exported
recordUsage function, using the actual value it returns (or Promise<void> if it
performs side effects without returning a value). Keep the existing parameter
contract and implementation behavior unchanged.
In `@apps/api/src/middleware/auth.ts`:
- Around line 12-26: Restrict the try/catch in the authentication middleware to
the prisma.session.findUnique call so only session lookup failures return 401.
Move await next() after the catch block while preserving the existing session
validation and c.set("user", ...) / c.set("session", ...) behavior, allowing
downstream handler exceptions to propagate.
In `@apps/api/src/providers/index.ts`:
- Around line 145-160: Prevent SSRF in the provider-selection branch for
“nvidia”, “mergedev”, “orcarouter”, and “concentrateai” by removing
client-controlled opts.baseUrl or validating it against a small
server-controlled allow-list of approved provider endpoints before passing it to
createOpenAICompatible. Preserve the existing environment/default URL fallback
and reject any unapproved scheme or host.
- Around line 10-11: Remove the local DAILY_TOKEN_LIMIT/DAILY_QUERY_LIMIT budget
logic and reuse lib/token-budget.ts’s checkDailyTokenBudget. Thread the
authenticated userId through createStreamText and its callers, passing it to
both the budget check and the onFinish usage-event persistence so accounting is
per user. Use the consolidated helper’s atomic/concurrency-safe path rather than
retaining the local check-then-act implementation.
- Around line 166-168: Update the option defaults in the provider configuration
to use nullish fallback semantics instead of || for opts.maxTokens,
opts.temperature, and opts.topP, preserving explicitly supplied 0 values while
still applying defaults when values are null or undefined.
- Around line 133-160: Update the provider switch so the google case passes
opts.apiKey to the Google provider factory, and the minimax case uses the
createMinimax factory with opts.apiKey before creating aiModel. Ensure
opts.apiKey is no longer silently ignored for these providers, while preserving
the existing model-selection behavior.
- Around line 170-187: Replace the inline Prisma usage-event creation in the
onFinish callback with the existing recordUsage helper from lib/track-usage.ts.
Pass the completed usage data and measured request duration so durationMs
reflects actual latency, while preserving the current provider, model, token,
and cost values.
In `@apps/api/src/routes/ai.ts`:
- Around line 27-38: Update the /ai/generate-object handler and its provider
flow to use the AI SDK structured generation path (generateObject or
streamObject) with an explicit output schema, rather than plain streamText. Pass
the schema through the provider invocation, honor
generateObjectBodySchema.responseFormat, and return the validated structured
object so the response cannot contain raw, unvalidated model text.
- Around line 56-101: Add the third onError callback to the Hono stream call in
the streaming response around result.textStream. Handle exceptions from
iteration or s.write by writing a JSON NDJSON error chunk to the client before
the stream closes, while preserving the existing text and finish chunk behavior;
keep the outer catch for errors occurring before streaming begins.
In `@packages/sdk/client.ts`:
- Around line 84-98: Replace the swallowed parse/validation errors in the
stream-decoding loop with a contextual error that is thrown to the caller,
covering malformed JSON, schema mismatches, and invalid finish chunks. Apply
this to both packages/sdk/client.ts lines 84-98 and lines 132-140 so the
corresponding streaming paths, including chatStream(), never return partial
results while silently omitting invalid chunks.
- Around line 158-159: Define and export a generateObjectResponseSchema for
validating the object-generation response, then update the response handling
around res.json() to return generateObjectResponseSchema.parse(data) instead of
constructing an unvalidated object. Preserve the existing GenerateObjectResponse
shape while rejecting empty or non-string object values.
---
Nitpick comments:
In `@apps/api/src/providers/index.ts`:
- Around line 61-77: Remove the local MODEL_PRICING table and computeCost
implementation in the provider module, and import and reuse the existing pricing
helper from lib/pricing.ts. Update callers to use that shared symbol so
cache-write pricing and the "nvidia/minimax-m3" entry remain consistent.
In `@apps/api/src/routes/ai.ts`:
- Around line 10-25: Extract the repeated message object definition into a
shared messageSchema near chatBodySchema, then reuse it for the messages arrays
in both chatBodySchema and generateObjectBodySchema. Preserve the existing role
enum and content validation unchanged.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: edbba77e-2d93-4aa8-abd4-30106e11ea78
⛔ Files ignored due to path filters (1)
bun.lockis excluded by!**/*.lock
📒 Files selected for processing (22)
apps/api/.env.exampleapps/api/package.jsonapps/api/src/app.tsapps/api/src/index.tsapps/api/src/lib/auth.tsapps/api/src/lib/pricing.tsapps/api/src/lib/prisma.tsapps/api/src/lib/token-budget.tsapps/api/src/lib/track-usage.tsapps/api/src/middleware/auth.tsapps/api/src/providers/index.tsapps/api/src/routes/ai.tsapps/api/src/routes/auth.tsapps/api/src/routes/health.tsapps/api/src/types.tsapps/api/tsconfig.jsonpackages/sdk/client.tspackages/sdk/index.tspackages/sdk/package.jsonpackages/sdk/schemas.tspackages/sdk/tsconfig.jsonpackages/sdk/types.ts
| "build": "bun build --target bun --outdir dist src/index.ts", | ||
| "start": "bun src/index.ts", |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Run the artifact produced by build.
build emits dist/index.js, but start runs src/index.ts. This means production does not use the built artifact and deployments containing only dist cannot start.
Suggested fix
- "start": "bun src/index.ts",
+ "start": "bun dist/index.js",📝 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.
| "build": "bun build --target bun --outdir dist src/index.ts", | |
| "start": "bun src/index.ts", | |
| "build": "bun build --target bun --outdir dist src/index.ts", | |
| "start": "bun dist/index.js", |
🤖 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 `@apps/api/package.json` around lines 8 - 9, Update the package.json start
script to execute the dist/index.js artifact generated by the build script,
instead of launching src/index.ts. Keep the existing build command unchanged so
production starts from the compiled output.
| "dev": "bun --watch src/index.ts", | ||
| "build": "bun build --target bun --outdir dist src/index.ts", | ||
| "start": "bun src/index.ts", | ||
| "lint": "echo 'api lint scaffold pending'", |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Replace the no-op lint script.
bun run lint currently succeeds without checking any source files, so the PR’s lint validation provides no protection against lint regressions. Wire an actual linter or fail until one is configured.
🤖 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 `@apps/api/package.json` at line 10, Replace the no-op "lint" script in
package.json with a command that runs the project's configured linter against
the API source files, or make the script exit nonzero until linting is
configured. Ensure bun run lint cannot succeed without performing real
validation.
| app.use("*", bodyLimit({ | ||
| maxSize: 10 * 1024 * 1024, | ||
| })) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Connect the documented body-limit configuration to runtime behavior.
The environment variable is declared but ignored by the application’s hardcoded limit.
apps/api/src/app.ts#L15-L17: parse and applySUPERCODE_JSON_BODY_LIMIT.apps/api/.env.example#L3-L3: keep the variable only if the application actually consumes it.
📍 Affects 2 files
apps/api/src/app.ts#L15-L17(this comment)apps/api/.env.example#L3-L3
🤖 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 `@apps/api/src/app.ts` around lines 15 - 17, Update the bodyLimit configuration
in apps/api/src/app.ts lines 15-17 to read and parse SUPERCODE_JSON_BODY_LIMIT,
using the parsed value as maxSize instead of the hardcoded limit. Keep
SUPERCODE_JSON_BODY_LIMIT in apps/api/.env.example line 3 because the runtime
configuration will consume it.
| app.route("/", healthRoute) | ||
| app.route("/", authRoute) | ||
| app.route("/", aiRoute) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '--- files ---\n'
git ls-files | rg '(^|/)(app|auth).*\.(ts|tsx|js)$|auth' || true
printf '\n--- app.ts outline ---\n'
ast-grep outline apps/api/src/app.ts --view expanded || true
printf '\n--- app.ts ---\n'
cat -n apps/api/src/app.ts
printf '\n--- auth.ts outline ---\n'
ast-grep outline apps/api/src/lib/auth.ts --view expanded || true
printf '\n--- auth.ts ---\n'
cat -n apps/api/src/lib/auth.ts
printf '\n--- route files matching auth/health/ai ---\n'
git ls-files apps/api/src | rg '(^|/)(routes|lib|index|auth|health|ai)\.(ts|js)$' || true
printf '\n--- route usages ---\n'
rg -n "healthRoute|authRoute|aiRoute|basePath|betterAuth|auth/me|sign-out|callback/github|`@auth`|/api/auth|toAuth" apps/api/src -S || trueRepository: yashdev9274/supercli
Length of output: 249
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- files ---'
git ls-files | rg '(^|/)(app|auth).*\.(ts|tsx|js)$|auth' || true
printf '%s\n' '\n--- app.ts outline ---'
ast-grep outline apps/api/src/app.ts --view expanded || true
printf '%s\n' '\n--- app.ts ---'
cat -n apps/api/src/app.ts
printf '%s\n' '\n--- auth.ts outline ---'
ast-grep outline apps/api/src/lib/auth.ts --view expanded || true
printf '%s\n' '\n--- auth.ts ---'
cat -n apps/api/src/lib/auth.ts
printf '%s\n' '\n--- route files matching auth/health/ai ---'
git ls-files apps/api/src | rg '(^|/)(routes|lib|index|auth|health|ai)\.(ts|js)$' || true
printf '%s\n' '\n--- route usages ---'
rg -n "healthRoute|authRoute|aiRoute|basePath|betterAuth|auth/me|sign-out|callback/github|`@auth`|/api/auth|toAuth" apps/api/src -S || trueRepository: yashdev9274/supercli
Length of output: 22917
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- apps/api/src/routes/auth.ts ---'
cat -n apps/api/src/routes/auth.ts
printf '%s\n' '\n--- apps/api/src/middleware/auth.ts ---'
cat -n apps/api/src/middleware/auth.ts
printf '%s\n' '\n--- apps/api package.json/auth deps ---'
if [ -f apps/api/package.json ]; then cat -n apps/api/package.json | sed -n '1,220p'; fi
printf '%s\n' '\n--- root/package lock snippets for hono/better-auth ---'
if [ -f package.json ]; then rg -n '"hono"|"better-auth"' package.json apps packages -S || true; fiRepository: yashdev9274/supercli
Length of output: 10194
🌐 Web query:
better-auth Node.js Hono getHandler postHandler basePath toAuth
💡 Result:
In Better Auth, the handler function acts as the unified endpoint for all authentication-related requests (GET and POST) [1][2]. When integrating with Hono, you mount this handler to a specific path using Hono's.on method [3][4]. Mounting the Handler The standard approach is to use a wildcard path to ensure all authentication sub-routes are captured [2][4]. It is recommended to use the single asterisk () wildcard rather than double, as the latter can cause routing conflicts in some Hono configurations [5]. Example implementation: const app = new Hono; app.on(["POST", "GET"], "/api/auth/", (c) => { return auth.handler(c.req.raw); }); Configuration and basePath The path you provide in app.on must align with the basePath defined in your Better Auth configuration [6]. By default, Better Auth uses /api/auth as its base path [6]. If you change the basePath in your auth options, you must update the mounting path in your Hono application to match [6]. Key Considerations 1. CORS Middleware: If your frontend is on a different domain, register the Hono CORS middleware before the auth route to ensure preflight requests (OPTIONS) are handled correctly [2][4]. 2. Session Middleware: You can define a global Hono middleware that calls auth.api.getSession to populate the Hono context (c.set) with user and session data, making it available to other routes [2][4]. 3. Client Configuration: When using the Hono RPC client (@hono/client) from a different domain, you must set credentials: "include" in the client's configuration to ensure cookies are sent with requests [2][4]. Note: The terms getHandler, postHandler, and toAuth are not standard, primary methods for Better Auth/Hono integration; the unified auth.handler method is the designated, authoritative way to process requests [1][2]. For frameworks like Next.js or generic Node.js pages, different utilities such as toNextJsHandler or toNodeHandler are used instead [7].
Citations:
- 1: https://github.com/better-auth/better-auth/blob/9fed16b6/docs/content/docs/integrations/hono.mdx
- 2: https://better-auth.com/docs/integrations/hono
- 3: https://hono.dev/examples/better-auth
- 4: https://better-auth-better-auth-16.mintlify.app/integrations/hono
- 5: Dynamic route params interfere with BetterAuth wildcard routes honojs/hono#4124
- 6: https://hono.dev/examples/better-auth-on-cloudflare
- 7: https://github.com/better-auth/better-auth/blob/3f75ee3e/docs/content/docs/integrations/next.mdx
Mount the Better Auth handler.
apps/api/src/lib/auth.ts configures Better Auth with basePath: "/api/auth", but apps/api/src/app.ts only mounts the custom /auth/me and /auth/sign-out routes. GitHub sign-in and /api/auth/callback/github need the Better Auth GET/POST handler mounted as a wildcard under the same base paths before the custom auth handlers.
🤖 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 `@apps/api/src/app.ts` around lines 24 - 26, Update the app route registration
in the app initialization flow to mount the Better Auth GET/POST wildcard
handler from the auth configuration before authRoute, using the configured
/api/auth base path so GitHub sign-in and callback requests are handled while
preserving the existing custom auth routes.
| clientId: process.env.GITHUB_CLIENT_ID as string, | ||
| clientSecret: process.env.GITHUB_CLIENT_SECRET as string, |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== files =="
git ls-files | rg '(^|/)(auth\.ts|\.env\.example|package\.json|pnpm-lock\.yaml)$' || true
echo "== auth outline =="
if [ -f apps/api/src/lib/auth.ts ]; then
ast-grep outline apps/api/src/lib/auth.ts || true
wc -l apps/api/src/lib/auth.ts
sed -n '1,220p' apps/api/src/lib/auth.ts | cat -n
fi
echo "== env references =="
rg -n "GITHUB_CLIENT_ID|GITHUB_CLIENT_SECRET|github|GITHUB|CLIENT_URL|SERVER_URL|better-auth" apps/api .env.example .env* -S 2>/dev/null || trueRepository: yashdev9274/supercli
Length of output: 2899
🌐 Web query:
Better Auth OAuth github redirectURI baseURL SERVER_URL CLIENT_URL environment
💡 Result:
In Better Auth, managing OAuth redirect URIs involves correctly configuring your environment and authentication settings to bridge the gap between your server (where the auth logic runs) and your client (where the user interacts with your application) [1]. Key Configuration Concepts baseURL: This is the root URL of your authentication server [2]. You should always set this explicitly in your Better Auth configuration or via the environment variable BETTER_AUTH_URL [3]. Relying on automatic request inference is not recommended for production environments [3]. redirectURI: By default, Better Auth constructs the redirect URI using the baseURL and a standard callback path (e.g., /api/auth/callback/github) [4][5]. You can override this by providing a specific redirectURI in the provider configuration [2][3], but it must match exactly what you have registered in your GitHub OAuth App settings. Handling Client vs. Server URLs A common issue occurs when your server runs on one port (e.g., 8787) and your client on another (e.g., 3000) [1]. Better Auth might default to the server's URL, leading to redirect mismatches [1]. To resolve this: 1. Trusted Origins: Add your client's URL to the trustedOrigins array in your auth configuration to allow cross-origin communication [1]. export const auth = betterAuth({ baseURL: "http://your-server-url", trustedOrigins: ["http://localhost:3000"], }); 2. Callback URL Parameter: When initiating the sign-in process on the client, pass the callbackURL parameter to specify where the user should be sent after the OAuth flow completes [1]. await authClient.signIn.social({ provider: "github", callbackURL: "http://localhost:3000/dashboard", }); 3. OAuth Proxy Plugin: For complex deployments (e.g., preview environments or multi-host setups), use the OAuth Proxy plugin [6]. This allows you to register only your production callback URL with GitHub while the plugin handles routing requests from other environments through your production server [6]. Always ensure that the redirect URL registered in your GitHub Developer Settings matches the callback endpoint constructed by Better Auth [7][6]. If you are using the Generic OAuth plugin, remember that the redirectURI must include the:providerId placeholder [5][8].
Citations:
- 1: Better Auth callback redirects to server (Hono 8787) port instead of client port 3000. better-auth/better-auth#5696
- 2: https://better-auth.com/docs/reference/options
- 3: https://github.com/better-auth/better-auth/blob/main/docs/content/docs/reference/options.mdx
- 4: fix(oauth): derive redirect URI from per-request baseURL better-auth/better-auth#10127
- 5: https://better-auth.com/docs/plugins/generic-oauth
- 6: https://github.com/better-auth/better-auth/blob/main/docs/content/docs/plugins/oauth-proxy.mdx
- 7: https://better-auth.com/docs/concepts/oauth
- 8: https://raw.githubusercontent.com/better-auth/better-auth/main/docs/content/docs/plugins/generic-oauth.mdx
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== root env example =="
if [ -f .env.example ]; then
cat -n .env.example | sed -n '1,180p'
fi
echo "== apps/api env example =="
if [ -f apps/api/.env.example ]; then
cat -n apps/api/.env.example | sed -n '1,180p'
fi
echo "== runtime probe process.env assertion behavior =="
node - <<'JS'
const env = { GITHUB_CLIENT_ID: undefined, GITHUB_CLIENT_SECRET: undefined }
function register(input) {
return {
clientId: input.GITHUB_CLIENT_ID,
clientSecret: input.GITHUB_CLIENT_SECRET,
authEnvIsNull: input.GITHUB_CLIENT_ID === null,
authEnvIsUndefined: input.GITHUB_CLIENT_ID === undefined,
authEnvEmptyString: input.GITHUB_CLIENT_ID === "",
}
}
console.log(JSON.stringify(register(env), null, 2))
JS
echo "== better-auth package metadata/source availability =="
python3 - <<'PY'
import json
from pathlib import Path
root = Path("apps/api")
for p in ["package.json", "pnpm-lock.yaml", "package-lock.json", "yarn.lock", "bun.lockb"]:
path = root / p
print(f"{path}: exists={path.exists()}")
PYRepository: yashdev9274/supercli
Length of output: 1958
Validate GitHub credentials before registering the provider.
as string only suppresses TypeScript errors; it does not convert missing environment variables into strings. With apps/api/.env.example, GitHub is configured with undefined credentials, so the provider can register with no clientId/clientSecret. Omit the provider when GitHub is optional, or fail fast with a clear configuration error when it is required.
🤖 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 `@apps/api/src/lib/auth.ts` around lines 16 - 17, Validate GITHUB_CLIENT_ID and
GITHUB_CLIENT_SECRET before registering the GitHub provider instead of relying
on type assertions. When GitHub is optional, omit the provider if either value
is missing; when required, fail fast with a clear configuration error, ensuring
no provider is registered with undefined credentials.
| onFinish: async ({ usage }) => { | ||
| const cost = computeCost(aiModel, usage.inputTokens ?? 0, usage.outputTokens ?? 0, 0) | ||
| try { | ||
| await prisma.usageEvent.create({ | ||
| data: { | ||
| provider: aiProvider, | ||
| model: aiModel, | ||
| inputTokens: usage.inputTokens ?? 0, | ||
| outputTokens: usage.outputTokens ?? 0, | ||
| cachedInputTokens: 0, | ||
| costUsd: cost, | ||
| durationMs: 0, | ||
| }, | ||
| }) | ||
| } catch (e) { | ||
| console.error("[track-usage] Failed to record usage:", e) | ||
| } | ||
| }, |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
Usage recording duplicates lib/track-usage.ts and hardcodes durationMs: 0.
This onFinish block reimplements recordUsage inline instead of calling it, and durationMs is always 0 rather than measured — usage records will never carry real latency data.
+ const startedAt = Date.now()
const result = streamText({
...
onFinish: async ({ usage }) => {
const cost = computeCost(aiModel, usage.inputTokens ?? 0, usage.outputTokens ?? 0, 0)
- try {
- await prisma.usageEvent.create({
- data: {
- provider: aiProvider,
- model: aiModel,
- inputTokens: usage.inputTokens ?? 0,
- outputTokens: usage.outputTokens ?? 0,
- cachedInputTokens: 0,
- costUsd: cost,
- durationMs: 0,
- },
- })
- } catch (e) {
- console.error("[track-usage] Failed to record usage:", e)
- }
+ await recordUsage({
+ provider: aiProvider,
+ model: aiModel,
+ inputTokens: usage.inputTokens ?? 0,
+ outputTokens: usage.outputTokens ?? 0,
+ costUsd: cost,
+ durationMs: Date.now() - startedAt,
+ })
},
})📝 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.
| onFinish: async ({ usage }) => { | |
| const cost = computeCost(aiModel, usage.inputTokens ?? 0, usage.outputTokens ?? 0, 0) | |
| try { | |
| await prisma.usageEvent.create({ | |
| data: { | |
| provider: aiProvider, | |
| model: aiModel, | |
| inputTokens: usage.inputTokens ?? 0, | |
| outputTokens: usage.outputTokens ?? 0, | |
| cachedInputTokens: 0, | |
| costUsd: cost, | |
| durationMs: 0, | |
| }, | |
| }) | |
| } catch (e) { | |
| console.error("[track-usage] Failed to record usage:", e) | |
| } | |
| }, | |
| onFinish: async ({ usage }) => { | |
| const cost = computeCost(aiModel, usage.inputTokens ?? 0, usage.outputTokens ?? 0, 0) | |
| await recordUsage({ | |
| provider: aiProvider, | |
| model: aiModel, | |
| inputTokens: usage.inputTokens ?? 0, | |
| outputTokens: usage.outputTokens ?? 0, | |
| costUsd: cost, | |
| durationMs: Date.now() - startedAt, | |
| }) | |
| }, |
🤖 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 `@apps/api/src/providers/index.ts` around lines 170 - 187, Replace the inline
Prisma usage-event creation in the onFinish callback with the existing
recordUsage helper from lib/track-usage.ts. Pass the completed usage data and
measured request duration so durationMs reflects actual latency, while
preserving the current provider, model, token, and cost values.
| const generateObjectBodySchema = z.object({ | ||
| messages: z.array(z.object({ | ||
| role: z.enum(["system", "user", "assistant"]), | ||
| content: z.string(), | ||
| })), | ||
| provider: z.string().optional().default("google"), | ||
| model: z.string().optional().default("gemini-3-flash-preview"), | ||
| apiKey: z.string().optional(), | ||
| baseUrl: z.string().optional(), | ||
| temperature: z.number().optional(), | ||
| responseFormat: z.enum(["json", "text"]).optional().default("json"), | ||
| }) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "## Locate file and package context"
git ls-files | rg '(^|/)apps/api/src/routes/ai\.ts$|(^|/)package\.json$|(^|/)pnpm-lock\.yaml$|(^|/)yarn\.lock$|(^|/)package-lock\.json$' || true
echo
echo "## Inspect apps/api/src/routes/ai.ts"
if [ -f apps/api/src/routes/ai.ts ]; then
wc -l apps/api/src/routes/ai.ts
sed -n '1,170p' apps/api/src/routes/ai.ts | cat -n
fi
echo
echo "## Search for ai route usage and createStreamText generate object usages"
rg -n "generate-object|generateObject|streamObject|createStreamText|responseFormat|/ai" apps/api/src -S || trueRepository: yashdev9274/supercli
Length of output: 7192
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "## apps/api/src/providers/index.ts relevant implementation"
sed -n '1,150p' apps/api/src/providers/index.ts | cat -n
echo
echo "## package versions for ai sdk and zod in relevant package manifests"
python3 - <<'PY'
import json, pathlib
for p in [pathlib.Path("package.json"), pathlib.Path("apps/api/package.json")]:
if p.exists():
print(f"--- {p} ---")
data=json.loads(p.read_text())
deps={}
for section in ("dependencies","devDependencies","peerDependencies"):
if section in data:
deps.update(data[section])
for name in ("ai","`@ai-sdk/google`","`@ai-sdk/provider`","dai-sdk","zod"):
if name in deps:
print(f"{name}: {deps[name]}")
PY
echo
echo "## References to responseFormat, generate-object JSON parsing downstream"
rg -n "responseFormat|generate-object|\"object\"|ai/generate-object|createStreamText" . -S --glob '!node_modules' --glob '!dist' --glob '!build' || true
echo
echo "## Behavioral probe: request body and implementation fields"
python3 - <<'PY'
from pathlib import Path
import re
src = Path("apps/api/src/routes/ai.ts")
text = src.read_text()
m = re.search(r'const generateObjectBodySchema = z\.object\((.*?)\n\)\n', text, re.S)
schema = m.group(0) if m else ""
print("has responseFormat field:", "responseFormat" in schema)
print("responseFormat is consumed by createStreamText call:", bool(re.search(r'generateObjectBodySchema[\s\S]*?createStreamText\(\{[\s\S]*?responseFormat', text)))
call = re.search(r'createStreamText\(\{(?P<body>.*?)(?=\}\),\s*async|\}\s*\)\.post)', text, re.S)
prompt = re.search(r'systemPrompt:\s*"(.*?)"', text)
for i in range(105,135):
line = text.splitlines()[i-1]
if any(s in line for s in ["let responseFormat", "const responseFormat", "responseFormat:"]):
print(f"line {i}: responseFormat reference: {line.strip()}")
if prompt:
print("explicit system prompt for generate-object:", prompt.group(1))
PYRepository: yashdev9274/supercli
Length of output: 13539
Use a schema-backed object generation path for /ai/generate-object.
responseFormat is validated but ignored, and the endpoint returns c.json({ object: result.text }) using the plain streamText flow, so the object payload is the raw model response and can be non-JSON or drift from the expected output shape. Use AI SDK generateObject/streamObject with a schema and pass that schema through the provider path so structured requests actually validate the model output.
🤖 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 `@apps/api/src/routes/ai.ts` around lines 27 - 38, Update the
/ai/generate-object handler and its provider flow to use the AI SDK structured
generation path (generateObject or streamObject) with an explicit output schema,
rather than plain streamText. Pass the schema through the provider invocation,
honor generateObjectBodySchema.responseFormat, and return the validated
structured object so the response cannot contain raw, unvalidated model text.
| try { | ||
| const result = await createStreamText({ | ||
| provider: body.provider, | ||
| model: body.model, | ||
| apiKey: body.apiKey, | ||
| baseUrl: body.baseUrl, | ||
| maxTokens: body.maxTokens, | ||
| temperature: body.temperature, | ||
| topP: body.topP, | ||
| messages: coreMessages, | ||
| systemPrompt, | ||
| conversationId: body.conversationId, | ||
| }) | ||
|
|
||
| c.header("Content-Type", "application/x-ndjson") | ||
| c.header("Cache-Control", "no-cache") | ||
| c.header("Connection", "keep-alive") | ||
|
|
||
| return stream(c, async (s) => { | ||
| for await (const delta of result.textStream) { | ||
| const chunk = JSON.stringify({ type: "text-delta", textDelta: delta }) + "\n" | ||
| await s.write(chunk) | ||
| } | ||
|
|
||
| const finishReason = await result.finishReason | ||
| const usage = await result.usage | ||
|
|
||
| const finishChunk = JSON.stringify({ | ||
| type: "finish", | ||
| finishReason, | ||
| usage: { | ||
| promptTokens: usage.inputTokens ?? 0, | ||
| completionTokens: usage.outputTokens ?? 0, | ||
| totalTokens: (usage.inputTokens ?? 0) + (usage.outputTokens ?? 0), | ||
| }, | ||
| }) + "\n" | ||
| await s.write(finishChunk) | ||
| }) | ||
| } catch (error: any) { | ||
| const errorMessage = error?.message || "Unknown error" | ||
| if (errorMessage.includes("Daily token limit")) { | ||
| return c.json({ error: errorMessage }, 429) | ||
| } | ||
| console.error("[AI Chat Error]", error) | ||
| return c.json({ error: errorMessage }, 500) | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🌐 Web query:
Does hono/streaming's stream() helper suppress errors thrown inside the callback when no onError handler is passed, in hono 4.7?
💡 Result:
In Hono 4.7, the stream helper does not suppress errors thrown inside the callback function, regardless of whether an onError handler is provided [1][2]. When an error is thrown within the callback: 1. If an onError handler is provided as the third argument, Hono executes it to allow for custom error logic (such as writing an error message to the stream) [1][2][3]. 2. If an onError handler is not provided, Hono catches the error and outputs it using console.error [1][2]. It is important to note that when an error occurs inside the streaming callback, the Hono app-level onError handler will not be triggered [1][4]. This is because the response has already been sent once the streaming begins, making it impossible to overwrite the response with an error page or custom response at that stage [1][5]. The stream will be automatically closed after the callback (or the error handler) finishes execution [1][4].
Citations:
- 1: https://hono.dev/docs/helpers/streaming
- 2: https://github.com/honojs/hono/blob/24547a6e/src/helper/streaming/stream.ts
- 3: https://honojs-hono.mintlify.app/helpers/streaming
- 4: https://www.honojs.com/docs/helpers/streaming
- 5: https://honodev.pages.dev/docs/helpers/streaming/
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== package hono versions =="
if [ -f package.json ]; then jq -r '.dependencies.hono?, .devDependencies.hono?' package.json; else true; fi
fd -a 'package.json$|pnpm-lock.yaml$|yarn.lock$|package-lock.json$' . | sed 's#^\./##' | head -40
echo "== locate ai.ts =="
fd 'ai\.ts$' apps src 2>/dev/null || fd 'ai\.ts$' .
echo "== inspect apps/api/src/routes/ai.ts relevant section =="
cat -n apps/api/src/routes/ai.ts | sed -n '1,150p'
echo "== read-only verifier: stream helper behavior from source if available =="
python3 - <<'PY'
from pathlib import Path
roots = [Path('node_modules/hono'), Path('/usr/local/lib/node_modules/hono'), Path('apps/api/node_modules/hono')]
for root in roots:
p = root / 'dist' / 'cjs' / 'helper' / 'streaming' / 'stream.cjs'
if not p.exists():
p = root / 'dist' / 'cjs' / 'helper' / 'streaming' / 'stream.js'
if not p.exists():
continue
print(f"FOUND {p}")
text = p.read_text(errors='ignore')
for needle in ['onError', 'c.res.body.close', 'console.error', 'callback']:
print(f'--- {needle} ---')
idx = text.find(needle)
print(text[max(0, idx-800): idx+1200] if idx != -1 else '<not found>')
PYRepository: yashdev9274/supercli
Length of output: 6757
🌐 Web query:
"hono" "stream" "onError" "callback" "console.error" "close"
💡 Result:
In Hono, the stream and streamSSE helpers automatically close the response stream after their callback function completes [1][2][3]. ### Error Handling The streaming helpers accept an optional third argument, which is an onError callback function [1][2][4]. * If an error occurs within the streaming callback and an onError handler is provided, that handler will execute [1][2]. * If no onError handler is specified, the error is caught and output to console.error by default [1][4]. * Important: The onError handler for the streaming helper is specific to the stream itself. It is distinct from the global app.onError hook [1][5]. Because the stream has already begun when the streaming callback executes, errors occurring during streaming cannot be caught by app.onError to overwrite the initial response [1]. ### Stream Closure * Automatic Closure: The underlying stream is automatically closed by the helper after the streaming callback (and the onError callback, if provided) finishes execution [1][2][3]. * Manual Closure: You can manually close the stream at any time by calling stream.close() within your code [2][6]. * Behavior with Exceptions: If the streaming callback throws an error, the streaming helper ensures the stream is closed (typically via a finally block in its internal implementation) [7][3]. Example usage with error handling: typescript app.get('/stream', (c) => { return stream( c, async (stream) => { await stream.write('Writing data...') // If this throws, execution moves to the onError callback throw new Error('Stream failed') }, (err, stream) => { console.error('Caught error:', err) stream.writeln('An error occurred!') // stream.close() is called automatically after this } ) }) [1] https://hono.dev/docs/helpers/streaming [2] https://mintlify.wiki/honojs/hono/api/helpers/streaming [7] honojs/hono#2164 [3] honojs/hono#2993 [5] https://hono.dev/docs/api/hono [4] https://github.com/MagicAPI/ai-gateway-ts/blob/main/HONO.md [6] honojs/hono#2050
Citations:
- 1: https://hono.dev/docs/helpers/streaming
- 2: https://mintlify.wiki/honojs/hono/api/helpers/streaming
- 3: Stream should not be closed automatically honojs/hono#2993
- 4: https://github.com/MagicAPI/ai-gateway-ts/blob/main/HONO.md
- 5: https://hono.dev/docs/api/hono
- 6: Hono streams immediately closes the connection unless sleep is used honojs/hono#2050
- 7: Throwing (unhandled) exception in streamSSE brings down server honojs/hono#2164
Handle mid-stream exceptions inside the Hono streaming callback.
stream(c, cb) is called without the third onError argument, so errors thrown after headers have been sent during result.textStream iteration or s.write() are not returned to the client, are not reachable by the surrounding try/catch, and only close with the default console.error. Add an onError handler that writes an error chunk before the stream closes.
🤖 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 `@apps/api/src/routes/ai.ts` around lines 56 - 101, Add the third onError
callback to the Hono stream call in the streaming response around
result.textStream. Handle exceptions from iteration or s.write by writing a JSON
NDJSON error chunk to the client before the stream closes, while preserving the
existing text and finish chunk behavior; keep the outer catch for errors
occurring before streaming begins.
| for (const line of lines) { | ||
| if (!line.trim()) continue | ||
| try { | ||
| const chunk = JSON.parse(line) | ||
| const parsed = streamChunkSchema.parse(chunk) | ||
|
|
||
| if (parsed.type === "text-delta") { | ||
| text += parsed.textDelta | ||
| } else if (parsed.type === "finish") { | ||
| finishReason = parsed.finishReason | ||
| usage = parsed.usage | ||
| } | ||
| } catch { | ||
| // Skip invalid JSON lines | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Do not silently discard malformed stream chunks.
A malformed JSON line, schema mismatch, or invalid finish chunk is ignored, so callers receive partial text or an empty finishReason as a successful result. Surface these failures instead of swallowing them.
packages/sdk/client.ts#L84-L98: throw a contextual stream-decoding error rather than continuing.packages/sdk/client.ts#L132-L140: apply the same behavior sochatStream()does not silently omit chunks.
📍 Affects 1 file
packages/sdk/client.ts#L84-L98(this comment)packages/sdk/client.ts#L132-L140
🤖 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/sdk/client.ts` around lines 84 - 98, Replace the swallowed
parse/validation errors in the stream-decoding loop with a contextual error that
is thrown to the caller, covering malformed JSON, schema mismatches, and invalid
finish chunks. Apply this to both packages/sdk/client.ts lines 84-98 and lines
132-140 so the corresponding streaming paths, including chatStream(), never
return partial results while silently omitting invalid chunks.
| const data = await res.json() | ||
| return { object: data.object } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
ast-grep outline apps/api/src/routes/ai.ts --items all
rg -n -C 4 'generate-object|object' apps/api/src/routes/ai.ts packages/sdkRepository: yashdev9274/supercli
Length of output: 7556
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '--- packages/sdk/types.ts relevant section ---\n'
sed -n '1,90p' packages/sdk/types.ts
printf '\n--- packages/sdk/schemas.ts relevant section ---\n'
sed -n '1,110p' packages/sdk/schemas.ts
printf '\n--- packages/sdk/client.ts relevant method ---\n'
sed -n '130,165p' packages/sdk/client.ts
printf '\n--- apps/api/src/routes/ai.ts route section ---\n'
sed -n '96,135p' apps/api/src/routes/ai.tsRepository: yashdev9274/supercli
Length of output: 249
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- packages/sdk/types.ts relevant section ---'
sed -n '1,90p' packages/sdk/types.ts
printf '%s\n' ''
printf '%s\n' '--- packages/sdk/schemas.ts relevant section ---'
sed -n '1,110p' packages/sdk/schemas.ts
printf '%s\n' ''
printf '%s\n' '--- packages/sdk/client.ts relevant method ---'
sed -n '130,165p' packages/sdk/client.ts
printf '%s\n' ''
printf '%s\n' '--- apps/api/src/routes/ai.ts route section ---'
sed -n '96,135p' apps/api/src/routes/ai.tsRepository: yashdev9274/supercli
Length of output: 5805
Validate the object-generation response before returning it.
res.json() is untyped, so an empty or non-string object can resolve with a value that doesn’t satisfy GenerateObjectResponse. Add and export a generateObjectResponseSchema, then return generateObjectResponseSchema.parse(data).
🤖 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/sdk/client.ts` around lines 158 - 159, Define and export a
generateObjectResponseSchema for validating the object-generation response, then
update the response handling around res.json() to return
generateObjectResponseSchema.parse(data) instead of constructing an unvalidated
object. Preserve the existing GenerateObjectResponse shape while rejecting empty
or non-string object values.
Description
Please include a summary of the change and which issue is fixed.
Fixes #(issue)
Type of change
How Has This Been Tested?
Please describe the tests that you ran to verify your changes.
bun testpassesbun run typecheckpassesbun run lintpasses (if applicable)Checklist:
Summary by CodeRabbit
New Features
Chores