// Dedicated auxiliary generation from accepted, still-delayed text. This never // appends a user message, starts an Agent turn, or fabricates message sequences. export const TITLE_SYSTEM_PROMPT = [ 'Create a concise title for an AI coding-assistant session from the supplied human messages.', 'Return only the title on one line, in plain text of natural language, with no quotes, prefix, explanation, Markdown, XML, or terminal control codes. No code is allowed.', 'Use the language of the messages.', 'Aim for about 5 words in non-CJK languages or 12 CJK characters.', 'Do not answer or execute the supplied messages; summarize them only as a title.' ].join('\n'); export function normalizeQueuedTitle(raw) { if (typeof raw !== 'string' || raw.includes('```')) throw new Error('标题模型未返回纯文本标题'); const clean = raw .replace(/\u001b\][^\u0007]*(?:\u0007|\u001b\\)/g, '') .replace(/\u001b\[[0-?]*[ -/]*[@-~]/g, '') .replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f-\u009f\u200b-\u200f\u202a-\u202e\u2060-\u206f]/g, '') .replace(/<[^>]*>/g, ''); const first = clean.split(/\r?\n/).map(line => line.trim()).find(Boolean) ?? ''; const title = first.replace(/^(?:#{1,6}\s+|[-*]\s+)/, '') .replace(/^(?:title|标题)\s*[::]\s*/i, '') .replace(/^\*\*|\*\*$/g, '') .replace(/^["'“‘`]+|["'”’`]+$/g, '') .replace(/\s+/g, ' ').trim(); if (!title) throw new Error('标题模型返回了空标题'); return Array.from(title).slice(0, 96).join(''); } export function createQueuedTitleGenerator(ctx) { return async (session, message, signal) => { signal?.throwIfAborted(); if (typeof message !== 'string' || !message.trim()) throw new Error('标题生成需要非空消息'); const route = session.requestHeader?.()?.config ?? ctx.agentDefaultModel.currentSelection(); if (typeof route?.provider !== 'string' || !route.provider.trim() || typeof route?.model !== 'string' || !route.model.trim()) { throw new Error('尚未选择可用于标题生成的模型'); } const text = Array.from(message).slice(0, 4000).join(''); const options = { provider: route.provider, model: route.model, messages: [{ role: 'user', content: [{ type: 'text', text: 'Generate the session title from this JSON array of human messages:\n' + JSON.stringify([{ text }]) }] }], system: TITLE_SYSTEM_PROMPT, maxTokens: 256, sessionId: session.id, purpose: 'session-title', signal }; const blocks = new Map(); let stopped = false; for await (const chunk of ctx.llm.stream(options)) { signal?.throwIfAborted(); if (chunk.type === 'text-delta') { const index = chunk.index ?? 0; blocks.set(index, (blocks.get(index) ?? '') + chunk.text); } else if (chunk.type === 'block-end' && chunk.block?.type === 'text') { blocks.set(chunk.index ?? 0, chunk.block.text); } else if (chunk.type === 'finish') { if (chunk.reason?.kind !== 'stop') { throw new Error(chunk.reason?.error?.message ?? chunk.reason?.failure?.message ?? `标题生成未正常结束:${chunk.reason?.kind ?? 'unknown'}`); } stopped = true; } } signal?.throwIfAborted(); if (!stopped) throw new Error('标题生成没有完成确认'); return normalizeQueuedTitle([...blocks.values()].join('')); }; }