@wabot-dev/framework 0.9.19 → 0.9.21

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.
@@ -8,6 +8,7 @@ import '../../../feature/async/AsyncMetadataStore.js';
8
8
  import '../../../feature/async/TransactionMetadataStore.js';
9
9
  import '../../../feature/async/Async.js';
10
10
  import { CronJob } from '../../../feature/async/CronJob.js';
11
+ import 'node:crypto';
11
12
  import '../../../core/error/setupErrorHandlers.js';
12
13
  import '../../../feature/async/JobRepository.js';
13
14
  import '../../../feature/async/JobRunner.js';
@@ -9,6 +9,7 @@ import '../../../feature/async/TransactionMetadataStore.js';
9
9
  import '../../../feature/async/Async.js';
10
10
  import '../../../_virtual/index.js';
11
11
  import { Job } from '../../../feature/async/Job.js';
12
+ import 'node:crypto';
12
13
  import '../../../feature/async/JobRepository.js';
13
14
  import '../../../feature/async/JobRunner.js';
14
15
  import '../../../feature/async/JobScheduler.js';
@@ -78,6 +79,23 @@ let InMemoryJobRepository = class InMemoryJobRepository {
78
79
  return j['data'].scheduledAt != null && j['data'].startedAt != null;
79
80
  });
80
81
  }
