/**
* Twitch message actions adapter .
*
* Handles tool - based actions for Twitch , such as sending messages .
*/
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime" ;
import { resolveTwitchAccountContext } from "./config.js" ;
import { twitchOutbound } from "./outbound.js" ;
import type { ChannelMessageActionAdapter, ChannelMessageActionContext } from "./types.js" ;
/**
* Create a tool result with error content .
*/
function errorResponse(error: string) {
return {
content: [
{
type: "text" as const ,
text: JSON.stringify({ ok: false , error }),
},
],
details: { ok: false },
};
}
/**
* Read a string parameter from action arguments .
*
* @ param args - Action arguments
* @ param key - Parameter key
* @ param options - Options for reading the parameter
* @ returns The parameter value or undefined if not found
*/
function readStringParam(
args: Record<string, unknown>,
key: string,
options: { required?: boolean ; trim?: boolean } = {},
): string | undefined {
const value = args[key];
if (value === undefined || value === null ) {
if (options.required) {
throw new Error(`Missing required parameter: ${key}`);
}
return undefined;
}
// Convert value to string safely
if (typeof value === "string" ) {
return options.trim !== false ? value.trim() : value;
}
if (typeof value === "number" || typeof value === "boolean" ) {
const str = String(value);
return options.trim !== false ? str.trim() : str;
}
throw new Error(`Parameter ${key} must be a string, number, or boolean `);
}
/** Supported Twitch actions */
const TWITCH_ACTIONS = new Set(["send" as const ]);
type TwitchAction = typeof TWITCH_ACTIONS extends Set<infer U> ? U : never;
/**
* Twitch message actions adapter .
*/
export const twitchMessageActions: ChannelMessageActionAdapter = {
/**
* List available actions for this channel .
*/
describeMessageTool: () => ({ actions: [...TWITCH_ACTIONS] }),
/**
* Check if an action is supported .
*/
supportsAction: ({ action }) => TWITCH_ACTIONS.has(action as TwitchAction),
/**
* Extract tool send parameters from action arguments .
*
* Parses and validates the " to " and " message " parameters for sending .
*
* @ param params - Arguments from the tool call
* @ returns Parsed send parameters or null if invalid
*
* @ example
* const result = twitchMessageActions . extractToolSend ! ( {
* args : { to : " # mychannel " , message : " Hello ! " }
* } ) ;
* // Returns: { to: "#mychannel", message: "Hello!" }
*/
extractToolSend: ({ args }) => {
try {
const to = readStringParam(args, "to" , { required: true });
const message = readStringParam(args, "message" , { required: true });
if (!to || !message) {
return null ;
}
return { to, message };
} catch {
return null ;
}
},
/**
* Handle an action execution .
*
* Processes the " send " action to send messages to Twitch .
*
* @ param ctx - Action context including action type , parameters , and config
* @ returns Tool result with content or null if action not supported
*
* @ example
* const result = await twitchMessageActions . handleAction ! ( {
* action : " send " ,
* params : { message : " Hello Twitch ! " , to : " # mychannel " } ,
* cfg : openclawConfig ,
* accountId : " default " ,
* } ) ;
*/
handleAction: async (ctx: ChannelMessageActionContext) => {
if (ctx.action !== "send" ) {
return {
content: [{ type: "text" as const , text: "Unsupported action" }],
details: { ok: false , error: "Unsupported action" },
};
}
const message = readStringParam(ctx.params, "message" , { required: true });
const to = readStringParam(ctx.params, "to" , { required: false });
const accountId = ctx.accountId ?? resolveTwitchAccountContext(ctx.cfg).accountId;
const { account, availableAccountIds } = resolveTwitchAccountContext(ctx.cfg, accountId);
if (!account) {
return errorResponse(
`Account not found: ${accountId}. Available accounts: ${availableAccountIds.join(", " ) || "none" }`,
);
}
// Use the channel from account config (or override with `to` parameter)
const targetChannel = to || account.channel;
if (!targetChannel) {
return errorResponse("No channel specified and no default channel in account config" );
}
if (!twitchOutbound.sendText) {
return errorResponse("sendText not implemented" );
}
try {
const result = await twitchOutbound.sendText({
cfg: ctx.cfg,
to: targetChannel,
text: message ?? "" ,
accountId,
});
return {
content: [
{
type: "text" as const ,
text: JSON.stringify(result),
},
],
details: { ok: true },
};
} catch (error) {
const errorMsg = formatErrorMessage(error);
return errorResponse(errorMsg);
}
},
};
Messung V0.5 in Prozent C=94 H=100 G=96
¤ Dauer der Verarbeitung: 0.10 Sekunden
(vorverarbeitet am 2026-06-09)
¤
*© Formatika GbR, Deutschland