@adonis-agora/agent 0.62.0 → 0.64.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +40 -0
- package/dist/providers/agent_provider.d.ts.map +1 -1
- package/dist/providers/agent_provider.js +1 -12
- package/dist/providers/agent_provider.js.map +1 -1
- package/dist/src/action-proposal-service.d.ts +3 -2
- package/dist/src/action-proposal-service.d.ts.map +1 -1
- package/dist/src/action-proposal-service.js +1 -0
- package/dist/src/action-proposal-service.js.map +1 -1
- package/dist/src/action-proposal-text.d.ts +5 -0
- package/dist/src/action-proposal-text.d.ts.map +1 -1
- package/dist/src/action-proposal-text.js +1 -0
- package/dist/src/action-proposal-text.js.map +1 -1
- package/dist/src/actor-resolver.d.ts +12 -4
- package/dist/src/actor-resolver.d.ts.map +1 -1
- package/dist/src/actor-resolver.js +24 -7
- package/dist/src/actor-resolver.js.map +1 -1
- package/dist/src/ag-ui/adapter.d.ts.map +1 -1
- package/dist/src/ag-ui/adapter.js +32 -6
- package/dist/src/ag-ui/adapter.js.map +1 -1
- package/dist/src/ag-ui/core/encoder.d.ts +2 -1
- package/dist/src/ag-ui/core/encoder.d.ts.map +1 -1
- package/dist/src/ag-ui/core/encoder.js +16 -5
- package/dist/src/ag-ui/core/encoder.js.map +1 -1
- package/dist/src/ag-ui/core/index.d.ts +2 -2
- package/dist/src/ag-ui/core/index.d.ts.map +1 -1
- package/dist/src/ag-ui/core/index.js +2 -2
- package/dist/src/ag-ui/core/index.js.map +1 -1
- package/dist/src/ag-ui/core/input.d.ts +21 -3
- package/dist/src/ag-ui/core/input.d.ts.map +1 -1
- package/dist/src/ag-ui/core/input.js +24 -6
- package/dist/src/ag-ui/core/input.js.map +1 -1
- package/dist/src/ag-ui/core/stream.d.ts +24 -2
- package/dist/src/ag-ui/core/stream.d.ts.map +1 -1
- package/dist/src/ag-ui/core/stream.js +25 -3
- package/dist/src/ag-ui/core/stream.js.map +1 -1
- package/dist/src/ag-ui/core/types.d.ts +2 -2
- package/dist/src/ag-ui/core/types.d.ts.map +1 -1
- package/dist/src/ag-ui/core/types.js +2 -2
- package/dist/src/ag-ui/core/types.js.map +1 -1
- package/dist/src/ag-ui/frames.d.ts +5 -1
- package/dist/src/ag-ui/frames.d.ts.map +1 -1
- package/dist/src/ag-ui/frames.js +13 -3
- package/dist/src/ag-ui/frames.js.map +1 -1
- package/dist/src/ag-ui/index.d.ts +1 -1
- package/dist/src/ag-ui/index.d.ts.map +1 -1
- package/dist/src/ag-ui/index.js +1 -1
- package/dist/src/ag-ui/index.js.map +1 -1
- package/dist/src/agent-service.d.ts +36 -3
- package/dist/src/agent-service.d.ts.map +1 -1
- package/dist/src/agent-service.js +65 -1
- package/dist/src/agent-service.js.map +1 -1
- package/dist/src/attachment-limits.d.ts +10 -0
- package/dist/src/attachment-limits.d.ts.map +1 -0
- package/dist/src/attachment-limits.js +13 -0
- package/dist/src/attachment-limits.js.map +1 -0
- package/dist/src/channels/adapters/evolution_api.d.ts +58 -0
- package/dist/src/channels/adapters/evolution_api.d.ts.map +1 -0
- package/dist/src/channels/adapters/evolution_api.js +256 -0
- package/dist/src/channels/adapters/evolution_api.js.map +1 -0
- package/dist/src/channels/adapters/telegram.d.ts +34 -0
- package/dist/src/channels/adapters/telegram.d.ts.map +1 -0
- package/dist/src/channels/adapters/telegram.js +196 -0
- package/dist/src/channels/adapters/telegram.js.map +1 -0
- package/dist/src/channels/adapters/whatsapp_cloud.d.ts +31 -0
- package/dist/src/channels/adapters/whatsapp_cloud.d.ts.map +1 -0
- package/dist/src/channels/adapters/whatsapp_cloud.js +177 -0
- package/dist/src/channels/adapters/whatsapp_cloud.js.map +1 -0
- package/dist/src/channels/adapters/whatsmiau.d.ts +26 -0
- package/dist/src/channels/adapters/whatsmiau.d.ts.map +1 -0
- package/dist/src/channels/adapters/whatsmiau.js +18 -0
- package/dist/src/channels/adapters/whatsmiau.js.map +1 -0
- package/dist/src/channels/handler.d.ts +190 -0
- package/dist/src/channels/handler.d.ts.map +1 -0
- package/dist/src/channels/handler.js +626 -0
- package/dist/src/channels/handler.js.map +1 -0
- package/dist/src/channels/http.d.ts +45 -0
- package/dist/src/channels/http.d.ts.map +1 -0
- package/dist/src/channels/http.js +147 -0
- package/dist/src/channels/http.js.map +1 -0
- package/dist/src/channels/index.d.ts +17 -0
- package/dist/src/channels/index.d.ts.map +1 -0
- package/dist/src/channels/index.js +16 -0
- package/dist/src/channels/index.js.map +1 -0
- package/dist/src/channels/markdown.d.ts +12 -0
- package/dist/src/channels/markdown.d.ts.map +1 -0
- package/dist/src/channels/markdown.js +120 -0
- package/dist/src/channels/markdown.js.map +1 -0
- package/dist/src/channels/questions.d.ts +45 -0
- package/dist/src/channels/questions.d.ts.map +1 -0
- package/dist/src/channels/questions.js +131 -0
- package/dist/src/channels/questions.js.map +1 -0
- package/dist/src/channels/split.d.ts +8 -0
- package/dist/src/channels/split.d.ts.map +1 -0
- package/dist/src/channels/split.js +45 -0
- package/dist/src/channels/split.js.map +1 -0
- package/dist/src/channels/store.d.ts +78 -0
- package/dist/src/channels/store.d.ts.map +1 -0
- package/dist/src/channels/store.js +151 -0
- package/dist/src/channels/store.js.map +1 -0
- package/dist/src/channels/types.d.ts +145 -0
- package/dist/src/channels/types.d.ts.map +1 -0
- package/dist/src/channels/types.js +7 -0
- package/dist/src/channels/types.js.map +1 -0
- package/dist/src/genui/builtins.d.ts.map +1 -1
- package/dist/src/genui/builtins.js +65 -6
- package/dist/src/genui/builtins.js.map +1 -1
- package/dist/src/genui/catalog.d.ts +11 -2
- package/dist/src/genui/catalog.d.ts.map +1 -1
- package/dist/src/genui/catalog.js +8 -0
- package/dist/src/genui/catalog.js.map +1 -1
- package/dist/src/genui/text.d.ts +4 -3
- package/dist/src/genui/text.d.ts.map +1 -1
- package/dist/src/genui/text.js +25 -24
- package/dist/src/genui/text.js.map +1 -1
- package/dist/src/genui/tools.d.ts +2 -1
- package/dist/src/genui/tools.d.ts.map +1 -1
- package/dist/src/genui/tools.js +6 -7
- package/dist/src/genui/tools.js.map +1 -1
- package/dist/src/index.d.ts +3 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +3 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/opencode/turns.d.ts +4 -2
- package/dist/src/opencode/turns.d.ts.map +1 -1
- package/dist/src/opencode/turns.js.map +1 -1
- package/dist/src/react/core/ag-ui-backend.d.ts +38 -14
- package/dist/src/react/core/ag-ui-backend.d.ts.map +1 -1
- package/dist/src/react/core/ag-ui-backend.js +182 -17
- package/dist/src/react/core/ag-ui-backend.js.map +1 -1
- package/dist/src/react/core/agent-chat-transport.d.ts.map +1 -1
- package/dist/src/react/core/agent-chat-transport.js +22 -1
- package/dist/src/react/core/agent-chat-transport.js.map +1 -1
- package/dist/src/react/core/backend.d.ts +11 -0
- package/dist/src/react/core/backend.d.ts.map +1 -1
- package/dist/src/react/core/backend.js.map +1 -1
- package/dist/src/react/core/index.d.ts +1 -1
- package/dist/src/react/core/index.d.ts.map +1 -1
- package/dist/src/react/core/index.js.map +1 -1
- package/dist/src/stores/lucid-schema.d.ts +24 -6
- package/dist/src/stores/lucid-schema.d.ts.map +1 -1
- package/dist/src/stores/lucid-schema.js +47 -6
- package/dist/src/stores/lucid-schema.js.map +1 -1
- package/dist/src/stores/lucid.d.ts +2 -0
- package/dist/src/stores/lucid.d.ts.map +1 -1
- package/dist/src/stores/lucid.js +4 -0
- package/dist/src/stores/lucid.js.map +1 -1
- package/package.json +6 -1
- package/skills/agent-setup/SKILL.md +40 -3
- package/skills/agent-tools/SKILL.md +3 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { isTypedQuestion, questionOptions, validateElicitationAnswer, validateElicitationValue, } from '../elicitation-input.js';
|
|
2
|
+
export const DEFAULT_CHANNEL_QUESTION_TEXTS = {
|
|
3
|
+
counter: (index, total) => `(${index + 1}/${total})`,
|
|
4
|
+
pickOne: 'Reply with the number of your choice.',
|
|
5
|
+
pickMany: 'Reply with one or more numbers, separated by commas.',
|
|
6
|
+
orWrite: 'Or write your own answer.',
|
|
7
|
+
skipWord: 'skip',
|
|
8
|
+
keep: (skipWord, labels) => `Reply *${skipWord}* to keep: ${labels}.`,
|
|
9
|
+
invalid: (problem) => `I could not read that answer (${problem}).`,
|
|
10
|
+
};
|
|
11
|
+
/** The checks' English problems ({@link parseChannelAnswer}), in Brazilian Portuguese. */
|
|
12
|
+
const PT_BR_PROBLEMS = [
|
|
13
|
+
[/^empty answer$/, () => 'resposta vazia'],
|
|
14
|
+
[/^not one of the options$/, () => 'não é uma das opções'],
|
|
15
|
+
[/^must be one of the offered options$/, () => 'precisa ser uma das opções'],
|
|
16
|
+
[/^requires an answer$/, () => 'precisa de uma resposta'],
|
|
17
|
+
[/^must be "true" or "false"$/, () => 'precisa ser "true" ou "false"'],
|
|
18
|
+
[/^must be a number$/, () => 'precisa ser um número'],
|
|
19
|
+
[/^must be at least (.+) characters$/, (n) => `precisa ter pelo menos ${n} caracteres`],
|
|
20
|
+
[/^must be at most (.+) characters$/, (n) => `precisa ter no máximo ${n} caracteres`],
|
|
21
|
+
[/^must be at least (.+)$/, (n) => `precisa ser no mínimo ${n}`],
|
|
22
|
+
[/^must be at most (.+)$/, (n) => `precisa ser no máximo ${n}`],
|
|
23
|
+
[/^must be a date as YYYY-MM-DD$/, () => 'precisa ser uma data no formato AAAA-MM-DD'],
|
|
24
|
+
[/^must be on or after (.+)$/, (d) => `precisa ser a partir de ${d}`],
|
|
25
|
+
[/^must be on or before (.+)$/, (d) => `precisa ser até ${d}`],
|
|
26
|
+
[/^must be an email address$/, () => 'precisa ser um endereço de e-mail'],
|
|
27
|
+
[/^must be an http\(s\) URL$/, () => 'precisa ser um link http(s)'],
|
|
28
|
+
[/^does not match the expected format$/, () => 'não está no formato esperado'],
|
|
29
|
+
[/^takes a single value$/, () => 'aceita só um valor'],
|
|
30
|
+
];
|
|
31
|
+
const ptBrProblem = (problem) => {
|
|
32
|
+
for (const [pattern, say] of PT_BR_PROBLEMS) {
|
|
33
|
+
const match = pattern.exec(problem);
|
|
34
|
+
if (match)
|
|
35
|
+
return say(...match.slice(1).map((group) => group ?? ''));
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
};
|
|
39
|
+
/** Brazilian Portuguese question texts — part of `ptBrChannelTexts`. */
|
|
40
|
+
export const ptBrChannelQuestionTexts = {
|
|
41
|
+
counter: (index, total) => `(${index + 1}/${total})`,
|
|
42
|
+
pickOne: 'Responda com o número da sua escolha.',
|
|
43
|
+
pickMany: 'Responda com um ou mais números, separados por vírgula.',
|
|
44
|
+
orWrite: 'Ou escreva sua própria resposta.',
|
|
45
|
+
skipWord: 'pular',
|
|
46
|
+
keep: (skipWord, labels) => `Responda *${skipWord}* para manter: ${labels}.`,
|
|
47
|
+
invalid: (problem) => {
|
|
48
|
+
const said = ptBrProblem(problem);
|
|
49
|
+
return said === null ? 'Não entendi essa resposta.' : `Não entendi essa resposta (${said}).`;
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
const labelOf = (question, value) => questionOptions(question).find((option) => option.value === value)?.label ?? value;
|
|
53
|
+
/** One question as a text message: its prompt, numbered options, and how to answer. */
|
|
54
|
+
export function formatChannelQuestion(question, position, texts = DEFAULT_CHANNEL_QUESTION_TEXTS) {
|
|
55
|
+
const lines = [];
|
|
56
|
+
if (position.index === 0 && position.preamble)
|
|
57
|
+
lines.push(position.preamble, '');
|
|
58
|
+
const counter = position.total > 1 ? `${texts.counter(position.index, position.total)} ` : '';
|
|
59
|
+
lines.push(`*${counter}${question.prompt}*`);
|
|
60
|
+
if (question.description)
|
|
61
|
+
lines.push(question.description);
|
|
62
|
+
const options = isTypedQuestion(question) ? [] : questionOptions(question);
|
|
63
|
+
if (options.length > 0) {
|
|
64
|
+
lines.push('');
|
|
65
|
+
for (const [index, option] of options.entries())
|
|
66
|
+
lines.push(`${index + 1}. ${option.label}`);
|
|
67
|
+
lines.push('', question.multiple === true ? texts.pickMany : texts.pickOne);
|
|
68
|
+
if (question.allowFreeText === true)
|
|
69
|
+
lines.push(texts.orWrite);
|
|
70
|
+
}
|
|
71
|
+
const defaults = question.defaults ?? [];
|
|
72
|
+
if (defaults.length > 0) {
|
|
73
|
+
lines.push(texts.keep(texts.skipWord, defaults.map((value) => labelOf(question, value)).join(', ')));
|
|
74
|
+
}
|
|
75
|
+
return lines.join('\n');
|
|
76
|
+
}
|
|
77
|
+
const fold = (value) => value.trim().toLowerCase();
|
|
78
|
+
/**
|
|
79
|
+
* Read a chat message as the answer to `question`: an option's number or label (several, separated
|
|
80
|
+
* by commas, for a multiple choice), a typed value for a typed question or one that allows free
|
|
81
|
+
* text, or the skip word. Checked against the question's own rules.
|
|
82
|
+
*/
|
|
83
|
+
export function parseChannelAnswer(question, text, skipWord = DEFAULT_CHANNEL_QUESTION_TEXTS.skipWord) {
|
|
84
|
+
const answer = text.trim();
|
|
85
|
+
if (fold(answer) === fold(skipWord))
|
|
86
|
+
return { status: 'skip' };
|
|
87
|
+
if (answer === '')
|
|
88
|
+
return { status: 'invalid', problem: 'empty answer' };
|
|
89
|
+
let values;
|
|
90
|
+
if (isTypedQuestion(question)) {
|
|
91
|
+
values = [answer];
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
const options = questionOptions(question);
|
|
95
|
+
const pick = (token) => {
|
|
96
|
+
const bare = token.trim().replace(/[.)]$/, '');
|
|
97
|
+
if (/^\d+$/.test(bare)) {
|
|
98
|
+
const option = options[Number(bare) - 1];
|
|
99
|
+
if (option)
|
|
100
|
+
return option.value;
|
|
101
|
+
}
|
|
102
|
+
const match = options.find((option) => fold(option.label) === fold(token) || fold(option.value) === fold(token));
|
|
103
|
+
return match?.value ?? null;
|
|
104
|
+
};
|
|
105
|
+
const whole = pick(answer);
|
|
106
|
+
if (whole !== null)
|
|
107
|
+
values = [whole];
|
|
108
|
+
else if (question.multiple === true) {
|
|
109
|
+
const tokens = answer.split(/\s*[,;]\s*|\s+/).filter((token) => token !== '');
|
|
110
|
+
const picked = tokens.map(pick);
|
|
111
|
+
if (picked.every((value) => value !== null))
|
|
112
|
+
values = [...new Set(picked)];
|
|
113
|
+
else if (question.allowFreeText === true)
|
|
114
|
+
values = [answer];
|
|
115
|
+
else
|
|
116
|
+
return { status: 'invalid', problem: 'not one of the options' };
|
|
117
|
+
}
|
|
118
|
+
else if (question.allowFreeText === true || options.length === 0)
|
|
119
|
+
values = [answer];
|
|
120
|
+
else
|
|
121
|
+
return { status: 'invalid', problem: 'not one of the options' };
|
|
122
|
+
}
|
|
123
|
+
for (const value of values) {
|
|
124
|
+
const problem = validateElicitationValue(question, value);
|
|
125
|
+
if (problem !== null)
|
|
126
|
+
return { status: 'invalid', problem };
|
|
127
|
+
}
|
|
128
|
+
const problem = validateElicitationAnswer(question, values);
|
|
129
|
+
return problem === null ? { status: 'answer', values } : { status: 'invalid', problem };
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=questions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"questions.js","sourceRoot":"","sources":["../../../src/channels/questions.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Split `text` into messages of at most `maxLength` characters, cutting where a reader would: at a
|
|
3
|
+
* paragraph break, else a line break, else the end of a sentence, else a space — and only when
|
|
4
|
+
* none of those falls past the first third of the window, mid-word. Whitespace at the cut is dropped;
|
|
5
|
+
* empty pieces are never returned.
|
|
6
|
+
*/
|
|
7
|
+
export declare function splitMessage(text: string, maxLength: number): string[];
|
|
8
|
+
//# sourceMappingURL=split.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"split.d.ts","sourceRoot":"","sources":["../../../src/channels/split.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,CAqBtE"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Split `text` into messages of at most `maxLength` characters, cutting where a reader would: at a
|
|
3
|
+
* paragraph break, else a line break, else the end of a sentence, else a space — and only when
|
|
4
|
+
* none of those falls past the first third of the window, mid-word. Whitespace at the cut is dropped;
|
|
5
|
+
* empty pieces are never returned.
|
|
6
|
+
*/
|
|
7
|
+
export function splitMessage(text, maxLength) {
|
|
8
|
+
if (!Number.isInteger(maxLength) || maxLength < 1) {
|
|
9
|
+
throw new RangeError('maxLength must be a positive integer');
|
|
10
|
+
}
|
|
11
|
+
const pieces = [];
|
|
12
|
+
let rest = text.trim();
|
|
13
|
+
while (rest.length > maxLength) {
|
|
14
|
+
const window = rest.slice(0, maxLength + 1);
|
|
15
|
+
const floor = Math.floor(maxLength / 3);
|
|
16
|
+
const cut = lastBreak(window, /\n\s*\n/g, floor) ??
|
|
17
|
+
lastBreak(window, /\n/g, floor) ??
|
|
18
|
+
lastBreak(window, /[.!?…](?=\s)/g, floor, true) ??
|
|
19
|
+
lastBreak(window, /\s/g, floor) ??
|
|
20
|
+
maxLength;
|
|
21
|
+
const piece = rest.slice(0, cut).trimEnd();
|
|
22
|
+
if (piece.length > 0)
|
|
23
|
+
pieces.push(piece);
|
|
24
|
+
rest = rest.slice(cut).trimStart();
|
|
25
|
+
}
|
|
26
|
+
if (rest.length > 0)
|
|
27
|
+
pieces.push(rest);
|
|
28
|
+
return pieces;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Where the last match of `pattern` in `window` lets the text be cut, if that is past `floor`:
|
|
32
|
+
* before the match, or after it (`keep` — a sentence keeps its full stop). `null` when there is none.
|
|
33
|
+
*/
|
|
34
|
+
function lastBreak(window, pattern, floor, keep = false) {
|
|
35
|
+
let found = null;
|
|
36
|
+
for (const match of window.matchAll(pattern)) {
|
|
37
|
+
const index = match.index ?? 0;
|
|
38
|
+
const at = keep ? index + match[0].length : index;
|
|
39
|
+
// A cut must leave the piece within the limit (the window is one character longer).
|
|
40
|
+
if (at > floor && at <= window.length - 1)
|
|
41
|
+
found = at;
|
|
42
|
+
}
|
|
43
|
+
return found;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=split.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"split.js","sourceRoot":"","sources":["../../../src/channels/split.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY,EAAE,SAAiB;IAC1D,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC,IAAI,SAAS,GAAG,CAAC,EAAE,CAAC;QAClD,MAAM,IAAI,UAAU,CAAC,sCAAsC,CAAC,CAAC;IAC/D,CAAC;IACD,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IACvB,OAAO,IAAI,CAAC,MAAM,GAAG,SAAS,EAAE,CAAC;QAC/B,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,GAAG,CAAC,CAAC,CAAC;QAC5C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC;QACxC,MAAM,GAAG,GACP,SAAS,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,CAAC;YACpC,SAAS,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC;YAC/B,SAAS,CAAC,MAAM,EAAE,eAAe,EAAE,KAAK,EAAE,IAAI,CAAC;YAC/C,SAAS,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC;YAC/B,SAAS,CAAC;QACZ,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,OAAO,EAAE,CAAC;QAC3C,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;YAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACzC,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,CAAC;IACrC,CAAC;IACD,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC;QAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACvC,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;GAGG;AACH,SAAS,SAAS,CAAC,MAAc,EAAE,OAAe,EAAE,KAAa,EAAE,IAAI,GAAG,KAAK;IAC7E,IAAI,KAAK,GAAkB,IAAI,CAAC;IAChC,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QAC7C,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,IAAI,CAAC,CAAC;QAC/B,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC;QAClD,oFAAoF;QACpF,IAAI,EAAE,GAAG,KAAK,IAAI,EAAE,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC;YAAE,KAAK,GAAG,EAAE,CAAC;IACxD,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { LucidRawRunner } from '../stores/lucid.js';
|
|
2
|
+
/**
|
|
3
|
+
* Text channels' short-lived state, every entry with a TTL: the provider message ids already taken
|
|
4
|
+
* (so a webhook delivered twice starts one turn), a question waiting for the person's answer, an
|
|
5
|
+
* outcome already relayed.
|
|
6
|
+
*
|
|
7
|
+
* {@link InMemoryChannelStore} only sees its own process. With several replicas a retry, an answer
|
|
8
|
+
* or a settled proposal can land on another one, so use a shared store there:
|
|
9
|
+
* {@link lucidChannelStore} (the default when the agent's own store is Lucid),
|
|
10
|
+
* {@link redisChannelStore}, or your own.
|
|
11
|
+
*/
|
|
12
|
+
export interface ChannelStore {
|
|
13
|
+
/**
|
|
14
|
+
* Take `key` for `ttlMs` — atomically. `true` when it was free (now taken), `false` when someone
|
|
15
|
+
* already took it.
|
|
16
|
+
*/
|
|
17
|
+
claim(key: string, ttlMs: number): boolean | Promise<boolean>;
|
|
18
|
+
/** The live value under `key`, or `null`. */
|
|
19
|
+
get(key: string): string | null | Promise<string | null>;
|
|
20
|
+
/** Store `value` under `key` for `ttlMs`, replacing what was there. */
|
|
21
|
+
set(key: string, value: string, ttlMs: number): void | Promise<void>;
|
|
22
|
+
delete(key: string): void | Promise<void>;
|
|
23
|
+
}
|
|
24
|
+
/** Process-local {@link ChannelStore}: a single replica, tests, development. */
|
|
25
|
+
export declare class InMemoryChannelStore implements ChannelStore {
|
|
26
|
+
#private;
|
|
27
|
+
private readonly maxEntries;
|
|
28
|
+
/** @param maxEntries Past this many live keys, the oldest are forgotten first. */
|
|
29
|
+
constructor(maxEntries?: number);
|
|
30
|
+
claim(key: string, ttlMs: number): boolean;
|
|
31
|
+
get(key: string): string | null;
|
|
32
|
+
set(key: string, value: string, ttlMs: number): void;
|
|
33
|
+
delete(key: string): void;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The slice of a Redis client {@link redisChannelStore} uses — an `@adonisjs/redis` connection
|
|
37
|
+
* (ioredis) has it: `redisChannelStore(redis)` or `redisChannelStore(redis.connection('main'))`.
|
|
38
|
+
*/
|
|
39
|
+
export interface ChannelStoreRedis {
|
|
40
|
+
set(key: string, value: string, px: 'PX', ttlMs: number, nx: 'NX'): Promise<unknown>;
|
|
41
|
+
set(key: string, value: string, px: 'PX', ttlMs: number): Promise<unknown>;
|
|
42
|
+
get(key: string): Promise<string | null>;
|
|
43
|
+
del(key: string): Promise<unknown>;
|
|
44
|
+
}
|
|
45
|
+
/** A {@link ChannelStore} shared by every replica, over Redis (`SET … PX … NX` for claims). */
|
|
46
|
+
export declare function redisChannelStore(redis: ChannelStoreRedis, options?: {
|
|
47
|
+
prefix?: string;
|
|
48
|
+
}): ChannelStore;
|
|
49
|
+
export interface LucidChannelStoreOptions {
|
|
50
|
+
/** A named connection of the `Database` manager. Default: the one `db` is. */
|
|
51
|
+
connection?: string;
|
|
52
|
+
/** Default `agent_channel_state`. */
|
|
53
|
+
table?: string;
|
|
54
|
+
/**
|
|
55
|
+
* Create the table (and its index) on first use. Default `true`; `false` when a migration owns it
|
|
56
|
+
* (`createAgentTables` and the published migration create it under the default name).
|
|
57
|
+
*/
|
|
58
|
+
autoCreateTables?: boolean;
|
|
59
|
+
/** How often a claim also deletes expired rows. Default 10 minutes. */
|
|
60
|
+
purgeEveryMs?: number;
|
|
61
|
+
}
|
|
62
|
+
/** A Lucid `Database` (or one of its connections): raw SQL, and named connections when it has them. */
|
|
63
|
+
export type LucidChannelStoreDatabase = LucidRawRunner & {
|
|
64
|
+
connection?(name?: string): LucidRawRunner;
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* A {@link ChannelStore} in the app's SQL database through Lucid — shared by every replica, no Redis
|
|
68
|
+
* needed. One row per key in `agent_channel_state`; a claim is one `INSERT` that skips on a duplicate
|
|
69
|
+
* key (`ON CONFLICT DO NOTHING`, `INSERT IGNORE` on MySQL), so the primary key is the lock. Expired
|
|
70
|
+
* rows are deleted as claims come in (`purgeEveryMs`), and an expired key can be taken again.
|
|
71
|
+
*
|
|
72
|
+
* ```ts
|
|
73
|
+
* import db from '@adonisjs/lucid/services/db'
|
|
74
|
+
* channels.handle(adapter, { …, store: lucidChannelStore(db) })
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
export declare function lucidChannelStore(database: LucidChannelStoreDatabase, options?: LucidChannelStoreOptions): ChannelStore;
|
|
78
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../../src/channels/store.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAIzD;;;;;;;;;GASG;AACH,MAAM,WAAW,YAAY;IAC3B;;;OAGG;IACH,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9D,6CAA6C;IAC7C,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACzD,uEAAuE;IACvE,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrE,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3C;AAED,gFAAgF;AAChF,qBAAa,oBAAqB,YAAW,YAAY;;IAI3C,OAAO,CAAC,QAAQ,CAAC,UAAU;IADvC,kFAAkF;IAClF,YAA6B,UAAU,SAAS,EAAI;IAsBpD,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAIzC;IAED,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAE9B;IAED,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAEnD;IAED,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAExB;CACF;AAED;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACrF,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC3E,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACzC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACpC;AAED,+FAA+F;AAC/F,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,iBAAiB,EACxB,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,GAChC,YAAY,CAed;AAED,MAAM,WAAW,wBAAwB;IACvC,8EAA8E;IAC9E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qCAAqC;IACrC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,uEAAuE;IACvE,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,uGAAuG;AACvG,MAAM,MAAM,yBAAyB,GAAG,cAAc,GAAG;IACvD,UAAU,CAAC,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,cAAc,CAAC;CAC5C,CAAC;AAeF;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,yBAAyB,EACnC,OAAO,GAAE,wBAA6B,GACrC,YAAY,CA4Ed"}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { AGENT_TABLES, ensureChannelStateTable, rowsOf } from '../stores/lucid-schema.js';
|
|
3
|
+
import { isMySql, portableSql } from '../stores/sql-dialect.js';
|
|
4
|
+
/** Process-local {@link ChannelStore}: a single replica, tests, development. */
|
|
5
|
+
export class InMemoryChannelStore {
|
|
6
|
+
maxEntries;
|
|
7
|
+
#entries = new Map();
|
|
8
|
+
/** @param maxEntries Past this many live keys, the oldest are forgotten first. */
|
|
9
|
+
constructor(maxEntries = 50_000) {
|
|
10
|
+
this.maxEntries = maxEntries;
|
|
11
|
+
}
|
|
12
|
+
#live(key) {
|
|
13
|
+
const entry = this.#entries.get(key);
|
|
14
|
+
if (entry === undefined)
|
|
15
|
+
return undefined;
|
|
16
|
+
if (entry.expiresAt > Date.now())
|
|
17
|
+
return entry;
|
|
18
|
+
this.#entries.delete(key);
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
#put(key, value, ttlMs) {
|
|
22
|
+
const now = Date.now();
|
|
23
|
+
this.#entries.delete(key);
|
|
24
|
+
this.#entries.set(key, { value, expiresAt: now + ttlMs });
|
|
25
|
+
if (this.#entries.size <= this.maxEntries)
|
|
26
|
+
return;
|
|
27
|
+
// Insertion order is write order: the first keys are the oldest.
|
|
28
|
+
for (const [stored, entry] of this.#entries) {
|
|
29
|
+
if (this.#entries.size <= this.maxEntries && entry.expiresAt > now)
|
|
30
|
+
break;
|
|
31
|
+
this.#entries.delete(stored);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
claim(key, ttlMs) {
|
|
35
|
+
if (this.#live(key) !== undefined)
|
|
36
|
+
return false;
|
|
37
|
+
this.#put(key, null, ttlMs);
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
get(key) {
|
|
41
|
+
return this.#live(key)?.value ?? null;
|
|
42
|
+
}
|
|
43
|
+
set(key, value, ttlMs) {
|
|
44
|
+
this.#put(key, value, ttlMs);
|
|
45
|
+
}
|
|
46
|
+
delete(key) {
|
|
47
|
+
this.#entries.delete(key);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/** A {@link ChannelStore} shared by every replica, over Redis (`SET … PX … NX` for claims). */
|
|
51
|
+
export function redisChannelStore(redis, options = {}) {
|
|
52
|
+
const prefix = options.prefix ?? 'agora:channel:';
|
|
53
|
+
const ttl = (ms) => Math.max(1, Math.round(ms));
|
|
54
|
+
return {
|
|
55
|
+
async claim(key, ttlMs) {
|
|
56
|
+
return (await redis.set(`${prefix}${key}`, '', 'PX', ttl(ttlMs), 'NX')) === 'OK';
|
|
57
|
+
},
|
|
58
|
+
get: (key) => redis.get(`${prefix}${key}`),
|
|
59
|
+
async set(key, value, ttlMs) {
|
|
60
|
+
await redis.set(`${prefix}${key}`, value, 'PX', ttl(ttlMs));
|
|
61
|
+
},
|
|
62
|
+
async delete(key) {
|
|
63
|
+
await redis.del(`${prefix}${key}`);
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/** Keys longer than the column are stored by their hash. */
|
|
68
|
+
const storedKey = (key) => key.length <= 255 ? key : `sha256:${createHash('sha256').update(key).digest('hex')}`;
|
|
69
|
+
/** How many rows a raw write touched, whatever the driver wraps it in. */
|
|
70
|
+
function affected(result) {
|
|
71
|
+
// mysql2: `[ResultSetHeader, fields]`. Everything else here answers `RETURNING` with rows.
|
|
72
|
+
const head = Array.isArray(result) ? result[0] : result;
|
|
73
|
+
const count = head?.affectedRows;
|
|
74
|
+
if (typeof count === 'number')
|
|
75
|
+
return count;
|
|
76
|
+
return rowsOf(result).length;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* A {@link ChannelStore} in the app's SQL database through Lucid — shared by every replica, no Redis
|
|
80
|
+
* needed. One row per key in `agent_channel_state`; a claim is one `INSERT` that skips on a duplicate
|
|
81
|
+
* key (`ON CONFLICT DO NOTHING`, `INSERT IGNORE` on MySQL), so the primary key is the lock. Expired
|
|
82
|
+
* rows are deleted as claims come in (`purgeEveryMs`), and an expired key can be taken again.
|
|
83
|
+
*
|
|
84
|
+
* ```ts
|
|
85
|
+
* import db from '@adonisjs/lucid/services/db'
|
|
86
|
+
* channels.handle(adapter, { …, store: lucidChannelStore(db) })
|
|
87
|
+
* ```
|
|
88
|
+
*/
|
|
89
|
+
export function lucidChannelStore(database, options = {}) {
|
|
90
|
+
const db = options.connection !== undefined && typeof database.connection === 'function'
|
|
91
|
+
? database.connection(options.connection)
|
|
92
|
+
: database;
|
|
93
|
+
const table = options.table ?? AGENT_TABLES.channelState;
|
|
94
|
+
const purgeEveryMs = options.purgeEveryMs ?? 10 * 60 * 1000;
|
|
95
|
+
const mysql = isMySql(db);
|
|
96
|
+
const sql = (statement) => portableSql(db, statement);
|
|
97
|
+
let ready;
|
|
98
|
+
let lastPurge = 0;
|
|
99
|
+
const init = () => {
|
|
100
|
+
if (options.autoCreateTables === false)
|
|
101
|
+
return Promise.resolve();
|
|
102
|
+
ready ??= ensureChannelStateTable(db, table).catch((error) => {
|
|
103
|
+
ready = undefined;
|
|
104
|
+
throw error;
|
|
105
|
+
});
|
|
106
|
+
return ready;
|
|
107
|
+
};
|
|
108
|
+
const purge = async (now) => {
|
|
109
|
+
if (now - lastPurge < purgeEveryMs)
|
|
110
|
+
return;
|
|
111
|
+
lastPurge = now;
|
|
112
|
+
await db.rawQuery(sql(`DELETE FROM "${table}" WHERE "expires_at" <= ?`), [now]);
|
|
113
|
+
};
|
|
114
|
+
return {
|
|
115
|
+
async claim(key, ttlMs) {
|
|
116
|
+
await init();
|
|
117
|
+
const now = Date.now();
|
|
118
|
+
await purge(now);
|
|
119
|
+
const id = storedKey(key);
|
|
120
|
+
// An expired claim is free again.
|
|
121
|
+
await db.rawQuery(sql(`DELETE FROM "${table}" WHERE "key" = ? AND "expires_at" <= ?`), [
|
|
122
|
+
id,
|
|
123
|
+
now,
|
|
124
|
+
]);
|
|
125
|
+
const bindings = [id, now + ttlMs, now];
|
|
126
|
+
const insert = mysql
|
|
127
|
+
? sql(`INSERT IGNORE INTO "${table}" ("key", "value", "expires_at", "created_at") VALUES (?, NULL, ?, ?)`)
|
|
128
|
+
: `INSERT INTO "${table}" ("key", "value", "expires_at", "created_at") VALUES (?, NULL, ?, ?) ON CONFLICT ("key") DO NOTHING RETURNING "key"`;
|
|
129
|
+
return affected(await db.rawQuery(insert, bindings)) === 1;
|
|
130
|
+
},
|
|
131
|
+
async get(key) {
|
|
132
|
+
await init();
|
|
133
|
+
const rows = rowsOf(await db.rawQuery(sql(`SELECT "value" FROM "${table}" WHERE "key" = ? AND "expires_at" > ?`), [storedKey(key), Date.now()]));
|
|
134
|
+
const value = rows[0]?.value;
|
|
135
|
+
return typeof value === 'string' ? value : null;
|
|
136
|
+
},
|
|
137
|
+
async set(key, value, ttlMs) {
|
|
138
|
+
await init();
|
|
139
|
+
const now = Date.now();
|
|
140
|
+
const bindings = [storedKey(key), value, now + ttlMs, now];
|
|
141
|
+
await db.rawQuery(mysql
|
|
142
|
+
? sql(`INSERT INTO "${table}" ("key", "value", "expires_at", "created_at") VALUES (?, ?, ?, ?) ON DUPLICATE KEY UPDATE "value" = VALUES("value"), "expires_at" = VALUES("expires_at")`)
|
|
143
|
+
: `INSERT INTO "${table}" ("key", "value", "expires_at", "created_at") VALUES (?, ?, ?, ?) ON CONFLICT ("key") DO UPDATE SET "value" = excluded."value", "expires_at" = excluded."expires_at"`, bindings);
|
|
144
|
+
},
|
|
145
|
+
async delete(key) {
|
|
146
|
+
await init();
|
|
147
|
+
await db.rawQuery(sql(`DELETE FROM "${table}" WHERE "key" = ?`), [storedKey(key)]);
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../../src/channels/store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The contract between a text channel (WhatsApp, Telegram, …) and the agent: what an adapter reads
|
|
3
|
+
* off a webhook and what it is asked to deliver. Framework-free — `channels.handle()` builds the
|
|
4
|
+
* {@link ChannelRequest} from Adonis's `HttpContext`, and a test builds one by hand.
|
|
5
|
+
*/
|
|
6
|
+
/** The markdown a channel renders: WhatsApp's, Telegram's MarkdownV2, or none (plain text). */
|
|
7
|
+
export type ChannelMarkdown = 'whatsapp' | 'telegram' | 'none';
|
|
8
|
+
/** What a channel can show — what the service shapes every reply to. */
|
|
9
|
+
export interface ChannelCapabilities {
|
|
10
|
+
/**
|
|
11
|
+
* The most reply buttons one message can carry. Omitted (or below 2) → the channel has none, and a
|
|
12
|
+
* proposal is put to the person as a text instruction instead of Confirm/Cancel buttons.
|
|
13
|
+
*/
|
|
14
|
+
buttons?: number;
|
|
15
|
+
/** The markdown dialect replies are converted to. */
|
|
16
|
+
markdown: ChannelMarkdown;
|
|
17
|
+
/** The longest text one message may carry; longer replies are split into several messages. */
|
|
18
|
+
maxLength: number;
|
|
19
|
+
}
|
|
20
|
+
/** One webhook request, as an adapter sees it. */
|
|
21
|
+
export interface ChannelRequest {
|
|
22
|
+
/** Upper-case HTTP method. */
|
|
23
|
+
method: string;
|
|
24
|
+
/** Path and query string, e.g. `/webhooks/whatsapp?hub.mode=subscribe`. */
|
|
25
|
+
url: string;
|
|
26
|
+
/** A request header by (case-insensitive) name. */
|
|
27
|
+
header(name: string): string | undefined;
|
|
28
|
+
/** Route params (`/webhooks/whatsapp/:token` → `{ token }`). */
|
|
29
|
+
params: Record<string, unknown>;
|
|
30
|
+
/** The parsed body. */
|
|
31
|
+
body: unknown;
|
|
32
|
+
/**
|
|
33
|
+
* The body exactly as it arrived — what a signature is computed over. `null` when the body parser
|
|
34
|
+
* did not keep it (a request it does not parse).
|
|
35
|
+
*/
|
|
36
|
+
rawBody: string | null;
|
|
37
|
+
}
|
|
38
|
+
/** A message a person sent on the channel. */
|
|
39
|
+
export interface InboundMessage {
|
|
40
|
+
/** The provider's id for this message — retries of one delivery carry the same id. */
|
|
41
|
+
id: string;
|
|
42
|
+
/** Who sent it: a phone number, a Telegram user id… — what `actor()` maps to an account. */
|
|
43
|
+
from: string;
|
|
44
|
+
/** Where replies go: the chat / conversation id {@link ChannelAdapter.send} is called with. */
|
|
45
|
+
conversation: string;
|
|
46
|
+
/**
|
|
47
|
+
* What the person wrote — for a button press, the button's label (or its id when there is none);
|
|
48
|
+
* for a media message, its caption (`''` when it has none).
|
|
49
|
+
*/
|
|
50
|
+
text: string;
|
|
51
|
+
/** Files the message carries (an image, a voice note, a document) — downloaded and attached to the turn. */
|
|
52
|
+
media?: InboundMedia[];
|
|
53
|
+
/** The id of the reply button the person pressed, when the message is a button press. */
|
|
54
|
+
buttonId?: string;
|
|
55
|
+
/** The provider's payload, untouched. */
|
|
56
|
+
raw: unknown;
|
|
57
|
+
}
|
|
58
|
+
/** One file in an inbound message, before it is downloaded. */
|
|
59
|
+
export interface InboundMedia {
|
|
60
|
+
kind: 'image' | 'audio' | 'video' | 'document' | 'sticker';
|
|
61
|
+
/** MIME type, when the webhook says (it usually does). */
|
|
62
|
+
contentType?: string;
|
|
63
|
+
/** The file's name, when the webhook says (documents). */
|
|
64
|
+
filename?: string;
|
|
65
|
+
/** Size in bytes, when the webhook says. */
|
|
66
|
+
sizeBytes?: number;
|
|
67
|
+
/** Whatever the adapter needs to download it (a media id, a file id, the message key). */
|
|
68
|
+
ref: unknown;
|
|
69
|
+
}
|
|
70
|
+
/** A downloaded file. */
|
|
71
|
+
export interface ChannelMediaFile {
|
|
72
|
+
data: Buffer;
|
|
73
|
+
contentType: string;
|
|
74
|
+
filename?: string;
|
|
75
|
+
}
|
|
76
|
+
export interface ChannelButton {
|
|
77
|
+
/** What comes back as {@link InboundMessage.buttonId} when it is pressed. */
|
|
78
|
+
id: string;
|
|
79
|
+
label: string;
|
|
80
|
+
}
|
|
81
|
+
/** One message to deliver: text, or text with reply buttons. */
|
|
82
|
+
export type OutboundMessage = {
|
|
83
|
+
text: string;
|
|
84
|
+
buttons?: undefined;
|
|
85
|
+
fallbackText?: undefined;
|
|
86
|
+
instruction?: undefined;
|
|
87
|
+
} | {
|
|
88
|
+
text: string;
|
|
89
|
+
buttons: ChannelButton[];
|
|
90
|
+
/**
|
|
91
|
+
* The same message as text only — a reply instruction in place of the buttons. What an adapter
|
|
92
|
+
* sends when the provider definitely refused the buttons (a 4xx), so the person still gets a
|
|
93
|
+
* way to answer.
|
|
94
|
+
*/
|
|
95
|
+
fallbackText: string;
|
|
96
|
+
/**
|
|
97
|
+
* The reply instruction alone ("Reply *yes* to confirm or *no* to cancel."), already in the
|
|
98
|
+
* channel's markdown. An adapter whose buttons may not render on every phone puts it in the
|
|
99
|
+
* buttons message too, so the person can still answer by text.
|
|
100
|
+
*/
|
|
101
|
+
instruction?: string;
|
|
102
|
+
};
|
|
103
|
+
/** A reply the adapter answers the webhook with instead of acknowledging it (a verification GET). */
|
|
104
|
+
export interface ChannelChallengeResponse {
|
|
105
|
+
status: number;
|
|
106
|
+
body: string;
|
|
107
|
+
contentType?: string;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* One text channel. The built-in ones are `evolutionApi()`, `whatsmiau()`, `whatsappCloud()` and
|
|
111
|
+
* `telegram()`; any object of this shape works the same way.
|
|
112
|
+
*/
|
|
113
|
+
export interface ChannelAdapter {
|
|
114
|
+
/** Names the channel: the dedupe key's prefix, and `via` on a decision made with its buttons. */
|
|
115
|
+
readonly name: string;
|
|
116
|
+
readonly capabilities: ChannelCapabilities;
|
|
117
|
+
/**
|
|
118
|
+
* Answer a request that is not a message — WhatsApp Cloud's `GET ?hub.challenge=…` subscription
|
|
119
|
+
* check. `null` → not one of those; the request goes on to {@link verify}.
|
|
120
|
+
*/
|
|
121
|
+
challenge?(request: ChannelRequest): ChannelChallengeResponse | null;
|
|
122
|
+
/** Is this request really from the provider (a signature, a shared token)? `false` → `401`. */
|
|
123
|
+
verify(request: ChannelRequest): boolean | Promise<boolean>;
|
|
124
|
+
/**
|
|
125
|
+
* The person's message(s) in a verified webhook body. `null` (or `[]`) → nothing to answer: a
|
|
126
|
+
* delivery receipt, the bot's own message, a group, a media message…
|
|
127
|
+
*/
|
|
128
|
+
parse(body: unknown): InboundMessage | InboundMessage[] | null;
|
|
129
|
+
/**
|
|
130
|
+
* Tell the provider the message was received, where it waits for that — Telegram's
|
|
131
|
+
* `answerCallbackQuery` after a button press. Runs in the background, before the turn.
|
|
132
|
+
*/
|
|
133
|
+
acknowledge?(message: InboundMessage): Promise<void>;
|
|
134
|
+
/**
|
|
135
|
+
* Download one of a message's {@link InboundMessage.media}. Throws {@link
|
|
136
|
+
* import('./http.js').ChannelMediaTooLargeError} past `maxBytes` (without reading the rest).
|
|
137
|
+
* Absent → media messages are answered with `texts.mediaRefused`.
|
|
138
|
+
*/
|
|
139
|
+
download?(media: InboundMedia, options: {
|
|
140
|
+
maxBytes: number;
|
|
141
|
+
}): Promise<ChannelMediaFile>;
|
|
142
|
+
/** Deliver one message to a conversation. Throws when the provider refused it. */
|
|
143
|
+
send(conversation: string, message: OutboundMessage): Promise<void>;
|
|
144
|
+
}
|
|
145
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/channels/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The contract between a text channel (WhatsApp, Telegram, …) and the agent: what an adapter reads
|
|
3
|
+
* off a webhook and what it is asked to deliver. Framework-free — `channels.handle()` builds the
|
|
4
|
+
* {@link ChannelRequest} from Adonis's `HttpContext`, and a test builds one by hand.
|
|
5
|
+
*/
|
|
6
|
+
export {};
|
|
7
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/channels/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"builtins.d.ts","sourceRoot":"","sources":["../../../src/genui/builtins.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,KAAK,mBAAmB,EAAmB,MAAM,cAAc,CAAC;AACzE,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAG5D,KAAK,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAyBrC,oEAAoE;AACpE,wBAAgB,SAAS,CACvB,OAAO,EAAE,SAAS;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,EAAE,EAClD,IAAI,EAAE,SAAS,KAAK,EAAE,EACtB,GAAG,SAAK,GACP,MAAM,
|
|
1
|
+
{"version":3,"file":"builtins.d.ts","sourceRoot":"","sources":["../../../src/genui/builtins.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,KAAK,mBAAmB,EAAmB,MAAM,cAAc,CAAC;AACzE,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAG5D,KAAK,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAyBrC,oEAAoE;AACpE,wBAAgB,SAAS,CACvB,OAAO,EAAE,SAAS;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,EAAE,EAClD,IAAI,EAAE,SAAS,KAAK,EAAE,EACtB,GAAG,SAAK,GACP,MAAM,CAER;AAyBD,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,GAAG,KAAK,GAAG,KAAK,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;CACd;AAED,4EAA4E;AAC5E,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,QAAQ,EAAE,CAoClE;AAOD,eAAO,MAAM,SAAS,iCA2BpB,CAAC;AAEH,eAAO,MAAM,KAAK,iCA6BhB,CAAC;AA+EH,eAAO,MAAM,QAAQ;YACX,MAAM;WACP;QACL,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;QACvB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,KAAK,CAAC,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,CAAC;QAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,EAAE;EA+BH,CAAC;AAEH,eAAO,MAAM,WAAW;YACd,MAAM;WACP;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE;EAwB3E,CAAC;AAEH,eAAO,MAAM,SAAS;YACZ,MAAM;WACP;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE;EAsBzD,CAAC;AAEH,eAAO,MAAM,QAAQ;YACX,MAAM;YACN;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE;EAsBhE,CAAC;AAEH,eAAO,MAAM,SAAS,4BAMpB,CAAC;AAEH,eAAO,MAAM,IAAI,4BASf,CAAC;AAEH,eAAO,MAAM,OAAO,4BAclB,CAAC;AAEH,eAAO,MAAM,KAAK,4BAOhB,CAAC;AAEH,eAAO,MAAM,IAAI,4BAQf,CAAC;AAEH,eAAO,MAAM,OAAO,4BAMlB,CAAC;AAEH,eAAO,MAAM,IAAI,4BAMf,CAAC;AAEH,eAAO,MAAM,KAAK,4BAYhB,CAAC;AAEH,eAAO,MAAM,IAAI,4BAMf,CAAC;AAEH,eAAO,MAAM,KAAK,4BAMhB,CAAC;AAEH,8CAA8C;AAC9C,eAAO,MAAM,kBAAkB,EAAE,SAAS,mBAAmB,CAAC,OAAO,CAAC,EAUrE,CAAC;AAEF,8FAA8F;AAC9F,eAAO,MAAM,iBAAiB,EAAE,SAAS,mBAAmB,CAAC,OAAO,CAAC,EAQpE,CAAC"}
|