@andreprado/agentkit 0.1.0-alpha.8 → 0.1.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/README.md +18 -1
- package/docs/guides/add-channel.md +251 -7
- package/docs/guides/add-knowledge.md +10 -0
- package/docs/guides/add-managed-composio.md +165 -0
- package/docs/guides/add-tool.md +10 -3
- package/docs/guides/channel-security.md +162 -32
- package/docs/guides/connect-discord.md +178 -0
- package/docs/guides/connect-slack.md +126 -0
- package/docs/guides/connect-telegram.md +61 -1
- package/docs/guides/connect-whatsapp-evolution.md +121 -0
- package/docs/guides/connect-whatsapp-uazapi.md +139 -0
- package/docs/guides/connect-whatsapp-zapster.md +119 -16
- package/docs/guides/create-agent.md +31 -4
- package/docs/guides/debug-channel.md +159 -0
- package/docs/guides/improve-from-production.md +151 -0
- package/docs/guides/prepare-deploy.md +32 -14
- package/docs/guides/replay-production-traces.md +72 -0
- package/docs/guides/run-evals.md +95 -25
- package/docs/guides/security-rules.md +9 -5
- package/docs/guides/send-feedback.md +135 -0
- package/docs/guides/use-jev.md +67 -0
- package/docs/guides/use-provider.md +70 -3
- package/docs/llms-full.txt +295 -25
- package/docs/llms.txt +54 -7
- package/package.json +3 -7
- package/src/cli/args.ts +23 -2
- package/src/cli/cloud-client.ts +121 -9
- package/src/cli/commands/channels.ts +1185 -50
- package/src/cli/commands/feedback.ts +438 -0
- package/src/cli/commands/provider.ts +47 -0
- package/src/cli/commands/transcribe.ts +171 -0
- package/src/cli/deploy-chat-ui.ts +232 -18
- package/src/cli/deploy-readiness.ts +227 -14
- package/src/cli/help.ts +68 -8
- package/src/cli/index.ts +740 -35
- package/src/cli/new-command.ts +41 -0
- package/src/cloud/client.ts +4 -3
- package/src/cloud/contracts.ts +1 -1
- package/src/create-project.ts +18 -35
- package/src/index.ts +565 -11
- package/src/providers/codex-auth.ts +111 -0
- package/src/providers/pi.ts +88 -19
- package/src/providers/test.ts +36 -0
- package/src/providers/types.ts +8 -0
- package/src/runtime/channel-test-harness.ts +21 -1
- package/src/runtime/channels/discord.ts +896 -0
- package/src/runtime/channels/generic-webhook.ts +974 -0
- package/src/runtime/channels/slack.ts +646 -0
- package/src/runtime/channels/telegram.ts +466 -23
- package/src/runtime/channels/whatsapp-evolution.ts +1357 -0
- package/src/runtime/channels/whatsapp-meta.ts +9 -0
- package/src/runtime/channels/whatsapp-uazapi.ts +1323 -0
- package/src/runtime/channels/whatsapp-zapster.ts +674 -40
- package/src/runtime/channels.ts +89 -4
- package/src/runtime/chat.ts +70 -44
- package/src/runtime/config.ts +489 -20
- package/src/runtime/core/manifest.ts +75 -5
- package/src/runtime/core/targets.ts +5 -5
- package/src/runtime/deploy-readiness.ts +34 -4
- package/src/runtime/dev-server.ts +639 -39
- package/src/runtime/env.ts +8 -3
- package/src/runtime/evals.ts +445 -74
- package/src/runtime/improve.ts +868 -0
- package/src/runtime/inspect.ts +173 -4
- package/src/runtime/integrations/composio.ts +425 -0
- package/src/runtime/knowledge/retrieve.ts +25 -5
- package/src/runtime/knowledge/schema.ts +45 -1
- package/src/runtime/prompt-context.ts +141 -0
- package/src/runtime/runtime-contract.ts +71 -7
- package/src/runtime/skills.ts +95 -0
- package/src/runtime/targets/cloudflare/build.ts +1010 -208
- package/src/runtime/targets/container/server.ts +1 -1
- package/src/runtime/targets/vps/deploy.ts +26 -9
- package/src/runtime/tool-runner.ts +9 -1
- package/src/runtime/tools.ts +26 -2
- package/src/runtime/transcription.ts +483 -0
- package/src/storage/sqlite.ts +7 -2
- package/src/templates/blank.ts +37 -9
- package/src/templates/dentista.ts +40 -14
- package/src/templates/skills/agentkit-build-agent/SKILL.md +34 -5
- package/src/templates/skills/agentkit-build-agent/templates/appointment-intake.instructions.md +2 -1
- package/src/templates/skills/agentkit-capsule/SKILL.md +32 -3
- package/src/templates/skills/agentkit-capsule/references/docs-router.md +2 -2
- package/src/templates/skills/agentkit-channels/SKILL.md +71 -6
- package/src/templates/skills/agentkit-channels/references/channel-buffering.md +9 -2
- package/src/templates/skills/agentkit-channels/references/channel-debugging.md +34 -5
- package/src/templates/skills/agentkit-channels/references/discord.md +93 -0
- package/src/templates/skills/agentkit-channels/references/slack.md +56 -0
- package/src/templates/skills/agentkit-channels/references/telegram.md +39 -4
- package/src/templates/skills/agentkit-channels/references/whatsapp-evolution.md +57 -0
- package/src/templates/skills/agentkit-channels/references/whatsapp-uazapi.md +54 -0
- package/src/templates/skills/agentkit-channels/references/whatsapp-zapster.md +42 -8
- package/src/templates/skills/agentkit-database/SKILL.md +11 -0
- package/src/templates/skills/agentkit-deploy/SKILL.md +9 -1
- package/src/templates/skills/agentkit-evals/SKILL.md +77 -13
- package/src/templates/skills/agentkit-evals/templates/multi-turn.eval.md +13 -6
- package/src/templates/skills/agentkit-evals/templates/no-leak.eval.md +8 -4
- package/src/templates/skills/agentkit-evals/templates/smoke.eval.md +8 -4
- package/src/templates/skills/agentkit-evals/templates/tool-call.eval.md +16 -7
- package/src/templates/skills/agentkit-improve/SKILL.md +96 -0
- package/src/templates/skills/agentkit-improve/references/replay-side-effects.md +18 -0
- package/src/templates/skills/agentkit-improve/references/trace-packets.md +22 -0
- package/src/templates/skills/agentkit-improve/templates/regression.eval.md +18 -0
- package/src/templates/skills/agentkit-integrations/SKILL.md +98 -0
- package/src/templates/skills/agentkit-knowledge/SKILL.md +4 -1
- package/src/templates/skills/agentkit-prompts/SKILL.md +3 -1
- package/src/templates/skills/agentkit-provider/SKILL.md +29 -4
- package/src/templates/skills/agentkit-security/SKILL.md +5 -2
- package/src/templates/skills/agentkit-tools/SKILL.md +8 -1
- package/src/templates/skills/agentkit-tools/examples/eval-safe-external-action.tool.md +8 -8
- package/src/templates/skills/agentkit-tools/examples/jev-service-fit.tool.md +110 -0
- package/src/templates/skills/agentkit-troubleshooting/SKILL.md +25 -1
- package/src/templates/support.ts +42 -12
- package/docs/guides/agentkit-skills-architecture.md +0 -471
- package/docs/guides/channels-implementation-map.md +0 -243
- package/docs/guides/channels-production-handoff.md +0 -101
- package/docs/portable-deploy-release-checklist.md +0 -41
|
@@ -1,11 +1,13 @@
|
|
|
1
|
+
import { createHmac } from "node:crypto";
|
|
1
2
|
import { readFile } from "node:fs/promises";
|
|
2
3
|
import { resolve } from "node:path";
|
|
3
4
|
|
|
4
5
|
import type { ParsedArgs } from "../args";
|
|
5
|
-
import { cloudFetch, cloudGet, cloudPost } from "../cloud-client";
|
|
6
|
+
import { cloudFetch, cloudGet, cloudPost, readCloudError } from "../cloud-client";
|
|
6
7
|
import { parseCloudApiUrl } from "../flags";
|
|
7
8
|
import { loadAgentCapsule } from "../../runtime/config";
|
|
8
9
|
import { readLocalDeployStateIfExists, type LocalDeployState } from "../../runtime/core/deploy-state";
|
|
10
|
+
import { loadCapsuleEnv } from "../../runtime/env";
|
|
9
11
|
import type { AgentChannel } from "../../index";
|
|
10
12
|
|
|
11
13
|
type DeployState = LocalDeployState;
|
|
@@ -13,12 +15,18 @@ type DeployState = LocalDeployState;
|
|
|
13
15
|
type CloudChannel = {
|
|
14
16
|
id: string;
|
|
15
17
|
name: string;
|
|
16
|
-
type: "website" | "telegram" | "whatsapp";
|
|
17
|
-
provider: "agentkit" | "telegram" | "zapster" | "meta";
|
|
18
|
+
type: "website" | "telegram" | "whatsapp" | "discord" | "slack" | "webhook";
|
|
19
|
+
provider: "agentkit" | "telegram" | "zapster" | "meta" | "uazapi" | "evolution" | "discord" | "slack" | "generic";
|
|
20
|
+
mode?: "interactions" | "bot";
|
|
21
|
+
direction?: "inbound" | "outbound";
|
|
18
22
|
status: string;
|
|
19
23
|
webhook_url: string;
|
|
20
24
|
required_secrets: Array<{ name: string; status: string }>;
|
|
21
25
|
buffer?: AgentChannel["buffer"];
|
|
26
|
+
audio?: AgentChannel["audio"];
|
|
27
|
+
reply_to?: Extract<AgentChannel, { replyTo?: unknown }>["replyTo"];
|
|
28
|
+
url_secret?: string;
|
|
29
|
+
auth?: Extract<AgentChannel, { type: "webhook"; direction: "outbound" }>["auth"];
|
|
22
30
|
};
|
|
23
31
|
|
|
24
32
|
type CloudDelivery = {
|
|
@@ -30,10 +38,84 @@ type CloudDelivery = {
|
|
|
30
38
|
event_id?: string;
|
|
31
39
|
};
|
|
32
40
|
received_at: string;
|
|
41
|
+
updated_at?: string;
|
|
42
|
+
error?: {
|
|
43
|
+
code: string;
|
|
44
|
+
message: string;
|
|
45
|
+
};
|
|
46
|
+
events?: Array<{
|
|
47
|
+
state: string;
|
|
48
|
+
message: string;
|
|
49
|
+
created_at: string;
|
|
50
|
+
}>;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
type CloudChannelBuffer = {
|
|
54
|
+
channel_id: string;
|
|
55
|
+
deploy_id: string;
|
|
56
|
+
conversation_id: string;
|
|
57
|
+
job_id?: string;
|
|
58
|
+
delivery_id?: string;
|
|
59
|
+
buffered_delivery_ids: string[];
|
|
60
|
+
buffered_message_count: number;
|
|
61
|
+
buffered_char_count: number;
|
|
62
|
+
status: string;
|
|
63
|
+
first_received_at: string;
|
|
64
|
+
last_received_at: string;
|
|
65
|
+
available_at?: string;
|
|
66
|
+
age_ms: number;
|
|
67
|
+
stale: boolean;
|
|
68
|
+
message_preview: string;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
type ChannelBufferActionResponse = {
|
|
72
|
+
buffer: CloudChannelBuffer | null;
|
|
73
|
+
job: {
|
|
74
|
+
id: string;
|
|
75
|
+
status: string;
|
|
76
|
+
conversation_id: string;
|
|
77
|
+
} | null;
|
|
78
|
+
cleared?: boolean;
|
|
79
|
+
retried?: boolean;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
type ChannelSetupResponse = {
|
|
83
|
+
channel: CloudChannel;
|
|
84
|
+
setup?: {
|
|
85
|
+
provider: "telegram" | "uazapi" | "evolution";
|
|
86
|
+
webhook_url: string;
|
|
87
|
+
status: "configured";
|
|
88
|
+
bot_username?: string;
|
|
89
|
+
webhook_count?: number;
|
|
90
|
+
matched_webhook_id?: string;
|
|
91
|
+
get_webhook_info?: {
|
|
92
|
+
url: string;
|
|
93
|
+
pending_update_count: number;
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
type ChannelDoctorReport = {
|
|
99
|
+
channel: CloudChannel;
|
|
100
|
+
checks: Array<{
|
|
101
|
+
name: string;
|
|
102
|
+
status: "ok" | "warn" | "fail" | "unknown";
|
|
103
|
+
message: string;
|
|
104
|
+
fix?: string;
|
|
105
|
+
}>;
|
|
106
|
+
buffers: CloudChannelBuffer[];
|
|
107
|
+
last_delivery: {
|
|
108
|
+
inbound: CloudDelivery | null;
|
|
109
|
+
outbound: CloudDelivery | null;
|
|
110
|
+
};
|
|
111
|
+
probable_error: string | null;
|
|
112
|
+
suggested_fix: string | null;
|
|
33
113
|
};
|
|
34
114
|
|
|
35
115
|
export async function handleChannelsCommand(args: ParsedArgs): Promise<void> {
|
|
36
|
-
|
|
116
|
+
await loadLocalEnvIntoProcess(process.cwd());
|
|
117
|
+
|
|
118
|
+
const [subcommand, first, second, third] = args.positional;
|
|
37
119
|
|
|
38
120
|
if (subcommand === "list") {
|
|
39
121
|
const deployState = await readDeployStateIfExists(process.cwd());
|
|
@@ -54,6 +136,7 @@ export async function handleChannelsCommand(args: ParsedArgs): Promise<void> {
|
|
|
54
136
|
name: channel.name,
|
|
55
137
|
type: channel.type,
|
|
56
138
|
provider: channel.provider,
|
|
139
|
+
...(channel.type === "discord" ? { mode: channel.mode } : {}),
|
|
57
140
|
status: "local_config",
|
|
58
141
|
webhook_url: "",
|
|
59
142
|
required_secrets: channel.secrets.map((name) => ({ name, status: "missing" })),
|
|
@@ -66,27 +149,138 @@ export async function handleChannelsCommand(args: ParsedArgs): Promise<void> {
|
|
|
66
149
|
const type = first;
|
|
67
150
|
const name = second;
|
|
68
151
|
|
|
69
|
-
if (!type || !name || (type
|
|
70
|
-
throw new Error("Usage: agentkit channels add <website|telegram|whatsapp> <name> [--provider zapster|meta]");
|
|
152
|
+
if (!type || !name || !isCliChannelType(type)) {
|
|
153
|
+
throw new Error("Usage: agentkit channels add <website|telegram|whatsapp|discord|slack|webhook> <name> [--provider zapster|meta|uazapi|evolution] [--mode interactions|bot]");
|
|
71
154
|
}
|
|
72
155
|
|
|
73
|
-
const
|
|
74
|
-
const provider = channelProviderForCli(type, args.flags.provider);
|
|
75
|
-
const localChannel = await readLocalChannelConfig(process.cwd(), type, provider, name);
|
|
76
|
-
const payload = {
|
|
77
|
-
name,
|
|
78
|
-
type,
|
|
79
|
-
provider,
|
|
80
|
-
secrets: localChannel?.secrets ?? defaultChannelSecretsForCli(type, provider),
|
|
81
|
-
...(localChannel?.limits ? { limits: channelLimitsForApi(localChannel.limits) } : {}),
|
|
82
|
-
...(localChannel?.buffer ? { buffer: localChannel.buffer } : {}),
|
|
83
|
-
};
|
|
84
|
-
const response = await cloudPost<{ channel: CloudChannel }>(
|
|
85
|
-
parseCloudApiUrl(args.flags.api),
|
|
86
|
-
`/v1/deploys/${encodeURIComponent(deployState.deploy_id)}/channels`,
|
|
87
|
-
payload,
|
|
88
|
-
);
|
|
156
|
+
const response = await createHostedChannel(type, name, args.flags.provider, args.flags.mode, args.flags.api);
|
|
89
157
|
printChannelStatus(response.channel);
|
|
158
|
+
printSecretRepairHint(response.channel);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (subcommand === "connect") {
|
|
163
|
+
const type = first;
|
|
164
|
+
const name = second;
|
|
165
|
+
|
|
166
|
+
if (!type || !name || !isCliChannelType(type)) {
|
|
167
|
+
throw new Error("Usage: agentkit channels connect <website|telegram|whatsapp|discord|slack|webhook> <name> [--provider zapster|meta|uazapi|evolution] [--mode interactions|bot]");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const created = await createHostedChannel(type, name, args.flags.provider, args.flags.mode, args.flags.api);
|
|
171
|
+
let channel = created.channel;
|
|
172
|
+
printChannelStatus(channel);
|
|
173
|
+
printSecretRepairHint(channel);
|
|
174
|
+
console.log("Channel resource: created_or_updated");
|
|
175
|
+
|
|
176
|
+
if (hasMissingRequiredSecrets(channel)) {
|
|
177
|
+
console.log("Secrets: missing");
|
|
178
|
+
console.log("");
|
|
179
|
+
console.log("Connect blocked: set the missing managed secrets, then rerun this same command.");
|
|
180
|
+
console.log("Run: npm run agentkit -- secret sync --from-local");
|
|
181
|
+
process.exitCode = 1;
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
console.log("Secrets: validated");
|
|
186
|
+
|
|
187
|
+
if (channel.type === "telegram") {
|
|
188
|
+
const setup = await applyTelegramWebhook(channel, args.flags.api);
|
|
189
|
+
channel = setup.channel ?? channel;
|
|
190
|
+
console.log("Telegram setWebhook: OK");
|
|
191
|
+
console.log("Telegram webhook confirmed.");
|
|
192
|
+
if (setup.setup?.get_webhook_info) {
|
|
193
|
+
console.log(`Telegram pending updates: ${setup.setup.get_webhook_info.pending_update_count}`);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const smoke = await runConnectSmoke(() => runChannelSmoke(channel, args.flags.api, args.flags.message));
|
|
197
|
+
printTelegramHumanNextStep(channel.name, setup.setup?.bot_username);
|
|
198
|
+
if (!smoke) {
|
|
199
|
+
process.exitCode = 1;
|
|
200
|
+
}
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (channel.type === "whatsapp" && channel.provider === "uazapi") {
|
|
205
|
+
const setup = await applyProviderWebhook(channel, args.flags.api);
|
|
206
|
+
channel = setup.channel ?? channel;
|
|
207
|
+
console.log("UAZAPI webhook setup: OK");
|
|
208
|
+
console.log("UAZAPI webhook confirmed.");
|
|
209
|
+
if (setup.setup?.webhook_count !== undefined) {
|
|
210
|
+
console.log(`UAZAPI webhooks: ${setup.setup.webhook_count}`);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const smoke = await runConnectSmoke(() => runChannelSmoke(channel, args.flags.api, args.flags.message));
|
|
214
|
+
printUazapiHumanNextStep(channel);
|
|
215
|
+
if (!smoke) {
|
|
216
|
+
process.exitCode = 1;
|
|
217
|
+
}
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (channel.type === "whatsapp" && channel.provider === "evolution") {
|
|
222
|
+
const setup = await applyProviderWebhook(channel, args.flags.api);
|
|
223
|
+
channel = setup.channel ?? channel;
|
|
224
|
+
console.log("Evolution webhook setup: OK");
|
|
225
|
+
console.log("Evolution webhook confirmed.");
|
|
226
|
+
|
|
227
|
+
const smoke = await runConnectSmoke(() => runChannelSmoke(channel, args.flags.api, args.flags.message));
|
|
228
|
+
printEvolutionHumanNextStep(channel);
|
|
229
|
+
if (!smoke) {
|
|
230
|
+
process.exitCode = 1;
|
|
231
|
+
}
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (channel.type === "discord") {
|
|
236
|
+
const smoke = await runConnectSmoke(() => runChannelSmoke(channel, args.flags.api, args.flags.message));
|
|
237
|
+
printDiscordHumanNextStep(channel);
|
|
238
|
+
if (!smoke) {
|
|
239
|
+
process.exitCode = 1;
|
|
240
|
+
}
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (channel.type === "slack") {
|
|
245
|
+
const smoke = await runConnectSmoke(() => runChannelSmoke(channel, args.flags.api, args.flags.message));
|
|
246
|
+
printSlackHumanNextStep(channel);
|
|
247
|
+
if (!smoke) {
|
|
248
|
+
process.exitCode = 1;
|
|
249
|
+
}
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (channel.type === "webhook") {
|
|
254
|
+
if (channel.direction === "outbound") {
|
|
255
|
+
console.log("Outbound webhook: configured");
|
|
256
|
+
console.log(`Next human step: make sure ${channel.url_secret ?? "the webhook URL secret"} is set as a managed secret.`);
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const smoke = await runConnectSmoke(() => runChannelSmoke(channel, args.flags.api, args.flags.message));
|
|
261
|
+
printGenericWebhookHumanNextStep(channel);
|
|
262
|
+
if (!smoke) {
|
|
263
|
+
process.exitCode = 1;
|
|
264
|
+
}
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (channel.type === "whatsapp" && channel.provider === "zapster") {
|
|
269
|
+
const smoke = await runConnectSmoke(() => runChannelSmoke(channel, args.flags.api, args.flags.message));
|
|
270
|
+
console.log(`Next human step: paste ${channel.webhook_url} into ${channel.provider}'s webhook settings.`);
|
|
271
|
+
if (!smoke) {
|
|
272
|
+
process.exitCode = 1;
|
|
273
|
+
}
|
|
274
|
+
} else if (channel.type === "whatsapp") {
|
|
275
|
+
console.log("Smoke: skipped until the provider webhook is configured.");
|
|
276
|
+
console.log(`Next human step: paste ${channel.webhook_url} into ${channel.provider}'s webhook settings.`);
|
|
277
|
+
} else {
|
|
278
|
+
const smoke = await runConnectSmoke(() => runChannelSmoke(channel, args.flags.api, args.flags.message));
|
|
279
|
+
console.log(`Next human step: use ${channel.webhook_url} from your website client.`);
|
|
280
|
+
if (!smoke) {
|
|
281
|
+
process.exitCode = 1;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
90
284
|
return;
|
|
91
285
|
}
|
|
92
286
|
|
|
@@ -99,14 +293,46 @@ export async function handleChannelsCommand(args: ParsedArgs): Promise<void> {
|
|
|
99
293
|
printChannelStatus(channel);
|
|
100
294
|
if (channel.type === "telegram") {
|
|
101
295
|
if (args.flags.apply) {
|
|
102
|
-
await applyTelegramWebhook(channel, args.flags.api);
|
|
103
|
-
console.log("Telegram webhook
|
|
296
|
+
const setup = await applyTelegramWebhook(channel, args.flags.api);
|
|
297
|
+
console.log("Telegram webhook confirmed.");
|
|
298
|
+
printChannelStatus(setup.channel ?? channel);
|
|
299
|
+
printTelegramHumanNextStep(channel.name, setup.setup?.bot_username);
|
|
104
300
|
} else {
|
|
105
301
|
console.log(`Setup: call Telegram setWebhook with ${channel.webhook_url}`);
|
|
106
302
|
console.log("Use --apply to mutate Telegram webhook settings.");
|
|
107
303
|
}
|
|
304
|
+
} else if (channel.type === "whatsapp" && channel.provider === "uazapi") {
|
|
305
|
+
if (args.flags.apply) {
|
|
306
|
+
const setup = await applyProviderWebhook(channel, args.flags.api);
|
|
307
|
+
console.log("UAZAPI webhook confirmed.");
|
|
308
|
+
printChannelStatus(setup.channel ?? channel);
|
|
309
|
+
printUazapiHumanNextStep(setup.channel ?? channel);
|
|
310
|
+
} else {
|
|
311
|
+
console.log(`Setup: configure UAZAPI /webhook with ${channel.webhook_url}`);
|
|
312
|
+
console.log("Use --apply to mutate UAZAPI webhook settings.");
|
|
313
|
+
}
|
|
314
|
+
} else if (channel.type === "whatsapp" && channel.provider === "evolution") {
|
|
315
|
+
if (args.flags.apply) {
|
|
316
|
+
const setup = await applyProviderWebhook(channel, args.flags.api);
|
|
317
|
+
console.log("Evolution webhook confirmed.");
|
|
318
|
+
printChannelStatus(setup.channel ?? channel);
|
|
319
|
+
printEvolutionHumanNextStep(setup.channel ?? channel);
|
|
320
|
+
} else {
|
|
321
|
+
console.log(`Setup: configure Evolution /webhook/set/{instance} with ${channel.webhook_url}`);
|
|
322
|
+
console.log("Use --apply to mutate Evolution webhook settings.");
|
|
323
|
+
}
|
|
108
324
|
} else if (channel.type === "whatsapp") {
|
|
109
325
|
console.log(`Next step: paste this URL into ${channel.provider}'s webhook settings.`);
|
|
326
|
+
} else if (channel.type === "discord") {
|
|
327
|
+
printDiscordHumanNextStep(channel);
|
|
328
|
+
} else if (channel.type === "slack") {
|
|
329
|
+
printSlackHumanNextStep(channel);
|
|
330
|
+
} else if (channel.type === "webhook") {
|
|
331
|
+
if (channel.direction === "outbound") {
|
|
332
|
+
console.log("Outbound webhook endpoint: configured from managed secrets.");
|
|
333
|
+
} else {
|
|
334
|
+
printGenericWebhookHumanNextStep(channel);
|
|
335
|
+
}
|
|
110
336
|
} else {
|
|
111
337
|
console.log(`Website endpoint: ${channel.webhook_url}`);
|
|
112
338
|
console.log("Use this endpoint from the AgentKit website client or a signed server-to-server test.");
|
|
@@ -125,22 +351,58 @@ export async function handleChannelsCommand(args: ParsedArgs): Promise<void> {
|
|
|
125
351
|
return;
|
|
126
352
|
}
|
|
127
353
|
|
|
354
|
+
if (subcommand === "doctor") {
|
|
355
|
+
if (!first) {
|
|
356
|
+
throw new Error("Usage: agentkit channels doctor <name>");
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const channel = await resolveCloudChannelByName(first, args.flags.api);
|
|
360
|
+
const report = await cloudGet<{ doctor: ChannelDoctorReport }>(
|
|
361
|
+
parseCloudApiUrl(args.flags.api),
|
|
362
|
+
`/v1/channels/${encodeURIComponent(channel.id)}/doctor`,
|
|
363
|
+
);
|
|
364
|
+
printChannelDoctor(report.doctor);
|
|
365
|
+
|
|
366
|
+
if (report.doctor.checks.some((check) => check.status === "fail")) {
|
|
367
|
+
process.exitCode = 1;
|
|
368
|
+
}
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
|
|
128
372
|
if (subcommand === "test") {
|
|
129
373
|
if (!first) {
|
|
130
374
|
throw new Error('Usage: agentkit channels test <name> [--message "hello"] [--fixture <path>]');
|
|
131
375
|
}
|
|
132
376
|
|
|
133
377
|
const channel = await resolveCloudChannelByName(first, args.flags.api);
|
|
378
|
+
if (channel.type === "webhook" && channel.direction === "outbound") {
|
|
379
|
+
throw new Error("Outbound webhook channels do not have an inbound test endpoint. Test them through a source channel that routes replies to this channel.");
|
|
380
|
+
}
|
|
134
381
|
const apiUrl = parseCloudApiUrl(args.flags.api);
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
const response =
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
382
|
+
const signed = channel.type === "whatsapp" && channel.provider === "evolution" && !args.flags.fixture
|
|
383
|
+
? await runServerGeneratedChannelTest(channel, apiUrl, args.flags.message)
|
|
384
|
+
: await runServerSignedChannelTest(channel, apiUrl, await channelTestBody(channel, args.flags.message, args.flags.fixture));
|
|
385
|
+
const response = signed.response;
|
|
386
|
+
const payload = signed.payload;
|
|
387
|
+
|
|
388
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
389
|
+
if (!response.ok) {
|
|
390
|
+
process.exitCode = 1;
|
|
391
|
+
}
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (subcommand === "test-audio") {
|
|
396
|
+
if (!first) {
|
|
397
|
+
throw new Error("Usage: agentkit channels test-audio <name> [--fixture voice-note|audio-file|<path>]");
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const channel = await resolveCloudChannelByName(first, args.flags.api);
|
|
401
|
+
const apiUrl = parseCloudApiUrl(args.flags.api);
|
|
402
|
+
const body = await channelAudioTestBody(channel, args.flags.fixture);
|
|
403
|
+
const signed = await runServerSignedChannelTest(channel, apiUrl, body);
|
|
404
|
+
const response = signed.response;
|
|
405
|
+
const payload = signed.payload;
|
|
144
406
|
|
|
145
407
|
console.log(JSON.stringify(payload, null, 2));
|
|
146
408
|
if (!response.ok) {
|
|
@@ -196,22 +458,223 @@ export async function handleChannelsCommand(args: ParsedArgs): Promise<void> {
|
|
|
196
458
|
}
|
|
197
459
|
}
|
|
198
460
|
|
|
461
|
+
if (subcommand === "buffers") {
|
|
462
|
+
if (first === "list") {
|
|
463
|
+
if (!second) {
|
|
464
|
+
throw new Error("Usage: agentkit channels buffers list <name>");
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
const channel = await resolveCloudChannelByName(second, args.flags.api);
|
|
468
|
+
const response = await cloudGet<{ buffers: CloudChannelBuffer[] }>(
|
|
469
|
+
parseCloudApiUrl(args.flags.api),
|
|
470
|
+
`/v1/channels/${encodeURIComponent(channel.id)}/buffers`,
|
|
471
|
+
);
|
|
472
|
+
printChannelBuffers(response.buffers);
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
if (first === "show") {
|
|
477
|
+
if (!second) {
|
|
478
|
+
throw new Error("Usage: agentkit channels buffers show [<name>] <conversation-id>");
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
const target = await resolveChannelBufferTarget(second, third, args.flags.api);
|
|
482
|
+
const response = await cloudGet<{ buffer: CloudChannelBuffer }>(
|
|
483
|
+
parseCloudApiUrl(args.flags.api),
|
|
484
|
+
`/v1/channels/${encodeURIComponent(target.channel.id)}/buffers/${encodeURIComponent(target.conversationId)}`,
|
|
485
|
+
);
|
|
486
|
+
console.log(JSON.stringify(response.buffer, null, 2));
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
if (first === "flush" || first === "clear" || first === "retry") {
|
|
491
|
+
if (!second) {
|
|
492
|
+
throw new Error(`Usage: agentkit channels buffers ${first} [<name>] <conversation-id>`);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
const target = await resolveChannelBufferTarget(second, third, args.flags.api);
|
|
496
|
+
const response = await cloudPost<ChannelBufferActionResponse>(
|
|
497
|
+
parseCloudApiUrl(args.flags.api),
|
|
498
|
+
`/v1/channels/${encodeURIComponent(target.channel.id)}/buffers/${encodeURIComponent(target.conversationId)}/${first}`,
|
|
499
|
+
{},
|
|
500
|
+
);
|
|
501
|
+
printChannelBufferAction(first, response);
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
199
506
|
throw new Error(
|
|
200
|
-
"Usage: agentkit channels list | add | setup | status | test | deliveries list | deliveries show",
|
|
507
|
+
"Usage: agentkit channels list | add | connect | setup | status | doctor | test | test-audio | deliveries list | deliveries show | buffers list | buffers show | buffers flush | buffers clear | buffers retry",
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
async function createHostedChannel(
|
|
512
|
+
type: "website" | "telegram" | "whatsapp" | "discord" | "slack" | "webhook",
|
|
513
|
+
name: string,
|
|
514
|
+
providerFlag: string | boolean | undefined,
|
|
515
|
+
modeFlag: string | boolean | undefined,
|
|
516
|
+
apiFlag: string | boolean | undefined,
|
|
517
|
+
): Promise<{ channel: CloudChannel }> {
|
|
518
|
+
const deployState = await readDeployStateRequired(process.cwd());
|
|
519
|
+
const provider = channelProviderForCli(type, providerFlag);
|
|
520
|
+
const localChannel = await readLocalChannelConfig(process.cwd(), type, provider, name);
|
|
521
|
+
const mode = discordModeForCli(type, modeFlag, localChannel);
|
|
522
|
+
const payload = {
|
|
523
|
+
name,
|
|
524
|
+
type,
|
|
525
|
+
provider,
|
|
526
|
+
...(mode ? { mode } : {}),
|
|
527
|
+
secrets: localChannel?.secrets ?? defaultChannelSecretsForCli(type, provider, mode),
|
|
528
|
+
...(localChannel?.limits ? { limits: channelLimitsForApi(localChannel.limits) } : {}),
|
|
529
|
+
...(localChannel?.buffer ? { buffer: localChannel.buffer } : {}),
|
|
530
|
+
...(localChannel?.audio ? { audio: localChannel.audio } : {}),
|
|
531
|
+
...channelExtraConfigForApi(localChannel),
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
return cloudPost<{ channel: CloudChannel }>(
|
|
535
|
+
parseCloudApiUrl(apiFlag),
|
|
536
|
+
`/v1/deploys/${encodeURIComponent(deployState.deploy_id)}/channels`,
|
|
537
|
+
payload,
|
|
538
|
+
);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
async function runChannelSmoke(
|
|
542
|
+
channel: CloudChannel,
|
|
543
|
+
apiFlag: string | boolean | undefined,
|
|
544
|
+
messageFlag: string | boolean | undefined,
|
|
545
|
+
): Promise<{ status: string; delivery_id?: string }> {
|
|
546
|
+
const apiUrl = parseCloudApiUrl(apiFlag);
|
|
547
|
+
const { response, payload } = channel.type === "whatsapp" && channel.provider === "evolution"
|
|
548
|
+
? await runServerGeneratedChannelTest(channel, apiUrl, messageFlag)
|
|
549
|
+
: await runServerSignedChannelTest(channel, apiUrl, await channelTestBody(channel, messageFlag, undefined));
|
|
550
|
+
|
|
551
|
+
if (!response.ok) {
|
|
552
|
+
throw new Error(readCloudError(payload, response.status));
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
if (isRecord(payload) && typeof payload.status === "string") {
|
|
556
|
+
return {
|
|
557
|
+
status: payload.status,
|
|
558
|
+
...(typeof payload.delivery_id === "string" ? { delivery_id: payload.delivery_id } : {}),
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
return { status: "unknown" };
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
async function runServerGeneratedChannelTest(
|
|
566
|
+
channel: CloudChannel,
|
|
567
|
+
apiUrl: string,
|
|
568
|
+
messageFlag: string | boolean | undefined,
|
|
569
|
+
): Promise<{ response: Response; payload: unknown }> {
|
|
570
|
+
const body: Record<string, unknown> = {};
|
|
571
|
+
|
|
572
|
+
if (typeof messageFlag === "string") {
|
|
573
|
+
body.message = messageFlag;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
const response = await cloudFetch(apiUrl, `/v1/channels/${encodeURIComponent(channel.id)}/test`, {
|
|
577
|
+
method: "POST",
|
|
578
|
+
headers: { "Content-Type": "application/json" },
|
|
579
|
+
body: JSON.stringify(body),
|
|
580
|
+
});
|
|
581
|
+
const payload = await response.json().catch(() => null);
|
|
582
|
+
|
|
583
|
+
if (!response.ok && isUnsupportedChannelTestRoute(payload)) {
|
|
584
|
+
return runLocallySignedChannelTest(
|
|
585
|
+
channel,
|
|
586
|
+
apiUrl,
|
|
587
|
+
await channelTestBody(channel, messageFlag, undefined),
|
|
588
|
+
);
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
return { response, payload };
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
async function runConnectSmoke(
|
|
595
|
+
run: () => Promise<{ status: string; delivery_id?: string }>,
|
|
596
|
+
): Promise<{ status: string; delivery_id?: string } | null> {
|
|
597
|
+
try {
|
|
598
|
+
const smoke = await run();
|
|
599
|
+
console.log(`Synthetic smoke: ${smoke.status}${smoke.delivery_id ? ` (${smoke.delivery_id})` : ""}`);
|
|
600
|
+
return smoke;
|
|
601
|
+
} catch (error) {
|
|
602
|
+
console.log("Synthetic smoke: failed");
|
|
603
|
+
console.log(` ${formatCommandError(error)}`);
|
|
604
|
+
return null;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
async function runServerSignedChannelTest(
|
|
609
|
+
channel: CloudChannel,
|
|
610
|
+
apiUrl: string,
|
|
611
|
+
rawBody: string,
|
|
612
|
+
): Promise<{ response: Response; payload: unknown }> {
|
|
613
|
+
const response = await cloudFetch(apiUrl, `/v1/channels/${encodeURIComponent(channel.id)}/test`, {
|
|
614
|
+
method: "POST",
|
|
615
|
+
headers: { "Content-Type": "application/json" },
|
|
616
|
+
body: JSON.stringify({ raw_body: rawBody }),
|
|
617
|
+
});
|
|
618
|
+
const payload = await response.json().catch(() => null);
|
|
619
|
+
|
|
620
|
+
if (!response.ok && isUnsupportedChannelTestRoute(payload)) {
|
|
621
|
+
return runLocallySignedChannelTest(channel, apiUrl, rawBody);
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
return { response, payload };
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
async function runLocallySignedChannelTest(
|
|
628
|
+
channel: CloudChannel,
|
|
629
|
+
apiUrl: string,
|
|
630
|
+
rawBody: string,
|
|
631
|
+
): Promise<{ response: Response; payload: unknown }> {
|
|
632
|
+
const webhookUrl = new URL(channel.webhook_url);
|
|
633
|
+
|
|
634
|
+
if (channel.required_secrets.some((secret) => secret.name === "ZAPSTER_WEBHOOK_TOKEN")) {
|
|
635
|
+
webhookUrl.searchParams.set("token", process.env.ZAPSTER_WEBHOOK_TOKEN ?? "");
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
if (channel.required_secrets.some((secret) => secret.name === "UAZAPI_WEBHOOK_TOKEN")) {
|
|
639
|
+
webhookUrl.searchParams.set("token", process.env.UAZAPI_WEBHOOK_TOKEN ?? "");
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
if (channel.required_secrets.some((secret) => secret.name === "EVOLUTION_WEBHOOK_TOKEN")) {
|
|
643
|
+
webhookUrl.searchParams.set("token", process.env.EVOLUTION_WEBHOOK_TOKEN ?? "");
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
const path = `${webhookUrl.pathname}${webhookUrl.search}`;
|
|
647
|
+
const headers = channelTestHeaders(channel, rawBody);
|
|
648
|
+
const response = await cloudFetch(apiUrl, path, {
|
|
649
|
+
method: "POST",
|
|
650
|
+
headers,
|
|
651
|
+
body: rawBody,
|
|
652
|
+
});
|
|
653
|
+
const payload = await response.json().catch(() => null);
|
|
654
|
+
|
|
655
|
+
return { response, payload };
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
function isUnsupportedChannelTestRoute(payload: unknown): boolean {
|
|
659
|
+
return (
|
|
660
|
+
isRecord(payload) &&
|
|
661
|
+
isRecord(payload.error) &&
|
|
662
|
+
(payload.error.code === "not_found" ||
|
|
663
|
+
payload.error.code === "channel_store_not_configured" ||
|
|
664
|
+
payload.error.code === "channel_delivery_not_found")
|
|
201
665
|
);
|
|
202
666
|
}
|
|
203
667
|
|
|
204
668
|
async function applyTelegramWebhook(
|
|
205
669
|
channel: CloudChannel,
|
|
206
670
|
apiFlag: string | boolean | undefined,
|
|
207
|
-
): Promise<
|
|
671
|
+
): Promise<ChannelSetupResponse> {
|
|
208
672
|
try {
|
|
209
|
-
await cloudPost(
|
|
673
|
+
return await cloudPost<ChannelSetupResponse>(
|
|
210
674
|
parseCloudApiUrl(apiFlag),
|
|
211
675
|
`/v1/channels/${encodeURIComponent(channel.id)}/setup`,
|
|
212
676
|
{},
|
|
213
677
|
);
|
|
214
|
-
return;
|
|
215
678
|
} catch (error) {
|
|
216
679
|
if (!shouldFallbackToLocalTelegramSetup(error)) {
|
|
217
680
|
throw error;
|
|
@@ -226,9 +689,10 @@ async function applyTelegramWebhook(
|
|
|
226
689
|
}
|
|
227
690
|
|
|
228
691
|
let response: Response;
|
|
692
|
+
const apiBaseUrl = telegramApiBaseUrlFromEnv();
|
|
229
693
|
|
|
230
694
|
try {
|
|
231
|
-
response = await fetch(
|
|
695
|
+
response = await fetch(new URL(`/bot${botToken}/setWebhook`, apiBaseUrl).href, {
|
|
232
696
|
method: "POST",
|
|
233
697
|
headers: { "Content-Type": "application/json" },
|
|
234
698
|
body: JSON.stringify({
|
|
@@ -246,6 +710,51 @@ async function applyTelegramWebhook(
|
|
|
246
710
|
if (!response.ok || !isTelegramOk(payload)) {
|
|
247
711
|
throw new Error(readTelegramSetupError(payload, response.status));
|
|
248
712
|
}
|
|
713
|
+
|
|
714
|
+
const webhookInfo = await fetch(new URL(`/bot${botToken}/getWebhookInfo`, apiBaseUrl).href)
|
|
715
|
+
.then((telegramResponse) => telegramResponse.json())
|
|
716
|
+
.catch(() => null);
|
|
717
|
+
const webhookUrl =
|
|
718
|
+
webhookInfo &&
|
|
719
|
+
typeof webhookInfo === "object" &&
|
|
720
|
+
"result" in webhookInfo &&
|
|
721
|
+
webhookInfo.result &&
|
|
722
|
+
typeof webhookInfo.result === "object" &&
|
|
723
|
+
"url" in webhookInfo.result &&
|
|
724
|
+
typeof webhookInfo.result.url === "string"
|
|
725
|
+
? webhookInfo.result.url
|
|
726
|
+
: "";
|
|
727
|
+
|
|
728
|
+
if (webhookUrl !== channel.webhook_url) {
|
|
729
|
+
throw new Error(`Telegram getWebhookInfo returned ${webhookUrl || "no webhook URL"} instead of ${channel.webhook_url}.`);
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
return {
|
|
733
|
+
channel: {
|
|
734
|
+
...channel,
|
|
735
|
+
status: "connected",
|
|
736
|
+
},
|
|
737
|
+
setup: {
|
|
738
|
+
provider: "telegram",
|
|
739
|
+
webhook_url: channel.webhook_url,
|
|
740
|
+
status: "configured",
|
|
741
|
+
get_webhook_info: {
|
|
742
|
+
url: webhookUrl,
|
|
743
|
+
pending_update_count: 0,
|
|
744
|
+
},
|
|
745
|
+
},
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
async function applyProviderWebhook(
|
|
750
|
+
channel: CloudChannel,
|
|
751
|
+
apiFlag: string | boolean | undefined,
|
|
752
|
+
): Promise<ChannelSetupResponse> {
|
|
753
|
+
return cloudPost<ChannelSetupResponse>(
|
|
754
|
+
parseCloudApiUrl(apiFlag),
|
|
755
|
+
`/v1/channels/${encodeURIComponent(channel.id)}/setup`,
|
|
756
|
+
{},
|
|
757
|
+
);
|
|
249
758
|
}
|
|
250
759
|
|
|
251
760
|
function shouldFallbackToLocalTelegramSetup(error: unknown): boolean {
|
|
@@ -270,10 +779,103 @@ function readTelegramSetupError(payload: unknown, status: number): string {
|
|
|
270
779
|
return `Telegram setWebhook failed with HTTP ${status}.`;
|
|
271
780
|
}
|
|
272
781
|
|
|
782
|
+
function telegramApiBaseUrlFromEnv(): string {
|
|
783
|
+
return process.env.AGENTKIT_TELEGRAM_API_BASE_URL ?? "https://api.telegram.org";
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
async function loadLocalEnvIntoProcess(cwd: string): Promise<void> {
|
|
787
|
+
const env = await loadCapsuleEnv(cwd, process.env);
|
|
788
|
+
|
|
789
|
+
for (const [key, value] of Object.entries(env)) {
|
|
790
|
+
if (value !== undefined && process.env[key] === undefined) {
|
|
791
|
+
process.env[key] = value;
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
function printTelegramHumanNextStep(channelName: string, botUsername: string | undefined): void {
|
|
797
|
+
console.log("");
|
|
798
|
+
console.log("Next human step:");
|
|
799
|
+
console.log(` Open ${botUsername ? `@${botUsername}` : "your Telegram bot"} in Telegram`);
|
|
800
|
+
console.log(" Send /start");
|
|
801
|
+
console.log(' Send "hello"');
|
|
802
|
+
console.log(` Then run: npm run agentkit -- channels deliveries list ${channelName}`);
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
function printUazapiHumanNextStep(channel: CloudChannel): void {
|
|
806
|
+
console.log("");
|
|
807
|
+
console.log("Next human step:");
|
|
808
|
+
console.log(" Confirm the UAZAPI instance is connected to WhatsApp");
|
|
809
|
+
console.log(' Send "hello" from a real WhatsApp contact');
|
|
810
|
+
console.log(` Then run: npm run agentkit -- channels deliveries list ${channel.name}`);
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
function printEvolutionHumanNextStep(channel: CloudChannel): void {
|
|
814
|
+
console.log("");
|
|
815
|
+
console.log("Next human step:");
|
|
816
|
+
console.log(" Confirm the Evolution API instance is connected to WhatsApp");
|
|
817
|
+
console.log(' Send "hello" from a real WhatsApp contact');
|
|
818
|
+
console.log(` Then run: npm run agentkit -- channels deliveries list ${channel.name}`);
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
function printDiscordHumanNextStep(channel: CloudChannel): void {
|
|
822
|
+
console.log("");
|
|
823
|
+
console.log("Next human step:");
|
|
824
|
+
|
|
825
|
+
if (channel.mode === "bot") {
|
|
826
|
+
console.log(" Open the Discord Developer Portal for your application");
|
|
827
|
+
console.log(" Bot page: enable Message Content Intent");
|
|
828
|
+
console.log(" Installation page: install the app into your server with bot permissions");
|
|
829
|
+
console.log(" Grant the bot View Channel, Read Message History, and Send Messages in the channels it should answer");
|
|
830
|
+
console.log(' Send "hello" in an allowed server channel');
|
|
831
|
+
console.log(` Then run: npm run agentkit -- channels deliveries list ${channel.name}`);
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
console.log(" Open the Discord Developer Portal for your application");
|
|
836
|
+
console.log(` Paste ${channel.webhook_url} into Interactions Endpoint URL`);
|
|
837
|
+
console.log(" Register a slash command with a string option named message, text, prompt, question, query, or input");
|
|
838
|
+
console.log(` Then run: npm run agentkit -- channels deliveries list ${channel.name}`);
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
function printSlackHumanNextStep(channel: CloudChannel): void {
|
|
842
|
+
console.log("");
|
|
843
|
+
console.log("Next human step:");
|
|
844
|
+
console.log(" Open the Slack app configuration");
|
|
845
|
+
console.log(" Event Subscriptions: enable events");
|
|
846
|
+
console.log(` Request URL: paste ${channel.webhook_url}`);
|
|
847
|
+
console.log(" Subscribe to bot events: app_mention and message.im");
|
|
848
|
+
console.log(" OAuth scopes: app_mentions:read, im:history, chat:write");
|
|
849
|
+
console.log(" Install or reinstall the app to the workspace");
|
|
850
|
+
console.log(' Mention the app with "hello" or send it a direct message');
|
|
851
|
+
console.log(` Then run: npm run agentkit -- channels deliveries list ${channel.name}`);
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
function printGenericWebhookHumanNextStep(channel: CloudChannel): void {
|
|
855
|
+
console.log("");
|
|
856
|
+
console.log("Next human step:");
|
|
857
|
+
console.log(` Point n8n, Make, Zapier, or your server to ${channel.webhook_url}`);
|
|
858
|
+
console.log(" Send JSON with event_id, external_id, and message");
|
|
859
|
+
console.log(" Authenticate with Authorization: Bearer <AGENTKIT_WEBHOOK_SECRET>");
|
|
860
|
+
console.log(` Then run: npm run agentkit -- channels deliveries list ${channel.name}`);
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
864
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
function formatCommandError(error: unknown): string {
|
|
868
|
+
return error instanceof Error ? error.message : String(error);
|
|
869
|
+
}
|
|
870
|
+
|
|
273
871
|
async function readDeployStateIfExists(cwd: string): Promise<DeployState | null> {
|
|
274
872
|
return readLocalDeployStateIfExists(cwd);
|
|
275
873
|
}
|
|
276
874
|
|
|
875
|
+
function isCliChannelType(value: string): value is "website" | "telegram" | "whatsapp" | "discord" | "slack" | "webhook" {
|
|
876
|
+
return value === "website" || value === "telegram" || value === "whatsapp" || value === "discord" || value === "slack" || value === "webhook";
|
|
877
|
+
}
|
|
878
|
+
|
|
277
879
|
async function readDeployStateRequired(cwd: string): Promise<DeployState> {
|
|
278
880
|
const state = await readDeployStateIfExists(cwd);
|
|
279
881
|
|
|
@@ -303,13 +905,28 @@ function printChannelStatus(channel: CloudChannel): void {
|
|
|
303
905
|
}
|
|
304
906
|
console.log(`Type: ${channel.type}`);
|
|
305
907
|
console.log(`Provider: ${channel.provider}`);
|
|
908
|
+
if (channel.type === "discord") {
|
|
909
|
+
console.log(`Mode: ${channel.mode ?? "interactions"}`);
|
|
910
|
+
}
|
|
911
|
+
if (channel.type === "webhook" && channel.direction) {
|
|
912
|
+
console.log(`Direction: ${channel.direction}`);
|
|
913
|
+
}
|
|
306
914
|
console.log(`Status: ${channel.status}`);
|
|
307
915
|
if (channel.webhook_url) {
|
|
308
916
|
console.log(`Webhook URL: ${channel.webhook_url}`);
|
|
309
917
|
}
|
|
310
918
|
console.log("Required secrets:");
|
|
311
|
-
|
|
312
|
-
|
|
919
|
+
if (
|
|
920
|
+
channel.required_secrets.length === 0 &&
|
|
921
|
+
defaultChannelSecretsForCli(channel.type, channel.provider, channel.mode).length > 0
|
|
922
|
+
) {
|
|
923
|
+
console.log(
|
|
924
|
+
` control-plane channel metadata bug: required_secrets is empty for ${channel.type}/${channel.provider}`,
|
|
925
|
+
);
|
|
926
|
+
} else {
|
|
927
|
+
for (const secret of channel.required_secrets) {
|
|
928
|
+
console.log(` ${secret.name}: ${secret.status}`);
|
|
929
|
+
}
|
|
313
930
|
}
|
|
314
931
|
if (channel.buffer) {
|
|
315
932
|
if (channel.buffer.mode === "off") {
|
|
@@ -322,6 +939,29 @@ function printChannelStatus(channel: CloudChannel): void {
|
|
|
322
939
|
}
|
|
323
940
|
}
|
|
324
941
|
|
|
942
|
+
function printSecretRepairHint(channel: CloudChannel): void {
|
|
943
|
+
if (
|
|
944
|
+
channel.required_secrets.length > 0 ||
|
|
945
|
+
defaultChannelSecretsForCli(channel.type, channel.provider, channel.mode).length === 0
|
|
946
|
+
) {
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
console.log(
|
|
951
|
+
"Hint: control-plane channel metadata bug. Rerun add/connect against the updated control-plane to repair required_secrets.",
|
|
952
|
+
);
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
function hasMissingRequiredSecrets(channel: CloudChannel): boolean {
|
|
956
|
+
const expected = defaultChannelSecretsForCli(channel.type, channel.provider, channel.mode);
|
|
957
|
+
|
|
958
|
+
if (channel.required_secrets.length === 0 && expected.length > 0) {
|
|
959
|
+
return true;
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
return channel.required_secrets.some((secret) => secret.status !== "set");
|
|
963
|
+
}
|
|
964
|
+
|
|
325
965
|
async function printChannelDeliverySummary(
|
|
326
966
|
channel: CloudChannel,
|
|
327
967
|
apiFlag: string | boolean | undefined,
|
|
@@ -339,19 +979,174 @@ async function printChannelDeliverySummary(
|
|
|
339
979
|
console.log("Provider health: not_checked");
|
|
340
980
|
}
|
|
341
981
|
|
|
982
|
+
function printChannelDoctor(report: ChannelDoctorReport): void {
|
|
983
|
+
console.log(`Channel doctor: ${report.channel.name}`);
|
|
984
|
+
console.log("");
|
|
985
|
+
console.log("Checks:");
|
|
986
|
+
for (const check of report.checks) {
|
|
987
|
+
console.log(` ${formatCheckStatus(check.status)} ${check.name}: ${check.message}`);
|
|
988
|
+
if (check.fix) {
|
|
989
|
+
console.log(` Fix: ${check.fix}`);
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
console.log("");
|
|
994
|
+
console.log("Last delivery:");
|
|
995
|
+
console.log(
|
|
996
|
+
` Inbound: ${report.last_delivery.inbound ? `${report.last_delivery.inbound.status} ${report.last_delivery.inbound.updated_at ?? report.last_delivery.inbound.received_at}` : "none"}`,
|
|
997
|
+
);
|
|
998
|
+
console.log(
|
|
999
|
+
` Outbound: ${report.last_delivery.outbound ? `${report.last_delivery.outbound.status} ${report.last_delivery.outbound.updated_at ?? report.last_delivery.outbound.received_at}` : "none"}`,
|
|
1000
|
+
);
|
|
1001
|
+
|
|
1002
|
+
if (report.buffers.length > 0) {
|
|
1003
|
+
console.log("");
|
|
1004
|
+
console.log("Buffers:");
|
|
1005
|
+
for (const buffer of report.buffers) {
|
|
1006
|
+
console.log(
|
|
1007
|
+
` ${buffer.conversation_id}: ${buffer.status} messages=${buffer.buffered_message_count} stale=${buffer.stale ? "yes" : "no"}`,
|
|
1008
|
+
);
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
if (report.probable_error) {
|
|
1013
|
+
console.log("");
|
|
1014
|
+
console.log(`Probable error: ${report.probable_error}`);
|
|
1015
|
+
}
|
|
1016
|
+
if (report.suggested_fix) {
|
|
1017
|
+
console.log(`Suggested fix: ${report.suggested_fix}`);
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
function formatCheckStatus(status: ChannelDoctorReport["checks"][number]["status"]): string {
|
|
1022
|
+
if (status === "ok") {
|
|
1023
|
+
return "OK";
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
if (status === "warn") {
|
|
1027
|
+
return "WARN";
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
if (status === "fail") {
|
|
1031
|
+
return "FAIL";
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
return "UNKNOWN";
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
function printChannelBuffers(buffers: CloudChannelBuffer[]): void {
|
|
1038
|
+
if (buffers.length === 0) {
|
|
1039
|
+
console.log("No channel buffers found.");
|
|
1040
|
+
return;
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
console.log("CONVERSATION\tSTATUS\tMESSAGES\tCHARS\tSTALE\tAVAILABLE_AT\tJOB");
|
|
1044
|
+
for (const buffer of buffers) {
|
|
1045
|
+
console.log(
|
|
1046
|
+
[
|
|
1047
|
+
buffer.conversation_id,
|
|
1048
|
+
buffer.status,
|
|
1049
|
+
String(buffer.buffered_message_count),
|
|
1050
|
+
String(buffer.buffered_char_count),
|
|
1051
|
+
buffer.stale ? "yes" : "no",
|
|
1052
|
+
buffer.available_at ?? "",
|
|
1053
|
+
buffer.job_id ?? "",
|
|
1054
|
+
].join("\t"),
|
|
1055
|
+
);
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
function printChannelBufferAction(action: string, response: ChannelBufferActionResponse): void {
|
|
1060
|
+
const conversationId = response.buffer?.conversation_id ?? response.job?.conversation_id ?? "unknown";
|
|
1061
|
+
|
|
1062
|
+
if (action === "flush") {
|
|
1063
|
+
console.log(
|
|
1064
|
+
response.job
|
|
1065
|
+
? `Flushed buffer ${conversationId}; queued job ${response.job.id}.`
|
|
1066
|
+
: `No queued buffer was flushed for ${conversationId}.`,
|
|
1067
|
+
);
|
|
1068
|
+
return;
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
if (action === "clear") {
|
|
1072
|
+
console.log(
|
|
1073
|
+
response.cleared
|
|
1074
|
+
? `Cleared buffer ${conversationId}.`
|
|
1075
|
+
: `No queued buffer was cleared for ${conversationId}.`,
|
|
1076
|
+
);
|
|
1077
|
+
return;
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
console.log(
|
|
1081
|
+
response.retried && response.job
|
|
1082
|
+
? `Retried channel job ${response.job.id} for ${conversationId}.`
|
|
1083
|
+
: `No failed or dead-lettered job was retried for ${conversationId}.`,
|
|
1084
|
+
);
|
|
1085
|
+
}
|
|
1086
|
+
|
|
342
1087
|
async function resolveCloudChannelByName(name: string, apiFlag: string | boolean | undefined): Promise<CloudChannel> {
|
|
1088
|
+
const channels = await listCloudChannelsForCurrentDeploy(apiFlag);
|
|
1089
|
+
const channel = channels.find((candidate) => candidate.name === name);
|
|
1090
|
+
|
|
1091
|
+
if (!channel) {
|
|
1092
|
+
const deployState = await readDeployStateRequired(process.cwd());
|
|
1093
|
+
throw new Error(`Channel "${name}" was not found for deploy ${deployState.deploy_id}.`);
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
return channel;
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
async function listCloudChannelsForCurrentDeploy(apiFlag: string | boolean | undefined): Promise<CloudChannel[]> {
|
|
343
1100
|
const deployState = await readDeployStateRequired(process.cwd());
|
|
344
1101
|
const response = await cloudGet<{ channels: CloudChannel[] }>(
|
|
345
1102
|
parseCloudApiUrl(apiFlag),
|
|
346
1103
|
`/v1/deploys/${encodeURIComponent(deployState.deploy_id)}/channels`,
|
|
347
1104
|
);
|
|
348
|
-
const channel = response.channels.find((candidate) => candidate.name === name);
|
|
349
1105
|
|
|
350
|
-
|
|
351
|
-
|
|
1106
|
+
return response.channels;
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
async function resolveChannelBufferTarget(
|
|
1110
|
+
first: string,
|
|
1111
|
+
second: string | undefined,
|
|
1112
|
+
apiFlag: string | boolean | undefined,
|
|
1113
|
+
): Promise<{ channel: CloudChannel; conversationId: string }> {
|
|
1114
|
+
if (second) {
|
|
1115
|
+
return {
|
|
1116
|
+
channel: await resolveCloudChannelByName(first, apiFlag),
|
|
1117
|
+
conversationId: second,
|
|
1118
|
+
};
|
|
352
1119
|
}
|
|
353
1120
|
|
|
354
|
-
|
|
1121
|
+
const conversationId = first;
|
|
1122
|
+
const channels = await listCloudChannelsForCurrentDeploy(apiFlag);
|
|
1123
|
+
const matches: CloudChannel[] = [];
|
|
1124
|
+
|
|
1125
|
+
for (const channel of channels) {
|
|
1126
|
+
const response = await cloudGet<{ buffers: CloudChannelBuffer[] }>(
|
|
1127
|
+
parseCloudApiUrl(apiFlag),
|
|
1128
|
+
`/v1/channels/${encodeURIComponent(channel.id)}/buffers`,
|
|
1129
|
+
);
|
|
1130
|
+
|
|
1131
|
+
if (response.buffers.some((buffer) => buffer.conversation_id === conversationId)) {
|
|
1132
|
+
matches.push(channel);
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
if (matches.length === 1) {
|
|
1137
|
+
return {
|
|
1138
|
+
channel: matches[0],
|
|
1139
|
+
conversationId,
|
|
1140
|
+
};
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
if (matches.length > 1) {
|
|
1144
|
+
throw new Error(
|
|
1145
|
+
`Conversation ${conversationId} has buffers in multiple channels. Use: agentkit channels buffers show <name> ${conversationId}`,
|
|
1146
|
+
);
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
throw new Error(`No channel buffer found for conversation ${conversationId}.`);
|
|
355
1150
|
}
|
|
356
1151
|
|
|
357
1152
|
function channelProviderForCli(type: string, providerFlag: string | boolean | undefined): string {
|
|
@@ -363,16 +1158,60 @@ function channelProviderForCli(type: string, providerFlag: string | boolean | un
|
|
|
363
1158
|
return "telegram";
|
|
364
1159
|
}
|
|
365
1160
|
|
|
1161
|
+
if (type === "discord") {
|
|
1162
|
+
return "discord";
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
if (type === "slack") {
|
|
1166
|
+
if (providerFlag !== undefined) {
|
|
1167
|
+
throw new Error("--provider is not supported for Slack channels.");
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
return "slack";
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
if (type === "webhook") {
|
|
1174
|
+
if (providerFlag !== undefined) {
|
|
1175
|
+
throw new Error("--provider is not supported for generic webhook channels.");
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
return "generic";
|
|
1179
|
+
}
|
|
1180
|
+
|
|
366
1181
|
const provider = providerFlag ?? "zapster";
|
|
367
1182
|
|
|
368
|
-
if (provider !== "zapster" && provider !== "meta") {
|
|
369
|
-
throw new Error('WhatsApp provider must be "zapster" or "
|
|
1183
|
+
if (provider !== "zapster" && provider !== "meta" && provider !== "uazapi" && provider !== "evolution") {
|
|
1184
|
+
throw new Error('WhatsApp provider must be "zapster", "meta", "uazapi", or "evolution".');
|
|
370
1185
|
}
|
|
371
1186
|
|
|
372
1187
|
return provider;
|
|
373
1188
|
}
|
|
374
1189
|
|
|
375
|
-
function
|
|
1190
|
+
function discordModeForCli(
|
|
1191
|
+
type: string,
|
|
1192
|
+
modeFlag: string | boolean | undefined,
|
|
1193
|
+
localChannel: AgentChannel | null,
|
|
1194
|
+
): "interactions" | "bot" | undefined {
|
|
1195
|
+
if (type !== "discord") {
|
|
1196
|
+
if (modeFlag !== undefined) {
|
|
1197
|
+
throw new Error("--mode is only supported for Discord channels.");
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
return undefined;
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
if (modeFlag !== undefined) {
|
|
1204
|
+
if (modeFlag !== "interactions" && modeFlag !== "bot") {
|
|
1205
|
+
throw new Error('Discord mode must be "interactions" or "bot".');
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
return modeFlag;
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
return localChannel?.type === "discord" ? localChannel.mode : "interactions";
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
function defaultChannelSecretsForCli(type: string, provider: string, mode?: "interactions" | "bot"): string[] {
|
|
376
1215
|
if (type === "website") {
|
|
377
1216
|
return ["AGENTKIT_WEBSITE_CHANNEL_TOKEN"];
|
|
378
1217
|
}
|
|
@@ -382,16 +1221,49 @@ function defaultChannelSecretsForCli(type: string, provider: string): string[] {
|
|
|
382
1221
|
}
|
|
383
1222
|
|
|
384
1223
|
if (type === "whatsapp" && provider === "zapster") {
|
|
385
|
-
return ["ZAPSTER_API_KEY", "
|
|
1224
|
+
return ["ZAPSTER_API_KEY", "ZAPSTER_INSTANCE_ID", "ZAPSTER_WEBHOOK_ID", "ZAPSTER_WEBHOOK_TOKEN"];
|
|
386
1225
|
}
|
|
387
1226
|
|
|
388
1227
|
if (type === "whatsapp" && provider === "meta") {
|
|
389
1228
|
return ["META_WHATSAPP_ACCESS_TOKEN", "META_WHATSAPP_APP_SECRET", "META_WHATSAPP_VERIFY_TOKEN"];
|
|
390
1229
|
}
|
|
391
1230
|
|
|
1231
|
+
if (type === "whatsapp" && provider === "uazapi") {
|
|
1232
|
+
return ["UAZAPI_BASE_URL", "UAZAPI_TOKEN", "UAZAPI_WEBHOOK_TOKEN"];
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
if (type === "whatsapp" && provider === "evolution") {
|
|
1236
|
+
return ["EVOLUTION_API_BASE_URL", "EVOLUTION_API_KEY", "EVOLUTION_INSTANCE_NAME", "EVOLUTION_WEBHOOK_TOKEN"];
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
if (type === "discord") {
|
|
1240
|
+
return mode === "bot" ? ["DISCORD_BOT_TOKEN"] : ["DISCORD_PUBLIC_KEY"];
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
if (type === "slack") {
|
|
1244
|
+
return ["SLACK_BOT_TOKEN", "SLACK_SIGNING_SECRET"];
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
if (type === "webhook") {
|
|
1248
|
+
return ["AGENTKIT_WEBHOOK_SECRET"];
|
|
1249
|
+
}
|
|
1250
|
+
|
|
392
1251
|
return [];
|
|
393
1252
|
}
|
|
394
1253
|
|
|
1254
|
+
function channelExtraConfigForApi(channel: AgentChannel | null): Record<string, unknown> {
|
|
1255
|
+
if (!channel) {
|
|
1256
|
+
return {};
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
return {
|
|
1260
|
+
...("direction" in channel && channel.direction ? { direction: channel.direction } : {}),
|
|
1261
|
+
...(channel.replyTo ? { reply_to: channel.replyTo } : {}),
|
|
1262
|
+
...("urlSecret" in channel && channel.urlSecret ? { url_secret: channel.urlSecret } : {}),
|
|
1263
|
+
...("auth" in channel && channel.auth ? { auth: channel.auth } : {}),
|
|
1264
|
+
};
|
|
1265
|
+
}
|
|
1266
|
+
|
|
395
1267
|
async function readLocalChannelConfig(
|
|
396
1268
|
cwd: string,
|
|
397
1269
|
type: string,
|
|
@@ -439,6 +1311,45 @@ async function channelTestBody(
|
|
|
439
1311
|
});
|
|
440
1312
|
}
|
|
441
1313
|
|
|
1314
|
+
if (channel.type === "whatsapp" && channel.provider === "uazapi") {
|
|
1315
|
+
return JSON.stringify({
|
|
1316
|
+
EventType: "messages",
|
|
1317
|
+
event: "messages",
|
|
1318
|
+
message: {
|
|
1319
|
+
id: `uaz_msg_${Date.now()}`,
|
|
1320
|
+
messageid: `uaz_wamid_${Date.now()}`,
|
|
1321
|
+
chatid: "5511999999999@s.whatsapp.net",
|
|
1322
|
+
sender: "5511999999999@s.whatsapp.net",
|
|
1323
|
+
senderName: "AgentKit",
|
|
1324
|
+
fromMe: false,
|
|
1325
|
+
wasSentByApi: false,
|
|
1326
|
+
messageType: "text",
|
|
1327
|
+
text: message,
|
|
1328
|
+
messageTimestamp: Date.now(),
|
|
1329
|
+
},
|
|
1330
|
+
});
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
if (channel.type === "whatsapp" && channel.provider === "evolution") {
|
|
1334
|
+
return JSON.stringify({
|
|
1335
|
+
event: "MESSAGES_UPSERT",
|
|
1336
|
+
instance: process.env.EVOLUTION_INSTANCE_NAME ?? "local",
|
|
1337
|
+
data: {
|
|
1338
|
+
key: {
|
|
1339
|
+
remoteJid: "5511999999999@s.whatsapp.net",
|
|
1340
|
+
fromMe: false,
|
|
1341
|
+
id: `evo_wamid_${Date.now()}`,
|
|
1342
|
+
},
|
|
1343
|
+
pushName: "AgentKit",
|
|
1344
|
+
messageType: "conversation",
|
|
1345
|
+
message: {
|
|
1346
|
+
conversation: message,
|
|
1347
|
+
},
|
|
1348
|
+
messageTimestamp: Math.floor(Date.now() / 1000),
|
|
1349
|
+
},
|
|
1350
|
+
});
|
|
1351
|
+
}
|
|
1352
|
+
|
|
442
1353
|
if (channel.type === "whatsapp") {
|
|
443
1354
|
return JSON.stringify({
|
|
444
1355
|
event_id: `evt_${Date.now()}`,
|
|
@@ -452,6 +1363,85 @@ async function channelTestBody(
|
|
|
452
1363
|
});
|
|
453
1364
|
}
|
|
454
1365
|
|
|
1366
|
+
if (channel.type === "discord") {
|
|
1367
|
+
if (channel.mode === "bot") {
|
|
1368
|
+
return JSON.stringify({
|
|
1369
|
+
id: `discord_message_${Date.now()}`,
|
|
1370
|
+
guild_id: "agentkit_guild",
|
|
1371
|
+
channel_id: "agentkit_channel",
|
|
1372
|
+
author: {
|
|
1373
|
+
id: "agentkit_user",
|
|
1374
|
+
username: "agentkit",
|
|
1375
|
+
},
|
|
1376
|
+
member: {
|
|
1377
|
+
nick: "AgentKit",
|
|
1378
|
+
},
|
|
1379
|
+
content: message,
|
|
1380
|
+
mentions: [],
|
|
1381
|
+
timestamp: new Date().toISOString(),
|
|
1382
|
+
});
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
return JSON.stringify({
|
|
1386
|
+
id: `discord_interaction_${Date.now()}`,
|
|
1387
|
+
application_id: "agentkit_discord_app",
|
|
1388
|
+
type: 2,
|
|
1389
|
+
data: {
|
|
1390
|
+
id: "agentkit_command",
|
|
1391
|
+
name: "ask",
|
|
1392
|
+
type: 1,
|
|
1393
|
+
options: [
|
|
1394
|
+
{
|
|
1395
|
+
name: "message",
|
|
1396
|
+
type: 3,
|
|
1397
|
+
value: message,
|
|
1398
|
+
},
|
|
1399
|
+
],
|
|
1400
|
+
},
|
|
1401
|
+
guild_id: "agentkit_guild",
|
|
1402
|
+
channel_id: "agentkit_channel",
|
|
1403
|
+
member: {
|
|
1404
|
+
user: {
|
|
1405
|
+
id: "agentkit_user",
|
|
1406
|
+
username: "agentkit",
|
|
1407
|
+
},
|
|
1408
|
+
},
|
|
1409
|
+
token: `agentkit_interaction_token_${Date.now()}`,
|
|
1410
|
+
version: 1,
|
|
1411
|
+
});
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
if (channel.type === "slack") {
|
|
1415
|
+
const now = Date.now();
|
|
1416
|
+
|
|
1417
|
+
return JSON.stringify({
|
|
1418
|
+
token: "deprecated-verification-token",
|
|
1419
|
+
team_id: "T_AGENTKIT",
|
|
1420
|
+
api_app_id: "A_AGENTKIT",
|
|
1421
|
+
type: "event_callback",
|
|
1422
|
+
event_id: `Ev${now}`,
|
|
1423
|
+
event_time: Math.floor(now / 1000),
|
|
1424
|
+
event: {
|
|
1425
|
+
type: "app_mention",
|
|
1426
|
+
user: "UAGENTKITUSER",
|
|
1427
|
+
text: `<@UAGENTKITBOT> ${message}`,
|
|
1428
|
+
ts: `${Math.floor(now / 1000)}.000100`,
|
|
1429
|
+
channel: "C_AGENTKIT",
|
|
1430
|
+
event_ts: `${Math.floor(now / 1000)}.000100`,
|
|
1431
|
+
},
|
|
1432
|
+
authorizations: [],
|
|
1433
|
+
is_ext_shared_channel: false,
|
|
1434
|
+
});
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
if (channel.type === "webhook") {
|
|
1438
|
+
return JSON.stringify({
|
|
1439
|
+
event_id: `evt_${Date.now()}`,
|
|
1440
|
+
external_id: "agentkit-webhook-test",
|
|
1441
|
+
message,
|
|
1442
|
+
});
|
|
1443
|
+
}
|
|
1444
|
+
|
|
455
1445
|
return JSON.stringify({
|
|
456
1446
|
event_id: `web_${Date.now()}`,
|
|
457
1447
|
visitor_id: "visitor_test",
|
|
@@ -462,7 +1452,127 @@ async function channelTestBody(
|
|
|
462
1452
|
});
|
|
463
1453
|
}
|
|
464
1454
|
|
|
465
|
-
function
|
|
1455
|
+
async function channelAudioTestBody(
|
|
1456
|
+
channel: CloudChannel,
|
|
1457
|
+
fixtureFlag: string | boolean | undefined,
|
|
1458
|
+
): Promise<string> {
|
|
1459
|
+
if (typeof fixtureFlag === "string" && isExplicitFixturePath(fixtureFlag)) {
|
|
1460
|
+
return readFile(resolve(process.cwd(), fixtureFlag), "utf8");
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
const fixture = typeof fixtureFlag === "string" ? fixtureFlag : "voice-note";
|
|
1464
|
+
|
|
1465
|
+
if (channel.type === "telegram") {
|
|
1466
|
+
if (fixture !== "voice-note" && fixture !== "telegram-voice-note" && fixture !== "telegram-audio-message") {
|
|
1467
|
+
throw new Error('Telegram audio smoke supports --fixture voice-note or --fixture telegram-voice-note.');
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
return JSON.stringify({
|
|
1471
|
+
update_id: Date.now(),
|
|
1472
|
+
message: {
|
|
1473
|
+
message_id: 1,
|
|
1474
|
+
date: Math.floor(Date.now() / 1000),
|
|
1475
|
+
chat: { id: 456, type: "private" },
|
|
1476
|
+
from: { id: 123, first_name: "AgentKit" },
|
|
1477
|
+
voice: {
|
|
1478
|
+
file_id: `agentkit_voice_${Date.now()}`,
|
|
1479
|
+
file_unique_id: `agentkit_voice_unique_${Date.now()}`,
|
|
1480
|
+
duration: 12,
|
|
1481
|
+
mime_type: "audio/ogg",
|
|
1482
|
+
file_size: 42,
|
|
1483
|
+
},
|
|
1484
|
+
},
|
|
1485
|
+
});
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
if (channel.type === "whatsapp" && channel.provider === "zapster") {
|
|
1489
|
+
if (fixture !== "voice-note" && fixture !== "audio-file" && fixture !== "zapster-audio" && fixture !== "zapster-audio-message") {
|
|
1490
|
+
throw new Error('Zapster audio smoke supports --fixture voice-note, --fixture audio-file, or --fixture zapster-audio.');
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
return JSON.stringify({
|
|
1494
|
+
event_id: `zap_evt_audio_${Date.now()}`,
|
|
1495
|
+
instance_id: "local",
|
|
1496
|
+
message: {
|
|
1497
|
+
id: `wamid.audio.${Date.now()}`,
|
|
1498
|
+
from: "5511999999999",
|
|
1499
|
+
type: "audio",
|
|
1500
|
+
audio: {
|
|
1501
|
+
id: `media_audio_${Date.now()}`,
|
|
1502
|
+
url: `https://api.zapsterapi.com/media/audio/media_audio_${Date.now()}.ogg`,
|
|
1503
|
+
mime_type: "audio/ogg",
|
|
1504
|
+
duration: 12,
|
|
1505
|
+
file_size: 42,
|
|
1506
|
+
...(fixture === "voice-note" ? { ptt: "true" } : {}),
|
|
1507
|
+
},
|
|
1508
|
+
},
|
|
1509
|
+
});
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
if (channel.type === "whatsapp" && channel.provider === "uazapi") {
|
|
1513
|
+
if (fixture !== "voice-note" && fixture !== "audio-file" && fixture !== "uazapi-audio" && fixture !== "uazapi-audio-message") {
|
|
1514
|
+
throw new Error('UAZAPI audio smoke supports --fixture voice-note, --fixture audio-file, or --fixture uazapi-audio.');
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
return JSON.stringify({
|
|
1518
|
+
EventType: "messages",
|
|
1519
|
+
event: "messages",
|
|
1520
|
+
message: {
|
|
1521
|
+
id: `uaz_audio_${Date.now()}`,
|
|
1522
|
+
messageid: `uaz_wamid_audio_${Date.now()}`,
|
|
1523
|
+
chatid: "5511999999999@s.whatsapp.net",
|
|
1524
|
+
sender: "5511999999999@s.whatsapp.net",
|
|
1525
|
+
senderName: "AgentKit",
|
|
1526
|
+
fromMe: false,
|
|
1527
|
+
wasSentByApi: false,
|
|
1528
|
+
messageType: fixture === "voice-note" ? "ptt" : "audio",
|
|
1529
|
+
mimetype: "audio/ogg",
|
|
1530
|
+
duration: 12,
|
|
1531
|
+
file_size: 42,
|
|
1532
|
+
messageTimestamp: Date.now(),
|
|
1533
|
+
},
|
|
1534
|
+
});
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
if (channel.type === "whatsapp" && channel.provider === "evolution") {
|
|
1538
|
+
if (fixture !== "voice-note" && fixture !== "audio-file" && fixture !== "evolution-audio" && fixture !== "evolution-audio-message") {
|
|
1539
|
+
throw new Error(
|
|
1540
|
+
'Evolution audio smoke supports --fixture voice-note, --fixture audio-file, --fixture evolution-audio, or --fixture evolution-audio-message.',
|
|
1541
|
+
);
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
return JSON.stringify({
|
|
1545
|
+
event: "MESSAGES_UPSERT",
|
|
1546
|
+
instance: process.env.EVOLUTION_INSTANCE_NAME ?? "local",
|
|
1547
|
+
data: {
|
|
1548
|
+
key: {
|
|
1549
|
+
remoteJid: "5511999999999@s.whatsapp.net",
|
|
1550
|
+
fromMe: false,
|
|
1551
|
+
id: `evo_wamid_audio_${Date.now()}`,
|
|
1552
|
+
},
|
|
1553
|
+
pushName: "AgentKit",
|
|
1554
|
+
messageType: "audioMessage",
|
|
1555
|
+
message: {
|
|
1556
|
+
audioMessage: {
|
|
1557
|
+
mimetype: "audio/ogg",
|
|
1558
|
+
seconds: 12,
|
|
1559
|
+
ptt: fixture === "voice-note",
|
|
1560
|
+
fileLength: 42,
|
|
1561
|
+
},
|
|
1562
|
+
},
|
|
1563
|
+
messageTimestamp: Math.floor(Date.now() / 1000),
|
|
1564
|
+
},
|
|
1565
|
+
});
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
throw new Error(`Audio smoke is not supported for ${channel.type}/${channel.provider}.`);
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
function isExplicitFixturePath(value: string): boolean {
|
|
1572
|
+
return value.includes("/") || value.startsWith(".") || value.endsWith(".json");
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
function channelTestHeaders(channel: CloudChannel, rawBody: string): Headers {
|
|
466
1576
|
const headers = new Headers({ "Content-Type": "application/json" });
|
|
467
1577
|
|
|
468
1578
|
if (channel.type === "website") {
|
|
@@ -478,7 +1588,32 @@ function channelTestHeaders(channel: CloudChannel): Headers {
|
|
|
478
1588
|
}
|
|
479
1589
|
|
|
480
1590
|
if (channel.type === "whatsapp" && channel.provider === "zapster") {
|
|
481
|
-
headers.set("X-
|
|
1591
|
+
headers.set("X-Instance-ID", process.env.ZAPSTER_INSTANCE_ID ?? "");
|
|
1592
|
+
headers.set("X-Webhook-ID", process.env.ZAPSTER_WEBHOOK_ID ?? "");
|
|
1593
|
+
headers.set("X-Message-ID", `cli_msg_${Date.now()}`);
|
|
1594
|
+
headers.set("X-Attempt-Count", "1");
|
|
1595
|
+
headers.set("User-Agent", "Zapsterapi/agentkit-cli");
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
if (channel.type === "slack") {
|
|
1599
|
+
const signingSecret = process.env.SLACK_SIGNING_SECRET;
|
|
1600
|
+
|
|
1601
|
+
if (signingSecret) {
|
|
1602
|
+
const timestamp = String(Math.floor(Date.now() / 1000));
|
|
1603
|
+
const signature = createHmac("sha256", signingSecret)
|
|
1604
|
+
.update(`v0:${timestamp}:${rawBody}`)
|
|
1605
|
+
.digest("hex");
|
|
1606
|
+
headers.set("X-Slack-Request-Timestamp", timestamp);
|
|
1607
|
+
headers.set("X-Slack-Signature", `v0=${signature}`);
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
if (channel.type === "webhook") {
|
|
1612
|
+
const secret = process.env.AGENTKIT_WEBHOOK_SECRET;
|
|
1613
|
+
|
|
1614
|
+
if (secret) {
|
|
1615
|
+
headers.set("Authorization", `Bearer ${secret}`);
|
|
1616
|
+
}
|
|
482
1617
|
}
|
|
483
1618
|
|
|
484
1619
|
return headers;
|