@gguf/claw
Version:
WhatsApp gateway CLI (Baileys web) with Pi RPC agent
174 lines (154 loc) • 4.83 kB
text/typescript
/**
* Twitch message actions adapter.
*
* Handles tool-based actions for Twitch, such as sending messages.
*/
import type { ChannelMessageActionAdapter, ChannelMessageActionContext } from "./types.js";
import { DEFAULT_ACCOUNT_ID, getAccountConfig } from "./config.js";
import { twitchOutbound } from "./outbound.js";
/**
* Create a tool result with error content.
*/
function errorResponse(error: string) {
return {
content: [
{
type: "text",
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.
*/
listActions: () => [...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,
): Promise<{ content: Array<{ type: string; text: string }> } | null> => {
if (ctx.action !== "send") {
return null;
}
const message = readStringParam(ctx.params, "message", { required: true });
const to = readStringParam(ctx.params, "to", { required: false });
const accountId = ctx.accountId ?? DEFAULT_ACCOUNT_ID;
const account = getAccountConfig(ctx.cfg, accountId);
if (!account) {
return errorResponse(
`Account not found: ${accountId}. Available accounts: ${Object.keys(ctx.cfg.channels?.twitch?.accounts ?? {}).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",
text: JSON.stringify(result),
},
],
details: { ok: true },
};
} catch (error) {
const errorMsg = error instanceof Error ? error.message : String(error);
return errorResponse(errorMsg);
}
},
};