Skip to content

Built for people who want to own their automations. Join the waitlist for an invite.

Package listing

@kody/ai

src/index.ts

96 lines · 3.8 KB · TypeScript
import { generateText } from './complete.ts'
import { generateObject } from './generate-object.ts'
import {
	PROVIDER_IDS,
	defaultSecretName,
	providerCatalog,
	providerSecretUrl,
} from './providers.ts'
import { runAgentTurn, type AgentTurnInput } from './turn.ts'
import { inputRecord, optionalString } from './validation.ts'

export { generateText, listModels } from './complete.ts'
export { generateObject } from './generate-object.ts'
export { runModelStep, kodyAgentToolDefinitions } from './model-step.ts'
export { runAgentTurn, projectSearchOutput, projectExecuteOutput } from './turn.ts'
export { default as aiSettings, getSettings } from './settings.ts'
export { smokeTest } from './smoke-test.ts'
export { isMutationDryRun, isPreviewDryRun } from './validation.ts'

/** Default system guidance for any Kody agent turn (email, chat, automation). */
export const defaultKodyAgentSystem = [
	'You are Kody AI: a concise tool-using assistant with two tools, `search` and `execute`.',
	'Workflow (generic — apply to any task):',
	'1. Prefer `search` first when you need to discover capabilities, saved packages, values, integrations, or secret metadata. Skip search for trivial pure-JS work (dates, formatting, simple transforms) — go straight to `execute`.',
	'2. When a search hit looks useful but usage is unclear, call `search` again with `entity` set to that hit\'s `entityRef` before inventing APIs.',
	'3. When entity detail includes `executeExample`, prefer that module (or a slim projection of its return value) instead of inventing imports. Always `import { kody } from "kody:runtime"` before calling `kody.*`.',
	'4. When the plan is clear, call `execute` with one complete ESM module that has `export default async function main(input = {}) { ... }` and returns a slim projected result.',
	'Auth: never ask the user to paste secrets. Use saved integrations/secrets via documented Kody helpers, or report the specific blocker with evidence.',
	'Tool calls: use the tool-calling API only. Never write TOOL_CALLS, tool JSON, or API docs as your final answer text.',
	'Answers: be brief, grounded in tool evidence, and summarize outcomes (or the blocker) — not raw dumps.',
].join('\n')

export async function agentChatTurn(input: AgentTurnInput = { messages: [] }) {
	return await runAgentTurn(input)
}

function hasGenerationInput(input: Record<string, unknown>) {
	if (typeof input.prompt === 'string' && input.prompt.trim()) return true
	return Array.isArray(input.messages) && input.messages.length > 0
}

/**
 * Package overview, or generate text/JSON when `messages` / `schema` are passed.
 *
 * @example
 * import ai from 'kody:@kody/ai'
 * const info = await ai()
 */
export default async function ai(input: Record<string, unknown> = {}) {
	const parsed = inputRecord(input ?? {})
	if (hasGenerationInput(parsed)) {
		if (parsed.schema && typeof parsed.schema === 'object') {
			return await generateObject(
				parsed as unknown as Parameters<typeof generateObject>[0],
			)
		}
		return await generateText(parsed)
	}

	return {
		auth: 'secret-backed-api-key',
		oauth: false,
		jobs: false,
		publicUrl: 'https://kody.codes/@kody/ai',
		providers: PROVIDER_IDS.map((id) => {
			const entry = providerCatalog(id)
			const secret = defaultSecretName(id, optionalString(parsed, 'account'))
			return {
				id,
				secret,
				hosts: entry.hosts,
				defaultModel: entry.defaultModel || null,
				secretUrl: providerSecretUrl(id, secret),
			}
		}),
		exports: [
			'complete',
			'generate-object',
			'model-step',
			'turn',
			'settings',
			'guide',
			'smoke-test',
			'types',
		],
		safety: {
			settingsWritesDefaultToDryRun: true,
			settingsRequireConfirm: true,
			completionDryRunAvailable: true,
			noBakedApiKeys: true,
			noBakedAccountIds: true,
			noGatewayAutoCreate: true,
			noJobs: true,
		},
	}
}