chore: apply PR 1495 and update changelog

This commit is contained in:
diegosouzapw
2026-04-21 21:12:29 -03:00
parent 98c5b250b9
commit a7b6dfb06e
204 changed files with 2515 additions and 1652 deletions

View File

@@ -1,12 +1,16 @@
"use server";
import { NextResponse } from "next/server";
import { requireCliToolsAuth } from "@/lib/api/requireCliToolsAuth";
import { getMitmAlias, setMitmAliasAll } from "@/models";
import { cliMitmAliasUpdateSchema } from "@/shared/validation/schemas";
import { isValidationFailure, validateBody } from "@/shared/validation/helpers";
// GET - Get MITM aliases for a tool
export async function GET(request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
try {
const { searchParams } = new URL(request.url);
const toolName = searchParams.get("tool");
@@ -20,6 +24,9 @@ export async function GET(request) {
// PUT - Save MITM aliases for a specific tool
export async function PUT(request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
let rawBody;
try {
rawBody = await request.json();

View File

@@ -3,12 +3,16 @@
export const runtime = "nodejs";
import { NextResponse } from "next/server";
import { requireCliToolsAuth } from "@/lib/api/requireCliToolsAuth";
import { cliMitmStartSchema, cliMitmStopSchema } from "@/shared/validation/schemas";
import { isValidationFailure, validateBody } from "@/shared/validation/helpers";
import { resolveApiKey } from "@/shared/services/apiKeyResolver";
// GET - Check MITM status
export async function GET() {
export async function GET(request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
try {
const { getMitmStatus, getCachedPassword } = await import("@/mitm/manager");
const status = await getMitmStatus();
@@ -27,6 +31,9 @@ export async function GET() {
// POST - Start MITM proxy
export async function POST(request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
let rawBody;
try {
rawBody = await request.json();
@@ -81,6 +88,9 @@ export async function POST(request) {
// DELETE - Stop MITM proxy
export async function DELETE(request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
let rawBody;
try {
rawBody = await request.json();

View File

@@ -1,6 +1,7 @@
"use server";
import { NextResponse } from "next/server";
import { requireCliToolsAuth } from "@/lib/api/requireCliToolsAuth";
import { listBackups, restoreBackup, deleteBackup } from "@/shared/services/backupService";
import { ensureCliConfigWriteAllowed } from "@/shared/services/cliRuntime";
import { cliBackupMutationSchema } from "@/shared/validation/schemas";
@@ -10,6 +11,9 @@ const VALID_TOOLS = ["claude", "codex", "droid", "openclaw", "cline", "kilo", "q
// GET /api/cli-tools/backups?tool=claude — list backups
export async function GET(request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
try {
const { searchParams } = new URL(request.url);
const tool = searchParams.get("tool") || searchParams.get("toolId");
@@ -37,6 +41,9 @@ export async function GET(request) {
// POST /api/cli-tools/backups { tool, backupId } — restore a backup
export async function POST(request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
let rawBody;
try {
rawBody = await request.json();
@@ -86,6 +93,9 @@ export async function POST(request) {
// DELETE /api/cli-tools/backups { tool, backupId } — delete a backup
export async function DELETE(request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
let rawBody;
try {
rawBody = await request.json();

View File

@@ -3,6 +3,7 @@
import { NextResponse } from "next/server";
import fs from "fs/promises";
import path from "path";
import { requireCliToolsAuth } from "@/lib/api/requireCliToolsAuth";
import {
ensureCliConfigWriteAllowed,
getCliPrimaryConfigPath,
@@ -32,7 +33,10 @@ const readSettings = async () => {
};
// GET - Check claude CLI and read current settings
export async function GET() {
export async function GET(request: Request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
try {
const runtime = await getCliRuntimeStatus("claude");
@@ -74,6 +78,9 @@ export async function GET() {
// POST - Backup old fields and write new settings
export async function POST(request: Request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
let rawBody;
try {
rawBody = await request.json();
@@ -186,7 +193,10 @@ const RESET_ENV_KEYS = [
];
// DELETE - Reset settings (remove env fields)
export async function DELETE() {
export async function DELETE(request: Request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
try {
const writeGuard = ensureCliConfigWriteAllowed();
if (writeGuard) {

View File

@@ -4,6 +4,7 @@ import { NextResponse } from "next/server";
import fs from "fs/promises";
import path from "path";
import os from "os";
import { requireCliToolsAuth } from "@/lib/api/requireCliToolsAuth";
import { ensureCliConfigWriteAllowed, getCliRuntimeStatus } from "@/shared/services/cliRuntime";
import { createBackup } from "@/shared/services/backupService";
import { saveCliToolLastConfigured, deleteCliToolLastConfigured } from "@/lib/db/cliToolState";
@@ -52,7 +53,10 @@ const hasOmniRouteConfig = (globalState: any) => {
};
// GET - Check cline CLI and read current settings
export async function GET() {
export async function GET(request: Request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
try {
const runtime = await getCliRuntimeStatus("cline");
@@ -101,6 +105,9 @@ export async function GET() {
// POST - Configure Cline to use OmniRoute as OpenAI-compatible provider
export async function POST(request: Request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
let rawBody;
try {
rawBody = await request.json();
@@ -193,7 +200,10 @@ export async function POST(request: Request) {
}
// DELETE - Remove OmniRoute OpenAI-compatible provider config
export async function DELETE() {
export async function DELETE(request: Request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
try {
const writeGuard = ensureCliConfigWriteAllowed();
if (writeGuard) {

View File

@@ -3,6 +3,7 @@
import { NextResponse } from "next/server";
import fs from "fs/promises";
import path from "path";
import { requireCliToolsAuth } from "@/lib/api/requireCliToolsAuth";
import { ensureCliConfigWriteAllowed, getCliConfigPaths } from "@/shared/services/cliRuntime";
import { resolveDataDir } from "@/lib/dataPaths";
import { codexProfileIdSchema, codexProfileNameSchema } from "@/shared/validation/schemas";
@@ -52,7 +53,10 @@ function extractAuthLabel(authJson) {
}
// GET - List all saved profiles
export async function GET() {
export async function GET(request: Request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
try {
await ensureProfilesDir();
@@ -94,6 +98,9 @@ export async function GET() {
// POST - Save current config as a named profile
export async function POST(request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
let rawBody;
try {
rawBody = await request.json();
@@ -180,6 +187,9 @@ export async function POST(request) {
// PUT - Activate a saved profile (restore its config + auth)
export async function PUT(request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
let rawBody;
try {
rawBody = await request.json();
@@ -251,6 +261,9 @@ export async function PUT(request) {
// DELETE - Remove a saved profile
export async function DELETE(request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
let rawBody;
try {
rawBody = await request.json();

View File

@@ -3,6 +3,7 @@
import { NextResponse } from "next/server";
import fs from "fs/promises";
import path from "path";
import { requireCliToolsAuth } from "@/lib/api/requireCliToolsAuth";
import {
ensureCliConfigWriteAllowed,
getCliConfigPaths,
@@ -120,7 +121,10 @@ const hasOmniRouteConfig = (config: string | null) => {
};
// GET - Check codex CLI and read current settings
export async function GET() {
export async function GET(request: Request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
try {
const runtime = await getCliRuntimeStatus("codex");
@@ -161,6 +165,9 @@ export async function GET() {
// POST - Update OmniRoute settings (merge with existing config)
export async function POST(request: Request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
let rawBody;
try {
rawBody = await request.json();
@@ -301,7 +308,10 @@ export async function POST(request: Request) {
}
// DELETE - Remove OmniRoute settings only (keep other settings)
export async function DELETE() {
export async function DELETE(request: Request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
try {
const writeGuard = ensureCliConfigWriteAllowed();
if (writeGuard) {

View File

@@ -3,6 +3,7 @@
import { NextResponse } from "next/server";
import fs from "fs/promises";
import path from "path";
import { requireCliToolsAuth } from "@/lib/api/requireCliToolsAuth";
import {
ensureCliConfigWriteAllowed,
getCliPrimaryConfigPath,
@@ -36,7 +37,10 @@ const hasOmniRouteConfig = (settings: any) => {
};
// GET - Check droid CLI and read current settings
export async function GET() {
export async function GET(request: Request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
try {
const runtime = await getCliRuntimeStatus("droid");
@@ -77,6 +81,9 @@ export async function GET() {
// POST - Update OmniRoute customModels (merge with existing settings)
export async function POST(request: Request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
let rawBody;
try {
rawBody = await request.json();
@@ -174,7 +181,10 @@ export async function POST(request: Request) {
}
// DELETE - Remove OmniRoute customModels only (keep other settings)
export async function DELETE() {
export async function DELETE(request: Request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
try {
const writeGuard = ensureCliConfigWriteAllowed();
if (writeGuard) {

View File

@@ -2,6 +2,7 @@ import { NextResponse } from "next/server";
import fs from "fs/promises";
import path from "path";
import os from "os";
import { requireCliToolsAuth } from "@/lib/api/requireCliToolsAuth";
import { getRuntimePorts } from "@/lib/runtime/ports";
import { getOpenCodeConfigPath } from "@/shared/services/cliRuntime";
import { mergeOpenCodeConfig } from "@/shared/services/opencodeConfig";
@@ -16,6 +17,9 @@ import { resolveApiKey } from "@/shared/services/apiKeyResolver";
* Currently supports: continue, opencode
*/
export async function POST(request, { params }) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
let rawBody;
try {
rawBody = await request.json();

View File

@@ -4,6 +4,7 @@ import { NextResponse } from "next/server";
import fs from "fs/promises";
import path from "path";
import os from "os";
import { requireCliToolsAuth } from "@/lib/api/requireCliToolsAuth";
import { ensureCliConfigWriteAllowed, getCliRuntimeStatus } from "@/shared/services/cliRuntime";
import { createBackup } from "@/shared/services/backupService";
import { saveCliToolLastConfigured, deleteCliToolLastConfigured } from "@/lib/db/cliToolState";
@@ -38,7 +39,10 @@ const hasOmniRouteConfig = (auth) => {
};
// GET - Check kilo CLI and read current settings
export async function GET() {
export async function GET(request: Request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
try {
const runtime = await getCliRuntimeStatus("kilo");
@@ -109,6 +113,9 @@ export async function GET() {
// POST - Configure Kilo Code to use OmniRoute as OpenAI-compatible provider
export async function POST(request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
let rawBody;
try {
rawBody = await request.json();
@@ -217,7 +224,10 @@ export async function POST(request) {
}
// DELETE - Remove OmniRoute config from Kilo
export async function DELETE() {
export async function DELETE(request: Request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
try {
const writeGuard = ensureCliConfigWriteAllowed();
if (writeGuard) {

View File

@@ -3,6 +3,7 @@
import { NextResponse } from "next/server";
import fs from "fs/promises";
import path from "path";
import { requireCliToolsAuth } from "@/lib/api/requireCliToolsAuth";
import {
ensureCliConfigWriteAllowed,
getCliPrimaryConfigPath,
@@ -36,7 +37,10 @@ const hasOmniRouteConfig = (settings: any) => {
};
// GET - Check openclaw CLI and read current settings
export async function GET() {
export async function GET(request: Request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
try {
const runtime = await getCliRuntimeStatus("openclaw");
@@ -77,6 +81,9 @@ export async function GET() {
// POST - Update OmniRoute settings (merge with existing settings)
export async function POST(request: Request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
let rawBody;
try {
rawBody = await request.json();
@@ -174,7 +181,10 @@ export async function POST(request: Request) {
}
// DELETE - Remove OmniRoute settings only (keep other settings)
export async function DELETE() {
export async function DELETE(request: Request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
try {
const writeGuard = ensureCliConfigWriteAllowed();
if (writeGuard) {

View File

@@ -5,12 +5,16 @@
*/
import { NextResponse } from "next/server";
import { requireCliToolsAuth } from "@/lib/api/requireCliToolsAuth";
import { getComboModelProvider } from "@/lib/combos/steps";
import { resolveOmniRouteBaseUrl } from "@/shared/utils/resolveOmniRouteBaseUrl";
const OMNIROUTE_BASE_URL = resolveOmniRouteBaseUrl();
export async function GET() {
export async function GET(request: Request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
try {
// Fetch current health and combos to determine best provider ordering
const [healthRes, combosRes] = await Promise.allSettled([

View File

@@ -4,6 +4,7 @@ import { NextResponse } from "next/server";
import fs from "fs/promises";
import path from "path";
import os from "os";
import { requireCliToolsAuth } from "@/lib/api/requireCliToolsAuth";
import {
ensureCliConfigWriteAllowed,
getCliPrimaryConfigPath,
@@ -64,7 +65,10 @@ const hasOmniRouteConfig = (settings: any) => {
};
// GET - Check Qwen CLI and read current settings
export async function GET() {
export async function GET(request: Request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
try {
const runtime = await getCliRuntimeStatus("qwen");
@@ -106,6 +110,9 @@ export async function GET() {
// POST - Write OmniRoute config to settings.json + .env
export async function POST(request: Request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
let rawBody;
try {
rawBody = await request.json();
@@ -272,7 +279,10 @@ export async function POST(request: Request) {
}
// DELETE - Remove OmniRoute config from settings.json and .env
export async function DELETE() {
export async function DELETE(request: Request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
try {
const writeGuard = ensureCliConfigWriteAllowed();
if (writeGuard) {

View File

@@ -1,13 +1,17 @@
"use server";
import { NextResponse } from "next/server";
import { requireCliToolsAuth } from "@/lib/api/requireCliToolsAuth";
import {
CLI_TOOL_IDS,
getCliPrimaryConfigPath,
getCliRuntimeStatus,
} from "@/shared/services/cliRuntime";
export async function GET(_request, { params }) {
export async function GET(request, { params }) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
try {
const { toolId } = await params;
const normalizedToolId = String(toolId || "")

View File

@@ -2,6 +2,7 @@
import { NextResponse } from "next/server";
import fs from "fs/promises";
import { requireCliToolsAuth } from "@/lib/api/requireCliToolsAuth";
import {
getCliRuntimeStatus,
CLI_TOOL_IDS,
@@ -99,7 +100,10 @@ async function checkToolConfigStatus(toolId: string): Promise<string> {
* Returns runtime + config status for all CLI tools in one batch call.
* Used by the CLI Tools page to show status badges in collapsed state.
*/
export async function GET() {
export async function GET(request: Request) {
const authError = await requireCliToolsAuth(request);
if (authError) return authError;
try {
const statuses = {};

View File

@@ -0,0 +1,50 @@
import { NextResponse } from "next/server";
import { getApiKeyMetadata } from "@/lib/localDb";
import { CORS_HEADERS } from "@/shared/utils/cors";
import { extractApiKey, isValidApiKey } from "@/sse/services/auth";
export interface ApiKeyRequestScope {
apiKey: string | null;
apiKeyId: string | null;
apiKeyMetadata: Awaited<ReturnType<typeof getApiKeyMetadata>>;
rejection: Response | null;
}
export async function getApiKeyRequestScope(request: Request): Promise<ApiKeyRequestScope> {
const apiKey = extractApiKey(request);
if (process.env.REQUIRE_API_KEY === "true") {
if (!apiKey) {
return {
apiKey: null,
apiKeyId: null,
apiKeyMetadata: null,
rejection: NextResponse.json(
{ error: { message: "Missing API key", type: "invalid_request_error" } },
{ status: 401, headers: CORS_HEADERS }
),
};
}
if (!(await isValidApiKey(apiKey))) {
return {
apiKey: null,
apiKeyId: null,
apiKeyMetadata: null,
rejection: NextResponse.json(
{ error: { message: "Invalid API key", type: "invalid_request_error" } },
{ status: 401, headers: CORS_HEADERS }
),
};
}
}
const apiKeyMetadata = await getApiKeyMetadata(apiKey);
return {
apiKey,
apiKeyId: apiKeyMetadata?.id || null,
apiKeyMetadata,
rejection: null,
};
}

View File

@@ -1,71 +1,73 @@
import { CORS_HEADERS } from "@/shared/utils/cors";
import { extractApiKey } from "@/sse/services/auth";
import { getBatch, updateBatch, getApiKeyMetadata } from "@/lib/localDb";
import { getBatch, updateBatch } from "@/lib/localDb";
import { NextResponse } from "next/server";
import { getApiKeyRequestScope } from "@/app/api/v1/_helpers/apiKeyScope";
function formatBatchResponse(batch: any) {
return {
id: batch.id,
object: "batch",
endpoint: batch.endpoint,
errors: batch.errors || null,
input_file_id: batch.inputFileId,
completion_window: batch.completionWindow,
status: batch.status,
output_file_id: batch.outputFileId || null,
error_file_id: batch.errorFileId || null,
created_at: batch.createdAt,
in_progress_at: batch.inProgressAt || null,
expires_at: batch.expiresAt || null,
finalizing_at: batch.finalizingAt || null,
completed_at: batch.completedAt || null,
failed_at: batch.failedAt || null,
expired_at: batch.expiredAt || null,
cancelling_at: batch.cancellingAt || null,
cancelled_at: batch.cancelledAt || null,
request_counts: {
total: batch.requestCountsTotal || 0,
completed: batch.requestCountsCompleted || 0,
failed: batch.requestCountsFailed || 0,
},
metadata: batch.metadata || null,
model: batch.model || null,
usage: batch.usage || null,
};
return {
id: batch.id,
object: "batch",
endpoint: batch.endpoint,
errors: batch.errors || null,
input_file_id: batch.inputFileId,
completion_window: batch.completionWindow,
status: batch.status,
output_file_id: batch.outputFileId || null,
error_file_id: batch.errorFileId || null,
created_at: batch.createdAt,
in_progress_at: batch.inProgressAt || null,
expires_at: batch.expiresAt || null,
finalizing_at: batch.finalizingAt || null,
completed_at: batch.completedAt || null,
failed_at: batch.failedAt || null,
expired_at: batch.expiredAt || null,
cancelling_at: batch.cancellingAt || null,
cancelled_at: batch.cancelledAt || null,
request_counts: {
total: batch.requestCountsTotal || 0,
completed: batch.requestCountsCompleted || 0,
failed: batch.requestCountsFailed || 0,
},
metadata: batch.metadata || null,
model: batch.model || null,
usage: batch.usage || null,
};
}
export async function POST(request: Request, { params }: { params: Promise<{ id: string }> }) {
const apiKey = extractApiKey(request);
const apiKeyMetadata = await getApiKeyMetadata(apiKey);
const apiKeyId = apiKeyMetadata?.id || null;
const scope = await getApiKeyRequestScope(request);
if (scope.rejection) return scope.rejection;
const apiKeyId = scope.apiKeyId;
const { id } = await params;
const batch = getBatch(id);
const { id } = await params;
const batch = getBatch(id);
if (!batch || (batch.apiKeyId !== null && batch.apiKeyId !== apiKeyId)) {
return NextResponse.json(
{ error: { message: "Batch not found", type: "invalid_request_error" } },
{ status: 404, headers: CORS_HEADERS }
);
}
if (!batch || (batch.apiKeyId !== null && batch.apiKeyId !== apiKeyId)) {
return NextResponse.json(
{ error: { message: "Batch not found", type: "invalid_request_error" } },
{ status: 404, headers: CORS_HEADERS }
);
}
if (["completed", "failed", "cancelled", "expired"].includes(batch.status)) {
return NextResponse.json(
{ error: { message: `Batch ${id} is already ${batch.status}`, type: "invalid_request_error" } },
{ status: 400, headers: CORS_HEADERS }
);
}
if (["completed", "failed", "cancelled", "expired"].includes(batch.status)) {
return NextResponse.json(
{
error: { message: `Batch ${id} is already ${batch.status}`, type: "invalid_request_error" },
},
{ status: 400, headers: CORS_HEADERS }
);
}
if (batch.status === "cancelling") {
return NextResponse.json(formatBatchResponse(batch), { headers: CORS_HEADERS });
}
if (batch.status === "cancelling") {
return NextResponse.json(formatBatchResponse(batch), { headers: CORS_HEADERS });
}
updateBatch(id, {
status: "cancelling",
cancellingAt: Math.floor(Date.now() / 1000)
});
updateBatch(id, {
status: "cancelling",
cancellingAt: Math.floor(Date.now() / 1000),
});
const updatedBatch = getBatch(id);
const updatedBatch = getBatch(id);
return NextResponse.json(formatBatchResponse(updatedBatch), { headers: CORS_HEADERS });
return NextResponse.json(formatBatchResponse(updatedBatch), { headers: CORS_HEADERS });
}

View File

@@ -1,53 +1,53 @@
import { CORS_HEADERS } from "@/shared/utils/cors";
import { extractApiKey } from "@/sse/services/auth";
import { getBatch, getApiKeyMetadata } from "@/lib/localDb";
import { getBatch } from "@/lib/localDb";
import { NextResponse } from "next/server";
import { getApiKeyRequestScope } from "@/app/api/v1/_helpers/apiKeyScope";
function formatBatchResponse(batch: any) {
return {
id: batch.id,
object: "batch",
endpoint: batch.endpoint,
errors: batch.errors || null,
input_file_id: batch.inputFileId,
completion_window: batch.completionWindow,
status: batch.status,
output_file_id: batch.outputFileId || null,
error_file_id: batch.errorFileId || null,
created_at: batch.createdAt,
in_progress_at: batch.inProgressAt || null,
expires_at: batch.expiresAt || null,
finalizing_at: batch.finalizingAt || null,
completed_at: batch.completedAt || null,
failed_at: batch.failedAt || null,
expired_at: batch.expiredAt || null,
cancelling_at: batch.cancellingAt || null,
cancelled_at: batch.cancelledAt || null,
request_counts: {
total: batch.requestCountsTotal || 0,
completed: batch.requestCountsCompleted || 0,
failed: batch.requestCountsFailed || 0,
},
metadata: batch.metadata || null,
model: batch.model || null,
usage: batch.usage || null,
};
return {
id: batch.id,
object: "batch",
endpoint: batch.endpoint,
errors: batch.errors || null,
input_file_id: batch.inputFileId,
completion_window: batch.completionWindow,
status: batch.status,
output_file_id: batch.outputFileId || null,
error_file_id: batch.errorFileId || null,
created_at: batch.createdAt,
in_progress_at: batch.inProgressAt || null,
expires_at: batch.expiresAt || null,
finalizing_at: batch.finalizingAt || null,
completed_at: batch.completedAt || null,
failed_at: batch.failedAt || null,
expired_at: batch.expiredAt || null,
cancelling_at: batch.cancellingAt || null,
cancelled_at: batch.cancelledAt || null,
request_counts: {
total: batch.requestCountsTotal || 0,
completed: batch.requestCountsCompleted || 0,
failed: batch.requestCountsFailed || 0,
},
metadata: batch.metadata || null,
model: batch.model || null,
usage: batch.usage || null,
};
}
export async function GET(request: Request, { params }: { params: Promise<{ id: string }> }) {
const apiKey = extractApiKey(request);
const apiKeyMetadata = await getApiKeyMetadata(apiKey);
const apiKeyId = apiKeyMetadata?.id || null;
const scope = await getApiKeyRequestScope(request);
if (scope.rejection) return scope.rejection;
const apiKeyId = scope.apiKeyId;
const { id } = await params;
const batch = getBatch(id);
const { id } = await params;
const batch = getBatch(id);
if (!batch || (batch.apiKeyId !== null && batch.apiKeyId !== apiKeyId)) {
return NextResponse.json(
{ error: { message: "Batch not found", type: "invalid_request_error" } },
{ status: 404, headers: CORS_HEADERS }
);
}
if (!batch || (batch.apiKeyId !== null && batch.apiKeyId !== apiKeyId)) {
return NextResponse.json(
{ error: { message: "Batch not found", type: "invalid_request_error" } },
{ status: 404, headers: CORS_HEADERS }
);
}
return NextResponse.json(formatBatchResponse(batch), { headers: CORS_HEADERS });
return NextResponse.json(formatBatchResponse(batch), { headers: CORS_HEADERS });
}

View File

@@ -1,44 +1,44 @@
import { CORS_HEADERS } from "@/shared/utils/cors";
import { extractApiKey } from "@/sse/services/auth";
import { createBatch, getApiKeyMetadata, getFile, listBatches } from "@/lib/localDb";
import { createBatch, getFile, listBatches } from "@/lib/localDb";
import { v1BatchCreateSchema } from "@/shared/validation/schemas";
import { NextResponse } from "next/server";
import { getApiKeyRequestScope } from "@/app/api/v1/_helpers/apiKeyScope";
function formatBatchResponse(batch: any) {
return {
id: batch.id,
object: "batch",
endpoint: batch.endpoint,
errors: batch.errors || null,
input_file_id: batch.inputFileId,
completion_window: batch.completionWindow,
status: batch.status,
output_file_id: batch.outputFileId || null,
error_file_id: batch.errorFileId || null,
created_at: batch.createdAt,
in_progress_at: batch.inProgressAt || null,
expires_at: batch.expiresAt || null,
finalizing_at: batch.finalizingAt || null,
completed_at: batch.completedAt || null,
failed_at: batch.failedAt || null,
expired_at: batch.expiredAt || null,
cancelling_at: batch.cancellingAt || null,
cancelled_at: batch.cancelledAt || null,
request_counts: {
total: batch.requestCountsTotal || 0,
completed: batch.requestCountsCompleted || 0,
failed: batch.requestCountsFailed || 0,
},
metadata: batch.metadata || null,
model: batch.model || null,
usage: batch.usage || null,
};
return {
id: batch.id,
object: "batch",
endpoint: batch.endpoint,
errors: batch.errors || null,
input_file_id: batch.inputFileId,
completion_window: batch.completionWindow,
status: batch.status,
output_file_id: batch.outputFileId || null,
error_file_id: batch.errorFileId || null,
created_at: batch.createdAt,
in_progress_at: batch.inProgressAt || null,
expires_at: batch.expiresAt || null,
finalizing_at: batch.finalizingAt || null,
completed_at: batch.completedAt || null,
failed_at: batch.failedAt || null,
expired_at: batch.expiredAt || null,
cancelling_at: batch.cancellingAt || null,
cancelled_at: batch.cancelledAt || null,
request_counts: {
total: batch.requestCountsTotal || 0,
completed: batch.requestCountsCompleted || 0,
failed: batch.requestCountsFailed || 0,
},
metadata: batch.metadata || null,
model: batch.model || null,
usage: batch.usage || null,
};
}
export async function POST(request: Request) {
const apiKey = extractApiKey(request);
const apiKeyMetadata = await getApiKeyMetadata(apiKey);
const apiKeyId = apiKeyMetadata?.id || null;
const scope = await getApiKeyRequestScope(request);
if (scope.rejection) return scope.rejection;
const apiKeyId = scope.apiKeyId;
try {
const body = await request.json();
@@ -46,55 +46,60 @@ export async function POST(request: Request) {
const inputFile = getFile(validated.input_file_id);
if (!inputFile || (inputFile.apiKeyId !== null && inputFile.apiKeyId !== apiKeyId)) {
return NextResponse.json(
{ error: { message: "Input file not found", type: "invalid_request_error" } },
{ status: 400, headers: CORS_HEADERS }
);
return NextResponse.json(
{ error: { message: "Input file not found", type: "invalid_request_error" } },
{ status: 400, headers: CORS_HEADERS }
);
}
const batch = createBatch({
endpoint: validated.endpoint as any,
completionWindow: validated.completion_window,
inputFileId: validated.input_file_id,
metadata: validated.metadata,
apiKeyId,
outputExpiresAfterSeconds: validated.output_expires_after?.seconds || null,
outputExpiresAfterAnchor: validated.output_expires_after?.anchor || null,
endpoint: validated.endpoint as any,
completionWindow: validated.completion_window,
inputFileId: validated.input_file_id,
metadata: validated.metadata,
apiKeyId,
outputExpiresAfterSeconds: validated.output_expires_after?.seconds || null,
outputExpiresAfterAnchor: validated.output_expires_after?.anchor || null,
});
return NextResponse.json(formatBatchResponse(batch), { headers: CORS_HEADERS });
} catch (error) {
console.error("[BATCHES] Create failed:", error);
return NextResponse.json(
{ error: { message: error instanceof Error ? error.message : "Create failed", type: "invalid_request_error" } },
{
error: {
message: error instanceof Error ? error.message : "Create failed",
type: "invalid_request_error",
},
},
{ status: 400, headers: CORS_HEADERS }
);
}
}
export async function GET(request: Request) {
const apiKey = extractApiKey(request);
const apiKeyMetadata = await getApiKeyMetadata(apiKey);
const apiKeyId = apiKeyMetadata?.id || null;
const scope = await getApiKeyRequestScope(request);
if (scope.rejection) return scope.rejection;
const apiKeyId = scope.apiKeyId;
const url = new URL(request.url);
const limit = Number.parseInt(url.searchParams.get("limit") || "20");
const after = url.searchParams.get("after") || undefined;
const url = new URL(request.url);
const limit = Number.parseInt(url.searchParams.get("limit") || "20");
const after = url.searchParams.get("after") || undefined;
const batches = listBatches(apiKeyId || undefined, limit + 1, after);
const hasMore = batches.length > limit;
const data = hasMore ? batches.slice(0, limit) : batches;
const formattedData = data.map(b => formatBatchResponse(b));
return NextResponse.json(
{
object: "list",
data: formattedData,
first_id: formattedData.length > 0 ? formattedData[0].id : null,
last_id: formattedData.length > 0 ? formattedData.at(-1).id : null,
has_more: hasMore,
},
{ headers: CORS_HEADERS }
);
const batches = listBatches(apiKeyId || undefined, limit + 1, after);
const hasMore = batches.length > limit;
const data = hasMore ? batches.slice(0, limit) : batches;
const formattedData = data.map((b) => formatBatchResponse(b));
return NextResponse.json(
{
object: "list",
data: formattedData,
first_id: formattedData.length > 0 ? formattedData[0].id : null,
last_id: formattedData.length > 0 ? formattedData.at(-1).id : null,
has_more: hasMore,
},
{ headers: CORS_HEADERS }
);
}

View File

@@ -1,12 +1,12 @@
import { CORS_HEADERS } from "@/shared/utils/cors";
import { extractApiKey } from "@/sse/services/auth";
import { getFile, getFileContent, getApiKeyMetadata } from "@/lib/localDb";
import { getFile, getFileContent } from "@/lib/localDb";
import { NextResponse } from "next/server";
import { getApiKeyRequestScope } from "@/app/api/v1/_helpers/apiKeyScope";
export async function GET(request: Request, { params }: { params: Promise<{ id: string }> }) {
const apiKey = extractApiKey(request);
const apiKeyMetadata = await getApiKeyMetadata(apiKey);
const apiKeyId = apiKeyMetadata?.id || null;
const scope = await getApiKeyRequestScope(request);
if (scope.rejection) return scope.rejection;
const apiKeyId = scope.apiKeyId;
const { id } = await params;
const file = getFile(id);
@@ -20,7 +20,7 @@ export async function GET(request: Request, { params }: { params: Promise<{ id:
const content = getFileContent(id);
if (!content) {
return NextResponse.json(
return NextResponse.json(
{ error: { message: "File content not found", type: "invalid_request_error" } },
{ status: 404, headers: CORS_HEADERS }
);

View File

@@ -1,12 +1,12 @@
import { CORS_HEADERS } from "@/shared/utils/cors";
import { extractApiKey } from "@/sse/services/auth";
import { getFile, deleteFile, getApiKeyMetadata, formatFileResponse } from "@/lib/localDb";
import { getFile, deleteFile, formatFileResponse } from "@/lib/localDb";
import { NextResponse } from "next/server";
import { getApiKeyRequestScope } from "@/app/api/v1/_helpers/apiKeyScope";
export async function GET(request: Request, { params }: { params: Promise<{ id: string }> }) {
const apiKey = extractApiKey(request);
const apiKeyMetadata = await getApiKeyMetadata(apiKey);
const apiKeyId = apiKeyMetadata?.id || null;
const scope = await getApiKeyRequestScope(request);
if (scope.rejection) return scope.rejection;
const apiKeyId = scope.apiKeyId;
const { id } = await params;
const file = getFile(id);
@@ -17,14 +17,14 @@ export async function GET(request: Request, { params }: { params: Promise<{ id:
{ status: 404, headers: CORS_HEADERS }
);
}
return NextResponse.json(formatFileResponse(file), { headers: CORS_HEADERS });
}
export async function DELETE(request: Request, { params }: { params: Promise<{ id: string }> }) {
const apiKey = extractApiKey(request);
const apiKeyMetadata = await getApiKeyMetadata(apiKey);
const apiKeyId = apiKeyMetadata?.id || null;
const scope = await getApiKeyRequestScope(request);
if (scope.rejection) return scope.rejection;
const apiKeyId = scope.apiKeyId;
const { id } = await params;
const file = getFile(id);
@@ -38,9 +38,12 @@ export async function DELETE(request: Request, { params }: { params: Promise<{ i
deleteFile(id);
return NextResponse.json({
id,
object: "file",
deleted: true
}, { headers: CORS_HEADERS });
return NextResponse.json(
{
id,
object: "file",
deleted: true,
},
{ headers: CORS_HEADERS }
);
}

View File

@@ -1,12 +1,12 @@
import { CORS_HEADERS } from "@/shared/utils/cors";
import { extractApiKey } from "@/sse/services/auth";
import { createFile, listFiles, getApiKeyMetadata, formatFileResponse } from "@/lib/localDb";
import { createFile, listFiles, formatFileResponse } from "@/lib/localDb";
import { NextResponse } from "next/server";
import { getApiKeyRequestScope } from "@/app/api/v1/_helpers/apiKeyScope";
export async function POST(request: Request) {
const apiKey = extractApiKey(request);
const apiKeyMetadata = await getApiKeyMetadata(apiKey);
const apiKeyId = apiKeyMetadata?.id || null;
const scope = await getApiKeyRequestScope(request);
if (scope.rejection) return scope.rejection;
const apiKeyId = scope.apiKeyId;
try {
const formData = await request.formData();
@@ -25,7 +25,12 @@ export async function POST(request: Request) {
const MAX_FILE_BYTES = 512 * 1024 * 1024; // 512 MB
if (file.size > MAX_FILE_BYTES) {
return NextResponse.json(
{ error: { message: "File exceeds maximum allowed size of 512 MB", type: "invalid_request_error" } },
{
error: {
message: "File exceeds maximum allowed size of 512 MB",
type: "invalid_request_error",
},
},
{ status: 400, headers: CORS_HEADERS }
);
}
@@ -61,7 +66,7 @@ export async function POST(request: Request) {
apiKeyId,
expiresAt,
});
return NextResponse.json(formatFileResponse(record), { headers: CORS_HEADERS });
} catch (error) {
console.error("[FILES] Upload failed:", error);
@@ -73,9 +78,9 @@ export async function POST(request: Request) {
}
export async function GET(request: Request) {
const apiKey = extractApiKey(request);
const apiKeyMetadata = await getApiKeyMetadata(apiKey);
const apiKeyId = apiKeyMetadata?.id || null;
const scope = await getApiKeyRequestScope(request);
if (scope.rejection) return scope.rejection;
const apiKeyId = scope.apiKeyId;
const { searchParams } = new URL(request.url);
const limit = Math.min(Number.parseInt(searchParams.get("limit") || "20") || 20, 10000);
@@ -89,12 +94,12 @@ export async function GET(request: Request) {
purpose,
limit: limit + 1,
after,
order
order,
});
const hasMore = files.length > limit;
const data = files.slice(0, limit);
return NextResponse.json(
{
object: "list",