@wabot-dev/framework 0.9.20 → 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;
@@ -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;
@@ -2648,4 +2677,4 @@ declare function HtmlModule(options: IHtmlModuleOptions): {
2648
2677
  new (): {};
2649
2678
  };
2650
2679
 
2651
- 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 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, 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 };
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';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wabot-dev/framework",
3
- "version": "0.9.20",
3
+ "version": "0.9.21",
4
4
  "description": "Framework for IA Chat Bots",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",