mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-06 05:51:15 +08:00
docs: document gateway utility helpers
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
// Gateway connection detail builder for CLI/user-facing target diagnostics.
|
||||
import { redactSensitiveUrlLikeString } from "@openclaw/net-policy/redact-sensitive-url";
|
||||
import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce";
|
||||
import { resolveConfigPath, resolveGatewayPort } from "../config/paths.js";
|
||||
import type { OpenClawConfig } from "../config/types.js";
|
||||
import { isSecureWebSocketUrl } from "./net.js";
|
||||
|
||||
/** Resolved gateway target plus redacted display text for diagnostics. */
|
||||
export type GatewayConnectionDetails = {
|
||||
url: string;
|
||||
urlSource: string;
|
||||
@@ -18,6 +20,7 @@ type GatewayConnectionDetailResolvers = {
|
||||
resolveGatewayPort?: (cfg?: OpenClawConfig, env?: NodeJS.ProcessEnv) => number;
|
||||
};
|
||||
|
||||
/** Build gateway target details and reject unsafe remote plaintext websocket URLs. */
|
||||
export function buildGatewayConnectionDetailsWithResolvers(
|
||||
options: {
|
||||
config?: OpenClawConfig;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// Process-local MCP loopback runtime state for owner/non-owner HTTP access.
|
||||
type McpLoopbackRuntime = {
|
||||
port: number;
|
||||
ownerToken: string;
|
||||
@@ -6,14 +7,17 @@ type McpLoopbackRuntime = {
|
||||
|
||||
let activeRuntime: McpLoopbackRuntime | undefined;
|
||||
|
||||
/** Return a copy of the active loopback runtime, if one has been installed. */
|
||||
export function getActiveMcpLoopbackRuntime(): McpLoopbackRuntime | undefined {
|
||||
return activeRuntime ? { ...activeRuntime } : undefined;
|
||||
}
|
||||
|
||||
/** Install the active loopback runtime used by in-process MCP callers. */
|
||||
export function setActiveMcpLoopbackRuntime(runtime: McpLoopbackRuntime): void {
|
||||
activeRuntime = { ...runtime };
|
||||
}
|
||||
|
||||
/** Choose the bearer token matching owner/non-owner caller identity. */
|
||||
export function resolveMcpLoopbackBearerToken(
|
||||
runtime: McpLoopbackRuntime,
|
||||
senderIsOwner: boolean,
|
||||
@@ -21,12 +25,14 @@ export function resolveMcpLoopbackBearerToken(
|
||||
return senderIsOwner ? runtime.ownerToken : runtime.nonOwnerToken;
|
||||
}
|
||||
|
||||
/** Clear loopback runtime only when the owning token matches the active runtime. */
|
||||
export function clearActiveMcpLoopbackRuntimeByOwnerToken(ownerToken: string): void {
|
||||
if (activeRuntime?.ownerToken === ownerToken) {
|
||||
activeRuntime = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/** Build the MCP server config injected into agents for loopback tool access. */
|
||||
export function createMcpLoopbackServerConfig(port: number) {
|
||||
return {
|
||||
mcpServers: {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// OpenAI-compatible `/v1/models` HTTP route backed by configured OpenClaw agents.
|
||||
import type { IncomingMessage, ServerResponse } from "node:http";
|
||||
import { listAgentIds, resolveDefaultAgentId } from "../agents/agent-scope.js";
|
||||
import { getRuntimeConfig } from "../config/io.js";
|
||||
@@ -74,6 +75,7 @@ function resolveRequestPath(req: IncomingMessage): string {
|
||||
return new URL(req.url ?? "/", "http://localhost").pathname;
|
||||
}
|
||||
|
||||
/** Handle OpenAI-compatible model list/detail requests, returning false for unrelated paths. */
|
||||
export async function handleOpenAiModelsHttpRequest(
|
||||
req: IncomingMessage,
|
||||
res: ServerResponse,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// Capability-token helpers for plugin-hosted node surfaces.
|
||||
import { randomBytes } from "node:crypto";
|
||||
import {
|
||||
asDateTimestampMs,
|
||||
@@ -7,22 +8,27 @@ import {
|
||||
} from "@openclaw/normalization-core/number-coercion";
|
||||
import { safeEqualSecret } from "../security/secret-equal.js";
|
||||
|
||||
/** Path marker used to scope plugin-hosted node URLs with one-time capabilities. */
|
||||
export const PLUGIN_NODE_CAPABILITY_PATH_PREFIX = "/__openclaw__/cap";
|
||||
const PLUGIN_NODE_CAPABILITY_QUERY_PARAM = "oc_cap";
|
||||
/** Default lifetime for plugin-node capability tokens. */
|
||||
export const DEFAULT_PLUGIN_NODE_CAPABILITY_TTL_MS = 10 * 60_000;
|
||||
|
||||
/** Declared plugin surface that may receive scoped node capabilities. */
|
||||
export type PluginNodeCapabilitySurface = {
|
||||
surface: string;
|
||||
ttlMs?: number;
|
||||
scopeKey?: string;
|
||||
};
|
||||
|
||||
/** Client-side storage for surface URLs and minted plugin-node capabilities. */
|
||||
export type PluginNodeCapabilityClient = {
|
||||
pluginSurfaceUrls?: Record<string, string>;
|
||||
pluginNodeCapabilitySurfaces?: Record<string, PluginNodeCapabilitySurface>;
|
||||
pluginNodeCapabilities?: Record<string, { capability: string; expiresAtMs: number }>;
|
||||
};
|
||||
|
||||
/** Index surfaces by normalized surface id, keeping the strictest TTL per surface. */
|
||||
export function indexPluginNodeCapabilitySurfaces(
|
||||
surfaces: readonly PluginNodeCapabilitySurface[],
|
||||
): Record<string, PluginNodeCapabilitySurface> {
|
||||
@@ -44,6 +50,7 @@ export function indexPluginNodeCapabilitySurfaces(
|
||||
return indexed;
|
||||
}
|
||||
|
||||
/** Parsed URL details after extracting path/query capability tokens. */
|
||||
export type NormalizedPluginNodeCapabilityUrl = {
|
||||
pathname: string;
|
||||
capability?: string;
|
||||
@@ -71,10 +78,12 @@ function resolvePluginNodeCapabilityStorageKey(surface: PluginNodeCapabilitySurf
|
||||
return scopeKey ? `${normalizedSurface}\0${scopeKey}` : normalizedSurface;
|
||||
}
|
||||
|
||||
/** Resolve a positive TTL for a plugin-node capability surface. */
|
||||
export function resolvePluginNodeCapabilityTtlMs(surface: PluginNodeCapabilitySurface) {
|
||||
return asPositiveSafeInteger(surface.ttlMs) ?? DEFAULT_PLUGIN_NODE_CAPABILITY_TTL_MS;
|
||||
}
|
||||
|
||||
/** Resolve the expiration timestamp for a capability minted against a surface. */
|
||||
export function resolvePluginNodeCapabilityExpiresAtMs(
|
||||
surface: PluginNodeCapabilitySurface,
|
||||
nowMs: number = Date.now(),
|
||||
@@ -82,10 +91,12 @@ export function resolvePluginNodeCapabilityExpiresAtMs(
|
||||
return resolveExpiresAtMsFromDurationMs(resolvePluginNodeCapabilityTtlMs(surface), { nowMs });
|
||||
}
|
||||
|
||||
/** Mint an opaque capability token for plugin-node surface access. */
|
||||
export function mintPluginNodeCapabilityToken(): string {
|
||||
return randomBytes(18).toString("base64url");
|
||||
}
|
||||
|
||||
/** Append a capability path segment to a plugin host URL. */
|
||||
export function buildPluginNodeCapabilityScopedHostUrl(
|
||||
baseUrl: string,
|
||||
capability: string,
|
||||
@@ -107,6 +118,7 @@ export function buildPluginNodeCapabilityScopedHostUrl(
|
||||
}
|
||||
}
|
||||
|
||||
/** Replace the capability segment in an already scoped host URL. */
|
||||
export function replacePluginNodeCapabilityInScopedHostUrl(
|
||||
scopedUrl: string,
|
||||
capability: string,
|
||||
@@ -140,6 +152,7 @@ export function replacePluginNodeCapabilityInScopedHostUrl(
|
||||
}
|
||||
}
|
||||
|
||||
/** Parse and rewrite scoped capability URLs into canonical paths plus query tokens. */
|
||||
export function normalizePluginNodeCapabilityScopedUrl(
|
||||
rawUrl: string,
|
||||
): NormalizedPluginNodeCapabilityUrl {
|
||||
@@ -199,6 +212,7 @@ export function normalizePluginNodeCapabilityScopedUrl(
|
||||
};
|
||||
}
|
||||
|
||||
/** Store a minted capability on a client under the surface/scope storage key. */
|
||||
export function setClientPluginNodeCapability(params: {
|
||||
client: PluginNodeCapabilityClient;
|
||||
surface: PluginNodeCapabilitySurface;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// Gateway event subscription wiring for agent, heartbeat, transcript, and lifecycle broadcasts.
|
||||
import { clearAgentRunContext, onAgentEvent } from "../infra/agent-events.js";
|
||||
import { onHeartbeatEvent } from "../infra/heartbeat-events.js";
|
||||
import { onSessionLifecycleEvent } from "../sessions/session-lifecycle-events.js";
|
||||
@@ -10,6 +11,7 @@ import type {
|
||||
ToolEventRecipientRegistry,
|
||||
} from "./server-chat-state.js";
|
||||
|
||||
/** Register gateway runtime event subscriptions and return unsubscribe handles. */
|
||||
export function startGatewayEventSubscriptions(params: {
|
||||
broadcast: (event: string, payload: unknown, opts?: { dropIfSlow?: boolean }) => void;
|
||||
broadcastToConnIds: (
|
||||
@@ -30,6 +32,7 @@ export function startGatewayEventSubscriptions(params: {
|
||||
ReturnType<typeof import("./server-chat.js").createAgentEventHandler>
|
||||
> | null = null;
|
||||
const getAgentEventHandler = () => {
|
||||
// Lazy-load heavy chat modules only after the first agent event reaches the gateway.
|
||||
agentEventHandlerPromise ??= Promise.all([
|
||||
import("./server-chat.js"),
|
||||
import("./server-session-key.js"),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// Gateway HTTP server listen helper with retry and lock-aware errors.
|
||||
import type { Server as HttpServer } from "node:http";
|
||||
import { GatewayLockError } from "../../infra/gateway-lock.js";
|
||||
import { sleep } from "../../utils.js";
|
||||
@@ -15,6 +16,7 @@ async function closeServerQuietly(httpServer: HttpServer): Promise<void> {
|
||||
});
|
||||
}
|
||||
|
||||
/** Listen on the configured gateway host/port, retrying transient EADDRINUSE windows. */
|
||||
export async function listenGatewayHttpServer(params: {
|
||||
httpServer: HttpServer;
|
||||
bindHost: string;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// Gateway readiness checker for channel health and startup sidecar state.
|
||||
import type { ChannelAccountSnapshot } from "../../channels/plugins/types.public.js";
|
||||
import {
|
||||
DEFAULT_CHANNEL_CONNECT_GRACE_MS,
|
||||
@@ -9,6 +10,7 @@ import {
|
||||
import type { ChannelManager } from "../server-channels.js";
|
||||
import type { GatewayEventLoopHealth } from "./event-loop-health.js";
|
||||
|
||||
/** Snapshot returned by the gateway readiness probe. */
|
||||
export type ReadinessResult = {
|
||||
ready: boolean;
|
||||
failing: string[];
|
||||
@@ -16,6 +18,7 @@ export type ReadinessResult = {
|
||||
eventLoop?: GatewayEventLoopHealth;
|
||||
};
|
||||
|
||||
/** Function form used by HTTP readiness endpoints and tests. */
|
||||
export type ReadinessChecker = () => ReadinessResult;
|
||||
|
||||
const DEFAULT_READINESS_CACHE_TTL_MS = 1_000;
|
||||
@@ -33,6 +36,7 @@ function shouldIgnoreReadinessFailure(
|
||||
return health.reason === "not-running" && accountSnapshot.restartPending === true;
|
||||
}
|
||||
|
||||
/** Create a cached readiness checker over channel runtime health. */
|
||||
export function createReadinessChecker(deps: {
|
||||
channelManager: ChannelManager;
|
||||
startedAt: number;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// Session-store key canonicalization across default agents, main aliases, and legacy keys.
|
||||
import {
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeOptionalString,
|
||||
@@ -17,6 +18,7 @@ import {
|
||||
} from "../routing/session-key.js";
|
||||
import { normalizeSessionKeyPreservingOpaquePeerIds } from "../sessions/session-key-utils.js";
|
||||
|
||||
/** Canonicalize an opaque session key into the agent-scoped store namespace. */
|
||||
export function canonicalizeSessionKeyForAgent(agentId: string, key: string): string {
|
||||
const lowered = normalizeLowercaseStringOrEmpty(key);
|
||||
if (lowered === "global" || lowered === "unknown") {
|
||||
@@ -68,6 +70,7 @@ function resolveParsedSessionStoreKey(
|
||||
return { agentId, sessionKey: `agent:${agentId}:${rest}` };
|
||||
}
|
||||
|
||||
/** Resolve any incoming session key into the canonical key used in persisted session stores. */
|
||||
export function resolveSessionStoreKey(params: {
|
||||
cfg: OpenClawConfig;
|
||||
sessionKey: string;
|
||||
@@ -107,6 +110,7 @@ export function resolveSessionStoreKey(params: {
|
||||
return canonicalizeSessionKeyForAgent(agentId, raw);
|
||||
}
|
||||
|
||||
/** Resolve the agent that owns a canonical session-store key. */
|
||||
export function resolveSessionStoreAgentId(cfg: OpenClawConfig, canonicalKey: string): string {
|
||||
if (canonicalKey === "global" || canonicalKey === "unknown") {
|
||||
return resolveDefaultStoreAgentId(cfg);
|
||||
@@ -118,6 +122,7 @@ export function resolveSessionStoreAgentId(cfg: OpenClawConfig, canonicalKey: st
|
||||
return resolveDefaultStoreAgentId(cfg);
|
||||
}
|
||||
|
||||
/** Resolve a session key for lookup inside a specific agent's store. */
|
||||
export function resolveStoredSessionKeyForAgentStore(params: {
|
||||
cfg: OpenClawConfig;
|
||||
agentId: string;
|
||||
@@ -139,6 +144,7 @@ export function resolveStoredSessionKeyForAgentStore(params: {
|
||||
});
|
||||
}
|
||||
|
||||
/** Resolve the owner agent for a stored session key, returning null for global/unknown keys. */
|
||||
export function resolveStoredSessionOwnerAgentId(params: {
|
||||
cfg: OpenClawConfig;
|
||||
agentId: string;
|
||||
@@ -151,6 +157,7 @@ export function resolveStoredSessionOwnerAgentId(params: {
|
||||
return resolveSessionStoreAgentId(params.cfg, canonicalKey);
|
||||
}
|
||||
|
||||
/** Canonicalize spawned-by parent references while preserving main-session aliases. */
|
||||
export function canonicalizeSpawnedByForAgent(
|
||||
cfg: OpenClawConfig,
|
||||
agentId: string,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// Session patch applier for gateway session metadata and model/runtime overrides.
|
||||
import { randomUUID } from "node:crypto";
|
||||
import {
|
||||
normalizeOptionalLowercaseString,
|
||||
@@ -129,6 +130,7 @@ function normalizeSubagentControlScope(raw: string): "children" | "none" | undef
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/** Apply a validated gateway session patch to an in-memory session store entry. */
|
||||
export async function applySessionsPatchToStore(params: {
|
||||
cfg: OpenClawConfig;
|
||||
store: Record<string, SessionEntry>;
|
||||
@@ -161,6 +163,7 @@ export async function applySessionsPatchToStore(params: {
|
||||
};
|
||||
|
||||
const existing = store[storeKey];
|
||||
// Existing entries without session ids are placeholder aliases; assigning an id makes them real.
|
||||
const next: SessionEntry = existing?.sessionId
|
||||
? {
|
||||
...existing,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// HTTP endpoint adapter for invoking gateway tools from OpenAI-compatible clients.
|
||||
import type { IncomingMessage, ServerResponse } from "node:http";
|
||||
import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce";
|
||||
import { normalizeMessageChannel } from "../utils/message-channel.js";
|
||||
@@ -14,6 +15,7 @@ import { invokeGatewayTool, type ToolsInvokeInput } from "./tools-invoke-shared.
|
||||
|
||||
const DEFAULT_BODY_BYTES = 2 * 1024 * 1024;
|
||||
|
||||
/** Handle `/tools/invoke` requests and return false when another HTTP route should handle them. */
|
||||
export async function handleToolsInvokeHttpRequest(
|
||||
req: IncomingMessage,
|
||||
res: ServerResponse,
|
||||
|
||||
Reference in New Issue
Block a user