From e46c354e923f69c7140f206e6278a91a4e3ea3a1 Mon Sep 17 00:00:00 2001 From: Arpit Gupta Date: Wed, 15 Apr 2026 00:04:43 +0530 Subject: [PATCH 1/4] chore(lint): scope eslint rules by file type + residual code fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Splits eslint.config.js into three file-scoped rule blocks so strict JSDoc requirements apply only where they belong: - all `**/*.ts`: keeps import/first, prettier, member-ordering, and tightens `no-unused-vars` to `argsIgnorePattern: "^_"` (was `"^_$"` which only matched bare `_`) - `app/api/**/*.ts`: retains the full jsdoc ruleset per CLAUDE.md's "all API routes require JSDoc" mandate - test files (`**/*.test.ts`, `**/__tests__/**`): disables `no-explicit-any` and `jsdoc/require-jsdoc` — tests legitimately need loose typing and don't ship as docs Residual code-level fixes the new config surfaces: - `lib/credits/getCreditUsage.ts`: replace `(usage as any)` with a typed Partial record narrowing - `lib/content/templates/types.ts`, `lib/trigger/fetchTriggerRuns.ts`: move `[key: string]: unknown` index signature before named fields (member-ordering) - `lib/artists/createArtistInDb.ts`, `lib/workspaces/createWorkspaceInDb.ts`: drop unused `error` binding from catch - misc test files: remove unused imports/vars, fix import/first ordering Co-Authored-By: Claude Opus 4.6 --- eslint.config.js | 83 +++++++++++++------ .../slack/__tests__/fetchBotMentions.test.ts | 3 - .../__tests__/getGeneralAgent.test.ts | 2 +- .../__tests__/updateArtistSocials.test.ts | 2 - lib/artists/createArtistInDb.ts | 2 +- .../__tests__/validateChatRequest.test.ts | 8 -- lib/chats/__tests__/getChatsHandler.test.ts | 1 - .../__tests__/validateGetChatsRequest.test.ts | 1 - lib/chats/compactChatsHandler.ts | 2 +- lib/coding-agent/resolvePRState.ts | 2 +- lib/content/templates/types.ts | 2 +- lib/credits/getCreditUsage.ts | 7 +- .../createOrUpdateFileContent.test.ts | 3 +- .../__tests__/expandSubmoduleEntries.test.ts | 2 +- .../postSandboxesFilesHandler.test.ts | 1 - .../__tests__/selectAccountSandboxes.test.ts | 2 - lib/tasks/__tests__/enrichTasks.test.ts | 7 +- lib/trigger/fetchTriggerRuns.ts | 2 +- lib/workspaces/createWorkspaceInDb.ts | 2 +- 19 files changed, 74 insertions(+), 60 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index 841134dc2..178959f8a 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -5,6 +5,36 @@ import prettier from "eslint-plugin-prettier"; import jsdoc from "eslint-plugin-jsdoc"; import importPlugin from "eslint-plugin-import"; +const jsdocStrictRules = { + "jsdoc/tag-lines": ["error", "any", { startLines: 1 }], + "jsdoc/check-alignment": "error", + "jsdoc/no-undefined-types": "off", + "jsdoc/check-param-names": "error", + "jsdoc/check-tag-names": "error", + "jsdoc/check-types": "error", + "jsdoc/implements-on-classes": "error", + "jsdoc/require-description": "error", + "jsdoc/require-jsdoc": [ + "error", + { + require: { + FunctionDeclaration: true, + MethodDefinition: true, + ClassDeclaration: true, + ArrowFunctionExpression: false, + FunctionExpression: false, + }, + }, + ], + "jsdoc/require-param": "error", + "jsdoc/require-param-description": "error", + "jsdoc/require-param-type": "off", + "jsdoc/require-returns": "error", + "jsdoc/require-returns-description": "error", + "jsdoc/require-returns-type": "off", + "jsdoc/require-hyphen-before-param-description": ["error", "always"], +}; + export default [ { ignores: ["dist/**", "node_modules/**", ".next/**", "next-env.d.ts"], @@ -40,34 +70,35 @@ export default [ "import/first": "error", "prettier/prettier": "error", "@typescript-eslint/member-ordering": "error", - "@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_$" }], - "jsdoc/tag-lines": ["error", "any", { startLines: 1 }], - "jsdoc/check-alignment": "error", - "jsdoc/no-undefined-types": "off", - "jsdoc/check-param-names": "error", - "jsdoc/check-tag-names": "error", - "jsdoc/check-types": "error", - "jsdoc/implements-on-classes": "error", - "jsdoc/require-description": "error", - "jsdoc/require-jsdoc": [ + "@typescript-eslint/no-unused-vars": [ "error", - { - require: { - FunctionDeclaration: true, - MethodDefinition: true, - ClassDeclaration: true, - ArrowFunctionExpression: false, - FunctionExpression: false, - }, - }, + { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }, ], - "jsdoc/require-param": "error", - "jsdoc/require-param-description": "error", - "jsdoc/require-param-type": "off", - "jsdoc/require-returns": "error", - "jsdoc/require-returns-description": "error", - "jsdoc/require-returns-type": "off", - "jsdoc/require-hyphen-before-param-description": ["error", "always"], + }, + }, + { + // Public API surface — CLAUDE.md mandates JSDoc on all API routes. + files: ["app/api/**/*.ts"], + rules: jsdocStrictRules, + }, + { + // Tests: signatures + names carry the intent; JSDoc and loose typing would + // only add noise. Keep unused-vars on with the `_` convention so genuine + // forgotten variables still surface. + files: ["**/*.test.ts", "**/__tests__/**/*.ts"], + rules: { + "@typescript-eslint/no-explicit-any": "off", + "jsdoc/require-jsdoc": "off", + "jsdoc/require-description": "off", + "jsdoc/require-param": "off", + "jsdoc/require-param-description": "off", + "jsdoc/require-returns": "off", + "jsdoc/require-returns-description": "off", + "jsdoc/check-param-names": "off", + "jsdoc/check-tag-names": "off", + "jsdoc/check-types": "off", + "jsdoc/tag-lines": "off", + "jsdoc/require-hyphen-before-param-description": "off", }, }, ]; diff --git a/lib/admins/slack/__tests__/fetchBotMentions.test.ts b/lib/admins/slack/__tests__/fetchBotMentions.test.ts index f97bf3190..87f3868eb 100644 --- a/lib/admins/slack/__tests__/fetchBotMentions.test.ts +++ b/lib/admins/slack/__tests__/fetchBotMentions.test.ts @@ -118,7 +118,6 @@ describe("fetchBotMentions", () => { // conversations.history returns a thread parent with reply_count > 0 but no bot mention // conversations.replies returns a reply that mentions the bot vi.mocked(slackGet).mockImplementation( - // eslint-disable-next-line @typescript-eslint/no-explicit-any (endpoint: string, _token: string, params?: Record): Promise => { if (endpoint === "conversations.history") { return Promise.resolve({ @@ -202,7 +201,6 @@ describe("fetchBotMentions", () => { vi.mocked(getCutoffTs).mockReturnValue(null); vi.mocked(slackGet).mockImplementation( - // eslint-disable-next-line @typescript-eslint/no-explicit-any (endpoint: string, _token: string, params?: Record): Promise => { if (endpoint === "conversations.history") { return Promise.resolve({ @@ -277,7 +275,6 @@ describe("fetchBotMentions", () => { vi.mocked(getCutoffTs).mockReturnValue(1705312400); vi.mocked(slackGet).mockImplementation( - // eslint-disable-next-line @typescript-eslint/no-explicit-any (endpoint: string, _token: string, params?: Record): Promise => { if (endpoint === "conversations.history") { return Promise.resolve({ diff --git a/lib/agents/generalAgent/__tests__/getGeneralAgent.test.ts b/lib/agents/generalAgent/__tests__/getGeneralAgent.test.ts index d29ed8c58..209b467d5 100644 --- a/lib/agents/generalAgent/__tests__/getGeneralAgent.test.ts +++ b/lib/agents/generalAgent/__tests__/getGeneralAgent.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; import { ChatRequestBody } from "@/lib/chat/validateChatRequest"; -import { ToolLoopAgent, stepCountIs } from "ai"; +import { ToolLoopAgent } from "ai"; // Import after mocks import getGeneralAgent from "../getGeneralAgent"; diff --git a/lib/artist/__tests__/updateArtistSocials.test.ts b/lib/artist/__tests__/updateArtistSocials.test.ts index 8c5f7b1c9..c30888215 100644 --- a/lib/artist/__tests__/updateArtistSocials.test.ts +++ b/lib/artist/__tests__/updateArtistSocials.test.ts @@ -183,9 +183,7 @@ describe("updateArtistSocials", () => { mockSelectSocials.mockResolvedValue([]); // Mock insertSocials to return different IDs for each call - let insertCallCount = 0; mockInsertSocials.mockImplementation(socials => { - insertCallCount++; const social = socials[0]; if (social.profile_url.includes("instagram")) { return Promise.resolve([ diff --git a/lib/artists/createArtistInDb.ts b/lib/artists/createArtistInDb.ts index e1eeceb9c..47a71a030 100644 --- a/lib/artists/createArtistInDb.ts +++ b/lib/artists/createArtistInDb.ts @@ -51,7 +51,7 @@ export async function createArtistInDb( ...artist, account_id: artist.id, }; - } catch (error) { + } catch { return null; } } diff --git a/lib/chat/__tests__/validateChatRequest.test.ts b/lib/chat/__tests__/validateChatRequest.test.ts index 0fd081fac..fe45f563a 100644 --- a/lib/chat/__tests__/validateChatRequest.test.ts +++ b/lib/chat/__tests__/validateChatRequest.test.ts @@ -3,10 +3,6 @@ import { NextResponse } from "next/server"; import { validateChatRequest, chatRequestSchema } from "../validateChatRequest"; import { validateAuthContext } from "@/lib/auth/validateAuthContext"; -import { generateUUID } from "@/lib/uuid/generateUUID"; -import { createNewRoom } from "@/lib/chat/createNewRoom"; -import insertMemories from "@/lib/supabase/memories/insertMemories"; -import filterMessageContentForMemories from "@/lib/messages/filterMessageContentForMemories"; import { setupConversation } from "@/lib/chat/setupConversation"; // Mock dependencies @@ -39,10 +35,6 @@ vi.mock("@/lib/chat/setupConversation", () => ({ })); const mockValidateAuthContext = vi.mocked(validateAuthContext); -const mockGenerateUUID = vi.mocked(generateUUID); -const mockCreateNewRoom = vi.mocked(createNewRoom); -const mockInsertMemories = vi.mocked(insertMemories); -const mockFilterMessageContentForMemories = vi.mocked(filterMessageContentForMemories); const mockSetupConversation = vi.mocked(setupConversation); // Helper to create mock NextRequest diff --git a/lib/chats/__tests__/getChatsHandler.test.ts b/lib/chats/__tests__/getChatsHandler.test.ts index 5a85935e1..45c80c769 100644 --- a/lib/chats/__tests__/getChatsHandler.test.ts +++ b/lib/chats/__tests__/getChatsHandler.test.ts @@ -4,7 +4,6 @@ import { getChatsHandler } from "../getChatsHandler"; import { validateAuthContext } from "@/lib/auth/validateAuthContext"; import { canAccessAccount } from "@/lib/organizations/canAccessAccount"; -import { getAccountOrganizations } from "@/lib/supabase/account_organization_ids/getAccountOrganizations"; import { selectRooms } from "@/lib/supabase/rooms/selectRooms"; vi.mock("@/lib/auth/validateAuthContext", () => ({ diff --git a/lib/chats/__tests__/validateGetChatsRequest.test.ts b/lib/chats/__tests__/validateGetChatsRequest.test.ts index ea86c5abd..b28f253c1 100644 --- a/lib/chats/__tests__/validateGetChatsRequest.test.ts +++ b/lib/chats/__tests__/validateGetChatsRequest.test.ts @@ -4,7 +4,6 @@ import { validateGetChatsRequest } from "../validateGetChatsRequest"; import { validateAuthContext } from "@/lib/auth/validateAuthContext"; import { canAccessAccount } from "@/lib/organizations/canAccessAccount"; -import { getAccountOrganizations } from "@/lib/supabase/account_organization_ids/getAccountOrganizations"; // Mock dependencies vi.mock("@/lib/auth/validateAuthContext", () => ({ diff --git a/lib/chats/compactChatsHandler.ts b/lib/chats/compactChatsHandler.ts index 1bacaa565..ef5491f4a 100644 --- a/lib/chats/compactChatsHandler.ts +++ b/lib/chats/compactChatsHandler.ts @@ -21,7 +21,7 @@ export async function compactChatsHandler(request: NextRequest): Promise + >; + const inputTokens = usageFields.inputTokens ?? usageFields.promptTokens; + const outputTokens = usageFields.outputTokens ?? usageFields.completionTokens; if (!inputTokens || !outputTokens) { console.error("No tokens found in usage"); diff --git a/lib/github/__tests__/createOrUpdateFileContent.test.ts b/lib/github/__tests__/createOrUpdateFileContent.test.ts index 8e2a19a15..a73bd49c8 100644 --- a/lib/github/__tests__/createOrUpdateFileContent.test.ts +++ b/lib/github/__tests__/createOrUpdateFileContent.test.ts @@ -1,12 +1,11 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; import { createOrUpdateFileContent } from "../createOrUpdateFileContent"; +import { parseGitHubRepoUrl } from "../parseGitHubRepoUrl"; vi.mock("../parseGitHubRepoUrl", () => ({ parseGitHubRepoUrl: vi.fn(), })); -import { parseGitHubRepoUrl } from "../parseGitHubRepoUrl"; - const mockFetch = vi.fn(); global.fetch = mockFetch; diff --git a/lib/github/__tests__/expandSubmoduleEntries.test.ts b/lib/github/__tests__/expandSubmoduleEntries.test.ts index aa7fdfb8a..6703f9a93 100644 --- a/lib/github/__tests__/expandSubmoduleEntries.test.ts +++ b/lib/github/__tests__/expandSubmoduleEntries.test.ts @@ -1,4 +1,4 @@ -import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; +import { describe, it, expect, vi, beforeEach } from "vitest"; import { expandSubmoduleEntries } from "../expandSubmoduleEntries"; import { getRepoGitModules } from "../getRepoGitModules"; import { getRepoFileTree } from "../getRepoFileTree"; diff --git a/lib/sandbox/__tests__/postSandboxesFilesHandler.test.ts b/lib/sandbox/__tests__/postSandboxesFilesHandler.test.ts index 9c5f2fd10..8d0772c27 100644 --- a/lib/sandbox/__tests__/postSandboxesFilesHandler.test.ts +++ b/lib/sandbox/__tests__/postSandboxesFilesHandler.test.ts @@ -205,7 +205,6 @@ describe("postSandboxesFilesHandler", () => { }); const result = await postSandboxesFilesHandler(mockRequest); - const body = await result.json(); expect(result.status).toBe(500); // Blobs are always cleaned up to allow retries diff --git a/lib/supabase/account_sandboxes/__tests__/selectAccountSandboxes.test.ts b/lib/supabase/account_sandboxes/__tests__/selectAccountSandboxes.test.ts index be02e5655..0edb640fd 100644 --- a/lib/supabase/account_sandboxes/__tests__/selectAccountSandboxes.test.ts +++ b/lib/supabase/account_sandboxes/__tests__/selectAccountSandboxes.test.ts @@ -105,12 +105,10 @@ describe("selectAccountSandboxes", () => { }); // First call is for account_sandboxes, second is for account_organization_ids - let callCount = 0; mockFrom.mockImplementation((table: string) => { if (table === "account_organization_ids") { return { select: mockOrgSelect }; } - callCount++; return { select: mockSelect }; }); diff --git a/lib/tasks/__tests__/enrichTasks.test.ts b/lib/tasks/__tests__/enrichTasks.test.ts index fde0daade..785204022 100644 --- a/lib/tasks/__tests__/enrichTasks.test.ts +++ b/lib/tasks/__tests__/enrichTasks.test.ts @@ -1,5 +1,8 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; import { enrichTasks } from "../enrichTasks"; +import { fetchTriggerRuns } from "@/lib/trigger/fetchTriggerRuns"; +import { retrieveTaskRun } from "@/lib/trigger/retrieveTaskRun"; +import selectAccountEmails from "@/lib/supabase/account_emails/selectAccountEmails"; vi.mock("@/lib/trigger/fetchTriggerRuns", () => ({ fetchTriggerRuns: vi.fn(), @@ -13,10 +16,6 @@ vi.mock("@/lib/supabase/account_emails/selectAccountEmails", () => ({ default: vi.fn(), })); -import { fetchTriggerRuns } from "@/lib/trigger/fetchTriggerRuns"; -import { retrieveTaskRun } from "@/lib/trigger/retrieveTaskRun"; -import selectAccountEmails from "@/lib/supabase/account_emails/selectAccountEmails"; - const mockTask = { id: "task-123", title: "Test Task", diff --git a/lib/trigger/fetchTriggerRuns.ts b/lib/trigger/fetchTriggerRuns.ts index d19d518f2..2fd11e6b5 100644 --- a/lib/trigger/fetchTriggerRuns.ts +++ b/lib/trigger/fetchTriggerRuns.ts @@ -1,11 +1,11 @@ export interface TriggerRun { + [key: string]: unknown; id: string; status: string; createdAt: string; startedAt: string | null; finishedAt: string | null; durationMs: number | null; - [key: string]: unknown; } export type TriggerRunFilter = { "filter[tag]": string } | { "filter[schedule]": string }; diff --git a/lib/workspaces/createWorkspaceInDb.ts b/lib/workspaces/createWorkspaceInDb.ts index d7684c5b2..712db62ae 100644 --- a/lib/workspaces/createWorkspaceInDb.ts +++ b/lib/workspaces/createWorkspaceInDb.ts @@ -49,7 +49,7 @@ export async function createWorkspaceInDb( account_id: workspace.id, isWorkspace: true, }; - } catch (error) { + } catch { return null; } } From 12a1bd7f5d496e9cb981aac51a5531cab6251eee Mon Sep 17 00:00:00 2001 From: Arpit Gupta Date: Wed, 15 Apr 2026 00:04:55 +0530 Subject: [PATCH 2/4] docs(api): meaningful JSDoc for 19 route handlers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Writes endpoint-specific JSDoc against the strict rules scoped to `app/api/**`. Each handler now documents the HTTP verb + path, the request body/query shape (referencing the `validate*Body` / `validate*Query` schema file where applicable), the success response shape, and the relevant non-200 status codes. No placeholder filler — every description conveys something the signature does not. Files: - accounts/[id], admins/coding/pr, admins/coding/slack, admins/content/slack, admins/privy - chats/[id]/messages/trailing - coding-agent/callback, coding-agent/github - connectors - content (PATCH), content/analyze, content/caption, content/image, content/templates/[id], content/transcribe, content/upscale, content/video - songs/analyze/presets - transcribe Co-Authored-By: Claude Opus 4.6 --- app/api/accounts/[id]/route.ts | 18 +++---- app/api/admins/coding/pr/route.ts | 11 +++- app/api/admins/coding/slack/route.ts | 11 +++- app/api/admins/content/slack/route.ts | 11 +++- app/api/admins/privy/route.ts | 10 ++++ app/api/chats/[id]/messages/trailing/route.ts | 19 +++++-- app/api/coding-agent/callback/route.ts | 12 +++-- app/api/coding-agent/github/route.ts | 12 +++-- app/api/connectors/route.ts | 53 ++++++++++--------- app/api/content/analyze/route.ts | 12 ++++- app/api/content/caption/route.ts | 12 ++++- app/api/content/image/route.ts | 10 +++- app/api/content/route.ts | 13 ++++- app/api/content/templates/[id]/route.ts | 12 ++++- app/api/content/transcribe/route.ts | 10 +++- app/api/content/upscale/route.ts | 11 +++- app/api/content/video/route.ts | 11 +++- app/api/songs/analyze/presets/route.ts | 16 +++--- app/api/transcribe/route.ts | 14 +++++ 19 files changed, 211 insertions(+), 67 deletions(-) diff --git a/app/api/accounts/[id]/route.ts b/app/api/accounts/[id]/route.ts index b272465ae..6e62d4d08 100644 --- a/app/api/accounts/[id]/route.ts +++ b/app/api/accounts/[id]/route.ts @@ -5,7 +5,7 @@ import { getAccountHandler } from "@/lib/accounts/getAccountHandler"; /** * OPTIONS handler for CORS preflight requests. * - * @returns A NextResponse with CORS headers. + * @returns A 200 NextResponse carrying the CORS headers. */ export async function OPTIONS() { return new NextResponse(null, { @@ -21,13 +21,13 @@ export async function OPTIONS() { * Requires authentication via `x-api-key` or `Authorization: Bearer`; the caller must be * allowed to access the requested account (same account, org delegation, or Recoup admin). * - * Path parameters: - * - id (required): The unique identifier of the account (UUID) - * - * @param request - The request object - * @param params - Route params containing the account ID - * @returns A NextResponse with account data + * @param request - The incoming request. Authentication is read from the + * `x-api-key` or `Authorization: Bearer` header by `getAccountHandler`. + * @param context - Route context from Next.js. + * @param context.params - Promise resolving to `{ id }`, the account UUID from the URL path. + * @returns A 200 NextResponse with the account payload, 401/403 if the caller lacks access, + * or 404 when the account does not exist. */ -export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { - return getAccountHandler(request, params); +export async function GET(request: NextRequest, context: { params: Promise<{ id: string }> }) { + return getAccountHandler(request, context.params); } diff --git a/app/api/admins/coding/pr/route.ts b/app/api/admins/coding/pr/route.ts index 33aef3f91..7a068b776 100644 --- a/app/api/admins/coding/pr/route.ts +++ b/app/api/admins/coding/pr/route.ts @@ -10,13 +10,20 @@ import { getPrStatusHandler } from "@/lib/admins/pr/getPrStatusHandler"; * Uses the GitHub REST API to check each PR's state. * Requires admin authentication. * - * @param request + * @param request - The incoming request; `pull_requests` is read from the query string + * and admin auth from the `x-api-key` / `Authorization: Bearer` header. + * @returns A 200 NextResponse with `{ results: Array<{ url, state }> }`, 400 when no + * `pull_requests` are supplied, or 401/403 for non-admin callers. */ export async function GET(request: NextRequest): Promise { return getPrStatusHandler(request); } -/** CORS preflight handler. */ +/** + * CORS preflight handler for /api/admins/coding/pr. + * + * @returns A 204 NextResponse carrying the CORS headers. + */ export async function OPTIONS(): Promise { return new NextResponse(null, { status: 204, headers: getCorsHeaders() }); } diff --git a/app/api/admins/coding/slack/route.ts b/app/api/admins/coding/slack/route.ts index ea880d306..a5f6a8b26 100644 --- a/app/api/admins/coding/slack/route.ts +++ b/app/api/admins/coding/slack/route.ts @@ -9,12 +9,21 @@ import { getSlackTagsHandler } from "@/lib/admins/slack/getSlackTagsHandler"; * Pulls directly from the Slack API as the source of truth. * Supports period filtering: all (default), daily, weekly, monthly. * Requires admin authentication. + * + * @param request - The incoming request; `period` is read from the query string + * and admin auth from the `x-api-key` / `Authorization: Bearer` header. + * @returns A 200 NextResponse with the tagging analytics payload, 400 on invalid + * `period`, or 401/403 for non-admin callers. */ export async function GET(request: NextRequest): Promise { return getSlackTagsHandler(request); } -/** CORS preflight handler. */ +/** + * CORS preflight handler for /api/admins/coding/slack. + * + * @returns A 204 NextResponse carrying the CORS headers. + */ export async function OPTIONS(): Promise { return new NextResponse(null, { status: 204, headers: getCorsHeaders() }); } diff --git a/app/api/admins/content/slack/route.ts b/app/api/admins/content/slack/route.ts index 35ba38f7e..d45ed2753 100644 --- a/app/api/admins/content/slack/route.ts +++ b/app/api/admins/content/slack/route.ts @@ -10,13 +10,20 @@ import { getContentSlackTagsHandler } from "@/lib/admins/content/getContentSlack * Supports period filtering: all (default), daily, weekly, monthly. * Requires admin authentication. * - * @param request + * @param request - The incoming request; `period` is read from the query string + * and admin auth from the `x-api-key` / `Authorization: Bearer` header. + * @returns A 200 NextResponse with the tagging analytics payload, 400 on invalid + * `period`, or 401/403 for non-admin callers. */ export async function GET(request: NextRequest): Promise { return getContentSlackTagsHandler(request); } -/** CORS preflight handler. */ +/** + * CORS preflight handler for /api/admins/content/slack. + * + * @returns A 204 NextResponse carrying the CORS headers. + */ export async function OPTIONS(): Promise { return new NextResponse(null, { status: 204, headers: getCorsHeaders() }); } diff --git a/app/api/admins/privy/route.ts b/app/api/admins/privy/route.ts index 073bac603..0ca15fed7 100644 --- a/app/api/admins/privy/route.ts +++ b/app/api/admins/privy/route.ts @@ -8,11 +8,21 @@ import { getPrivyLoginsHandler } from "@/lib/admins/privy/getPrivyLoginsHandler" * Returns Privy login statistics for the requested time period. * Supports daily (last 24h), weekly (last 7 days), and monthly (last 30 days) periods. * Requires admin authentication. + * + * @param request - The incoming request; `period` is read from the query string + * and admin auth from the `x-api-key` / `Authorization: Bearer` header. + * @returns A 200 NextResponse with the login statistics payload, 400 on invalid + * `period`, or 401/403 for non-admin callers. */ export async function GET(request: NextRequest): Promise { return getPrivyLoginsHandler(request); } +/** + * CORS preflight handler for /api/admins/privy. + * + * @returns A 204 NextResponse carrying the CORS headers. + */ export async function OPTIONS(): Promise { return new NextResponse(null, { status: 204, headers: getCorsHeaders() }); } diff --git a/app/api/chats/[id]/messages/trailing/route.ts b/app/api/chats/[id]/messages/trailing/route.ts index afe9b6737..0a7003f1e 100644 --- a/app/api/chats/[id]/messages/trailing/route.ts +++ b/app/api/chats/[id]/messages/trailing/route.ts @@ -5,6 +5,8 @@ import { deleteTrailingChatMessagesHandler } from "@/lib/chats/deleteTrailingCha /** * OPTIONS handler for CORS preflight requests. + * + * @returns A 200 NextResponse carrying the CORS headers. */ export async function OPTIONS() { return new NextResponse(null, { @@ -16,12 +18,23 @@ export async function OPTIONS() { /** * DELETE /api/chats/[id]/messages/trailing * - * Deletes all messages in chat `id` from `from_message_id` onward. + * Removes the selected message and every message that follows it in chat `id`, so the + * conversation can be resumed from an earlier turn. The caller must own the chat or + * have organization-level access; authentication uses `x-api-key` or + * `Authorization: Bearer`. + * + * @param request - The incoming request. `from_message_id` is read from the JSON body + * and identifies the oldest message to delete (that message and all newer ones go). + * @param context - Route context from Next.js. + * @param context.params - Promise resolving to `{ id }`, the chat UUID from the URL path. + * @returns A 200 NextResponse with the count of deleted messages, 400 on a missing + * `from_message_id`, 401/403 when the caller cannot access the chat, or 404 when the + * chat or message does not exist. */ export async function DELETE( request: NextRequest, - { params }: { params: Promise<{ id: string }> }, + context: { params: Promise<{ id: string }> }, ): Promise { - const { id } = await params; + const { id } = await context.params; return deleteTrailingChatMessagesHandler(request, id); } diff --git a/app/api/coding-agent/callback/route.ts b/app/api/coding-agent/callback/route.ts index 2582611f1..acba3e1cc 100644 --- a/app/api/coding-agent/callback/route.ts +++ b/app/api/coding-agent/callback/route.ts @@ -5,10 +5,16 @@ import { handleCodingAgentCallback } from "@/lib/coding-agent/handleCodingAgentC /** * POST /api/coding-agent/callback * - * Callback endpoint for the coding agent Trigger.dev task. - * Receives task results and posts them back to the Slack thread. + * Callback endpoint for the coding agent Trigger.dev task. Receives the task result + * payload and posts it back to the originating Slack thread. The request is + * authenticated by the shared secret checked inside `handleCodingAgentCallback` + * (not by `x-api-key`), since it is called server-to-server from Trigger.dev. * - * @param request - The incoming callback request + * @param request - The incoming callback request from the Trigger.dev task. The JSON + * body carries the task outcome plus `thread_ts` / `channel` so the bot knows where + * to reply in Slack. + * @returns A 200 NextResponse when the Slack post succeeds, 401 if the shared secret + * is missing or wrong, or 500 if Slack posting fails. */ export async function POST(request: NextRequest) { await codingAgentBot.initialize(); diff --git a/app/api/coding-agent/github/route.ts b/app/api/coding-agent/github/route.ts index f1d615a78..bdbe7b098 100644 --- a/app/api/coding-agent/github/route.ts +++ b/app/api/coding-agent/github/route.ts @@ -4,10 +4,16 @@ import { handleGitHubWebhook } from "@/lib/coding-agent/handleGitHubWebhook"; /** * POST /api/coding-agent/github * - * Webhook endpoint for GitHub PR comment feedback. - * Receives issue_comment events and triggers update-pr when the bot is mentioned. + * Webhook endpoint for GitHub PR comment feedback. Receives `issue_comment` events + * and triggers the `update-pr` coding agent task when the bot is @-mentioned in a + * comment. Authentication relies on the GitHub `X-Hub-Signature-256` signature that + * `handleGitHubWebhook` validates against the webhook secret. * - * @param request - The incoming GitHub webhook request + * @param request - The incoming GitHub webhook POST. Headers `X-GitHub-Event` and + * `X-Hub-Signature-256` are required; the JSON body is the raw GitHub event + * payload (typically `issue_comment`). + * @returns A 200 NextResponse with `{ ok: true }` when the event is accepted or + * ignored, 401 when the signature is missing or invalid, or 500 on internal error. */ export async function POST(request: NextRequest) { return handleGitHubWebhook(request); diff --git a/app/api/connectors/route.ts b/app/api/connectors/route.ts index 9a20d83a9..780f28a98 100644 --- a/app/api/connectors/route.ts +++ b/app/api/connectors/route.ts @@ -7,6 +7,8 @@ import { disconnectConnectorHandler } from "@/lib/composio/connectors/disconnect /** * OPTIONS handler for CORS preflight requests. + * + * @returns A 200 NextResponse carrying the CORS headers. */ export async function OPTIONS() { return new NextResponse(null, { @@ -18,15 +20,15 @@ export async function OPTIONS() { /** * GET /api/connectors * - * List all available connectors and their connection status. - * - * Query params: - * - account_id (optional): Entity ID for entity-specific connections (e.g., artist ID) + * Lists every available Composio connector with its connection status for the caller. + * When `account_id` is supplied, the statuses are scoped to that account (e.g. an + * artist) instead of the authenticated caller. Requires `x-api-key` or + * `Authorization: Bearer`. * - * Authentication: x-api-key OR Authorization Bearer token required. - * - * @param request - * @returns List of connectors with connection status + * @param request - The incoming request. Optional query parameter: `account_id` — an + * account UUID (e.g. artist) to scope the connection status lookup to. + * @returns A 200 NextResponse with `{ connectors: Array<{ slug, connected, ... }> }`, + * 401 when unauthenticated, or 403 when the caller cannot access `account_id`. */ export async function GET(request: NextRequest) { return getConnectorsHandler(request); @@ -35,17 +37,16 @@ export async function GET(request: NextRequest) { /** * POST /api/connectors * - * Generate an OAuth authorization URL for a specific connector. - * - * Authentication: x-api-key OR Authorization Bearer token required. + * Generates a Composio OAuth authorization URL for a single connector. The caller + * completes OAuth in the browser and is redirected to `callback_url` (or a default). + * Requires `x-api-key` or `Authorization: Bearer`. * - * Request body: - * - connector: The connector slug, e.g., "googlesheets" or "tiktok" (required) - * - callback_url: Optional custom callback URL after OAuth - * - account_id: Optional account ID for account-specific connections - * - * @param request - * @returns The redirect URL for OAuth authorization + * @param request - The incoming request. JSON body: `connector` (required slug, e.g. + * `"googlesheets"` or `"tiktok"`); `callback_url` (optional post-OAuth redirect); + * `account_id` (optional — the account to associate the connection with). + * @returns A 200 NextResponse with `{ redirectUrl }` pointing the caller at the + * provider's OAuth consent page, 400 on a missing/invalid `connector`, 401 when + * unauthenticated, or 403 when the caller cannot access `account_id`. */ export async function POST(request: NextRequest) { return authorizeConnectorHandler(request); @@ -54,15 +55,15 @@ export async function POST(request: NextRequest) { /** * DELETE /api/connectors * - * Disconnect a connected account from Composio. - * - * Body: - * - connected_account_id (required): The connected account ID to disconnect - * - account_id (optional): Entity ID for ownership verification (e.g., artist ID) - * - * Authentication: x-api-key OR Authorization Bearer token required. + * Disconnects a previously-connected Composio account so it can no longer be used for + * tool calls. Requires `x-api-key` or `Authorization: Bearer`. * - * @param request + * @param request - The incoming request. JSON body: `connected_account_id` (required + * — the Composio connected-account id to remove); `account_id` (optional — used to + * verify ownership of the connected account). + * @returns A 200 NextResponse on successful disconnect, 400 on a missing + * `connected_account_id`, 401 when unauthenticated, 403 when the caller does not + * own the connection, or 404 when the connected account does not exist. */ export async function DELETE(request: NextRequest) { return disconnectConnectorHandler(request); diff --git a/app/api/content/analyze/route.ts b/app/api/content/analyze/route.ts index 2679338b5..7e60552b9 100644 --- a/app/api/content/analyze/route.ts +++ b/app/api/content/analyze/route.ts @@ -4,6 +4,8 @@ import { createAnalyzeHandler } from "@/lib/content/analyze/createAnalyzeHandler /** * OPTIONS handler for CORS preflight requests. + * + * @returns A 204 NextResponse carrying the CORS headers. */ export async function OPTIONS() { return new NextResponse(null, { status: 204, headers: getCorsHeaders() }); @@ -12,7 +14,15 @@ export async function OPTIONS() { /** * POST /api/content/analyze * - * Analyze a video with AI — describe scenes, check quality, evaluate content. + * Runs a TwelveLabs analysis over a hosted video to describe scenes, check quality, + * or evaluate content against a caller-supplied prompt. Body is validated by + * `validateAnalyzeVideoBody` in `lib/content/analyze/`. + * + * @param request - The incoming request with JSON body `{ video_url, prompt, ... }` + * (see `validateAnalyzeVideoBody` for the full schema). + * @returns A 200 NextResponse with `{ text, finish_reason, usage }`, 400 on a bad + * body, 500 when TwelveLabs is not configured, or 502 when the upstream analysis + * request fails. */ export async function POST(request: NextRequest): Promise { return createAnalyzeHandler(request); diff --git a/app/api/content/caption/route.ts b/app/api/content/caption/route.ts index 59b1a9ae1..83353d4b6 100644 --- a/app/api/content/caption/route.ts +++ b/app/api/content/caption/route.ts @@ -4,6 +4,8 @@ import { createTextHandler } from "@/lib/content/caption/createTextHandler"; /** * OPTIONS handler for CORS preflight requests. + * + * @returns A 204 NextResponse carrying the CORS headers. */ export async function OPTIONS() { return new NextResponse(null, { status: 204, headers: getCorsHeaders() }); @@ -12,7 +14,15 @@ export async function OPTIONS() { /** * POST /api/content/caption * - * Generate on-screen caption text for a social video. + * Generates on-screen caption text for a social video using a lightweight LLM, with + * optional template-driven styling (tone, formats, example phrasing). Body is + * validated by `validateCreateCaptionBody` in `lib/content/caption/`. + * + * @param request - The incoming request with JSON body `{ topic, length, template? }` + * (see `validateCreateCaptionBody` for the full schema). + * @returns A 200 NextResponse with `{ content, font, color, borderColor, maxFontSize }` + * for the rendered caption, 400 on a bad body, 502 when the LLM returns empty text, + * or 500 on other generation errors. */ export async function POST(request: NextRequest): Promise { return createTextHandler(request); diff --git a/app/api/content/image/route.ts b/app/api/content/image/route.ts index 06c7bc9f3..86cbb5320 100644 --- a/app/api/content/image/route.ts +++ b/app/api/content/image/route.ts @@ -4,6 +4,8 @@ import { createImageHandler } from "@/lib/content/image/createImageHandler"; /** * OPTIONS handler for CORS preflight requests. + * + * @returns A 204 NextResponse carrying the CORS headers. */ export async function OPTIONS() { return new NextResponse(null, { status: 204, headers: getCorsHeaders() }); @@ -12,7 +14,13 @@ export async function OPTIONS() { /** * POST /api/content/image * - * Generate an image from a prompt and optional reference image. + * Generates an image via Fal from a text prompt with optional reference images. + * Body is validated by `validateCreateImageBody` in `lib/content/image/`. + * + * @param request - The incoming request with JSON body `{ prompt, reference_images?, + * model?, ... }` (see `validateCreateImageBody` for the full schema). + * @returns A 200 NextResponse with `{ imageUrl, images: string[] }`, 400 on a bad + * body, 502 when Fal returns no image, or 500 on other generation errors. */ export async function POST(request: NextRequest): Promise { return createImageHandler(request); diff --git a/app/api/content/route.ts b/app/api/content/route.ts index f5703b378..9c23d0007 100644 --- a/app/api/content/route.ts +++ b/app/api/content/route.ts @@ -4,6 +4,8 @@ import { editHandler } from "@/lib/content/edit/editHandler"; /** * OPTIONS handler for CORS preflight requests. + * + * @returns A 204 NextResponse carrying the CORS headers. */ export async function OPTIONS() { return new NextResponse(null, { status: 204, headers: getCorsHeaders() }); @@ -12,7 +14,16 @@ export async function OPTIONS() { /** * PATCH /api/content * - * Edit media with operations or a template preset. + * Edits a media asset by triggering the `ffmpeg-edit` Trigger.dev task with explicit + * operations or a template preset. Returns immediately with a run id; the actual + * edit happens asynchronously. Body is validated by `validateEditContentBody` in + * `lib/content/edit/`. + * + * @param request - The incoming request with JSON body `{ url, operations? | template? }` + * (see `validateEditContentBody` for the full schema). Exactly one of `operations` + * or `template` must be provided. + * @returns A 202 NextResponse with `{ runId, status: "triggered" }` when the task is + * queued, 400 on a bad body, or 500 when the task cannot be triggered. */ export async function PATCH(request: NextRequest): Promise { return editHandler(request); diff --git a/app/api/content/templates/[id]/route.ts b/app/api/content/templates/[id]/route.ts index e4c272512..44e2c2f97 100644 --- a/app/api/content/templates/[id]/route.ts +++ b/app/api/content/templates/[id]/route.ts @@ -4,6 +4,8 @@ import { getContentTemplateDetailHandler } from "@/lib/content/getContentTemplat /** * OPTIONS handler for CORS preflight requests. + * + * @returns A 204 NextResponse carrying the CORS headers. */ export async function OPTIONS() { return new NextResponse(null, { status: 204, headers: getCorsHeaders() }); @@ -12,7 +14,15 @@ export async function OPTIONS() { /** * GET /api/content/templates/[id] * - * Returns the full template configuration for a given template id. + * Returns the full template configuration for the given template id — the image, + * video, caption, and edit presets used elsewhere under `/api/content/*`. Requires + * `x-api-key` or `Authorization: Bearer`. + * + * @param request - The incoming request. Authentication only; no query or body. + * @param context - Route context from Next.js. + * @param context.params - Promise resolving to `{ id }`, the template id from the URL path. + * @returns A 200 NextResponse with the full `Template` object, 401 when + * unauthenticated, or 404 when the template id is unknown. */ export async function GET( request: NextRequest, diff --git a/app/api/content/transcribe/route.ts b/app/api/content/transcribe/route.ts index 75f7be638..1c4ad53b4 100644 --- a/app/api/content/transcribe/route.ts +++ b/app/api/content/transcribe/route.ts @@ -4,6 +4,8 @@ import { createAudioHandler } from "@/lib/content/transcribe/createAudioHandler" /** * OPTIONS handler for CORS preflight requests. + * + * @returns A 204 NextResponse carrying the CORS headers. */ export async function OPTIONS() { return new NextResponse(null, { status: 204, headers: getCorsHeaders() }); @@ -12,7 +14,13 @@ export async function OPTIONS() { /** * POST /api/content/transcribe * - * Transcribe audio into text with word-level timestamps. + * Transcribes one or more hosted audio files into text with word-level timestamps. + * Body is validated by `validateTranscribeAudioBody` in `lib/content/transcribe/`. + * + * @param request - The incoming request with JSON body `{ audio_urls, ... }` (see + * `validateTranscribeAudioBody` for the full schema). + * @returns A 200 NextResponse with `{ transcript, segments, language, ... }` per the + * transcription result, 400 on a bad body, or 500 when transcription fails. */ export async function POST(request: NextRequest): Promise { return createAudioHandler(request); diff --git a/app/api/content/upscale/route.ts b/app/api/content/upscale/route.ts index 63739f5d1..d64f3d8f0 100644 --- a/app/api/content/upscale/route.ts +++ b/app/api/content/upscale/route.ts @@ -4,6 +4,8 @@ import { createUpscaleHandler } from "@/lib/content/upscale/createUpscaleHandler /** * OPTIONS handler for CORS preflight requests. + * + * @returns A 204 NextResponse carrying the CORS headers. */ export async function OPTIONS() { return new NextResponse(null, { status: 204, headers: getCorsHeaders() }); @@ -12,7 +14,14 @@ export async function OPTIONS() { /** * POST /api/content/upscale * - * Upscale an image or video to higher resolution. + * Upscales an image or video to a higher resolution via Fal and returns a URL to + * the upscaled asset. Body is validated by `validateUpscaleBody` in + * `lib/content/upscale/`. + * + * @param request - The incoming request with JSON body `{ url, type, ... }` where + * `type` selects the upscale path (see `validateUpscaleBody` for the full schema). + * @returns A 200 NextResponse with `{ url }` pointing at the upscaled asset, 400 on + * a bad body, 502 when the upstream returns no result, or 500 on other errors. */ export async function POST(request: NextRequest): Promise { return createUpscaleHandler(request); diff --git a/app/api/content/video/route.ts b/app/api/content/video/route.ts index fde60b30c..a1a83ce92 100644 --- a/app/api/content/video/route.ts +++ b/app/api/content/video/route.ts @@ -4,6 +4,8 @@ import { createVideoHandler } from "@/lib/content/video/createVideoHandler"; /** * OPTIONS handler for CORS preflight requests. + * + * @returns A 204 NextResponse carrying the CORS headers. */ export async function OPTIONS() { return new NextResponse(null, { status: 204, headers: getCorsHeaders() }); @@ -12,7 +14,14 @@ export async function OPTIONS() { /** * POST /api/content/video * - * Generate a video from a prompt, image, or existing video. + * Generates a video from a text prompt, a seed image, or an existing video. Body + * is validated by `validateCreateVideoBody` in `lib/content/video/`. + * + * @param request - The incoming request with JSON body `{ prompt, image_url? | + * video_url?, model?, ... }` (see `validateCreateVideoBody` for the full schema). + * @returns A 200 NextResponse with the generated video URL and related metadata, + * 400 on a bad body, 502 when the upstream returns no video, or 500 on other + * generation errors. */ export async function POST(request: NextRequest): Promise { return createVideoHandler(request); diff --git a/app/api/songs/analyze/presets/route.ts b/app/api/songs/analyze/presets/route.ts index 8baccd38d..68a422ae4 100644 --- a/app/api/songs/analyze/presets/route.ts +++ b/app/api/songs/analyze/presets/route.ts @@ -18,17 +18,13 @@ export async function OPTIONS() { /** * GET /api/songs/analyze/presets * - * Lists all available music analysis presets. Each preset is a curated - * prompt with optimized generation parameters for a specific use case - * (e.g. catalog metadata, sync licensing, audience profiling). + * Lists all available music analysis presets. Each preset is a curated prompt with + * optimized generation parameters for a specific use case (e.g. catalog metadata, + * sync licensing, audience profiling). Requires `x-api-key` or `Authorization: Bearer`. * - * Authentication: x-api-key header or Authorization Bearer token required. - * - * Response (200): - * - status: "success" - * - presets: Array of { name, label, description, requiresAudio, responseFormat } - * - * @returns A NextResponse with the list of available presets + * @param request - The incoming request. Authentication only; no query or body params. + * @returns A 200 NextResponse with `{ status: "success", presets: Array<{ name, label, + * description, requiresAudio, responseFormat }> }`, or 401 when unauthenticated. */ export async function GET(request: NextRequest): Promise { return getFlamingoPresetsHandler(request); diff --git a/app/api/transcribe/route.ts b/app/api/transcribe/route.ts index 28cf4261d..6841f71dc 100644 --- a/app/api/transcribe/route.ts +++ b/app/api/transcribe/route.ts @@ -2,6 +2,20 @@ import { NextRequest, NextResponse } from "next/server"; import { processAudioTranscription } from "@/lib/transcribe/processAudioTranscription"; import { formatTranscriptionError } from "@/lib/transcribe/types"; +/** + * POST /api/transcribe + * + * Transcribes a hosted audio file into text and persists both the audio and the + * transcript as files against the owner account and artist account. Unlike + * `/api/content/transcribe`, this endpoint writes the results to storage and + * returns file references in addition to the raw text. + * + * @param req - The incoming request with JSON body `{ audio_url, account_id, + * artist_account_id, title?, include_timestamps? }`. All three ids are required. + * @returns A 200 NextResponse with `{ success, audioFile, transcriptFile, text, + * language }`, 400 when a required field is missing, or the status/message + * produced by `formatTranscriptionError` on downstream failures. + */ export async function POST(req: NextRequest) { try { const body = await req.json(); From 9c83deb3878f805156a030bd83d26c38287981a6 Mon Sep 17 00:00:00 2001 From: Arpit Gupta Date: Wed, 15 Apr 2026 00:20:55 +0530 Subject: [PATCH 3/4] fix(credits): use typed LanguageModelUsage fields; restore json() assertion in sandbox test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - `getCreditUsage`: drop the hand-rolled Partial-record cast and the v2-compat `promptTokens`/`completionTokens` fallback — `LanguageModelUsage` (= V3Usage) already types `inputTokens` / `outputTokens` directly, and the real runtime caller (`handleChatCredits`) passes this exact type. The compat branch was dead against the typed surface. - `getCreditUsage.test.ts`: update mock usage objects from the legacy `promptTokens`/`completionTokens` shape to the SDK V3 shape the function is actually typed against. - `postSandboxesFilesHandler.test.ts`: restore `result.json()` so the 500-path still asserts the response is valid JSON (not just status code), matching the original test intent. Co-Authored-By: Claude Opus 4.6 --- lib/credits/__tests__/getCreditUsage.test.ts | 32 +++++++++---------- lib/credits/getCreditUsage.ts | 8 +---- .../postSandboxesFilesHandler.test.ts | 2 ++ 3 files changed, 19 insertions(+), 23 deletions(-) diff --git a/lib/credits/__tests__/getCreditUsage.test.ts b/lib/credits/__tests__/getCreditUsage.test.ts index 8aa291909..d79edd7de 100644 --- a/lib/credits/__tests__/getCreditUsage.test.ts +++ b/lib/credits/__tests__/getCreditUsage.test.ts @@ -29,8 +29,8 @@ describe("getCreditUsage", () => { } as any); const usage = { - promptTokens: 1000, - completionTokens: 500, + inputTokens: 1000, + outputTokens: 500, }; const cost = await getCreditUsage(usage, "gpt-4"); @@ -43,8 +43,8 @@ describe("getCreditUsage", () => { mockGetModel.mockResolvedValue(undefined); const usage = { - promptTokens: 1000, - completionTokens: 500, + inputTokens: 1000, + outputTokens: 500, }; const cost = await getCreditUsage(usage, "unknown-model"); @@ -52,7 +52,7 @@ describe("getCreditUsage", () => { expect(cost).toBe(0); }); - it("returns 0 when promptTokens is undefined", async () => { + it("returns 0 when inputTokens is undefined", async () => { mockGetModel.mockResolvedValue({ id: "gpt-4", pricing: { @@ -62,8 +62,8 @@ describe("getCreditUsage", () => { } as any); const usage = { - promptTokens: undefined as unknown as number, - completionTokens: 500, + inputTokens: undefined as unknown as number, + outputTokens: 500, }; const cost = await getCreditUsage(usage, "gpt-4"); @@ -71,7 +71,7 @@ describe("getCreditUsage", () => { expect(cost).toBe(0); }); - it("returns 0 when completionTokens is undefined", async () => { + it("returns 0 when outputTokens is undefined", async () => { mockGetModel.mockResolvedValue({ id: "gpt-4", pricing: { @@ -81,8 +81,8 @@ describe("getCreditUsage", () => { } as any); const usage = { - promptTokens: 1000, - completionTokens: undefined as unknown as number, + inputTokens: 1000, + outputTokens: undefined as unknown as number, }; const cost = await getCreditUsage(usage, "gpt-4"); @@ -97,8 +97,8 @@ describe("getCreditUsage", () => { } as any); const usage = { - promptTokens: 1000, - completionTokens: 500, + inputTokens: 1000, + outputTokens: 500, }; const cost = await getCreditUsage(usage, "gpt-4"); @@ -117,8 +117,8 @@ describe("getCreditUsage", () => { } as any); const usage = { - promptTokens: 0, - completionTokens: 0, + inputTokens: 0, + outputTokens: 0, }; const cost = await getCreditUsage(usage, "gpt-4"); @@ -130,8 +130,8 @@ describe("getCreditUsage", () => { mockGetModel.mockRejectedValue(new Error("API error")); const usage = { - promptTokens: 1000, - completionTokens: 500, + inputTokens: 1000, + outputTokens: 500, }; const cost = await getCreditUsage(usage, "gpt-4"); diff --git a/lib/credits/getCreditUsage.ts b/lib/credits/getCreditUsage.ts index ec17e51b0..4e1dc5b28 100644 --- a/lib/credits/getCreditUsage.ts +++ b/lib/credits/getCreditUsage.ts @@ -18,13 +18,7 @@ export const getCreditUsage = async ( return 0; } - // LanguageModelUsage uses inputTokens/outputTokens (SDK v3) - // or promptTokens/completionTokens (SDK v2 compatibility) - const usageFields = usage as Partial< - Record<"inputTokens" | "outputTokens" | "promptTokens" | "completionTokens", number> - >; - const inputTokens = usageFields.inputTokens ?? usageFields.promptTokens; - const outputTokens = usageFields.outputTokens ?? usageFields.completionTokens; + const { inputTokens, outputTokens } = usage; if (!inputTokens || !outputTokens) { console.error("No tokens found in usage"); diff --git a/lib/sandbox/__tests__/postSandboxesFilesHandler.test.ts b/lib/sandbox/__tests__/postSandboxesFilesHandler.test.ts index 8d0772c27..4c73c16d1 100644 --- a/lib/sandbox/__tests__/postSandboxesFilesHandler.test.ts +++ b/lib/sandbox/__tests__/postSandboxesFilesHandler.test.ts @@ -205,8 +205,10 @@ describe("postSandboxesFilesHandler", () => { }); const result = await postSandboxesFilesHandler(mockRequest); + const body = await result.json(); expect(result.status).toBe(500); + expect(body).toBeDefined(); // Blobs are always cleaned up to allow retries expect(del).toHaveBeenCalledWith("https://blob.example.com/f.txt"); }); From c3e07d3bada3bb073ecc1d0074f4f1df1b64946a Mon Sep 17 00:00:00 2001 From: Arpit Gupta Date: Wed, 15 Apr 2026 00:23:04 +0530 Subject: [PATCH 4/4] docs(api): correct @returns shape for coding-agent github webhook Cubic review feedback: the JSDoc claimed `{ ok: true }` on 200 but the handler returns `{ status: }` payloads (`update_triggered`, `ignored`, `busy`, `no_state`, `updating`) and `{ status: "error", error }` on non-200s. Update the doc to match the actual contract. Co-Authored-By: Claude Opus 4.6 --- app/api/coding-agent/github/route.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/api/coding-agent/github/route.ts b/app/api/coding-agent/github/route.ts index bdbe7b098..0901f3a6a 100644 --- a/app/api/coding-agent/github/route.ts +++ b/app/api/coding-agent/github/route.ts @@ -12,8 +12,10 @@ import { handleGitHubWebhook } from "@/lib/coding-agent/handleGitHubWebhook"; * @param request - The incoming GitHub webhook POST. Headers `X-GitHub-Event` and * `X-Hub-Signature-256` are required; the JSON body is the raw GitHub event * payload (typically `issue_comment`). - * @returns A 200 NextResponse with `{ ok: true }` when the event is accepted or - * ignored, 401 when the signature is missing or invalid, or 500 on internal error. + * @returns A NextResponse JSON payload with a `status` field indicating outcome + * (`update_triggered`, `ignored`, `busy`, `no_state`, or `updating`) on 200; + * `{ status: "error", error }` with 401 when signature validation fails; and + * `{ status: "error", error }` with 500 on internal failure. */ export async function POST(request: NextRequest) { return handleGitHubWebhook(request);