openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
118 lines (117 loc) • 5.22 kB
JavaScript
import { c as isRecord } from "./record-coerce-TIsquoVR.mjs";
import { b as redactToolPayloadText } from "./redact-DJ8j6ADa.mjs";
import { t as ToolAuthorizationError } from "./tool-input-error-mjW74R8m.mjs";
import { n as authorizeOperatorScopesForMethod } from "./method-scopes-BrMPjfvW.mjs";
import { i as describeFailoverError, m as resolveFailoverStatus } from "./failover-error-CYuKSasF.mjs";
import { d as sendMethodNotAllowed, f as sendMissingScopeForbidden, o as readJsonBodyOrError } from "./http-common-BJjsxTZ6.mjs";
import { d as resolveTrustedHttpOperatorScopes, r as authorizeGatewayHttpRequestOrReply } from "./http-auth-utils-DUIIp4wk.mjs";
import "./http-utils-C8zPoxoQ.mjs";
//#region src/gateway/http-endpoint-helpers.ts
/** Handles a gateway POST JSON endpoint and returns the parsed body when authorized. */
async function handleGatewayPostJsonEndpoint(req, res, opts) {
if (new URL(req.url ?? "/", "http://localhost").pathname !== opts.pathname) return false;
if (req.method !== "POST") {
sendMethodNotAllowed(res);
return;
}
const requestAuth = await authorizeGatewayHttpRequestOrReply({
...opts,
req,
res
});
if (!requestAuth) return;
const operatorScopes = opts.resolveOperatorScopes?.(req, requestAuth) ?? resolveTrustedHttpOperatorScopes(req, requestAuth);
if (opts.requiredOperatorMethod) {
const scopeAuth = authorizeOperatorScopesForMethod(opts.requiredOperatorMethod, operatorScopes);
if (!scopeAuth.allowed) {
sendMissingScopeForbidden(res, scopeAuth.missingScope);
return;
}
}
const body = await readJsonBodyOrError(req, res, opts.maxBodyBytes);
if (body === void 0) return;
try {
await requestAuth.revalidate();
} catch (error) {
if (res.writableEnded || res.destroyed) return;
throw error;
}
return {
body,
requestAuth,
operatorScopes
};
}
//#endregion
//#region src/gateway/openai-compat-errors.ts
const ERROR_TYPE_BY_REASON = {
auth: "authentication_error",
auth_permanent: "permission_error",
format: "invalid_request_error",
rate_limit: "rate_limit_error",
overloaded: "api_error",
billing: "insufficient_quota",
server_error: "api_error",
timeout: "api_error",
tls_certificate: "api_error",
context_overflow: "invalid_request_error",
model_not_found: "invalid_request_error",
session_expired: "invalid_request_error",
empty_response: void 0,
no_error_details: void 0,
unclassified: void 0,
unknown: void 0
};
/** Converts a provider failover error into an OpenAI-compatible error envelope. */
function resolveOpenAiCompatError(err) {
if (err instanceof ToolAuthorizationError) return {
status: 403,
error: {
message: err.message,
type: "permission_error"
}
};
const described = describeFailoverError(err);
const reason = described.reason;
if (!reason) return;
const type = ERROR_TYPE_BY_REASON[reason];
if (!type) return;
let status = described.status ?? resolveFailoverStatus(reason) ?? 500;
let message;
if (reason === "server_error" || reason === "timeout") {
status = described.status && described.status >= 400 && described.status < 500 ? described.status : reason === "timeout" ? 504 : 502;
message = reason === "timeout" ? "upstream provider timeout" : "upstream provider error";
} else message = reason === "overloaded" ? "upstream provider overloaded" : described.rawError?.trim() || described.message.trim() || "request failed";
return {
status,
error: {
message: redactToolPayloadText(message),
type,
...described.code ? { code: redactToolPayloadText(described.code) } : {}
}
};
}
/** Validates sampling ranges after the HTTP request schema admits numeric fields. */
function validateOpenAiSamplingParams(params) {
if (params.temperature != null && (params.temperature < 0 || params.temperature > 2)) return "`temperature` must be between 0 and 2.";
if (params.topP != null && (params.topP < 0 || params.topP > 1)) return "`top_p` must be between 0 and 1.";
if (params.frequencyPenalty != null && (params.frequencyPenalty < -2 || params.frequencyPenalty > 2)) return "`frequency_penalty` must be between -2.0 and 2.0.";
if (params.presencePenalty != null && (params.presencePenalty < -2 || params.presencePenalty > 2)) return "`presence_penalty` must be between -2.0 and 2.0.";
if (params.seed != null && !Number.isInteger(params.seed)) return "`seed` must be an integer.";
}
function resolveResponseFormat(value) {
if (value == null) return;
if (!isRecord(value)) throw new Error("response_format must be an object");
const type = value.type;
if (type !== "text" && type !== "json_object" && type !== "json_schema") throw new Error("response_format.type must be text, json_object, or json_schema");
return value;
}
function resolveStopSequences(value) {
if (value == null) return;
const list = typeof value === "string" ? [value] : value;
if (list.length > 4) throw new Error("stop supports at most 4 sequences");
if (list.some((item) => item.length === 0)) throw new Error("stop entries must be non-empty strings");
return list.length > 0 ? list : void 0;
}
//#endregion
export { handleGatewayPostJsonEndpoint as a, validateOpenAiSamplingParams as i, resolveResponseFormat as n, resolveStopSequences as r, resolveOpenAiCompatError as t };