openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
130 lines (129 loc) • 5.5 kB
JavaScript
import { c as isRecord } from "./record-coerce-TIsquoVR.mjs";
import { b as redactToolPayloadText } from "./redact-BEIE6NCT.mjs";
import { t as ToolAuthorizationError } from "./tool-input-error-mjW74R8m.mjs";
import { n as authorizeOperatorScopesForMethod } from "./method-scopes-C-MKLkWf.mjs";
import { i as describeFailoverError, m as resolveFailoverStatus } from "./failover-error-DXCr0mZv.mjs";
import { d as sendMethodNotAllowed, f as sendMissingScopeForbidden, o as readJsonBodyOrError } from "./http-common-Cl0x-bzn.mjs";
import { d as resolveTrustedHttpOperatorScopes, r as authorizeGatewayHttpRequestOrReply } from "./http-auth-utils-BSns8ce1.mjs";
import "./http-utils-D6gh3mxI.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
};
function statusForReason(reason, status) {
if (reason === "server_error") return status && status >= 400 && status < 500 ? status : 502;
if (reason === "timeout") return status && status >= 400 && status < 500 ? status : 504;
return status ?? resolveFailoverStatus(reason) ?? 500;
}
function messageForReason(params) {
if (params.reason === "server_error") return "upstream provider error";
if (params.reason === "timeout") return "upstream provider timeout";
if (params.reason === "overloaded") return "upstream provider overloaded";
return params.rawError?.trim() || params.message.trim() || "request failed";
}
/** 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;
const status = statusForReason(reason, described.status);
const message = messageForReason({
reason,
message: described.message,
rawError: described.rawError
});
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 obj = value;
const type = obj.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 obj;
}
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 };