openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
399 lines (398 loc) • 19.5 kB
JavaScript
import { a as normalizeLowercaseStringOrEmpty, c as normalizeOptionalString, s as normalizeOptionalLowercaseString } from "./string-coerce-mnp54Vah.js";
import { o as isRecord } from "./record-coerce-DHZ4bFlT.js";
import { d as normalizeTrimmedStringList } from "./string-normalization-WNUDCpXX.js";
import { s as resolveRuntimeServiceVersion } from "./version-Crcn9X9T.js";
import { i as normalizeProviderId } from "./provider-id-Dq06Bcx6.js";
import { t as asBoolean } from "./boolean-By0bZpFH.js";
import { t as listOpenClawPluginManifestMetadata } from "./manifest-metadata-scan-B1LSltL_.js";
//#region src/agents/provider-attribution.ts
/**
* Provider endpoint attribution and request capability resolver.
*
* Classifies provider routes so transports know which attribution headers, payload features, and endpoint policies apply.
*/
function readCompatBoolean(compat, key) {
if (!compat || typeof compat !== "object") return;
return asBoolean(compat[key]);
}
const OPENCLAW_ATTRIBUTION_PRODUCT = "OpenClaw";
const OPENCLAW_ATTRIBUTION_ORIGINATOR = "openclaw";
const OPENROUTER_ATTRIBUTION_CATEGORIES = "cli-agent,cloud-agent,programming-app,creative-writing,writing-assistant,general-chat,personal-agent";
const LOCAL_ENDPOINT_HOSTS = new Set([
"localhost",
"127.0.0.1",
"::1",
"[::1]"
]);
const OPENAI_RESPONSES_APIS = new Set([
"openai-responses",
"azure-openai-responses",
"openai-chatgpt-responses"
]);
const OPENAI_RESPONSES_PROVIDERS = new Set([
"openai",
"azure-openai",
"azure-openai-responses"
]);
const MANIFEST_PROVIDER_ENDPOINT_CLASSES = new Set([
"anthropic-public",
"cerebras-native",
"chutes-native",
"deepseek-native",
"github-copilot-native",
"groq-native",
"mistral-public",
"moonshot-native",
"modelstudio-native",
"nvidia-native",
"openai-public",
"openai",
"opencode-native",
"azure-openai",
"openrouter",
"xai-native",
"xiaomi-native",
"zai-native",
"google-generative-ai",
"google-vertex"
]);
let manifestProviderEndpointCache = null;
let manifestProviderRequestCache = null;
function formatOpenClawUserAgent(version) {
return `${OPENCLAW_ATTRIBUTION_ORIGINATOR}/${version}`;
}
function tryParseHostname(value) {
try {
return normalizeOptionalLowercaseString(new URL(value).hostname);
} catch {
return;
}
}
function isSchemelessHostnameCandidate(value) {
return /^[a-z0-9.[\]-]+(?::\d+)?(?:[/?#].*)?$/i.test(value);
}
function resolveUrlHostname(value) {
const trimmed = normalizeOptionalString(value);
if (!trimmed) return;
const parsedHostname = tryParseHostname(trimmed);
if (parsedHostname) return parsedHostname;
if (!isSchemelessHostnameCandidate(trimmed)) return;
return tryParseHostname(`https://${trimmed}`);
}
function normalizeComparableBaseUrl(value) {
const trimmed = normalizeOptionalString(value);
if (!trimmed) return;
const parsedValue = tryParseHostname(trimmed) || !isSchemelessHostnameCandidate(trimmed) ? trimmed : `https://${trimmed}`;
try {
const url = new URL(parsedValue);
if (url.protocol !== "http:" && url.protocol !== "https:") return;
url.hash = "";
url.search = "";
return normalizeOptionalLowercaseString(url.toString().replace(/\/+$/, ""));
} catch {
return;
}
}
function isManifestProviderEndpointClass(value) {
return MANIFEST_PROVIDER_ENDPOINT_CLASSES.has(value);
}
function readManifestProviderEndpoints(manifest) {
if (!Array.isArray(manifest.providerEndpoints)) return [];
const entries = [];
for (const rawEndpoint of manifest.providerEndpoints) {
if (!isRecord(rawEndpoint)) continue;
const endpointClassRaw = normalizeOptionalString(rawEndpoint.endpointClass);
if (!endpointClassRaw || !isManifestProviderEndpointClass(endpointClassRaw)) continue;
entries.push({
endpointClass: endpointClassRaw,
hosts: normalizeTrimmedStringList(rawEndpoint.hosts).map((host) => host.toLowerCase()),
hostSuffixes: normalizeTrimmedStringList(rawEndpoint.hostSuffixes).map((host) => host.toLowerCase()),
normalizedBaseUrls: normalizeTrimmedStringList(rawEndpoint.baseUrls).map((baseUrl) => normalizeComparableBaseUrl(baseUrl)).filter((baseUrl) => baseUrl !== void 0),
...normalizeOptionalString(rawEndpoint.googleVertexRegion) ? { googleVertexRegion: normalizeOptionalString(rawEndpoint.googleVertexRegion) } : {},
...normalizeOptionalString(rawEndpoint.googleVertexRegionHostSuffix) ? { googleVertexRegionHostSuffix: normalizeOptionalString(rawEndpoint.googleVertexRegionHostSuffix) } : {}
});
}
return entries;
}
function readManifestProviderRequests(manifest) {
const providerRequest = manifest.providerRequest;
if (!isRecord(providerRequest) || !isRecord(providerRequest.providers)) return [];
const entries = [];
for (const [providerRaw, requestRaw] of Object.entries(providerRequest.providers)) {
if (!isRecord(requestRaw)) continue;
const provider = normalizeLowercaseStringOrEmpty(providerRaw);
if (!provider) continue;
const compatibilityFamily = normalizeOptionalString(requestRaw.compatibilityFamily) === "moonshot" ? "moonshot" : void 0;
const supportsStreamingUsage = isRecord(requestRaw.openAICompletions) ? requestRaw.openAICompletions.supportsStreamingUsage : void 0;
entries.push([provider, {
...normalizeOptionalString(requestRaw.family) ? { family: normalizeOptionalString(requestRaw.family) } : {},
...compatibilityFamily ? { compatibilityFamily } : {},
...typeof supportsStreamingUsage === "boolean" ? { supportsOpenAICompletionsStreamingUsageCompat: supportsStreamingUsage } : {}
}]);
}
return entries;
}
function collectManifestProviderEndpoints() {
const entries = [];
for (const { manifest } of listOpenClawPluginManifestMetadata()) entries.push(...readManifestProviderEndpoints(manifest));
return entries;
}
function collectManifestProviderRequests() {
const entries = /* @__PURE__ */ new Map();
for (const { manifest } of listOpenClawPluginManifestMetadata()) for (const [provider, request] of readManifestProviderRequests(manifest)) entries.set(provider, request);
return entries;
}
function loadManifestProviderEndpointCache() {
if (!manifestProviderEndpointCache) manifestProviderEndpointCache = collectManifestProviderEndpoints();
return manifestProviderEndpointCache;
}
function loadManifestProviderRequestCache() {
if (!manifestProviderRequestCache) manifestProviderRequestCache = collectManifestProviderRequests();
return manifestProviderRequestCache;
}
function resolveManifestProviderRequest(provider) {
return provider ? loadManifestProviderRequestCache().get(provider) : void 0;
}
function hostMatchesSuffix(host, suffix) {
if (!suffix) return false;
return suffix.startsWith(".") || suffix.startsWith("-") ? host.endsWith(suffix) : host === suffix || host.endsWith(`.${suffix}`);
}
function buildManifestEndpointResolution(endpoint, host) {
const regionSuffix = endpoint.googleVertexRegionHostSuffix;
const googleVertexRegion = endpoint.googleVertexRegion ?? (regionSuffix && host.endsWith(regionSuffix) ? host.slice(0, -regionSuffix.length) : void 0);
return {
endpointClass: endpoint.endpointClass,
hostname: host,
...googleVertexRegion ? { googleVertexRegion } : {}
};
}
function resolveManifestProviderEndpoint(params) {
for (const endpoint of loadManifestProviderEndpointCache()) {
if (endpoint.hosts.includes(params.host)) return buildManifestEndpointResolution(endpoint, params.host);
if (endpoint.hostSuffixes.some((suffix) => hostMatchesSuffix(params.host, suffix))) return buildManifestEndpointResolution(endpoint, params.host);
if (params.normalizedBaseUrl && endpoint.normalizedBaseUrls.includes(params.normalizedBaseUrl)) return buildManifestEndpointResolution(endpoint, params.host);
}
}
function isLocalEndpointHost(host) {
return LOCAL_ENDPOINT_HOSTS.has(host) || host.endsWith(".localhost") || host.endsWith(".local") || host.endsWith(".internal");
}
function resolveProviderEndpoint(baseUrl) {
if (typeof baseUrl !== "string" || !baseUrl.trim()) return { endpointClass: "default" };
const host = resolveUrlHostname(baseUrl);
if (!host) return { endpointClass: "invalid" };
const manifestEndpoint = resolveManifestProviderEndpoint({
host,
normalizedBaseUrl: normalizeComparableBaseUrl(baseUrl)
});
if (manifestEndpoint) return manifestEndpoint;
if (isLocalEndpointHost(host)) return {
endpointClass: "local",
hostname: host
};
return {
endpointClass: "custom",
hostname: host
};
}
function resolveKnownProviderFamily(provider) {
const manifestFamily = resolveManifestProviderRequest(provider)?.family;
if (manifestFamily) return manifestFamily;
switch (provider) {
case "openai":
case "azure-openai":
case "azure-openai-responses": return "openai-family";
default: return provider || "unknown";
}
}
function isOpenAIResponsesApi(api) {
const normalizedApi = normalizeOptionalLowercaseString(api);
return normalizedApi !== void 0 && OPENAI_RESPONSES_APIS.has(normalizedApi);
}
function isCanonicalOrLegacyOpenAIProvider(provider) {
return provider === "openai";
}
function resolveProviderAttributionIdentity(env = process.env) {
return {
product: OPENCLAW_ATTRIBUTION_PRODUCT,
version: resolveRuntimeServiceVersion(env)
};
}
function buildOpenRouterAttributionPolicy(env = process.env) {
const identity = resolveProviderAttributionIdentity(env);
return {
provider: "openrouter",
enabledByDefault: true,
verification: "vendor-documented",
hook: "request-headers",
docsUrl: "https://openrouter.ai/docs/app-attribution",
reviewNote: "Documented app attribution headers. Verified in OpenClaw runtime wrapper.",
...identity,
headers: {
"HTTP-Referer": "https://openclaw.ai",
"X-OpenRouter-Title": identity.product,
"X-OpenRouter-Categories": OPENROUTER_ATTRIBUTION_CATEGORIES
}
};
}
function buildNvidiaAttributionPolicy(env = process.env) {
return {
provider: "nvidia",
enabledByDefault: true,
verification: "vendor-documented",
hook: "request-headers",
reviewNote: "NVIDIA NIM billing invoke-origin attribution header. Applied only on verified NVIDIA routes.",
...resolveProviderAttributionIdentity(env),
headers: { "X-BILLING-INVOKE-ORIGIN": OPENCLAW_ATTRIBUTION_PRODUCT }
};
}
function buildOpenAIAttributionPolicy(env = process.env) {
const identity = resolveProviderAttributionIdentity(env);
return {
provider: "openai",
enabledByDefault: true,
verification: "vendor-hidden-api-spec",
hook: "request-headers",
reviewNote: "OpenAI native traffic supports hidden originator/User-Agent attribution. Verified against the Codex wire contract.",
...identity,
headers: {
originator: OPENCLAW_ATTRIBUTION_ORIGINATOR,
version: identity.version,
"User-Agent": formatOpenClawUserAgent(identity.version)
}
};
}
function buildXaiAttributionPolicy(env = process.env) {
const identity = resolveProviderAttributionIdentity(env);
return {
provider: "xai",
enabledByDefault: true,
verification: "vendor-hidden-api-spec",
hook: "request-headers",
reviewNote: "xAI api.x.ai accepts a standard openclaw User-Agent. Companion originator/version headers mirror the OpenAI attribution shape for consistency; they are not validated against an xAI-specific spec and are expected to be ignored by xAI's OpenAI-compatible surface.",
...identity,
headers: {
originator: OPENCLAW_ATTRIBUTION_ORIGINATOR,
version: identity.version,
"User-Agent": formatOpenClawUserAgent(identity.version)
}
};
}
function buildSdkHookOnlyPolicy(provider, hook, reviewNote, env = process.env) {
return {
provider,
enabledByDefault: false,
verification: "vendor-sdk-hook-only",
hook,
reviewNote,
...resolveProviderAttributionIdentity(env)
};
}
function listProviderAttributionPolicies(env = process.env) {
return [
buildOpenRouterAttributionPolicy(env),
buildNvidiaAttributionPolicy(env),
buildOpenAIAttributionPolicy(env),
buildXaiAttributionPolicy(env),
buildSdkHookOnlyPolicy("anthropic", "default-headers", "Anthropic JS SDK exposes defaultHeaders, but app attribution is not yet verified.", env),
buildSdkHookOnlyPolicy("google", "user-agent-extra", "Google GenAI JS SDK exposes userAgentExtra/httpOptions, but provider-side attribution is not yet verified.", env),
buildSdkHookOnlyPolicy("groq", "default-headers", "Groq JS SDK exposes defaultHeaders, but app attribution is not yet verified.", env),
buildSdkHookOnlyPolicy("mistral", "custom-user-agent", "Mistral JS SDK exposes a custom userAgent option, but app attribution is not yet verified.", env),
buildSdkHookOnlyPolicy("together", "default-headers", "Together JS SDK exposes defaultHeaders, but app attribution is not yet verified.", env)
];
}
function resolveProviderAttributionPolicy(provider, env = process.env) {
const normalized = normalizeProviderId(provider ?? "");
const canonical = normalized === "openai" ? "openai" : normalized;
return listProviderAttributionPolicies(env).find((policy) => policy.provider === canonical);
}
function resolveProviderRequestPolicy(input, env = process.env) {
const provider = normalizeProviderId(input.provider ?? "");
const policy = resolveProviderAttributionPolicy(provider, env);
const endpointClass = resolveProviderEndpoint(input.baseUrl).endpointClass;
const usesConfiguredBaseUrl = endpointClass !== "default";
const usesKnownNativeOpenAIEndpoint = endpointClass === "openai-public" || endpointClass === "openai" || endpointClass === "azure-openai";
const usesVerifiedOpenAIAttributionHost = endpointClass === "openai-public" || endpointClass === "openai";
const usesXaiNativeAttributionHost = endpointClass === "xai-native";
const usesExplicitProxyLikeEndpoint = usesConfiguredBaseUrl && !usesKnownNativeOpenAIEndpoint;
let attributionProvider;
if (isCanonicalOrLegacyOpenAIProvider(provider) && usesVerifiedOpenAIAttributionHost) attributionProvider = "openai";
else if (provider === "openrouter" && policy?.enabledByDefault) {
if (endpointClass === "openrouter" || endpointClass === "default") attributionProvider = "openrouter";
} else if (provider === "xai" && policy?.enabledByDefault) {
if (usesXaiNativeAttributionHost || endpointClass === "default") attributionProvider = "xai";
}
if (!attributionProvider && endpointClass === "nvidia-native") attributionProvider = "nvidia";
const attributionPolicy = attributionProvider ? resolveProviderAttributionPolicy(attributionProvider, env) : void 0;
const attributionHeaders = attributionPolicy?.enabledByDefault ? attributionPolicy.headers : void 0;
return {
provider: provider || void 0,
policy: attributionPolicy ?? policy,
endpointClass,
usesConfiguredBaseUrl,
knownProviderFamily: resolveKnownProviderFamily(provider || void 0),
attributionProvider,
attributionHeaders,
allowsHiddenAttribution: attributionProvider !== void 0 && attributionPolicy?.verification === "vendor-hidden-api-spec",
usesKnownNativeOpenAIEndpoint,
usesKnownNativeOpenAIRoute: endpointClass === "default" ? isCanonicalOrLegacyOpenAIProvider(provider) : usesKnownNativeOpenAIEndpoint,
usesVerifiedOpenAIAttributionHost,
usesExplicitProxyLikeEndpoint
};
}
function resolveProviderRequestCapabilities(input, env = process.env) {
const policy = resolveProviderRequestPolicy(input, env);
const provider = policy.provider;
const api = normalizeOptionalLowercaseString(input.api);
const endpointClass = policy.endpointClass;
const isKnownNativeEndpoint = endpointClass === "anthropic-public" || endpointClass === "cerebras-native" || endpointClass === "chutes-native" || endpointClass === "deepseek-native" || endpointClass === "github-copilot-native" || endpointClass === "groq-native" || endpointClass === "mistral-public" || endpointClass === "moonshot-native" || endpointClass === "modelstudio-native" || endpointClass === "nvidia-native" || endpointClass === "openai-public" || endpointClass === "openai" || endpointClass === "opencode-native" || endpointClass === "azure-openai" || endpointClass === "openrouter" || endpointClass === "xai-native" || endpointClass === "xiaomi-native" || endpointClass === "zai-native" || endpointClass === "google-generative-ai" || endpointClass === "google-vertex";
const manifestProviderRequest = resolveManifestProviderRequest(provider);
const compatibilityFamily = manifestProviderRequest?.compatibilityFamily;
const isResponsesApi = isOpenAIResponsesApi(api);
const promptCacheKeySupport = readCompatBoolean(input.compat, "supportsPromptCacheKey");
const shouldStripResponsesPromptCache = promptCacheKeySupport === true ? false : promptCacheKeySupport === false ? isResponsesApi : isResponsesApi && policy.usesExplicitProxyLikeEndpoint;
return {
...policy,
isKnownNativeEndpoint,
allowsOpenAIServiceTier: isCanonicalOrLegacyOpenAIProvider(provider) && api === "openai-responses" && endpointClass === "openai-public" || isCanonicalOrLegacyOpenAIProvider(provider) && (api === "openai-chatgpt-responses" || api === "openai-responses") && endpointClass === "openai",
supportsOpenAIReasoningCompatPayload: provider !== void 0 && api !== void 0 && !policy.usesExplicitProxyLikeEndpoint && (isCanonicalOrLegacyOpenAIProvider(provider) || provider === "azure-openai" || provider === "azure-openai-responses") && (api === "openai-completions" || api === "openai-responses" || api === "openai-chatgpt-responses" || api === "azure-openai-responses"),
allowsAnthropicServiceTier: provider === "anthropic" && api === "anthropic-messages" && (endpointClass === "default" || endpointClass === "anthropic-public"),
supportsResponsesStoreField: readCompatBoolean(input.compat, "supportsStore") !== false && isResponsesApi,
allowsResponsesStore: readCompatBoolean(input.compat, "supportsStore") !== false && provider !== void 0 && isResponsesApi && OPENAI_RESPONSES_PROVIDERS.has(provider) && policy.usesKnownNativeOpenAIEndpoint,
shouldStripResponsesPromptCache,
supportsNativeStreamingUsageCompat: endpointClass === "moonshot-native" || endpointClass === "modelstudio-native",
supportsOpenAICompletionsStreamingUsageCompat: manifestProviderRequest?.supportsOpenAICompletionsStreamingUsageCompat === true,
compatibilityFamily
};
}
function describeProviderRequestRoutingPolicy(policy) {
if (!policy.attributionProvider) return "none";
switch (policy.policy?.verification) {
case "vendor-hidden-api-spec": return "hidden";
case "vendor-documented": return "documented";
case "vendor-sdk-hook-only": return "sdk-hook-only";
default: return "none";
}
}
function describeProviderRequestRouteClass(policy) {
if (policy.endpointClass === "default") return "default";
if (policy.endpointClass === "invalid") return "invalid";
if (policy.endpointClass === "local") return "local";
if (policy.endpointClass === "custom" || policy.endpointClass === "openrouter") return "proxy-like";
return "native";
}
function describeProviderRequestRoutingSummary(input, env = process.env) {
const policy = resolveProviderRequestPolicy(input, env);
const api = normalizeOptionalLowercaseString(input.api) ?? "unknown";
const provider = policy.provider ?? "unknown";
const routeClass = describeProviderRequestRouteClass(policy);
const routingPolicy = describeProviderRequestRoutingPolicy(policy);
return [
`provider=${provider}`,
`api=${api}`,
`endpoint=${policy.endpointClass}`,
`route=${routeClass}`,
`policy=${routingPolicy}`
].join(" ");
}
//#endregion
export { resolveProviderRequestPolicy as i, resolveProviderEndpoint as n, resolveProviderRequestCapabilities as r, describeProviderRequestRoutingSummary as t };