@wabot-dev/framework 0.9.23 → 0.9.25
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 +8 -6
- package/dist/src/addon/chat-bot/google/GoogleChatAdapter.js +7 -5
- package/dist/src/addon/chat-bot/openia/OpenaiChatAdapter.js +8 -6
- package/dist/src/addon/chat-bot/openrouter/OpenRouterChatAdapter.js +8 -6
- 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/feature/chat-bot/ChatBot.js +9 -5
- package/dist/src/feature/chat-bot/pendingMediaStartIndex.js +22 -0
- package/dist/src/feature/chat-bot/unconsumedMediaStartIndex.js +27 -0
- package/dist/src/index.d.ts +79 -34
- package/dist/src/index.js +2 -0
- package/package.json +2 -2
|
@@ -14,6 +14,7 @@ import 'uuid';
|
|
|
14
14
|
import '../../../feature/chat-bot/metadata/ChatBotMetadataStore.js';
|
|
15
15
|
import '../../../feature/chat-bot/metadata/ChatAdapterMetadataStore.js';
|
|
16
16
|
import { safeJsonParse } from '../../../feature/chat-bot/safeJsonParse.js';
|
|
17
|
+
import { unconsumedMediaStartIndex } from '../../../feature/chat-bot/unconsumedMediaStartIndex.js';
|
|
17
18
|
import { Anthropic } from '@anthropic-ai/sdk';
|
|
18
19
|
|
|
19
20
|
const ANTHROPIC_SUPPORTED_IMAGE_MIME_TYPES = [
|
|
@@ -60,10 +61,11 @@ let AnthropicChatAdapter = class AnthropicChatAdapter {
|
|
|
60
61
|
}
|
|
61
62
|
mapChatItems(chatItems) {
|
|
62
63
|
const messages = [];
|
|
63
|
-
|
|
64
|
+
const mediaStart = unconsumedMediaStartIndex(chatItems);
|
|
65
|
+
chatItems.forEach((chatItem, index) => {
|
|
64
66
|
switch (chatItem.type) {
|
|
65
67
|
case 'humanMessage':
|
|
66
|
-
messages.push(this.mapHumanMessage(chatItem.humanMessage));
|
|
68
|
+
messages.push(this.mapHumanMessage(chatItem.humanMessage, index >= mediaStart));
|
|
67
69
|
break;
|
|
68
70
|
case 'botMessage':
|
|
69
71
|
messages.push(this.mapBotMessage(chatItem.botMessage));
|
|
@@ -72,10 +74,10 @@ let AnthropicChatAdapter = class AnthropicChatAdapter {
|
|
|
72
74
|
messages.push(...this.mapFunctionCall(chatItem.functionCall));
|
|
73
75
|
break;
|
|
74
76
|
}
|
|
75
|
-
}
|
|
77
|
+
});
|
|
76
78
|
return messages;
|
|
77
79
|
}
|
|
78
|
-
mapHumanMessage(item) {
|
|
80
|
+
mapHumanMessage(item, includeMedia) {
|
|
79
81
|
if (isChatMessageEmpty(item)) {
|
|
80
82
|
throw new Error('User message content is empty');
|
|
81
83
|
}
|
|
@@ -87,14 +89,14 @@ let AnthropicChatAdapter = class AnthropicChatAdapter {
|
|
|
87
89
|
supportedDocumentMimeTypes: ANTHROPIC_SUPPORTED_DOCUMENT_MIME_TYPES,
|
|
88
90
|
}),
|
|
89
91
|
});
|
|
90
|
-
if (item.images) {
|
|
92
|
+
if (includeMedia && item.images) {
|
|
91
93
|
for (const image of item.images) {
|
|
92
94
|
if (!ANTHROPIC_SUPPORTED_IMAGE_MIME_TYPES.includes(image.mimeType))
|
|
93
95
|
continue;
|
|
94
96
|
blocks.push({ type: 'image', source: this.toAnthropicImageSource(image) });
|
|
95
97
|
}
|
|
96
98
|
}
|
|
97
|
-
if (item.documents) {
|
|
99
|
+
if (includeMedia && item.documents) {
|
|
98
100
|
for (const doc of item.documents) {
|
|
99
101
|
if (!ANTHROPIC_SUPPORTED_DOCUMENT_MIME_TYPES.includes(doc.mimeType))
|
|
100
102
|
continue;
|
|
@@ -15,6 +15,7 @@ import 'uuid';
|
|
|
15
15
|
import '../../../feature/chat-bot/metadata/ChatBotMetadataStore.js';
|
|
16
16
|
import '../../../feature/chat-bot/metadata/ChatAdapterMetadataStore.js';
|
|
17
17
|
import { safeJsonParse } from '../../../feature/chat-bot/safeJsonParse.js';
|
|
18
|
+
import { unconsumedMediaStartIndex } from '../../../feature/chat-bot/unconsumedMediaStartIndex.js';
|
|
18
19
|
import { GoogleGenAI } from '@google/genai';
|
|
19
20
|
|
|
20
21
|
const GOOGLE_SUPPORTED_IMAGE_MIME_TYPES = [
|
|
@@ -82,10 +83,11 @@ let GoogleChatAdapter = class GoogleChatAdapter {
|
|
|
82
83
|
}
|
|
83
84
|
async mapChatItems(chatItems) {
|
|
84
85
|
const contents = [];
|
|
85
|
-
|
|
86
|
+
const mediaStart = unconsumedMediaStartIndex(chatItems);
|
|
87
|
+
for (const [index, chatItem] of chatItems.entries()) {
|
|
86
88
|
switch (chatItem.type) {
|
|
87
89
|
case 'humanMessage':
|
|
88
|
-
contents.push(await this.mapHumanMessage(chatItem.humanMessage));
|
|
90
|
+
contents.push(await this.mapHumanMessage(chatItem.humanMessage, index >= mediaStart));
|
|
89
91
|
break;
|
|
90
92
|
case 'botMessage':
|
|
91
93
|
contents.push(this.mapBotMessage(chatItem.botMessage));
|
|
@@ -97,7 +99,7 @@ let GoogleChatAdapter = class GoogleChatAdapter {
|
|
|
97
99
|
}
|
|
98
100
|
return contents;
|
|
99
101
|
}
|
|
100
|
-
async mapHumanMessage(item) {
|
|
102
|
+
async mapHumanMessage(item, includeMedia) {
|
|
101
103
|
if (isChatMessageEmpty(item)) {
|
|
102
104
|
throw new Error('User message content is empty');
|
|
103
105
|
}
|
|
@@ -109,14 +111,14 @@ let GoogleChatAdapter = class GoogleChatAdapter {
|
|
|
109
111
|
}),
|
|
110
112
|
});
|
|
111
113
|
const filesToSend = [];
|
|
112
|
-
if (item.images) {
|
|
114
|
+
if (includeMedia && item.images) {
|
|
113
115
|
for (const image of item.images) {
|
|
114
116
|
if (!GOOGLE_SUPPORTED_IMAGE_MIME_TYPES.includes(image.mimeType))
|
|
115
117
|
continue;
|
|
116
118
|
filesToSend.push(image);
|
|
117
119
|
}
|
|
118
120
|
}
|
|
119
|
-
if (item.documents) {
|
|
121
|
+
if (includeMedia && item.documents) {
|
|
120
122
|
for (const doc of item.documents) {
|
|
121
123
|
if (!GOOGLE_SUPPORTED_DOCUMENT_MIME_TYPES.includes(doc.mimeType))
|
|
122
124
|
continue;
|
|
@@ -12,6 +12,7 @@ import 'uuid';
|
|
|
12
12
|
import '../../../feature/chat-bot/metadata/ChatBotMetadataStore.js';
|
|
13
13
|
import '../../../feature/chat-bot/metadata/ChatAdapterMetadataStore.js';
|
|
14
14
|
import '../../../core/error/setupErrorHandlers.js';
|
|
15
|
+
import { unconsumedMediaStartIndex } from '../../../feature/chat-bot/unconsumedMediaStartIndex.js';
|
|
15
16
|
import { Logger } from '../../../core/logger/Logger.js';
|
|
16
17
|
import { OpenAI } from 'openai';
|
|
17
18
|
|
|
@@ -51,10 +52,11 @@ let OpenaiChatAdapter = class OpenaiChatAdapter {
|
|
|
51
52
|
}
|
|
52
53
|
mapChatItems(chatItems) {
|
|
53
54
|
const openIaInput = [];
|
|
54
|
-
|
|
55
|
+
const mediaStart = unconsumedMediaStartIndex(chatItems);
|
|
56
|
+
chatItems.forEach((chatItem, index) => {
|
|
55
57
|
switch (chatItem.type) {
|
|
56
58
|
case 'humanMessage':
|
|
57
|
-
openIaInput.push(this.mapConectionMessage(chatItem.humanMessage));
|
|
59
|
+
openIaInput.push(this.mapConectionMessage(chatItem.humanMessage, index >= mediaStart));
|
|
58
60
|
break;
|
|
59
61
|
case 'botMessage':
|
|
60
62
|
openIaInput.push(this.mapBotMessage(chatItem.botMessage));
|
|
@@ -63,10 +65,10 @@ let OpenaiChatAdapter = class OpenaiChatAdapter {
|
|
|
63
65
|
openIaInput.push(...this.mapFunctionCall(chatItem.functionCall));
|
|
64
66
|
break;
|
|
65
67
|
}
|
|
66
|
-
}
|
|
68
|
+
});
|
|
67
69
|
return openIaInput;
|
|
68
70
|
}
|
|
69
|
-
mapConectionMessage(item) {
|
|
71
|
+
mapConectionMessage(item, includeMedia) {
|
|
70
72
|
if (isChatMessageEmpty(item)) {
|
|
71
73
|
throw new Error('User message content is empty');
|
|
72
74
|
}
|
|
@@ -78,7 +80,7 @@ let OpenaiChatAdapter = class OpenaiChatAdapter {
|
|
|
78
80
|
supportedDocumentMimeTypes: OPENAI_SUPPORTED_DOCUMENT_MIME_TYPES,
|
|
79
81
|
}),
|
|
80
82
|
});
|
|
81
|
-
if (item.images) {
|
|
83
|
+
if (includeMedia && item.images) {
|
|
82
84
|
for (const image of item.images) {
|
|
83
85
|
if (!OPENAI_SUPPORTED_IMAGE_MIME_TYPES.includes(image.mimeType))
|
|
84
86
|
continue;
|
|
@@ -89,7 +91,7 @@ let OpenaiChatAdapter = class OpenaiChatAdapter {
|
|
|
89
91
|
});
|
|
90
92
|
}
|
|
91
93
|
}
|
|
92
|
-
if (item.documents) {
|
|
94
|
+
if (includeMedia && item.documents) {
|
|
93
95
|
for (const doc of item.documents) {
|
|
94
96
|
if (!OPENAI_SUPPORTED_DOCUMENT_MIME_TYPES.includes(doc.mimeType))
|
|
95
97
|
continue;
|
|
@@ -14,6 +14,7 @@ import 'uuid';
|
|
|
14
14
|
import '../../../feature/chat-bot/metadata/ChatBotMetadataStore.js';
|
|
15
15
|
import '../../../feature/chat-bot/metadata/ChatAdapterMetadataStore.js';
|
|
16
16
|
import '../../../core/error/setupErrorHandlers.js';
|
|
17
|
+
import { unconsumedMediaStartIndex } from '../../../feature/chat-bot/unconsumedMediaStartIndex.js';
|
|
17
18
|
import { OpenRouter } from '@openrouter/sdk';
|
|
18
19
|
|
|
19
20
|
const OPENROUTER_SUPPORTED_IMAGE_MIME_TYPES = [
|
|
@@ -66,10 +67,11 @@ let OpenRouterChatAdapter = class OpenRouterChatAdapter {
|
|
|
66
67
|
}
|
|
67
68
|
mapChatItems(chatItems) {
|
|
68
69
|
const messages = [];
|
|
69
|
-
|
|
70
|
+
const mediaStart = unconsumedMediaStartIndex(chatItems);
|
|
71
|
+
chatItems.forEach((chatItem, index) => {
|
|
70
72
|
switch (chatItem.type) {
|
|
71
73
|
case 'humanMessage':
|
|
72
|
-
messages.push(this.mapHumanMessage(chatItem.humanMessage));
|
|
74
|
+
messages.push(this.mapHumanMessage(chatItem.humanMessage, index >= mediaStart));
|
|
73
75
|
break;
|
|
74
76
|
case 'botMessage':
|
|
75
77
|
messages.push(this.mapBotMessage(chatItem.botMessage));
|
|
@@ -78,10 +80,10 @@ let OpenRouterChatAdapter = class OpenRouterChatAdapter {
|
|
|
78
80
|
messages.push(...this.mapFunctionCall(chatItem.functionCall));
|
|
79
81
|
break;
|
|
80
82
|
}
|
|
81
|
-
}
|
|
83
|
+
});
|
|
82
84
|
return messages;
|
|
83
85
|
}
|
|
84
|
-
mapHumanMessage(item) {
|
|
86
|
+
mapHumanMessage(item, includeMedia) {
|
|
85
87
|
if (isChatMessageEmpty(item)) {
|
|
86
88
|
throw new Error('User message content is empty');
|
|
87
89
|
}
|
|
@@ -90,7 +92,7 @@ let OpenRouterChatAdapter = class OpenRouterChatAdapter {
|
|
|
90
92
|
supportedImageMimeTypes: OPENROUTER_SUPPORTED_IMAGE_MIME_TYPES,
|
|
91
93
|
supportedDocumentMimeTypes: OPENROUTER_SUPPORTED_DOCUMENT_MIME_TYPES,
|
|
92
94
|
}));
|
|
93
|
-
if (item.images) {
|
|
95
|
+
if (includeMedia && item.images) {
|
|
94
96
|
for (const image of item.images) {
|
|
95
97
|
if (!OPENROUTER_SUPPORTED_IMAGE_MIME_TYPES.includes(image.mimeType))
|
|
96
98
|
continue;
|
|
@@ -99,7 +101,7 @@ let OpenRouterChatAdapter = class OpenRouterChatAdapter {
|
|
|
99
101
|
contentParts.push(imageUrl);
|
|
100
102
|
}
|
|
101
103
|
}
|
|
102
|
-
if (item.documents) {
|
|
104
|
+
if (includeMedia && item.documents) {
|
|
103
105
|
for (const doc of item.documents) {
|
|
104
106
|
if (!OPENROUTER_SUPPORTED_DOCUMENT_MIME_TYPES.includes(doc.mimeType))
|
|
105
107
|
continue;
|
|
@@ -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
|
|
|
@@ -5,6 +5,7 @@ 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 { pendingMediaStartIndex } from './pendingMediaStartIndex.js';
|
|
8
9
|
import { Logger } from '../../core/logger/Logger.js';
|
|
9
10
|
|
|
10
11
|
const MAX_CONSECUTIVE_INVALID_ARGS = 2;
|
|
@@ -44,10 +45,13 @@ let ChatBot = class ChatBot {
|
|
|
44
45
|
const systemPrompt = await this.mindset.systemPrompt();
|
|
45
46
|
const tools = this.mindset.tools();
|
|
46
47
|
const identity = await this.mindset.identity();
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
const prevItemsData = prevItems.map((x) => x.getData());
|
|
49
|
+
// Only media from the pending exchange is actually sent to the model; images
|
|
50
|
+
// in already-answered messages are not, so they must not force a vision model.
|
|
51
|
+
const mediaStart = pendingMediaStartIndex(prevItemsData);
|
|
52
|
+
const needsVision = prevItemsData.some((data, index) => index >= mediaStart &&
|
|
53
|
+
data.type === 'humanMessage' &&
|
|
54
|
+
(data.humanMessage.images?.length ?? 0) > 0);
|
|
51
55
|
const kind = needsVision ? 'visionLlm' : 'llm';
|
|
52
56
|
const candidates = await this.mindset.resolveModels(kind);
|
|
53
57
|
if (candidates.length === 0) {
|
|
@@ -57,7 +61,7 @@ let ChatBot = class ChatBot {
|
|
|
57
61
|
models: candidates,
|
|
58
62
|
systemPrompt,
|
|
59
63
|
tools,
|
|
60
|
-
prevItems:
|
|
64
|
+
prevItems: prevItemsData,
|
|
61
65
|
});
|
|
62
66
|
for (const newItemData of newItemsData) {
|
|
63
67
|
if (newItemData.type === 'functionCall') {
|
|
@@ -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 };
|
package/dist/src/index.d.ts
CHANGED
|
@@ -1163,10 +1163,44 @@ declare class ChatBotMetadataStore {
|
|
|
1163
1163
|
getChatBotsMetadata(): IChatBotMetadata[];
|
|
1164
1164
|
}
|
|
1165
1165
|
|
|
1166
|
+
/**
|
|
1167
|
+
* Index of the first chat item that belongs to the current, not-yet-answered
|
|
1168
|
+
* exchange — i.e. the item right after the last bot message.
|
|
1169
|
+
*
|
|
1170
|
+
* Image and document binaries should only be sent to the model for human
|
|
1171
|
+
* messages at or after this index. Media in earlier human messages has already
|
|
1172
|
+
* been answered by the bot, so its analysis is captured in the bot's replies;
|
|
1173
|
+
* re-sending the binary would make the model analyze the same files again on
|
|
1174
|
+
* every turn (and re-upload them, wasting tokens).
|
|
1175
|
+
*
|
|
1176
|
+
* Returns 0 when the bot has not replied yet, so the whole pending exchange
|
|
1177
|
+
* keeps its media.
|
|
1178
|
+
*/
|
|
1179
|
+
declare function pendingMediaStartIndex(items: IChatItem[]): number;
|
|
1180
|
+
|
|
1166
1181
|
declare function runChatAdapters(adapters: IConstructor<IChatAdapter>[]): void;
|
|
1167
1182
|
|
|
1168
1183
|
declare function safeJsonParse<T = unknown>(json: string | undefined | null, context?: string): T;
|
|
1169
1184
|
|
|
1185
|
+
/**
|
|
1186
|
+
* Index of the first item whose media (images/documents) the model has not been
|
|
1187
|
+
* shown yet — i.e. right after the last model output, be it a bot message or a
|
|
1188
|
+
* function call.
|
|
1189
|
+
*
|
|
1190
|
+
* A file's binary should only be sent on the model's first exposure to it. Once
|
|
1191
|
+
* the model has produced anything in response to a human message (a function
|
|
1192
|
+
* call during a tool loop, or a reply), it has already analyzed that message's
|
|
1193
|
+
* files; re-sending the bytes on the next call would analyze the same file
|
|
1194
|
+
* again. This is the within-turn counterpart of {@link pendingMediaStartIndex}:
|
|
1195
|
+
* that one keeps a turn's media "active" for vision-model selection across the
|
|
1196
|
+
* whole tool loop, while this one stops re-uploading the bytes after the first
|
|
1197
|
+
* call.
|
|
1198
|
+
*
|
|
1199
|
+
* Returns 0 when the model has not produced any output yet, so a brand-new
|
|
1200
|
+
* message keeps its media.
|
|
1201
|
+
*/
|
|
1202
|
+
declare function unconsumedMediaStartIndex(items: IChatItem[]): number;
|
|
1203
|
+
|
|
1170
1204
|
interface IProjectRunnerConfig {
|
|
1171
1205
|
directories?: string[];
|
|
1172
1206
|
exclude?: string[];
|
|
@@ -2056,8 +2090,51 @@ declare class CmdChannelConfig {
|
|
|
2056
2090
|
constructor(route: string);
|
|
2057
2091
|
}
|
|
2058
2092
|
|
|
2093
|
+
interface ICmdChannelEntry {
|
|
2094
|
+
route: string;
|
|
2095
|
+
}
|
|
2096
|
+
interface ICmdImage {
|
|
2097
|
+
name?: string;
|
|
2098
|
+
mimeType: string;
|
|
2099
|
+
/** Data URL: `data:<mimeType>;base64,<...>`. */
|
|
2100
|
+
base64Url: string;
|
|
2101
|
+
}
|
|
2102
|
+
type CmdClientMessage = {
|
|
2103
|
+
type: 'hello';
|
|
2104
|
+
} | {
|
|
2105
|
+
type: 'select';
|
|
2106
|
+
route: string;
|
|
2107
|
+
} | {
|
|
2108
|
+
type: 'message';
|
|
2109
|
+
text?: string;
|
|
2110
|
+
images?: ICmdImage[];
|
|
2111
|
+
} | {
|
|
2112
|
+
type: 'clear';
|
|
2113
|
+
};
|
|
2114
|
+
type CmdServerMessage = {
|
|
2115
|
+
type: 'channels';
|
|
2116
|
+
list: ICmdChannelEntry[];
|
|
2117
|
+
} | {
|
|
2118
|
+
type: 'selected';
|
|
2119
|
+
route: string;
|
|
2120
|
+
} | {
|
|
2121
|
+
type: 'reply';
|
|
2122
|
+
senderName?: string;
|
|
2123
|
+
text: string;
|
|
2124
|
+
} | {
|
|
2125
|
+
type: 'cleared';
|
|
2126
|
+
route: string;
|
|
2127
|
+
} | {
|
|
2128
|
+
type: 'error';
|
|
2129
|
+
message: string;
|
|
2130
|
+
};
|
|
2131
|
+
|
|
2132
|
+
interface ICmdIncomingMessage {
|
|
2133
|
+
text?: string;
|
|
2134
|
+
images?: ICmdImage[];
|
|
2135
|
+
}
|
|
2059
2136
|
interface ICmdChannelHandlers {
|
|
2060
|
-
onMessage: (
|
|
2137
|
+
onMessage: (message: ICmdIncomingMessage, reply: (response: {
|
|
2061
2138
|
senderName?: string;
|
|
2062
2139
|
text: string;
|
|
2063
2140
|
}) => void) => Promise<void> | void;
|
|
@@ -2113,38 +2190,6 @@ declare function readJsonFromFile<T>(filename: string): T | null;
|
|
|
2113
2190
|
|
|
2114
2191
|
declare function cmdChannelSocketPath(): string;
|
|
2115
2192
|
|
|
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
2193
|
declare function runCmdClient(): void;
|
|
2149
2194
|
|
|
2150
2195
|
interface ISocketChannelConfig {
|
|
@@ -2686,4 +2731,4 @@ declare function HtmlModule(options: IHtmlModuleOptions): {
|
|
|
2686
2731
|
new (): {};
|
|
2687
2732
|
};
|
|
2688
2733
|
|
|
2689
|
-
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 };
|
|
2734
|
+
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 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, 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
|
@@ -77,8 +77,10 @@ export { chatAdapter } from './feature/chat-bot/metadata/@chatAdapter.js';
|
|
|
77
77
|
export { chatBot } from './feature/chat-bot/metadata/@chatBot.js';
|
|
78
78
|
export { ChatAdapterMetadataStore } from './feature/chat-bot/metadata/ChatAdapterMetadataStore.js';
|
|
79
79
|
export { ChatBotMetadataStore } from './feature/chat-bot/metadata/ChatBotMetadataStore.js';
|
|
80
|
+
export { pendingMediaStartIndex } from './feature/chat-bot/pendingMediaStartIndex.js';
|
|
80
81
|
export { runChatAdapters } from './feature/chat-bot/runChatAdapters.js';
|
|
81
82
|
export { safeJsonParse } from './feature/chat-bot/safeJsonParse.js';
|
|
83
|
+
export { unconsumedMediaStartIndex } from './feature/chat-bot/unconsumedMediaStartIndex.js';
|
|
82
84
|
export { chatController } from './feature/chat-controller/metadata/controller/@chatController.js';
|
|
83
85
|
export { ControllerMetadataStore } from './feature/chat-controller/metadata/ControllerMetadataStore.js';
|
|
84
86
|
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.25",
|
|
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",
|