82
+ async findActiveByDedupKey(commandName, dedupKey, succeededSinceTimestamp) {
83
+ const candidates = [...this.items.values()]
84
+ .filter((j) => {
85
+ const d = j['data'];
86
+ if (d.commandName !== commandName)
87
+ return false;
88
+ if (d.dedupKey !== dedupKey)
89
+ return false;
90
+ if (d.failedAt != null)
91
+ return false;
92
+ if (d.successAt != null && d.successAt < succeededSinceTimestamp)
93
+ return false;
94
+ return true;
95
+ })
96
+ .sort((a, b) => (b['data'].createdAt ?? 0) - (a['data'].createdAt ?? 0));
97
+ return candidates[0] ?? null;
98
+ }
81
99
  async countRunningByCommand(commandName) {
82
100
  let count = 0;
83
101
  for (const j of this.items.values()) {
@@ -56,6 +56,23 @@ let PgJobRepository = class PgJobRepository extends PgCrudRepository {
56
56
  const items = await this.query(sql, []);
57
57
  return items;
58
58
  }
59
+ async findActiveByDedupKey(commandName, dedupKey, succeededSinceTimestamp) {
60
+ const sql = `
61
+ SELECT ${this.columns}
62
+ FROM ${this.table}
63
+ WHERE data->>'commandName' = $1
64
+ AND data->>'dedupKey' = $2
65
+ AND data->>'failedAt' IS NULL
66
+ AND (
67
+ data->>'successAt' IS NULL
68
+ OR (data->>'successAt')::bigint >= $3
69
+ )
70
+ ORDER BY (data->>'createdAt')::bigint DESC
71
+ LIMIT 1
72
+ `;
73
+ const items = await this.query(sql, [commandName, dedupKey, succeededSinceTimestamp]);
74
+ return items[0] ?? null;
75
+ }
59
76
  async countRunningByCommand(commandName) {
60
77
  const sql = `
61
78
  SELECT COUNT(*)::int AS count
@@ -49,14 +49,7 @@ let GoogleChatAdapter = class GoogleChatAdapter {
49
49
  let lastError;
50
50
  for (const ref of req.models) {
51
51
  try {
52
- const response = await this.ai.models.generateContent({
53
- model: ref.model,
54
- contents,
55
- config: {
56
- tools: [{ functionDeclarations }],
57
- ...(hasUnsignedFunctionCall ? { thinkingConfig: { thinkingBudget: 0 } } : {}),
58
- },
59
- });
52
+ const response = await this.callGenerate(ref.model, contents, functionDeclarations, hasUnsignedFunctionCall);
60
53
  return this.mapResponse(response, ref.model);
61
54
  }
62
55
  catch (err) {
@@ -68,6 +61,25 @@ let GoogleChatAdapter = class GoogleChatAdapter {
68
61
  }
69
62
  throw lastError ?? new Error('No Google model could handle the request');
70
63
  }
64
+ async callGenerate(model, contents, functionDeclarations, hasUnsignedFunctionCall) {
65
+ const baseConfig = { tools: [{ functionDeclarations }] };
66
+ if (!hasUnsignedFunctionCall) {
67
+ return this.ai.models.generateContent({ model, contents, config: baseConfig });
68
+ }
69
+ try {
70
+ return await this.ai.models.generateContent({
71
+ model,
72
+ contents,
73
+ config: { ...baseConfig, thinkingConfig: { thinkingBudget: 0 } },
74
+ });
75
+ }
76
+ catch (err) {
77
+ if (!isThinkingModeRequiredError(err))
78
+ throw err;
79
+ this.logger.warn(`Google model '${model}' requires thinking mode; retrying without thinkingBudget override`);
80
+ return this.ai.models.generateContent({ model, contents, config: baseConfig });
81
+ }
82
+ }
71
83
  async mapChatItems(chatItems) {
72
84
  const contents = [];
73
85
  for (const chatItem of chatItems) {
@@ -179,9 +191,7 @@ let GoogleChatAdapter = class GoogleChatAdapter {
179
191
  if (!response.candidates || !response.candidates.length) {
180
192
  throw new Error('No candidates in response');
181
193
  }
182
- if (!response.usageMetadata ||
183
- !response.usageMetadata.promptTokenCount ||
184
- !response.usageMetadata.candidatesTokenCount) {
194
+ if (!response.usageMetadata) {
185
195
  throw new Error('Not usage metadata');
186
196
  }
187
197
  const content = response.candidates.find((x) => x.content)?.content;
@@ -210,9 +220,10 @@ let GoogleChatAdapter = class GoogleChatAdapter {
210
220
  }
211
221
  }
212
222
  const cachedTokens = response.usageMetadata.cachedContentTokenCount ?? 0;
223
+ const thoughtsTokens = response.usageMetadata.thoughtsTokenCount ?? 0;
213
224
  let usage = {
214
- inputTokens: response.usageMetadata.promptTokenCount,
215
- outputTokens: response.usageMetadata.candidatesTokenCount,
225
+ inputTokens: response.usageMetadata.promptTokenCount ?? 0,
226
+ outputTokens: (response.usageMetadata.candidatesTokenCount ?? 0) + thoughtsTokens,
216
227
  cacheReadTokens: cachedTokens || undefined,
217
228
  provider: 'google',
218
229
  model: response.modelVersion ?? modelName,
@@ -225,6 +236,12 @@ GoogleChatAdapter = __decorate([
225
236
  singleton(),
226
237
  __metadata("design:paramtypes", [Env])
227
238
  ], GoogleChatAdapter);
239
+ function isThinkingModeRequiredError(err) {
240
+ if (!err || typeof err !== 'object')
241
+ return false;
242
+ const message = err.message;
243
+ return typeof message === 'string' && /only works in thinking mode/i.test(message);
244
+ }
228
245
  function stripDataUrlPrefix(dataUrl) {
229
246
  const commaIndex = dataUrl.indexOf(',');
230
247
  return commaIndex >= 0 && dataUrl.startsWith('data:') ? dataUrl.slice(commaIndex + 1) : dataUrl;
@@ -0,0 +1,23 @@
1
+ import { container } from '../../../../core/injection/index.js';
2
+ import { resolveConfigReferences } from '../../../../core/config/resolver.js';
3
+ import { ControllerMetadataStore } from '../../../../feature/chat-controller/metadata/ControllerMetadataStore.js';
4
+ import '../../../../feature/chat-controller/ChatResolver.js';
5
+ import '../../../../feature/chat-controller/runChatControllers.js';
6
+ import { KapsoChannel } from './KapsoChannel.js';
7
+ import { KapsoChannelConfig } from './KapsoChannelConfig.js';
8
+
9
+ function kapso(config) {
10
+ return function (target, propertyKey) {
11
+ const cfg = config ?? {};
12
+ const resolvedConfig = resolveConfigReferences(cfg);
13
+ const store = container.resolve(ControllerMetadataStore);
14
+ store.saveChannelMetadata({
15
+ channelConstructor: KapsoChannel,
16
+ functionName: propertyKey.toString(),
17
+ controllerConstructor: target.constructor,
18
+ channelConfig: new KapsoChannelConfig(resolvedConfig),
19
+ });
20
+ };
21
+ }
22
+
23
+ export { kapso };
@@ -0,0 +1,65 @@
1
+ import { __decorate, __metadata } from 'tslib';
2
+ import { injectable } from '../../../../core/injection/index.js';
3
+ import { Env } from '../../../../core/env/Env.js';
4
+ import { Logger } from '../../../../core/logger/Logger.js';
5
+ import { KapsoChannelConfig } from './KapsoChannelConfig.js';
6
+ import { KapsoReceiver } from './KapsoReceiver.js';
7
+ import { KapsoSender } from './KapsoSender.js';
8
+ import { kapsoChannelName } from './KapsoChannelName.js';
9
+
10
+ var KapsoChannel_1;
11
+ let KapsoChannel = class KapsoChannel {
12
+ static { KapsoChannel_1 = this; }
13
+ logger = new Logger('wabot:whatsapp-by-kapso-channel');
14
+ sender;
15
+ receiver;
16
+ phoneNumberId;
17
+ static channelName = kapsoChannelName;
18
+ constructor(config, env) {
19
+ const apiKey = config.apiKey ?? env.requireString('KAPSO_API_KEY');
20
+ const webhookSecret = config.webhookSecret ?? process.env.KAPSO_WEBHOOK_SECRET;
21
+ this.phoneNumberId = config.phoneNumberId ?? env.requireString('KAPSO_PHONE_NUMBER_ID');
22
+ this.sender = new KapsoSender(apiKey, this.phoneNumberId);
23
+ this.receiver = new KapsoReceiver({
24
+ webhookSecret,
25
+ webhookPath: config.webhookPath,
26
+ });
27
+ }
28
+ listen(callback) {
29
+ this.receiver.listenMessage(async (message, from) => {
30
+ try {
31
+ await callback({
32
+ channel: kapsoChannelName,
33
+ chatConnection: {
34
+ chatType: 'PRIVATE',
35
+ channelName: KapsoChannel_1.channelName,
36
+ id: from,
37
+ },
38
+ message,
39
+ reply: async (replyMessage) => {
40
+ await this.sender.sendMessage({
41
+ from: this.phoneNumberId,
42
+ to: from,
43
+ message: replyMessage,
44
+ });
45
+ },
46
+ });
47
+ }
48
+ catch (err) {
49
+ this.logger.error('Failed to handle WhatsApp message', err);
50
+ }
51
+ });
52
+ }
53
+ connect() {
54
+ this.receiver.connect();
55
+ }
56
+ disconnect() {
57
+ this.receiver.disconnect();
58
+ }
59
+ };
60
+ KapsoChannel = KapsoChannel_1 = __decorate([
61
+ injectable(),
62
+ __metadata("design:paramtypes", [KapsoChannelConfig, Env])
63
+ ], KapsoChannel);
64
+
65
+ export { KapsoChannel };
@@ -0,0 +1,14 @@
1
+ class KapsoChannelConfig {
2
+ apiKey;
3
+ webhookSecret;
4
+ phoneNumberId;
5
+ webhookPath;
6
+ constructor(config) {
7
+ this.apiKey = config.apiKey;
8
+ this.webhookSecret = config.webhookSecret;
9
+ this.phoneNumberId = config.phoneNumberId;
10
+ this.webhookPath = config.webhookPath ?? '/kapso/hook';
11
+ }
12
+ }
13
+
14
+ export { KapsoChannelConfig };
@@ -0,0 +1,3 @@
1
+ const kapsoChannelName = 'WhatsAppByKapsoChannel';
2
+
3
+ export { kapsoChannelName };
@@ -0,0 +1,36 @@
1
+ import { __decorate, __metadata } from 'tslib';
2
+ import '../../../../core/injection/index.js';
3
+ import '../../../../feature/rest-controller/metadata/RestControllerMetadataStore.js';
4
+ import { restController } from '../../../../feature/rest-controller/metadata/@restController.js';
5
+ import { runRestControllers } from '../../../../feature/rest-controller/runRestControllers.js';
6
+ import { KapsoWebhookController } from './KapsoWebhookController.js';
7
+
8
+ class KapsoReceiver {
9
+ config;
10
+ listener = null;
11
+ constructor(config) {
12
+ this.config = config;
13
+ }
14
+ listenMessage(listener) {
15
+ this.listener = listener;
16
+ }
17
+ connect() {
18
+ const webhookSecret = this.config.webhookSecret;
19
+ const listener = this.listener;
20
+ let UniqueController = class UniqueController extends KapsoWebhookController {
21
+ constructor() {
22
+ super(webhookSecret, listener);
23
+ }
24
+ };
25
+ UniqueController = __decorate([
26
+ restController(this.config.webhookPath),
27
+ __metadata("design:paramtypes", [])
28
+ ], UniqueController);
29
+ runRestControllers([UniqueController]);
30
+ }
31
+ disconnect() {
32
+ // Nothing to disconnect
33
+ }
34
+ }
35
+
36
+ export { KapsoReceiver };
@@ -0,0 +1,44 @@
1
+ import { Logger } from '../../../../core/logger/Logger.js';
2
+
3
+ class KapsoSender {
4
+ apiKey;
5
+ phoneNumberId;
6
+ logger = new Logger('wabot:whatsapp-sender-by-kapso');
7
+ baseUrl = 'https://api.kapso.ai/meta/whatsapp/v24.0';
8
+ constructor(apiKey, phoneNumberId) {
9
+ this.apiKey = apiKey;
10
+ this.phoneNumberId = phoneNumberId;
11
+ }
12
+ async sendMessage(request) {
13
+ const url = `${this.baseUrl}/${this.phoneNumberId}/messages`;
14
+ const payload = {
15
+ messaging_product: 'whatsapp',
16
+ recipient_type: 'individual',
17
+ to: request.to.replace(/\D+/g, ''),
18
+ type: 'text',
19
+ text: {
20
+ preview_url: false,
21
+ body: request.message.text ?? '',
22
+ },
23
+ };
24
+ const response = await fetch(url, {
25
+ method: 'POST',
26
+ headers: {
27
+ 'X-API-Key': this.apiKey,
28
+ 'Content-Type': 'application/json',
29
+ },
30
+ body: JSON.stringify(payload),
31
+ });
32
+ if (!response.ok) {
33
+ const errorBody = await response.text();
34
+ this.logger.error(`Failed to send message from '${request.from}' to '${request.to}': ${response.status} ${errorBody}`);
35
+ throw new Error(`Kapso send message failed: ${response.status} ${errorBody}`);
36
+ }
37
+ this.logger.trace(`message sent from '${request.from}' to '${request.to}'`);
38
+ }
39
+ async sendTemplate(_request) {
40
+ throw new Error('Method not implemented.');
41
+ }
42
+ }
43
+
44
+ export { KapsoSender };
@@ -0,0 +1,99 @@
1
+ import { __decorate, __metadata } from 'tslib';
2
+ import { createHmac, timingSafeEqual } from 'node:crypto';
3
+ import { IncomingMessage } from 'node:http';
4
+ import { Logger } from '../../../../core/logger/Logger.js';
5
+ import '../../../../core/injection/index.js';
6
+ import '../../../../feature/rest-controller/metadata/RestControllerMetadataStore.js';
7
+ import { onPost } from '../../../../feature/rest-controller/metadata/@onPost.js';
8
+
9
+ class KapsoWebhookController {
10
+ webhookSecret;
11
+ listener;
12
+ logger = new Logger('wabot:kapso-webhook');
13
+ constructor(webhookSecret, listener) {
14
+ this.webhookSecret = webhookSecret;
15
+ this.listener = listener;
16
+ }
17
+ async handleWebhook(req) {
18
+ const rawBody = await this.getRawBody(req);
19
+ if (!this.verifySignature(req, rawBody)) {
20
+ this.logger.warn('rejected webhook with invalid signature');
21
+ throw new Error('Invalid webhook signature');
22
+ }
23
+ let event;
24
+ try {
25
+ event = JSON.parse(rawBody);
26
+ }
27
+ catch (err) {
28
+ this.logger.error('Failed to parse webhook payload', err);
29
+ throw new Error('Invalid webhook payload');
30
+ }
31
+ this.logger.trace(`received event ${event.event}`);
32
+ switch (event.event) {
33
+ case 'whatsapp.message.received':
34
+ await this.handleMessageReceived(event);
35
+ break;
36
+ default:
37
+ this.logger.trace(`unhandled event type ${event.event}`);
38
+ }
39
+ }
40
+ async handleMessageReceived(event) {
41
+ const message = event.message;
42
+ if (message.type !== 'text' || !message.text) {
43
+ this.logger.warn(`message type '${message.type}' is not supported yet`);
44
+ return;
45
+ }
46
+ const rawNumber = message.from ?? event.conversation?.phone_number ?? '';
47
+ if (!rawNumber) {
48
+ this.logger.warn('received message without a sender number');
49
+ return;
50
+ }
51
+ const from = rawNumber.startsWith('+') ? rawNumber : `+${rawNumber}`;
52
+ const senderName = event.conversation?.kapso?.contact_name ?? message.username ?? from;
53
+ this.logger.trace(`new message from '${from}'`);
54
+ await this.listener({
55
+ text: message.text.body,
56
+ senderName,
57
+ senderId: from,
58
+ metadata: {
59
+ whatsAppNumber: from,
60
+ },
61
+ }, from);
62
+ }
63
+ verifySignature(req, rawBody) {
64
+ if (!this.webhookSecret) {
65
+ return true;
66
+ }
67
+ const headerValue = req.headers['x-webhook-signature'];
68
+ const provided = Array.isArray(headerValue) ? headerValue[0] : headerValue;
69
+ if (!provided) {
70
+ return false;
71
+ }
72
+ const expected = createHmac('sha256', this.webhookSecret).update(rawBody).digest('hex');
73
+ const providedHex = provided.startsWith('sha256=') ? provided.slice('sha256='.length) : provided;
74
+ const expectedBuf = Buffer.from(expected, 'hex');
75
+ const providedBuf = Buffer.from(providedHex, 'hex');
76
+ if (expectedBuf.length !== providedBuf.length) {
77
+ return false;
78
+ }
79
+ return timingSafeEqual(expectedBuf, providedBuf);
80
+ }
81
+ getRawBody(req) {
82
+ return new Promise((resolve, reject) => {
83
+ let data = '';
84
+ req.on('data', (chunk) => {
85
+ data += chunk;
86
+ });
87
+ req.on('end', () => resolve(data));
88
+ req.on('error', (err) => reject(err));
89
+ });
90
+ }
91
+ }
92
+ __decorate([
93
+ onPost({ disableJsonParser: true, disableUrlEncodedParser: true }),
94
+ __metadata("design:type", Function),
95
+ __metadata("design:paramtypes", [IncomingMessage]),
96
+ __metadata("design:returntype", Promise)
97
+ ], KapsoWebhookController.prototype, "handleWebhook", null);
98
+
99
+ export { KapsoWebhookController };
@@ -4,7 +4,17 @@ import { container, injectable } from '../../core/injection/index.js';
4
4
  function commandHandler(config) {
5
5
  return function (target) {
6
6
  const metadataStore = container.resolve(AsyncMetadataStore);
7
- metadataStore.registerCommandHandler(typeof config === 'function' ? config : config.command, target);
7
+ const isCtor = typeof config === 'function';
8
+ const command = isCtor ? config : config.command;
9
+ const options = isCtor
10
+ ? {}
11
+ : {
12
+ reintentsDelaysInSeconds: config.reintentsDelaysInSeconds,
13
+ aceptableRunningTimeSeconds: config.aceptableRunningTimeSeconds,
14
+ stuckRetryAttempts: config.stuckRetryAttempts,
15
+ };
16
+ const dedup = isCtor ? undefined : config.dedup;
17
+ metadataStore.registerCommandHandler(command, target, options, dedup);
8
18
  injectable()(target);
9
19
  };
10
20
  }
@@ -9,6 +9,11 @@ function cronHandler(config) {
9
9
  commandName: `cron:${config.name}`,
10
10
  cron: config.cron,
11
11
  enabled: !config.disabled,
12
+ maxRunningJobs: config.maxRunningJobs,
13
+ misfirePolicy: config.misfirePolicy,
14
+ reintentsDelaysInSeconds: config.reintentsDelaysInSeconds,
15
+ aceptableRunningTimeSeconds: config.aceptableRunningTimeSeconds,
16
+ stuckRetryAttempts: config.stuckRetryAttempts,
12
17
  });
13
18
  singleton()(target);
14
19
  };
@@ -8,15 +8,19 @@ import { JobRepository } from './JobRepository.js';
8
8
  import { JobScheduler } from './JobScheduler.js';
9
9
  import '../../core/validation/metadata/ValidationMetadataStore.js';
10
10
  import { validateAndTransform } from '../../core/validation/validateAndTransform.js';
11
+ import { Locker } from '../../core/lock/Locker.js';
12
+ import { computeDedupKey } from './computeDedupKey.js';
11
13
 
12
14
  let Async = class Async {
13
15
  jobRepository;
14
16
  metadataStore;
15
17
  jobScheduler;
16
- constructor(jobRepository, metadataStore, jobScheduler) {
18
+ locker;
19
+ constructor(jobRepository, metadataStore, jobScheduler, locker) {
17
20
  this.jobRepository = jobRepository;
18
21
  this.metadataStore = metadataStore;
19
22
  this.jobScheduler = jobScheduler;
23
+ this.locker = locker;
20
24
  }
21
25
  async runCommand(ctor, data) {
22
26
  const job = await this.scheduleCommand(ctor, data, new Date());
@@ -36,13 +40,34 @@ let Async = class Async {
36
40
  });
37
41
  }
38
42
  const scheduledDate = this.resolveScheduledDate(scheduledAt);
39
- const job = new Job({
43
+ const options = this.metadataStore.getJobOptionsForCommandName(commandName);
44
+ const dedupConfig = this.metadataStore.getDedupConfigForCommandName(commandName);
45
+ const buildJob = (dedupKey) => new Job({
40
46
  commandName,
41
47
  commandData,
42
48
  scheduledAt: scheduledDate.getTime(),
49
+ reintentsDelaysInSeconds: options.reintentsDelaysInSeconds,
50
+ aceptableRunningTimeSeconds: options.aceptableRunningTimeSeconds,
51
+ stuckRetryAttempts: options.stuckRetryAttempts,
52
+ dedupKey,
53
+ });
54
+ if (!dedupConfig) {
55
+ const job = buildJob();
56
+ await this.jobRepository.create(job);
57
+ return job;
58
+ }
59
+ const dedupKey = computeDedupKey(commandData);
60
+ const succeededSinceTimestamp = dedupConfig === 'forever' ? 0 : Date.now() - dedupConfig.windowSeconds * 1000;
61
+ return await this.locker
62
+ .withKey(`wabot-async-dedup-${commandName}-${dedupKey}`)
63
+ .run(async () => {
64
+ const existing = await this.jobRepository.findActiveByDedupKey(commandName, dedupKey, succeededSinceTimestamp);
65
+ if (existing)
66
+ return existing;
67
+ const job = buildJob(dedupKey);
68
+ await this.jobRepository.create(job);
69
+ return job;
43
70
  });
44
- await this.jobRepository.create(job);
45
- return job;
46
71
  }
47
72
  resolveScheduledDate(scheduledAt) {
48
73
  if (scheduledAt instanceof Date) {
@@ -68,7 +93,8 @@ Async = __decorate([
68
93
  singleton(),
69
94
  __metadata("design:paramtypes", [JobRepository,
70
95
  AsyncMetadataStore,
71
- JobScheduler])
96
+ JobScheduler,
97
+ Locker])
72
98
  ], Async);
73
99
 
74
100
  export { Async };
@@ -6,6 +6,8 @@ let AsyncMetadataStore = class AsyncMetadataStore {
6
6
  handlersInverseMap = new Map();
7
7
  commandsMap = new Map();
8
8
  commandsInverseMap = new Map();
9
+ handlerOptionsMap = new Map();
10
+ dedupConfigMap = new Map();
9
11
  cronsMap = new Map();
10
12
  registerCron(cron, config) {
11
13
  let ctorCrons = this.cronsMap.get(cron);
@@ -26,13 +28,20 @@ let AsyncMetadataStore = class AsyncMetadataStore {
26
28
  this.commandsMap.set(commandName, command);
27
29
  this.commandsInverseMap.set(command, commandName);
28
30
  }
29
- registerCommandHandler(command, handlerConstructor) {
31
+ registerCommandHandler(command, handlerConstructor, options = {}, dedup) {
30
32
  let commandName = this.commandsInverseMap.get(command);
31
33
  if (!commandName) {
32
34
  throw new Error(`Should use @command decorator on command class ${command.name}`);
33
35
  }
34
36
  this.handlersMap.set(commandName, handlerConstructor);
35
37
  this.handlersInverseMap.set(handlerConstructor, commandName);
38
+ this.handlerOptionsMap.set(commandName, options);
39
+ if (dedup !== undefined) {
40
+ this.dedupConfigMap.set(commandName, dedup);
41
+ }
42
+ else {
43
+ this.dedupConfigMap.delete(commandName);
44
+ }
36
45
  }
37
46
  getHandlerForCommandName(commandName) {
38
47
  return this.handlersMap.get(commandName) ?? null;
@@ -52,6 +61,12 @@ let AsyncMetadataStore = class AsyncMetadataStore {
52
61
  getCommandForCommandName(commandName) {
53
62
  return this.commandsMap.get(commandName) ?? null;
54
63
  }
64
+ getJobOptionsForCommandName(commandName) {
65
+ return this.handlerOptionsMap.get(commandName) ?? {};
66
+ }
67
+ getDedupConfigForCommandName(commandName) {
68
+ return this.dedupConfigMap.get(commandName);
69
+ }
55
70
  getAllCommandHandlers() {
56
71
  return Array.from(this.handlersInverseMap.keys());
57
72
  }
@@ -136,7 +136,15 @@ let CronScheduler = class CronScheduler {
136
136
  let cronJob = await this.cronRepo.findByName(config.name);
137
137
  if (cronJob) {
138
138
  this.logger.debug(`found cron job for name='${config.name}'`);
139
- cronJob.update({ cron: config.cron, enabled: config.enabled });
139
+ cronJob.update({
140
+ cron: config.cron,
141
+ enabled: config.enabled,
142
+ maxRunningJobs: config.maxRunningJobs,
143
+ misfirePolicy: config.misfirePolicy,
144
+ reintentsDelaysInSeconds: config.reintentsDelaysInSeconds,
145
+ aceptableRunningTimeSeconds: config.aceptableRunningTimeSeconds,
146
+ stuckRetryAttempts: config.stuckRetryAttempts,
147
+ });
140
148
  await this.cronRepo.update(cronJob);
141
149
  this.logger.debug(`cron job for name='${config.name}' updated`);
142
150
  }
@@ -146,6 +154,11 @@ let CronScheduler = class CronScheduler {
146
154
  cron: config.cron,
147
155
  commandName: config.commandName,
148
156
  enabled: config.enabled,
157
+ maxRunningJobs: config.maxRunningJobs,
158
+ misfirePolicy: config.misfirePolicy,
159
+ reintentsDelaysInSeconds: config.reintentsDelaysInSeconds,
160
+ aceptableRunningTimeSeconds: config.aceptableRunningTimeSeconds,
161
+ stuckRetryAttempts: config.stuckRetryAttempts,
149
162
  });
150
163
  await this.cronRepo.create(cronJob);
151
164
  }
@@ -18,6 +18,9 @@ class Job extends Entity {
18
18
  get stuckRetryAttempts() {
19
19
  return this.data.stuckRetryAttempts;
20
20
  }
21
+ get dedupKey() {
22
+ return this.data.dedupKey;
23
+ }
21
24
  get runningSeconds() {
22
25
  if (!this.isRunning())
23
26
  return -1;
@@ -32,6 +32,9 @@ let JobRepository = class JobRepository {
32
32
  countRunningByCommand(commandName) {
33
33
  throw new Error('Method not implemented.');
34
34
  }
35
+ findActiveByDedupKey(commandName, dedupKey, succeededSinceTimestamp) {
36
+ throw new Error('Method not implemented.');
37
+ }
35
38
  };
36
39
  JobRepository = __decorate([
37
40
  singleton()
@@ -0,0 +1,21 @@
1
+ import { createHash } from 'node:crypto';
2
+
3
+ function canonicalize(value) {
4
+ if (value === undefined || value === null)
5
+ return 'null';
6
+ if (typeof value === 'number')
7
+ return Number.isFinite(value) ? JSON.stringify(value) : 'null';
8
+ if (typeof value !== 'object')
9
+ return JSON.stringify(value);
10
+ if (Array.isArray(value))
11
+ return '[' + value.map(canonicalize).join(',') + ']';
12
+ const keys = Object.keys(value)
13
+ .filter((k) => value[k] !== undefined)
14
+ .sort();
15
+ return ('{' + keys.map((k) => JSON.stringify(k) + ':' + canonicalize(value[k])).join(',') + '}');
16
+ }
17
+ function computeDedupKey(commandData) {
18
+ return createHash('sha256').update(canonicalize(commandData)).digest('hex');
19
+ }
20
+
21
+ export { computeDedupKey };
@@ -22,6 +22,7 @@ import { TransactionMetadataStore } from '../async/TransactionMetadataStore.js';
22
22
  import '../async/Async.js';
23
23
  import '../../_virtual/index.js';
24
24
  import { CronJobRepository } from '../async/CronJobRepository.js';
25
+ import 'node:crypto';
25
26
  import { JobRepository } from '../async/JobRepository.js';
26
27
  import '../async/JobRunner.js';
27
28
  import { runCommandHandlers } from '../async/runCommandHandlers.js';
@@ -464,8 +464,19 @@ interface ICommandHandler<C extends object> {
464
464
  handle(command: C): void | Promise<void>;
465
465
  }
466
466
 
467
- interface ICommandHandlerConfig<C extends object> {
467
+ interface IJobOptions {
468
+ reintentsDelaysInSeconds?: number[];
469
+ aceptableRunningTimeSeconds?: number;
470
+ stuckRetryAttempts?: number;
471
+ }
472
+
473
+ type IDedupConfig = {
474
+ windowSeconds: number;
475
+ } | 'forever';
476
+
477
+ interface ICommandHandlerConfig<C extends object> extends IJobOptions {
468
478
  command: IConstructor<IStorableData<C>>;
479
+ dedup?: IDedupConfig;
469
480
  }
470
481
  declare function commandHandler<C extends object>(config: ICommandHandlerConfig<C> | IConstructor<IStorableData<C>>): (target: IConstructor<ICommandHandler<C>>) => void;
471
482
 
@@ -474,20 +485,24 @@ interface ICronHandler {
474
485
  handleError?(e: any): void | Promise<void>;
475
486
  }
476
487
 
477
- interface ICronConfig {
488
+ interface ICronConfig extends IJobOptions {
478
489
  name: string;
479
490
  cron: string;
480
491
  disabled?: boolean;
492
+ maxRunningJobs?: number;
493
+ misfirePolicy?: 'RUN_ONCE' | 'RUN_ALL' | 'SKIP';
481
494
  }
482
495
  declare function cronHandler(config: ICronConfig): (target: IConstructor<ICronHandler>) => void;
483
496
 
484
497
  declare function transaction(dbNames?: readonly string[]): <This, A extends unknown[], R>(_target: object, _propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<(this: This, ...args: A) => Promise<R>>) => TypedPropertyDescriptor<(this: This, ...args: A) => Promise<R>>;
485
498
 
486
- interface ICronJobScheduleConfig {
499
+ interface ICronJobScheduleConfig extends IJobOptions {
487
500
  name: string;
488
501
  commandName: string;
489
502
  cron: string;
490
503
  enabled: boolean;
504
+ maxRunningJobs?: number;
505
+ misfirePolicy?: 'RUN_ONCE' | 'RUN_ALL' | 'SKIP';
491
506
  }
492
507
 
493
508
  interface ICronMetadata {
@@ -500,16 +515,20 @@ declare class AsyncMetadataStore {
500
515
  private handlersInverseMap;
501
516
  private commandsMap;
502
517
  private commandsInverseMap;
518
+ private handlerOptionsMap;
519
+ private dedupConfigMap;
503
520
  private cronsMap;
504
521
  registerCron(cron: IConstructor<ICronHandler>, config: ICronJobScheduleConfig): void;
505
522
  requireCronMetadata(cron: IConstructor<ICronHandler>): ICronMetadata[];
506
523
  registerCommand(command: IConstructor<any>, commandName: string): void;
507
- registerCommandHandler<C extends object>(command: IConstructor<IStorableData<C>>, handlerConstructor: IConstructor<ICommandHandler<C>>): void;
524
+ registerCommandHandler<C extends object>(command: IConstructor<IStorableData<C>>, handlerConstructor: IConstructor<ICommandHandler<C>>, options?: IJobOptions, dedup?: IDedupConfig): void;
508
525
  getHandlerForCommandName(commandName: string): IConstructor<ICommandHandler<any>> | null;
509
526
  getCommandNameForHandler(handlerConstructor: IConstructor<ICommandHandler<any>>): string | null;
510
527
  requireCommandNameForHandler(handlerConstructor: IConstructor<ICommandHandler<any>>): string;
511
528
  getCommandName(command: IConstructor<any>): string | null;
512
529
  getCommandForCommandName(commandName: string): IConstructor<any> | null;
530
+ getJobOptionsForCommandName(commandName: string): IJobOptions;
531
+ getDedupConfigForCommandName(commandName: string): IDedupConfig | undefined;
513
532
  getAllCommandHandlers(): IConstructor<ICommandHandler<any>>[];
514
533
  getAllCronHandlers(): IConstructor<ICronHandler>[];
515
534
  }
@@ -532,6 +551,7 @@ interface IJobData extends IEntityData {
532
551
  };
533
552
  aceptableRunningTimeSeconds?: number;
534
553
  stuckRetryAttempts?: number;
554
+ dedupKey?: string;
535
555
  }
536
556
  declare class Job extends Entity<IJobData> {
537
557
  get commandName(): string;
@@ -539,6 +559,7 @@ declare class Job extends Entity<IJobData> {
539
559
  get reintentsDelaysInSeconds(): number[] | undefined;
540
560
  get aceptableRunningTimeSeconds(): number | undefined;
541
561
  get stuckRetryAttempts(): number | undefined;
562
+ get dedupKey(): string | undefined;
542
563
  get runningSeconds(): number;
543
564
  get successAt(): Date | undefined;
544
565
  get failedAt(): Date | undefined;
@@ -560,6 +581,7 @@ interface IJobRepository extends ICrudRepository<Job> {
560
581
  findPendingForRunFrom(date: Date, limit: number): Promise<Job[]>;
561
582
  findRunningJobs(): Promise<Job[]>;
562
583
  countRunningByCommand(commandName: string): Promise<number>;
584
+ findActiveByDedupKey(commandName: string, dedupKey: string, succeededSinceTimestamp: number): Promise<Job | null>;
563
585
  }
564
586
 
565
587
  declare class JobRepository implements IJobRepository {
@@ -573,6 +595,7 @@ declare class JobRepository implements IJobRepository {
573
595
  update(item: Job): Promise<void>;
574
596
  delete(item: Job): Promise<void>;
575
597
  countRunningByCommand(commandName: string): Promise<number>;
598
+ findActiveByDedupKey(commandName: string, dedupKey: string, succeededSinceTimestamp: number): Promise<Job | null>;
576
599
  }
577
600
 
578
601
  declare class JobRunner {
@@ -627,7 +650,8 @@ declare class Async {
627
650
  private jobRepository;
628
651
  private metadataStore;
629
652
  private jobScheduler;
630
- constructor(jobRepository: JobRepository, metadataStore: AsyncMetadataStore, jobScheduler: JobScheduler);
653
+ private locker;
654
+ constructor(jobRepository: JobRepository, metadataStore: AsyncMetadataStore, jobScheduler: JobScheduler, locker: Locker);
631
655
  runCommand<T>(ctor: IConstructor<T>, data: IValidateInputShape<T>): Promise<Job>;
632
656
  scheduleCommand<T>(ctor: IConstructor<T>, data: IValidateInputShape<T>, scheduledAt: IScheduleAt): Promise<Job>;
633
657
  private resolveScheduledDate;
@@ -679,6 +703,8 @@ declare class CronJobRepository implements ICronJobRepository {
679
703
  findOrThrow(id: string): Promise<CronJob>;
680
704
  }
681
705
 
706
+ declare function computeDedupKey(commandData: unknown): string;
707
+
682
708
  interface ITransactionAdapter {
683
709
  run<T>(fn: () => Promise<T>): Promise<T>;
684
710
  }
@@ -1659,6 +1685,7 @@ declare class PgJobRepository extends PgCrudRepository<Job> implements IJobRepos
1659
1685
  constructor(pool: Pool);
1660
1686
  findPendingForRunFrom(date: Date, limit: number): Promise<Job[]>;
1661
1687
  findRunningJobs(): Promise<Job[]>;
1688
+ findActiveByDedupKey(commandName: string, dedupKey: string, succeededSinceTimestamp: number): Promise<Job | null>;
1662
1689
  countRunningByCommand(commandName: string): Promise<number>;
1663
1690
  }
1664
1691
 
@@ -1680,6 +1707,7 @@ declare class InMemoryJobRepository implements IJobRepository {
1680
1707
  delete(item: Job): Promise<void>;
1681
1708
  findPendingForRunFrom(date: Date, limit: number): Promise<Job[]>;
1682
1709
  findRunningJobs(): Promise<Job[]>;
1710
+ findActiveByDedupKey(commandName: string, dedupKey: string, succeededSinceTimestamp: number): Promise<Job | null>;
1683
1711
  countRunningByCommand(commandName: string): Promise<number>;
1684
1712
  private touch;
1685
1713
  private enforceLimit;
@@ -1934,6 +1962,7 @@ declare class GoogleChatAdapter implements IChatAdapter {
1934
1962
  private readonly logger;
1935
1963
  constructor(env: Env);
1936
1964
  nextItems(req: IChatAdapterNextItemsReq): Promise<IChatAdapterNextItemsRes>;
1965
+ private callGenerate;
1937
1966
  private mapChatItems;
1938
1967
  private mapHumanMessage;
1939
1968
  private toGoogleFilePart;
@@ -2355,6 +2384,130 @@ declare class WhatsAppApiSender implements IWhatsAppSender {
2355
2384
  sendTemplate(request: ISendWhatsAppTemplateReq): Promise<void>;
2356
2385
  }
2357
2386
 
2387
+ interface IKapsoChannelConfig {
2388
+ apiKey?: string | ConfigReference<string>;
2389
+ webhookSecret?: string | ConfigReference<string>;
2390
+ phoneNumberId?: string | ConfigReference<string>;
2391
+ webhookPath?: string;
2392
+ }
2393
+
2394
+ declare function kapso(config?: IKapsoChannelConfig): (target: object, propertyKey: string | symbol) => void;
2395
+
2396
+ interface IKapsoChatMessage extends IChatMessage {
2397
+ metadata: {
2398
+ whatsAppNumber: string;
2399
+ };
2400
+ }
2401
+
2402
+ declare const kapsoChannelName: "WhatsAppByKapsoChannel";
2403
+
2404
+ interface IKapsoReceivedMessage extends IReceivedMessage {
2405
+ channel: typeof kapsoChannelName;
2406
+ message: IKapsoChatMessage;
2407
+ }
2408
+
2409
+ interface IKapsoChannelMessage extends IKapsoReceivedMessage {
2410
+ chatConnection: IChatConnection;
2411
+ injectInstances?: [any, any][];
2412
+ }
2413
+
2414
+ type IKapsoEvent = IKapsoMessageReceivedEvent | IKapsoUnknownEvent;
2415
+ interface IKapsoUnknownEvent {
2416
+ event: string;
2417
+ [key: string]: unknown;
2418
+ }
2419
+ interface IKapsoMessageReceivedEvent {
2420
+ event: 'whatsapp.message.received';
2421
+ message: IKapsoIncomingMessage;
2422
+ conversation: IKapsoConversation;
2423
+ is_new_conversation?: boolean;
2424
+ phone_number_id?: string;
2425
+ }
2426
+ interface IKapsoIncomingMessage {
2427
+ id: string;
2428
+ timestamp: string;
2429
+ type: string;
2430
+ from: string;
2431
+ from_user_id?: string;
2432
+ from_parent_user_id?: string;
2433
+ username?: string;
2434
+ text?: {
2435
+ body: string;
2436
+ };
2437
+ }
2438
+ interface IKapsoConversation {
2439
+ id: string;
2440
+ phone_number?: string;
2441
+ business_scoped_user_id?: string;
2442
+ parent_business_scoped_user_id?: string;
2443
+ username?: string;
2444
+ status?: string;
2445
+ phone_number_id?: string;
2446
+ kapso?: {
2447
+ contact_name?: string;
2448
+ [key: string]: unknown;
2449
+ };
2450
+ }
2451
+
2452
+ declare class KapsoChannelConfig {
2453
+ readonly apiKey?: string;
2454
+ readonly webhookSecret?: string;
2455
+ readonly phoneNumberId?: string;
2456
+ readonly webhookPath: string;
2457
+ constructor(config: {
2458
+ apiKey?: string;
2459
+ webhookSecret?: string;
2460
+ phoneNumberId?: string;
2461
+ webhookPath?: string;
2462
+ });
2463
+ }
2464
+
2465
+ declare class KapsoChannel implements IChatChannel {
2466
+ private logger;
2467
+ private sender;
2468
+ private receiver;
2469
+ private phoneNumberId;
2470
+ static channelName: "WhatsAppByKapsoChannel";
2471
+ constructor(config: KapsoChannelConfig, env: Env);
2472
+ listen(callback: (message: IKapsoChannelMessage) => Promise<void>): void;
2473
+ connect(): void;
2474
+ disconnect(): void;
2475
+ }
2476
+
2477
+ type IKapsoChannelMessageListener = (message: IKapsoChatMessage, from: string) => Promise<void>;
2478
+ declare class KapsoWebhookController {
2479
+ private webhookSecret;
2480
+ private listener;
2481
+ private logger;
2482
+ constructor(webhookSecret: string | undefined, listener: IKapsoChannelMessageListener);
2483
+ handleWebhook(req: IncomingMessage): Promise<void>;
2484
+ private handleMessageReceived;
2485
+ private verifySignature;
2486
+ private getRawBody;
2487
+ }
2488
+
2489
+ declare class KapsoReceiver {
2490
+ private config;
2491
+ private listener;
2492
+ constructor(config: {
2493
+ webhookSecret?: string;
2494
+ webhookPath: string;
2495
+ });
2496
+ listenMessage(listener: IKapsoChannelMessageListener): void;
2497
+ connect(): void;
2498
+ disconnect(): void;
2499
+ }
2500
+
2501
+ declare class KapsoSender implements IWhatsAppSender {
2502
+ private apiKey;
2503
+ private phoneNumberId;
2504
+ private logger;
2505
+ private baseUrl;
2506
+ constructor(apiKey: string, phoneNumberId: string);
2507
+ sendMessage(request: ISendWhatsAppMessageReq): Promise<void>;
2508
+ sendTemplate(_request: ISendWhatsAppTemplateReq): Promise<void>;
2509
+ }
2510
+
2358
2511
  interface IWasenderChannelConfig {
2359
2512
  apiKey?: string | ConfigReference<string>;
2360
2513
  webhookSecret?: string | ConfigReference<string>;
@@ -2524,4 +2677,4 @@ declare function HtmlModule(options: IHtmlModuleOptions): {
2524
2677
  new (): {};
2525
2678
  };
2526
2679
 
2527
- 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 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 IJobRepository, type IJwtRefreshTokenData, type IJwtRefreshTokenRepository, 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, 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, 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, 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 };
2680
+ 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 };
package/dist/src/index.js CHANGED
@@ -51,6 +51,7 @@ export { Async } from './feature/async/Async.js';
51
51
  export { AsyncMetadataStore } from './feature/async/AsyncMetadataStore.js';
52
52
  export { CronJob } from './feature/async/CronJob.js';
53
53
  export { CronJobRepository } from './feature/async/CronJobRepository.js';
54
+ export { computeDedupKey } from './feature/async/computeDedupKey.js';
54
55
  export { Job } from './feature/async/Job.js';
55
56
  export { JobRepository } from './feature/async/JobRepository.js';
56
57
  export { JobRunner } from './feature/async/JobRunner.js';
@@ -180,6 +181,13 @@ export { telegramChannelName } from './addon/chat-controller/telegram/telegramCh
180
181
  export { markdownToTelegramHtml } from './addon/chat-controller/telegram/markdownToTelegramHtml.js';
181
182
  export { WhatsAppReceiverByCloudApi } from './addon/chat-controller/whatsapp/cloud-api/WhatsAppReceiverByCloudApi.js';
182
183
  export { WhatsAppApiSender } from './addon/chat-controller/whatsapp/cloud-api/WhatsAppApiSender.js';
184
+ export { kapso } from './addon/chat-controller/whatsapp/kapso/@kapso.js';
185
+ export { KapsoChannel } from './addon/chat-controller/whatsapp/kapso/KapsoChannel.js';
186
+ export { KapsoChannelConfig } from './addon/chat-controller/whatsapp/kapso/KapsoChannelConfig.js';
187
+ export { kapsoChannelName } from './addon/chat-controller/whatsapp/kapso/KapsoChannelName.js';
188
+ export { KapsoReceiver } from './addon/chat-controller/whatsapp/kapso/KapsoReceiver.js';
189
+ export { KapsoSender } from './addon/chat-controller/whatsapp/kapso/KapsoSender.js';
190
+ export { KapsoWebhookController } from './addon/chat-controller/whatsapp/kapso/KapsoWebhookController.js';
183
191
  export { wasender } from './addon/chat-controller/whatsapp/wasender/@wasender.js';
184
192
  export { WasenderChannel } from './addon/chat-controller/whatsapp/wasender/WasenderChannel.js';
185
193
  export { WasenderChannelConfig } from './addon/chat-controller/whatsapp/wasender/WasenderChannelConfig.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wabot-dev/framework",
3
- "version": "0.9.19",
3
+ "version": "0.9.21",
4
4
  "description": "Framework for IA Chat Bots",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",