import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime"; import type { GatewayRequestHandlerOptions } from "openclaw/plugin-sdk/gateway-runtime"; import { definePluginEntry, type OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry"; import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime"; import { Type } from "typebox"; import {
resolveGoogleMeetConfig,
type GoogleMeetConfig,
type GoogleMeetMode,
type GoogleMeetTransport,
} from "./src/config.js"; import {
buildGoogleMeetPreflightReport,
fetchGoogleMeetArtifacts,
fetchGoogleMeetAttendance,
fetchLatestGoogleMeetConferenceRecord,
fetchGoogleMeetSpace,
} from "./src/meet.js"; import { handleGoogleMeetNodeHostCommand } from "./src/node-host.js"; import { GoogleMeetRuntime } from "./src/runtime.js"; import { isGoogleMeetBrowserManualActionError } from "./src/transports/chrome-create.js";
const googleMeetConfigSchema = {
parse(value: unknown) { return resolveGoogleMeetConfig(value);
},
uiHints: { "defaults.meeting": {
label: "Default Meeting",
help: "Meet URL, meeting code, or spaces/{id} used when CLI commands omit a meeting.",
}, "preview.enrollmentAcknowledged": {
label: "Preview Acknowledged",
help: "Confirms you understand the Google Meet Media API is still Developer Preview.",
advanced: true,
},
defaultTransport: {
label: "Default Transport",
help: "Chrome uses a signed-in browser profile. Chrome-node runs Chrome on a paired node. Twilio uses Meet dial-in numbers.",
},
defaultMode: {
label: "Default Mode",
help: "Realtime starts the duplex voice model loop. Transcribe joins/observes without the realtime talk-back bridge.",
}, "chrome.audioBackend": {
label: "Chrome Audio Backend",
help: "BlackHole 2ch is required for local duplex audio routing.",
}, "chrome.launch": { label: "Launch Chrome" }, "chrome.browserProfile": { label: "Chrome Profile", advanced: true }, "chrome.guestName": {
label: "Guest Name",
help: "Used when Chrome lands on the signed-out Meet guest-name screen.",
}, "chrome.reuseExistingTab": {
label: "Reuse Existing Meet Tab",
help: "Avoids opening duplicate tabs for the same Meet URL.",
}, "chrome.autoJoin": {
label: "Auto Join Guest Screen",
help: "Best-effort guest-name fill and Join Now click through OpenClaw browser automation.",
}, "chrome.waitForInCallMs": {
label: "Wait For In-Call (ms)",
help: "Waits for Chrome to report that the Meet tab is in-call before the realtime intro speaks.",
advanced: true,
}, "chrome.audioInputCommand": {
label: "Audio Input Command",
help: "Command that writes 8 kHz G.711 mu-law meeting audio to stdout.",
advanced: true,
}, "chrome.audioOutputCommand": {
label: "Audio Output Command",
help: "Command that reads 8 kHz G.711 mu-law assistant audio from stdin.",
advanced: true,
}, "chrome.audioBridgeCommand": { label: "Audio Bridge Command", advanced: true }, "chrome.audioBridgeHealthCommand": {
label: "Audio Bridge Health Command",
advanced: true,
}, "chromeNode.node": {
label: "Chrome Node",
help: "Node id/name/IP that owns Chrome, BlackHole, and SoX for chrome-node transport.",
advanced: true,
}, "twilio.defaultDialInNumber": {
label: "Default Dial-In Number",
placeholder: "+15551234567",
}, "twilio.defaultPin": { label: "Default PIN", advanced: true }, "twilio.defaultDtmfSequence": { label: "Default DTMF Sequence", advanced: true }, "voiceCall.enabled": { label: "Delegate To Voice Call" }, "voiceCall.gatewayUrl": { label: "Voice Call Gateway URL", advanced: true }, "voiceCall.token": {
label: "Voice Call Gateway Token",
sensitive: true,
advanced: true,
}, "voiceCall.requestTimeoutMs": {
label: "Voice Call Request Timeout (ms)",
advanced: true,
}, "voiceCall.dtmfDelayMs": { label: "DTMF Delay (ms)", advanced: true }, "voiceCall.introMessage": { label: "Voice Call Intro Message", advanced: true }, "realtime.provider": {
label: "Realtime Provider",
help: "Defaults to OpenAI; uses OPENAI_API_KEY when no provider config is set.",
}, "realtime.model": { label: "Realtime Model", advanced: true }, "realtime.instructions": { label: "Realtime Instructions", advanced: true }, "realtime.introMessage": {
label: "Realtime Intro Message",
help: "Spoken once when the realtime bridge is ready. Set to an empty string to join silently.",
}, "realtime.toolPolicy": {
label: "Realtime Tool Policy",
help: "Safe read-only tools are available by default; owner requests can unlock broader tools.",
advanced: true,
}, "oauth.clientId": { label: "OAuth Client ID" }, "oauth.clientSecret": { label: "OAuth Client Secret", sensitive: true }, "oauth.refreshToken": { label: "OAuth Refresh Token", sensitive: true }, "oauth.accessToken": {
label: "Cached Access Token",
sensitive: true,
advanced: true,
}, "oauth.expiresAt": {
label: "Cached Access Token Expiry",
help: "Unix epoch milliseconds used only for the cached access-token fast path.",
advanced: true,
},
},
};
const GoogleMeetToolSchema = Type.Object({
action: Type.String({ enum: [ "join", "create", "status", "setup_status", "resolve_space", "preflight", "latest", "artifacts", "attendance", "recover_current_tab", "leave", "speak", "test_speech",
],
description: "Google Meet action to run. create creates and joins by default; pass join=false to only mint a URL. After a timeout or unclear browser state, call recover_current_tab before retrying join.",
}),
join: Type.Optional(
Type.Boolean({
description: "For action=create, set false to create the URL without joining.",
}),
),
url: Type.Optional(Type.String({ description: "Explicit https://meet.google.com/... URL" })),
transport: Type.Optional(
Type.String({ enum: ["chrome", "chrome-node", "twilio"], description: "Join transport" }),
),
mode: Type.Optional(
Type.String({ enum: ["realtime", "transcribe"],
description: "Join mode. realtime starts live listen/talk-back through the realtime voice model; transcribe joins without the realtime talk-back bridge.",
}),
),
dialInNumber: Type.Optional(Type.String({ description: "Meet dial-in number for Twilio"})),
pin: Type.Optional(Type.String({ description: "Meet phone PIN for Twilio" })),
dtmfSequence: Type.Optional(Type.String({ description: "Explicit DTMF sequence for Twilio" })),
sessionId: Type.Optional(Type.String({ description: "Meet session ID" })),
message: Type.Optional(Type.String({ description: "Realtime instructions to speak now" })),
meeting: Type.Optional(Type.String({ description: "Meet URL, meeting code, or spaces/{id}" })),
conferenceRecord: Type.Optional(
Type.String({ description: "Meet conferenceRecords/{id} resource name or id" }),
),
pageSize: Type.Optional(Type.Number({ description: "Meet API page size for list actions" })),
includeTranscriptEntries: Type.Optional(
Type.Boolean({ description: "For artifacts, include structured transcript entries" }),
),
includeAllConferenceRecords: Type.Optional(
Type.Boolean({
description: "For artifacts or attendance with meeting input, fetch all conference records instead of only the latest.",
}),
),
accessToken: Type.Optional(Type.String({ description: "Access token override" })),
refreshToken: Type.Optional(Type.String({ description: "Refresh token override" })),
clientId: Type.Optional(Type.String({ description: "OAuth client id override" })),
clientSecret: Type.Optional(Type.String({ description: "OAuth client secret override" })),
expiresAt: Type.Optional(Type.Number({ description: "Cached access token expiry ms" })),
});
function normalizeTransport(value: unknown): GoogleMeetTransport | undefined { return value === "chrome" || value === "chrome-node" || value === "twilio" ? value : undefined;
}
function normalizeMode(value: unknown): GoogleMeetMode | undefined { return value === "realtime" || value === "transcribe" ? value : undefined;
}
function resolveMeetingInput(config: GoogleMeetConfig, value: unknown): string { const meeting = normalizeOptionalString(value) ?? config.defaults.meeting; if (!meeting) { thrownew Error("Meeting input is required");
} return meeting;
}
function resolveOptionalPositiveInteger(value: unknown): number | undefined { if (value === undefined) { return undefined;
} const parsed = typeof value === "number" ? value : Number(normalizeOptionalString(value)); if (!Number.isInteger(parsed) || parsed <= 0) { thrownew Error("Expected pageSize to be a positive integer");
} return parsed;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.