@hazeljs/messaging 0.2.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +192 -0
- package/README.md +190 -0
- package/dist/adapters/slack.adapter.d.ts +20 -0
- package/dist/adapters/slack.adapter.d.ts.map +1 -0
- package/dist/adapters/slack.adapter.js +28 -0
- package/dist/adapters/slack.adapter.js.map +1 -0
- package/dist/adapters/slack.adapter.test.d.ts +2 -0
- package/dist/adapters/slack.adapter.test.d.ts.map +1 -0
- package/dist/adapters/slack.adapter.test.js +56 -0
- package/dist/adapters/slack.adapter.test.js.map +1 -0
- package/dist/adapters/teams.adapter.d.ts +20 -0
- package/dist/adapters/teams.adapter.d.ts.map +1 -0
- package/dist/adapters/teams.adapter.js +28 -0
- package/dist/adapters/teams.adapter.js.map +1 -0
- package/dist/adapters/teams.adapter.test.d.ts +2 -0
- package/dist/adapters/teams.adapter.test.d.ts.map +1 -0
- package/dist/adapters/teams.adapter.test.js +56 -0
- package/dist/adapters/teams.adapter.test.js.map +1 -0
- package/dist/adapters/telegram.adapter.d.ts +24 -0
- package/dist/adapters/telegram.adapter.d.ts.map +1 -0
- package/dist/adapters/telegram.adapter.js +70 -0
- package/dist/adapters/telegram.adapter.js.map +1 -0
- package/dist/adapters/telegram.adapter.test.d.ts +2 -0
- package/dist/adapters/telegram.adapter.test.d.ts.map +1 -0
- package/dist/adapters/telegram.adapter.test.js +154 -0
- package/dist/adapters/telegram.adapter.test.js.map +1 -0
- package/dist/adapters/viber.adapter.d.ts +29 -0
- package/dist/adapters/viber.adapter.d.ts.map +1 -0
- package/dist/adapters/viber.adapter.js +98 -0
- package/dist/adapters/viber.adapter.js.map +1 -0
- package/dist/adapters/viber.adapter.test.d.ts +2 -0
- package/dist/adapters/viber.adapter.test.d.ts.map +1 -0
- package/dist/adapters/viber.adapter.test.js +89 -0
- package/dist/adapters/viber.adapter.test.js.map +1 -0
- package/dist/adapters/whatsapp.adapter.d.ts +26 -0
- package/dist/adapters/whatsapp.adapter.d.ts.map +1 -0
- package/dist/adapters/whatsapp.adapter.js +87 -0
- package/dist/adapters/whatsapp.adapter.js.map +1 -0
- package/dist/adapters/whatsapp.adapter.test.d.ts +2 -0
- package/dist/adapters/whatsapp.adapter.test.d.ts.map +1 -0
- package/dist/adapters/whatsapp.adapter.test.js +198 -0
- package/dist/adapters/whatsapp.adapter.test.js.map +1 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +31 -0
- package/dist/index.js.map +1 -0
- package/dist/messaging-kafka.bootstrap.d.ts +14 -0
- package/dist/messaging-kafka.bootstrap.d.ts.map +1 -0
- package/dist/messaging-kafka.bootstrap.js +42 -0
- package/dist/messaging-kafka.bootstrap.js.map +1 -0
- package/dist/messaging-kafka.bootstrap.test.d.ts +2 -0
- package/dist/messaging-kafka.bootstrap.test.d.ts.map +1 -0
- package/dist/messaging-kafka.bootstrap.test.js +34 -0
- package/dist/messaging-kafka.bootstrap.test.js.map +1 -0
- package/dist/messaging-kafka.consumer.d.ts +10 -0
- package/dist/messaging-kafka.consumer.d.ts.map +1 -0
- package/dist/messaging-kafka.consumer.js +80 -0
- package/dist/messaging-kafka.consumer.js.map +1 -0
- package/dist/messaging-kafka.consumer.test.d.ts +2 -0
- package/dist/messaging-kafka.consumer.test.d.ts.map +1 -0
- package/dist/messaging-kafka.consumer.test.js +129 -0
- package/dist/messaging-kafka.consumer.test.js.map +1 -0
- package/dist/messaging-kafka.types.d.ts +10 -0
- package/dist/messaging-kafka.types.d.ts.map +1 -0
- package/dist/messaging-kafka.types.js +5 -0
- package/dist/messaging-kafka.types.js.map +1 -0
- package/dist/messaging-kafka.types.test.d.ts +2 -0
- package/dist/messaging-kafka.types.test.d.ts.map +1 -0
- package/dist/messaging-kafka.types.test.js +12 -0
- package/dist/messaging-kafka.types.test.js.map +1 -0
- package/dist/messaging.controller.d.ts +27 -0
- package/dist/messaging.controller.d.ts.map +1 -0
- package/dist/messaging.controller.js +151 -0
- package/dist/messaging.controller.js.map +1 -0
- package/dist/messaging.controller.test.d.ts +2 -0
- package/dist/messaging.controller.test.d.ts.map +1 -0
- package/dist/messaging.controller.test.js +221 -0
- package/dist/messaging.controller.test.js.map +1 -0
- package/dist/messaging.module.d.ts +75 -0
- package/dist/messaging.module.d.ts.map +1 -0
- package/dist/messaging.module.js +160 -0
- package/dist/messaging.module.js.map +1 -0
- package/dist/messaging.module.test.d.ts +2 -0
- package/dist/messaging.module.test.d.ts.map +1 -0
- package/dist/messaging.module.test.js +147 -0
- package/dist/messaging.module.test.js.map +1 -0
- package/dist/messaging.service.d.ts +42 -0
- package/dist/messaging.service.d.ts.map +1 -0
- package/dist/messaging.service.js +109 -0
- package/dist/messaging.service.js.map +1 -0
- package/dist/messaging.service.test.d.ts +2 -0
- package/dist/messaging.service.test.d.ts.map +1 -0
- package/dist/messaging.service.test.js +163 -0
- package/dist/messaging.service.test.js.map +1 -0
- package/dist/store/conversation-context.interface.d.ts +17 -0
- package/dist/store/conversation-context.interface.d.ts.map +1 -0
- package/dist/store/conversation-context.interface.js +7 -0
- package/dist/store/conversation-context.interface.js.map +1 -0
- package/dist/store/memory-conversation-context.d.ts +11 -0
- package/dist/store/memory-conversation-context.d.ts.map +1 -0
- package/dist/store/memory-conversation-context.js +22 -0
- package/dist/store/memory-conversation-context.js.map +1 -0
- package/dist/store/memory-conversation-context.test.d.ts +2 -0
- package/dist/store/memory-conversation-context.test.d.ts.map +1 -0
- package/dist/store/memory-conversation-context.test.js +69 -0
- package/dist/store/memory-conversation-context.test.js.map +1 -0
- package/dist/store/redis-conversation-context.d.ts +24 -0
- package/dist/store/redis-conversation-context.d.ts.map +1 -0
- package/dist/store/redis-conversation-context.js +37 -0
- package/dist/store/redis-conversation-context.js.map +1 -0
- package/dist/store/redis-conversation-context.test.d.ts +2 -0
- package/dist/store/redis-conversation-context.test.d.ts.map +1 -0
- package/dist/store/redis-conversation-context.test.js +85 -0
- package/dist/store/redis-conversation-context.test.js.map +1 -0
- package/dist/types/message.types.d.ts +55 -0
- package/dist/types/message.types.d.ts.map +1 -0
- package/dist/types/message.types.js +7 -0
- package/dist/types/message.types.js.map +1 -0
- package/dist/types/response-handler.types.d.ts +47 -0
- package/dist/types/response-handler.types.d.ts.map +1 -0
- package/dist/types/response-handler.types.js +3 -0
- package/dist/types/response-handler.types.js.map +1 -0
- package/package.json +88 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
/**
|
|
4
|
+
* MessagingService tests
|
|
5
|
+
*/
|
|
6
|
+
const messaging_service_1 = require("./messaging.service");
|
|
7
|
+
const memory_conversation_context_1 = require("./store/memory-conversation-context");
|
|
8
|
+
const createMockMessage = (overrides = {}) => ({
|
|
9
|
+
id: 'msg-1',
|
|
10
|
+
channel: 'telegram',
|
|
11
|
+
conversationId: 'conv-123',
|
|
12
|
+
userId: 'user-456',
|
|
13
|
+
text: 'Hello',
|
|
14
|
+
timestamp: new Date(),
|
|
15
|
+
...overrides,
|
|
16
|
+
});
|
|
17
|
+
describe('MessagingService', () => {
|
|
18
|
+
describe('customHandler', () => {
|
|
19
|
+
it('calls customHandler and returns response', async () => {
|
|
20
|
+
const customHandler = jest.fn().mockResolvedValue('Custom reply');
|
|
21
|
+
const service = new messaging_service_1.MessagingService({
|
|
22
|
+
customHandler,
|
|
23
|
+
});
|
|
24
|
+
const msg = createMockMessage({ text: 'Hi' });
|
|
25
|
+
const reply = await service.handleMessage(msg);
|
|
26
|
+
expect(customHandler).toHaveBeenCalledWith(msg);
|
|
27
|
+
expect(reply).toBe('Custom reply');
|
|
28
|
+
});
|
|
29
|
+
it('appends user and assistant turns to store', async () => {
|
|
30
|
+
const store = new memory_conversation_context_1.MemoryConversationContextStore();
|
|
31
|
+
const customHandler = jest.fn().mockResolvedValue('Echo');
|
|
32
|
+
const service = new messaging_service_1.MessagingService({ customHandler }, store);
|
|
33
|
+
await service.handleMessage(createMockMessage({ text: 'Hello' }));
|
|
34
|
+
const turns = await store.getTurns('default:telegram:user-456');
|
|
35
|
+
expect(turns).toHaveLength(2);
|
|
36
|
+
expect(turns[0]).toEqual({ role: 'user', content: 'Hello' });
|
|
37
|
+
expect(turns[1]).toEqual({ role: 'assistant', content: 'Echo' });
|
|
38
|
+
});
|
|
39
|
+
it('uses sessionId from message when provided', async () => {
|
|
40
|
+
const store = new memory_conversation_context_1.MemoryConversationContextStore();
|
|
41
|
+
const customHandler = jest.fn().mockResolvedValue('OK');
|
|
42
|
+
const service = new messaging_service_1.MessagingService({ customHandler }, store);
|
|
43
|
+
await service.handleMessage(createMockMessage({ sessionId: 'custom-session', text: 'Hi' }));
|
|
44
|
+
const turns = await store.getTurns('custom-session');
|
|
45
|
+
expect(turns).toHaveLength(2);
|
|
46
|
+
});
|
|
47
|
+
it('does not append assistant turn when reply is empty', async () => {
|
|
48
|
+
const store = new memory_conversation_context_1.MemoryConversationContextStore();
|
|
49
|
+
const customHandler = jest.fn().mockResolvedValue('');
|
|
50
|
+
const service = new messaging_service_1.MessagingService({ customHandler }, store);
|
|
51
|
+
const reply = await service.handleMessage(createMockMessage({ text: 'Hi' }));
|
|
52
|
+
expect(reply).toBe('');
|
|
53
|
+
const turns = await store.getTurns('default:telegram:user-456');
|
|
54
|
+
expect(turns).toHaveLength(1);
|
|
55
|
+
expect(turns[0].role).toBe('user');
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
describe('agentHandler', () => {
|
|
59
|
+
it('calls agentHandler and returns string result', async () => {
|
|
60
|
+
const agentHandler = jest.fn().mockResolvedValue('Agent says hi');
|
|
61
|
+
const store = new memory_conversation_context_1.MemoryConversationContextStore();
|
|
62
|
+
const service = new messaging_service_1.MessagingService({ agentHandler }, store);
|
|
63
|
+
const msg = createMockMessage({ text: 'Help' });
|
|
64
|
+
const reply = await service.handleMessage(msg);
|
|
65
|
+
expect(agentHandler).toHaveBeenCalledWith(expect.objectContaining({ message: msg, sessionId: expect.any(String) }));
|
|
66
|
+
expect(reply).toBe('Agent says hi');
|
|
67
|
+
});
|
|
68
|
+
it('handles AgentHandlerResult with response property', async () => {
|
|
69
|
+
const agentHandler = jest.fn().mockResolvedValue({
|
|
70
|
+
response: 'Here is the answer',
|
|
71
|
+
sources: [{ content: 'doc1', score: 0.9 }],
|
|
72
|
+
});
|
|
73
|
+
const service = new messaging_service_1.MessagingService({ agentHandler });
|
|
74
|
+
const reply = await service.handleMessage(createMockMessage({ text: 'Query' }));
|
|
75
|
+
expect(reply).toBe('Here is the answer');
|
|
76
|
+
});
|
|
77
|
+
it('appends turns to store for agentHandler', async () => {
|
|
78
|
+
const store = new memory_conversation_context_1.MemoryConversationContextStore();
|
|
79
|
+
const agentHandler = jest.fn().mockResolvedValue('Done');
|
|
80
|
+
const service = new messaging_service_1.MessagingService({ agentHandler }, store);
|
|
81
|
+
await service.handleMessage(createMockMessage({ text: 'Go' }));
|
|
82
|
+
const turns = await store.getTurns('default:telegram:user-456');
|
|
83
|
+
expect(turns).toHaveLength(2);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
describe('aiProvider (LLM)', () => {
|
|
87
|
+
it('throws when no aiProvider, agentHandler, or customHandler', async () => {
|
|
88
|
+
const service = new messaging_service_1.MessagingService({});
|
|
89
|
+
await expect(service.handleMessage(createMockMessage())).rejects.toThrow('MessagingService requires aiProvider, agentHandler, or customHandler');
|
|
90
|
+
});
|
|
91
|
+
it('calls aiProvider.complete and returns response', async () => {
|
|
92
|
+
const mockComplete = jest.fn().mockResolvedValue({ content: ' LLM reply ' });
|
|
93
|
+
const aiProvider = { complete: mockComplete };
|
|
94
|
+
const service = new messaging_service_1.MessagingService({ aiProvider });
|
|
95
|
+
const reply = await service.handleMessage(createMockMessage({ text: 'Hello' }));
|
|
96
|
+
expect(mockComplete).toHaveBeenCalledWith(expect.objectContaining({
|
|
97
|
+
model: 'gpt-4o-mini',
|
|
98
|
+
messages: expect.arrayContaining([
|
|
99
|
+
expect.objectContaining({ role: 'system' }),
|
|
100
|
+
expect.objectContaining({ role: 'user', content: 'Hello' }),
|
|
101
|
+
]),
|
|
102
|
+
}));
|
|
103
|
+
expect(reply).toBe('LLM reply');
|
|
104
|
+
});
|
|
105
|
+
it('uses fallback when response content is null/undefined', async () => {
|
|
106
|
+
const aiProvider = {
|
|
107
|
+
complete: jest.fn().mockResolvedValue({ content: null }),
|
|
108
|
+
};
|
|
109
|
+
const service = new messaging_service_1.MessagingService({ aiProvider });
|
|
110
|
+
const reply = await service.handleMessage(createMockMessage());
|
|
111
|
+
expect(reply).toBe('I could not generate a response.');
|
|
112
|
+
});
|
|
113
|
+
it('uses RAG when ragService provided', async () => {
|
|
114
|
+
const mockComplete = jest.fn().mockResolvedValue({ content: 'RAG answer' });
|
|
115
|
+
const aiProvider = { complete: mockComplete };
|
|
116
|
+
const ragService = {
|
|
117
|
+
search: jest.fn().mockResolvedValue([
|
|
118
|
+
{ content: 'Doc 1 content', score: 0.9 },
|
|
119
|
+
{ content: 'Doc 2 content', score: 0.8 },
|
|
120
|
+
]),
|
|
121
|
+
};
|
|
122
|
+
const service = new messaging_service_1.MessagingService({ aiProvider, ragService });
|
|
123
|
+
await service.handleMessage(createMockMessage({ text: 'What is X?' }));
|
|
124
|
+
expect(ragService.search).toHaveBeenCalledWith('What is X?', expect.objectContaining({ topK: 5, minScore: 0.5 }));
|
|
125
|
+
const systemContent = mockComplete.mock.calls[0][0].messages[0].content;
|
|
126
|
+
expect(systemContent).toContain('Doc 1 content');
|
|
127
|
+
expect(systemContent).toContain('Doc 2 content');
|
|
128
|
+
});
|
|
129
|
+
it('does not augment prompt when RAG returns no results', async () => {
|
|
130
|
+
const mockComplete = jest.fn().mockResolvedValue({ content: 'Answer' });
|
|
131
|
+
const aiProvider = { complete: mockComplete };
|
|
132
|
+
const ragService = { search: jest.fn().mockResolvedValue([]) };
|
|
133
|
+
const service = new messaging_service_1.MessagingService({ aiProvider, ragService });
|
|
134
|
+
await service.handleMessage(createMockMessage({ text: 'Query' }));
|
|
135
|
+
const systemContent = mockComplete.mock.calls[0][0].messages[0].content;
|
|
136
|
+
expect(systemContent).not.toContain('Context:');
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
describe('clearSession', () => {
|
|
140
|
+
it('clears conversation history', async () => {
|
|
141
|
+
const store = new memory_conversation_context_1.MemoryConversationContextStore();
|
|
142
|
+
const customHandler = jest.fn().mockResolvedValue('Hi');
|
|
143
|
+
const service = new messaging_service_1.MessagingService({ customHandler }, store);
|
|
144
|
+
await service.handleMessage(createMockMessage({ text: 'Hello' }));
|
|
145
|
+
await service.clearSession('default:telegram:user-456');
|
|
146
|
+
const turns = await store.getTurns('default:telegram:user-456');
|
|
147
|
+
expect(turns).toEqual([]);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
describe('getOptions', () => {
|
|
151
|
+
it('returns copy of options', () => {
|
|
152
|
+
const service = new messaging_service_1.MessagingService({
|
|
153
|
+
customHandler: () => 'x',
|
|
154
|
+
model: 'gpt-4',
|
|
155
|
+
});
|
|
156
|
+
const opts = service.getOptions();
|
|
157
|
+
expect(opts.model).toBe('gpt-4');
|
|
158
|
+
expect(opts.customHandler).toBeDefined();
|
|
159
|
+
expect(opts).not.toBe(service.getOptions());
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
//# sourceMappingURL=messaging.service.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"messaging.service.test.js","sourceRoot":"","sources":["../src/messaging.service.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conversation context store - abstract storage for multi-turn history
|
|
3
|
+
* Implementations: memory (dev), Redis (production, horizontally scalable)
|
|
4
|
+
*/
|
|
5
|
+
export interface ConversationTurn {
|
|
6
|
+
role: 'user' | 'assistant';
|
|
7
|
+
content: string;
|
|
8
|
+
}
|
|
9
|
+
export interface IConversationContextStore {
|
|
10
|
+
/** Get conversation turns for a session */
|
|
11
|
+
getTurns(sessionId: string): Promise<ConversationTurn[]>;
|
|
12
|
+
/** Append turn and optionally trim to max count */
|
|
13
|
+
appendTurn(sessionId: string, turn: ConversationTurn, maxTurns?: number): Promise<void>;
|
|
14
|
+
/** Clear session history */
|
|
15
|
+
clearSession(sessionId: string): Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=conversation-context.interface.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conversation-context.interface.d.ts","sourceRoot":"","sources":["../../src/store/conversation-context.interface.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,yBAAyB;IACxC,2CAA2C;IAC3C,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAAC;IAEzD,mDAAmD;IACnD,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,gBAAgB,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAExF,4BAA4B;IAC5B,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAChD"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Conversation context store - abstract storage for multi-turn history
|
|
4
|
+
* Implementations: memory (dev), Redis (production, horizontally scalable)
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
//# sourceMappingURL=conversation-context.interface.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conversation-context.interface.js","sourceRoot":"","sources":["../../src/store/conversation-context.interface.ts"],"names":[],"mappings":";AAAA;;;GAGG"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory conversation context - for dev/single-instance only
|
|
3
|
+
*/
|
|
4
|
+
import type { IConversationContextStore, ConversationTurn } from './conversation-context.interface';
|
|
5
|
+
export declare class MemoryConversationContextStore implements IConversationContextStore {
|
|
6
|
+
private history;
|
|
7
|
+
getTurns(sessionId: string): Promise<ConversationTurn[]>;
|
|
8
|
+
appendTurn(sessionId: string, turn: ConversationTurn, maxTurns?: number): Promise<void>;
|
|
9
|
+
clearSession(sessionId: string): Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=memory-conversation-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-conversation-context.d.ts","sourceRoot":"","sources":["../../src/store/memory-conversation-context.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,yBAAyB,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AAEpG,qBAAa,8BAA+B,YAAW,yBAAyB;IAC9E,OAAO,CAAC,OAAO,CAAyC;IAElD,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAIxD,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,gBAAgB,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOvF,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAGrD"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MemoryConversationContextStore = void 0;
|
|
4
|
+
class MemoryConversationContextStore {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.history = new Map();
|
|
7
|
+
}
|
|
8
|
+
async getTurns(sessionId) {
|
|
9
|
+
return this.history.get(sessionId) ?? [];
|
|
10
|
+
}
|
|
11
|
+
async appendTurn(sessionId, turn, maxTurns) {
|
|
12
|
+
const turns = this.history.get(sessionId) ?? [];
|
|
13
|
+
turns.push(turn);
|
|
14
|
+
const trimmed = maxTurns && turns.length > maxTurns * 2 ? turns.slice(-(maxTurns * 2)) : turns;
|
|
15
|
+
this.history.set(sessionId, trimmed);
|
|
16
|
+
}
|
|
17
|
+
async clearSession(sessionId) {
|
|
18
|
+
this.history.delete(sessionId);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
exports.MemoryConversationContextStore = MemoryConversationContextStore;
|
|
22
|
+
//# sourceMappingURL=memory-conversation-context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-conversation-context.js","sourceRoot":"","sources":["../../src/store/memory-conversation-context.ts"],"names":[],"mappings":";;;AAKA,MAAa,8BAA8B;IAA3C;QACU,YAAO,GAAG,IAAI,GAAG,EAA8B,CAAC;IAgB1D,CAAC;IAdC,KAAK,CAAC,QAAQ,CAAC,SAAiB;QAC9B,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC;IAC3C,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,SAAiB,EAAE,IAAsB,EAAE,QAAiB;QAC3E,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC;QAChD,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACjB,MAAM,OAAO,GAAG,QAAQ,IAAI,KAAK,CAAC,MAAM,GAAG,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;QAC/F,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IACvC,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,SAAiB;QAClC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IACjC,CAAC;CACF;AAjBD,wEAiBC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-conversation-context.test.d.ts","sourceRoot":"","sources":["../../src/store/memory-conversation-context.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
/**
|
|
4
|
+
* MemoryConversationContextStore tests
|
|
5
|
+
*/
|
|
6
|
+
const memory_conversation_context_1 = require("./memory-conversation-context");
|
|
7
|
+
describe('MemoryConversationContextStore', () => {
|
|
8
|
+
let store;
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
store = new memory_conversation_context_1.MemoryConversationContextStore();
|
|
11
|
+
});
|
|
12
|
+
describe('getTurns', () => {
|
|
13
|
+
it('returns empty array for unknown session', async () => {
|
|
14
|
+
const turns = await store.getTurns('unknown-session');
|
|
15
|
+
expect(turns).toEqual([]);
|
|
16
|
+
});
|
|
17
|
+
it('returns stored turns for known session', async () => {
|
|
18
|
+
await store.appendTurn('s1', { role: 'user', content: 'Hello' });
|
|
19
|
+
const turns = await store.getTurns('s1');
|
|
20
|
+
expect(turns).toEqual([{ role: 'user', content: 'Hello' }]);
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
describe('appendTurn', () => {
|
|
24
|
+
it('appends user and assistant turns', async () => {
|
|
25
|
+
await store.appendTurn('s1', { role: 'user', content: 'Hi' });
|
|
26
|
+
await store.appendTurn('s1', { role: 'assistant', content: 'Hello!' });
|
|
27
|
+
const turns = await store.getTurns('s1');
|
|
28
|
+
expect(turns).toHaveLength(2);
|
|
29
|
+
expect(turns[0]).toEqual({ role: 'user', content: 'Hi' });
|
|
30
|
+
expect(turns[1]).toEqual({ role: 'assistant', content: 'Hello!' });
|
|
31
|
+
});
|
|
32
|
+
it('trims to maxTurns*2 when exceeded', async () => {
|
|
33
|
+
for (let i = 0; i < 15; i++) {
|
|
34
|
+
await store.appendTurn('s1', {
|
|
35
|
+
role: i % 2 === 0 ? 'user' : 'assistant',
|
|
36
|
+
content: `msg${i}`,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
const turns = await store.getTurns('s1');
|
|
40
|
+
expect(turns.length).toBeLessThanOrEqual(20);
|
|
41
|
+
expect(turns[turns.length - 1].content).toBe('msg14');
|
|
42
|
+
});
|
|
43
|
+
it('does not trim when under maxTurns*2', async () => {
|
|
44
|
+
for (let i = 0; i < 5; i++) {
|
|
45
|
+
await store.appendTurn('s1', {
|
|
46
|
+
role: i % 2 === 0 ? 'user' : 'assistant',
|
|
47
|
+
content: `msg${i}`,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
const turns = await store.getTurns('s1');
|
|
51
|
+
expect(turns).toHaveLength(5);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
describe('clearSession', () => {
|
|
55
|
+
it('removes session data', async () => {
|
|
56
|
+
await store.appendTurn('s1', { role: 'user', content: 'Hi' });
|
|
57
|
+
await store.clearSession('s1');
|
|
58
|
+
const turns = await store.getTurns('s1');
|
|
59
|
+
expect(turns).toEqual([]);
|
|
60
|
+
});
|
|
61
|
+
it('does not affect other sessions', async () => {
|
|
62
|
+
await store.appendTurn('s1', { role: 'user', content: 'Hi' });
|
|
63
|
+
await store.appendTurn('s2', { role: 'user', content: 'Bye' });
|
|
64
|
+
await store.clearSession('s1');
|
|
65
|
+
expect(await store.getTurns('s2')).toEqual([{ role: 'user', content: 'Bye' }]);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
//# sourceMappingURL=memory-conversation-context.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-conversation-context.test.js","sourceRoot":"","sources":["../../src/store/memory-conversation-context.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Redis-backed conversation context - horizontally scalable
|
|
3
|
+
*/
|
|
4
|
+
import type { IConversationContextStore, ConversationTurn } from './conversation-context.interface';
|
|
5
|
+
export interface RedisConversationContextConfig {
|
|
6
|
+
/** Redis client (ioredis) */
|
|
7
|
+
client: {
|
|
8
|
+
get: (k: string) => Promise<string | null>;
|
|
9
|
+
setex: (k: string, t: number, v: string) => Promise<string>;
|
|
10
|
+
del: (k: string) => Promise<number>;
|
|
11
|
+
};
|
|
12
|
+
/** TTL for context keys in seconds (default: 24h) */
|
|
13
|
+
ttlSeconds?: number;
|
|
14
|
+
}
|
|
15
|
+
export declare class RedisConversationContextStore implements IConversationContextStore {
|
|
16
|
+
private client;
|
|
17
|
+
private ttl;
|
|
18
|
+
constructor(config: RedisConversationContextConfig);
|
|
19
|
+
private key;
|
|
20
|
+
getTurns(sessionId: string): Promise<ConversationTurn[]>;
|
|
21
|
+
appendTurn(sessionId: string, turn: ConversationTurn, maxTurns?: number): Promise<void>;
|
|
22
|
+
clearSession(sessionId: string): Promise<void>;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=redis-conversation-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redis-conversation-context.d.ts","sourceRoot":"","sources":["../../src/store/redis-conversation-context.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,yBAAyB,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AAKpG,MAAM,WAAW,8BAA8B;IAC7C,6BAA6B;IAC7B,MAAM,EAAE;QACN,GAAG,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;QAC3C,KAAK,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;QAC5D,GAAG,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;KACrC,CAAC;IACF,qDAAqD;IACrD,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,qBAAa,6BAA8B,YAAW,yBAAyB;IAC7E,OAAO,CAAC,MAAM,CAA2C;IACzD,OAAO,CAAC,GAAG,CAAS;gBAER,MAAM,EAAE,8BAA8B;IAKlD,OAAO,CAAC,GAAG;IAIL,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAUxD,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,gBAAgB,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQvF,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAGrD"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RedisConversationContextStore = void 0;
|
|
4
|
+
const KEY_PREFIX = 'messaging:context:';
|
|
5
|
+
const DEFAULT_TTL_SEC = 86400; // 24h
|
|
6
|
+
class RedisConversationContextStore {
|
|
7
|
+
constructor(config) {
|
|
8
|
+
this.client = config.client;
|
|
9
|
+
this.ttl = config.ttlSeconds ?? DEFAULT_TTL_SEC;
|
|
10
|
+
}
|
|
11
|
+
key(sessionId) {
|
|
12
|
+
return `${KEY_PREFIX}${sessionId}`;
|
|
13
|
+
}
|
|
14
|
+
async getTurns(sessionId) {
|
|
15
|
+
const raw = await this.client.get(this.key(sessionId));
|
|
16
|
+
if (!raw)
|
|
17
|
+
return [];
|
|
18
|
+
try {
|
|
19
|
+
return JSON.parse(raw);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
async appendTurn(sessionId, turn, maxTurns) {
|
|
26
|
+
const turns = await this.getTurns(sessionId);
|
|
27
|
+
turns.push(turn);
|
|
28
|
+
const trimmed = maxTurns && turns.length > maxTurns * 2 ? turns.slice(-(maxTurns * 2)) : turns;
|
|
29
|
+
const k = this.key(sessionId);
|
|
30
|
+
await this.client.setex(k, this.ttl, JSON.stringify(trimmed));
|
|
31
|
+
}
|
|
32
|
+
async clearSession(sessionId) {
|
|
33
|
+
await this.client.del(this.key(sessionId));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
exports.RedisConversationContextStore = RedisConversationContextStore;
|
|
37
|
+
//# sourceMappingURL=redis-conversation-context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redis-conversation-context.js","sourceRoot":"","sources":["../../src/store/redis-conversation-context.ts"],"names":[],"mappings":";;;AAKA,MAAM,UAAU,GAAG,oBAAoB,CAAC;AACxC,MAAM,eAAe,GAAG,KAAK,CAAC,CAAC,MAAM;AAarC,MAAa,6BAA6B;IAIxC,YAAY,MAAsC;QAChD,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;QAC5B,IAAI,CAAC,GAAG,GAAG,MAAM,CAAC,UAAU,IAAI,eAAe,CAAC;IAClD,CAAC;IAEO,GAAG,CAAC,SAAiB;QAC3B,OAAO,GAAG,UAAU,GAAG,SAAS,EAAE,CAAC;IACrC,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,SAAiB;QAC9B,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC;QACvD,IAAI,CAAC,GAAG;YAAE,OAAO,EAAE,CAAC;QACpB,IAAI,CAAC;YACH,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAuB,CAAC;QAC/C,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,EAAE,CAAC;QACZ,CAAC;IACH,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,SAAiB,EAAE,IAAsB,EAAE,QAAiB;QAC3E,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;QAC7C,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACjB,MAAM,OAAO,GAAG,QAAQ,IAAI,KAAK,CAAC,MAAM,GAAG,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;QAC/F,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAC9B,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;IAChE,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,SAAiB;QAClC,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC;IAC7C,CAAC;CACF;AAlCD,sEAkCC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redis-conversation-context.test.d.ts","sourceRoot":"","sources":["../../src/store/redis-conversation-context.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
/**
|
|
4
|
+
* RedisConversationContextStore tests
|
|
5
|
+
*/
|
|
6
|
+
const redis_conversation_context_1 = require("./redis-conversation-context");
|
|
7
|
+
describe('RedisConversationContextStore', () => {
|
|
8
|
+
let store;
|
|
9
|
+
let mockClient;
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
mockClient = {
|
|
12
|
+
data: new Map(),
|
|
13
|
+
get: jest.fn(async (k) => mockClient.data.get(k) ?? null),
|
|
14
|
+
setex: jest.fn(async (k, _t, v) => {
|
|
15
|
+
mockClient.data.set(k, v);
|
|
16
|
+
return 'OK';
|
|
17
|
+
}),
|
|
18
|
+
del: jest.fn(async (k) => {
|
|
19
|
+
const had = mockClient.data.has(k);
|
|
20
|
+
mockClient.data.delete(k);
|
|
21
|
+
return had ? 1 : 0;
|
|
22
|
+
}),
|
|
23
|
+
};
|
|
24
|
+
store = new redis_conversation_context_1.RedisConversationContextStore({ client: mockClient, ttlSeconds: 3600 });
|
|
25
|
+
});
|
|
26
|
+
describe('getTurns', () => {
|
|
27
|
+
it('returns empty array when key does not exist', async () => {
|
|
28
|
+
const turns = await store.getTurns('s1');
|
|
29
|
+
expect(turns).toEqual([]);
|
|
30
|
+
expect(mockClient.get).toHaveBeenCalledWith('messaging:context:s1');
|
|
31
|
+
});
|
|
32
|
+
it('returns parsed turns from Redis', async () => {
|
|
33
|
+
const stored = JSON.stringify([
|
|
34
|
+
{ role: 'user', content: 'Hi' },
|
|
35
|
+
{ role: 'assistant', content: 'Hello!' },
|
|
36
|
+
]);
|
|
37
|
+
mockClient.data.set('messaging:context:s1', stored);
|
|
38
|
+
const turns = await store.getTurns('s1');
|
|
39
|
+
expect(turns).toEqual([
|
|
40
|
+
{ role: 'user', content: 'Hi' },
|
|
41
|
+
{ role: 'assistant', content: 'Hello!' },
|
|
42
|
+
]);
|
|
43
|
+
});
|
|
44
|
+
it('returns empty array on invalid JSON', async () => {
|
|
45
|
+
mockClient.data.set('messaging:context:s1', 'invalid-json');
|
|
46
|
+
const turns = await store.getTurns('s1');
|
|
47
|
+
expect(turns).toEqual([]);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
describe('appendTurn', () => {
|
|
51
|
+
it('appends turn and persists to Redis', async () => {
|
|
52
|
+
await store.appendTurn('s1', { role: 'user', content: 'Hi' });
|
|
53
|
+
await store.appendTurn('s1', { role: 'assistant', content: 'Hello!' });
|
|
54
|
+
const stored = mockClient.data.get('messaging:context:s1');
|
|
55
|
+
expect(stored).toBeDefined();
|
|
56
|
+
const turns = JSON.parse(stored);
|
|
57
|
+
expect(turns).toHaveLength(2);
|
|
58
|
+
expect(turns[0]).toEqual({ role: 'user', content: 'Hi' });
|
|
59
|
+
expect(turns[1]).toEqual({ role: 'assistant', content: 'Hello!' });
|
|
60
|
+
});
|
|
61
|
+
it('trims to maxTurns*2 when exceeded', async () => {
|
|
62
|
+
for (let i = 0; i < 25; i++) {
|
|
63
|
+
await store.appendTurn('s1', { role: i % 2 === 0 ? 'user' : 'assistant', content: `msg${i}` }, 10);
|
|
64
|
+
}
|
|
65
|
+
const stored = mockClient.data.get('messaging:context:s1');
|
|
66
|
+
const turns = JSON.parse(stored);
|
|
67
|
+
expect(turns.length).toBeLessThanOrEqual(20);
|
|
68
|
+
});
|
|
69
|
+
it('uses default TTL when not provided', async () => {
|
|
70
|
+
const storeDefault = new redis_conversation_context_1.RedisConversationContextStore({ client: mockClient });
|
|
71
|
+
await storeDefault.appendTurn('s1', { role: 'user', content: 'Hi' });
|
|
72
|
+
expect(mockClient.setex).toHaveBeenCalledWith('messaging:context:s1', 86400, expect.any(String));
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
describe('clearSession', () => {
|
|
76
|
+
it('deletes key from Redis', async () => {
|
|
77
|
+
await store.appendTurn('s1', { role: 'user', content: 'Hi' });
|
|
78
|
+
await store.clearSession('s1');
|
|
79
|
+
expect(mockClient.del).toHaveBeenCalledWith('messaging:context:s1');
|
|
80
|
+
const turns = await store.getTurns('s1');
|
|
81
|
+
expect(turns).toEqual([]);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
//# sourceMappingURL=redis-conversation-context.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redis-conversation-context.test.js","sourceRoot":"","sources":["../../src/store/redis-conversation-context.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @hazeljs/messaging - Core message types
|
|
3
|
+
* Channel-agnostic normalized format for multichannel bots
|
|
4
|
+
*/
|
|
5
|
+
/** Supported messaging channels */
|
|
6
|
+
export type MessagingChannel = 'telegram' | 'whatsapp' | 'viber' | 'slack' | 'teams' | 'custom';
|
|
7
|
+
/** Normalized incoming message - same structure regardless of channel */
|
|
8
|
+
export interface IncomingMessage {
|
|
9
|
+
/** Unique message ID */
|
|
10
|
+
id: string;
|
|
11
|
+
/** Channel source */
|
|
12
|
+
channel: MessagingChannel;
|
|
13
|
+
/** Channel-specific conversation/chat ID */
|
|
14
|
+
conversationId: string;
|
|
15
|
+
/** User/sender identifier (channel-specific) */
|
|
16
|
+
userId: string;
|
|
17
|
+
/** Display name when available */
|
|
18
|
+
userName?: string;
|
|
19
|
+
/** Message text content */
|
|
20
|
+
text: string;
|
|
21
|
+
/** Raw payload from channel (for advanced use) */
|
|
22
|
+
rawPayload?: unknown;
|
|
23
|
+
/** Timestamp */
|
|
24
|
+
timestamp: Date;
|
|
25
|
+
/** Session/thread identifier for context */
|
|
26
|
+
sessionId?: string;
|
|
27
|
+
/** Optional metadata */
|
|
28
|
+
metadata?: Record<string, unknown>;
|
|
29
|
+
}
|
|
30
|
+
/** Normalized outgoing message */
|
|
31
|
+
export interface OutgoingMessage {
|
|
32
|
+
/** Target conversation ID */
|
|
33
|
+
conversationId: string;
|
|
34
|
+
/** Response text */
|
|
35
|
+
text: string;
|
|
36
|
+
/** Optional reply to specific message */
|
|
37
|
+
replyToMessageId?: string;
|
|
38
|
+
/** Optional metadata */
|
|
39
|
+
metadata?: Record<string, unknown>;
|
|
40
|
+
}
|
|
41
|
+
/** Channel adapter - receives raw webhook, produces IncomingMessage; sends OutgoingMessage */
|
|
42
|
+
export interface IChannelAdapter {
|
|
43
|
+
readonly channel: MessagingChannel;
|
|
44
|
+
/** Parse webhook payload into IncomingMessage */
|
|
45
|
+
parseIncoming(payload: unknown): IncomingMessage | IncomingMessage[] | null;
|
|
46
|
+
/** Send response to the channel */
|
|
47
|
+
send(message: OutgoingMessage): Promise<void>;
|
|
48
|
+
/** Optional: verify webhook (e.g. signature validation) */
|
|
49
|
+
verifyWebhook?(payload: unknown, headers?: Record<string, string>): boolean;
|
|
50
|
+
}
|
|
51
|
+
/** Handler for incoming messages - receives normalized message, returns response text */
|
|
52
|
+
export type MessageHandler = (message: IncomingMessage) => Promise<string> | string;
|
|
53
|
+
/** LLM response mode */
|
|
54
|
+
export type ResponseMode = 'llm' | 'agent' | 'custom';
|
|
55
|
+
//# sourceMappingURL=message.types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message.types.d.ts","sourceRoot":"","sources":["../../src/types/message.types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,mCAAmC;AACnC,MAAM,MAAM,gBAAgB,GAAG,UAAU,GAAG,UAAU,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,QAAQ,CAAC;AAEhG,yEAAyE;AACzE,MAAM,WAAW,eAAe;IAC9B,wBAAwB;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,qBAAqB;IACrB,OAAO,EAAE,gBAAgB,CAAC;IAC1B,4CAA4C;IAC5C,cAAc,EAAE,MAAM,CAAC;IACvB,gDAAgD;IAChD,MAAM,EAAE,MAAM,CAAC;IACf,kCAAkC;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,2BAA2B;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,kDAAkD;IAClD,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,gBAAgB;IAChB,SAAS,EAAE,IAAI,CAAC;IAChB,4CAA4C;IAC5C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wBAAwB;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,kCAAkC;AAClC,MAAM,WAAW,eAAe;IAC9B,6BAA6B;IAC7B,cAAc,EAAE,MAAM,CAAC;IACvB,oBAAoB;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,yCAAyC;IACzC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,wBAAwB;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,8FAA8F;AAC9F,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC;IACnC,iDAAiD;IACjD,aAAa,CAAC,OAAO,EAAE,OAAO,GAAG,eAAe,GAAG,eAAe,EAAE,GAAG,IAAI,CAAC;IAC5E,mCAAmC;IACnC,IAAI,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9C,2DAA2D;IAC3D,aAAa,CAAC,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC;CAC7E;AAED,yFAAyF;AACzF,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,EAAE,eAAe,KAAK,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC;AAEpF,wBAAwB;AACxB,MAAM,MAAM,YAAY,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"message.types.js","sourceRoot":"","sources":["../../src/types/message.types.ts"],"names":[],"mappings":";AAAA;;;GAGG"}
|