mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-05 06:42:12 +03:00
Release v3.7.4 (#1730)
* chore(release): v3.7.4 — version bump, openapi and changelog sync * fix: preserve previous_response_id and conversation_id fields on empty input array (#1729) * fix: bypass UI validation block for optional API keys and fix string fallback typing (#1721) * fix(proxy): disable HTTP keep-alive and pipelining in Undici proxy dispatcher to prevent socket hang up * feat(proxy): implement bulk proxy import via pipe-delimited parser with update-or-create logic * docs: update changelog for v3.7.4 fixes and proxy features * test: update responses store expectations for empty input arrays * feat(pwa): add fullscreen installable PWA with manifest, service worker, and cross-platform app icons. (#1728) Integrated into release/v3.7.4 * Fix image provider validation and Stability image requests (#1726) Integrated into release/v3.7.4 * docs: add PR 1726 and PR 1728 to v3.7.4 changelog * fix(security): replace insecure Math.random with crypto.getRandomValues for fallback UUID generation * fix(migrations): intercept 007 migration to use IF NOT EXISTS logic on fresh installs Fixes #1733 * test: fix typescript compilation errors in unit tests * fix(db): reconcile legacy reasoning cache migration * chore(release): bump to v3.7.4 — changelog, docs, version sync * fix(cc-compatible): preserve Claude Code system skeleton (#1740) Integrated into release/v3.7.4 * docs(changelog): update for PR #1740 merge * docs(changelog): include workflow updates * fix(db): reconcile legacy reasoning cache migration (#1734) Integrated into release/v3.7.4 * Add endpoint tunnel visibility settings (#1743) Integrated into release/v3.7.4 * Normalize max reasoning effort for Codex routing (#1744) Integrated into release/v3.7.4 * Fix Claude Code gateway config helper (#1745) Integrated into release/v3.7.4 * Refresh CLI fingerprint provider profiles (#1746) Integrated into release/v3.7.4 * Integrated into release/v3.7.4 (PR #1742) * docs(changelog): update for PRs 1742-1746 --------- Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com> Co-authored-by: Yash Ghule <y.ghule77@gmail.com> Co-authored-by: backryun <bakryun0718@proton.me> Co-authored-by: dhaern <manker_lol@hotmail.com> Co-authored-by: Randi <55005611+rdself@users.noreply.github.com> Co-authored-by: Duncan L <leungd@gmail.com>
This commit is contained in:
committed by
GitHub
parent
4cdd0dfd1a
commit
0cd388efb8
@@ -10,6 +10,7 @@ import {
|
||||
getCliRuntimeStatus,
|
||||
} from "@/shared/services/cliRuntime";
|
||||
import { createBackup } from "@/shared/services/backupService";
|
||||
import { normalizeClaudeBaseUrl } from "@/shared/services/claudeCliConfig";
|
||||
import { saveCliToolLastConfigured, deleteCliToolLastConfigured } from "@/lib/db/cliToolState";
|
||||
import { cliSettingsEnvSchema } from "@/shared/validation/schemas";
|
||||
import { isValidationFailure, validateBody } from "@/shared/validation/helpers";
|
||||
@@ -122,7 +123,7 @@ export async function POST(request: Request) {
|
||||
env.ANTHROPIC_AUTH_TOKEN = keyRecord.key as string;
|
||||
}
|
||||
} catch {
|
||||
// Non-critical: fall back to whatever value was in env (e.g. sk_omniroute)
|
||||
// Non-critical: fall back to whatever value was already provided in env.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,11 +147,9 @@ export async function POST(request: Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize ANTHROPIC_BASE_URL to ensure /v1 suffix
|
||||
// Claude Code gateway mode expects the unified root endpoint, not a forced /v1 suffix.
|
||||
if (env.ANTHROPIC_BASE_URL) {
|
||||
env.ANTHROPIC_BASE_URL = env.ANTHROPIC_BASE_URL.endsWith("/v1")
|
||||
? env.ANTHROPIC_BASE_URL
|
||||
: `${env.ANTHROPIC_BASE_URL}/v1`;
|
||||
env.ANTHROPIC_BASE_URL = normalizeClaudeBaseUrl(env.ANTHROPIC_BASE_URL);
|
||||
}
|
||||
|
||||
// Merge new env with existing settings
|
||||
@@ -186,6 +185,7 @@ export async function POST(request: Request) {
|
||||
const RESET_ENV_KEYS = [
|
||||
"ANTHROPIC_BASE_URL",
|
||||
"ANTHROPIC_AUTH_TOKEN",
|
||||
"ANTHROPIC_API_KEY",
|
||||
"ANTHROPIC_DEFAULT_OPUS_MODEL",
|
||||
"ANTHROPIC_DEFAULT_SONNET_MODEL",
|
||||
"ANTHROPIC_DEFAULT_HAIKU_MODEL",
|
||||
|
||||
75
src/app/api/settings/proxies/bulk-import/route.ts
Normal file
75
src/app/api/settings/proxies/bulk-import/route.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import { upsertProxy } from "@/lib/localDb";
|
||||
import { bulkImportProxiesSchema } from "@/shared/validation/schemas";
|
||||
import { isValidationFailure, validateBody } from "@/shared/validation/helpers";
|
||||
import { createErrorResponse, createErrorResponseFromUnknown } from "@/lib/api/errorResponse";
|
||||
import { requireManagementAuth } from "@/lib/api/requireManagementAuth";
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const authError = await requireManagementAuth(request);
|
||||
if (authError) return authError;
|
||||
|
||||
let rawBody: unknown;
|
||||
try {
|
||||
rawBody = await request.json();
|
||||
} catch {
|
||||
return createErrorResponse({
|
||||
status: 400,
|
||||
message: "Invalid JSON body",
|
||||
type: "invalid_request",
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const validation = validateBody(bulkImportProxiesSchema, rawBody);
|
||||
if (isValidationFailure(validation)) {
|
||||
return createErrorResponse({
|
||||
status: 400,
|
||||
message: validation.error.message,
|
||||
details: validation.error.details,
|
||||
type: "invalid_request",
|
||||
});
|
||||
}
|
||||
|
||||
const { items } = validation.data;
|
||||
let created = 0;
|
||||
let updated = 0;
|
||||
let failed = 0;
|
||||
const results: Array<{
|
||||
name: string;
|
||||
success: boolean;
|
||||
action?: "created" | "updated";
|
||||
id?: string;
|
||||
error?: string;
|
||||
}> = [];
|
||||
|
||||
for (const item of items) {
|
||||
try {
|
||||
const result = await upsertProxy(item);
|
||||
if (result.proxy) {
|
||||
if (result.action === "created") created++;
|
||||
else updated++;
|
||||
results.push({
|
||||
name: item.name,
|
||||
success: true,
|
||||
action: result.action,
|
||||
id: result.proxy.id,
|
||||
});
|
||||
} else {
|
||||
failed++;
|
||||
results.push({ name: item.name, success: false, error: "Unknown error" });
|
||||
}
|
||||
} catch (error) {
|
||||
failed++;
|
||||
results.push({
|
||||
name: item.name,
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : "Unknown error",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return Response.json({ created, updated, failed, results });
|
||||
} catch (error) {
|
||||
return createErrorResponseFromUnknown(error, "Failed to bulk import proxies");
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
launchAutoUpdate,
|
||||
validateAutoUpdateRuntime,
|
||||
} from "@/lib/system/autoUpdate";
|
||||
import { NEWS_JSON_URL, parseActiveNewsPayload } from "@/shared/utils/releaseNotes";
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
|
||||
@@ -50,16 +51,32 @@ function isNewer(a: string | null, b: string): boolean {
|
||||
return aPat > bPat;
|
||||
}
|
||||
|
||||
async function getNews() {
|
||||
try {
|
||||
const res = await fetch(NEWS_JSON_URL, { next: { revalidate: 3600 } });
|
||||
if (!res.ok) return null;
|
||||
const data = await res.json();
|
||||
return parseActiveNewsPayload(data);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function GET(req: NextRequest) {
|
||||
if (!(await isAuthenticated(req))) {
|
||||
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||
}
|
||||
|
||||
const current = getCurrentVersion();
|
||||
const latest = await getLatestNpmVersion();
|
||||
const updateAvailable = isNewer(latest, current);
|
||||
const config = getAutoUpdateConfig();
|
||||
const validation = await validateAutoUpdateRuntime(config);
|
||||
|
||||
const [latest, news, validation] = await Promise.all([
|
||||
getLatestNpmVersion(),
|
||||
getNews(),
|
||||
validateAutoUpdateRuntime(config),
|
||||
]);
|
||||
|
||||
const updateAvailable = isNewer(latest, current);
|
||||
|
||||
return NextResponse.json({
|
||||
current,
|
||||
@@ -68,6 +85,7 @@ export async function GET(req: NextRequest) {
|
||||
channel: config.mode,
|
||||
autoUpdateSupported: validation.supported,
|
||||
autoUpdateError: validation.reason,
|
||||
news,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
import { handleImageEdit } from "@omniroute/open-sse/handlers/imageGeneration.ts";
|
||||
import {
|
||||
getProviderCredentials,
|
||||
clearRecoveredProviderState,
|
||||
extractApiKey,
|
||||
isValidApiKey,
|
||||
} from "@/sse/services/auth";
|
||||
import { getProviderCredentials, clearRecoveredProviderState } from "@/sse/services/auth";
|
||||
import { parseImageModel, getImageProvider } from "@omniroute/open-sse/config/imageRegistry.ts";
|
||||
import { errorResponse, unavailableResponse } from "@omniroute/open-sse/utils/error.ts";
|
||||
import { HTTP_STATUS } from "@omniroute/open-sse/config/constants.ts";
|
||||
@@ -99,18 +94,11 @@ export async function POST(request: Request) {
|
||||
return errorResponse(HTTP_STATUS.BAD_REQUEST, "Missing required field: image");
|
||||
}
|
||||
|
||||
const apiKey = extractApiKey(request);
|
||||
if (!isValidApiKey(apiKey)) {
|
||||
const policyError = enforceApiKeyPolicy(apiKey);
|
||||
if (policyError) {
|
||||
return new Response(JSON.stringify(policyError.body), {
|
||||
status: policyError.status,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const fullModel = model || "cgpt-web/gpt-5.3-instant";
|
||||
|
||||
const policy = await enforceApiKeyPolicy(request, fullModel);
|
||||
if (policy.rejection) return policy.rejection;
|
||||
|
||||
const parsed = parseImageModel(fullModel);
|
||||
const providerConfig = getImageProvider(parsed.provider);
|
||||
if (!providerConfig) {
|
||||
@@ -126,7 +114,16 @@ export async function POST(request: Request) {
|
||||
);
|
||||
}
|
||||
|
||||
const credentials = await getProviderCredentials(parsed.provider, apiKey);
|
||||
const allowedConnections =
|
||||
policy.apiKeyInfo?.allowedConnections && policy.apiKeyInfo.allowedConnections.length > 0
|
||||
? policy.apiKeyInfo.allowedConnections
|
||||
: null;
|
||||
const credentials = await getProviderCredentials(
|
||||
parsed.provider,
|
||||
null,
|
||||
allowedConnections,
|
||||
fullModel
|
||||
);
|
||||
if (!credentials) {
|
||||
return errorResponse(
|
||||
HTTP_STATUS.UNAUTHORIZED,
|
||||
|
||||
Reference in New Issue
Block a user