Download src/meeting-bot/plugin-entry.ts from SaylorTwift/openclaw: direct link, hf CLI and curl.
- Browser
- Download file 16.7 kB
-
https://huggingface.co/SaylorTwift/openclaw/resolve/main/src/meeting-bot/plugin-entry.ts
- Command line
-
hf download hf://SaylorTwift/openclaw/src/meeting-bot/plugin-entry.ts
-
curl -L -o plugin-entry.ts https://huggingface.co/SaylorTwift/openclaw/resolve/main/src/meeting-bot/plugin-entry.ts
16.7 kB
| import { asOptionalRecord } from "@openclaw/normalization-core/record-coerce"; | |
| import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce"; | |
| import type { TObject } from "typebox"; | |
| import { ErrorCodes, errorShape } from "../../packages/gateway-protocol/src/schema/error-codes.js"; | |
| import { | |
| readNonNegativeIntegerParam, | |
| readPositiveIntegerParam, | |
| jsonResult, | |
| } from "../agents/tools/common.js"; | |
| import { callGatewayFromCli } from "../cli/gateway-rpc.js"; | |
| import type { GatewayRequestHandlerOptions } from "../gateway/server-methods/types.js"; | |
| import { formatErrorMessage } from "../infra/errors.js"; | |
| import type { OpenClawPluginApi } from "../plugins/plugin-api.types.js"; | |
| import type { OpenClawPluginConfigSchema } from "../plugins/plugin-config-schema.types.js"; | |
| import type { OpenClawPluginNodeInvokePolicy } from "../plugins/plugin-registration.types.js"; | |
| import { parseAgentSessionKey } from "../sessions/session-key-utils.js"; | |
| import type { MeetingPluginConfig as SharedMeetingPluginConfig } from "./plugin-config.js"; | |
| import type { MeetingPluginJoinRequest } from "./session-types.js"; | |
| import { | |
| createMeetingTranscriptSourceProvider, | |
| type MeetingTranscriptSourceRuntime, | |
| } from "./transcripts-bridge.js"; | |
| type MeetingToolAction = "join" | "leave" | "status" | "transcript" | "speak"; | |
| type MeetingMode = "agent" | "bidi" | "transcribe"; | |
| type MeetingTransport = "chrome" | "chrome-node"; | |
| export type MeetingJoinRequest = MeetingPluginJoinRequest<MeetingTransport, MeetingMode>; | |
| export type MeetingPluginConfig = Pick<SharedMeetingPluginConfig, "enabled" | "chromeNode">; | |
| export type MeetingPluginRuntime<Request extends MeetingJoinRequest> = | |
| Partial<MeetingTranscriptSourceRuntime> & { | |
| join(request: Request): Promise<unknown>; | |
| leave(sessionId: string): Promise<unknown>; | |
| ownsSession(agentId: string, sessionId: string): boolean; | |
| setupStatus(params: { mode?: MeetingMode; transport?: MeetingTransport }): Promise<unknown>; | |
| speak(sessionId: string, message?: string): Promise<unknown>; | |
| status(sessionId?: string): Promise<unknown>; | |
| statusForAgent(agentId: string, sessionId?: string): Promise<unknown>; | |
| testListen(request: Request): Promise<unknown>; | |
| testSpeech(request: Request): Promise<unknown>; | |
| transcript(sessionId: string, options: { sinceIndex?: number }): Promise<unknown>; | |
| }; | |
| export type MeetingPluginEntryOptions< | |
| Config extends MeetingPluginConfig, | |
| Request extends MeetingJoinRequest, | |
| Runtime extends MeetingPluginRuntime<Request>, | |
| > = { | |
| cap: string; | |
| configSchema: OpenClawPluginConfigSchema & { parse(value: unknown): Config }; | |
| createNodePolicy(config: Config): OpenClawPluginNodeInvokePolicy; | |
| createRuntime(params: { api: OpenClawPluginApi; config: Config }): Runtime; | |
| description: string; | |
| disabledMessage: string; | |
| gatewayMethodPrefix: string; | |
| id: string; | |
| invalidRequest(message: string): Error; | |
| isInvalidRequest(error: unknown): boolean; | |
| name: string; | |
| nodeCommand: string; | |
| nodeHandler(paramsJSON?: string | null): Promise<string>; | |
| normalizeRequesterSessionKey(value: unknown, trustedOwner: boolean): string | undefined; | |
| normalizeToolAgentId(agentId: string | undefined): string | undefined; | |
| normalizeUrl(url: string): string; | |
| registerCli(api: OpenClawPluginApi, config: Config): void; | |
| registerNodeWhen(config: Config): boolean; | |
| resolveGatewayTimeoutMs(config: Config): number; | |
| resolveToolRuntime( | |
| api: OpenClawPluginApi, | |
| agentId: string | undefined, | |
| ): Promise<OpenClawPluginApi["runtime"] | undefined>; | |
| toolDescription: string; | |
| toolLabel: string; | |
| toolName: string; | |
| toolParameters: TObject; | |
| transcriptSource?: { | |
| id: string; | |
| aliases?: readonly string[]; | |
| name: string; | |
| }; | |
| unknownActionMessage: string; | |
| }; | |
| function readErrorDetails(error: unknown): unknown { | |
| return error && typeof error === "object" && "details" in error | |
| ? (error as { details?: unknown }).details | |
| : undefined; | |
| } | |
| export function createMeetingPluginEntryOptions< | |
| Config extends MeetingPluginConfig, | |
| Request extends MeetingJoinRequest, | |
| Runtime extends MeetingPluginRuntime<Request>, | |
| >(options: MeetingPluginEntryOptions<Config, Request, Runtime>) { | |
| const invalidRequest = (message: string): never => { | |
| throw options.invalidRequest(message); | |
| }; | |
| const normalizeTransport = (value: unknown): MeetingTransport | undefined => { | |
| if (value === undefined) { | |
| return undefined; | |
| } | |
| if (value === "chrome" || value === "chrome-node") { | |
| return value; | |
| } | |
| return invalidRequest("transport must be chrome or chrome-node"); | |
| }; | |
| const normalizeMode = (value: unknown): MeetingMode | undefined => { | |
| if (value === undefined) { | |
| return undefined; | |
| } | |
| if (value === "agent" || value === "bidi" || value === "transcribe") { | |
| return value; | |
| } | |
| return invalidRequest("mode must be agent, bidi, or transcribe"); | |
| }; | |
| const requireString = (value: unknown, name: string): string => { | |
| const normalized = normalizeOptionalString(value); | |
| return normalized ?? invalidRequest(`${name} required`); | |
| }; | |
| const readSinceIndex = (raw: Record<string, unknown>): number | undefined => { | |
| try { | |
| return readNonNegativeIntegerParam(raw, "sinceIndex"); | |
| } catch (error) { | |
| return invalidRequest(formatErrorMessage(error)); | |
| } | |
| }; | |
| const keepTrustedToolContext = ( | |
| raw: Record<string, unknown>, | |
| client: GatewayRequestHandlerOptions["client"], | |
| ): Record<string, unknown> => { | |
| const { agentId: rawAgentId, requesterSessionKey: rawRequesterSessionKey, ...rest } = raw; | |
| const trustedOwner = client?.internal?.pluginRuntimeOwnerId === options.id; | |
| const agentId = trustedOwner ? normalizeOptionalString(rawAgentId) : undefined; | |
| const requesterSessionKey = options.normalizeRequesterSessionKey( | |
| rawRequesterSessionKey, | |
| trustedOwner, | |
| ); | |
| return { | |
| ...rest, | |
| ...(agentId ? { agentId } : {}), | |
| ...(requesterSessionKey ? { requesterSessionKey } : {}), | |
| }; | |
| }; | |
| const trustedToolAgentId = ( | |
| raw: Record<string, unknown>, | |
| client: GatewayRequestHandlerOptions["client"], | |
| ) => normalizeOptionalString(keepTrustedToolContext(raw, client).agentId); | |
| const joinRequest = ( | |
| raw: Record<string, unknown>, | |
| joinOptions?: { allowTimeout?: boolean }, | |
| ): Request => { | |
| if (!joinOptions?.allowTimeout && raw.timeoutMs !== undefined) { | |
| return invalidRequest("timeoutMs is supported only by testSpeech or testListen"); | |
| } | |
| try { | |
| return { | |
| url: options.normalizeUrl(requireString(raw.url, "url")), | |
| transport: normalizeTransport(raw.transport), | |
| mode: normalizeMode(raw.mode), | |
| message: normalizeOptionalString(raw.message), | |
| requesterSessionKey: normalizeOptionalString(raw.requesterSessionKey), | |
| agentId: normalizeOptionalString(raw.agentId), | |
| timeoutMs: readPositiveIntegerParam(raw, "timeoutMs"), | |
| } as Request; | |
| } catch (error) { | |
| if (options.isInvalidRequest(error)) { | |
| throw error; | |
| } | |
| return invalidRequest(formatErrorMessage(error)); | |
| } | |
| }; | |
| const gatewayMethod = (action: MeetingToolAction) => `${options.gatewayMethodPrefix}.${action}`; | |
| const callGatewayFromTool = async (params: { | |
| action: MeetingToolAction; | |
| config: Config; | |
| raw: Record<string, unknown>; | |
| runtime?: OpenClawPluginApi["runtime"]; | |
| }) => { | |
| try { | |
| const timeoutMs = options.resolveGatewayTimeoutMs(params.config); | |
| if (params.runtime) { | |
| return await params.runtime.gateway.request(gatewayMethod(params.action), params.raw, { | |
| timeoutMs, | |
| scopes: ["operator.admin"], | |
| }); | |
| } | |
| return await callGatewayFromCli( | |
| gatewayMethod(params.action), | |
| { json: true, timeout: String(timeoutMs) }, | |
| params.raw, | |
| { progress: false, scopes: ["operator.admin"] }, | |
| ); | |
| } catch (error) { | |
| const details = readErrorDetails(error); | |
| if (details && typeof details === "object") { | |
| return details; | |
| } | |
| throw error; | |
| } | |
| }; | |
| return { | |
| id: options.id, | |
| name: options.name, | |
| description: options.description, | |
| configSchema: options.configSchema, | |
| register(api: OpenClawPluginApi) { | |
| const config = options.configSchema.parse(api.pluginConfig) as Config; | |
| let runtime: Runtime | undefined; | |
| const ensureRuntime = async () => { | |
| if (!config.enabled) { | |
| throw new Error(options.disabledMessage); | |
| } | |
| runtime ??= options.createRuntime({ api, config }); | |
| return runtime; | |
| }; | |
| if (options.transcriptSource) { | |
| api.registerTranscriptSourceProvider( | |
| createMeetingTranscriptSourceProvider({ | |
| ...options.transcriptSource, | |
| runtime: async () => { | |
| const resolved = await ensureRuntime(); | |
| if (!resolved.startTranscriptSource || !resolved.stopTranscriptSource) { | |
| throw new Error(`${options.name} transcript source runtime is unavailable`); | |
| } | |
| return resolved as MeetingTranscriptSourceRuntime; | |
| }, | |
| }), | |
| ); | |
| } | |
| const sendError = ( | |
| respond: GatewayRequestHandlerOptions["respond"], | |
| error: unknown, | |
| code: Parameters<typeof errorShape>[0] = ErrorCodes.UNAVAILABLE, | |
| ) => { | |
| const payload = { error: formatErrorMessage(error) }; | |
| respond(false, payload, errorShape(code, payload.error, { details: payload })); | |
| }; | |
| const sendRequestError = (respond: GatewayRequestHandlerOptions["respond"], error: unknown) => | |
| sendError( | |
| respond, | |
| error, | |
| options.isInvalidRequest(error) ? ErrorCodes.INVALID_REQUEST : ErrorCodes.UNAVAILABLE, | |
| ); | |
| api.registerGatewayMethod( | |
| `${options.gatewayMethodPrefix}.join`, | |
| async ({ params, client, respond }: GatewayRequestHandlerOptions) => { | |
| try { | |
| const raw = keepTrustedToolContext(asOptionalRecord(params) ?? {}, client); | |
| respond(true, await (await ensureRuntime()).join(joinRequest(raw))); | |
| } catch (error) { | |
| sendRequestError(respond, error); | |
| } | |
| }, | |
| ); | |
| api.registerGatewayMethod( | |
| `${options.gatewayMethodPrefix}.leave`, | |
| async ({ params, client, respond }: GatewayRequestHandlerOptions) => { | |
| try { | |
| const raw = asOptionalRecord(params) ?? {}; | |
| const agentId = trustedToolAgentId(raw, client); | |
| const sessionId = requireString(raw.sessionId, "sessionId"); | |
| const rt = await ensureRuntime(); | |
| respond( | |
| true, | |
| agentId && !rt.ownsSession(agentId, sessionId) | |
| ? { found: false } | |
| : await rt.leave(sessionId), | |
| ); | |
| } catch (error) { | |
| sendRequestError(respond, error); | |
| } | |
| }, | |
| ); | |
| api.registerGatewayMethod( | |
| `${options.gatewayMethodPrefix}.status`, | |
| async ({ params, client, respond }: GatewayRequestHandlerOptions) => { | |
| try { | |
| const raw = asOptionalRecord(params) ?? {}; | |
| const agentId = trustedToolAgentId(raw, client); | |
| const rt = await ensureRuntime(); | |
| respond( | |
| true, | |
| agentId | |
| ? await rt.statusForAgent(agentId, normalizeOptionalString(raw.sessionId)) | |
| : await rt.status(normalizeOptionalString(raw.sessionId)), | |
| ); | |
| } catch (error) { | |
| sendRequestError(respond, error); | |
| } | |
| }, | |
| ); | |
| api.registerGatewayMethod( | |
| `${options.gatewayMethodPrefix}.transcript`, | |
| async ({ params, client, respond }: GatewayRequestHandlerOptions) => { | |
| try { | |
| const raw = asOptionalRecord(params) ?? {}; | |
| const sessionId = requireString(raw.sessionId, "sessionId"); | |
| const sinceIndex = readSinceIndex(raw); | |
| const agentId = trustedToolAgentId(raw, client); | |
| const rt = await ensureRuntime(); | |
| respond( | |
| true, | |
| agentId && !rt.ownsSession(agentId, sessionId) | |
| ? { found: false } | |
| : await rt.transcript(sessionId, sinceIndex === undefined ? {} : { sinceIndex }), | |
| ); | |
| } catch (error) { | |
| sendRequestError(respond, error); | |
| } | |
| }, | |
| ); | |
| api.registerGatewayMethod( | |
| `${options.gatewayMethodPrefix}.speak`, | |
| async ({ params, client, respond }: GatewayRequestHandlerOptions) => { | |
| try { | |
| const raw = asOptionalRecord(params) ?? {}; | |
| const sessionId = requireString(raw.sessionId, "sessionId"); | |
| const agentId = trustedToolAgentId(raw, client); | |
| const rt = await ensureRuntime(); | |
| respond( | |
| true, | |
| agentId && !rt.ownsSession(agentId, sessionId) | |
| ? { found: false, spoken: false } | |
| : await rt.speak(sessionId, normalizeOptionalString(raw.message)), | |
| ); | |
| } catch (error) { | |
| sendRequestError(respond, error); | |
| } | |
| }, | |
| ); | |
| api.registerGatewayMethod( | |
| `${options.gatewayMethodPrefix}.setup`, | |
| async ({ params, respond }: GatewayRequestHandlerOptions) => { | |
| try { | |
| respond( | |
| true, | |
| await ( | |
| await ensureRuntime() | |
| ).setupStatus({ | |
| mode: normalizeMode(params?.mode), | |
| transport: normalizeTransport(params?.transport), | |
| }), | |
| ); | |
| } catch (error) { | |
| sendRequestError(respond, error); | |
| } | |
| }, | |
| ); | |
| for (const [method, run] of [ | |
| [ | |
| `${options.gatewayMethodPrefix}.testSpeech`, | |
| (rt: Runtime, raw: Record<string, unknown>) => | |
| rt.testSpeech(joinRequest(raw, { allowTimeout: true })), | |
| ], | |
| [ | |
| `${options.gatewayMethodPrefix}.testListen`, | |
| (rt: Runtime, raw: Record<string, unknown>) => | |
| rt.testListen(joinRequest(raw, { allowTimeout: true })), | |
| ], | |
| ] as const) { | |
| api.registerGatewayMethod( | |
| method, | |
| async ({ params, client, respond }: GatewayRequestHandlerOptions) => { | |
| try { | |
| const raw = keepTrustedToolContext(asOptionalRecord(params) ?? {}, client); | |
| respond(true, await run(await ensureRuntime(), raw)); | |
| } catch (error) { | |
| sendRequestError(respond, error); | |
| } | |
| }, | |
| ); | |
| } | |
| api.registerTool( | |
| (toolContext) => ({ | |
| name: options.toolName, | |
| label: options.toolLabel, | |
| description: options.toolDescription, | |
| parameters: options.toolParameters, | |
| async execute(_toolCallId, params) { | |
| const raw = asOptionalRecord(params) ?? {}; | |
| const action = raw.action as MeetingToolAction; | |
| const requesterSessionKey = normalizeOptionalString(toolContext.sessionKey); | |
| const contextAgentId = | |
| toolContext.agentId ?? parseAgentSessionKey(requesterSessionKey)?.agentId; | |
| const agentId = options.normalizeToolAgentId(contextAgentId); | |
| try { | |
| if (!(["join", "leave", "status", "transcript", "speak"] as const).includes(action)) { | |
| throw new Error(options.unknownActionMessage); | |
| } | |
| const runtimeForTool = await options.resolveToolRuntime(api, agentId); | |
| return jsonResult( | |
| await callGatewayFromTool({ | |
| action, | |
| config, | |
| raw: { | |
| ...raw, | |
| ...(requesterSessionKey ? { requesterSessionKey } : {}), | |
| ...(runtimeForTool && agentId ? { agentId } : {}), | |
| }, | |
| runtime: runtimeForTool, | |
| }), | |
| ); | |
| } catch (error) { | |
| return jsonResult({ error: formatErrorMessage(error) }); | |
| } | |
| }, | |
| }), | |
| { name: options.toolName }, | |
| ); | |
| if (options.registerNodeWhen(config)) { | |
| api.registerNodeHostCommand({ | |
| command: options.nodeCommand, | |
| cap: options.cap, | |
| dangerous: true, | |
| handle: (paramsJSON) => options.nodeHandler(paramsJSON), | |
| }); | |
| api.registerNodeInvokePolicy(options.createNodePolicy(config)); | |
| } | |
| options.registerCli(api, config); | |
| }, | |
| }; | |
| } | |