Skip to content

src/handle-wake.ts

62 lines · 2.3 KB · TypeScript
Raw
import { wakeDirect, type WakeInput } from './wake.ts'

function asRecord(value: unknown): Record<string, unknown> | null {
	return value && typeof value === 'object' ? (value as Record<string, unknown>) : null
}

function wakeInputFrom(input: Record<string, unknown> = {}): WakeInput {
	const nested = asRecord(input.params) ?? asRecord(input.event) ?? input
	const images = Array.isArray(nested.images) ? nested.images : undefined
	return {
		bot: typeof nested.bot === 'string' ? nested.bot : '',
		prompt: typeof nested.prompt === 'string' ? nested.prompt : '',
		dryRun: nested.dryRun === true,
		...(typeof nested.discordChannelId === 'string'
			? { discordChannelId: nested.discordChannelId }
			: {}),
		...(typeof nested.discordMessageId === 'string'
			? { discordMessageId: nested.discordMessageId }
			: {}),
		...(typeof nested.emailMessageId === 'string' ? { emailMessageId: nested.emailMessageId } : {}),
		...(typeof nested.emailThreadId === 'string' ? { emailThreadId: nested.emailThreadId } : {}),
		...(typeof nested.emailAlias === 'string' ? { emailAlias: nested.emailAlias } : {}),
		...(typeof nested.emailAddress === 'string' ? { emailAddress: nested.emailAddress } : {}),
		...(images ? { images: images as WakeInput['images'] } : {}),
	}
}

/**
 * Wake a named Grok Bot in this package's own secret context.
 * The `wake` webhook (and this export) land here.
 *
 * Always uses the direct wake path (never re-dispatches).
 *
 * From other packages prefer `import wake from 'kody:@kentcdodds/grok-bot/wake'`
 * with `@kentcdodds/grok-bot` in `kody.dependencies`. Stamp-aligned secret
 * authority resolves package-scoped sender keys without dispatch.
 *
 * @param input.bot - Bot id or name
 * @param input.prompt - Non-empty prompt
 * @param input.dryRun - Validate only; do not POST
 * @returns Slim wake result from `./wake` direct path
 *
 * @example
 * import wake from 'kody:@kentcdodds/grok-bot/wake'
 * const preview = await wake({
 *   bot: 'cole',
 *   prompt: 'Review this Loom on a waitlist thread.',
 *   dryRun: true,
 * })
 */
export default async function handleWake(input: Record<string, unknown> = {}) {
	try {
		// Direct path only — topic/webhook handler must not re-dispatch.
		return await wakeDirect(wakeInputFrom(input))
	} catch (error) {
		return {
			ok: false,
			woke: false,
			error: error instanceof Error ? error.message : String(error),
		}
	}
}