mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-20 13:52:28 +03:00
Validated on the resolved merge against the current tip (527da656 + the post-#11281 rebaseline): the single conflict was a comment-only collision in providers/[id]/models/route.ts (kept the tip's #10828-ordering note). Focused suites 125/125 across all 13 touched test files (build-sqlite-stub, cc-compatible, copilot-claude-messages, copilot-gemini-route, executor-github, ghe-copilot, github-copilot-discovery-token, github-copilot-model-discovery, noauth-sibling-7620, provider-header-profiles, provider-models-config, request-log-payloads, upstream-error-passthrough), typecheck:core clean, file-size/changelog-integrity OK. Merged --admin over the inherited 2026-08-23 base-red cluster (#9985) — the reds are proven tip failures (CLI catalog cluster + @testing-library allowlist, being drained by #11280), not from this diff. Note: the rebase means several items the body listed (relay x-relay-path SSRF, /v1/search blocked-providers, #10736 rotation fence, #10903, #10865, #10899, #10916) already landed upstream and are NOT in this delta — the delta is: better-sqlite3 build guard + build heap/worker caps + telemetry-off (#10060 re-derived), credential-echo passthrough refusal + OCR/moderation redaction + call-log key redaction, Copilot CLI 1.0.81-6 wire identity + Claude→/v1/messages name-matched routing + discovery token fix, CC model_not_found 400, compat overrides for no-auth aliases (#7620-pinned). The Copilot wire-identity change is the one to watch in production. Thank you @arminanton — and the ported-author credits in the commit history (@rqzbeh, yidecode, the #10899/#10916 authors) are preserved. Your config-posture finding (REQUIRE_API_KEY default vs 0.0.0.0) is noted for a maintainer decision, as you scoped it.
85 lines
2.6 KiB
TypeScript
85 lines
2.6 KiB
TypeScript
import { CORS_HEADERS } from "../utils/cors.ts";
|
|
/**
|
|
* Moderation Handler
|
|
*
|
|
* Handles POST /v1/moderations (OpenAI Moderations API format).
|
|
*/
|
|
|
|
import { getModerationProvider, parseModerationModel } from "../config/moderationRegistry.ts";
|
|
import { errorResponse, redactSensitiveErrorText } from "../utils/error.ts";
|
|
import { attachOmniRouteMetaHeaders } from "@/domain/omnirouteResponseMeta";
|
|
import { generateRequestId } from "@/shared/utils/requestId";
|
|
|
|
/**
|
|
* Handle moderation request
|
|
*
|
|
* @param {Object} options
|
|
* @param {Object} options.body - JSON body { model, input }
|
|
* @param {Object} options.credentials - Provider credentials { apiKey }
|
|
* @returns {Response}
|
|
*/
|
|
/** @returns {Promise<unknown>} */
|
|
export async function handleModeration({ body, credentials }) {
|
|
const startTime = Date.now();
|
|
if (!body.input) {
|
|
return errorResponse(400, "input is required");
|
|
}
|
|
|
|
// Default to latest moderation model
|
|
const model = body.model || "omni-moderation-latest";
|
|
const { provider: providerId, model: modelId } = parseModerationModel(model);
|
|
const providerConfig = providerId ? getModerationProvider(providerId) : null;
|
|
|
|
if (!providerConfig) {
|
|
return errorResponse(
|
|
400,
|
|
`No moderation provider found for model "${model}". Available: openai`
|
|
);
|
|
}
|
|
|
|
const token = credentials?.apiKey || credentials?.accessToken;
|
|
if (!token) {
|
|
return errorResponse(401, `No credentials for moderation provider: ${providerId}`);
|
|
}
|
|
|
|
try {
|
|
const res = await fetch(providerConfig.baseUrl, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
Authorization: `Bearer ${token}`,
|
|
},
|
|
body: JSON.stringify({
|
|
model: modelId,
|
|
input: body.input,
|
|
}),
|
|
});
|
|
|
|
if (!res.ok) {
|
|
const errText = await res.text();
|
|
// secret-leak hardening: redact any credential the upstream echoed back
|
|
// before relaying the error body to the client (structure-preserving).
|
|
return new Response(redactSensitiveErrorText(errText), {
|
|
status: res.status,
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
...CORS_HEADERS,
|
|
},
|
|
});
|
|
}
|
|
|
|
const data = await res.json();
|
|
const headers = new Headers({ ...CORS_HEADERS, "Content-Type": "application/json" });
|
|
attachOmniRouteMetaHeaders(headers, {
|
|
provider: providerId,
|
|
model: modelId,
|
|
costUsd: 0,
|
|
latencyMs: Date.now() - startTime,
|
|
requestId: generateRequestId(),
|
|
});
|
|
return new Response(JSON.stringify(data), { status: 200, headers });
|
|
} catch (err) {
|
|
return errorResponse(500, `Moderation request failed: ${err.message}`);
|
|
}
|
|
}
|