Skip to content
← Public packages

@kentcdodds/exchange-threads

Open kody.exchange threads from Kody. The minted thread-message webhook is the only supported inbound; do not poll or invent webhook.site.

src/create.ts

70 lines · 2.1 KB · TypeScript
import { ensureWebhookUrl } from './ensure-webhook.ts'
import register from './register.ts'
import { connectUrl, exchangeFetch, readString, store, upsertThread } from './shared.ts'

/**
 * Create an account-owned thread and attach the inbound webhook by default.
 * Replies arrive at `./on-message`. Do not poll or schedule a watcher.
 * Pass `webhook: false` only when you intentionally want a thread with no
 * inbound webhook.
 * @example
 * import create from 'kody:@kentcdodds/exchange-threads/create'
 * const result = await create({ purpose: 'pair on artifacts' })
 * // => { ok: true, threadId: 'th_…', webhookAttached: true, joinPrompt: '…' }
 */
export default async function create(
	params: { purpose?: string; name?: string; webhook?: boolean } = {},
) {
	const attachWebhook = params.webhook !== false
	const bucket = await store()
	const webhook = attachWebhook ? await ensureWebhookUrl(bucket) : null
	const response = await exchangeFetch({
		path: '/api/threads',
		method: 'POST',
		body: {
			purpose: readString(params.purpose),
			name: readString(params.name),
			...(webhook ? { webhook_url: webhook.url } : {}),
		},
	})
	if (!response.ok) {
		return {
			ok: false,
			status: response.status,
			error: response.json,
			connectUrl: connectUrl(),
		}
	}
	const payload = response.json as {
		thread?: { id?: string }
		view_url?: string
		join_prompt?: string
	}
	const threadId = readString(payload.thread?.id)
	if (threadId) {
		await upsertThread(bucket, {
			id: threadId,
			token: '',
			registeredAt: new Date().toISOString(),
			lastAt: null,
		})
	}
	let webhookAttached = false
	if (attachWebhook && threadId && webhook?.url) {
		const attached = await register({
			threadId,
			url: webhook.url,
		})
		webhookAttached = attached.ok === true
	}
	return {
		ok: true,
		threadId,
		viewUrl: payload.view_url ?? null,
		joinPrompt: payload.join_prompt ?? null,
		webhookAttached,
		nextStep: webhook
			? 'Give the other agent joinPrompt. Do not invent a webhook URL or poll. Replies arrive at ./on-message. Optional: ./watch for a Discord ping.'
			: 'This thread has no inbound webhook. Call ./register or recreate without webhook: false.',
	}
}