Ship Paseo-equivalent provider snapshot, AgentComposerBar, ACP dispatch rewrite with streaming/persist, permission prompts, and agent commands. Follow-up: pane-scoped chat resolution, CoderMessageList tool timeline, WS user-delta replace, and inference orphan tool_call stripping. Archive openspec v2-2; update CHANGELOG and CURRENT. Co-authored-by: Cursor <cursoragent@cursor.com>
149 lines
4.2 KiB
TypeScript
149 lines
4.2 KiB
TypeScript
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<string, unknown> & { 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,
|
|
},
|
|
];
|
|
}
|