mirror of
https://github.com/hansjone/dsh-im-ops.git
synced 2026-10-09 06:30:45 +08:00
183 lines
5.2 KiB
JavaScript
183 lines
5.2 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
|
|
import { TextHarnessBridge } from '../src/channels/shared/text-harness-bridge.mjs';
|
|
|
|
const PNG_1X1 = Buffer.from(
|
|
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=',
|
|
'base64',
|
|
);
|
|
|
|
function memoryState() {
|
|
const sessions = new Map();
|
|
const seen = new Set();
|
|
return {
|
|
sessionFor: (key) => sessions.get(key) ?? null,
|
|
setSession: async (key, value) => sessions.set(key, value),
|
|
clearSession: async (key) => sessions.delete(key),
|
|
hasSeen: (id) => seen.has(id),
|
|
markSeen: async (id) => seen.add(id),
|
|
};
|
|
}
|
|
|
|
function bridgeFixture() {
|
|
const sent = [];
|
|
const prompts = [];
|
|
const bridge = new TextHarnessBridge({
|
|
descriptor: { key: 'test', label: 'Test' },
|
|
bot: { sendText: async (_target, text) => sent.push(text) },
|
|
harness: {
|
|
ensureRunning: async () => true,
|
|
sessionExists: async () => true,
|
|
createSession: async () => 'session-image',
|
|
ask: async (_sessionId, prompt) => {
|
|
prompts.push(prompt);
|
|
return '识别成功';
|
|
},
|
|
},
|
|
state: memoryState(),
|
|
});
|
|
return { bridge, sent, prompts };
|
|
}
|
|
|
|
test('the shared bridge sends image and caption content to Harness', async () => {
|
|
const { bridge, sent, prompts } = bridgeFixture();
|
|
let loads = 0;
|
|
await bridge.accept({
|
|
messageId: 'image-1',
|
|
senderId: 'user-1',
|
|
kind: 'direct',
|
|
conversationId: 'user-1',
|
|
content: '图中是什么?',
|
|
images: [{ async load() { loads += 1; return PNG_1X1; } }],
|
|
replyTarget: {},
|
|
});
|
|
|
|
assert.equal(loads, 1);
|
|
assert.deepEqual(prompts, [[
|
|
{ type: 'text', text: '图中是什么?' },
|
|
{ type: 'image', mediaType: 'image/png', data: PNG_1X1.toString('base64') },
|
|
]]);
|
|
assert.deepEqual(sent, ['识别成功']);
|
|
});
|
|
|
|
test('unaddressed group images are rejected before their bytes are downloaded', async () => {
|
|
const { bridge, sent, prompts } = bridgeFixture();
|
|
let loads = 0;
|
|
await bridge.accept({
|
|
messageId: 'image-2',
|
|
senderId: 'user-1',
|
|
kind: 'group',
|
|
conversationId: 'group-1',
|
|
content: '',
|
|
addressed: false,
|
|
images: [{ async load() { loads += 1; return PNG_1X1; } }],
|
|
replyTarget: {},
|
|
});
|
|
|
|
assert.equal(loads, 0);
|
|
assert.deepEqual(prompts, []);
|
|
assert.deepEqual(sent, []);
|
|
});
|
|
|
|
test('image validation failures receive a specific safe reply', async () => {
|
|
const { bridge, sent, prompts } = bridgeFixture();
|
|
await bridge.accept({
|
|
messageId: 'image-3',
|
|
senderId: 'user-1',
|
|
kind: 'direct',
|
|
conversationId: 'user-1',
|
|
content: '',
|
|
images: [{ data: Buffer.from('not an image') }],
|
|
replyTarget: {},
|
|
});
|
|
|
|
assert.deepEqual(prompts, []);
|
|
assert.deepEqual(sent, ['暂不支持该图片格式,请发送 JPEG、PNG、WebP 或 GIF 图片。']);
|
|
});
|
|
|
|
test('an image caption cannot answer a pending Harness question', async () => {
|
|
const sent = [];
|
|
const questionSent = Promise.withResolvers();
|
|
const answered = Promise.withResolvers();
|
|
let interactionResult;
|
|
let imageLoads = 0;
|
|
const bridge = new TextHarnessBridge({
|
|
descriptor: { key: 'test', label: 'Test' },
|
|
bot: {
|
|
async sendText(_target, text) {
|
|
sent.push(text);
|
|
if (text.includes('请选择环境')) questionSent.resolve();
|
|
},
|
|
},
|
|
harness: {
|
|
ensureRunning: async () => true,
|
|
sessionExists: async () => true,
|
|
createSession: async () => 'session-question',
|
|
async ask(_sessionId, prompt, options) {
|
|
assert.equal(prompt, '开始测试');
|
|
await options.onInteraction({
|
|
kind: 'question',
|
|
interactionId: 'question-1',
|
|
rpcId: 'question-1',
|
|
sessionId: 'session-question',
|
|
payload: {
|
|
questions: [{
|
|
id: 'environment',
|
|
question: '请选择环境',
|
|
options: [{ label: '生产环境' }],
|
|
}],
|
|
},
|
|
async respond(result) {
|
|
interactionResult = result;
|
|
answered.resolve();
|
|
return { accepted: true };
|
|
},
|
|
});
|
|
await answered.promise;
|
|
return '已完成';
|
|
},
|
|
},
|
|
state: memoryState(),
|
|
});
|
|
|
|
const original = bridge.accept({
|
|
messageId: 'question-start',
|
|
senderId: 'user-1',
|
|
kind: 'direct',
|
|
conversationId: 'user-1',
|
|
content: '开始测试',
|
|
replyTarget: {},
|
|
});
|
|
await questionSent.promise;
|
|
|
|
await bridge.accept({
|
|
messageId: 'question-image',
|
|
senderId: 'user-1',
|
|
kind: 'direct',
|
|
conversationId: 'user-1',
|
|
content: '生产环境',
|
|
images: [{ async load() { imageLoads += 1; return PNG_1X1; } }],
|
|
replyTarget: {},
|
|
});
|
|
assert.equal(imageLoads, 0);
|
|
assert.equal(interactionResult, undefined);
|
|
assert.equal(sent.at(-1), '请用文字回答当前问题。');
|
|
|
|
await bridge.accept({
|
|
messageId: 'question-answer',
|
|
senderId: 'user-1',
|
|
kind: 'direct',
|
|
conversationId: 'user-1',
|
|
content: '生产环境',
|
|
replyTarget: {},
|
|
});
|
|
await original;
|
|
assert.deepEqual(interactionResult, {
|
|
ok: true,
|
|
value: {
|
|
sessionId: 'session-question',
|
|
answer: { answers: [{ id: 'environment', selected: ['生产环境'] }] },
|
|
},
|
|
});
|
|
});
|