@andreprado/agentkit 0.1.0-alpha.10
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 +69 -0
- package/bin/agentkit.mjs +23 -0
- package/docs/guides/add-channel.md +114 -0
- package/docs/guides/add-knowledge.md +134 -0
- package/docs/guides/add-tool.md +342 -0
- package/docs/guides/agentkit-skills-architecture.md +471 -0
- package/docs/guides/channel-security.md +81 -0
- package/docs/guides/channels-implementation-map.md +243 -0
- package/docs/guides/channels-production-handoff.md +102 -0
- package/docs/guides/connect-telegram.md +110 -0
- package/docs/guides/connect-whatsapp-zapster.md +119 -0
- package/docs/guides/create-agent.md +220 -0
- package/docs/guides/prepare-deploy.md +209 -0
- package/docs/guides/run-evals.md +179 -0
- package/docs/guides/security-rules.md +156 -0
- package/docs/guides/use-provider.md +140 -0
- package/docs/llms-full.txt +876 -0
- package/docs/llms.txt +83 -0
- package/docs/portable-deploy-release-checklist.md +41 -0
- package/package.json +47 -0
- package/src/cli/args.ts +36 -0
- package/src/cli/cloud-client.ts +265 -0
- package/src/cli/commands/channels.ts +810 -0
- package/src/cli/commands/knowledge.ts +136 -0
- package/src/cli/constants.ts +4 -0
- package/src/cli/deploy-chat-ui.ts +392 -0
- package/src/cli/deploy-readiness.ts +348 -0
- package/src/cli/flags.ts +162 -0
- package/src/cli/help.ts +184 -0
- package/src/cli/index.ts +1276 -0
- package/src/cli/process.ts +31 -0
- package/src/cloud/artifact.ts +139 -0
- package/src/cloud/client.ts +79 -0
- package/src/cloud/contracts.ts +63 -0
- package/src/cloud/index.ts +3 -0
- package/src/create-project.ts +177 -0
- package/src/index.ts +408 -0
- package/src/providers/index.ts +25 -0
- package/src/providers/pi.ts +286 -0
- package/src/providers/test.ts +133 -0
- package/src/providers/types.ts +34 -0
- package/src/runtime/build.ts +43 -0
- package/src/runtime/channel-buffer.ts +30 -0
- package/src/runtime/channel-test-harness.ts +112 -0
- package/src/runtime/channels/telegram.ts +360 -0
- package/src/runtime/channels/website.ts +132 -0
- package/src/runtime/channels/whatsapp-meta.ts +71 -0
- package/src/runtime/channels/whatsapp-zapster.ts +278 -0
- package/src/runtime/channels.ts +138 -0
- package/src/runtime/chat.ts +218 -0
- package/src/runtime/config.ts +684 -0
- package/src/runtime/conversations.ts +38 -0
- package/src/runtime/core/deploy-state.ts +54 -0
- package/src/runtime/core/manifest.ts +213 -0
- package/src/runtime/core/targets.ts +133 -0
- package/src/runtime/database.ts +256 -0
- package/src/runtime/db-commands.ts +167 -0
- package/src/runtime/deploy-readiness.ts +105 -0
- package/src/runtime/deploy.ts +1 -0
- package/src/runtime/dev-server.ts +1247 -0
- package/src/runtime/docs.ts +36 -0
- package/src/runtime/env.ts +152 -0
- package/src/runtime/errors.ts +13 -0
- package/src/runtime/evals.ts +509 -0
- package/src/runtime/inspect.ts +203 -0
- package/src/runtime/knowledge/chunk.ts +333 -0
- package/src/runtime/knowledge/config.ts +135 -0
- package/src/runtime/knowledge/embeddings.ts +133 -0
- package/src/runtime/knowledge/ingest.ts +521 -0
- package/src/runtime/knowledge/prompt-policy.ts +30 -0
- package/src/runtime/knowledge/retrieve.ts +283 -0
- package/src/runtime/knowledge/schema.ts +56 -0
- package/src/runtime/knowledge/tool.ts +64 -0
- package/src/runtime/knowledge/vector.ts +258 -0
- package/src/runtime/runtime-contract.ts +93 -0
- package/src/runtime/spec.ts +152 -0
- package/src/runtime/sync.ts +144 -0
- package/src/runtime/targets/cloudflare/build.ts +2517 -0
- package/src/runtime/targets/container/build.ts +146 -0
- package/src/runtime/targets/container/server.ts +33 -0
- package/src/runtime/targets/vps/deploy.ts +206 -0
- package/src/runtime/tool-runner.ts +65 -0
- package/src/runtime/tools.ts +470 -0
- package/src/runtime/traces.ts +41 -0
- package/src/storage/sqlite.ts +1118 -0
- package/src/templates/blank.ts +394 -0
- package/src/templates/dentista.ts +1003 -0
- package/src/templates/index.ts +33 -0
- package/src/templates/skills/agentkit-build-agent/SKILL.md +51 -0
- package/src/templates/skills/agentkit-build-agent/templates/appointment-intake.instructions.md +20 -0
- package/src/templates/skills/agentkit-build-agent/templates/sales-qualifier.instructions.md +17 -0
- package/src/templates/skills/agentkit-build-agent/templates/support-agent.instructions.md +16 -0
- package/src/templates/skills/agentkit-capsule/SKILL.md +62 -0
- package/src/templates/skills/agentkit-capsule/references/docs-router.md +15 -0
- package/src/templates/skills/agentkit-channels/SKILL.md +62 -0
- package/src/templates/skills/agentkit-channels/references/channel-buffering.md +58 -0
- package/src/templates/skills/agentkit-channels/references/channel-debugging.md +41 -0
- package/src/templates/skills/agentkit-channels/references/telegram.md +38 -0
- package/src/templates/skills/agentkit-channels/references/whatsapp-zapster.md +44 -0
- package/src/templates/skills/agentkit-database/SKILL.md +45 -0
- package/src/templates/skills/agentkit-database/templates/appointments.schema.sql +15 -0
- package/src/templates/skills/agentkit-database/templates/leads.schema.sql +17 -0
- package/src/templates/skills/agentkit-deploy/SKILL.md +44 -0
- package/src/templates/skills/agentkit-evals/SKILL.md +60 -0
- package/src/templates/skills/agentkit-evals/templates/multi-turn.eval.md +22 -0
- package/src/templates/skills/agentkit-evals/templates/no-leak.eval.md +14 -0
- package/src/templates/skills/agentkit-evals/templates/smoke.eval.md +14 -0
- package/src/templates/skills/agentkit-evals/templates/tool-call.eval.md +18 -0
- package/src/templates/skills/agentkit-knowledge/SKILL.md +40 -0
- package/src/templates/skills/agentkit-knowledge/templates/faq.md +14 -0
- package/src/templates/skills/agentkit-knowledge/templates/policies.md +14 -0
- package/src/templates/skills/agentkit-knowledge/templates/prices.csv +3 -0
- package/src/templates/skills/agentkit-prompts/SKILL.md +45 -0
- package/src/templates/skills/agentkit-prompts/templates/knowledge-grounded-faq.instructions.md +11 -0
- package/src/templates/skills/agentkit-provider/SKILL.md +57 -0
- package/src/templates/skills/agentkit-security/SKILL.md +55 -0
- package/src/templates/skills/agentkit-tools/SKILL.md +36 -0
- package/src/templates/skills/agentkit-tools/examples/database-write.tool.md +35 -0
- package/src/templates/skills/agentkit-tools/examples/eval-safe-external-action.tool.md +37 -0
- package/src/templates/skills/agentkit-tools/examples/lookup-order.tool.md +46 -0
- package/src/templates/skills/agentkit-troubleshooting/SKILL.md +52 -0
- package/src/templates/support.ts +401 -0
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ChannelAdapter,
|
|
3
|
+
ChannelSendInput,
|
|
4
|
+
ChannelStatusResult,
|
|
5
|
+
RawWebhookEvent,
|
|
6
|
+
WebhookVerificationResult,
|
|
7
|
+
} from "../channels";
|
|
8
|
+
import { AgentKitError } from "../errors";
|
|
9
|
+
|
|
10
|
+
export const telegramChannelAdapter: ChannelAdapter = {
|
|
11
|
+
type: "telegram",
|
|
12
|
+
provider: "telegram",
|
|
13
|
+
requiredSecrets: ["TELEGRAM_BOT_TOKEN", "TELEGRAM_WEBHOOK_SECRET"],
|
|
14
|
+
verifyWebhook(input): WebhookVerificationResult {
|
|
15
|
+
const expected = input.secrets.TELEGRAM_WEBHOOK_SECRET;
|
|
16
|
+
|
|
17
|
+
if (!expected) {
|
|
18
|
+
return {
|
|
19
|
+
ok: false,
|
|
20
|
+
code: "channel_secret_missing",
|
|
21
|
+
message: "Secret TELEGRAM_WEBHOOK_SECRET is not set for this channel.",
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const actual = input.headers.get("x-telegram-bot-api-secret-token");
|
|
26
|
+
|
|
27
|
+
if (actual !== expected) {
|
|
28
|
+
return {
|
|
29
|
+
ok: false,
|
|
30
|
+
code: "channel_signature_invalid",
|
|
31
|
+
message: "Telegram webhook secret token did not match.",
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return { ok: true };
|
|
36
|
+
},
|
|
37
|
+
normalizeInbound(input) {
|
|
38
|
+
const body = parseJsonObject(input);
|
|
39
|
+
const updateId = readNumber(body, "update_id");
|
|
40
|
+
|
|
41
|
+
if (updateId === undefined) {
|
|
42
|
+
throw new AgentKitError("channel_payload_invalid", "Telegram payload is missing update_id.");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const base = {
|
|
46
|
+
type: "telegram" as const,
|
|
47
|
+
provider: "telegram" as const,
|
|
48
|
+
providerEventId: String(updateId),
|
|
49
|
+
dedupeKey: `telegram:${input.channelId}:${updateId}`,
|
|
50
|
+
receivedAt: input.receivedAt,
|
|
51
|
+
};
|
|
52
|
+
const message = readRecord(body, "message");
|
|
53
|
+
const chat = readRecord(message, "chat");
|
|
54
|
+
const from = readRecord(message, "from");
|
|
55
|
+
const chatId = readNumberishString(chat, "id") ?? "unknown";
|
|
56
|
+
const userId = readNumberishString(from, "id") ?? "unknown";
|
|
57
|
+
const identity = {
|
|
58
|
+
key: `telegram:user:${userId}:chat:${chatId}`,
|
|
59
|
+
display: readString(from, "first_name"),
|
|
60
|
+
redactedDisplay: `telegram:user:${redactId(userId)}:chat:${chatId}`,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
if (!Object.keys(message).length) {
|
|
64
|
+
return [
|
|
65
|
+
{
|
|
66
|
+
...base,
|
|
67
|
+
kind: "unsupported",
|
|
68
|
+
externalIdentity: identity,
|
|
69
|
+
unsupportedReason: "unsupported_update_type:non_message",
|
|
70
|
+
},
|
|
71
|
+
];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const chatType = readString(chat, "type");
|
|
75
|
+
|
|
76
|
+
if (chatType !== "private") {
|
|
77
|
+
return [
|
|
78
|
+
{
|
|
79
|
+
...base,
|
|
80
|
+
kind: "unsupported",
|
|
81
|
+
externalIdentity: identity,
|
|
82
|
+
unsupportedReason: `unsupported_chat_type:${chatType ?? "unknown"}`,
|
|
83
|
+
},
|
|
84
|
+
];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const text = readString(message, "text");
|
|
88
|
+
|
|
89
|
+
if (!text) {
|
|
90
|
+
return [
|
|
91
|
+
{
|
|
92
|
+
...base,
|
|
93
|
+
kind: "unsupported",
|
|
94
|
+
externalIdentity: identity,
|
|
95
|
+
unsupportedReason: "unsupported_message_type:non_text",
|
|
96
|
+
},
|
|
97
|
+
];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return [
|
|
101
|
+
{
|
|
102
|
+
...base,
|
|
103
|
+
kind: "message",
|
|
104
|
+
externalIdentity: identity,
|
|
105
|
+
message: {
|
|
106
|
+
role: "user",
|
|
107
|
+
contentType: "text",
|
|
108
|
+
content: text,
|
|
109
|
+
providerMessageId: readNumberishString(message, "message_id"),
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
];
|
|
113
|
+
},
|
|
114
|
+
async sendMessage(input) {
|
|
115
|
+
return sendTelegramMessage(input);
|
|
116
|
+
},
|
|
117
|
+
getStatus(input) {
|
|
118
|
+
if (!input.secrets.TELEGRAM_BOT_TOKEN) {
|
|
119
|
+
return {
|
|
120
|
+
status: "needs_setup",
|
|
121
|
+
checkedAt: new Date().toISOString(),
|
|
122
|
+
message: "Secret TELEGRAM_BOT_TOKEN is missing.",
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
status: "needs_setup",
|
|
128
|
+
checkedAt: new Date().toISOString(),
|
|
129
|
+
message: "Run agentkit channels setup to call Telegram setWebhook.",
|
|
130
|
+
};
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
export type TelegramSendMessageRequest = {
|
|
135
|
+
method: "POST";
|
|
136
|
+
url: string;
|
|
137
|
+
body: {
|
|
138
|
+
chat_id: string;
|
|
139
|
+
text: string;
|
|
140
|
+
};
|
|
141
|
+
idempotencyKey: string;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
type TelegramFetch = (url: string, init: RequestInit) => Promise<Response>;
|
|
145
|
+
|
|
146
|
+
export function buildTelegramSendMessageRequest(input: ChannelSendInput): TelegramSendMessageRequest {
|
|
147
|
+
return buildTelegramSendMessageRequestInternal(input, "<redacted>");
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export async function sendTelegramMessage(
|
|
151
|
+
input: ChannelSendInput,
|
|
152
|
+
fetcher: TelegramFetch = fetch,
|
|
153
|
+
): Promise<Awaited<ReturnType<ChannelAdapter["sendMessage"]>>> {
|
|
154
|
+
if (input.secrets.AGENTKIT_CHANNEL_SEND_DRY_RUN === "1") {
|
|
155
|
+
const request = buildTelegramSendMessageRequest(input);
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
ok: true,
|
|
159
|
+
status: "sent",
|
|
160
|
+
providerRequestId: request.idempotencyKey,
|
|
161
|
+
providerMetadata: {
|
|
162
|
+
method: request.method,
|
|
163
|
+
url: request.url,
|
|
164
|
+
body: request.body,
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const token = input.secrets.TELEGRAM_BOT_TOKEN;
|
|
170
|
+
|
|
171
|
+
if (!token) {
|
|
172
|
+
return {
|
|
173
|
+
ok: false,
|
|
174
|
+
retryable: false,
|
|
175
|
+
code: "channel_secret_missing",
|
|
176
|
+
message: "Secret TELEGRAM_BOT_TOKEN is not set for this channel.",
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
let request: TelegramSendMessageRequest;
|
|
181
|
+
let actualRequest: TelegramSendMessageRequest;
|
|
182
|
+
|
|
183
|
+
try {
|
|
184
|
+
request = buildTelegramSendMessageRequest(input);
|
|
185
|
+
actualRequest = buildTelegramSendMessageRequestInternal(input, token);
|
|
186
|
+
} catch (error) {
|
|
187
|
+
return {
|
|
188
|
+
ok: false,
|
|
189
|
+
retryable: false,
|
|
190
|
+
code: "channel_send_failed",
|
|
191
|
+
message: error instanceof Error ? error.message : String(error),
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
let response: Response;
|
|
196
|
+
|
|
197
|
+
try {
|
|
198
|
+
response = await fetcher(actualRequest.url, {
|
|
199
|
+
method: actualRequest.method,
|
|
200
|
+
headers: {
|
|
201
|
+
"Content-Type": "application/json",
|
|
202
|
+
},
|
|
203
|
+
body: JSON.stringify(actualRequest.body),
|
|
204
|
+
});
|
|
205
|
+
} catch (error) {
|
|
206
|
+
const errorMessage = (error instanceof Error ? error.message : String(error)).replace(token, "<redacted>");
|
|
207
|
+
|
|
208
|
+
return {
|
|
209
|
+
ok: false,
|
|
210
|
+
retryable: true,
|
|
211
|
+
code: "channel_provider_unavailable",
|
|
212
|
+
message: `Telegram sendMessage request failed before a response: ${errorMessage}`,
|
|
213
|
+
providerRequestId: request.idempotencyKey,
|
|
214
|
+
providerMetadata: {
|
|
215
|
+
method: request.method,
|
|
216
|
+
url: request.url,
|
|
217
|
+
body: request.body,
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const payload = await response.json().catch(() => null);
|
|
223
|
+
|
|
224
|
+
if (!response.ok || !isRecord(payload) || payload.ok !== true) {
|
|
225
|
+
const description =
|
|
226
|
+
isRecord(payload) && typeof payload.description === "string"
|
|
227
|
+
? payload.description
|
|
228
|
+
: `Telegram sendMessage returned HTTP ${response.status}.`;
|
|
229
|
+
const syntheticExpectedFailure = isSyntheticTelegramChatId(request.body.chat_id) && /chat not found/i.test(description);
|
|
230
|
+
|
|
231
|
+
return {
|
|
232
|
+
ok: false,
|
|
233
|
+
retryable: syntheticExpectedFailure ? false : response.status === 429 || response.status >= 500,
|
|
234
|
+
code: syntheticExpectedFailure ? "synthetic_expected_failure" : "channel_send_failed",
|
|
235
|
+
message: syntheticExpectedFailure
|
|
236
|
+
? "Synthetic Telegram smoke reached AgentKit. Telegram correctly rejected the fake chat ID."
|
|
237
|
+
: description,
|
|
238
|
+
providerRequestId: request.idempotencyKey,
|
|
239
|
+
providerMetadata: {
|
|
240
|
+
method: request.method,
|
|
241
|
+
url: request.url,
|
|
242
|
+
body: request.body,
|
|
243
|
+
},
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const result = isRecord(payload.result) ? payload.result : undefined;
|
|
248
|
+
const messageId = readNumberishString(result ?? {}, "message_id");
|
|
249
|
+
|
|
250
|
+
return {
|
|
251
|
+
ok: true,
|
|
252
|
+
status: "sent",
|
|
253
|
+
providerRequestId: request.idempotencyKey,
|
|
254
|
+
...(messageId ? { providerMessageId: messageId } : {}),
|
|
255
|
+
providerMetadata: {
|
|
256
|
+
method: request.method,
|
|
257
|
+
url: request.url,
|
|
258
|
+
body: request.body,
|
|
259
|
+
},
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function isSyntheticTelegramChatId(chatId: string): boolean {
|
|
264
|
+
return chatId === "456" || chatId.startsWith("agentkit-synthetic-");
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function buildTelegramSendMessageRequestInternal(input: ChannelSendInput, token: string): TelegramSendMessageRequest {
|
|
268
|
+
const chatId = input.externalIdentity.key.match(/:chat:([^:]+)$/)?.[1];
|
|
269
|
+
|
|
270
|
+
if (!chatId) {
|
|
271
|
+
throw new AgentKitError("channel_payload_invalid", "Telegram external identity is missing chat ID.");
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return {
|
|
275
|
+
method: "POST",
|
|
276
|
+
url: `https://api.telegram.org/bot${token}/sendMessage`,
|
|
277
|
+
body: {
|
|
278
|
+
chat_id: chatId,
|
|
279
|
+
text: input.message.content,
|
|
280
|
+
},
|
|
281
|
+
idempotencyKey: `telegram:${input.channelId}:${input.conversationId}:${chatId}`,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export function parseTelegramWebhookInfo(input: unknown, checkedAt: string): ChannelStatusResult {
|
|
286
|
+
if (!isRecord(input) || input.ok !== true || !isRecord(input.result)) {
|
|
287
|
+
return {
|
|
288
|
+
status: "failed",
|
|
289
|
+
checkedAt,
|
|
290
|
+
message: "Telegram getWebhookInfo response was not ok.",
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const url = readString(input.result, "url");
|
|
295
|
+
const pendingUpdateCount = readNumber(input.result, "pending_update_count") ?? 0;
|
|
296
|
+
|
|
297
|
+
return {
|
|
298
|
+
status: url ? "connected" : "needs_setup",
|
|
299
|
+
checkedAt,
|
|
300
|
+
providerMetadata: {
|
|
301
|
+
hasWebhookUrl: Boolean(url),
|
|
302
|
+
pendingUpdateCount,
|
|
303
|
+
},
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function parseJsonObject(input: RawWebhookEvent): Record<string, unknown> {
|
|
308
|
+
try {
|
|
309
|
+
const parsed = JSON.parse(input.rawBody) as unknown;
|
|
310
|
+
|
|
311
|
+
if (isRecord(parsed)) {
|
|
312
|
+
return parsed;
|
|
313
|
+
}
|
|
314
|
+
} catch (error) {
|
|
315
|
+
throw new AgentKitError("channel_payload_invalid", "Telegram payload must be valid JSON.", { cause: error });
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
throw new AgentKitError("channel_payload_invalid", "Telegram payload must be a JSON object.");
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function readRecord(value: Record<string, unknown>, key: string): Record<string, unknown> {
|
|
322
|
+
const record = value[key];
|
|
323
|
+
return isRecord(record) ? record : {};
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function readString(value: Record<string, unknown>, key: string): string | undefined {
|
|
327
|
+
const field = value[key];
|
|
328
|
+
return typeof field === "string" && field.length > 0 ? field : undefined;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function readNumber(value: Record<string, unknown>, key: string): number | undefined {
|
|
332
|
+
const field = value[key];
|
|
333
|
+
return typeof field === "number" && Number.isFinite(field) ? field : undefined;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function readNumberishString(value: Record<string, unknown>, key: string): string | undefined {
|
|
337
|
+
const field = value[key];
|
|
338
|
+
|
|
339
|
+
if (typeof field === "string" && field.length > 0) {
|
|
340
|
+
return field;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (typeof field === "number" && Number.isFinite(field)) {
|
|
344
|
+
return String(field);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return undefined;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function redactId(value: string): string {
|
|
351
|
+
if (value.length <= 2) {
|
|
352
|
+
return "*".repeat(value.length);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return `${value.slice(0, 1)}***${value.slice(-1)}`;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
359
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
360
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import type { ChannelAdapter, ChannelSendInput, NormalizedChannelEvent, RawWebhookEvent } from "../channels";
|
|
2
|
+
import { AgentKitError } from "../errors";
|
|
3
|
+
|
|
4
|
+
export const websiteChannelAdapter: ChannelAdapter = {
|
|
5
|
+
type: "website",
|
|
6
|
+
provider: "agentkit",
|
|
7
|
+
requiredSecrets: ["AGENTKIT_WEBSITE_CHANNEL_TOKEN"],
|
|
8
|
+
verifyWebhook(input) {
|
|
9
|
+
const expected = input.secrets.AGENTKIT_WEBSITE_CHANNEL_TOKEN;
|
|
10
|
+
|
|
11
|
+
if (!expected) {
|
|
12
|
+
return {
|
|
13
|
+
ok: false,
|
|
14
|
+
code: "channel_secret_missing",
|
|
15
|
+
message: "Secret AGENTKIT_WEBSITE_CHANNEL_TOKEN is not set for this channel.",
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const bearer = input.headers.get("authorization");
|
|
20
|
+
const headerToken = input.headers.get("x-agentkit-channel-token");
|
|
21
|
+
|
|
22
|
+
if (bearer !== `Bearer ${expected}` && headerToken !== expected) {
|
|
23
|
+
return {
|
|
24
|
+
ok: false,
|
|
25
|
+
code: "channel_signature_invalid",
|
|
26
|
+
message: "Website channel token did not match.",
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return { ok: true };
|
|
31
|
+
},
|
|
32
|
+
normalizeInbound(input) {
|
|
33
|
+
const body = parseJsonObject(input);
|
|
34
|
+
const message = readRecord(body, "message");
|
|
35
|
+
const text = readString(message, "text");
|
|
36
|
+
const eventId = readString(body, "event_id") ?? readString(message, "id");
|
|
37
|
+
const clientId = readString(body, "client_id");
|
|
38
|
+
const visitorId = readString(body, "visitor_id") ?? clientId;
|
|
39
|
+
|
|
40
|
+
if (!eventId) {
|
|
41
|
+
throw new AgentKitError("channel_payload_invalid", "Website channel payload is missing event_id.");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!visitorId) {
|
|
45
|
+
throw new AgentKitError("channel_payload_invalid", "Website channel payload is missing visitor_id or client_id.");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const base = {
|
|
49
|
+
type: "website" as const,
|
|
50
|
+
provider: "agentkit" as const,
|
|
51
|
+
providerEventId: eventId,
|
|
52
|
+
dedupeKey: `website:${input.channelId}:${eventId}`,
|
|
53
|
+
externalIdentity: {
|
|
54
|
+
key: `website:visitor:${visitorId}`,
|
|
55
|
+
redactedDisplay: `website:visitor:${visitorId}`,
|
|
56
|
+
},
|
|
57
|
+
receivedAt: input.receivedAt,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
if (!text) {
|
|
61
|
+
return [
|
|
62
|
+
{
|
|
63
|
+
...base,
|
|
64
|
+
kind: "unsupported",
|
|
65
|
+
unsupportedReason: "unsupported_message_type:non_text",
|
|
66
|
+
},
|
|
67
|
+
];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return [
|
|
71
|
+
{
|
|
72
|
+
...base,
|
|
73
|
+
kind: "message",
|
|
74
|
+
message: {
|
|
75
|
+
role: "user",
|
|
76
|
+
contentType: "text",
|
|
77
|
+
content: text,
|
|
78
|
+
providerMessageId: readString(message, "id"),
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
];
|
|
82
|
+
},
|
|
83
|
+
async sendMessage(input) {
|
|
84
|
+
return {
|
|
85
|
+
ok: true,
|
|
86
|
+
status: "sent",
|
|
87
|
+
providerMessageId: buildWebsiteProviderMessageId(input),
|
|
88
|
+
};
|
|
89
|
+
},
|
|
90
|
+
getStatus() {
|
|
91
|
+
return {
|
|
92
|
+
status: "connected",
|
|
93
|
+
checkedAt: new Date().toISOString(),
|
|
94
|
+
};
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
function buildWebsiteProviderMessageId(input: ChannelSendInput): string {
|
|
99
|
+
return `website:${input.channelId}:${input.conversationId}`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function parseJsonObject(input: RawWebhookEvent): Record<string, unknown> {
|
|
103
|
+
try {
|
|
104
|
+
const parsed = JSON.parse(input.rawBody) as unknown;
|
|
105
|
+
|
|
106
|
+
if (isRecord(parsed)) {
|
|
107
|
+
return parsed;
|
|
108
|
+
}
|
|
109
|
+
} catch (error) {
|
|
110
|
+
throw new AgentKitError("channel_payload_invalid", "Website channel payload must be valid JSON.", {
|
|
111
|
+
cause: error,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
throw new AgentKitError("channel_payload_invalid", "Website channel payload must be a JSON object.");
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function readRecord(value: Record<string, unknown>, key: string): Record<string, unknown> {
|
|
119
|
+
const record = value[key];
|
|
120
|
+
return isRecord(record) ? record : {};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function readString(value: Record<string, unknown>, key: string): string | undefined {
|
|
124
|
+
const field = value[key];
|
|
125
|
+
return typeof field === "string" && field.length > 0 ? field : undefined;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
129
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export type { NormalizedChannelEvent };
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { ChannelAdapter, WebhookVerificationResult } from "../channels";
|
|
2
|
+
|
|
3
|
+
export const metaWhatsappChannelAdapter: ChannelAdapter = {
|
|
4
|
+
type: "whatsapp",
|
|
5
|
+
provider: "meta",
|
|
6
|
+
requiredSecrets: ["META_WHATSAPP_ACCESS_TOKEN", "META_WHATSAPP_APP_SECRET", "META_WHATSAPP_VERIFY_TOKEN"],
|
|
7
|
+
verifyWebhook(input): WebhookVerificationResult {
|
|
8
|
+
if (input.method === "GET") {
|
|
9
|
+
const url = new URL(input.url);
|
|
10
|
+
const mode = url.searchParams.get("hub.mode");
|
|
11
|
+
const verifyToken = url.searchParams.get("hub.verify_token");
|
|
12
|
+
const expected = input.secrets.META_WHATSAPP_VERIFY_TOKEN;
|
|
13
|
+
|
|
14
|
+
if (!expected) {
|
|
15
|
+
return {
|
|
16
|
+
ok: false,
|
|
17
|
+
code: "channel_secret_missing",
|
|
18
|
+
message: "Secret META_WHATSAPP_VERIFY_TOKEN is not set for this channel.",
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (mode === "subscribe" && verifyToken === expected) {
|
|
23
|
+
return { ok: true };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
ok: false,
|
|
28
|
+
code: "channel_signature_invalid",
|
|
29
|
+
message: "Meta webhook verification token did not match.",
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
ok: false,
|
|
35
|
+
code: "channel_payload_invalid",
|
|
36
|
+
message: "Meta Cloud API POST signature verification is planned but not implemented yet.",
|
|
37
|
+
};
|
|
38
|
+
},
|
|
39
|
+
normalizeInbound(input) {
|
|
40
|
+
return [
|
|
41
|
+
{
|
|
42
|
+
type: "whatsapp",
|
|
43
|
+
provider: "meta",
|
|
44
|
+
kind: "unsupported",
|
|
45
|
+
providerEventId: `meta:${input.channelId}:planned`,
|
|
46
|
+
dedupeKey: `whatsapp:meta:${input.channelId}:planned`,
|
|
47
|
+
externalIdentity: {
|
|
48
|
+
key: "whatsapp:meta:planned",
|
|
49
|
+
redactedDisplay: "whatsapp:meta:planned",
|
|
50
|
+
},
|
|
51
|
+
receivedAt: input.receivedAt,
|
|
52
|
+
unsupportedReason: "adapter_planned:meta_cloud_api",
|
|
53
|
+
},
|
|
54
|
+
];
|
|
55
|
+
},
|
|
56
|
+
async sendMessage() {
|
|
57
|
+
return {
|
|
58
|
+
ok: false,
|
|
59
|
+
retryable: false,
|
|
60
|
+
code: "channel_send_failed",
|
|
61
|
+
message: "Meta Cloud API sendMessage is planned but not implemented yet.",
|
|
62
|
+
};
|
|
63
|
+
},
|
|
64
|
+
getStatus() {
|
|
65
|
+
return {
|
|
66
|
+
status: "needs_setup",
|
|
67
|
+
checkedAt: new Date().toISOString(),
|
|
68
|
+
message: "Meta Cloud API adapter is planned for a later Channels milestone.",
|
|
69
|
+
};
|
|
70
|
+
},
|
|
71
|
+
};
|