





















@@ -1,4 +1,5 @@
11import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
2+import { ErrorCodes, errorShape } from "openclaw/plugin-sdk/gateway-runtime";
23import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
34import { Type } from "typebox";
45import {
@@ -19,6 +20,7 @@ import {
1920type VoiceCallConfig,
2021} from "./src/config.js";
2122import type { CoreConfig } from "./src/core-bridge.js";
23+import { createVoiceCallContinueOperationStore } from "./src/gateway-continue-operation.js";
22242325const voiceCallConfigSchema = {
2426parse(value: unknown): VoiceCallConfig {
@@ -203,6 +205,10 @@ export default definePluginEntry({
203205}
204206205207const runtimeState = getVoiceCallRuntimeGlobalState();
208+const continueOperationStore = createVoiceCallContinueOperationStore({
209+ config,
210+coreConfig: api.config as CoreConfig,
211+});
206212207213const ensureRuntime = async (): Promise<VoiceCallRuntime> => {
208214if (!config.enabled) {
@@ -258,8 +264,16 @@ export default definePluginEntry({
258264}
259265};
260266261-const sendError = (respond: (ok: boolean, payload?: unknown) => void, err: unknown) => {
262-respond(false, { error: formatErrorMessage(err) });
267+const respondError = (
268+respond: GatewayRequestHandlerOptions["respond"],
269+message: string,
270+code: (typeof ErrorCodes)[keyof typeof ErrorCodes] = ErrorCodes.UNAVAILABLE,
271+) => {
272+respond(false, undefined, errorShape(code, message));
273+};
274+275+const sendError = (respond: GatewayRequestHandlerOptions["respond"], err: unknown) => {
276+respondError(respond, formatErrorMessage(err));
263277};
264278265279const resolveCallMessageRequest = async (params: GatewayRequestHandlerOptions["params"]) => {
@@ -271,6 +285,7 @@ export default definePluginEntry({
271285const rt = await ensureRuntime();
272286return { rt, callId, message } as const;
273287};
288+274289const initiateCallAndRespond = async (params: {
275290rt: VoiceCallRuntime;
276291respond: GatewayRequestHandlerOptions["respond"];
@@ -285,7 +300,7 @@ export default definePluginEntry({
285300dtmfSequence: params.dtmfSequence,
286301});
287302if (!result.success) {
288-params.respond(false, { error: result.error || "initiate failed" });
303+respondError(params.respond, result.error || "initiate failed");
289304return;
290305}
291306params.respond(true, { callId: result.callId, initiated: true });
@@ -306,12 +321,16 @@ export default definePluginEntry({
306321}) => {
307322const request = await resolveCallMessageRequest(params.requestParams);
308323if ("error" in request) {
309-params.respond(false, { error: request.error });
324+respondError(
325+params.respond,
326+request.error ?? "callId and message required",
327+ErrorCodes.INVALID_REQUEST,
328+);
310329return;
311330}
312331const result = await params.action(request);
313332if (!result.success) {
314-params.respond(false, { error: result.error || params.failure });
333+respondError(params.respond, result.error || params.failure);
315334return;
316335}
317336params.respond(
@@ -328,13 +347,13 @@ export default definePluginEntry({
328347try {
329348const message = normalizeOptionalString(params?.message) ?? "";
330349if (!message) {
331-respond(false, { error: "message required" });
350+respondError(respond, "message required", ErrorCodes.INVALID_REQUEST);
332351return;
333352}
334353const rt = await ensureRuntime();
335354const to = normalizeOptionalString(params?.to) ?? rt.config.toNumber;
336355if (!to) {
337-respond(false, { error: "to required" });
356+respondError(respond, "to required", ErrorCodes.INVALID_REQUEST);
338357return;
339358}
340359const mode =
@@ -369,13 +388,58 @@ export default definePluginEntry({
369388},
370389);
371390391+api.registerGatewayMethod(
392+"voicecall.continue.start",
393+async ({ params, respond }: GatewayRequestHandlerOptions) => {
394+try {
395+const request = await resolveCallMessageRequest(params);
396+if ("error" in request) {
397+respondError(
398+respond,
399+request.error ?? "callId and message required",
400+ErrorCodes.INVALID_REQUEST,
401+);
402+return;
403+}
404+respond(true, continueOperationStore.start(request));
405+} catch (err) {
406+sendError(respond, err);
407+}
408+},
409+);
410+411+api.registerGatewayMethod(
412+"voicecall.continue.result",
413+async ({ params, respond }: GatewayRequestHandlerOptions) => {
414+try {
415+const operationId = normalizeOptionalString(params?.operationId) ?? "";
416+if (!operationId) {
417+respondError(respond, "operationId required", ErrorCodes.INVALID_REQUEST);
418+return;
419+}
420+const operation = continueOperationStore.read(operationId);
421+if (!operation.ok) {
422+respondError(respond, operation.error, ErrorCodes.INVALID_REQUEST);
423+return;
424+}
425+respond(true, operation.payload);
426+} catch (err) {
427+sendError(respond, err);
428+}
429+},
430+);
431+372432api.registerGatewayMethod(
373433"voicecall.speak",
374434async ({ params, respond }: GatewayRequestHandlerOptions) => {
375435try {
376436const request = await resolveCallMessageRequest(params);
377437if ("error" in request) {
378-respond(false, { error: request.error });
438+respondError(
439+respond,
440+request.error ?? "callId and message required",
441+ErrorCodes.INVALID_REQUEST,
442+);
379443return;
380444}
381445if (request.rt.config.realtime.enabled) {
@@ -390,7 +454,7 @@ export default definePluginEntry({
390454}
391455const result = await request.rt.manager.speak(request.callId, request.message);
392456if (!result.success) {
393-respond(false, { error: result.error || "speak failed" });
457+respondError(respond, result.error || "speak failed");
394458return;
395459}
396460respond(true, { success: true });
@@ -407,13 +471,13 @@ export default definePluginEntry({
407471const callId = normalizeOptionalString(params?.callId) ?? "";
408472const digits = normalizeOptionalString(params?.digits) ?? "";
409473if (!callId || !digits) {
410-respond(false, { error: "callId and digits required" });
474+respondError(respond, "callId and digits required", ErrorCodes.INVALID_REQUEST);
411475return;
412476}
413477const rt = await ensureRuntime();
414478const result = await rt.manager.sendDtmf(callId, digits);
415479if (!result.success) {
416-respond(false, { error: result.error || "dtmf failed" });
480+respondError(respond, result.error || "dtmf failed");
417481return;
418482}
419483respond(true, { success: true });
@@ -429,13 +493,13 @@ export default definePluginEntry({
429493try {
430494const callId = normalizeOptionalString(params?.callId) ?? "";
431495if (!callId) {
432-respond(false, { error: "callId required" });
496+respondError(respond, "callId required", ErrorCodes.INVALID_REQUEST);
433497return;
434498}
435499const rt = await ensureRuntime();
436500const result = await rt.manager.endCall(callId);
437501if (!result.success) {
438-respond(false, { error: result.error || "end failed" });
502+respondError(respond, result.error || "end failed");
439503return;
440504}
441505respond(true, { success: true });
@@ -476,7 +540,7 @@ export default definePluginEntry({
476540const message = normalizeOptionalString(params?.message) ?? "";
477541const dtmfSequence = normalizeOptionalString(params?.dtmfSequence);
478542if (!to) {
479-respond(false, { error: "to required" });
543+respondError(respond, "to required", ErrorCodes.INVALID_REQUEST);
480544return;
481545}
482546const rt = await ensureRuntime();
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。