import assert from 'node:assert/strict'; import test from 'node:test'; import { createDirectChatService, isDirectChatSessionId, isPortalDirectChatSnapshot, sendDirectChatSessionEvents, shouldExpirePortalDirectChatSnapshot, } from './direct-chat-service.mjs'; test('direct chat is enabled by default', () => { const service = createDirectChatService({ userAuth: {}, llmProviderService: {}, sessionSnapshotService: {}, }); assert.equal(service.getStatus().enabled, true); assert.equal(service.canHandle({ userMessage: { role: 'user', content: [{ type: 'text', text: 'hi' }] }, routingDecision: 'direct_chat', }), true); }); test('direct chat registers session through sessionAccess when provided', async () => { const registered = []; const service = createDirectChatService({ enabled: true, userAuth: {}, sessionAccess: { enabled: true, async registerAgentSession(userId, sessionId, target) { registered.push({ userId, sessionId, target }); }, }, llmProviderService: { async createChatCompletion() { return { ok: true, providerId: 'custom_deepseek', model: 'deepseek-chat', reply: 'ok', usage: { inputTokens: 1, outputTokens: 1 }, }; }, }, sessionSnapshotService: { async get() { return null; }, async save() {}, }, }); const result = await service.run({ userId: 'user-1', requestId: 'req-access', userMessage: { role: 'user', content: [{ type: 'text', text: 'hi' }], metadata: { userVisible: true }, }, }); assert.equal(registered.length, 1); assert.equal(registered[0].target, 'h5-direct'); assert.equal(isDirectChatSessionId(result.sessionId), true); }); test('direct chat creates a direct session, saves snapshot, and bills returned usage', async () => { const registered = []; const saved = []; const billed = []; const llmMessages = []; const service = createDirectChatService({ enabled: true, userAuth: { async registerAgentSession(userId, sessionId, target) { registered.push({ userId, sessionId, target }); }, async getBillingState() { return { lastInputTokens: 10, lastOutputTokens: 5 }; }, async billSessionUsage(userId, sessionId, tokenState, requestId) { billed.push({ userId, sessionId, tokenState, requestId }); return { ok: true, costCents: 1 }; }, }, llmProviderService: { async createChatCompletion({ messages }) { llmMessages.push(messages); return { ok: true, providerId: 'custom_deepseek', model: 'deepseek-chat', reply: '你好,已收到。', usage: { inputTokens: 3, outputTokens: 4 }, }; }, }, sessionSnapshotService: { async get() { return null; }, async save(sessionId, userId, session, messages) { saved.push({ sessionId, userId, session, messages }); }, }, memoryV2: { async resolve() { return { memories: [{ label: 'preference', text: '喜欢简洁回答' }] }; }, }, }); const result = await service.run({ userId: 'user-1', requestId: 'req-1', userMessage: { id: 'msg-user-1', role: 'user', content: [{ type: 'text', text: '打个招呼' }], metadata: { userVisible: true }, }, }); assert.equal(result.ok, true); assert.equal(isDirectChatSessionId(result.sessionId), true); assert.deepEqual(registered, [ { userId: 'user-1', sessionId: result.sessionId, target: 'h5-direct' }, ]); assert.equal(saved.length, 2); assert.equal(saved[1].messages.length, 2); assert.equal(saved[1].messages[1].content[0].text, '你好,已收到。'); assert.match(llmMessages[0][0].content, /喜欢简洁回答/); assert.deepEqual(billed, [{ userId: 'user-1', sessionId: result.sessionId, tokenState: { accumulatedInputTokens: 13, accumulatedOutputTokens: 9, }, requestId: 'req-1', }]); }); test('direct chat run can be replayed through finite session events', async () => { const snapshots = new Map(); const service = createDirectChatService({ enabled: true, userAuth: { async registerAgentSession() {}, async getBillingState() { return null; }, async billSessionUsage() { return null; }, }, llmProviderService: { async createChatCompletion() { return { ok: true, reply: '这是直连回复。', usage: null, }; }, }, sessionSnapshotService: { async get(sessionId) { return snapshots.get(sessionId) ?? null; }, async save(sessionId, userId, session, messages) { snapshots.set(sessionId, { session, messages, userId }); }, }, }); const result = await service.run({ userId: 'user-1', requestId: 'req-e2e', userMessage: { id: 'user-e2e', role: 'user', content: [{ type: 'text', text: '讲个程序员笑话' }], metadata: { userVisible: true }, }, }); const snapshot = snapshots.get(result.sessionId); const res = { statusCode: 200, headers: {}, chunks: [], ended: false, writableEnded: false, status(code) { this.statusCode = code; return this; }, setHeader(key, value) { this.headers[key] = value; }, write(chunk) { this.chunks.push(chunk); }, end() { this.ended = true; this.writableEnded = true; }, }; sendDirectChatSessionEvents({ once() {} }, res, snapshot); const stream = res.chunks.join(''); assert.equal(res.ended, true); assert.equal(res.headers['Content-Type'], 'text/event-stream; charset=utf-8'); assert.match(stream, /"type":"UpdateConversation"/); assert.match(stream, /"type":"Finish"/); assert.match(stream, /这是直连回复。/); }); test('direct chat rejects non-text messages', () => { const service = createDirectChatService({ enabled: true, userAuth: {}, llmProviderService: {}, sessionSnapshotService: {}, }); assert.equal(service.canHandle({ userMessage: { role: 'user', content: [{ type: 'image_url', image_url: { url: 'x' } }], }, }), false); }); test('direct chat rejects page generation tasks so the execution backend can handle them', () => { const service = createDirectChatService({ enabled: true, userAuth: {}, llmProviderService: {}, sessionSnapshotService: {}, }); assert.equal(service.canHandle({ toolMode: 'chat', userMessage: { role: 'user', content: [{ type: 'text', text: '帮我生成一个秋夜诗页面 public/autumn-night-poem.html' }], }, }), false); }); test('direct chat rejects themed article page tasks without explicit html path', () => { const service = createDirectChatService({ enabled: true, userAuth: {}, llmProviderService: {}, sessionSnapshotService: {}, }); assert.equal(service.canHandle({ toolMode: 'chat', userMessage: { role: 'user', content: [{ type: 'text', text: '帮我写个主题文章' }], }, }), false); assert.equal(service.explainCanHandle({ toolMode: 'chat', userMessage: { role: 'user', content: [{ type: 'text', text: '帮我写个主题文章' }], }, }).reason, 'task_execution_required'); }); test('direct chat allows llm-routed replies on regular agent sessions', () => { const service = createDirectChatService({ enabled: true, userAuth: {}, llmProviderService: {}, sessionSnapshotService: {}, }); const userMessage = { role: 'user', content: [{ type: 'text', text: '我想对太湖有更多的了解' }], metadata: { displayText: '我想对太湖有更多的了解' }, }; assert.equal(service.canHandle({ sessionId: '20260704_11', routingDecision: 'direct_chat', userMessage, }), true); assert.equal(service.explainCanHandle({ sessionId: '20260704_11', routingDecision: 'direct_chat', userMessage, }).reason, null); }); test('direct chat run saves portal reply into an existing agent session', async () => { const saved = []; const service = createDirectChatService({ enabled: true, userAuth: { async getBillingState() { return null; }, async billSessionUsage() { return null; }, }, llmProviderService: { async createChatCompletion() { return { ok: true, model: 'deepseek-chat', reply: '太湖是中国第三大淡水湖。', usage: null, }; }, }, sessionSnapshotService: { async get(sessionId) { return { session: { id: sessionId, name: 'New Chat', updated_at: '2026-07-04T00:00:00.000Z' }, messages: [{ id: 'user-1', role: 'user', content: [{ type: 'text', text: 'hi' }], metadata: { userVisible: true }, }], }; }, async save(sessionId, userId, session, messages) { saved.push({ sessionId, userId, session, messages }); }, }, }); const result = await service.run({ userId: 'user-1', sessionId: '20260704_11', requestId: 'req-taihu', routingDecision: 'direct_chat', userMessage: { id: 'user-taihu', role: 'user', content: [{ type: 'text', text: '我想对太湖有更多的了解' }], metadata: { displayText: '我想对太湖有更多的了解', userVisible: true }, }, }); assert.equal(result.sessionId, '20260704_11'); assert.equal(saved.length, 2); assert.equal(saved[1].messages.at(-1)?.metadata?.source, 'portal-direct-chat'); assert.match(saved[1].messages.at(-1)?.content?.[0]?.text ?? '', /太湖/); }); test('portal direct chat snapshot replay stays enabled for dedicated h5direct sessions', () => { const now = new Date().toISOString(); const snapshot = { session: { updated_at: now }, messages: [ { role: 'user', content: [{ type: 'text', text: 'hi' }] }, { role: 'assistant', metadata: { source: 'portal-direct-chat' }, content: [{ type: 'text', text: '你好' }], }, ], }; assert.equal( isPortalDirectChatSnapshot(snapshot, { sessionId: 'h5direct_abc' }), true, ); }); test('portal direct chat snapshot is disabled after agent escalation on regular sessions', () => { const now = new Date().toISOString(); const snapshot = { session: { updated_at: now }, messages: [ { role: 'user', content: [{ type: 'text', text: 'hi' }] }, { role: 'assistant', metadata: { source: 'portal-direct-chat' }, content: [{ type: 'text', text: '你好' }], }, { role: 'user', content: [{ type: 'text', text: '帮我写一个 200 字散文,做成页面' }], }, ], }; assert.equal( isPortalDirectChatSnapshot(snapshot, { sessionId: '20260704_31' }), false, ); }); test('portal direct chat snapshot still replays a completed direct turn on regular sessions', () => { const now = new Date().toISOString(); const snapshot = { session: { updated_at: now }, messages: [ { role: 'user', content: [{ type: 'text', text: '我想对太湖有更多的了解' }] }, { role: 'assistant', metadata: { source: 'portal-direct-chat' }, content: [{ type: 'text', text: '太湖是中国第三大淡水湖。' }], }, ], }; assert.equal( isPortalDirectChatSnapshot(snapshot, { sessionId: '20260704_11' }), true, ); }); test('shouldExpirePortalDirectChatSnapshot detects agent runs after a portal direct turn', async () => { const now = new Date().toISOString(); const snapshot = { session: { updated_at: now }, meta: { synced_at: 1000 }, messages: [ { role: 'user', content: [{ type: 'text', text: 'hi' }] }, { role: 'assistant', metadata: { source: 'portal-direct-chat' }, content: [{ type: 'text', text: '你好' }], }, ], }; const pool = { async query(_sql, params) { assert.deepEqual(params, ['20260704_31', 1000]); return [[{ id: 'agent-run-1' }]]; }, }; assert.equal( await shouldExpirePortalDirectChatSnapshot(pool, '20260704_31', snapshot), true, ); }); test('shouldExpirePortalDirectChatSnapshot keeps pure direct chat sessions fast', async () => { const now = new Date().toISOString(); const snapshot = { session: { updated_at: now }, meta: { synced_at: 1000 }, messages: [ { role: 'user', content: [{ type: 'text', text: '太湖' }] }, { role: 'assistant', metadata: { source: 'portal-direct-chat' }, content: [{ type: 'text', text: '太湖是中国第三大淡水湖。' }], }, ], }; const pool = { async query() { return [[]]; }, }; assert.equal( await shouldExpirePortalDirectChatSnapshot(pool, '20260704_11', snapshot), false, ); });