import { randomUUID } from 'node:crypto'; import type { Sql } from '../db.js'; export const DEFAULT_SKILL_USER_MESSAGE = 'Apply this skill.'; export interface SkillInvokeTransactionResult { synth_assistant_id: string; tool_message_id: string; user_message_id: string; assistant_message_id: string; } export interface SkillInvokeToolCall { id: string; name: 'skill_use'; args: { name: string }; } export type SkillInvokeSessionFrame = Record & { type: string }; export async function runSkillInvokeTransaction( sql: Sql, args: { sessionId: string; chatId: string; skillName: string; skillBody: string; userText: string; }, ): Promise<{ result: SkillInvokeTransactionResult; toolCall: SkillInvokeToolCall }> { const toolCallId = randomUUID(); const toolCall: SkillInvokeToolCall = { id: toolCallId, name: 'skill_use', args: { name: args.skillName }, }; const toolResults = { tool_call_id: toolCallId, output: args.skillBody, truncated: false, }; const result = await sql.begin(async (tx) => { const [synthAssistant] = await tx<{ id: string }[]>` INSERT INTO messages (session_id, chat_id, role, content, status, created_at) VALUES (${args.sessionId}, ${args.chatId}, 'assistant', '', 'complete', clock_timestamp()) RETURNING id `; await tx` INSERT INTO message_parts (message_id, sequence, kind, payload) VALUES (${synthAssistant!.id}, 0, 'tool_call', ${tx.json({ id: toolCallId, name: 'skill_use', args: { name: args.skillName }, } as never)}) `; const [toolMsg] = await tx<{ id: string }[]>` INSERT INTO messages (session_id, chat_id, role, content, status, created_at) VALUES (${args.sessionId}, ${args.chatId}, 'tool', '', 'complete', clock_timestamp()) RETURNING id `; await tx` INSERT INTO message_parts (message_id, sequence, kind, payload) VALUES (${toolMsg!.id}, 0, 'tool_result', ${tx.json(toolResults as never)}) `; const [userMsg] = await tx<{ id: string }[]>` INSERT INTO messages (session_id, chat_id, role, content, status, created_at) VALUES (${args.sessionId}, ${args.chatId}, 'user', ${args.userText}, 'complete', clock_timestamp()) RETURNING id `; const [assistantMsg] = await tx<{ id: string }[]>` INSERT INTO messages (session_id, chat_id, role, content, status, created_at) VALUES (${args.sessionId}, ${args.chatId}, 'assistant', '', 'streaming', clock_timestamp()) RETURNING id `; await tx`UPDATE sessions SET updated_at = clock_timestamp() WHERE id = ${args.sessionId}`; await tx`UPDATE chats SET updated_at = clock_timestamp() WHERE id = ${args.chatId}`; return { synth_assistant_id: synthAssistant!.id, tool_message_id: toolMsg!.id, user_message_id: userMsg!.id, assistant_message_id: assistantMsg!.id, }; }); return { result, toolCall }; } export function buildSkillInvokeSyntheticFrames( chatId: string, result: SkillInvokeTransactionResult, toolCall: SkillInvokeToolCall, skillBody: string, ): SkillInvokeSessionFrame[] { return [ { type: 'message_started', message_id: result.synth_assistant_id, chat_id: chatId, role: 'assistant', }, { type: 'tool_call', message_id: result.synth_assistant_id, chat_id: chatId, tool_call: toolCall, }, { type: 'message_complete', message_id: result.synth_assistant_id, chat_id: chatId, }, { type: 'tool_result', tool_message_id: result.tool_message_id, tool_call_id: toolCall.id, chat_id: chatId, output: skillBody, truncated: false, }, ]; } export function buildSkillInvokeUserFrames( chatId: string, userMessageId: string, userText: string, ): SkillInvokeSessionFrame[] { return [ { type: 'message_started', message_id: userMessageId, chat_id: chatId, role: 'user', }, { type: 'delta', message_id: userMessageId, chat_id: chatId, content: userText, }, { type: 'message_complete', message_id: userMessageId, chat_id: chatId, }, ]; }