Skip to content

Kody is live

Watch the launch video — what Kody is, and why it exists.

← Public packages

@kentcdodds/jev

Call TypeSafe Jev evaluations via Cloudflare AI Gateway (same token/gateway as @kentcdodds/ai).

src/resolve.ts

126 lines · 3.6 KB · TypeScript
import { packageStorage, packages } from 'kody:runtime'
import {
	DEFAULT_GATEWAY_ID,
	DEFAULT_JEV_MODEL,
} from './types.ts'

const settingNames = {
	accountId: 'cloudflareAccountId',
	gatewayId: 'cloudflareAiGatewayId',
	model: 'cloudflareJevModel',
} as const

export const cloudflareApiTokenPlaceholder = '{{secret:cloudflareApiToken}}'

export const secretsNewUrl =
	'https://kody.codes/account/secrets/new?name=cloudflareApiToken&description=Cloudflare%20API%20token%20for%20Workers%20AI%20%2F%20AI%20Gateway&allowedHosts=api.cloudflare.com%2Cgateway.ai.cloudflare.com&scope=user'

function trimString(value: unknown) {
	return typeof value === 'string' ? value.trim() : ''
}

async function readLocal(name: string) {
	const storage = packageStorage()
	const stored = await storage.get(name)
	return trimString(stored)
}

/**
 * Read account/gateway from @kentcdodds/ai in *that* package's runtime.
 * Static `kody:@` imports stamp into this package and would see empty local
 * storage — invoke is required for cross-package packageStorage reads.
 */
async function readAiFallback(): Promise<{
	accountId: string
	gatewayId: string
	model: string
}> {
	if (typeof packages?.invoke !== 'function') {
		return { accountId: '', gatewayId: '', model: '' }
	}
	try {
		const settings = (await packages.invoke('kody:@kentcdodds/ai', {
			exportName: './settings',
			params: {},
		})) as { accountId?: unknown; gatewayId?: unknown; model?: unknown } | null
		return {
			accountId: trimString(settings?.accountId),
			gatewayId: trimString(settings?.gatewayId),
			model: trimString(settings?.model),
		}
	} catch {
		return { accountId: '', gatewayId: '', model: '' }
	}
}

export async function resolveJevConfig(overrides: {
	accountId?: string
	gatewayId?: string
	model?: string
} = {}) {
	const localAccountId = await readLocal(settingNames.accountId)
	const localGatewayId = await readLocal(settingNames.gatewayId)
	const localModel = await readLocal(settingNames.model)

	let accountId = trimString(overrides.accountId) || localAccountId
	let gatewayId = trimString(overrides.gatewayId) || localGatewayId
	let model = trimString(overrides.model) || localModel

	if (!accountId || !gatewayId) {
		const fromAi = await readAiFallback()
		if (!accountId && fromAi.accountId) {
			accountId = fromAi.accountId
			// Cache into jev storage so later calls stay local
			await packageStorage().set(settingNames.accountId, accountId)
		}
		if (!gatewayId) {
			gatewayId = fromAi.gatewayId || DEFAULT_GATEWAY_ID
			if (fromAi.gatewayId) {
				await packageStorage().set(settingNames.gatewayId, gatewayId)
			}
		}
	}

	if (!gatewayId) gatewayId = DEFAULT_GATEWAY_ID
	if (!model) model = DEFAULT_JEV_MODEL

	return {
		accountId,
		gatewayId,
		model,
		sources: {
			accountId: trimString(overrides.accountId)
				? 'override'
				: localAccountId
					? 'jev-storage'
					: accountId
						? 'ai-settings'
						: 'missing',
			gatewayId: trimString(overrides.gatewayId)
				? 'override'
				: localGatewayId
					? 'jev-storage'
					: fromAiGatewaySource(gatewayId),
			model: trimString(overrides.model)
				? 'override'
				: localModel
					? 'jev-storage'
					: 'default',
		},
	}
}

function fromAiGatewaySource(gatewayId: string) {
	return gatewayId ? 'fallback' : 'missing'
}

export { settingNames, readLocal }

export function missingSetup(accountId: string) {
	if (accountId) return undefined
	return {
		secretsNewUrl,
		settingsHint:
			'Save cloudflareApiToken (user scope, hosts api.cloudflare.com). Then set accountId/gatewayId via kody:@kentcdodds/jev/settings — or leave them empty to reuse values already stored on @kentcdodds/ai/settings (gateway defaults to kody).',
	}
}