@wabot-dev/framework 0.9.22 → 0.9.24
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/dist/src/addon/chat-bot/anthropic/AnthropicChatAdapter.js +9 -6
- package/dist/src/addon/chat-bot/deepseek/DeepSeekChatAdapter.js +1 -0
- package/dist/src/addon/chat-bot/google/GoogleChatAdapter.js +8 -5
- package/dist/src/addon/chat-bot/in-memory/InMemoryChatMemory.js +1 -0
- package/dist/src/addon/chat-bot/in-memory/InMemoryChatRepository.js +1 -0
- package/dist/src/addon/chat-bot/openia/OpenaiChatAdapter.js +9 -6
- package/dist/src/addon/chat-bot/openrouter/OpenRouterChatAdapter.js +9 -6
- package/dist/src/addon/chat-bot/pg/PgChatMemory.js +1 -0
- package/dist/src/addon/chat-bot/pg/PgChatRepository.js +1 -0
- package/dist/src/addon/chat-bot/wabot/WabotChatAdapter.js +1 -0
- package/dist/src/addon/chat-controller/cmd/CmdChannel.js +12 -2
- package/dist/src/addon/chat-controller/cmd/CmdChannelServer.js +1 -1
- package/dist/src/addon/chat-controller/cmd/cmdClientImages.js +132 -0
- package/dist/src/addon/chat-controller/cmd/runCmdClient.js +99 -6
- package/dist/src/addon/chat-controller/telegram/TelegramChannel.js +43 -1
- package/dist/src/feature/chat-bot/ChatBot.js +30 -7
- package/dist/src/feature/chat-bot/ImageDescriber.js +95 -0
- package/dist/src/feature/chat-bot/extractChatMessageText.js +1 -0
- package/dist/src/feature/chat-bot/pendingMediaStartIndex.js +22 -0
- package/dist/src/feature/chat-bot/unconsumedMediaStartIndex.js +27 -0
- package/dist/src/feature/chat-controller/ChatResolver.js +1 -0
- package/dist/src/feature/chat-controller/runChatControllers.js +1 -0
- package/dist/src/feature/project-runner/ProjectRunner.js +1 -0
- package/dist/src/index.d.ts +117 -35
- package/dist/src/index.js +3 -0
- package/package.json +2 -2
|
@@ -7,6 +7,7 @@ import '../../../feature/chat-bot/ChatBot.js';
|
|
|
7
7
|
import '../../../feature/chat-bot/ChatOperator.js';
|
|
8
8
|
import '../../../feature/chat-bot/UnionChatAdapter.js';
|
|
9
9
|
import { extractChatMessageText } from '../../../feature/chat-bot/extractChatMessageText.js';
|
|
10
|
+
import '../../../feature/chat-bot/ImageDescriber.js';
|
|
10
11
|
import { isChatMessageEmpty } from '../../../feature/chat-bot/isChatMessageEmpty.js';
|
|
11
12
|
import { isRetryableError } from '../../../feature/chat-bot/isRetryableError.js';
|
|
12
13
|
import { chatAdapter } from '../../../feature/chat-bot/metadata/@chatAdapter.js';
|
|
@@ -14,6 +15,7 @@ import 'uuid';
|
|
|
14
15
|
import '../../../feature/chat-bot/metadata/ChatBotMetadataStore.js';
|
|
15
16
|
import '../../../feature/chat-bot/metadata/ChatAdapterMetadataStore.js';
|
|
16
17
|
import { safeJsonParse } from '../../../feature/chat-bot/safeJsonParse.js';
|
|
18
|
+
import { unconsumedMediaStartIndex } from '../../../feature/chat-bot/unconsumedMediaStartIndex.js';
|
|
17
19
|
import { Anthropic } from '@anthropic-ai/sdk';
|
|
18
20
|
|
|
19
21
|
const ANTHROPIC_SUPPORTED_IMAGE_MIME_TYPES = [
|
|
@@ -60,10 +62,11 @@ let AnthropicChatAdapter = class AnthropicChatAdapter {
|
|
|
60
62
|
}
|
|
61
63
|
mapChatItems(chatItems) {
|
|
62
64
|
const messages = [];
|
|
63
|
-
|
|
65
|
+
const mediaStart = unconsumedMediaStartIndex(chatItems);
|
|
66
|
+
chatItems.forEach((chatItem, index) => {
|
|
64
67
|
switch (chatItem.type) {
|
|
65
68
|
case 'humanMessage':
|
|
66
|
-
messages.push(this.mapHumanMessage(chatItem.humanMessage));
|
|
69
|
+
messages.push(this.mapHumanMessage(chatItem.humanMessage, index >= mediaStart));
|
|
67
70
|
break;
|
|
68
71
|
case 'botMessage':
|
|
69
72
|
messages.push(this.mapBotMessage(chatItem.botMessage));
|
|
@@ -72,10 +75,10 @@ let AnthropicChatAdapter = class AnthropicChatAdapter {
|
|
|
72
75
|
messages.push(...this.mapFunctionCall(chatItem.functionCall));
|
|
73
76
|
break;
|
|
74
77
|
}
|
|
75
|
-
}
|
|
78
|
+
});
|
|
76
79
|
return messages;
|
|
77
80
|
}
|
|
78
|
-
mapHumanMessage(item) {
|
|
81
|
+
mapHumanMessage(item, includeMedia) {
|
|
79
82
|
if (isChatMessageEmpty(item)) {
|
|
80
83
|
throw new Error('User message content is empty');
|
|
81
84
|
}
|
|
@@ -87,14 +90,14 @@ let AnthropicChatAdapter = class AnthropicChatAdapter {
|
|
|
87
90
|
supportedDocumentMimeTypes: ANTHROPIC_SUPPORTED_DOCUMENT_MIME_TYPES,
|
|
88
91
|
}),
|
|
89
92
|
});
|
|
90
|
-
if (item.images) {
|
|
93
|
+
if (includeMedia && item.images) {
|
|
91
94
|
for (const image of item.images) {
|
|
92
95
|
if (!ANTHROPIC_SUPPORTED_IMAGE_MIME_TYPES.includes(image.mimeType))
|
|
93
96
|
continue;
|
|
94
97
|
blocks.push({ type: 'image', source: this.toAnthropicImageSource(image) });
|
|
95
98
|
}
|
|
96
99
|
}
|
|
97
|
-
if (item.documents) {
|
|
100
|
+
if (includeMedia && item.documents) {
|
|
98
101
|
for (const doc of item.documents) {
|
|
99
102
|
if (!ANTHROPIC_SUPPORTED_DOCUMENT_MIME_TYPES.includes(doc.mimeType))
|
|
100
103
|
continue;
|
|
@@ -6,6 +6,7 @@ import '../../../feature/chat-bot/ChatBot.js';
|
|
|
6
6
|
import '../../../feature/chat-bot/ChatOperator.js';
|
|
7
7
|
import '../../../feature/chat-bot/UnionChatAdapter.js';
|
|
8
8
|
import { extractChatMessageText } from '../../../feature/chat-bot/extractChatMessageText.js';
|
|
9
|
+
import '../../../feature/chat-bot/ImageDescriber.js';
|
|
9
10
|
import { isChatMessageEmpty } from '../../../feature/chat-bot/isChatMessageEmpty.js';
|
|
10
11
|
import { isRetryableError } from '../../../feature/chat-bot/isRetryableError.js';
|
|
11
12
|
import { chatAdapter } from '../../../feature/chat-bot/metadata/@chatAdapter.js';
|
|
@@ -8,6 +8,7 @@ import '../../../feature/chat-bot/ChatBot.js';
|
|
|
8
8
|
import '../../../feature/chat-bot/ChatOperator.js';
|
|
9
9
|
import '../../../feature/chat-bot/UnionChatAdapter.js';
|
|
10
10
|
import { extractChatMessageText } from '../../../feature/chat-bot/extractChatMessageText.js';
|
|
11
|
+
import '../../../feature/chat-bot/ImageDescriber.js';
|
|
11
12
|
import { isChatMessageEmpty } from '../../../feature/chat-bot/isChatMessageEmpty.js';
|
|
12
13
|
import { isRetryableError } from '../../../feature/chat-bot/isRetryableError.js';
|
|
13
14
|
import { chatAdapter } from '../../../feature/chat-bot/metadata/@chatAdapter.js';
|
|
@@ -15,6 +16,7 @@ import 'uuid';
|
|
|
15
16
|
import '../../../feature/chat-bot/metadata/ChatBotMetadataStore.js';
|
|
16
17
|
import '../../../feature/chat-bot/metadata/ChatAdapterMetadataStore.js';
|
|
17
18
|
import { safeJsonParse } from '../../../feature/chat-bot/safeJsonParse.js';
|
|
19
|
+
import { unconsumedMediaStartIndex } from '../../../feature/chat-bot/unconsumedMediaStartIndex.js';
|
|
18
20
|
import { GoogleGenAI } from '@google/genai';
|
|
19
21
|
|
|
20
22
|
const GOOGLE_SUPPORTED_IMAGE_MIME_TYPES = [
|
|
@@ -82,10 +84,11 @@ let GoogleChatAdapter = class GoogleChatAdapter {
|
|
|
82
84
|
}
|
|
83
85
|
async mapChatItems(chatItems) {
|
|
84
86
|
const contents = [];
|
|
85
|
-
|
|
87
|
+
const mediaStart = unconsumedMediaStartIndex(chatItems);
|
|
88
|
+
for (const [index, chatItem] of chatItems.entries()) {
|
|
86
89
|
switch (chatItem.type) {
|
|
87
90
|
case 'humanMessage':
|
|
88
|
-
contents.push(await this.mapHumanMessage(chatItem.humanMessage));
|
|
91
|
+
contents.push(await this.mapHumanMessage(chatItem.humanMessage, index >= mediaStart));
|
|
89
92
|
break;
|
|
90
93
|
case 'botMessage':
|
|
91
94
|
contents.push(this.mapBotMessage(chatItem.botMessage));
|
|
@@ -97,7 +100,7 @@ let GoogleChatAdapter = class GoogleChatAdapter {
|
|
|
97
100
|
}
|
|
98
101
|
return contents;
|
|
99
102
|
}
|
|
100
|
-
async mapHumanMessage(item) {
|
|
103
|
+
async mapHumanMessage(item, includeMedia) {
|
|
101
104
|
if (isChatMessageEmpty(item)) {
|
|
102
105
|
throw new Error('User message content is empty');
|
|
103
106
|
}
|
|
@@ -109,14 +112,14 @@ let GoogleChatAdapter = class GoogleChatAdapter {
|
|
|
109
112
|
}),
|
|
110
113
|
});
|
|
111
114
|
const filesToSend = [];
|
|
112
|
-
if (item.images) {
|
|
115
|
+
if (includeMedia && item.images) {
|
|
113
116
|
for (const image of item.images) {
|
|
114
117
|
if (!GOOGLE_SUPPORTED_IMAGE_MIME_TYPES.includes(image.mimeType))
|
|
115
118
|
continue;
|
|
116
119
|
filesToSend.push(image);
|
|
117
120
|
}
|
|
118
121
|
}
|
|
119
|
-
if (item.documents) {
|
|
122
|
+
if (includeMedia && item.documents) {
|
|
120
123
|
for (const doc of item.documents) {
|
|
121
124
|
if (!GOOGLE_SUPPORTED_DOCUMENT_MIME_TYPES.includes(doc.mimeType))
|
|
122
125
|
continue;
|
|
@@ -6,6 +6,7 @@ import '../../../feature/chat-bot/ChatBot.js';
|
|
|
6
6
|
import { ChatItem } from '../../../feature/chat-bot/ChatItem.js';
|
|
7
7
|
import '../../../feature/chat-bot/ChatOperator.js';
|
|
8
8
|
import '../../../feature/chat-bot/UnionChatAdapter.js';
|
|
9
|
+
import '../../../feature/chat-bot/ImageDescriber.js';
|
|
9
10
|
import '../../../core/injection/index.js';
|
|
10
11
|
import '../../../feature/chat-bot/metadata/ChatAdapterMetadataStore.js';
|
|
11
12
|
import 'uuid';
|
|
@@ -10,6 +10,7 @@ import '../../../feature/chat-bot/ChatAdapterRegistry.js';
|
|
|
10
10
|
import '../../../feature/chat-bot/ChatBot.js';
|
|
11
11
|
import { ChatOperator } from '../../../feature/chat-bot/ChatOperator.js';
|
|
12
12
|
import '../../../feature/chat-bot/UnionChatAdapter.js';
|
|
13
|
+
import '../../../feature/chat-bot/ImageDescriber.js';
|
|
13
14
|
import '../../../feature/chat-bot/metadata/ChatAdapterMetadataStore.js';
|
|
14
15
|
import '../../../feature/chat-bot/metadata/ChatBotMetadataStore.js';
|
|
15
16
|
import '../../../core/error/setupErrorHandlers.js';
|
|
@@ -4,6 +4,7 @@ import '../../../feature/chat-bot/ChatBot.js';
|
|
|
4
4
|
import '../../../feature/chat-bot/ChatOperator.js';
|
|
5
5
|
import '../../../feature/chat-bot/UnionChatAdapter.js';
|
|
6
6
|
import { extractChatMessageText } from '../../../feature/chat-bot/extractChatMessageText.js';
|
|
7
|
+
import '../../../feature/chat-bot/ImageDescriber.js';
|
|
7
8
|
import { isChatMessageEmpty } from '../../../feature/chat-bot/isChatMessageEmpty.js';
|
|
8
9
|
import { isRetryableError } from '../../../feature/chat-bot/isRetryableError.js';
|
|
9
10
|
import { chatAdapter } from '../../../feature/chat-bot/metadata/@chatAdapter.js';
|
|
@@ -12,6 +13,7 @@ import 'uuid';
|
|
|
12
13
|
import '../../../feature/chat-bot/metadata/ChatBotMetadataStore.js';
|
|
13
14
|
import '../../../feature/chat-bot/metadata/ChatAdapterMetadataStore.js';
|
|
14
15
|
import '../../../core/error/setupErrorHandlers.js';
|
|
16
|
+
import { unconsumedMediaStartIndex } from '../../../feature/chat-bot/unconsumedMediaStartIndex.js';
|
|
15
17
|
import { Logger } from '../../../core/logger/Logger.js';
|
|
16
18
|
import { OpenAI } from 'openai';
|
|
17
19
|
|
|
@@ -51,10 +53,11 @@ let OpenaiChatAdapter = class OpenaiChatAdapter {
|
|
|
51
53
|
}
|
|
52
54
|
mapChatItems(chatItems) {
|
|
53
55
|
const openIaInput = [];
|
|
54
|
-
|
|
56
|
+
const mediaStart = unconsumedMediaStartIndex(chatItems);
|
|
57
|
+
chatItems.forEach((chatItem, index) => {
|
|
55
58
|
switch (chatItem.type) {
|
|
56
59
|
case 'humanMessage':
|
|
57
|
-
openIaInput.push(this.mapConectionMessage(chatItem.humanMessage));
|
|
60
|
+
openIaInput.push(this.mapConectionMessage(chatItem.humanMessage, index >= mediaStart));
|
|
58
61
|
break;
|
|
59
62
|
case 'botMessage':
|
|
60
63
|
openIaInput.push(this.mapBotMessage(chatItem.botMessage));
|
|
@@ -63,10 +66,10 @@ let OpenaiChatAdapter = class OpenaiChatAdapter {
|
|
|
63
66
|
openIaInput.push(...this.mapFunctionCall(chatItem.functionCall));
|
|
64
67
|
break;
|
|
65
68
|
}
|
|
66
|
-
}
|
|
69
|
+
});
|
|
67
70
|
return openIaInput;
|
|
68
71
|
}
|
|
69
|
-
mapConectionMessage(item) {
|
|
72
|
+
mapConectionMessage(item, includeMedia) {
|
|
70
73
|
if (isChatMessageEmpty(item)) {
|
|
71
74
|
throw new Error('User message content is empty');
|
|
72
75
|
}
|
|
@@ -78,7 +81,7 @@ let OpenaiChatAdapter = class OpenaiChatAdapter {
|
|
|
78
81
|
supportedDocumentMimeTypes: OPENAI_SUPPORTED_DOCUMENT_MIME_TYPES,
|
|
79
82
|
}),
|
|
80
83
|
});
|
|
81
|
-
if (item.images) {
|
|
84
|
+
if (includeMedia && item.images) {
|
|
82
85
|
for (const image of item.images) {
|
|
83
86
|
if (!OPENAI_SUPPORTED_IMAGE_MIME_TYPES.includes(image.mimeType))
|
|
84
87
|
continue;
|
|
@@ -89,7 +92,7 @@ let OpenaiChatAdapter = class OpenaiChatAdapter {
|
|
|
89
92
|
});
|
|
90
93
|
}
|
|
91
94
|
}
|
|
92
|
-
if (item.documents) {
|
|
95
|
+
if (includeMedia && item.documents) {
|
|
93
96
|
for (const doc of item.documents) {
|
|
94
97
|
if (!OPENAI_SUPPORTED_DOCUMENT_MIME_TYPES.includes(doc.mimeType))
|
|
95
98
|
continue;
|
|
@@ -7,6 +7,7 @@ import '../../../feature/chat-bot/ChatBot.js';
|
|
|
7
7
|
import '../../../feature/chat-bot/ChatOperator.js';
|
|
8
8
|
import '../../../feature/chat-bot/UnionChatAdapter.js';
|
|
9
9
|
import { extractChatMessageText } from '../../../feature/chat-bot/extractChatMessageText.js';
|
|
10
|
+
import '../../../feature/chat-bot/ImageDescriber.js';
|
|
10
11
|
import { isChatMessageEmpty } from '../../../feature/chat-bot/isChatMessageEmpty.js';
|
|
11
12
|
import { isRetryableError } from '../../../feature/chat-bot/isRetryableError.js';
|
|
12
13
|
import { chatAdapter } from '../../../feature/chat-bot/metadata/@chatAdapter.js';
|
|
@@ -14,6 +15,7 @@ import 'uuid';
|
|
|
14
15
|
import '../../../feature/chat-bot/metadata/ChatBotMetadataStore.js';
|
|
15
16
|
import '../../../feature/chat-bot/metadata/ChatAdapterMetadataStore.js';
|
|
16
17
|
import '../../../core/error/setupErrorHandlers.js';
|
|
18
|
+
import { unconsumedMediaStartIndex } from '../../../feature/chat-bot/unconsumedMediaStartIndex.js';
|
|
17
19
|
import { OpenRouter } from '@openrouter/sdk';
|
|
18
20
|
|
|
19
21
|
const OPENROUTER_SUPPORTED_IMAGE_MIME_TYPES = [
|
|
@@ -66,10 +68,11 @@ let OpenRouterChatAdapter = class OpenRouterChatAdapter {
|
|
|
66
68
|
}
|
|
67
69
|
mapChatItems(chatItems) {
|
|
68
70
|
const messages = [];
|
|
69
|
-
|
|
71
|
+
const mediaStart = unconsumedMediaStartIndex(chatItems);
|
|
72
|
+
chatItems.forEach((chatItem, index) => {
|
|
70
73
|
switch (chatItem.type) {
|
|
71
74
|
case 'humanMessage':
|
|
72
|
-
messages.push(this.mapHumanMessage(chatItem.humanMessage));
|
|
75
|
+
messages.push(this.mapHumanMessage(chatItem.humanMessage, index >= mediaStart));
|
|
73
76
|
break;
|
|
74
77
|
case 'botMessage':
|
|
75
78
|
messages.push(this.mapBotMessage(chatItem.botMessage));
|
|
@@ -78,10 +81,10 @@ let OpenRouterChatAdapter = class OpenRouterChatAdapter {
|
|
|
78
81
|
messages.push(...this.mapFunctionCall(chatItem.functionCall));
|
|
79
82
|
break;
|
|
80
83
|
}
|
|
81
|
-
}
|
|
84
|
+
});
|
|
82
85
|
return messages;
|
|
83
86
|
}
|
|
84
|
-
mapHumanMessage(item) {
|
|
87
|
+
mapHumanMessage(item, includeMedia) {
|
|
85
88
|
if (isChatMessageEmpty(item)) {
|
|
86
89
|
throw new Error('User message content is empty');
|
|
87
90
|
}
|
|
@@ -90,7 +93,7 @@ let OpenRouterChatAdapter = class OpenRouterChatAdapter {
|
|
|
90
93
|
supportedImageMimeTypes: OPENROUTER_SUPPORTED_IMAGE_MIME_TYPES,
|
|
91
94
|
supportedDocumentMimeTypes: OPENROUTER_SUPPORTED_DOCUMENT_MIME_TYPES,
|
|
92
95
|
}));
|
|
93
|
-
if (item.images) {
|
|
96
|
+
if (includeMedia && item.images) {
|
|
94
97
|
for (const image of item.images) {
|
|
95
98
|
if (!OPENROUTER_SUPPORTED_IMAGE_MIME_TYPES.includes(image.mimeType))
|
|
96
99
|
continue;
|
|
@@ -99,7 +102,7 @@ let OpenRouterChatAdapter = class OpenRouterChatAdapter {
|
|
|
99
102
|
contentParts.push(imageUrl);
|
|
100
103
|
}
|
|
101
104
|
}
|
|
102
|
-
if (item.documents) {
|
|
105
|
+
if (includeMedia && item.documents) {
|
|
103
106
|
for (const doc of item.documents) {
|
|
104
107
|
if (!OPENROUTER_SUPPORTED_DOCUMENT_MIME_TYPES.includes(doc.mimeType))
|
|
105
108
|
continue;
|
|
@@ -13,6 +13,7 @@ import '../../../feature/chat-bot/ChatBot.js';
|
|
|
13
13
|
import { ChatItem } from '../../../feature/chat-bot/ChatItem.js';
|
|
14
14
|
import '../../../feature/chat-bot/ChatOperator.js';
|
|
15
15
|
import '../../../feature/chat-bot/UnionChatAdapter.js';
|
|
16
|
+
import '../../../feature/chat-bot/ImageDescriber.js';
|
|
16
17
|
import '../../../feature/chat-bot/metadata/ChatAdapterMetadataStore.js';
|
|
17
18
|
import 'uuid';
|
|
18
19
|
import '../../../feature/chat-bot/metadata/ChatBotMetadataStore.js';
|
|
@@ -16,6 +16,7 @@ import '../../../feature/chat-bot/ChatAdapterRegistry.js';
|
|
|
16
16
|
import '../../../feature/chat-bot/ChatBot.js';
|
|
17
17
|
import { ChatOperator } from '../../../feature/chat-bot/ChatOperator.js';
|
|
18
18
|
import '../../../feature/chat-bot/UnionChatAdapter.js';
|
|
19
|
+
import '../../../feature/chat-bot/ImageDescriber.js';
|
|
19
20
|
import '../../../feature/chat-bot/metadata/ChatAdapterMetadataStore.js';
|
|
20
21
|
import 'uuid';
|
|
21
22
|
import '../../../feature/chat-bot/metadata/ChatBotMetadataStore.js';
|
|
@@ -8,6 +8,7 @@ import '../../../feature/chat-bot/ChatAdapterRegistry.js';
|
|
|
8
8
|
import '../../../feature/chat-bot/ChatBot.js';
|
|
9
9
|
import '../../../feature/chat-bot/ChatOperator.js';
|
|
10
10
|
import '../../../feature/chat-bot/UnionChatAdapter.js';
|
|
11
|
+
import '../../../feature/chat-bot/ImageDescriber.js';
|
|
11
12
|
import { chatAdapter } from '../../../feature/chat-bot/metadata/@chatAdapter.js';
|
|
12
13
|
import 'uuid';
|
|
13
14
|
import '../../../feature/chat-bot/metadata/ChatBotMetadataStore.js';
|
|
@@ -33,7 +33,7 @@ let CmdChannel = class CmdChannel {
|
|
|
33
33
|
}
|
|
34
34
|
connect() {
|
|
35
35
|
this.server.register(this.config.route, {
|
|
36
|
-
onMessage: async (text, reply) => {
|
|
36
|
+
onMessage: async ({ text, images }, reply) => {
|
|
37
37
|
if (!this.callBack)
|
|
38
38
|
return;
|
|
39
39
|
this.ensureChatId();
|
|
@@ -46,7 +46,7 @@ let CmdChannel = class CmdChannel {
|
|
|
46
46
|
await this.callBack({
|
|
47
47
|
channel: cmdChannelName,
|
|
48
48
|
chatConnection,
|
|
49
|
-
message: { text },
|
|
49
|
+
message: { text, images: toChatImages(images) },
|
|
50
50
|
reply: async (message) => {
|
|
51
51
|
reply({
|
|
52
52
|
senderName: message.senderName,
|
|
@@ -98,6 +98,16 @@ CmdChannel = CmdChannel_1 = __decorate([
|
|
|
98
98
|
CmdChannelServer,
|
|
99
99
|
CmdChannelConfig])
|
|
100
100
|
], CmdChannel);
|
|
101
|
+
function toChatImages(images) {
|
|
102
|
+
if (!images || images.length === 0)
|
|
103
|
+
return undefined;
|
|
104
|
+
return images.map((image) => ({
|
|
105
|
+
id: Random.alphaNumericLowerCase(10),
|
|
106
|
+
name: image.name,
|
|
107
|
+
mimeType: image.mimeType,
|
|
108
|
+
base64Url: image.base64Url,
|
|
109
|
+
}));
|
|
110
|
+
}
|
|
101
111
|
function extractDisplayText(message) {
|
|
102
112
|
const raw = message.text ?? '';
|
|
103
113
|
const trimmed = raw.trim();
|
|
@@ -146,7 +146,7 @@ let CmdChannelServer = class CmdChannelServer {
|
|
|
146
146
|
this.activeRoute = null;
|
|
147
147
|
return;
|
|
148
148
|
}
|
|
149
|
-
await handlers.onMessage(msg.text, (reply) => {
|
|
149
|
+
await handlers.onMessage({ text: msg.text, images: msg.images }, (reply) => {
|
|
150
150
|
this.sendToClient({ type: 'reply', ...reply });
|
|
151
151
|
});
|
|
152
152
|
return;
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import * as fs from 'node:fs';
|
|
3
|
+
import * as os from 'node:os';
|
|
4
|
+
import * as path from 'node:path';
|
|
5
|
+
|
|
6
|
+
const IMAGE_MIME_BY_EXT = {
|
|
7
|
+
'.png': 'image/png',
|
|
8
|
+
'.jpg': 'image/jpeg',
|
|
9
|
+
'.jpeg': 'image/jpeg',
|
|
10
|
+
'.gif': 'image/gif',
|
|
11
|
+
'.webp': 'image/webp',
|
|
12
|
+
'.heic': 'image/heic',
|
|
13
|
+
'.heif': 'image/heif',
|
|
14
|
+
'.bmp': 'image/bmp',
|
|
15
|
+
};
|
|
16
|
+
/** Image mime type for a file path based on its extension, or null if not an image. */
|
|
17
|
+
function imageMimeForPath(filePath) {
|
|
18
|
+
return IMAGE_MIME_BY_EXT[path.extname(filePath).toLowerCase()] ?? null;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Normalizes a path as a terminal hands it over on drag-and-drop or paste:
|
|
22
|
+
* strips surrounding single/double quotes and unescapes backslash-escaped
|
|
23
|
+
* characters (e.g. `\ ` for spaces).
|
|
24
|
+
*/
|
|
25
|
+
function parseDroppedPath(input) {
|
|
26
|
+
let value = input.trim();
|
|
27
|
+
if (value.length >= 2 &&
|
|
28
|
+
((value.startsWith("'") && value.endsWith("'")) ||
|
|
29
|
+
(value.startsWith('"') && value.endsWith('"')))) {
|
|
30
|
+
value = value.slice(1, -1);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
value = value.replace(/\\(.)/g, '$1');
|
|
34
|
+
}
|
|
35
|
+
return value;
|
|
36
|
+
}
|
|
37
|
+
/** Reads an image file into a wire image, or returns null if it is not a readable image file. */
|
|
38
|
+
function imageFromPath(rawPath) {
|
|
39
|
+
const filePath = parseDroppedPath(rawPath);
|
|
40
|
+
const mimeType = imageMimeForPath(filePath);
|
|
41
|
+
if (!mimeType)
|
|
42
|
+
return null;
|
|
43
|
+
try {
|
|
44
|
+
if (!fs.statSync(filePath).isFile())
|
|
45
|
+
return null;
|
|
46
|
+
const base64 = fs.readFileSync(filePath).toString('base64');
|
|
47
|
+
return {
|
|
48
|
+
name: path.basename(filePath),
|
|
49
|
+
mimeType,
|
|
50
|
+
base64Url: `data:${mimeType};base64,${base64}`,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Reads an image from the system clipboard, cross-platform. Returns null when
|
|
59
|
+
* the clipboard holds no image or the platform tool is unavailable.
|
|
60
|
+
*
|
|
61
|
+
* - macOS: `osascript` (built in)
|
|
62
|
+
* - Linux: `wl-paste` (Wayland) then `xclip` (X11)
|
|
63
|
+
* - Windows: PowerShell `System.Windows.Forms.Clipboard`
|
|
64
|
+
*/
|
|
65
|
+
function readClipboardImage() {
|
|
66
|
+
const tmpFile = path.join(os.tmpdir(), `wabot-clip-${process.pid}-${Date.now()}.png`);
|
|
67
|
+
try {
|
|
68
|
+
if (!dumpClipboardImage(tmpFile))
|
|
69
|
+
return null;
|
|
70
|
+
const stat = fs.statSync(tmpFile);
|
|
71
|
+
if (!stat.isFile() || stat.size === 0)
|
|
72
|
+
return null;
|
|
73
|
+
const base64 = fs.readFileSync(tmpFile).toString('base64');
|
|
74
|
+
return {
|
|
75
|
+
name: 'clipboard.png',
|
|
76
|
+
mimeType: 'image/png',
|
|
77
|
+
base64Url: `data:image/png;base64,${base64}`,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
finally {
|
|
84
|
+
try {
|
|
85
|
+
fs.unlinkSync(tmpFile);
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
// ignore: file may not have been created
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function dumpClipboardImage(outFile) {
|
|
93
|
+
switch (process.platform) {
|
|
94
|
+
case 'darwin': {
|
|
95
|
+
const script = [
|
|
96
|
+
`set theFile to (open for access (POSIX file ${JSON.stringify(outFile)}) with write permission)`,
|
|
97
|
+
'try',
|
|
98
|
+
' set eof theFile to 0',
|
|
99
|
+
' write (the clipboard as «class PNGf») to theFile',
|
|
100
|
+
' close access theFile',
|
|
101
|
+
'on error',
|
|
102
|
+
' close access theFile',
|
|
103
|
+
' error "no image on clipboard"',
|
|
104
|
+
'end try',
|
|
105
|
+
].join('\n');
|
|
106
|
+
return run('osascript', ['-e', script]);
|
|
107
|
+
}
|
|
108
|
+
case 'linux': {
|
|
109
|
+
const quoted = `'${outFile.replace(/'/g, `'\\''`)}'`;
|
|
110
|
+
return (run('sh', ['-c', `wl-paste --type image/png > ${quoted}`]) ||
|
|
111
|
+
run('sh', ['-c', `xclip -selection clipboard -t image/png -o > ${quoted}`]));
|
|
112
|
+
}
|
|
113
|
+
case 'win32': {
|
|
114
|
+
const ps = 'Add-Type -AssemblyName System.Windows.Forms;' +
|
|
115
|
+
'$img=[Windows.Forms.Clipboard]::GetImage();' +
|
|
116
|
+
`if($img -ne $null){$img.Save(${JSON.stringify(outFile)});exit 0}else{exit 1}`;
|
|
117
|
+
return run('powershell', ['-NoProfile', '-Command', ps]);
|
|
118
|
+
}
|
|
119
|
+
default:
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function run(command, args) {
|
|
124
|
+
try {
|
|
125
|
+
return spawnSync(command, args, { stdio: ['ignore', 'ignore', 'ignore'] }).status === 0;
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export { imageFromPath, imageMimeForPath, parseDroppedPath, readClipboardImage };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as net from 'node:net';
|
|
2
2
|
import * as readline from 'node:readline';
|
|
3
3
|
import { cmdChannelSocketPath } from './cmdChannelSocketPath.js';
|
|
4
|
+
import { readClipboardImage, imageFromPath } from './cmdClientImages.js';
|
|
4
5
|
|
|
5
6
|
const useColor = process.stdout.isTTY && !process.env.NO_COLOR && process.env.TERM !== 'dumb';
|
|
6
7
|
const ansi = (code) => (text) => useColor ? `\x1b[${code}m${text}\x1b[0m` : text;
|
|
@@ -11,11 +12,13 @@ const green = ansi('1;32');
|
|
|
11
12
|
const greenText = ansi('32');
|
|
12
13
|
const red = ansi('1;31');
|
|
13
14
|
const yellow = ansi('33');
|
|
14
|
-
const COMMANDS = ['/channels', '/clear', '/help', '/exit'];
|
|
15
|
+
const COMMANDS = ['/channels', '/clear', '/help', '/image', '/paste', '/exit'];
|
|
15
16
|
const HELP_LINES = [
|
|
16
17
|
'Commands:',
|
|
17
18
|
' /channels list channels and switch',
|
|
18
19
|
' /clear start a fresh conversation on the current channel',
|
|
20
|
+
' /image <p> attach an image file (or drag a file into the terminal)',
|
|
21
|
+
' /paste attach an image from the clipboard (or press Ctrl+V)',
|
|
19
22
|
' /help show this help',
|
|
20
23
|
' /exit quit',
|
|
21
24
|
];
|
|
@@ -31,6 +34,11 @@ function runCmdClient() {
|
|
|
31
34
|
let waitingNoticeShown = false;
|
|
32
35
|
let exiting = false;
|
|
33
36
|
let restoring = false;
|
|
37
|
+
let pendingImages = [];
|
|
38
|
+
const chattingPrompt = (route) => {
|
|
39
|
+
const tag = pendingImages.length > 0 ? yellow(` [${pendingImages.length} img]`) : '';
|
|
40
|
+
return cyan(route) + tag + dim(' > ');
|
|
41
|
+
};
|
|
34
42
|
const completer = (line) => {
|
|
35
43
|
if (line.startsWith('/')) {
|
|
36
44
|
const hits = COMMANDS.filter((c) => c.startsWith(line));
|
|
@@ -58,6 +66,41 @@ function runCmdClient() {
|
|
|
58
66
|
socket.write(JSON.stringify(msg) + '\n');
|
|
59
67
|
return true;
|
|
60
68
|
};
|
|
69
|
+
const refreshChatPrompt = () => {
|
|
70
|
+
if (state === 'chatting' && selected)
|
|
71
|
+
rl.setPrompt(chattingPrompt(selected));
|
|
72
|
+
};
|
|
73
|
+
const attachImage = (image, label) => {
|
|
74
|
+
pendingImages.push(image);
|
|
75
|
+
process.stdout.write(green(`[attached ${label}]`) + dim(' — press Enter to send, or type a caption first') + '\n');
|
|
76
|
+
refreshChatPrompt();
|
|
77
|
+
rl.prompt();
|
|
78
|
+
};
|
|
79
|
+
const attachFromClipboard = () => {
|
|
80
|
+
if (state !== 'chatting') {
|
|
81
|
+
process.stderr.write(red('select a channel before pasting an image.') + '\n');
|
|
82
|
+
rl.prompt();
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const image = readClipboardImage();
|
|
86
|
+
if (!image) {
|
|
87
|
+
process.stdout.write(yellow('No image found on the clipboard.') + '\n');
|
|
88
|
+
rl.prompt();
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
attachImage(image, 'clipboard image');
|
|
92
|
+
};
|
|
93
|
+
const sendChatMessage = (text) => {
|
|
94
|
+
const images = pendingImages.length > 0 ? pendingImages : undefined;
|
|
95
|
+
if (!text && !images) {
|
|
96
|
+
rl.prompt();
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (send({ type: 'message', text: text || undefined, images })) {
|
|
100
|
+
pendingImages = [];
|
|
101
|
+
refreshChatPrompt();
|
|
102
|
+
}
|
|
103
|
+
};
|
|
61
104
|
const printChannels = (list) => {
|
|
62
105
|
routes = list;
|
|
63
106
|
if (list.length === 0) {
|
|
@@ -90,7 +133,7 @@ function runCmdClient() {
|
|
|
90
133
|
process.stdout.write(green(`[connected to ${msg.route}]`) + '\n');
|
|
91
134
|
}
|
|
92
135
|
restoring = false;
|
|
93
|
-
rl.setPrompt(
|
|
136
|
+
rl.setPrompt(chattingPrompt(msg.route));
|
|
94
137
|
rl.prompt();
|
|
95
138
|
return;
|
|
96
139
|
case 'reply':
|
|
@@ -128,7 +171,7 @@ function runCmdClient() {
|
|
|
128
171
|
restoring = true;
|
|
129
172
|
socket.write(JSON.stringify({ type: 'select', route: selected }) + '\n');
|
|
130
173
|
state = 'chatting';
|
|
131
|
-
rl.setPrompt(
|
|
174
|
+
rl.setPrompt(chattingPrompt(selected));
|
|
132
175
|
rl.prompt();
|
|
133
176
|
}
|
|
134
177
|
else {
|
|
@@ -188,10 +231,15 @@ function runCmdClient() {
|
|
|
188
231
|
socket.end();
|
|
189
232
|
process.exit(code);
|
|
190
233
|
};
|
|
191
|
-
rl.on('line', (
|
|
234
|
+
rl.on('line', (rawInput) => {
|
|
235
|
+
// Strip stray control chars (e.g. a ^V left behind by the Ctrl+V shortcut).
|
|
236
|
+
const input = rawInput.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f]/g, '');
|
|
192
237
|
const trimmed = input.trim();
|
|
193
238
|
if (!trimmed) {
|
|
194
|
-
|
|
239
|
+
if (state === 'chatting' && pendingImages.length > 0)
|
|
240
|
+
sendChatMessage('');
|
|
241
|
+
else
|
|
242
|
+
rl.prompt();
|
|
195
243
|
return;
|
|
196
244
|
}
|
|
197
245
|
if (trimmed === '/exit' || trimmed.toLowerCase() === 'exit') {
|
|
@@ -213,9 +261,36 @@ function runCmdClient() {
|
|
|
213
261
|
rl.prompt();
|
|
214
262
|
return;
|
|
215
263
|
}
|
|
264
|
+
pendingImages = [];
|
|
265
|
+
refreshChatPrompt();
|
|
216
266
|
send({ type: 'clear' });
|
|
217
267
|
return;
|
|
218
268
|
}
|
|
269
|
+
if (trimmed === '/paste') {
|
|
270
|
+
attachFromClipboard();
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
if (trimmed === '/image' || trimmed.startsWith('/image ')) {
|
|
274
|
+
if (state !== 'chatting') {
|
|
275
|
+
process.stderr.write(red('select a channel before attaching an image.') + '\n');
|
|
276
|
+
rl.prompt();
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
const rawPath = trimmed.slice('/image'.length).trim();
|
|
280
|
+
if (!rawPath) {
|
|
281
|
+
process.stderr.write(red('usage: /image <path-to-image>') + '\n');
|
|
282
|
+
rl.prompt();
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
const image = imageFromPath(rawPath);
|
|
286
|
+
if (!image) {
|
|
287
|
+
process.stderr.write(red(`not a readable image file: ${rawPath}`) + '\n');
|
|
288
|
+
rl.prompt();
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
attachImage(image, image.name ?? 'image');
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
219
294
|
if (state === 'disconnected') {
|
|
220
295
|
process.stderr.write(red('not connected to framework — waiting for server...') + '\n');
|
|
221
296
|
rl.prompt();
|
|
@@ -231,9 +306,27 @@ function runCmdClient() {
|
|
|
231
306
|
send({ type: 'select', route: routes[num - 1].route });
|
|
232
307
|
return;
|
|
233
308
|
}
|
|
234
|
-
|
|
309
|
+
// A bare path to an image file — from drag-and-drop, or a terminal that
|
|
310
|
+
// pastes a file path on Cmd+V — attaches instead of being sent as text.
|
|
311
|
+
const dropped = imageFromPath(trimmed);
|
|
312
|
+
if (dropped) {
|
|
313
|
+
attachImage(dropped, dropped.name ?? 'image');
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
sendChatMessage(trimmed);
|
|
235
317
|
});
|
|
236
318
|
rl.on('close', () => cleanup(0));
|
|
319
|
+
// Ctrl+V pastes a clipboard image. (Cmd+V is handled by the terminal itself,
|
|
320
|
+
// which pastes text/a file path — the latter is picked up as a dropped path.)
|
|
321
|
+
if (process.stdin.isTTY) {
|
|
322
|
+
readline.emitKeypressEvents(process.stdin);
|
|
323
|
+
process.stdin.on('keypress', (_str, key) => {
|
|
324
|
+
if (key && key.ctrl && key.name === 'v') {
|
|
325
|
+
process.stdout.write('\n');
|
|
326
|
+
attachFromClipboard();
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
}
|
|
237
330
|
connect();
|
|
238
331
|
}
|
|
239
332
|
|
|
@@ -2,6 +2,7 @@ import { __decorate, __metadata } from 'tslib';
|
|
|
2
2
|
import { Bot } from 'grammy';
|
|
3
3
|
import { TelegramChannelConfig } from './TelegramChannelConfig.js';
|
|
4
4
|
import { injectable } from '../../../core/injection/index.js';
|
|
5
|
+
import { Logger } from '../../../core/logger/Logger.js';
|
|
5
6
|
import { markdownToTelegramHtml } from './markdownToTelegramHtml.js';
|
|
6
7
|
import { telegramChannelName } from './telegramChannelName.js';
|
|
7
8
|
|
|
@@ -11,6 +12,7 @@ let TelegramChannel = class TelegramChannel {
|
|
|
11
12
|
config;
|
|
12
13
|
static channelName = telegramChannelName;
|
|
13
14
|
bot;
|
|
15
|
+
logger = new Logger('wabot:telegram-channel');
|
|
14
16
|
constructor(config) {
|
|
15
17
|
this.config = config;
|
|
16
18
|
this.bot = new Bot(this.config.botToken);
|
|
@@ -25,13 +27,16 @@ let TelegramChannel = class TelegramChannel {
|
|
|
25
27
|
chatType: ctx.message.chat.type === 'private' ? 'PRIVATE' : 'GROUP',
|
|
26
28
|
channelName: TelegramChannel_1.channelName,
|
|
27
29
|
};
|
|
30
|
+
const { images, documents } = await this.extractMedia(ctx.api, ctx.message);
|
|
28
31
|
await callback({
|
|
29
32
|
channel: telegramChannelName,
|
|
30
33
|
chatConnection,
|
|
31
34
|
message: {
|
|
32
35
|
senderId: ctx.from.id.toString(),
|
|
33
36
|
senderName: ctx.from.first_name,
|
|
34
|
-
text: ctx.message.text,
|
|
37
|
+
text: ctx.message.text ?? ctx.message.caption,
|
|
38
|
+
images: images.length > 0 ? images : undefined,
|
|
39
|
+
documents: documents.length > 0 ? documents : undefined,
|
|
35
40
|
},
|
|
36
41
|
reply: async (replyMessage) => {
|
|
37
42
|
if (!replyMessage.text)
|
|
@@ -43,6 +48,43 @@ let TelegramChannel = class TelegramChannel {
|
|
|
43
48
|
});
|
|
44
49
|
});
|
|
45
50
|
}
|
|
51
|
+
async extractMedia(api, message) {
|
|
52
|
+
const images = [];
|
|
53
|
+
const documents = [];
|
|
54
|
+
if (message.photo && message.photo.length > 0) {
|
|
55
|
+
// Photos arrive in multiple sizes; the last entry is the highest resolution.
|
|
56
|
+
const largest = message.photo[message.photo.length - 1];
|
|
57
|
+
const file = await this.downloadChatFile(api, largest.file_id, largest.file_unique_id, 'image/jpeg');
|
|
58
|
+
if (file)
|
|
59
|
+
images.push(file);
|
|
60
|
+
}
|
|
61
|
+
if (message.document) {
|
|
62
|
+
const doc = message.document;
|
|
63
|
+
const mimeType = doc.mime_type ?? 'application/octet-stream';
|
|
64
|
+
const file = await this.downloadChatFile(api, doc.file_id, doc.file_unique_id, mimeType, doc.file_name);
|
|
65
|
+
// An image sent as an uncompressed file still belongs with the images.
|
|
66
|
+
if (file)
|
|
67
|
+
(mimeType.startsWith('image/') ? images : documents).push(file);
|
|
68
|
+
}
|
|
69
|
+
return { images, documents };
|
|
70
|
+
}
|
|
71
|
+
async downloadChatFile(api, fileId, id, mimeType, name) {
|
|
72
|
+
try {
|
|
73
|
+
const file = await api.getFile(fileId);
|
|
74
|
+
if (!file.file_path)
|
|
75
|
+
return null;
|
|
76
|
+
const url = `https://api.telegram.org/file/bot${this.config.botToken}/${file.file_path}`;
|
|
77
|
+
const res = await fetch(url);
|
|
78
|
+
if (!res.ok)
|
|
79
|
+
throw new Error(`${res.status} ${res.statusText}`);
|
|
80
|
+
const base64 = Buffer.from(await res.arrayBuffer()).toString('base64');
|
|
81
|
+
return { id, name, mimeType, base64Url: `data:${mimeType};base64,${base64}` };
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
this.logger.warn(`failed to download telegram file '${id}'`, err instanceof Error ? { message: err.message } : { err });
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
46
88
|
connect() {
|
|
47
89
|
this.bot.start();
|
|
48
90
|
}
|
|
@@ -5,6 +5,8 @@ import { MindsetOperator } from '../mindset/MindsetOperator.js';
|
|
|
5
5
|
import { ChatAdapter } from './ChatAdapter.js';
|
|
6
6
|
import { ChatItem } from './ChatItem.js';
|
|
7
7
|
import { ChatMemory } from './ChatMemory.js';
|
|
8
|
+
import { ImageDescriber } from './ImageDescriber.js';
|
|
9
|
+
import { pendingMediaStartIndex } from './pendingMediaStartIndex.js';
|
|
8
10
|
import { Logger } from '../../core/logger/Logger.js';
|
|
9
11
|
|
|
10
12
|
const MAX_CONSECUTIVE_INVALID_ARGS = 2;
|
|
@@ -18,13 +20,16 @@ let ChatBot = class ChatBot {
|
|
|
18
20
|
memory;
|
|
19
21
|
adapter;
|
|
20
22
|
mindset;
|
|
23
|
+
imageDescriber;
|
|
21
24
|
logger = new Logger('wabot:chat-bot');
|
|
22
|
-
constructor(memory, adapter, mindset) {
|
|
25
|
+
constructor(memory, adapter, mindset, imageDescriber) {
|
|
23
26
|
this.memory = memory;
|
|
24
27
|
this.adapter = adapter;
|
|
25
28
|
this.mindset = mindset;
|
|
29
|
+
this.imageDescriber = imageDescriber;
|
|
26
30
|
}
|
|
27
31
|
async sendMessage(message, callback) {
|
|
32
|
+
await this.describeImages(message);
|
|
28
33
|
const newChatItem = new ChatItem({
|
|
29
34
|
type: 'humanMessage',
|
|
30
35
|
humanMessage: message,
|
|
@@ -32,6 +37,20 @@ let ChatBot = class ChatBot {
|
|
|
32
37
|
await this.memory.create(newChatItem);
|
|
33
38
|
await this.processLoop(callback, 0);
|
|
34
39
|
}
|
|
40
|
+
// Caption incoming images once, before persisting, so later turns can recall
|
|
41
|
+
// them from the stored description instead of re-sending the binary. The
|
|
42
|
+
// mindset's context/skills/limits focus the description on what matters here.
|
|
43
|
+
async describeImages(message) {
|
|
44
|
+
if (!message.images?.some((image) => !image.description))
|
|
45
|
+
return;
|
|
46
|
+
const [models, context, skills, limits] = await Promise.all([
|
|
47
|
+
this.mindset.resolveModels('visionLlm'),
|
|
48
|
+
this.mindset.context(),
|
|
49
|
+
this.mindset.skills(),
|
|
50
|
+
this.mindset.limits(),
|
|
51
|
+
]);
|
|
52
|
+
await this.imageDescriber.describeMessageImages(message, models, { context, skills, limits });
|
|
53
|
+
}
|
|
35
54
|
async processLoop(callback, invalidArgsCount) {
|
|
36
55
|
const prevItems = await this.memory.findLastItems(16);
|
|
37
56
|
if (prevItems.length === 0) {
|
|
@@ -44,10 +63,13 @@ let ChatBot = class ChatBot {
|
|
|
44
63
|
const systemPrompt = await this.mindset.systemPrompt();
|
|
45
64
|
const tools = this.mindset.tools();
|
|
46
65
|
const identity = await this.mindset.identity();
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
66
|
+
const prevItemsData = prevItems.map((x) => x.getData());
|
|
67
|
+
// Only media from the pending exchange is actually sent to the model; images
|
|
68
|
+
// in already-answered messages are not, so they must not force a vision model.
|
|
69
|
+
const mediaStart = pendingMediaStartIndex(prevItemsData);
|
|
70
|
+
const needsVision = prevItemsData.some((data, index) => index >= mediaStart &&
|
|
71
|
+
data.type === 'humanMessage' &&
|
|
72
|
+
(data.humanMessage.images?.length ?? 0) > 0);
|
|
51
73
|
const kind = needsVision ? 'visionLlm' : 'llm';
|
|
52
74
|
const candidates = await this.mindset.resolveModels(kind);
|
|
53
75
|
if (candidates.length === 0) {
|
|
@@ -57,7 +79,7 @@ let ChatBot = class ChatBot {
|
|
|
57
79
|
models: candidates,
|
|
58
80
|
systemPrompt,
|
|
59
81
|
tools,
|
|
60
|
-
prevItems:
|
|
82
|
+
prevItems: prevItemsData,
|
|
61
83
|
});
|
|
62
84
|
for (const newItemData of newItemsData) {
|
|
63
85
|
if (newItemData.type === 'functionCall') {
|
|
@@ -92,7 +114,8 @@ ChatBot = __decorate([
|
|
|
92
114
|
injectable(),
|
|
93
115
|
__metadata("design:paramtypes", [ChatMemory,
|
|
94
116
|
ChatAdapter,
|
|
95
|
-
MindsetOperator
|
|
117
|
+
MindsetOperator,
|
|
118
|
+
ImageDescriber])
|
|
96
119
|
], ChatBot);
|
|
97
120
|
|
|
98
121
|
export { ChatBot };
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { __decorate, __metadata } from 'tslib';
|
|
2
|
+
import { injectable } from '../../core/injection/index.js';
|
|
3
|
+
import { Logger } from '../../core/logger/Logger.js';
|
|
4
|
+
import { ChatAdapter } from './ChatAdapter.js';
|
|
5
|
+
|
|
6
|
+
const DESCRIBE_INSTRUCTIONS = `
|
|
7
|
+
You describe images so an assistant can reference them later in a conversation
|
|
8
|
+
without seeing the image again.
|
|
9
|
+
|
|
10
|
+
Write a single, factual, self-contained paragraph covering the visible content:
|
|
11
|
+
objects, people, scene, layout, colors, and any readable text. Be thorough but
|
|
12
|
+
concise. Do not add opinions, greetings, or markdown — output only the
|
|
13
|
+
description.
|
|
14
|
+
`.trim();
|
|
15
|
+
function buildSystemPrompt(describeContext) {
|
|
16
|
+
const sections = [DESCRIBE_INSTRUCTIONS];
|
|
17
|
+
const mindsetSections = [
|
|
18
|
+
['Assistant context', describeContext.context],
|
|
19
|
+
['Assistant skills', describeContext.skills],
|
|
20
|
+
['Assistant limits', describeContext.limits],
|
|
21
|
+
].filter(([, value]) => value && value.trim());
|
|
22
|
+
if (mindsetSections.length > 0) {
|
|
23
|
+
sections.push('The assistant you describe images for is outlined below. Use it only to ' +
|
|
24
|
+
'decide which details deserve emphasis; still describe anything else that ' +
|
|
25
|
+
'is clearly visible.');
|
|
26
|
+
for (const [title, value] of mindsetSections) {
|
|
27
|
+
sections.push(`## ${title}\n${value.trim()}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return sections.join('\n\n');
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Generates a text description for images so the conversation can recall their
|
|
34
|
+
* content on later turns without re-sending (and re-analyzing) the binary.
|
|
35
|
+
*
|
|
36
|
+
* The description is produced once, on arrival, with a single vision-model call
|
|
37
|
+
* and stored on the image itself; from then on it travels with the message text
|
|
38
|
+
* via {@link extractChatMessageText}.
|
|
39
|
+
*/
|
|
40
|
+
let ImageDescriber = class ImageDescriber {
|
|
41
|
+
adapter;
|
|
42
|
+
logger = new Logger('wabot:image-describer');
|
|
43
|
+
constructor(adapter) {
|
|
44
|
+
this.adapter = adapter;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Fills `description` for every image on the message that does not have one
|
|
48
|
+
* yet, mutating the images in place. Safe to call with no images or no models:
|
|
49
|
+
* it simply does nothing. Failures are logged and leave the image undescribed
|
|
50
|
+
* rather than aborting the message.
|
|
51
|
+
*/
|
|
52
|
+
async describeMessageImages(message, models, describeContext = {}) {
|
|
53
|
+
const pending = message.images?.filter((image) => !image.description);
|
|
54
|
+
if (!pending || pending.length === 0 || models.length === 0)
|
|
55
|
+
return;
|
|
56
|
+
await Promise.all(pending.map(async (image) => {
|
|
57
|
+
const description = await this.describe(image, models, describeContext);
|
|
58
|
+
if (description)
|
|
59
|
+
image.description = description;
|
|
60
|
+
}));
|
|
61
|
+
}
|
|
62
|
+
async describe(image, models, describeContext = {}) {
|
|
63
|
+
if (models.length === 0)
|
|
64
|
+
return undefined;
|
|
65
|
+
try {
|
|
66
|
+
const { nextItems } = await this.adapter.nextItems({
|
|
67
|
+
models,
|
|
68
|
+
systemPrompt: buildSystemPrompt(describeContext),
|
|
69
|
+
tools: [],
|
|
70
|
+
prevItems: [
|
|
71
|
+
{
|
|
72
|
+
type: 'humanMessage',
|
|
73
|
+
humanMessage: { text: 'Describe this image.', images: [image] },
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
});
|
|
77
|
+
const parts = [];
|
|
78
|
+
for (const item of nextItems) {
|
|
79
|
+
if (item.type === 'botMessage' && item.botMessage.text)
|
|
80
|
+
parts.push(item.botMessage.text);
|
|
81
|
+
}
|
|
82
|
+
return parts.join('\n').trim() || undefined;
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
this.logger.warn(`failed to describe image '${image.id}'`, err instanceof Error ? { message: err.message } : { err });
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
ImageDescriber = __decorate([
|
|
91
|
+
injectable(),
|
|
92
|
+
__metadata("design:paramtypes", [ChatAdapter])
|
|
93
|
+
], ImageDescriber);
|
|
94
|
+
|
|
95
|
+
export { ImageDescriber };
|
|
@@ -9,6 +9,7 @@ function extractChatMessageText(message, options = {}) {
|
|
|
9
9
|
id: x.id,
|
|
10
10
|
name: x.name,
|
|
11
11
|
mimeType: x.mimeType,
|
|
12
|
+
description: x.description,
|
|
12
13
|
notAnalyzed: isAnalyzed(x.mimeType, options.supportedImageMimeTypes) ? undefined : true,
|
|
13
14
|
})),
|
|
14
15
|
documents: message.documents?.map((x) => ({
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Index of the first chat item that belongs to the current, not-yet-answered
|
|
3
|
+
* exchange — i.e. the item right after the last bot message.
|
|
4
|
+
*
|
|
5
|
+
* Image and document binaries should only be sent to the model for human
|
|
6
|
+
* messages at or after this index. Media in earlier human messages has already
|
|
7
|
+
* been answered by the bot, so its analysis is captured in the bot's replies;
|
|
8
|
+
* re-sending the binary would make the model analyze the same files again on
|
|
9
|
+
* every turn (and re-upload them, wasting tokens).
|
|
10
|
+
*
|
|
11
|
+
* Returns 0 when the bot has not replied yet, so the whole pending exchange
|
|
12
|
+
* keeps its media.
|
|
13
|
+
*/
|
|
14
|
+
function pendingMediaStartIndex(items) {
|
|
15
|
+
for (let i = items.length - 1; i >= 0; i--) {
|
|
16
|
+
if (items[i].type === 'botMessage')
|
|
17
|
+
return i + 1;
|
|
18
|
+
}
|
|
19
|
+
return 0;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export { pendingMediaStartIndex };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Index of the first item whose media (images/documents) the model has not been
|
|
3
|
+
* shown yet — i.e. right after the last model output, be it a bot message or a
|
|
4
|
+
* function call.
|
|
5
|
+
*
|
|
6
|
+
* A file's binary should only be sent on the model's first exposure to it. Once
|
|
7
|
+
* the model has produced anything in response to a human message (a function
|
|
8
|
+
* call during a tool loop, or a reply), it has already analyzed that message's
|
|
9
|
+
* files; re-sending the bytes on the next call would analyze the same file
|
|
10
|
+
* again. This is the within-turn counterpart of {@link pendingMediaStartIndex}:
|
|
11
|
+
* that one keeps a turn's media "active" for vision-model selection across the
|
|
12
|
+
* whole tool loop, while this one stops re-uploading the bytes after the first
|
|
13
|
+
* call.
|
|
14
|
+
*
|
|
15
|
+
* Returns 0 when the model has not produced any output yet, so a brand-new
|
|
16
|
+
* message keeps its media.
|
|
17
|
+
*/
|
|
18
|
+
function unconsumedMediaStartIndex(items) {
|
|
19
|
+
for (let i = items.length - 1; i >= 0; i--) {
|
|
20
|
+
const type = items[i].type;
|
|
21
|
+
if (type === 'botMessage' || type === 'functionCall')
|
|
22
|
+
return i + 1;
|
|
23
|
+
}
|
|
24
|
+
return 0;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export { unconsumedMediaStartIndex };
|
|
@@ -5,6 +5,7 @@ import '../chat-bot/ChatBot.js';
|
|
|
5
5
|
import '../chat-bot/ChatOperator.js';
|
|
6
6
|
import { ChatRepository } from '../chat-bot/ChatRepository.js';
|
|
7
7
|
import '../chat-bot/UnionChatAdapter.js';
|
|
8
|
+
import '../chat-bot/ImageDescriber.js';
|
|
8
9
|
import { injectable } from '../../core/injection/index.js';
|
|
9
10
|
import '../chat-bot/metadata/ChatAdapterMetadataStore.js';
|
|
10
11
|
import 'uuid';
|
|
@@ -8,6 +8,7 @@ import { ChatMemory } from '../chat-bot/ChatMemory.js';
|
|
|
8
8
|
import '../chat-bot/ChatOperator.js';
|
|
9
9
|
import { ChatRepository } from '../chat-bot/ChatRepository.js';
|
|
10
10
|
import '../chat-bot/UnionChatAdapter.js';
|
|
11
|
+
import '../chat-bot/ImageDescriber.js';
|
|
11
12
|
import '../chat-bot/metadata/ChatAdapterMetadataStore.js';
|
|
12
13
|
import 'uuid';
|
|
13
14
|
import { ChatBotMetadataStore } from '../chat-bot/metadata/ChatBotMetadataStore.js';
|
|
@@ -7,6 +7,7 @@ import '../chat-bot/ChatBot.js';
|
|
|
7
7
|
import '../chat-bot/ChatOperator.js';
|
|
8
8
|
import { ChatRepository } from '../chat-bot/ChatRepository.js';
|
|
9
9
|
import '../chat-bot/UnionChatAdapter.js';
|
|
10
|
+
import '../chat-bot/ImageDescriber.js';
|
|
10
11
|
import '../chat-bot/metadata/ChatAdapterMetadataStore.js';
|
|
11
12
|
import 'uuid';
|
|
12
13
|
import '../chat-bot/metadata/ChatBotMetadataStore.js';
|
package/dist/src/index.d.ts
CHANGED
|
@@ -953,6 +953,8 @@ interface IChatMessagesPublicFile {
|
|
|
953
953
|
base64Url?: undefined;
|
|
954
954
|
mimeType: string;
|
|
955
955
|
id: string;
|
|
956
|
+
/** Text description of the file content, used to recall it without re-analyzing the binary. */
|
|
957
|
+
description?: string;
|
|
956
958
|
}
|
|
957
959
|
interface IChatMessagesPrivateFile {
|
|
958
960
|
name?: string;
|
|
@@ -960,6 +962,8 @@ interface IChatMessagesPrivateFile {
|
|
|
960
962
|
base64Url: string;
|
|
961
963
|
mimeType: string;
|
|
962
964
|
id: string;
|
|
965
|
+
/** Text description of the file content, used to recall it without re-analyzing the binary. */
|
|
966
|
+
description?: string;
|
|
963
967
|
}
|
|
964
968
|
type IChatMessageFile = IChatMessagesPrivateFile | IChatMessagesPublicFile;
|
|
965
969
|
|
|
@@ -1067,13 +1071,43 @@ interface IChatBot {
|
|
|
1067
1071
|
sendMessage(message: IChatMessage, callback: (message: IChatMessage) => Promise<void>): void | Promise<void>;
|
|
1068
1072
|
}
|
|
1069
1073
|
|
|
1074
|
+
/** Slice of the mindset used to focus the description on what the assistant cares about. */
|
|
1075
|
+
interface IImageDescribeContext {
|
|
1076
|
+
context?: string;
|
|
1077
|
+
skills?: string;
|
|
1078
|
+
limits?: string;
|
|
1079
|
+
}
|
|
1080
|
+
/**
|
|
1081
|
+
* Generates a text description for images so the conversation can recall their
|
|
1082
|
+
* content on later turns without re-sending (and re-analyzing) the binary.
|
|
1083
|
+
*
|
|
1084
|
+
* The description is produced once, on arrival, with a single vision-model call
|
|
1085
|
+
* and stored on the image itself; from then on it travels with the message text
|
|
1086
|
+
* via {@link extractChatMessageText}.
|
|
1087
|
+
*/
|
|
1088
|
+
declare class ImageDescriber {
|
|
1089
|
+
private adapter;
|
|
1090
|
+
private logger;
|
|
1091
|
+
constructor(adapter: ChatAdapter);
|
|
1092
|
+
/**
|
|
1093
|
+
* Fills `description` for every image on the message that does not have one
|
|
1094
|
+
* yet, mutating the images in place. Safe to call with no images or no models:
|
|
1095
|
+
* it simply does nothing. Failures are logged and leave the image undescribed
|
|
1096
|
+
* rather than aborting the message.
|
|
1097
|
+
*/
|
|
1098
|
+
describeMessageImages(message: IChatMessage, models: IMindsetModelRef[], describeContext?: IImageDescribeContext): Promise<void>;
|
|
1099
|
+
describe(image: IChatMessageImage, models: IMindsetModelRef[], describeContext?: IImageDescribeContext): Promise<string | undefined>;
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1070
1102
|
declare class ChatBot implements IChatBot {
|
|
1071
1103
|
private memory;
|
|
1072
1104
|
private adapter;
|
|
1073
1105
|
private mindset;
|
|
1106
|
+
private imageDescriber;
|
|
1074
1107
|
private logger;
|
|
1075
|
-
constructor(memory: ChatMemory, adapter: ChatAdapter, mindset: MindsetOperator);
|
|
1108
|
+
constructor(memory: ChatMemory, adapter: ChatAdapter, mindset: MindsetOperator, imageDescriber: ImageDescriber);
|
|
1076
1109
|
sendMessage(message: IChatMessage, callback: (message: IChatMessage) => Promise<void>): Promise<void>;
|
|
1110
|
+
private describeImages;
|
|
1077
1111
|
protected processLoop(callback: (message: IChatMessage) => Promise<void>, invalidArgsCount: number): Promise<void>;
|
|
1078
1112
|
}
|
|
1079
1113
|
|
|
@@ -1163,10 +1197,44 @@ declare class ChatBotMetadataStore {
|
|
|
1163
1197
|
getChatBotsMetadata(): IChatBotMetadata[];
|
|
1164
1198
|
}
|
|
1165
1199
|
|
|
1200
|
+
/**
|
|
1201
|
+
* Index of the first chat item that belongs to the current, not-yet-answered
|
|
1202
|
+
* exchange — i.e. the item right after the last bot message.
|
|
1203
|
+
*
|
|
1204
|
+
* Image and document binaries should only be sent to the model for human
|
|
1205
|
+
* messages at or after this index. Media in earlier human messages has already
|
|
1206
|
+
* been answered by the bot, so its analysis is captured in the bot's replies;
|
|
1207
|
+
* re-sending the binary would make the model analyze the same files again on
|
|
1208
|
+
* every turn (and re-upload them, wasting tokens).
|
|
1209
|
+
*
|
|
1210
|
+
* Returns 0 when the bot has not replied yet, so the whole pending exchange
|
|
1211
|
+
* keeps its media.
|
|
1212
|
+
*/
|
|
1213
|
+
declare function pendingMediaStartIndex(items: IChatItem[]): number;
|
|
1214
|
+
|
|
1166
1215
|
declare function runChatAdapters(adapters: IConstructor<IChatAdapter>[]): void;
|
|
1167
1216
|
|
|
1168
1217
|
declare function safeJsonParse<T = unknown>(json: string | undefined | null, context?: string): T;
|
|
1169
1218
|
|
|
1219
|
+
/**
|
|
1220
|
+
* Index of the first item whose media (images/documents) the model has not been
|
|
1221
|
+
* shown yet — i.e. right after the last model output, be it a bot message or a
|
|
1222
|
+
* function call.
|
|
1223
|
+
*
|
|
1224
|
+
* A file's binary should only be sent on the model's first exposure to it. Once
|
|
1225
|
+
* the model has produced anything in response to a human message (a function
|
|
1226
|
+
* call during a tool loop, or a reply), it has already analyzed that message's
|
|
1227
|
+
* files; re-sending the bytes on the next call would analyze the same file
|
|
1228
|
+
* again. This is the within-turn counterpart of {@link pendingMediaStartIndex}:
|
|
1229
|
+
* that one keeps a turn's media "active" for vision-model selection across the
|
|
1230
|
+
* whole tool loop, while this one stops re-uploading the bytes after the first
|
|
1231
|
+
* call.
|
|
1232
|
+
*
|
|
1233
|
+
* Returns 0 when the model has not produced any output yet, so a brand-new
|
|
1234
|
+
* message keeps its media.
|
|
1235
|
+
*/
|
|
1236
|
+
declare function unconsumedMediaStartIndex(items: IChatItem[]): number;
|
|
1237
|
+
|
|
1170
1238
|
interface IProjectRunnerConfig {
|
|
1171
1239
|
directories?: string[];
|
|
1172
1240
|
exclude?: string[];
|
|
@@ -2056,8 +2124,51 @@ declare class CmdChannelConfig {
|
|
|
2056
2124
|
constructor(route: string);
|
|
2057
2125
|
}
|
|
2058
2126
|
|
|
2127
|
+
interface ICmdChannelEntry {
|
|
2128
|
+
route: string;
|
|
2129
|
+
}
|
|
2130
|
+
interface ICmdImage {
|
|
2131
|
+
name?: string;
|
|
2132
|
+
mimeType: string;
|
|
2133
|
+
/** Data URL: `data:<mimeType>;base64,<...>`. */
|
|
2134
|
+
base64Url: string;
|
|
2135
|
+
}
|
|
2136
|
+
type CmdClientMessage = {
|
|
2137
|
+
type: 'hello';
|
|
2138
|
+
} | {
|
|
2139
|
+
type: 'select';
|
|
2140
|
+
route: string;
|
|
2141
|
+
} | {
|
|
2142
|
+
type: 'message';
|
|
2143
|
+
text?: string;
|
|
2144
|
+
images?: ICmdImage[];
|
|
2145
|
+
} | {
|
|
2146
|
+
type: 'clear';
|
|
2147
|
+
};
|
|
2148
|
+
type CmdServerMessage = {
|
|
2149
|
+
type: 'channels';
|
|
2150
|
+
list: ICmdChannelEntry[];
|
|
2151
|
+
} | {
|
|
2152
|
+
type: 'selected';
|
|
2153
|
+
route: string;
|
|
2154
|
+
} | {
|
|
2155
|
+
type: 'reply';
|
|
2156
|
+
senderName?: string;
|
|
2157
|
+
text: string;
|
|
2158
|
+
} | {
|
|
2159
|
+
type: 'cleared';
|
|
2160
|
+
route: string;
|
|
2161
|
+
} | {
|
|
2162
|
+
type: 'error';
|
|
2163
|
+
message: string;
|
|
2164
|
+
};
|
|
2165
|
+
|
|
2166
|
+
interface ICmdIncomingMessage {
|
|
2167
|
+
text?: string;
|
|
2168
|
+
images?: ICmdImage[];
|
|
2169
|
+
}
|
|
2059
2170
|
interface ICmdChannelHandlers {
|
|
2060
|
-
onMessage: (
|
|
2171
|
+
onMessage: (message: ICmdIncomingMessage, reply: (response: {
|
|
2061
2172
|
senderName?: string;
|
|
2062
2173
|
text: string;
|
|
2063
2174
|
}) => void) => Promise<void> | void;
|
|
@@ -2113,38 +2224,6 @@ declare function readJsonFromFile<T>(filename: string): T | null;
|
|
|
2113
2224
|
|
|
2114
2225
|
declare function cmdChannelSocketPath(): string;
|
|
2115
2226
|
|
|
2116
|
-
interface ICmdChannelEntry {
|
|
2117
|
-
route: string;
|
|
2118
|
-
}
|
|
2119
|
-
type CmdClientMessage = {
|
|
2120
|
-
type: 'hello';
|
|
2121
|
-
} | {
|
|
2122
|
-
type: 'select';
|
|
2123
|
-
route: string;
|
|
2124
|
-
} | {
|
|
2125
|
-
type: 'message';
|
|
2126
|
-
text: string;
|
|
2127
|
-
} | {
|
|
2128
|
-
type: 'clear';
|
|
2129
|
-
};
|
|
2130
|
-
type CmdServerMessage = {
|
|
2131
|
-
type: 'channels';
|
|
2132
|
-
list: ICmdChannelEntry[];
|
|
2133
|
-
} | {
|
|
2134
|
-
type: 'selected';
|
|
2135
|
-
route: string;
|
|
2136
|
-
} | {
|
|
2137
|
-
type: 'reply';
|
|
2138
|
-
senderName?: string;
|
|
2139
|
-
text: string;
|
|
2140
|
-
} | {
|
|
2141
|
-
type: 'cleared';
|
|
2142
|
-
route: string;
|
|
2143
|
-
} | {
|
|
2144
|
-
type: 'error';
|
|
2145
|
-
message: string;
|
|
2146
|
-
};
|
|
2147
|
-
|
|
2148
2227
|
declare function runCmdClient(): void;
|
|
2149
2228
|
|
|
2150
2229
|
interface ISocketChannelConfig {
|
|
@@ -2234,8 +2313,11 @@ declare class TelegramChannel implements IChatChannel {
|
|
|
2234
2313
|
private config;
|
|
2235
2314
|
static channelName: "TelegramChannel";
|
|
2236
2315
|
private bot;
|
|
2316
|
+
private logger;
|
|
2237
2317
|
constructor(config: TelegramChannelConfig);
|
|
2238
2318
|
listen(callback: (message: ITelegramChannelMessage) => Promise<void>): void;
|
|
2319
|
+
private extractMedia;
|
|
2320
|
+
private downloadChatFile;
|
|
2239
2321
|
connect(): void;
|
|
2240
2322
|
disconnect(): void;
|
|
2241
2323
|
}
|
|
@@ -2683,4 +2765,4 @@ declare function HtmlModule(options: IHtmlModuleOptions): {
|
|
|
2683
2765
|
new (): {};
|
|
2684
2766
|
};
|
|
2685
2767
|
|
|
2686
|
-
export { AnthropicChatAdapter, ApiKey, ApiKeyGuardMiddleware, ApiKeyHandshakeGuardMiddleware, ApiKeyRepository, Async, AsyncMetadataStore, Auth, Chat, ChatAdapter, ChatAdapterMetadataStore, ChatAdapterRegistry, ChatBot, ChatBotMetadataStore, ChatItem, ChatMemory, ChatOperator, ChatRepository, ChatResolver, type ClientMap, CmdChannel, CmdChannelConfig, CmdChannelServer, type CmdClientMessage, type CmdServerMessage, type ConfigReference, type ConfigReferenceType, ConfigResolver, Container, ControllerMetadataStore, CronJob, CronJobRepository, CrudRepository, CustomError, DeepSeekChatAdapter, DescriptionMetadataStore, EXPRESS_REQ, EXPRESS_RES, Entity, Env, type ErrorSeverity, ExpressProvider, GoogleChatAdapter, type GoogleChatAdapterV2Options, HtmlModule, HttpServerProvider, type IApiKeyData, type IApiKeyRepository, type IArrayValidationError, type IArrayValidationResult, type IBotMessageItem, type IBuiltQuery, type IChannelMessage, type IChannelMetadata, type IChatAdapter, type IChatAdapterDecoratorConfig, type IChatAdapterMetadata, type IChatAdapterNextItemsReq, type IChatAdapterNextItemsRes, type IChatAssociation, type IChatBot, type IChatBotMetadata, type IChatChannel, type IChatConnection, type IChatControllerMetadata, type IChatData, type IChatItem, type IChatItemData, type IChatItemType, type IChatMemory, type IChatMessage, type IChatMessageDocument, type IChatMessageFile, type IChatMessageImage, type IChatMessagesPrivateFile, type IChatMessagesPublicFile, type IChatRepository, type IChatType, type ICmdChannelEntry, type ICmdChannelHandlers, type ICmdChannelMessage, type ICmdReceivedMessage, type ICommandConfig, type ICommandHandler, type ICommandHandlerConfig, type IConstructor, type ICronConfig, type ICronHandler, type ICronJobData, type ICronJobRepository, type ICrudRepository, type ICustomErrorData, type IDedupConfig, type IDescriptionMetadata, type IEndPointConfig, type IEndPointMetadata, type IEntityData, type IEnvType, type IErrorHandlersConfig, type IErrorMonitor, type IErrorMonitorContext, type IExtractChatMessageTextOptions, type IFunctionCall, type IFunctionCallItem, type IGenerateApiKeyReq, type IGenerateApiKeyRes, type IHandshakeMiddleware, type IHandshakeMiddlewareMetadata, type IHtmlModuleOptions, type IHumanMessageItem, type IJobData, type IJobOptions, type IJobRepository, type IJwtRefreshTokenData, type IJwtRefreshTokenRepository, type IKapsoChannelConfig, type IKapsoChannelMessage, type IKapsoChannelMessageListener, type IKapsoChatMessage, type IKapsoConversation, type IKapsoEvent, type IKapsoIncomingMessage, type IKapsoMessageReceivedEvent, type IKapsoReceivedMessage, type IKapsoUnknownEvent, type ILanguageModelUsage, type ILockKey, type ILocker, type ILockerKey, type IMemoryRepositoryAdapterOptions, type IMessageContext, type IMiddleware, type IMiddlewareMetadata, type IMindset, type IMindsetConfig, type IMindsetIdentity, type IMindsetLlm, type IMindsetMetadata, type IMindsetModelKind, type IMindsetModelRef, type IMindsetModels, type IMindsetModuleConfig, type IMindsetModuleMetadata, type IMindsetParameterSchema, type IMindsetTool, type IMindsetToolParameter, type IModelValidationError, type IModelValidationResult, type IModelValidatorsInfo, type IMoneyData, type IPersistentData, type IPgRepositoryConfig, type IProjectRunnerConfig, type IPropertyValidatorInfo, type IQueryAst, type IQueryCondition, type IQueryMethodMetadata, type IQueryOrderBy, type IReceivedMessage, type IRemoteApiKeyFetcher, type IRepositoryAdapter, type IRepositoryConfig, type IRepositoryRuntime, type IRestControllerConfig, type IRestControllerMetadata, type IScanProjectFilesOptions, type IScheduleAt, type IScheduleDelay, type ISendWhatsAppMessageReq, type ISendWhatsAppTemplateReq, type ISocketChannelConfig, type ISocketChannelMessage, type ISocketChannelReceivedMessage, type ISocketControllerConfig, type ISocketControllerMetadata, type ISocketEventConfig, type ISocketEventMetadata, type ISocketReceivedMessage, type IStorableData, type ITelegramChannelConfig, type ITelegramChannelMessage, type ITelegramReceivedMessage, type ITransactionAdapter, type IValidateArrayOptions, type IValidateArrayOptionsWithItemsValidators, type IValidateInputShape, type IValidateIsInOptions, type IValidateIsRecordOptions, type IValidateMaxOptions, type IValidateMinOptions, type IValidationError, type IValidationResult, type IValidator, type IValidatorMetadata, type IWasenderChannelConfig, type IWasenderChannelMessageListener, type IWasenderDeviceListMetadata, type IWasenderEvent, type IWasenderMessageContent, type IWasenderMessageContextInfo, type IWasenderMessageKey, type IWasenderMessageReceivedData, type IWasenderMessageReceivedEvent, type IWasenderQrUpdatedEvent, type IWasenderReceivedMessage, type IWhatsAppCloudContact, type IWhatsAppCloudMessage, type IWhatsAppCloudMessageMetadata, type IWhatsAppCloudTemplate, type IWhatsAppCloudTemplateComponent, type IWhatsAppCloudTemplateResponse, type IWhatsAppCloudWebhookPayload, type IWhatsAppSender, type IWhatsAppTemplateData, type IWhatsAppTemplateParameter, type IchatControllerConfig, InMemoryChatMemory, InMemoryChatRepository, InMemoryCronJobRepository, InMemoryJobRepository, InMemoryLockKey, InMemoryLocker, Job, JobRepository, JobRunner, Jwt, JwtAccessAndRefreshTokenDto, JwtConfig, JwtGuardMiddleware, JwtHandshakeGuardMiddleware, JwtRefreshToken, JwtRefreshTokenRepository, JwtSigner, JwtTokenDto, KapsoChannel, KapsoChannelConfig, KapsoReceiver, KapsoSender, KapsoWebhookController, Lifecycle, Locker, Logger, MEMORY_ADAPTER_ID, Mapper, MemoryRepositoryAdapter, MemoryRepositoryExtension, Mindset, MindsetMetadataStore, MindsetOperator, Money, MoneyDto, OpenRouterChatAdapter, OpenaiChatAdapter, PG_ADAPTER_ID, Password, type PasswordHashOptions, Persistent, PgApiKeyRepository, PgChatMemory, PgChatRepository, PgCronJobRepository, PgCrudRepository, PgJobRepository, PgJsonRepositoryAdapter, PgJwtRefreshTokenRepository, PgLockKey, PgLocker, PgRepositoryBase, PgRepositoryBase as PgRepositoryExtension, PgTransactionAdapter, ProjectRunner, type QueryConnector, type QueryOperator, type QueryPrefix, Random, RemoteApiKeyRepository, RepositoryAdapterRegistry, RepositoryMetadataStore, type ResolvedConfig, RestControllerMetadataStore, RestRequest, SocketChannel, SocketChannelConfig, SocketChannelMessageFile, SocketChannelReceivedMessage, SocketControllerMetadataStore, SocketServerConfig, SocketServerProvider, Storable, TelegramChannel, TelegramChannelConfig, TransactionMetadataStore, UnionChatAdapter, ValidationMetadataStore, WabotChatAdapter, WasenderChannel, WasenderChannelConfig, WasenderReceiver, WasenderSender, WasenderWebhookController, WhatsAppApiSender, WhatsAppReceiverByCloudApi, WhatsAppSender, apiKeyGuard, apiKeyHandshakeGuard, bool, boolArr, buildQuerySql, chatAdapter, chatBot, chatController, chatItemTypeOptions, cmd, cmdChannelName, cmdChannelSocketPath, command, commandHandler, computeDedupKey, container, cronHandler, description, errorToPlainObject, evaluateQueryAst, extractChatMessageText, extractNumberFromWasenderMessageKey, getClientMap, getPgClient, handshakeMiddlewares, inject, injectable, isArray, isBoolean, isChatMessageEmpty, isDate, isIn, isModel, isNotEmpty, isNumber, isOptional, isPresent, isRecord, isRetryableError, isString, jwtGuard, jwtHandshakeGuard, kapso, kapsoChannelName, markdownToTelegramHtml, max, memExtension, middleware, min, mindset, mindsetModule, modelInfo, num, numArr, obj, onDelete, onGet, onPost, onPut, onSocketEvent, parseQueryMethodName, pgExtension, pgStorage, query, queryExtension, readJsonFromFile, repository, resolveConfigReferences, restController, run, runChatAdapters, runChatControllers, runCmdClient, runCommandHandlers, runCronHandlers, runRestControllers, runSocketControllers, safeJsonParse, scanProjectFiles, scoped, setupErrorHandlers, singleton, socket, socketChannelName, socketController, stopCommandHandlers, stopCronHandlers, str, strArr, telegram, telegramChannelName, transaction, validateAndTransform, validateArray, validateIsBoolean, validateIsDate, validateIsIn, validateIsNotEmpty, validateIsNumber, validateIsPresent, validateIsRecord, validateIsString, validateMax, validateMin, validateModel, wasender, wasenderChannelName, withPgClient, withPgTransaction, writeJsonToFile };
|
|
2768
|
+
export { AnthropicChatAdapter, ApiKey, ApiKeyGuardMiddleware, ApiKeyHandshakeGuardMiddleware, ApiKeyRepository, Async, AsyncMetadataStore, Auth, Chat, ChatAdapter, ChatAdapterMetadataStore, ChatAdapterRegistry, ChatBot, ChatBotMetadataStore, ChatItem, ChatMemory, ChatOperator, ChatRepository, ChatResolver, type ClientMap, CmdChannel, CmdChannelConfig, CmdChannelServer, type CmdClientMessage, type CmdServerMessage, type ConfigReference, type ConfigReferenceType, ConfigResolver, Container, ControllerMetadataStore, CronJob, CronJobRepository, CrudRepository, CustomError, DeepSeekChatAdapter, DescriptionMetadataStore, EXPRESS_REQ, EXPRESS_RES, Entity, Env, type ErrorSeverity, ExpressProvider, GoogleChatAdapter, type GoogleChatAdapterV2Options, HtmlModule, HttpServerProvider, type IApiKeyData, type IApiKeyRepository, type IArrayValidationError, type IArrayValidationResult, type IBotMessageItem, type IBuiltQuery, type IChannelMessage, type IChannelMetadata, type IChatAdapter, type IChatAdapterDecoratorConfig, type IChatAdapterMetadata, type IChatAdapterNextItemsReq, type IChatAdapterNextItemsRes, type IChatAssociation, type IChatBot, type IChatBotMetadata, type IChatChannel, type IChatConnection, type IChatControllerMetadata, type IChatData, type IChatItem, type IChatItemData, type IChatItemType, type IChatMemory, type IChatMessage, type IChatMessageDocument, type IChatMessageFile, type IChatMessageImage, type IChatMessagesPrivateFile, type IChatMessagesPublicFile, type IChatRepository, type IChatType, type ICmdChannelEntry, type ICmdChannelHandlers, type ICmdChannelMessage, type ICmdImage, type ICmdIncomingMessage, type ICmdReceivedMessage, type ICommandConfig, type ICommandHandler, type ICommandHandlerConfig, type IConstructor, type ICronConfig, type ICronHandler, type ICronJobData, type ICronJobRepository, type ICrudRepository, type ICustomErrorData, type IDedupConfig, type IDescriptionMetadata, type IEndPointConfig, type IEndPointMetadata, type IEntityData, type IEnvType, type IErrorHandlersConfig, type IErrorMonitor, type IErrorMonitorContext, type IExtractChatMessageTextOptions, type IFunctionCall, type IFunctionCallItem, type IGenerateApiKeyReq, type IGenerateApiKeyRes, type IHandshakeMiddleware, type IHandshakeMiddlewareMetadata, type IHtmlModuleOptions, type IHumanMessageItem, type IImageDescribeContext, type IJobData, type IJobOptions, type IJobRepository, type IJwtRefreshTokenData, type IJwtRefreshTokenRepository, type IKapsoChannelConfig, type IKapsoChannelMessage, type IKapsoChannelMessageListener, type IKapsoChatMessage, type IKapsoConversation, type IKapsoEvent, type IKapsoIncomingMessage, type IKapsoMessageReceivedEvent, type IKapsoReceivedMessage, type IKapsoUnknownEvent, type ILanguageModelUsage, type ILockKey, type ILocker, type ILockerKey, type IMemoryRepositoryAdapterOptions, type IMessageContext, type IMiddleware, type IMiddlewareMetadata, type IMindset, type IMindsetConfig, type IMindsetIdentity, type IMindsetLlm, type IMindsetMetadata, type IMindsetModelKind, type IMindsetModelRef, type IMindsetModels, type IMindsetModuleConfig, type IMindsetModuleMetadata, type IMindsetParameterSchema, type IMindsetTool, type IMindsetToolParameter, type IModelValidationError, type IModelValidationResult, type IModelValidatorsInfo, type IMoneyData, type IPersistentData, type IPgRepositoryConfig, type IProjectRunnerConfig, type IPropertyValidatorInfo, type IQueryAst, type IQueryCondition, type IQueryMethodMetadata, type IQueryOrderBy, type IReceivedMessage, type IRemoteApiKeyFetcher, type IRepositoryAdapter, type IRepositoryConfig, type IRepositoryRuntime, type IRestControllerConfig, type IRestControllerMetadata, type IScanProjectFilesOptions, type IScheduleAt, type IScheduleDelay, type ISendWhatsAppMessageReq, type ISendWhatsAppTemplateReq, type ISocketChannelConfig, type ISocketChannelMessage, type ISocketChannelReceivedMessage, type ISocketControllerConfig, type ISocketControllerMetadata, type ISocketEventConfig, type ISocketEventMetadata, type ISocketReceivedMessage, type IStorableData, type ITelegramChannelConfig, type ITelegramChannelMessage, type ITelegramReceivedMessage, type ITransactionAdapter, type IValidateArrayOptions, type IValidateArrayOptionsWithItemsValidators, type IValidateInputShape, type IValidateIsInOptions, type IValidateIsRecordOptions, type IValidateMaxOptions, type IValidateMinOptions, type IValidationError, type IValidationResult, type IValidator, type IValidatorMetadata, type IWasenderChannelConfig, type IWasenderChannelMessageListener, type IWasenderDeviceListMetadata, type IWasenderEvent, type IWasenderMessageContent, type IWasenderMessageContextInfo, type IWasenderMessageKey, type IWasenderMessageReceivedData, type IWasenderMessageReceivedEvent, type IWasenderQrUpdatedEvent, type IWasenderReceivedMessage, type IWhatsAppCloudContact, type IWhatsAppCloudMessage, type IWhatsAppCloudMessageMetadata, type IWhatsAppCloudTemplate, type IWhatsAppCloudTemplateComponent, type IWhatsAppCloudTemplateResponse, type IWhatsAppCloudWebhookPayload, type IWhatsAppSender, type IWhatsAppTemplateData, type IWhatsAppTemplateParameter, type IchatControllerConfig, ImageDescriber, InMemoryChatMemory, InMemoryChatRepository, InMemoryCronJobRepository, InMemoryJobRepository, InMemoryLockKey, InMemoryLocker, Job, JobRepository, JobRunner, Jwt, JwtAccessAndRefreshTokenDto, JwtConfig, JwtGuardMiddleware, JwtHandshakeGuardMiddleware, JwtRefreshToken, JwtRefreshTokenRepository, JwtSigner, JwtTokenDto, KapsoChannel, KapsoChannelConfig, KapsoReceiver, KapsoSender, KapsoWebhookController, Lifecycle, Locker, Logger, MEMORY_ADAPTER_ID, Mapper, MemoryRepositoryAdapter, MemoryRepositoryExtension, Mindset, MindsetMetadataStore, MindsetOperator, Money, MoneyDto, OpenRouterChatAdapter, OpenaiChatAdapter, PG_ADAPTER_ID, Password, type PasswordHashOptions, Persistent, PgApiKeyRepository, PgChatMemory, PgChatRepository, PgCronJobRepository, PgCrudRepository, PgJobRepository, PgJsonRepositoryAdapter, PgJwtRefreshTokenRepository, PgLockKey, PgLocker, PgRepositoryBase, PgRepositoryBase as PgRepositoryExtension, PgTransactionAdapter, ProjectRunner, type QueryConnector, type QueryOperator, type QueryPrefix, Random, RemoteApiKeyRepository, RepositoryAdapterRegistry, RepositoryMetadataStore, type ResolvedConfig, RestControllerMetadataStore, RestRequest, SocketChannel, SocketChannelConfig, SocketChannelMessageFile, SocketChannelReceivedMessage, SocketControllerMetadataStore, SocketServerConfig, SocketServerProvider, Storable, TelegramChannel, TelegramChannelConfig, TransactionMetadataStore, UnionChatAdapter, ValidationMetadataStore, WabotChatAdapter, WasenderChannel, WasenderChannelConfig, WasenderReceiver, WasenderSender, WasenderWebhookController, WhatsAppApiSender, WhatsAppReceiverByCloudApi, WhatsAppSender, apiKeyGuard, apiKeyHandshakeGuard, bool, boolArr, buildQuerySql, chatAdapter, chatBot, chatController, chatItemTypeOptions, cmd, cmdChannelName, cmdChannelSocketPath, command, commandHandler, computeDedupKey, container, cronHandler, description, errorToPlainObject, evaluateQueryAst, extractChatMessageText, extractNumberFromWasenderMessageKey, getClientMap, getPgClient, handshakeMiddlewares, inject, injectable, isArray, isBoolean, isChatMessageEmpty, isDate, isIn, isModel, isNotEmpty, isNumber, isOptional, isPresent, isRecord, isRetryableError, isString, jwtGuard, jwtHandshakeGuard, kapso, kapsoChannelName, markdownToTelegramHtml, max, memExtension, middleware, min, mindset, mindsetModule, modelInfo, num, numArr, obj, onDelete, onGet, onPost, onPut, onSocketEvent, parseQueryMethodName, pendingMediaStartIndex, pgExtension, pgStorage, query, queryExtension, readJsonFromFile, repository, resolveConfigReferences, restController, run, runChatAdapters, runChatControllers, runCmdClient, runCommandHandlers, runCronHandlers, runRestControllers, runSocketControllers, safeJsonParse, scanProjectFiles, scoped, setupErrorHandlers, singleton, socket, socketChannelName, socketController, stopCommandHandlers, stopCronHandlers, str, strArr, telegram, telegramChannelName, transaction, unconsumedMediaStartIndex, validateAndTransform, validateArray, validateIsBoolean, validateIsDate, validateIsIn, validateIsNotEmpty, validateIsNumber, validateIsPresent, validateIsRecord, validateIsString, validateMax, validateMin, validateModel, wasender, wasenderChannelName, withPgClient, withPgTransaction, writeJsonToFile };
|
package/dist/src/index.js
CHANGED
|
@@ -71,14 +71,17 @@ export { ChatRepository } from './feature/chat-bot/ChatRepository.js';
|
|
|
71
71
|
export { chatItemTypeOptions } from './feature/chat-bot/IChatItem.js';
|
|
72
72
|
export { UnionChatAdapter } from './feature/chat-bot/UnionChatAdapter.js';
|
|
73
73
|
export { extractChatMessageText } from './feature/chat-bot/extractChatMessageText.js';
|
|
74
|
+
export { ImageDescriber } from './feature/chat-bot/ImageDescriber.js';
|
|
74
75
|
export { isChatMessageEmpty } from './feature/chat-bot/isChatMessageEmpty.js';
|
|
75
76
|
export { isRetryableError } from './feature/chat-bot/isRetryableError.js';
|
|
76
77
|
export { chatAdapter } from './feature/chat-bot/metadata/@chatAdapter.js';
|
|
77
78
|
export { chatBot } from './feature/chat-bot/metadata/@chatBot.js';
|
|
78
79
|
export { ChatAdapterMetadataStore } from './feature/chat-bot/metadata/ChatAdapterMetadataStore.js';
|
|
79
80
|
export { ChatBotMetadataStore } from './feature/chat-bot/metadata/ChatBotMetadataStore.js';
|
|
81
|
+
export { pendingMediaStartIndex } from './feature/chat-bot/pendingMediaStartIndex.js';
|
|
80
82
|
export { runChatAdapters } from './feature/chat-bot/runChatAdapters.js';
|
|
81
83
|
export { safeJsonParse } from './feature/chat-bot/safeJsonParse.js';
|
|
84
|
+
export { unconsumedMediaStartIndex } from './feature/chat-bot/unconsumedMediaStartIndex.js';
|
|
82
85
|
export { chatController } from './feature/chat-controller/metadata/controller/@chatController.js';
|
|
83
86
|
export { ControllerMetadataStore } from './feature/chat-controller/metadata/ControllerMetadataStore.js';
|
|
84
87
|
export { ChatResolver } from './feature/chat-controller/ChatResolver.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wabot-dev/framework",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.24",
|
|
4
4
|
"description": "Framework for IA Chat Bots",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/src/index.js",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"types:check": "tsc --noEmit",
|
|
53
53
|
"elia:dev": "node --import @yucacodes/ts --import ./env.mjs ./test/elia/_run_.ts",
|
|
54
54
|
"elia:watch": "node --watch --import @yucacodes/ts --import ./env.mjs ./test/elia/_run_.ts",
|
|
55
|
-
"elia:cmd
|
|
55
|
+
"elia:cmd": "node --import @yucacodes/ts ./test/elia/_cmd_.ts"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
58
|
"@rollup/plugin-alias": "5.1.1",
|