6db460eb89
Memind CI / Test, build, and release guards (push) Failing after 4m34s
When DeepSeek returns 402, try other chat providers before giving up, and cancel the sole active task only during LLM outages so user 123 can cancel without Goose. Co-authored-by: Cursor <cursoragent@cursor.com>
115 lines
3.6 KiB
JavaScript
115 lines
3.6 KiB
JavaScript
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']);
|
||
});
|