feat(plugins): simplify google meet realtime defaults

This commit is contained in:
Peter Steinberger
2026-04-24 03:03:14 +01:00
parent 28299a94ba
commit 0c9659b70c
6 changed files with 200 additions and 95 deletions

View File

@@ -13,6 +13,7 @@ Docs: https://docs.openclaw.ai
- Dependencies/Pi: update bundled Pi packages to `0.70.0`, use Pi's upstream `gpt-5.5` catalog metadata for OpenAI and OpenAI Codex, and keep only local `gpt-5.5-pro` forward-compat handling.
- Models/CLI: split `openclaw models list` row-source orchestration and registry loading into narrower helpers without changing list output behavior. (#70867) Thanks @shakkernerd.
- Plugins/Google Meet: add a bundled participant plugin with personal Google auth, explicit meeting URL joins, Chrome and Twilio transports, and realtime voice support. (#70765) Thanks @steipete.
- Plugins/Google Meet: default Chrome realtime sessions to OpenAI plus SoX `rec`/`play` audio bridge commands, so the usual setup only needs the plugin enabled and `OPENAI_API_KEY`.
- Providers/OpenAI: add image generation and reference-image editing through Codex OAuth, so `openai/gpt-image-2` works without an `OPENAI_API_KEY`. Fixes #70703.
- Providers/OpenRouter: add image generation and reference-image editing through `image_generate`, so OpenRouter image models work with `OPENROUTER_API_KEY`. Fixes #55066 via #67668. Thanks @notamicrodose.
- Image generation: let agents request provider-supported quality and output format hints, and pass OpenAI-specific background, moderation, compression, and user hints through the `image_generate` tool. (#70503) Thanks @ottodeng.

View File

@@ -103,7 +103,15 @@ Workspace Developer Preview Program for Meet media APIs.
## Config
Set config under `plugins.entries.google-meet.config`:
The common Chrome realtime path only needs the plugin enabled, BlackHole, SoX,
and an OpenAI key:
```bash
brew install blackhole-2ch sox
export OPENAI_API_KEY=sk-...
```
Set the plugin config under `plugins.entries.google-meet.config`:
```json5
{
@@ -111,86 +119,58 @@ Set config under `plugins.entries.google-meet.config`:
entries: {
"google-meet": {
enabled: true,
config: {
defaultTransport: "chrome",
defaultMode: "realtime",
defaults: {
meeting: "https://meet.google.com/abc-defg-hij",
},
preview: {
enrollmentAcknowledged: false,
},
chrome: {
audioBackend: "blackhole-2ch",
launch: true,
browserProfile: "Default",
// Command-pair bridge: input writes 8 kHz G.711 mu-law audio to stdout.
audioInputCommand: [
"rec",
"-q",
"-t",
"raw",
"-r",
"8000",
"-c",
"1",
"-e",
"mu-law",
"-b",
"8",
"-",
],
// Output reads 8 kHz G.711 mu-law audio from stdin.
audioOutputCommand: [
"play",
"-q",
"-t",
"raw",
"-r",
"8000",
"-c",
"1",
"-e",
"mu-law",
"-b",
"8",
"-",
],
},
twilio: {
defaultDialInNumber: "+15551234567",
defaultPin: "123456",
},
voiceCall: {
enabled: true,
gatewayUrl: "ws://127.0.0.1:18789",
dtmfDelayMs: 2500,
},
realtime: {
provider: "openai",
model: "gpt-realtime",
instructions: "You are joining a private Google Meet as my OpenClaw agent. Keep replies brief unless asked.",
toolPolicy: "safe-read-only",
providers: {
openai: {
apiKey: { env: "OPENAI_API_KEY" },
},
},
},
auth: {
provider: "google-oauth",
},
oauth: {
clientId: "your-google-oauth-client-id.apps.googleusercontent.com",
refreshToken: "stored-refresh-token",
},
},
config: {},
},
},
},
}
```
Defaults:
- `defaultTransport: "chrome"`
- `defaultMode: "realtime"`
- `chrome.audioBackend: "blackhole-2ch"`
- `chrome.audioInputCommand`: SoX `rec` command writing 8 kHz G.711 mu-law
audio to stdout
- `chrome.audioOutputCommand`: SoX `play` command reading 8 kHz G.711 mu-law
audio from stdin
- `realtime.provider: "openai"`
- `realtime.toolPolicy: "safe-read-only"`
- `realtime.instructions`: brief spoken replies, with
`openclaw_agent_consult` for deeper answers
Optional overrides:
```json5
{
defaults: {
meeting: "https://meet.google.com/abc-defg-hij",
},
chrome: {
browserProfile: "Default",
},
realtime: {
toolPolicy: "owner",
},
}
```
Twilio-only config:
```json5
{
defaultTransport: "twilio",
twilio: {
defaultDialInNumber: "+15551234567",
defaultPin: "123456",
},
voiceCall: {
gatewayUrl: "ws://127.0.0.1:18789",
},
}
```
## Tool
Agents can use the `google_meet` tool:

View File

@@ -113,12 +113,49 @@ describe("google-meet plugin", () => {
preview: { enrollmentAcknowledged: false },
defaultTransport: "chrome",
defaultMode: "realtime",
chrome: { audioBackend: "blackhole-2ch", launch: true },
chrome: {
audioBackend: "blackhole-2ch",
launch: true,
audioInputCommand: [
"rec",
"-q",
"-t",
"raw",
"-r",
"8000",
"-c",
"1",
"-e",
"mu-law",
"-b",
"8",
"-",
],
audioOutputCommand: [
"play",
"-q",
"-t",
"raw",
"-r",
"8000",
"-c",
"1",
"-e",
"mu-law",
"-b",
"8",
"-",
],
},
voiceCall: { enabled: true, requestTimeoutMs: 30000, dtmfDelayMs: 2500 },
realtime: { toolPolicy: "safe-read-only" },
realtime: {
provider: "openai",
toolPolicy: "safe-read-only",
},
oauth: {},
auth: { provider: "google-oauth" },
});
expect(resolveGoogleMeetConfig({}).realtime.instructions).toContain("openclaw_agent_consult");
});
it("uses env fallbacks for OAuth, preview, and default meeting values", () => {

View File

@@ -78,7 +78,7 @@ const googleMeetConfigSchema = {
"voiceCall.introMessage": { label: "Voice Call Intro Message", advanced: true },
"realtime.provider": {
label: "Realtime Provider",
help: "Uses the first registered realtime voice provider when unset.",
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 },

View File

@@ -93,7 +93,7 @@
},
"realtime.provider": {
"label": "Realtime Provider",
"help": "Uses the first registered realtime voice provider when unset."
"help": "Defaults to OpenAI; uses OPENAI_API_KEY when no provider config is set."
},
"realtime.model": {
"label": "Realtime Model",
@@ -157,11 +157,13 @@
},
"defaultTransport": {
"type": "string",
"enum": ["chrome", "twilio"]
"enum": ["chrome", "twilio"],
"default": "chrome"
},
"defaultMode": {
"type": "string",
"enum": ["realtime", "transcribe"]
"enum": ["realtime", "transcribe"],
"default": "realtime"
},
"chrome": {
"type": "object",
@@ -169,25 +171,58 @@
"properties": {
"audioBackend": {
"type": "string",
"enum": ["blackhole-2ch"]
"enum": ["blackhole-2ch"],
"default": "blackhole-2ch"
},
"launch": {
"type": "boolean"
"type": "boolean",
"default": true
},
"browserProfile": {
"type": "string"
},
"joinTimeoutMs": {
"type": "number"
"type": "number",
"default": 30000
},
"audioInputCommand": {
"type": "array",
"default": [
"rec",
"-q",
"-t",
"raw",
"-r",
"8000",
"-c",
"1",
"-e",
"mu-law",
"-b",
"8",
"-"
],
"items": {
"type": "string"
}
},
"audioOutputCommand": {
"type": "array",
"default": [
"play",
"-q",
"-t",
"raw",
"-r",
"8000",
"-c",
"1",
"-e",
"mu-law",
"-b",
"8",
"-"
],
"items": {
"type": "string"
}
@@ -226,7 +261,8 @@
"additionalProperties": false,
"properties": {
"enabled": {
"type": "boolean"
"type": "boolean",
"default": true
},
"gatewayUrl": {
"type": "string"
@@ -235,10 +271,12 @@
"type": "string"
},
"requestTimeoutMs": {
"type": "number"
"type": "number",
"default": 30000
},
"dtmfDelayMs": {
"type": "number"
"type": "number",
"default": 2500
},
"introMessage": {
"type": "string"
@@ -250,17 +288,20 @@
"additionalProperties": false,
"properties": {
"provider": {
"type": "string"
"type": "string",
"default": "openai"
},
"model": {
"type": "string"
},
"instructions": {
"type": "string"
"type": "string",
"default": "You are joining a private Google Meet as an OpenClaw agent. Keep spoken replies brief and natural. When a question needs deeper reasoning, current information, or tools, call openclaw_agent_consult before answering."
},
"toolPolicy": {
"type": "string",
"enum": ["safe-read-only", "owner", "none"]
"enum": ["safe-read-only", "owner", "none"],
"default": "safe-read-only"
},
"providers": {
"type": "object",

View File

@@ -62,6 +62,41 @@ export type GoogleMeetConfig = {
};
};
export const DEFAULT_GOOGLE_MEET_AUDIO_INPUT_COMMAND = [
"rec",
"-q",
"-t",
"raw",
"-r",
"8000",
"-c",
"1",
"-e",
"mu-law",
"-b",
"8",
"-",
] as const;
export const DEFAULT_GOOGLE_MEET_AUDIO_OUTPUT_COMMAND = [
"play",
"-q",
"-t",
"raw",
"-r",
"8000",
"-c",
"1",
"-e",
"mu-law",
"-b",
"8",
"-",
] as const;
export const DEFAULT_GOOGLE_MEET_REALTIME_INSTRUCTIONS =
"You are joining a private Google Meet as an OpenClaw agent. Keep spoken replies brief and natural. When a question needs deeper reasoning, current information, or tools, call openclaw_agent_consult before answering.";
export const DEFAULT_GOOGLE_MEET_CONFIG: GoogleMeetConfig = {
enabled: true,
defaults: {},
@@ -74,6 +109,8 @@ export const DEFAULT_GOOGLE_MEET_CONFIG: GoogleMeetConfig = {
audioBackend: "blackhole-2ch",
launch: true,
joinTimeoutMs: 30_000,
audioInputCommand: [...DEFAULT_GOOGLE_MEET_AUDIO_INPUT_COMMAND],
audioOutputCommand: [...DEFAULT_GOOGLE_MEET_AUDIO_OUTPUT_COMMAND],
},
twilio: {},
voiceCall: {
@@ -82,6 +119,8 @@ export const DEFAULT_GOOGLE_MEET_CONFIG: GoogleMeetConfig = {
dtmfDelayMs: 2_500,
},
realtime: {
provider: "openai",
instructions: DEFAULT_GOOGLE_MEET_REALTIME_INSTRUCTIONS,
toolPolicy: "safe-read-only",
providers: {},
},
@@ -255,8 +294,12 @@ export function resolveGoogleMeetConfigWithEnv(
chrome.joinTimeoutMs,
DEFAULT_GOOGLE_MEET_CONFIG.chrome.joinTimeoutMs,
),
audioInputCommand: resolveStringArray(chrome.audioInputCommand),
audioOutputCommand: resolveStringArray(chrome.audioOutputCommand),
audioInputCommand: resolveStringArray(chrome.audioInputCommand) ?? [
...DEFAULT_GOOGLE_MEET_AUDIO_INPUT_COMMAND,
],
audioOutputCommand: resolveStringArray(chrome.audioOutputCommand) ?? [
...DEFAULT_GOOGLE_MEET_AUDIO_OUTPUT_COMMAND,
],
audioBridgeCommand: resolveStringArray(chrome.audioBridgeCommand),
audioBridgeHealthCommand: resolveStringArray(chrome.audioBridgeHealthCommand),
},
@@ -280,9 +323,12 @@ export function resolveGoogleMeetConfigWithEnv(
introMessage: normalizeOptionalString(voiceCall.introMessage),
},
realtime: {
provider: normalizeOptionalString(realtime.provider),
model: normalizeOptionalString(realtime.model),
instructions: normalizeOptionalString(realtime.instructions),
provider:
normalizeOptionalString(realtime.provider) ?? DEFAULT_GOOGLE_MEET_CONFIG.realtime.provider,
model: normalizeOptionalString(realtime.model) ?? DEFAULT_GOOGLE_MEET_CONFIG.realtime.model,
instructions:
normalizeOptionalString(realtime.instructions) ??
DEFAULT_GOOGLE_MEET_CONFIG.realtime.instructions,
toolPolicy: resolveToolPolicy(
realtime.toolPolicy,
DEFAULT_GOOGLE_MEET_CONFIG.realtime.toolPolicy,