import { FUNCTIONS, isFunctionTag, type FunctionTag } from './tags' export const CLASSIFY_TIMEOUT_MS = 3000 // 判斷的是「角色接下來最自然的回應」屬於哪種功能,不是使用者這句話本身的功能。 export function buildClassifyPrompt(userText: string): string { const choices = Object.entries(FUNCTIONS) .filter(([key]) => key !== 'other') .map(([key, label]) => `${key}:${label}`) .join('\n') return [ '使用者正在請助理協助做事。判斷在他這句話之後,一位親切的角色最自然的第一句回應屬於下列哪一種功能。', '只回答功能代號一個詞,不要解釋;都不適合就回答 other。', '', choices, '', '<使用者的話>', userText.slice(0, 600), '', ].join('\n') } // 只認回覆開頭的第一個詞,且必須在白名單內;其餘一律視為判斷失敗。 export function parseFunction(reply: string): FunctionTag | null { const word = reply.trim().toLowerCase().match(/^[a-z_]+/)?.[0] return isFunctionTag(word) ? word : null } export type Complete = (request: { model: string prompt: string maxTokens: number effort: 'low' timeoutMs: number }) => Promise<{ isAnswered: boolean; text?: string }> // 任何失敗(逾時、API 錯誤、空回覆、白名單外)都回傳 null,由呼叫端退回不注入。 export async function classify(complete: Complete, userText: string): Promise { try { const r = await complete({ model: 'haiku', prompt: buildClassifyPrompt(userText), maxTokens: 16, effort: 'low', timeoutMs: CLASSIFY_TIMEOUT_MS, }) return r.isAnswered && r.text ? parseFunction(r.text) : null } catch { return null } }