mirror of https://github.com/openclaw/openclaw.git
204 lines
7.2 KiB
TypeScript
204 lines
7.2 KiB
TypeScript
import { resolveStateDir } from "../../config/paths.js";
|
|
import {
|
|
listRuntimeImageGenerationProviders,
|
|
generateImage,
|
|
} from "../../image-generation/runtime.js";
|
|
import { resolveGlobalSingleton } from "../../shared/global-singleton.js";
|
|
import {
|
|
createLazyRuntimeMethod,
|
|
createLazyRuntimeMethodBinder,
|
|
createLazyRuntimeModule,
|
|
} from "../../shared/lazy-runtime.js";
|
|
import { VERSION } from "../../version.js";
|
|
import { listWebSearchProviders, runWebSearch } from "../../web-search/runtime.js";
|
|
import { createRuntimeAgent } from "./runtime-agent.js";
|
|
import { defineCachedValue } from "./runtime-cache.js";
|
|
import { createRuntimeChannel } from "./runtime-channel.js";
|
|
import { createRuntimeConfig } from "./runtime-config.js";
|
|
import { createRuntimeEvents } from "./runtime-events.js";
|
|
import { createRuntimeLogging } from "./runtime-logging.js";
|
|
import { createRuntimeMedia } from "./runtime-media.js";
|
|
import { createRuntimeSystem } from "./runtime-system.js";
|
|
import type { PluginRuntime } from "./types.js";
|
|
|
|
const loadTtsRuntime = createLazyRuntimeModule(() => import("./runtime-tts.runtime.js"));
|
|
const loadMediaUnderstandingRuntime = createLazyRuntimeModule(
|
|
() => import("./runtime-media-understanding.runtime.js"),
|
|
);
|
|
const loadModelAuthRuntime = createLazyRuntimeModule(
|
|
() => import("./runtime-model-auth.runtime.js"),
|
|
);
|
|
|
|
function createRuntimeTts(): PluginRuntime["tts"] {
|
|
const bindTtsRuntime = createLazyRuntimeMethodBinder(loadTtsRuntime);
|
|
return {
|
|
textToSpeech: bindTtsRuntime((runtime) => runtime.textToSpeech),
|
|
textToSpeechTelephony: bindTtsRuntime((runtime) => runtime.textToSpeechTelephony),
|
|
listVoices: bindTtsRuntime((runtime) => runtime.listSpeechVoices),
|
|
};
|
|
}
|
|
|
|
function createRuntimeMediaUnderstandingFacade(): PluginRuntime["mediaUnderstanding"] {
|
|
const bindMediaUnderstandingRuntime = createLazyRuntimeMethodBinder(
|
|
loadMediaUnderstandingRuntime,
|
|
);
|
|
return {
|
|
runFile: bindMediaUnderstandingRuntime((runtime) => runtime.runMediaUnderstandingFile),
|
|
describeImageFile: bindMediaUnderstandingRuntime((runtime) => runtime.describeImageFile),
|
|
describeImageFileWithModel: bindMediaUnderstandingRuntime(
|
|
(runtime) => runtime.describeImageFileWithModel,
|
|
),
|
|
describeVideoFile: bindMediaUnderstandingRuntime((runtime) => runtime.describeVideoFile),
|
|
transcribeAudioFile: bindMediaUnderstandingRuntime((runtime) => runtime.transcribeAudioFile),
|
|
};
|
|
}
|
|
|
|
function createRuntimeModelAuth(): PluginRuntime["modelAuth"] {
|
|
const getApiKeyForModel = createLazyRuntimeMethod(
|
|
loadModelAuthRuntime,
|
|
(runtime) => runtime.getApiKeyForModel,
|
|
);
|
|
const resolveApiKeyForProvider = createLazyRuntimeMethod(
|
|
loadModelAuthRuntime,
|
|
(runtime) => runtime.resolveApiKeyForProvider,
|
|
);
|
|
return {
|
|
getApiKeyForModel: (params) =>
|
|
getApiKeyForModel({
|
|
model: params.model,
|
|
cfg: params.cfg,
|
|
}),
|
|
resolveApiKeyForProvider: (params) =>
|
|
resolveApiKeyForProvider({
|
|
provider: params.provider,
|
|
cfg: params.cfg,
|
|
}),
|
|
};
|
|
}
|
|
|
|
function createUnavailableSubagentRuntime(): PluginRuntime["subagent"] {
|
|
const unavailable = () => {
|
|
throw new Error("Plugin runtime subagent methods are only available during a gateway request.");
|
|
};
|
|
return {
|
|
run: unavailable,
|
|
waitForRun: unavailable,
|
|
getSessionMessages: unavailable,
|
|
getSession: unavailable,
|
|
deleteSession: unavailable,
|
|
};
|
|
}
|
|
|
|
// ── Process-global gateway subagent runtime ─────────────────────────
|
|
// The gateway creates a real subagent runtime during startup, but gateway-owned
|
|
// plugin registries may be loaded (and cached) before the gateway path runs.
|
|
// A process-global holder lets explicitly gateway-bindable runtimes resolve the
|
|
// active gateway subagent dynamically without changing the default behavior for
|
|
// ordinary plugin runtimes.
|
|
|
|
const GATEWAY_SUBAGENT_SYMBOL: unique symbol = Symbol.for(
|
|
"openclaw.plugin.gatewaySubagentRuntime",
|
|
) as unknown as typeof GATEWAY_SUBAGENT_SYMBOL;
|
|
|
|
type GatewaySubagentState = {
|
|
subagent: PluginRuntime["subagent"] | undefined;
|
|
};
|
|
|
|
const gatewaySubagentState = resolveGlobalSingleton<GatewaySubagentState>(
|
|
GATEWAY_SUBAGENT_SYMBOL,
|
|
() => ({
|
|
subagent: undefined,
|
|
}),
|
|
);
|
|
|
|
/**
|
|
* Set the process-global gateway subagent runtime.
|
|
* Called during gateway startup so that gateway-bindable plugin runtimes can
|
|
* resolve subagent methods dynamically even when their registry was cached
|
|
* before the gateway finished loading plugins.
|
|
*/
|
|
export function setGatewaySubagentRuntime(subagent: PluginRuntime["subagent"]): void {
|
|
gatewaySubagentState.subagent = subagent;
|
|
}
|
|
|
|
/**
|
|
* Reset the process-global gateway subagent runtime.
|
|
* Used by tests to avoid leaking gateway state across module reloads.
|
|
*/
|
|
export function clearGatewaySubagentRuntime(): void {
|
|
gatewaySubagentState.subagent = undefined;
|
|
}
|
|
|
|
/**
|
|
* Create a late-binding subagent that resolves to:
|
|
* 1. An explicitly provided subagent (from runtimeOptions), OR
|
|
* 2. The process-global gateway subagent when the caller explicitly opts in, OR
|
|
* 3. The unavailable fallback (throws with a clear error message).
|
|
*/
|
|
function createLateBindingSubagent(
|
|
explicit?: PluginRuntime["subagent"],
|
|
allowGatewaySubagentBinding = false,
|
|
): PluginRuntime["subagent"] {
|
|
if (explicit) {
|
|
return explicit;
|
|
}
|
|
|
|
const unavailable = createUnavailableSubagentRuntime();
|
|
if (!allowGatewaySubagentBinding) {
|
|
return unavailable;
|
|
}
|
|
|
|
return new Proxy(unavailable, {
|
|
get(_target, prop, _receiver) {
|
|
const resolved = gatewaySubagentState.subagent ?? unavailable;
|
|
return Reflect.get(resolved, prop, resolved);
|
|
},
|
|
});
|
|
}
|
|
|
|
export type CreatePluginRuntimeOptions = {
|
|
subagent?: PluginRuntime["subagent"];
|
|
allowGatewaySubagentBinding?: boolean;
|
|
};
|
|
|
|
export function createPluginRuntime(_options: CreatePluginRuntimeOptions = {}): PluginRuntime {
|
|
const mediaUnderstanding = createRuntimeMediaUnderstandingFacade();
|
|
const runtime = {
|
|
// Sourced from the shared OpenClaw version resolver (#52899) so plugins
|
|
// always see the same version the CLI reports, avoiding API-version drift.
|
|
version: VERSION,
|
|
config: createRuntimeConfig(),
|
|
agent: createRuntimeAgent(),
|
|
subagent: createLateBindingSubagent(
|
|
_options.subagent,
|
|
_options.allowGatewaySubagentBinding === true,
|
|
),
|
|
system: createRuntimeSystem(),
|
|
media: createRuntimeMedia(),
|
|
imageGeneration: {
|
|
generate: generateImage,
|
|
listProviders: listRuntimeImageGenerationProviders,
|
|
},
|
|
webSearch: {
|
|
listProviders: listWebSearchProviders,
|
|
search: runWebSearch,
|
|
},
|
|
channel: createRuntimeChannel(),
|
|
events: createRuntimeEvents(),
|
|
logging: createRuntimeLogging(),
|
|
state: { resolveStateDir },
|
|
} satisfies Omit<PluginRuntime, "tts" | "mediaUnderstanding" | "stt" | "modelAuth"> &
|
|
Partial<Pick<PluginRuntime, "tts" | "mediaUnderstanding" | "stt" | "modelAuth">>;
|
|
|
|
defineCachedValue(runtime, "tts", createRuntimeTts);
|
|
defineCachedValue(runtime, "mediaUnderstanding", () => mediaUnderstanding);
|
|
defineCachedValue(runtime, "stt", () => ({
|
|
transcribeAudioFile: mediaUnderstanding.transcribeAudioFile,
|
|
}));
|
|
defineCachedValue(runtime, "modelAuth", createRuntimeModelAuth);
|
|
|
|
return runtime as PluginRuntime;
|
|
}
|
|
|
|
export type { PluginRuntime } from "./types.js";
|