@openclaw/google-meet 2026.9.6 → 2026.9.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/icon.png +0 -0
- package/dist/.setup/{chrome-create-BZRBHJp9.mjs → chrome-create-wuAK5Pnq.mjs} +14 -19
- package/dist/.setup/{cli-CbxqjjTC.mjs → cli-DVDq0TFu.mjs} +27 -695
- package/dist/.setup/cli-export-D4IM6KLJ.mjs +632 -0
- package/dist/.setup/config-BXGAMntj.mjs +240 -0
- package/dist/.setup/{create-CG9Pgwsb.mjs → create-UrHOmnWb.mjs} +4 -17
- package/dist/.setup/{google-meet-platform-adapter-DQZ74Kd5.mjs → google-meet-platform-adapter-gosgbMIM.mjs} +337 -228
- package/dist/.setup/{google-meet-platform-constants-Bs5iAg3E.mjs → google-meet-platform-constants-CnRfrYX7.mjs} +1 -4
- package/dist/.setup/{node-host-BUrsParx.mjs → node-host-Tr7Mx3da.mjs} +3 -3
- package/dist/.setup/{node-invoke-policy-hwXq9MfV.mjs → node-invoke-policy-Dd3krTYV.mjs} +2 -2
- package/dist/.setup/{oauth-22Ey-zCb.mjs → oauth-BoyruWcS.mjs} +2 -2
- package/dist/.setup/{plugin-helpers--7X3icub.mjs → plugin-helpers-CdQ_YNA7.mjs} +147 -236
- package/dist/.setup/{plugin-registration-B3LXKV4R.mjs → plugin-registration-aCEuCerg.mjs} +34 -10
- package/dist/.setup/{runtime-BuIZ5e3V.mjs → runtime-BdiF53A2.mjs} +85 -41
- package/dist/doctor-contract-api.js +3 -6
- package/dist/index.js +48 -12
- package/node_modules/typebox/build/compile/code.mjs +1 -2
- package/node_modules/typebox/build/compile/validator.mjs +5 -1
- package/node_modules/typebox/build/format/iri.mjs +16 -9
- package/node_modules/typebox/build/guard/emit.mjs +2 -2
- package/node_modules/typebox/build/guard/guard.d.mts +7 -5
- package/node_modules/typebox/build/guard/guard.mjs +14 -9
- package/node_modules/typebox/build/guard/index.mjs +6 -0
- package/node_modules/typebox/build/guard/unicode/unicode.d.mts +6 -0
- package/node_modules/typebox/build/guard/unicode/unicode.mjs +35 -0
- package/node_modules/typebox/build/guard/{string.mjs → unicode/unicode_segment.mjs} +16 -19
- package/node_modules/typebox/build/schema/build.mjs +2 -3
- package/node_modules/typebox/build/schema/engine/_pathing.d.mts +1 -0
- package/node_modules/typebox/build/schema/engine/_pathing.mjs +11 -0
- package/node_modules/typebox/build/schema/engine/_refine.mjs +13 -5
- package/node_modules/typebox/build/schema/engine/additionalProperties.mjs +2 -1
- package/node_modules/typebox/build/schema/engine/dependencies.mjs +2 -1
- package/node_modules/typebox/build/schema/engine/dependentSchemas.mjs +2 -1
- package/node_modules/typebox/build/schema/engine/dynamicRef.d.mts +1 -1
- package/node_modules/typebox/build/schema/engine/dynamicRef.mjs +2 -2
- package/node_modules/typebox/build/schema/engine/format.mjs +9 -2
- package/node_modules/typebox/build/schema/engine/patternProperties.mjs +3 -2
- package/node_modules/typebox/build/schema/engine/properties.mjs +3 -2
- package/node_modules/typebox/build/schema/engine/propertyNames.mjs +2 -1
- package/node_modules/typebox/build/schema/engine/recursiveRef.d.mts +1 -1
- package/node_modules/typebox/build/schema/engine/recursiveRef.mjs +2 -2
- package/node_modules/typebox/build/schema/engine/ref.d.mts +1 -1
- package/node_modules/typebox/build/schema/engine/ref.mjs +2 -2
- package/node_modules/typebox/build/schema/engine/schema.mjs +24 -24
- package/node_modules/typebox/build/schema/parse.mjs +3 -0
- package/node_modules/typebox/build/schema/pointer/pointer.d.mts +1 -6
- package/node_modules/typebox/build/schema/pointer/pointer.mjs +47 -64
- package/node_modules/typebox/build/system/settings/internal.d.mts +2 -0
- package/node_modules/typebox/build/system/settings/internal.mjs +20 -0
- package/node_modules/typebox/build/system/settings/settings.d.mts +11 -0
- package/node_modules/typebox/build/system/settings/settings.mjs +14 -0
- package/node_modules/typebox/build/type/extends/inference.d.mts +1 -1
- package/node_modules/typebox/build/type/extends/inference.mjs +15 -2
- package/node_modules/typebox/build/type/extends/tuple.d.mts +2 -1
- package/node_modules/typebox/build/type/extends/tuple.mjs +21 -1
- package/node_modules/typebox/build/value/parse/parse.mjs +10 -4
- package/node_modules/typebox/package.json +1 -1
- package/node_modules/typebox/readme.md +3 -1
- package/package.json +5 -5
- package/dist/.setup/config-C-6enWkx.mjs +0 -328
- package/dist/.setup/google-api-errors-BDYXfKRa.mjs +0 -17
- /package/node_modules/typebox/build/guard/{string.d.mts → unicode/unicode_segment.d.mts} +0 -0
|
@@ -1,28 +1,23 @@
|
|
|
1
|
-
import { n as GOOGLE_MEET_NODE_COMMAND, t as GOOGLE_MEET_BROWSER_NODE_ADAPTER } from "./google-meet-platform-constants-
|
|
1
|
+
import { n as GOOGLE_MEET_NODE_COMMAND, t as GOOGLE_MEET_BROWSER_NODE_ADAPTER } from "./google-meet-platform-constants-CnRfrYX7.mjs";
|
|
2
2
|
import { t as normalizeMeetUrl } from "./meet-url-BP2Jlvjs.mjs";
|
|
3
|
-
import { c as callBrowserProxyOnNode, l as resolveChromeNode, u as resolveChromeNodeInfo } from "./chrome-create-
|
|
4
|
-
import { n as normalizeDialInNumber, t as GOOGLE_MEET_PLATFORM_ADAPTER } from "./google-meet-platform-adapter-
|
|
3
|
+
import { c as callBrowserProxyOnNode, l as resolveChromeNode, u as resolveChromeNodeInfo } from "./chrome-create-wuAK5Pnq.mjs";
|
|
4
|
+
import { n as normalizeDialInNumber, t as GOOGLE_MEET_PLATFORM_ADAPTER } from "./google-meet-platform-adapter-gosgbMIM.mjs";
|
|
5
5
|
import { asOptionalRecord, asRecord, normalizeOptionalString, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
6
6
|
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
|
7
|
-
import { GatewayClient, startGatewayClientWhenEventLoopReady } from "openclaw/plugin-sdk/gateway-runtime";
|
|
8
7
|
import path from "node:path";
|
|
9
8
|
import { isBlockedHostnameOrIp } from "openclaw/plugin-sdk/ssrf-runtime";
|
|
10
|
-
import {
|
|
9
|
+
import { GatewayClient, startGatewayClientWhenEventLoopReady } from "openclaw/plugin-sdk/gateway-runtime";
|
|
10
|
+
import { MeetingPlatformAdapter, MeetingSessionRuntime, addMeetingSetupCheck, createMeetingSession, createMeetingSetupStatus, createMeetingVoiceCallGateway, endMeetingVoiceCallGatewayCall, getMeetingVoiceCallGatewayCall, isMeetingVoiceCallMissingError, joinMeetingViaVoiceCallGateway, leaveMeetingWithBrowser, readMeetingTranscriptWithBrowser, recoverMeetingBrowserTab, resolveLocalMeetingBrowserRequest, runMeetingParticipationWithBrowser, speakMeetingViaVoiceCallGateway } from "openclaw/plugin-sdk/meeting-runtime";
|
|
11
11
|
import { resolveDefaultAgentId } from "openclaw/plugin-sdk/agent-runtime";
|
|
12
12
|
import { normalizeAgentId } from "openclaw/plugin-sdk/routing";
|
|
13
13
|
import fs from "node:fs";
|
|
14
14
|
import os from "node:os";
|
|
15
15
|
import { resolveTranscriptsConfig } from "openclaw/plugin-sdk/transcripts";
|
|
16
16
|
//#region extensions/google-meet/src/runtime-probes.ts
|
|
17
|
-
function resolveProbeTimeoutMs(input, fallback) {
|
|
18
|
-
if (input === void 0) return Math.min(Math.max(fallback, 1), 12e4);
|
|
19
|
-
if (!Number.isFinite(input) || input <= 0) throw new Error("timeoutMs must be a positive number");
|
|
20
|
-
return Math.min(Math.trunc(input), 12e4);
|
|
21
|
-
}
|
|
22
17
|
const probes = MeetingPlatformAdapter.createRuntimeProbes({
|
|
23
18
|
defaultSpeechMessage: "Say exactly: Google Meet speech test complete.",
|
|
24
19
|
invalidRequest: (message) => new Error(message),
|
|
25
|
-
resolveTimeoutMs: resolveProbeTimeoutMs,
|
|
20
|
+
resolveTimeoutMs: MeetingPlatformAdapter.resolveProbeTimeoutMs,
|
|
26
21
|
shouldWaitForListening: (session) => Boolean((session.transport === "chrome" || session.transport === "chrome-node") && session.chrome?.launched),
|
|
27
22
|
talkBackMode: MeetingPlatformAdapter.isTalkBackMode,
|
|
28
23
|
normalizeUrl: normalizeMeetUrl,
|
|
@@ -200,9 +195,6 @@ function getGoogleMeetSetupStatus(config, options) {
|
|
|
200
195
|
}
|
|
201
196
|
return createMeetingSetupStatus(checks);
|
|
202
197
|
}
|
|
203
|
-
function addGoogleMeetSetupCheck(status, check) {
|
|
204
|
-
return addMeetingSetupCheck(status, check);
|
|
205
|
-
}
|
|
206
198
|
//#endregion
|
|
207
199
|
//#region extensions/google-meet/src/transports/chrome.ts
|
|
208
200
|
const chromeTransport = MeetingPlatformAdapter.createChromeTransportWithExternalAudio({
|
|
@@ -218,13 +210,7 @@ const chromeTransport = MeetingPlatformAdapter.createChromeTransportWithExternal
|
|
|
218
210
|
});
|
|
219
211
|
const assertGoogleMeetAudioAvailable = chromeTransport.assertAudioDeviceAvailable;
|
|
220
212
|
const launchChromeMeetOnNode = chromeTransport.launchOnNode;
|
|
221
|
-
|
|
222
|
-
const result = await chromeTransport.launchInChrome(params);
|
|
223
|
-
return {
|
|
224
|
-
...result,
|
|
225
|
-
audioBridge: result.audioBridge
|
|
226
|
-
};
|
|
227
|
-
}
|
|
213
|
+
const launchChromeMeet = chromeTransport.launchInChrome;
|
|
228
214
|
function shouldCaptureCaptions(mode, fullConfig) {
|
|
229
215
|
return mode === "transcribe" || !fullConfig || resolveTranscriptsConfig(fullConfig.transcripts).enabled;
|
|
230
216
|
}
|
|
@@ -329,7 +315,7 @@ async function getGoogleMeetRuntimeSetupStatus(params) {
|
|
|
329
315
|
requestedNode: params.config.chromeNode.node
|
|
330
316
|
});
|
|
331
317
|
const label = node.displayName ?? node.remoteIp ?? node.nodeId ?? "connected node";
|
|
332
|
-
status =
|
|
318
|
+
status = addMeetingSetupCheck(status, {
|
|
333
319
|
id: "chrome-node-connected",
|
|
334
320
|
ok: true,
|
|
335
321
|
message: `Connected Google Meet node ready: ${label}`
|
|
@@ -348,14 +334,14 @@ async function getGoogleMeetRuntimeSetupStatus(params) {
|
|
|
348
334
|
},
|
|
349
335
|
timeoutMs: 12e3
|
|
350
336
|
});
|
|
351
|
-
status =
|
|
337
|
+
status = addMeetingSetupCheck(status, {
|
|
352
338
|
id: "chrome-node-audio-prerequisites",
|
|
353
339
|
ok: true,
|
|
354
340
|
message: setup ? "Remote virtual audio backend and command-pair prerequisites are ready" : "Remote audio setup completed"
|
|
355
341
|
});
|
|
356
342
|
}
|
|
357
343
|
} catch (error) {
|
|
358
|
-
status =
|
|
344
|
+
status = addMeetingSetupCheck(status, {
|
|
359
345
|
id: "chrome-node-connected",
|
|
360
346
|
ok: false,
|
|
361
347
|
message: formatErrorMessage(error)
|
|
@@ -368,13 +354,13 @@ async function getGoogleMeetRuntimeSetupStatus(params) {
|
|
|
368
354
|
config: params.config,
|
|
369
355
|
timeoutMs: Math.min(params.config.chrome.joinTimeoutMs, 1e4)
|
|
370
356
|
});
|
|
371
|
-
status =
|
|
357
|
+
status = addMeetingSetupCheck(status, {
|
|
372
358
|
id: "chrome-local-audio-device",
|
|
373
359
|
ok: true,
|
|
374
360
|
message: "Virtual meeting audio backend is ready"
|
|
375
361
|
});
|
|
376
362
|
} catch (error) {
|
|
377
|
-
status =
|
|
363
|
+
status = addMeetingSetupCheck(status, {
|
|
378
364
|
id: "chrome-local-audio-device",
|
|
379
365
|
ok: false,
|
|
380
366
|
message: formatErrorMessage(error)
|
|
@@ -387,13 +373,51 @@ async function getGoogleMeetRuntimeSetupStatus(params) {
|
|
|
387
373
|
} catch {
|
|
388
374
|
missingCommands.push(command);
|
|
389
375
|
}
|
|
390
|
-
return
|
|
376
|
+
return addMeetingSetupCheck(status, {
|
|
391
377
|
id: "chrome-local-audio-commands",
|
|
392
378
|
ok: commands.length > 0 && missingCommands.length === 0,
|
|
393
379
|
message: commands.length === 0 ? "Chrome talk-back audio commands are not configured" : missingCommands.length === 0 ? `Chrome audio command${commands.length === 1 ? "" : "s"} available: ${commands.join(", ")}` : `Chrome audio command${missingCommands.length === 1 ? "" : "s"} missing: ${missingCommands.join(", ")}`
|
|
394
380
|
});
|
|
395
381
|
}
|
|
396
382
|
//#endregion
|
|
383
|
+
//#region extensions/google-meet/src/transports/chrome-participation.ts
|
|
384
|
+
async function participateInChromeMeet(params) {
|
|
385
|
+
const adapter = GOOGLE_MEET_PLATFORM_ADAPTER.browser.participation;
|
|
386
|
+
const { session } = params;
|
|
387
|
+
const tab = session.chrome?.browserTab;
|
|
388
|
+
if (!adapter || !tab || session.transport !== "chrome" && session.transport !== "chrome-node") return {
|
|
389
|
+
status: "unsupported",
|
|
390
|
+
message: "Participation requires a supported tracked browser meeting."
|
|
391
|
+
};
|
|
392
|
+
const { nodeId } = session.chrome ?? {};
|
|
393
|
+
const meetingSessionId = session.id;
|
|
394
|
+
const meetingUrl = session.url;
|
|
395
|
+
const targetId = tab.targetId;
|
|
396
|
+
if (session.transport === "chrome-node" && nodeId === void 0) return {
|
|
397
|
+
status: "rejected",
|
|
398
|
+
message: "The meeting has no pinned browser node."
|
|
399
|
+
};
|
|
400
|
+
params.assertCurrent();
|
|
401
|
+
const callBrowser = session.transport === "chrome-node" ? (request) => callBrowserProxyOnNode({
|
|
402
|
+
...request,
|
|
403
|
+
runtime: params.runtime,
|
|
404
|
+
nodeId
|
|
405
|
+
}) : await resolveLocalMeetingBrowserRequest(params.runtime);
|
|
406
|
+
params.assertCurrent();
|
|
407
|
+
return await runMeetingParticipationWithBrowser({
|
|
408
|
+
adapter,
|
|
409
|
+
callBrowser,
|
|
410
|
+
meetingSessionId,
|
|
411
|
+
meetingUrl,
|
|
412
|
+
targetId,
|
|
413
|
+
requestId: params.request.requestId,
|
|
414
|
+
action: params.request.action,
|
|
415
|
+
assertCurrent: () => params.assertCurrent(),
|
|
416
|
+
isSameMeetingUrl: (left, right) => GOOGLE_MEET_PLATFORM_ADAPTER.urls.isSameMeeting(left, right),
|
|
417
|
+
timeoutMs: Math.min(1e4, params.config.chrome.joinTimeoutMs)
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
//#endregion
|
|
397
421
|
//#region extensions/google-meet/src/voice-call-gateway.ts
|
|
398
422
|
const GOOGLE_MEET_VOICE_CALL_SURFACE = {
|
|
399
423
|
clientDisplayName: "Google Meet plugin",
|
|
@@ -458,7 +482,6 @@ function createVoiceCallGateway(params) {
|
|
|
458
482
|
connectClient: createConnectedGatewayClient
|
|
459
483
|
});
|
|
460
484
|
}
|
|
461
|
-
const isVoiceCallMissingError = isMeetingVoiceCallMissingError;
|
|
462
485
|
async function joinMeetViaVoiceCallGateway(params) {
|
|
463
486
|
return await joinMeetingViaVoiceCallGateway({
|
|
464
487
|
...params,
|
|
@@ -466,15 +489,6 @@ async function joinMeetViaVoiceCallGateway(params) {
|
|
|
466
489
|
surface: GOOGLE_MEET_VOICE_CALL_SURFACE
|
|
467
490
|
});
|
|
468
491
|
}
|
|
469
|
-
async function endMeetVoiceCallGatewayCall(params) {
|
|
470
|
-
await endMeetingVoiceCallGatewayCall(params);
|
|
471
|
-
}
|
|
472
|
-
async function getMeetVoiceCallGatewayCall(params) {
|
|
473
|
-
return await getMeetingVoiceCallGatewayCall(params);
|
|
474
|
-
}
|
|
475
|
-
async function speakMeetViaVoiceCallGateway(params) {
|
|
476
|
-
await speakMeetingViaVoiceCallGateway(params);
|
|
477
|
-
}
|
|
478
492
|
//#endregion
|
|
479
493
|
//#region extensions/google-meet/src/runtime.ts
|
|
480
494
|
const nowIso = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -491,7 +505,31 @@ var GoogleMeetRuntime = class {
|
|
|
491
505
|
this.transcriptSourceRuntime = () => this.#sessions;
|
|
492
506
|
const adapter = GOOGLE_MEET_PLATFORM_ADAPTER;
|
|
493
507
|
this.#voiceCallGateway = createVoiceCallGateway(params);
|
|
508
|
+
let participationStore;
|
|
509
|
+
const getParticipationStore = () => participationStore ??= params.runtime.state.openKeyedStore({
|
|
510
|
+
namespace: "meeting-participation",
|
|
511
|
+
maxEntries: 1e4,
|
|
512
|
+
overflowPolicy: "reject-new"
|
|
513
|
+
});
|
|
494
514
|
this.#sessions = new MeetingSessionRuntime({
|
|
515
|
+
participation: {
|
|
516
|
+
store: {
|
|
517
|
+
entries: async () => await getParticipationStore().entries(),
|
|
518
|
+
delete: async (key) => await getParticipationStore().delete(key),
|
|
519
|
+
lookup: async (key) => await getParticipationStore().lookup(key),
|
|
520
|
+
registerIfAbsent: async (key, attempt) => await getParticipationStore().registerIfAbsent(key, attempt),
|
|
521
|
+
register: async (key, attempt) => await getParticipationStore().register(key, attempt)
|
|
522
|
+
},
|
|
523
|
+
capabilities: (session) => isBrowserTransport(session.transport) && session.chrome?.launched && session.chrome.browserTab && session.chrome.health?.inCall === true && !session.chrome.health.manualAction ? adapter.browser.participation?.capabilities ?? [] : [],
|
|
524
|
+
validateAction: (action) => adapter.browser.participation?.validateAction(action),
|
|
525
|
+
execute: async (session, request, assertCurrent) => await participateInChromeMeet({
|
|
526
|
+
runtime: params.runtime,
|
|
527
|
+
config: params.config,
|
|
528
|
+
session,
|
|
529
|
+
request,
|
|
530
|
+
assertCurrent
|
|
531
|
+
})
|
|
532
|
+
},
|
|
495
533
|
logger: params.logger,
|
|
496
534
|
logScope: "[google-meet]",
|
|
497
535
|
formatError: formatErrorMessage,
|
|
@@ -576,6 +614,12 @@ var GoogleMeetRuntime = class {
|
|
|
576
614
|
async status(sessionId) {
|
|
577
615
|
return await this.#sessions.status(sessionId);
|
|
578
616
|
}
|
|
617
|
+
participationContext(sessionId) {
|
|
618
|
+
return this.#sessions.participationContext(sessionId);
|
|
619
|
+
}
|
|
620
|
+
participate(sessionId, request) {
|
|
621
|
+
return this.#sessions.participate(sessionId, request);
|
|
622
|
+
}
|
|
579
623
|
async transcript(sessionId, options = {}) {
|
|
580
624
|
return await this.#sessions.transcript(sessionId, options);
|
|
581
625
|
}
|
|
@@ -713,7 +757,7 @@ var GoogleMeetRuntime = class {
|
|
|
713
757
|
introSent: voiceCallResult?.introSent
|
|
714
758
|
};
|
|
715
759
|
if (voiceCallResult?.callId) context.attachRuntimeHandles(session, { stop: async () => {
|
|
716
|
-
await
|
|
760
|
+
await endMeetingVoiceCallGatewayCall({
|
|
717
761
|
gateway: this.#voiceCallGateway,
|
|
718
762
|
callId: voiceCallResult.callId
|
|
719
763
|
});
|
|
@@ -805,7 +849,7 @@ var GoogleMeetRuntime = class {
|
|
|
805
849
|
return;
|
|
806
850
|
}
|
|
807
851
|
try {
|
|
808
|
-
const status = await
|
|
852
|
+
const status = await getMeetingVoiceCallGatewayCall({
|
|
809
853
|
gateway: this.#voiceCallGateway,
|
|
810
854
|
callId
|
|
811
855
|
});
|
|
@@ -819,13 +863,13 @@ var GoogleMeetRuntime = class {
|
|
|
819
863
|
async #speakViaTransport(session, instructions) {
|
|
820
864
|
if (session.transport !== "twilio" || !session.twilio?.voiceCallId) return;
|
|
821
865
|
try {
|
|
822
|
-
await
|
|
866
|
+
await speakMeetingViaVoiceCallGateway({
|
|
823
867
|
gateway: this.#voiceCallGateway,
|
|
824
868
|
callId: session.twilio.voiceCallId,
|
|
825
869
|
message: instructions || this.params.config.voiceCall.introMessage || this.params.config.realtime.introMessage || ""
|
|
826
870
|
});
|
|
827
871
|
} catch (error) {
|
|
828
|
-
if (!
|
|
872
|
+
if (!isMeetingVoiceCallMissingError(error)) throw error;
|
|
829
873
|
this.#sessions.markSessionEnded(session, "Voice Call is no longer active.");
|
|
830
874
|
return {
|
|
831
875
|
handled: true,
|
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
import { asNullableRecord, normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
2
2
|
//#region extensions/google-meet/src/config-compat.ts
|
|
3
|
-
function hasOwn(record, key) {
|
|
4
|
-
return Object.hasOwn(record, key);
|
|
5
|
-
}
|
|
6
3
|
function hasLegacyGoogleRealtimeProvider(value) {
|
|
7
4
|
const realtime = asNullableRecord(value);
|
|
8
5
|
if (!realtime || normalizeOptionalLowercaseString(realtime.provider) !== "google") return false;
|
|
9
|
-
return !hasOwn(realtime, "voiceProvider") || !hasOwn(realtime, "transcriptionProvider");
|
|
6
|
+
return !Object.hasOwn(realtime, "voiceProvider") || !Object.hasOwn(realtime, "transcriptionProvider");
|
|
10
7
|
}
|
|
11
8
|
const legacyConfigRules = [{
|
|
12
9
|
path: [
|
|
@@ -36,8 +33,8 @@ function migrateGoogleMeetLegacyRealtimeProvider(config) {
|
|
|
36
33
|
const nextRealtime = asNullableRecord(nextPluginConfig.realtime) ?? {};
|
|
37
34
|
nextPluginConfig.realtime = nextRealtime;
|
|
38
35
|
nextRealtime.provider = "openai";
|
|
39
|
-
if (!hasOwn(nextRealtime, "transcriptionProvider")) nextRealtime.transcriptionProvider = "openai";
|
|
40
|
-
if (!hasOwn(nextRealtime, "voiceProvider")) nextRealtime.voiceProvider = "google";
|
|
36
|
+
if (!Object.hasOwn(nextRealtime, "transcriptionProvider")) nextRealtime.transcriptionProvider = "openai";
|
|
37
|
+
if (!Object.hasOwn(nextRealtime, "voiceProvider")) nextRealtime.voiceProvider = "google";
|
|
41
38
|
return {
|
|
42
39
|
config: nextConfig,
|
|
43
40
|
changes: ["Moved Google Meet legacy realtime.provider=\"google\" intent to realtime.voiceProvider=\"google\" and realtime.transcriptionProvider=\"openai\"."]
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { t as GOOGLE_MEET_CLI_DESCRIPTOR } from "./.setup/cli-output-mode-BwH_6tH8.mjs";
|
|
2
|
-
import { n as GOOGLE_MEET_NODE_COMMAND } from "./.setup/google-meet-platform-constants-
|
|
3
|
-
import { r as resolveGoogleMeetConfig } from "./.setup/config-
|
|
4
|
-
import { a as
|
|
2
|
+
import { n as GOOGLE_MEET_NODE_COMMAND } from "./.setup/google-meet-platform-constants-CnRfrYX7.mjs";
|
|
3
|
+
import { r as resolveGoogleMeetConfig } from "./.setup/config-BXGAMntj.mjs";
|
|
4
|
+
import { a as formatGoogleMeetGatewayError, c as loadGoogleMeetNodeHostModule, d as normalizeTransport, f as readGoogleMeetParticipationParams, h as shouldJoinCreatedMeet, i as createLazyGoogleMeetNodeInvokePolicy, l as loadGoogleMeetPluginHelpers, m as sendGoogleMeetGatewayError, n as callGoogleMeetGatewayFromTool, o as keepTrustedToolAgentId, p as resolveMeetingInput, r as createGoogleMeetRuntimeAccessor, s as loadGoogleMeetCliModule, t as assertGoogleMeetAgentToolActionSupported, u as normalizeMode } from "./.setup/plugin-registration-aCEuCerg.mjs";
|
|
5
5
|
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
|
6
|
-
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
6
|
+
import { asNonArrayRecord, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
7
7
|
import { jsonResult } from "openclaw/plugin-sdk/tool-results";
|
|
8
8
|
import { optionalPositiveIntegerSchema } from "openclaw/plugin-sdk/channel-actions";
|
|
9
9
|
import { Type } from "typebox";
|
|
@@ -18,6 +18,8 @@ const GoogleMeetToolSchema = Type.Object({
|
|
|
18
18
|
"create",
|
|
19
19
|
"status",
|
|
20
20
|
"transcript",
|
|
21
|
+
"participation_context",
|
|
22
|
+
"participate",
|
|
21
23
|
"setup_status",
|
|
22
24
|
"resolve_space",
|
|
23
25
|
"preflight",
|
|
@@ -69,6 +71,14 @@ const GoogleMeetToolSchema = Type.Object({
|
|
|
69
71
|
pin: Type.Optional(Type.String({ description: "Meet phone PIN for Twilio; # is appended if omitted" })),
|
|
70
72
|
dtmfSequence: Type.Optional(Type.String({ description: "Explicit DTMF sequence for Twilio" })),
|
|
71
73
|
sessionId: Type.Optional(Type.String({ description: "Meet session ID" })),
|
|
74
|
+
requestId: Type.Optional(Type.String({ description: "For participate, a unique request ID. Reuse the same ID only to retry the same action." })),
|
|
75
|
+
sourceId: Type.Optional(Type.String({ description: "For participate, the current source ID from participation_context." })),
|
|
76
|
+
correctionOf: Type.Optional(Type.String({ description: "For participate, the rejected request ID whose correction is being submitted." })),
|
|
77
|
+
participationAction: Type.Optional(Type.Object({
|
|
78
|
+
type: Type.String({ description: "Action type advertised by participation_context capabilities." }),
|
|
79
|
+
text: Type.Optional(Type.String({ description: "Text for a supported participation action." })),
|
|
80
|
+
reaction: Type.Optional(Type.String({ description: "Native reaction advertised by the current capability." }))
|
|
81
|
+
})),
|
|
72
82
|
sinceIndex: Type.Optional(Type.Integer({
|
|
73
83
|
description: "For transcript, resume from the previous response's nextIndex.",
|
|
74
84
|
minimum: 0
|
|
@@ -119,7 +129,7 @@ var google_meet_default = definePluginEntry({
|
|
|
119
129
|
});
|
|
120
130
|
};
|
|
121
131
|
const resolveTrustedJoinParams = ({ params, client }) => {
|
|
122
|
-
const trustedParams = keepTrustedToolAgentId(
|
|
132
|
+
const trustedParams = keepTrustedToolAgentId(asNonArrayRecord(params), client);
|
|
123
133
|
return {
|
|
124
134
|
url: resolveMeetingInput(config, trustedParams.url),
|
|
125
135
|
transport: normalizeTransport(trustedParams.transport),
|
|
@@ -183,7 +193,7 @@ var google_meet_default = definePluginEntry({
|
|
|
183
193
|
options.respond(true, await runtime.join(resolveTrustedJoinParams(options)));
|
|
184
194
|
});
|
|
185
195
|
registerGatewayMethod("googlemeet.create", async ({ params, client, respond }) => {
|
|
186
|
-
const raw = keepTrustedToolAgentId(
|
|
196
|
+
const raw = keepTrustedToolAgentId(asNonArrayRecord(params), client);
|
|
187
197
|
const helpers = await loadGoogleMeetPluginHelpers();
|
|
188
198
|
respond(true, shouldJoinCreatedMeet(raw) ? await helpers.createAndJoinMeetFromParams({
|
|
189
199
|
config,
|
|
@@ -212,6 +222,24 @@ var google_meet_default = definePluginEntry({
|
|
|
212
222
|
}
|
|
213
223
|
respond(true, await (await ensureRuntime()).transcript(sessionId, sinceIndex === void 0 ? {} : { sinceIndex }));
|
|
214
224
|
});
|
|
225
|
+
registerGatewayMethod("googlemeet.participationContext", async ({ params, respond }) => {
|
|
226
|
+
const sessionId = normalizeOptionalString(params?.sessionId);
|
|
227
|
+
if (!sessionId) {
|
|
228
|
+
sendGoogleMeetGatewayError(respond, /* @__PURE__ */ new Error("sessionId required"), "INVALID_REQUEST");
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
respond(true, (await ensureRuntime()).participationContext(sessionId));
|
|
232
|
+
});
|
|
233
|
+
registerGatewayMethod("googlemeet.participate", async ({ params, respond }) => {
|
|
234
|
+
let parsed;
|
|
235
|
+
try {
|
|
236
|
+
parsed = readGoogleMeetParticipationParams(asNonArrayRecord(params));
|
|
237
|
+
} catch (err) {
|
|
238
|
+
sendGoogleMeetGatewayError(respond, err, "INVALID_REQUEST");
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
respond(true, await (await ensureRuntime()).participate(parsed.sessionId, parsed.request));
|
|
242
|
+
});
|
|
215
243
|
registerGatewayMethod("googlemeet.recoverCurrentTab", async ({ params, respond }) => {
|
|
216
244
|
respond(true, await (await ensureRuntime()).recoverCurrentTab({
|
|
217
245
|
url: normalizeOptionalString(params?.url),
|
|
@@ -231,10 +259,10 @@ var google_meet_default = definePluginEntry({
|
|
|
231
259
|
["googlemeet.artifacts", "artifacts"],
|
|
232
260
|
["googlemeet.attendance", "attendance"]
|
|
233
261
|
]) registerGatewayMethod(method, async ({ params, respond }) => {
|
|
234
|
-
respond(true, await queryActions[action](
|
|
262
|
+
respond(true, await queryActions[action](asNonArrayRecord(params)));
|
|
235
263
|
});
|
|
236
264
|
registerGatewayMethod("googlemeet.export", async ({ params, respond }) => {
|
|
237
|
-
respond(true, await (await loadGoogleMeetPluginHelpers()).exportGoogleMeetBundleFromParams(config,
|
|
265
|
+
respond(true, await (await loadGoogleMeetPluginHelpers()).exportGoogleMeetBundleFromParams(config, asNonArrayRecord(params)));
|
|
238
266
|
});
|
|
239
267
|
registerGatewayMethod("googlemeet.leave", async ({ params, respond }) => {
|
|
240
268
|
const sessionId = normalizeOptionalString(params?.sessionId);
|
|
@@ -245,7 +273,7 @@ var google_meet_default = definePluginEntry({
|
|
|
245
273
|
respond(true, await (await ensureRuntime()).leave(sessionId));
|
|
246
274
|
});
|
|
247
275
|
registerGatewayMethod("googlemeet.endActiveConference", async ({ params, respond }) => {
|
|
248
|
-
const raw =
|
|
276
|
+
const raw = asNonArrayRecord(params);
|
|
249
277
|
const helpers = await loadGoogleMeetPluginHelpers();
|
|
250
278
|
const token = await helpers.resolveGoogleMeetTokenFromParams(config, raw);
|
|
251
279
|
respond(true, await helpers.endGoogleMeetActiveConference({
|
|
@@ -266,7 +294,7 @@ var google_meet_default = definePluginEntry({
|
|
|
266
294
|
options.respond(true, await runtime.testSpeech(resolveTrustedJoinParams(options)));
|
|
267
295
|
});
|
|
268
296
|
registerGatewayMethod("googlemeet.testListen", async ({ params, client, respond }) => {
|
|
269
|
-
const trustedParams = keepTrustedToolAgentId(
|
|
297
|
+
const trustedParams = keepTrustedToolAgentId(asNonArrayRecord(params), client);
|
|
270
298
|
const runtime = await ensureRuntime();
|
|
271
299
|
const { readPositiveIntegerParam } = await import("openclaw/plugin-sdk/param-readers");
|
|
272
300
|
respond(true, await runtime.testListen({
|
|
@@ -280,10 +308,10 @@ var google_meet_default = definePluginEntry({
|
|
|
280
308
|
api.registerTool((toolContext) => ({
|
|
281
309
|
name: "google_meet",
|
|
282
310
|
label: "Google Meet",
|
|
283
|
-
description: "Join and track Google Meet sessions through Chrome or Twilio. Call setup_status before join/create/test_listen/test_speech; if it reports a Chrome node offline, local audio missing, or missing Twilio dial plan, surface that blocker instead of retrying or switching transports. Twilio cannot dial a Meet URL directly: provide dialInNumber plus optional pin/dtmfSequence, or configure twilio.defaultDialInNumber. Offline nodes are diagnostics only, not usable candidates. Local Chrome talk-back needs macOS with BlackHole 2ch or Linux with PipeWire-Pulse; otherwise use mode=transcribe, transport=twilio, or a supported chrome-node. If a Meet tab is already open after a timeout, call recover_current_tab before retrying join to report login, permission, or admission blockers without opening another tab.",
|
|
311
|
+
description: "Join and track Google Meet sessions through Chrome or Twilio. Call setup_status before join/create/test_listen/test_speech; if it reports a Chrome node offline, local audio missing, or missing Twilio dial plan, surface that blocker instead of retrying or switching transports. Twilio cannot dial a Meet URL directly: provide dialInNumber plus optional pin/dtmfSequence, or configure twilio.defaultDialInNumber. Offline nodes are diagnostics only, not usable candidates. Local Chrome talk-back needs macOS with BlackHole 2ch or Linux with PipeWire-Pulse; otherwise use mode=transcribe, transport=twilio, or a supported chrome-node. If a Meet tab is already open after a timeout, call recover_current_tab before retrying join to report login, permission, or admission blockers without opening another tab. For meeting participation, call participation_context for current source identities and supported capabilities, then participate with a unique requestId and participationAction. An empty capability list means native participation is unavailable. Reuse a requestId only for an identical retry; use correctionOf only when a rejected result permits one correction.",
|
|
284
312
|
parameters: GoogleMeetToolSchema,
|
|
285
313
|
async execute(_toolCallId, params) {
|
|
286
|
-
const raw =
|
|
314
|
+
const raw = asNonArrayRecord(params);
|
|
287
315
|
const requesterSessionKey = normalizeOptionalString(toolContext.sessionKey);
|
|
288
316
|
try {
|
|
289
317
|
const { normalizeAgentId, parseAgentSessionKey } = await import("openclaw/plugin-sdk/routing");
|
|
@@ -342,6 +370,14 @@ var google_meet_default = definePluginEntry({
|
|
|
342
370
|
const helpers = await loadGoogleMeetPluginHelpers();
|
|
343
371
|
return jsonResult(await helpers.exportGoogleMeetBundleFromParams(config, raw));
|
|
344
372
|
}
|
|
373
|
+
case "participate":
|
|
374
|
+
readGoogleMeetParticipationParams(raw);
|
|
375
|
+
return jsonResult(await callGoogleMeetGatewayFromTool({
|
|
376
|
+
config,
|
|
377
|
+
action: raw.action,
|
|
378
|
+
raw
|
|
379
|
+
}));
|
|
380
|
+
case "participation_context":
|
|
345
381
|
case "leave":
|
|
346
382
|
case "speak":
|
|
347
383
|
if (!normalizeOptionalString(raw.sessionId)) throw new Error("sessionId required");
|
|
@@ -16,9 +16,8 @@ function Separator() {
|
|
|
16
16
|
function ImportSection(build) {
|
|
17
17
|
const context = build.UseUnevaluated() ? [`import { CheckContext } from "typebox/schema"`] : [];
|
|
18
18
|
const hashing = `import { Hashing } from "typebox/system"`;
|
|
19
|
-
const format = `import { Format } from "typebox/format"`;
|
|
20
19
|
const guard = `import { Guard } from "typebox/guard"`;
|
|
21
|
-
return [...context, hashing,
|
|
20
|
+
return [...context, hashing, guard];
|
|
22
21
|
}
|
|
23
22
|
// ------------------------------------------------------------------
|
|
24
23
|
// ExternalSection
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// deno-fmt-ignore-file
|
|
2
|
+
import { EnableParseErrors, DisableParseErrors } from '../system/settings/internal.mjs';
|
|
2
3
|
import { Settings } from '../system/settings/index.mjs';
|
|
3
4
|
import { Errors, Clean, Convert, Create, Default, Decode, Encode, HasCodec, Parser, ParseError } from '../value/index.mjs';
|
|
4
5
|
import { Build } from '../schema/index.mjs';
|
|
@@ -51,7 +52,10 @@ export class Validator {
|
|
|
51
52
|
return value;
|
|
52
53
|
if (Settings.Get().correctiveParse)
|
|
53
54
|
return Parser(this.Context(), this.Type(), value);
|
|
54
|
-
|
|
55
|
+
EnableParseErrors();
|
|
56
|
+
const errors = this.Errors(value);
|
|
57
|
+
DisableParseErrors();
|
|
58
|
+
throw new ParseError(value, errors);
|
|
55
59
|
}
|
|
56
60
|
/** Returns an array of validation errors for the given value. */
|
|
57
61
|
Errors(value) {
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// deno-lint-ignore-file no-control-regex
|
|
2
|
-
const IpvFutureMatchMaxLength = 2048;
|
|
3
|
-
const IpvFutureMatch = /\[[vV][0-9a-fA-F]+\.[^\]]+\]/; // Guarded By IpvFutureMatchMaxLength
|
|
4
|
-
const InvalidIriChars = /[\x00-\x20<>\^`{|}\\]/;
|
|
5
|
-
const InvalidPercentEncoding = /%(?![0-9a-fA-F]{2})/;
|
|
6
2
|
// ------------------------------------------------------------------
|
|
3
|
+
//
|
|
4
|
+
// Removed by the following PR
|
|
5
|
+
//
|
|
6
|
+
// https://github.com/json-schema-org/JSON-Schema-Test-Suite/pull/1176
|
|
7
|
+
//
|
|
7
8
|
// NarrowIpvFuture
|
|
8
9
|
//
|
|
9
10
|
// Substitutes an IPvFuture address with a standard IPv6 loopback
|
|
@@ -13,12 +14,18 @@ const InvalidPercentEncoding = /%(?![0-9a-fA-F]{2})/;
|
|
|
13
14
|
// be expensive on large strings, this operation is strictly
|
|
14
15
|
// limited to inputs under a defined length threshold.
|
|
15
16
|
//
|
|
16
|
-
//
|
|
17
|
+
// ------------------------------------------------------------------
|
|
18
|
+
//
|
|
19
|
+
// const IpvFutureMatchMaxLength = 2048
|
|
20
|
+
// const IpvFutureMatch = /\[[vV][0-9a-fA-F]+\.[^\]]+\]/ // Guarded By IpvFutureMatchMaxLength
|
|
21
|
+
//
|
|
22
|
+
// function NarrowIpvFuture(value: string): string {
|
|
23
|
+
// return value.length < IpvFutureMatchMaxLength ? value.replace(IpvFutureMatch, '[::1]') : value
|
|
24
|
+
// }
|
|
17
25
|
//
|
|
18
26
|
// ------------------------------------------------------------------
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
27
|
+
const InvalidIriChars = /[\x00-\x20<>\^`{|}\\]/;
|
|
28
|
+
const InvalidPercentEncoding = /%(?![0-9a-fA-F]{2})/;
|
|
22
29
|
/**
|
|
23
30
|
* Returns true if the value is a valid Internationalized Resource Identifier.
|
|
24
31
|
* @specification https://datatracker.ietf.org/doc/html/rfc3987
|
|
@@ -31,5 +38,5 @@ export function IsIri(value) {
|
|
|
31
38
|
if (InvalidPercentEncoding.test(value))
|
|
32
39
|
return false;
|
|
33
40
|
// 3. Delegate to the native URL parser, patching the IPvFuture edge case beforehand.
|
|
34
|
-
return URL.canParse(
|
|
41
|
+
return URL.canParse(value);
|
|
35
42
|
}
|
|
@@ -97,10 +97,10 @@ export function IsGreaterEqualThan(left, right) {
|
|
|
97
97
|
// String
|
|
98
98
|
// --------------------------------------------------------------------------
|
|
99
99
|
export function IsMinLength(value, length) {
|
|
100
|
-
return `Guard.
|
|
100
|
+
return `(${value}.length >= ${(+length) << 1} || (${value}.length >= ${length} && Guard.CodePointCount(${value}) >= ${length}))`;
|
|
101
101
|
}
|
|
102
102
|
export function IsMaxLength(value, length) {
|
|
103
|
-
return `Guard.
|
|
103
|
+
return `(${value}.length <= ${length} || (${value}.length <= ${(+length) << 1} && Guard.CodePointCount(${value}) <= ${length}))`;
|
|
104
104
|
}
|
|
105
105
|
// --------------------------------------------------------------------------
|
|
106
106
|
// Array
|
|
@@ -33,12 +33,14 @@ export declare function IsMultipleOf(dividend: bigint | number, divisor: bigint
|
|
|
33
33
|
/** Returns true if the value appears to be an instance of a class. */
|
|
34
34
|
export declare function IsClassInstance(value: unknown): boolean;
|
|
35
35
|
export declare function IsValueLike(value: unknown): value is bigint | boolean | null | number | string | undefined;
|
|
36
|
-
/** Returns the number of grapheme clusters in the string */
|
|
36
|
+
/** Returns the total number of visual grapheme clusters in the string. */
|
|
37
37
|
export declare function GraphemeCount(value: string): number;
|
|
38
|
-
/** Returns
|
|
39
|
-
export declare function
|
|
40
|
-
/** Returns true if the string
|
|
41
|
-
export declare function
|
|
38
|
+
/** Returns the total number of Unicode code points in the string. */
|
|
39
|
+
export declare function CodePointCount(value: string): number;
|
|
40
|
+
/** Returns true if the string length in Unicode code points does not exceed maxLength */
|
|
41
|
+
export declare function IsMaxLength(value: string, maxLength: number): boolean;
|
|
42
|
+
/** Returns true if the string length in Unicode code points is at least minLength */
|
|
43
|
+
export declare function IsMinLength(value: string, minLength: number): boolean;
|
|
42
44
|
/** Returns true if every element from offset satisfies the callback, short-circuiting on the first failure */
|
|
43
45
|
export declare function Every<T>(value: T[], offset: number, callback: (value: T, index: number) => boolean): boolean;
|
|
44
46
|
/** Returns true if every element from offset satisfies the callback, using exhaustive enumeration */
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// deno-fmt-ignore-file
|
|
2
|
-
import * as
|
|
2
|
+
import * as UnicodeSegmentGuard from './unicode/unicode_segment.mjs';
|
|
3
|
+
import * as UnicodeGuard from './unicode/unicode.mjs';
|
|
3
4
|
// --------------------------------------------------------------------------
|
|
4
5
|
// Guards
|
|
5
6
|
// --------------------------------------------------------------------------
|
|
@@ -123,17 +124,21 @@ export function IsValueLike(value) {
|
|
|
123
124
|
// --------------------------------------------------------------------------
|
|
124
125
|
// String
|
|
125
126
|
// --------------------------------------------------------------------------
|
|
126
|
-
/** Returns the number of grapheme clusters in the string */
|
|
127
|
+
/** Returns the total number of visual grapheme clusters in the string. */
|
|
127
128
|
export function GraphemeCount(value) {
|
|
128
|
-
return
|
|
129
|
+
return UnicodeSegmentGuard.GraphemeCount(value);
|
|
129
130
|
}
|
|
130
|
-
/** Returns
|
|
131
|
-
export function
|
|
132
|
-
return
|
|
131
|
+
/** Returns the total number of Unicode code points in the string. */
|
|
132
|
+
export function CodePointCount(value) {
|
|
133
|
+
return UnicodeGuard.CodePointCount(value);
|
|
133
134
|
}
|
|
134
|
-
/** Returns true if the string
|
|
135
|
-
export function
|
|
136
|
-
return
|
|
135
|
+
/** Returns true if the string length in Unicode code points does not exceed maxLength */
|
|
136
|
+
export function IsMaxLength(value, maxLength) {
|
|
137
|
+
return UnicodeGuard.IsMaxLength(value, maxLength);
|
|
138
|
+
}
|
|
139
|
+
/** Returns true if the string length in Unicode code points is at least minLength */
|
|
140
|
+
export function IsMinLength(value, minLength) {
|
|
141
|
+
return UnicodeGuard.IsMinLength(value, minLength);
|
|
137
142
|
}
|
|
138
143
|
// --------------------------------------------------------------------------
|
|
139
144
|
// Array
|
|
@@ -1,6 +1,12 @@
|
|
|
1
|
+
// ------------------------------------------------------------------
|
|
2
|
+
// Guards
|
|
3
|
+
// ------------------------------------------------------------------
|
|
1
4
|
export * as EmitGuard from './emit.mjs';
|
|
2
5
|
export * as GlobalsGuard from './globals.mjs';
|
|
3
6
|
export * as NativeGuard from './native.mjs';
|
|
7
|
+
// ------------------------------------------------------------------
|
|
8
|
+
// Default
|
|
9
|
+
// ------------------------------------------------------------------
|
|
4
10
|
import * as Guard from './guard.mjs';
|
|
5
11
|
export * as Guard from './guard.mjs';
|
|
6
12
|
export default Guard;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Returns the total number of Unicode code points in the string */
|
|
2
|
+
export declare function CodePointCount(value: string): number;
|
|
3
|
+
/** Returns true if the string length in Unicode code points is less than or equal to maxLength */
|
|
4
|
+
export declare function IsMaxLength(value: string, maxLength: number): boolean;
|
|
5
|
+
/** Returns true if the string length in Unicode code points is greater than or equal to minLength */
|
|
6
|
+
export declare function IsMinLength(value: string, minLength: number): boolean;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// ------------------------------------------------------------------
|
|
2
|
+
// CodePointCount (10-bit Shift Branchless)
|
|
3
|
+
//
|
|
4
|
+
// Counts code points by enumerating UTF-16 sequences and incrementing
|
|
5
|
+
// when not within a high/low pairing. Because surrogate blocks are
|
|
6
|
+
// 1024 (2^10) wide and aligned, by shifting right by (0xA), we can
|
|
7
|
+
// collapse each unit to a constant per block (high: 0x36, low: 0x37)
|
|
8
|
+
// and shift again into a packed (0x3637) for comparison. Fetch calls
|
|
9
|
+
// to charCodeAt(...) are kept to one call per iteration.
|
|
10
|
+
//
|
|
11
|
+
// ------------------------------------------------------------------
|
|
12
|
+
/** Returns the total number of Unicode code points in the string */
|
|
13
|
+
export function CodePointCount(value) {
|
|
14
|
+
let result = 0, index = 0, prev = 0;
|
|
15
|
+
while (index < value.length) {
|
|
16
|
+
const next = value.charCodeAt(index++) >> 0xA; // shift (10-bits into high/low)
|
|
17
|
+
result += +(((prev << 8) | next) !== 0x3637); // packed (or +!(prev === 0x36 && next === 0x37))
|
|
18
|
+
prev = next;
|
|
19
|
+
}
|
|
20
|
+
return result;
|
|
21
|
+
}
|
|
22
|
+
// ------------------------------------------------------------------
|
|
23
|
+
// IsMaxLength
|
|
24
|
+
// ------------------------------------------------------------------
|
|
25
|
+
/** Returns true if the string length in Unicode code points is less than or equal to maxLength */
|
|
26
|
+
export function IsMaxLength(value, maxLength) {
|
|
27
|
+
return value.length <= maxLength || (value.length <= (maxLength << 1) && CodePointCount(value) <= maxLength);
|
|
28
|
+
}
|
|
29
|
+
// ------------------------------------------------------------------
|
|
30
|
+
// IsMinLength
|
|
31
|
+
// ------------------------------------------------------------------
|
|
32
|
+
/** Returns true if the string length in Unicode code points is greater than or equal to minLength */
|
|
33
|
+
export function IsMinLength(value, minLength) {
|
|
34
|
+
return value.length >= (minLength << 1) || (value.length >= minLength && CodePointCount(value) >= minLength);
|
|
35
|
+
}
|