@andreprado/agentkit 0.1.0-alpha.23 → 0.1.0-alpha.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/guides/add-channel.md +92 -5
- package/docs/guides/channel-security.md +35 -3
- package/docs/guides/debug-channel.md +16 -4
- package/docs/llms-full.txt +54 -1
- package/docs/llms.txt +2 -0
- package/package.json +1 -1
- package/src/cli/commands/channels.ts +35 -1
- package/src/index.ts +191 -5
- package/src/runtime/channels/generic-webhook.ts +752 -3
- package/src/runtime/channels.ts +3 -0
- package/src/runtime/config.ts +144 -0
- package/src/runtime/dev-server.ts +131 -1
- package/src/runtime/inspect.ts +18 -0
- package/src/runtime/targets/cloudflare/build.ts +112 -4
package/src/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ export type DiscordChannelMode = "interactions" | "bot";
|
|
|
9
9
|
export type TelegramAllowedUpdate = "message";
|
|
10
10
|
export type ChannelAudioMode = "off" | "reject" | "transcribe";
|
|
11
11
|
export type TranscriptionProviderName = "test" | "openai" | "groq";
|
|
12
|
+
export type GenericWebhookDirection = "inbound" | "outbound";
|
|
12
13
|
|
|
13
14
|
export type AgentProvider = {
|
|
14
15
|
name: AgentProviderName;
|
|
@@ -66,6 +67,37 @@ export type ChannelAudioConfig =
|
|
|
66
67
|
maxBytes?: number;
|
|
67
68
|
};
|
|
68
69
|
|
|
70
|
+
export type ChannelReplyRouteInput = {
|
|
71
|
+
channel: string;
|
|
72
|
+
recipientFrom?: string;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export type ChannelReplyRouteConfig = {
|
|
76
|
+
channel: string;
|
|
77
|
+
recipientFrom?: string;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export type GenericWebhookOutputAuthInput =
|
|
81
|
+
| {
|
|
82
|
+
type: "none";
|
|
83
|
+
}
|
|
84
|
+
| {
|
|
85
|
+
type: "bearer";
|
|
86
|
+
tokenSecret: string;
|
|
87
|
+
}
|
|
88
|
+
| {
|
|
89
|
+
type: "header";
|
|
90
|
+
headerName: string;
|
|
91
|
+
valueSecret: string;
|
|
92
|
+
}
|
|
93
|
+
| {
|
|
94
|
+
type: "hmac";
|
|
95
|
+
secret: string;
|
|
96
|
+
headerName?: string;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export type GenericWebhookOutputAuthConfig = GenericWebhookOutputAuthInput;
|
|
100
|
+
|
|
69
101
|
export type AgentTranscriptionLimits = {
|
|
70
102
|
maxDurationSeconds?: number;
|
|
71
103
|
maxBytes?: number;
|
|
@@ -92,6 +124,7 @@ export type WebsiteChannelInput = {
|
|
|
92
124
|
limits?: ChannelLimits;
|
|
93
125
|
buffer?: ChannelBufferInput;
|
|
94
126
|
audio?: ChannelAudioInput;
|
|
127
|
+
replyTo?: ChannelReplyRouteInput;
|
|
95
128
|
};
|
|
96
129
|
|
|
97
130
|
export type WebsiteChannelConfig = {
|
|
@@ -103,6 +136,7 @@ export type WebsiteChannelConfig = {
|
|
|
103
136
|
limits?: ChannelLimits;
|
|
104
137
|
buffer?: ChannelBufferConfig;
|
|
105
138
|
audio?: ChannelAudioConfig;
|
|
139
|
+
replyTo?: ChannelReplyRouteConfig;
|
|
106
140
|
};
|
|
107
141
|
|
|
108
142
|
export type TelegramChannelInput = {
|
|
@@ -112,6 +146,7 @@ export type TelegramChannelInput = {
|
|
|
112
146
|
limits?: ChannelLimits;
|
|
113
147
|
buffer?: ChannelBufferInput;
|
|
114
148
|
audio?: ChannelAudioInput;
|
|
149
|
+
replyTo?: ChannelReplyRouteInput;
|
|
115
150
|
};
|
|
116
151
|
|
|
117
152
|
export type TelegramChannelConfig = {
|
|
@@ -123,6 +158,7 @@ export type TelegramChannelConfig = {
|
|
|
123
158
|
limits?: ChannelLimits;
|
|
124
159
|
buffer?: ChannelBufferConfig;
|
|
125
160
|
audio?: ChannelAudioConfig;
|
|
161
|
+
replyTo?: ChannelReplyRouteConfig;
|
|
126
162
|
};
|
|
127
163
|
|
|
128
164
|
export type WhatsappChannelInput = {
|
|
@@ -132,6 +168,7 @@ export type WhatsappChannelInput = {
|
|
|
132
168
|
limits?: ChannelLimits;
|
|
133
169
|
buffer?: ChannelBufferInput;
|
|
134
170
|
audio?: ChannelAudioInput;
|
|
171
|
+
replyTo?: ChannelReplyRouteInput;
|
|
135
172
|
};
|
|
136
173
|
|
|
137
174
|
export type WhatsappChannelConfig = {
|
|
@@ -142,6 +179,7 @@ export type WhatsappChannelConfig = {
|
|
|
142
179
|
limits?: ChannelLimits;
|
|
143
180
|
buffer?: ChannelBufferConfig;
|
|
144
181
|
audio?: ChannelAudioConfig;
|
|
182
|
+
replyTo?: ChannelReplyRouteConfig;
|
|
145
183
|
};
|
|
146
184
|
|
|
147
185
|
export type DiscordChannelInput = {
|
|
@@ -150,6 +188,7 @@ export type DiscordChannelInput = {
|
|
|
150
188
|
secrets?: string[];
|
|
151
189
|
limits?: ChannelLimits;
|
|
152
190
|
buffer?: ChannelBufferInput;
|
|
191
|
+
replyTo?: ChannelReplyRouteInput;
|
|
153
192
|
};
|
|
154
193
|
|
|
155
194
|
export type DiscordChannelConfig = {
|
|
@@ -160,6 +199,7 @@ export type DiscordChannelConfig = {
|
|
|
160
199
|
secrets: string[];
|
|
161
200
|
limits?: ChannelLimits;
|
|
162
201
|
buffer?: ChannelBufferConfig;
|
|
202
|
+
replyTo?: ChannelReplyRouteConfig;
|
|
163
203
|
audio?: never;
|
|
164
204
|
};
|
|
165
205
|
|
|
@@ -168,6 +208,7 @@ export type SlackChannelInput = {
|
|
|
168
208
|
secrets?: string[];
|
|
169
209
|
limits?: ChannelLimits;
|
|
170
210
|
buffer?: ChannelBufferInput;
|
|
211
|
+
replyTo?: ChannelReplyRouteInput;
|
|
171
212
|
};
|
|
172
213
|
|
|
173
214
|
export type SlackChannelConfig = {
|
|
@@ -177,6 +218,7 @@ export type SlackChannelConfig = {
|
|
|
177
218
|
secrets: string[];
|
|
178
219
|
limits?: ChannelLimits;
|
|
179
220
|
buffer?: ChannelBufferConfig;
|
|
221
|
+
replyTo?: ChannelReplyRouteConfig;
|
|
180
222
|
audio?: never;
|
|
181
223
|
};
|
|
182
224
|
|
|
@@ -185,15 +227,42 @@ export type GenericWebhookChannelInput = {
|
|
|
185
227
|
secrets?: string[];
|
|
186
228
|
limits?: ChannelLimits;
|
|
187
229
|
buffer?: ChannelBufferInput;
|
|
230
|
+
replyTo?: ChannelReplyRouteInput;
|
|
188
231
|
};
|
|
189
232
|
|
|
190
233
|
export type GenericWebhookChannelConfig = {
|
|
191
234
|
type: "webhook";
|
|
192
235
|
provider: "generic";
|
|
236
|
+
direction: "inbound";
|
|
193
237
|
name: string;
|
|
194
238
|
secrets: string[];
|
|
195
239
|
limits?: ChannelLimits;
|
|
196
240
|
buffer?: ChannelBufferConfig;
|
|
241
|
+
replyTo?: ChannelReplyRouteConfig;
|
|
242
|
+
urlSecret?: never;
|
|
243
|
+
auth?: never;
|
|
244
|
+
audio?: never;
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
export type GenericWebhookOutputChannelInput = {
|
|
248
|
+
name: string;
|
|
249
|
+
urlSecret: string;
|
|
250
|
+
auth?: GenericWebhookOutputAuthInput;
|
|
251
|
+
secrets?: string[];
|
|
252
|
+
limits?: ChannelLimits;
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
export type GenericWebhookOutputChannelConfig = {
|
|
256
|
+
type: "webhook";
|
|
257
|
+
provider: "generic";
|
|
258
|
+
direction: "outbound";
|
|
259
|
+
name: string;
|
|
260
|
+
urlSecret: string;
|
|
261
|
+
auth: GenericWebhookOutputAuthConfig;
|
|
262
|
+
secrets: string[];
|
|
263
|
+
limits?: ChannelLimits;
|
|
264
|
+
buffer?: never;
|
|
265
|
+
replyTo?: never;
|
|
197
266
|
audio?: never;
|
|
198
267
|
};
|
|
199
268
|
|
|
@@ -203,7 +272,8 @@ export type AgentChannel =
|
|
|
203
272
|
| WhatsappChannelConfig
|
|
204
273
|
| DiscordChannelConfig
|
|
205
274
|
| SlackChannelConfig
|
|
206
|
-
| GenericWebhookChannelConfig
|
|
275
|
+
| GenericWebhookChannelConfig
|
|
276
|
+
| GenericWebhookOutputChannelConfig;
|
|
207
277
|
|
|
208
278
|
export type IntegrationTagsConfig = {
|
|
209
279
|
enable?: string[];
|
|
@@ -454,6 +524,7 @@ export function websiteChannel(input: WebsiteChannelInput): WebsiteChannelConfig
|
|
|
454
524
|
...(input.limits ? { limits: input.limits } : {}),
|
|
455
525
|
...(input.buffer ? { buffer: normalizeChannelBuffer(input.buffer, "website channel") } : {}),
|
|
456
526
|
...(input.audio ? { audio: normalizeChannelAudio(input.audio, "website channel") } : {}),
|
|
527
|
+
...(input.replyTo ? { replyTo: normalizeChannelReplyRoute(input.replyTo, "website channel") } : {}),
|
|
457
528
|
};
|
|
458
529
|
}
|
|
459
530
|
|
|
@@ -470,6 +541,7 @@ export function telegramChannel(input: TelegramChannelInput): TelegramChannelCon
|
|
|
470
541
|
...(input.limits ? { limits: input.limits } : {}),
|
|
471
542
|
...(input.buffer ? { buffer: normalizeChannelBuffer(input.buffer, "telegram channel") } : {}),
|
|
472
543
|
...(input.audio ? { audio: normalizeChannelAudio(input.audio, "telegram channel") } : {}),
|
|
544
|
+
...(input.replyTo ? { replyTo: normalizeChannelReplyRoute(input.replyTo, "telegram channel") } : {}),
|
|
473
545
|
};
|
|
474
546
|
}
|
|
475
547
|
|
|
@@ -485,6 +557,7 @@ export function whatsappChannel(input: WhatsappChannelInput): WhatsappChannelCon
|
|
|
485
557
|
...(input.limits ? { limits: input.limits } : {}),
|
|
486
558
|
...(input.buffer ? { buffer: normalizeChannelBuffer(input.buffer, `${input.provider} whatsapp channel`) } : {}),
|
|
487
559
|
...(input.audio ? { audio: normalizeChannelAudio(input.audio, `${input.provider} whatsapp channel`) } : {}),
|
|
560
|
+
...(input.replyTo ? { replyTo: normalizeChannelReplyRoute(input.replyTo, `${input.provider} whatsapp channel`) } : {}),
|
|
488
561
|
};
|
|
489
562
|
}
|
|
490
563
|
|
|
@@ -499,6 +572,7 @@ export function discordChannel(input: DiscordChannelInput): DiscordChannelConfig
|
|
|
499
572
|
secrets: normalizeChannelSecrets(input.secrets ?? defaultDiscordChannelSecrets(mode), "discord channel"),
|
|
500
573
|
...(input.limits ? { limits: input.limits } : {}),
|
|
501
574
|
...(input.buffer ? { buffer: normalizeChannelBuffer(input.buffer, "discord channel") } : {}),
|
|
575
|
+
...(input.replyTo ? { replyTo: normalizeChannelReplyRoute(input.replyTo, "discord channel") } : {}),
|
|
502
576
|
};
|
|
503
577
|
}
|
|
504
578
|
|
|
@@ -510,6 +584,7 @@ export function slackChannel(input: SlackChannelInput): SlackChannelConfig {
|
|
|
510
584
|
secrets: normalizeChannelSecrets(input.secrets ?? ["SLACK_BOT_TOKEN", "SLACK_SIGNING_SECRET"], "slack channel"),
|
|
511
585
|
...(input.limits ? { limits: input.limits } : {}),
|
|
512
586
|
...(input.buffer ? { buffer: normalizeChannelBuffer(input.buffer, "slack channel") } : {}),
|
|
587
|
+
...(input.replyTo ? { replyTo: normalizeChannelReplyRoute(input.replyTo, "slack channel") } : {}),
|
|
513
588
|
};
|
|
514
589
|
}
|
|
515
590
|
|
|
@@ -517,10 +592,32 @@ export function webhookChannel(input: GenericWebhookChannelInput): GenericWebhoo
|
|
|
517
592
|
return {
|
|
518
593
|
type: "webhook",
|
|
519
594
|
provider: "generic",
|
|
595
|
+
direction: "inbound",
|
|
520
596
|
name: input.name,
|
|
521
597
|
secrets: normalizeChannelSecrets(input.secrets ?? ["AGENTKIT_WEBHOOK_SECRET"], "generic webhook channel"),
|
|
522
598
|
...(input.limits ? { limits: input.limits } : {}),
|
|
523
599
|
...(input.buffer ? { buffer: normalizeChannelBuffer(input.buffer, "generic webhook channel") } : {}),
|
|
600
|
+
...(input.replyTo ? { replyTo: normalizeChannelReplyRoute(input.replyTo, "generic webhook channel") } : {}),
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
export function webhookOutputChannel(input: GenericWebhookOutputChannelInput): GenericWebhookOutputChannelConfig {
|
|
605
|
+
const auth = normalizeGenericWebhookOutputAuth(input.auth ?? { type: "none" }, "generic webhook output channel");
|
|
606
|
+
const requiredSecrets = [
|
|
607
|
+
input.urlSecret,
|
|
608
|
+
...genericWebhookOutputAuthSecrets(auth),
|
|
609
|
+
...(input.secrets ?? []),
|
|
610
|
+
];
|
|
611
|
+
|
|
612
|
+
return {
|
|
613
|
+
type: "webhook",
|
|
614
|
+
provider: "generic",
|
|
615
|
+
direction: "outbound",
|
|
616
|
+
name: input.name,
|
|
617
|
+
urlSecret: normalizeSecretName(input.urlSecret, "generic webhook output channel urlSecret"),
|
|
618
|
+
auth,
|
|
619
|
+
secrets: normalizeChannelSecrets(requiredSecrets, "generic webhook output channel"),
|
|
620
|
+
...(input.limits ? { limits: input.limits } : {}),
|
|
524
621
|
};
|
|
525
622
|
}
|
|
526
623
|
|
|
@@ -548,14 +645,22 @@ function normalizeChannelSecrets(secrets: string[], context: string): string[] {
|
|
|
548
645
|
const uniqueSecrets = new Set<string>();
|
|
549
646
|
|
|
550
647
|
for (const secret of secrets) {
|
|
551
|
-
|
|
552
|
-
|
|
648
|
+
uniqueSecrets.add(normalizeSecretName(secret, `${context} secrets`));
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
return Array.from(uniqueSecrets).sort();
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
function normalizeSecretName(secret: string, context: string): string {
|
|
655
|
+
if (!/^[A-Z_][A-Z0-9_]*$/.test(secret)) {
|
|
656
|
+
if (context.endsWith("secrets")) {
|
|
657
|
+
throw new Error(`${context} must be environment variable names, not secret values.`);
|
|
553
658
|
}
|
|
554
659
|
|
|
555
|
-
|
|
660
|
+
throw new Error(`${context} must be an environment variable name, not a secret value.`);
|
|
556
661
|
}
|
|
557
662
|
|
|
558
|
-
return
|
|
663
|
+
return secret;
|
|
559
664
|
}
|
|
560
665
|
|
|
561
666
|
function normalizeIntegrationName(value: string, context: string): string {
|
|
@@ -717,6 +822,87 @@ function normalizeChannelAudio(input: ChannelAudioInput, context: string): Chann
|
|
|
717
822
|
};
|
|
718
823
|
}
|
|
719
824
|
|
|
825
|
+
function normalizeChannelReplyRoute(input: ChannelReplyRouteInput, context: string): ChannelReplyRouteConfig {
|
|
826
|
+
const channel = input.channel.trim();
|
|
827
|
+
|
|
828
|
+
if (!/^[a-z][a-z0-9-]*$/.test(channel)) {
|
|
829
|
+
throw new Error(`${context} replyTo.channel must start with a lowercase letter and contain only lowercase letters, numbers, and hyphens.`);
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
return {
|
|
833
|
+
channel,
|
|
834
|
+
...(input.recipientFrom ? { recipientFrom: normalizeJsonPath(input.recipientFrom, `${context} replyTo.recipientFrom`) } : {}),
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
function normalizeJsonPath(value: string, context: string): string {
|
|
839
|
+
const path = value.trim().replace(/^payload\./, "");
|
|
840
|
+
|
|
841
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)*$/.test(path)) {
|
|
842
|
+
throw new Error(`${context} must be a dotted JSON field path such as "phone" or "customer.phone".`);
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
return path;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
function normalizeGenericWebhookOutputAuth(
|
|
849
|
+
auth: GenericWebhookOutputAuthInput,
|
|
850
|
+
context: string,
|
|
851
|
+
): GenericWebhookOutputAuthConfig {
|
|
852
|
+
if (auth.type === "none") {
|
|
853
|
+
return { type: "none" };
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
if (auth.type === "bearer") {
|
|
857
|
+
return {
|
|
858
|
+
type: "bearer",
|
|
859
|
+
tokenSecret: normalizeSecretName(auth.tokenSecret, `${context} auth.tokenSecret`),
|
|
860
|
+
};
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
if (auth.type === "header") {
|
|
864
|
+
if (!/^[A-Za-z][A-Za-z0-9-]*$/.test(auth.headerName)) {
|
|
865
|
+
throw new Error(`${context} auth.headerName must be an HTTP header name.`);
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
return {
|
|
869
|
+
type: "header",
|
|
870
|
+
headerName: auth.headerName,
|
|
871
|
+
valueSecret: normalizeSecretName(auth.valueSecret, `${context} auth.valueSecret`),
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
if (auth.type === "hmac") {
|
|
876
|
+
if (auth.headerName !== undefined && !/^[A-Za-z][A-Za-z0-9-]*$/.test(auth.headerName)) {
|
|
877
|
+
throw new Error(`${context} auth.headerName must be an HTTP header name.`);
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
return {
|
|
881
|
+
type: "hmac",
|
|
882
|
+
secret: normalizeSecretName(auth.secret, `${context} auth.secret`),
|
|
883
|
+
...(auth.headerName ? { headerName: auth.headerName } : {}),
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
throw new Error(`${context} auth.type must be "none", "bearer", "header", or "hmac".`);
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
function genericWebhookOutputAuthSecrets(auth: GenericWebhookOutputAuthConfig): string[] {
|
|
891
|
+
if (auth.type === "bearer") {
|
|
892
|
+
return [auth.tokenSecret];
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
if (auth.type === "header") {
|
|
896
|
+
return [auth.valueSecret];
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
if (auth.type === "hmac") {
|
|
900
|
+
return [auth.secret];
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
return [];
|
|
904
|
+
}
|
|
905
|
+
|
|
720
906
|
function validatePositiveInteger(value: number, field: string): void {
|
|
721
907
|
if (!Number.isInteger(value) || value <= 0) {
|
|
722
908
|
throw new Error(`${field} must be a positive integer.`);
|