import { generateText, type CompleteInput, type ToolCall } from './complete.ts' export type ModelStepMessage = { role: 'system' | 'user' | 'assistant' | 'tool' content?: string | null tool_calls?: unknown[] tool_call_id?: string name?: string } export type ModelStepInput = CompleteInput & { messages: Array conversationId?: string useTools?: boolean } export type ModelStepResult = { ok: boolean error?: string text: string reasoningText: string finishReason: string toolCalls: Array conversationId: string provider?: string model?: string } /** Tool schemas hosts should expose when they execute Kody search/execute themselves. */ export function kodyAgentToolDefinitions() { return [ { type: 'function', function: { name: 'search', description: 'Discover Kody capabilities, saved packages, persisted values, integrations, and secret metadata (not secret values). Use a natural-language `query` for ranked matches, or pass `entity` (`"{id}:{type}"` or an array of refs) for exact detail/snippet for one or more hits before you execute.', parameters: { type: 'object', properties: { query: { type: 'string', description: 'Natural language description of what you need, or an exact package UUID / kody id / hosted package URL.', }, entity: { description: 'Exact entity ref like "name:capability", "uuid:package", "name:integration", "user:name:value", or "name:secret", or an array of 1–10 refs.', anyOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }], }, limit: { type: 'number', description: 'Max ranked matches (default 15).', }, }, }, }, }, { type: 'function', function: { name: 'execute', description: 'Run one complete ESM module in the Kody sandbox. Required shape: `export default async function main(input = {}) { ... }`. Prefer `import { kody } from "kody:runtime"` and `kody:@scope/package[/export]` from search/entity detail. Project/slim large payloads before returning. Pass optional `params` as main\'s first argument.', parameters: { type: 'object', properties: { code: { type: 'string', description: 'Full ESM module string with a default export function.', }, params: { type: 'object', description: 'Optional JSON passed as the first argument to main.', }, }, required: ['code'], }, }, }, ] } /** * One model completion only. Does not execute Kody tools. * Host packages should run `kody.search` / `kody.execute` in their own package context. */ export async function runModelStep( input: ModelStepInput = { messages: [] }, ): Promise { const conversationId = input.conversationId || crypto.randomUUID() try { if (!Array.isArray(input.messages) || input.messages.length === 0) { throw new Error('messages must include at least one message.') } const result = await generateText({ ...input, tools: input.useTools === false ? undefined : Array.isArray(input.tools) && input.tools.length > 0 ? input.tools : kodyAgentToolDefinitions(), toolChoice: input.useTools === false ? undefined : input.toolChoice ?? 'auto', }) if ('dryRun' in result) { return { ok: true, text: '', reasoningText: '', finishReason: 'dry_run', toolCalls: [], conversationId, provider: result.provider, model: result.model, } } return { ok: result.ok, error: result.error, text: result.text, reasoningText: result.reasoningText, finishReason: result.finishReason, toolCalls: result.toolCalls, conversationId, provider: result.provider, model: result.model, } } catch (error) { return { ok: false, error: error instanceof Error ? error.message : String(error), text: '', reasoningText: '', finishReason: 'error', toolCalls: [], conversationId, } } } export default runModelStep