@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,5 +1,7 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
ChannelAdapter,
|
|
3
|
+
ChannelMediaDownloadInput,
|
|
4
|
+
ChannelMediaDownloadResult,
|
|
3
5
|
ChannelSendInput,
|
|
4
6
|
ChannelStatusResult,
|
|
5
7
|
RawWebhookEvent,
|
|
@@ -86,13 +88,37 @@ export const telegramChannelAdapter: ChannelAdapter = {
|
|
|
86
88
|
|
|
87
89
|
const text = readString(message, "text");
|
|
88
90
|
|
|
89
|
-
if (
|
|
91
|
+
if (text) {
|
|
90
92
|
return [
|
|
91
93
|
{
|
|
92
94
|
...base,
|
|
93
|
-
kind: "
|
|
95
|
+
kind: "message",
|
|
96
|
+
externalIdentity: identity,
|
|
97
|
+
message: {
|
|
98
|
+
role: "user",
|
|
99
|
+
contentType: "text",
|
|
100
|
+
content: text,
|
|
101
|
+
providerMessageId: readNumberishString(message, "message_id"),
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
];
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const audio = normalizeTelegramAudio(message);
|
|
108
|
+
|
|
109
|
+
if (audio) {
|
|
110
|
+
return [
|
|
111
|
+
{
|
|
112
|
+
...base,
|
|
113
|
+
kind: "message",
|
|
94
114
|
externalIdentity: identity,
|
|
95
|
-
|
|
115
|
+
message: {
|
|
116
|
+
role: "user",
|
|
117
|
+
contentType: "audio",
|
|
118
|
+
content: "",
|
|
119
|
+
providerMessageId: readNumberishString(message, "message_id"),
|
|
120
|
+
audio,
|
|
121
|
+
},
|
|
96
122
|
},
|
|
97
123
|
];
|
|
98
124
|
}
|
|
@@ -100,30 +126,17 @@ export const telegramChannelAdapter: ChannelAdapter = {
|
|
|
100
126
|
return [
|
|
101
127
|
{
|
|
102
128
|
...base,
|
|
103
|
-
kind: "
|
|
129
|
+
kind: "unsupported",
|
|
104
130
|
externalIdentity: identity,
|
|
105
|
-
|
|
106
|
-
role: "user",
|
|
107
|
-
contentType: "text",
|
|
108
|
-
content: text,
|
|
109
|
-
providerMessageId: readNumberishString(message, "message_id"),
|
|
110
|
-
},
|
|
131
|
+
unsupportedReason: "unsupported_message_type:non_text",
|
|
111
132
|
},
|
|
112
133
|
];
|
|
113
134
|
},
|
|
135
|
+
async downloadMedia(input) {
|
|
136
|
+
return downloadTelegramMedia(input);
|
|
137
|
+
},
|
|
114
138
|
async sendMessage(input) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
return {
|
|
118
|
-
ok: true,
|
|
119
|
-
status: "sent",
|
|
120
|
-
providerRequestId: request.idempotencyKey,
|
|
121
|
-
providerMetadata: {
|
|
122
|
-
method: request.method,
|
|
123
|
-
url: request.url,
|
|
124
|
-
body: request.body,
|
|
125
|
-
},
|
|
126
|
-
};
|
|
139
|
+
return sendTelegramMessage(input);
|
|
127
140
|
},
|
|
128
141
|
getStatus(input) {
|
|
129
142
|
if (!input.secrets.TELEGRAM_BOT_TOKEN) {
|
|
@@ -152,7 +165,289 @@ export type TelegramSendMessageRequest = {
|
|
|
152
165
|
idempotencyKey: string;
|
|
153
166
|
};
|
|
154
167
|
|
|
168
|
+
type TelegramFetch = (url: string, init: RequestInit) => Promise<Response>;
|
|
169
|
+
const TELEGRAM_MAX_DOWNLOAD_BYTES = 20_000_000;
|
|
170
|
+
|
|
155
171
|
export function buildTelegramSendMessageRequest(input: ChannelSendInput): TelegramSendMessageRequest {
|
|
172
|
+
return buildTelegramSendMessageRequestInternal(input, "<redacted>");
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export async function sendTelegramMessage(
|
|
176
|
+
input: ChannelSendInput,
|
|
177
|
+
fetcher: TelegramFetch = fetch,
|
|
178
|
+
): Promise<Awaited<ReturnType<ChannelAdapter["sendMessage"]>>> {
|
|
179
|
+
if (input.secrets.AGENTKIT_CHANNEL_SEND_DRY_RUN === "1") {
|
|
180
|
+
const request = buildTelegramSendMessageRequest(input);
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
ok: true,
|
|
184
|
+
status: "stubbed",
|
|
185
|
+
providerRequestId: request.idempotencyKey,
|
|
186
|
+
providerMetadata: {
|
|
187
|
+
method: request.method,
|
|
188
|
+
url: request.url,
|
|
189
|
+
body: request.body,
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const token = input.secrets.TELEGRAM_BOT_TOKEN;
|
|
195
|
+
|
|
196
|
+
if (!token) {
|
|
197
|
+
return {
|
|
198
|
+
ok: false,
|
|
199
|
+
retryable: false,
|
|
200
|
+
code: "channel_secret_missing",
|
|
201
|
+
message: "Secret TELEGRAM_BOT_TOKEN is not set for this channel.",
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
let request: TelegramSendMessageRequest;
|
|
206
|
+
let actualRequest: TelegramSendMessageRequest;
|
|
207
|
+
|
|
208
|
+
try {
|
|
209
|
+
request = buildTelegramSendMessageRequest(input);
|
|
210
|
+
actualRequest = buildTelegramSendMessageRequestInternal(input, token);
|
|
211
|
+
} catch (error) {
|
|
212
|
+
return {
|
|
213
|
+
ok: false,
|
|
214
|
+
retryable: false,
|
|
215
|
+
code: "channel_send_failed",
|
|
216
|
+
message: error instanceof Error ? error.message : String(error),
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
let response: Response;
|
|
221
|
+
|
|
222
|
+
try {
|
|
223
|
+
response = await fetcher(actualRequest.url, {
|
|
224
|
+
method: actualRequest.method,
|
|
225
|
+
headers: {
|
|
226
|
+
"Content-Type": "application/json",
|
|
227
|
+
},
|
|
228
|
+
body: JSON.stringify(actualRequest.body),
|
|
229
|
+
});
|
|
230
|
+
} catch (error) {
|
|
231
|
+
const errorMessage = (error instanceof Error ? error.message : String(error)).replace(token, "<redacted>");
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
ok: false,
|
|
235
|
+
retryable: true,
|
|
236
|
+
code: "channel_provider_unavailable",
|
|
237
|
+
message: `Telegram sendMessage request failed before a response: ${errorMessage}`,
|
|
238
|
+
providerRequestId: request.idempotencyKey,
|
|
239
|
+
providerMetadata: {
|
|
240
|
+
method: request.method,
|
|
241
|
+
url: request.url,
|
|
242
|
+
body: request.body,
|
|
243
|
+
},
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const payload = await response.json().catch(() => null);
|
|
248
|
+
|
|
249
|
+
if (!response.ok || !isRecord(payload) || payload.ok !== true) {
|
|
250
|
+
const description =
|
|
251
|
+
isRecord(payload) && typeof payload.description === "string"
|
|
252
|
+
? payload.description
|
|
253
|
+
: `Telegram sendMessage returned HTTP ${response.status}.`;
|
|
254
|
+
const syntheticExpectedFailure = isSyntheticTelegramChatId(request.body.chat_id) && /chat not found/i.test(description);
|
|
255
|
+
|
|
256
|
+
return {
|
|
257
|
+
ok: false,
|
|
258
|
+
retryable: syntheticExpectedFailure ? false : response.status === 429 || response.status >= 500,
|
|
259
|
+
code: syntheticExpectedFailure ? "synthetic_expected_failure" : "channel_send_failed",
|
|
260
|
+
message: syntheticExpectedFailure
|
|
261
|
+
? "Synthetic Telegram smoke reached AgentKit. Telegram correctly rejected the fake chat ID."
|
|
262
|
+
: description,
|
|
263
|
+
providerRequestId: request.idempotencyKey,
|
|
264
|
+
providerMetadata: {
|
|
265
|
+
method: request.method,
|
|
266
|
+
url: request.url,
|
|
267
|
+
body: request.body,
|
|
268
|
+
},
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const result = isRecord(payload.result) ? payload.result : undefined;
|
|
273
|
+
const messageId = readNumberishString(result ?? {}, "message_id");
|
|
274
|
+
|
|
275
|
+
if (!messageId) {
|
|
276
|
+
return {
|
|
277
|
+
ok: false,
|
|
278
|
+
retryable: false,
|
|
279
|
+
code: "channel_send_failed",
|
|
280
|
+
message: "Telegram sendMessage did not return result.message_id.",
|
|
281
|
+
providerRequestId: request.idempotencyKey,
|
|
282
|
+
providerMetadata: {
|
|
283
|
+
method: request.method,
|
|
284
|
+
url: request.url,
|
|
285
|
+
body: request.body,
|
|
286
|
+
},
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return {
|
|
291
|
+
ok: true,
|
|
292
|
+
status: "sent",
|
|
293
|
+
providerRequestId: request.idempotencyKey,
|
|
294
|
+
providerMessageId: messageId,
|
|
295
|
+
providerMetadata: {
|
|
296
|
+
method: request.method,
|
|
297
|
+
url: request.url,
|
|
298
|
+
body: request.body,
|
|
299
|
+
},
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
export async function downloadTelegramMedia(
|
|
304
|
+
input: ChannelMediaDownloadInput,
|
|
305
|
+
fetcher: TelegramFetch = fetch,
|
|
306
|
+
): Promise<ChannelMediaDownloadResult> {
|
|
307
|
+
const token = input.secrets.TELEGRAM_BOT_TOKEN;
|
|
308
|
+
|
|
309
|
+
if (!token) {
|
|
310
|
+
return {
|
|
311
|
+
ok: false,
|
|
312
|
+
retryable: false,
|
|
313
|
+
code: "channel_secret_missing",
|
|
314
|
+
message: "Secret TELEGRAM_BOT_TOKEN is not set for this channel.",
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const audio = input.message.audio;
|
|
319
|
+
|
|
320
|
+
if (!audio?.providerMediaId) {
|
|
321
|
+
return {
|
|
322
|
+
ok: false,
|
|
323
|
+
retryable: false,
|
|
324
|
+
code: "channel_audio_download_unavailable",
|
|
325
|
+
message: "Telegram audio message is missing file_id.",
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
let fileResponse: Response;
|
|
330
|
+
|
|
331
|
+
try {
|
|
332
|
+
const url = new URL(`/bot${token}/getFile`, telegramApiBaseUrl(input.secrets));
|
|
333
|
+
url.searchParams.set("file_id", audio.providerMediaId);
|
|
334
|
+
fileResponse = await fetcher(url.href, { method: "GET" });
|
|
335
|
+
} catch (error) {
|
|
336
|
+
return {
|
|
337
|
+
ok: false,
|
|
338
|
+
retryable: true,
|
|
339
|
+
code: "channel_provider_unavailable",
|
|
340
|
+
message: `Telegram getFile request failed before a response: ${redactToken(
|
|
341
|
+
error instanceof Error ? error.message : String(error),
|
|
342
|
+
token,
|
|
343
|
+
)}`,
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const filePayload = await fileResponse.json().catch(() => null);
|
|
348
|
+
|
|
349
|
+
if (!fileResponse.ok || !isRecord(filePayload) || filePayload.ok !== true || !isRecord(filePayload.result)) {
|
|
350
|
+
const description =
|
|
351
|
+
isRecord(filePayload) && typeof filePayload.description === "string"
|
|
352
|
+
? filePayload.description
|
|
353
|
+
: `Telegram getFile returned HTTP ${fileResponse.status}.`;
|
|
354
|
+
|
|
355
|
+
return {
|
|
356
|
+
ok: false,
|
|
357
|
+
retryable: fileResponse.status === 429 || fileResponse.status >= 500,
|
|
358
|
+
code: fileResponse.status === 429 || fileResponse.status >= 500
|
|
359
|
+
? "channel_provider_unavailable"
|
|
360
|
+
: "channel_media_download_failed",
|
|
361
|
+
message: redactToken(description, token),
|
|
362
|
+
providerMetadata: {
|
|
363
|
+
status: fileResponse.status,
|
|
364
|
+
},
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const filePath = readString(filePayload.result, "file_path");
|
|
369
|
+
|
|
370
|
+
if (!filePath) {
|
|
371
|
+
return {
|
|
372
|
+
ok: false,
|
|
373
|
+
retryable: false,
|
|
374
|
+
code: "channel_media_download_failed",
|
|
375
|
+
message: "Telegram getFile did not return result.file_path.",
|
|
376
|
+
providerMetadata: {
|
|
377
|
+
status: fileResponse.status,
|
|
378
|
+
},
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const maxDownloadBytes = Math.min(input.maxBytes ?? TELEGRAM_MAX_DOWNLOAD_BYTES, TELEGRAM_MAX_DOWNLOAD_BYTES);
|
|
383
|
+
const fileSize = readNumber(filePayload.result, "file_size") ?? audio.sizeBytes;
|
|
384
|
+
|
|
385
|
+
if (fileSize !== undefined && fileSize > maxDownloadBytes) {
|
|
386
|
+
return {
|
|
387
|
+
ok: false,
|
|
388
|
+
retryable: false,
|
|
389
|
+
code: "channel_audio_too_large",
|
|
390
|
+
message: `Telegram audio is ${fileSize} bytes, which exceeds the configured ${maxDownloadBytes} byte limit.`,
|
|
391
|
+
providerMetadata: {
|
|
392
|
+
sizeBytes: fileSize,
|
|
393
|
+
},
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
let mediaResponse: Response;
|
|
398
|
+
|
|
399
|
+
try {
|
|
400
|
+
mediaResponse = await fetcher(`${telegramApiBaseUrl(input.secrets)}/file/bot${token}/${filePath}`, { method: "GET" });
|
|
401
|
+
} catch (error) {
|
|
402
|
+
return {
|
|
403
|
+
ok: false,
|
|
404
|
+
retryable: true,
|
|
405
|
+
code: "channel_provider_unavailable",
|
|
406
|
+
message: `Telegram file download failed before a response: ${redactToken(
|
|
407
|
+
error instanceof Error ? error.message : String(error),
|
|
408
|
+
token,
|
|
409
|
+
)}`,
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
if (!mediaResponse.ok) {
|
|
414
|
+
return {
|
|
415
|
+
ok: false,
|
|
416
|
+
retryable: mediaResponse.status === 429 || mediaResponse.status >= 500,
|
|
417
|
+
code: mediaResponse.status === 429 || mediaResponse.status >= 500
|
|
418
|
+
? "channel_provider_unavailable"
|
|
419
|
+
: "channel_media_download_failed",
|
|
420
|
+
message: `Telegram file download returned HTTP ${mediaResponse.status}.`,
|
|
421
|
+
providerMetadata: {
|
|
422
|
+
status: mediaResponse.status,
|
|
423
|
+
},
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const bytes = await readResponseBytesWithLimit(mediaResponse, maxDownloadBytes);
|
|
428
|
+
|
|
429
|
+
if (!bytes.ok) {
|
|
430
|
+
return bytes;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
return {
|
|
434
|
+
ok: true,
|
|
435
|
+
audio: bytes.audio,
|
|
436
|
+
filename: audio.filename ?? basename(filePath) ?? `${audio.providerMediaId}.ogg`,
|
|
437
|
+
...(audio.mimeType ? { mimeType: audio.mimeType } : {}),
|
|
438
|
+
...(audio.durationSeconds !== undefined ? { durationSeconds: audio.durationSeconds } : {}),
|
|
439
|
+
sizeBytes: bytes.audio.byteLength,
|
|
440
|
+
providerMetadata: {
|
|
441
|
+
fileSize: fileSize ?? bytes.audio.byteLength,
|
|
442
|
+
},
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function isSyntheticTelegramChatId(chatId: string): boolean {
|
|
447
|
+
return chatId === "456" || chatId.startsWith("agentkit-synthetic-");
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function buildTelegramSendMessageRequestInternal(input: ChannelSendInput, token: string): TelegramSendMessageRequest {
|
|
156
451
|
const chatId = input.externalIdentity.key.match(/:chat:([^:]+)$/)?.[1];
|
|
157
452
|
|
|
158
453
|
if (!chatId) {
|
|
@@ -161,7 +456,7 @@ export function buildTelegramSendMessageRequest(input: ChannelSendInput): Telegr
|
|
|
161
456
|
|
|
162
457
|
return {
|
|
163
458
|
method: "POST",
|
|
164
|
-
url:
|
|
459
|
+
url: `https://api.telegram.org/bot${token}/sendMessage`,
|
|
165
460
|
body: {
|
|
166
461
|
chat_id: chatId,
|
|
167
462
|
text: input.message.content,
|
|
@@ -170,6 +465,48 @@ export function buildTelegramSendMessageRequest(input: ChannelSendInput): Telegr
|
|
|
170
465
|
};
|
|
171
466
|
}
|
|
172
467
|
|
|
468
|
+
function normalizeTelegramAudio(message: Record<string, unknown>) {
|
|
469
|
+
const voice = readRecord(message, "voice");
|
|
470
|
+
|
|
471
|
+
if (Object.keys(voice).length > 0) {
|
|
472
|
+
const fileId = readString(voice, "file_id");
|
|
473
|
+
|
|
474
|
+
if (!fileId) {
|
|
475
|
+
return null;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
return {
|
|
479
|
+
providerMediaId: fileId,
|
|
480
|
+
kind: "voice" as const,
|
|
481
|
+
mimeType: readString(voice, "mime_type") ?? "audio/ogg",
|
|
482
|
+
filename: `${fileId}.ogg`,
|
|
483
|
+
...(readNumber(voice, "duration") !== undefined ? { durationSeconds: readNumber(voice, "duration") } : {}),
|
|
484
|
+
...(readNumber(voice, "file_size") !== undefined ? { sizeBytes: readNumber(voice, "file_size") } : {}),
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
const audio = readRecord(message, "audio");
|
|
489
|
+
|
|
490
|
+
if (Object.keys(audio).length > 0) {
|
|
491
|
+
const fileId = readString(audio, "file_id");
|
|
492
|
+
|
|
493
|
+
if (!fileId) {
|
|
494
|
+
return null;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
return {
|
|
498
|
+
providerMediaId: fileId,
|
|
499
|
+
kind: "audio" as const,
|
|
500
|
+
...(readString(audio, "mime_type") ? { mimeType: readString(audio, "mime_type") } : {}),
|
|
501
|
+
filename: readString(audio, "file_name") ?? `${fileId}.mp3`,
|
|
502
|
+
...(readNumber(audio, "duration") !== undefined ? { durationSeconds: readNumber(audio, "duration") } : {}),
|
|
503
|
+
...(readNumber(audio, "file_size") !== undefined ? { sizeBytes: readNumber(audio, "file_size") } : {}),
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
return null;
|
|
508
|
+
}
|
|
509
|
+
|
|
173
510
|
export function parseTelegramWebhookInfo(input: unknown, checkedAt: string): ChannelStatusResult {
|
|
174
511
|
if (!isRecord(input) || input.ok !== true || !isRecord(input.result)) {
|
|
175
512
|
return {
|
|
@@ -235,6 +572,112 @@ function readNumberishString(value: Record<string, unknown>, key: string): strin
|
|
|
235
572
|
return undefined;
|
|
236
573
|
}
|
|
237
574
|
|
|
575
|
+
function telegramApiBaseUrl(env: Record<string, string | undefined>): string {
|
|
576
|
+
return env.AGENTKIT_TELEGRAM_API_BASE_URL ?? "https://api.telegram.org";
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
function basename(path: string): string | null {
|
|
580
|
+
const name = path.split("/").filter(Boolean).at(-1);
|
|
581
|
+
return name && name.length > 0 ? name : null;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
async function readResponseBytesWithLimit(
|
|
585
|
+
response: Response,
|
|
586
|
+
maxBytes: number,
|
|
587
|
+
): Promise<
|
|
588
|
+
| {
|
|
589
|
+
ok: true;
|
|
590
|
+
audio: Uint8Array;
|
|
591
|
+
}
|
|
592
|
+
| {
|
|
593
|
+
ok: false;
|
|
594
|
+
retryable: false;
|
|
595
|
+
code: "channel_audio_too_large";
|
|
596
|
+
message: string;
|
|
597
|
+
providerMetadata?: Record<string, unknown>;
|
|
598
|
+
}
|
|
599
|
+
> {
|
|
600
|
+
const contentLength = response.headers.get("content-length");
|
|
601
|
+
const declaredBytes = contentLength ? Number(contentLength) : undefined;
|
|
602
|
+
|
|
603
|
+
if (declaredBytes !== undefined && Number.isFinite(declaredBytes) && declaredBytes > maxBytes) {
|
|
604
|
+
return {
|
|
605
|
+
ok: false,
|
|
606
|
+
retryable: false,
|
|
607
|
+
code: "channel_audio_too_large",
|
|
608
|
+
message: `Audio response declares ${declaredBytes} bytes, which exceeds the configured ${maxBytes} byte limit.`,
|
|
609
|
+
providerMetadata: {
|
|
610
|
+
sizeBytes: declaredBytes,
|
|
611
|
+
},
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
if (!response.body) {
|
|
616
|
+
const audio = new Uint8Array(await response.arrayBuffer());
|
|
617
|
+
|
|
618
|
+
if (audio.byteLength > maxBytes) {
|
|
619
|
+
return {
|
|
620
|
+
ok: false,
|
|
621
|
+
retryable: false,
|
|
622
|
+
code: "channel_audio_too_large",
|
|
623
|
+
message: `Audio response is ${audio.byteLength} bytes, which exceeds the configured ${maxBytes} byte limit.`,
|
|
624
|
+
providerMetadata: {
|
|
625
|
+
sizeBytes: audio.byteLength,
|
|
626
|
+
},
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
return { ok: true, audio };
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
const reader = response.body.getReader();
|
|
634
|
+
const chunks: Uint8Array[] = [];
|
|
635
|
+
let totalBytes = 0;
|
|
636
|
+
|
|
637
|
+
try {
|
|
638
|
+
while (true) {
|
|
639
|
+
const chunk = await reader.read();
|
|
640
|
+
|
|
641
|
+
if (chunk.done) {
|
|
642
|
+
break;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
totalBytes += chunk.value.byteLength;
|
|
646
|
+
|
|
647
|
+
if (totalBytes > maxBytes) {
|
|
648
|
+
await reader.cancel().catch(() => undefined);
|
|
649
|
+
return {
|
|
650
|
+
ok: false,
|
|
651
|
+
retryable: false,
|
|
652
|
+
code: "channel_audio_too_large",
|
|
653
|
+
message: `Audio response exceeded the configured ${maxBytes} byte limit while downloading.`,
|
|
654
|
+
providerMetadata: {
|
|
655
|
+
sizeBytes: totalBytes,
|
|
656
|
+
},
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
chunks.push(chunk.value);
|
|
661
|
+
}
|
|
662
|
+
} finally {
|
|
663
|
+
reader.releaseLock();
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
const audio = new Uint8Array(totalBytes);
|
|
667
|
+
let offset = 0;
|
|
668
|
+
|
|
669
|
+
for (const chunk of chunks) {
|
|
670
|
+
audio.set(chunk, offset);
|
|
671
|
+
offset += chunk.byteLength;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
return { ok: true, audio };
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
function redactToken(value: string, token: string): string {
|
|
678
|
+
return token ? value.replaceAll(token, "<redacted>") : value;
|
|
679
|
+
}
|
|
680
|
+
|
|
238
681
|
function redactId(value: string): string {
|
|
239
682
|
if (value.length <= 2) {
|
|
240
683
|
return "*".repeat(value.length);
|