import assert from 'node:assert/strict'; import test from 'node:test'; import { buildScheduledTaskListPayload, normalizeScheduledTaskManageLlmDecision, resolveScheduledTaskCancelWithLlm, } from './wechat-scheduled-task-manage-llm.mjs'; const tasks = [ { id: 'task-suzhou', title: '每天早上6:30推送苏州市天气预报', recurrence: 'daily', hour: 6, minute: 30, taskSpec: '查询苏州天气并推送', }, { id: 'task-news', title: '每日新闻早报页面(5:30)', recurrence: 'daily', hour: 5, minute: 30, taskSpec: '生成新闻早报页面', }, ]; test('buildScheduledTaskListPayload maps active tasks for llm input', () => { const payload = buildScheduledTaskListPayload(tasks); assert.equal(payload.length, 2); assert.equal(payload[0].taskId, 'task-suzhou'); assert.match(payload[0].taskSpec, /苏州/u); }); test('normalizeScheduledTaskManageLlmDecision accepts valid cancel taskId', () => { const decision = normalizeScheduledTaskManageLlmDecision({ action: 'cancel', taskId: 'task-news', }, tasks); assert.deepEqual(decision, { action: 'cancel', taskId: 'task-news' }); }); test('normalizeScheduledTaskManageLlmDecision rejects unknown taskId', () => { const decision = normalizeScheduledTaskManageLlmDecision({ action: 'cancel', taskId: 'missing', }, tasks); assert.deepEqual(decision, { action: 'none' }); }); test('resolveScheduledTaskCancelWithLlm can pick the matching task', async () => { const reply = await resolveScheduledTaskCancelWithLlm({ text: '取消定时推送苏州天气任务', tasks, llmProviderService: { async createChatCompletion() { return { ok: true, reply: JSON.stringify({ action: 'cancel', taskId: 'task-suzhou' }), }; }, }, }); assert.deepEqual(reply, { action: 'cancel', taskId: 'task-suzhou' }); }); test('resolveScheduledTaskCancelWithLlm returns clarify when ambiguous', async () => { const reply = await resolveScheduledTaskCancelWithLlm({ text: '帮我取消定时任务', tasks, llmProviderService: { async createChatCompletion() { return { ok: true, reply: JSON.stringify({ action: 'clarify', message: '你是想取消苏州天气,还是 5:30 新闻早报?', }), }; }, }, }); assert.equal(reply.action, 'clarify'); assert.match(reply.message, /新闻早报/u); }); test('resolveScheduledTaskCancelWithLlm falls back to another provider when primary is unavailable', async () => { const calls = []; const reply = await resolveScheduledTaskCancelWithLlm({ text: '取消定时推送苏州天气任务', tasks: [tasks[0]], modelProviderKeyId: 'deepseek-key', llmProviderService: { async listKeys() { return [ { id: 'deepseek-key', name: 'DeepSeek', status: 'active', defaultModel: 'deepseek-chat', models: ['deepseek-chat'] }, { id: 'kimi-key', name: 'kimi', status: 'active', defaultModel: 'kimi-k2.6', models: ['kimi-k2.6'] }, ]; }, async createChatCompletion(payload) { calls.push(payload.providerKeyId ?? 'default'); if ((payload.providerKeyId ?? 'default') !== 'kimi-key') { return { ok: false, status: 402, message: '{"error":{"message":"Insufficient Balance"}}' }; } return { ok: true, reply: JSON.stringify({ action: 'cancel', taskId: 'task-suzhou' }), }; }, }, }); assert.deepEqual(reply, { action: 'cancel', taskId: 'task-suzhou' }); assert.deepEqual(calls, ['deepseek-key', 'default', 'kimi-key']); });