@andreprado/agentkit 0.1.0-alpha.9 → 0.1.1
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 +856 -36
- 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 +67 -9
- 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 +904 -0
- package/src/runtime/channels/generic-webhook.ts +682 -0
- package/src/runtime/channels/net-guard.ts +480 -0
- package/src/runtime/channels/provider-fetch.ts +54 -0
- package/src/runtime/channels/slack.ts +652 -0
- package/src/runtime/channels/telegram.ts +379 -15
- package/src/runtime/channels/whatsapp-evolution.ts +1330 -0
- package/src/runtime/channels/whatsapp-meta.ts +9 -0
- package/src/runtime/channels/whatsapp-uazapi.ts +1192 -0
- package/src/runtime/channels/whatsapp-zapster.ts +702 -40
- package/src/runtime/channels.ts +83 -3
- package/src/runtime/chat.ts +70 -44
- package/src/runtime/config.ts +512 -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/embeddings.ts +45 -7
- package/src/runtime/knowledge/ingest.ts +69 -6
- package/src/runtime/knowledge/retrieve.ts +25 -5
- package/src/runtime/knowledge/schema.ts +45 -1
- package/src/runtime/knowledge/vector.ts +30 -30
- 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 +66 -1
- package/src/templates/skills/agentkit-channels/references/channel-buffering.md +8 -1
- package/src/templates/skills/agentkit-channels/references/channel-debugging.md +28 -3
- 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 +34 -0
- 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,19 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
ChannelAdapter,
|
|
3
|
+
ChannelMediaDownloadInput,
|
|
4
|
+
ChannelMediaDownloadResult,
|
|
3
5
|
ChannelSendInput,
|
|
4
6
|
ChannelStatusResult,
|
|
5
7
|
RawWebhookEvent,
|
|
6
8
|
WebhookVerificationResult,
|
|
7
9
|
} from "../channels";
|
|
8
10
|
import { AgentKitError } from "../errors";
|
|
11
|
+
import {
|
|
12
|
+
CHANNEL_DOWNLOAD_TIMEOUT_MS,
|
|
13
|
+
CHANNEL_SEND_TIMEOUT_MS,
|
|
14
|
+
classifySendRequestError,
|
|
15
|
+
isRetryableReadRequestError,
|
|
16
|
+
} from "./provider-fetch";
|
|
9
17
|
|
|
10
18
|
export const telegramChannelAdapter: ChannelAdapter = {
|
|
11
19
|
type: "telegram",
|
|
@@ -86,13 +94,37 @@ export const telegramChannelAdapter: ChannelAdapter = {
|
|
|
86
94
|
|
|
87
95
|
const text = readString(message, "text");
|
|
88
96
|
|
|
89
|
-
if (
|
|
97
|
+
if (text) {
|
|
90
98
|
return [
|
|
91
99
|
{
|
|
92
100
|
...base,
|
|
93
|
-
kind: "
|
|
101
|
+
kind: "message",
|
|
94
102
|
externalIdentity: identity,
|
|
95
|
-
|
|
103
|
+
message: {
|
|
104
|
+
role: "user",
|
|
105
|
+
contentType: "text",
|
|
106
|
+
content: text,
|
|
107
|
+
providerMessageId: readNumberishString(message, "message_id"),
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
];
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const audio = normalizeTelegramAudio(message);
|
|
114
|
+
|
|
115
|
+
if (audio) {
|
|
116
|
+
return [
|
|
117
|
+
{
|
|
118
|
+
...base,
|
|
119
|
+
kind: "message",
|
|
120
|
+
externalIdentity: identity,
|
|
121
|
+
message: {
|
|
122
|
+
role: "user",
|
|
123
|
+
contentType: "audio",
|
|
124
|
+
content: "",
|
|
125
|
+
providerMessageId: readNumberishString(message, "message_id"),
|
|
126
|
+
audio,
|
|
127
|
+
},
|
|
96
128
|
},
|
|
97
129
|
];
|
|
98
130
|
}
|
|
@@ -100,17 +132,15 @@ export const telegramChannelAdapter: ChannelAdapter = {
|
|
|
100
132
|
return [
|
|
101
133
|
{
|
|
102
134
|
...base,
|
|
103
|
-
kind: "
|
|
135
|
+
kind: "unsupported",
|
|
104
136
|
externalIdentity: identity,
|
|
105
|
-
|
|
106
|
-
role: "user",
|
|
107
|
-
contentType: "text",
|
|
108
|
-
content: text,
|
|
109
|
-
providerMessageId: readNumberishString(message, "message_id"),
|
|
110
|
-
},
|
|
137
|
+
unsupportedReason: "unsupported_message_type:non_text",
|
|
111
138
|
},
|
|
112
139
|
];
|
|
113
140
|
},
|
|
141
|
+
async downloadMedia(input) {
|
|
142
|
+
return downloadTelegramMedia(input);
|
|
143
|
+
},
|
|
114
144
|
async sendMessage(input) {
|
|
115
145
|
return sendTelegramMessage(input);
|
|
116
146
|
},
|
|
@@ -142,6 +172,7 @@ export type TelegramSendMessageRequest = {
|
|
|
142
172
|
};
|
|
143
173
|
|
|
144
174
|
type TelegramFetch = (url: string, init: RequestInit) => Promise<Response>;
|
|
175
|
+
const TELEGRAM_MAX_DOWNLOAD_BYTES = 20_000_000;
|
|
145
176
|
|
|
146
177
|
export function buildTelegramSendMessageRequest(input: ChannelSendInput): TelegramSendMessageRequest {
|
|
147
178
|
return buildTelegramSendMessageRequestInternal(input, "<redacted>");
|
|
@@ -156,7 +187,7 @@ export async function sendTelegramMessage(
|
|
|
156
187
|
|
|
157
188
|
return {
|
|
158
189
|
ok: true,
|
|
159
|
-
status: "
|
|
190
|
+
status: "stubbed",
|
|
160
191
|
providerRequestId: request.idempotencyKey,
|
|
161
192
|
providerMetadata: {
|
|
162
193
|
method: request.method,
|
|
@@ -196,6 +227,7 @@ export async function sendTelegramMessage(
|
|
|
196
227
|
|
|
197
228
|
try {
|
|
198
229
|
response = await fetcher(actualRequest.url, {
|
|
230
|
+
signal: AbortSignal.timeout(CHANNEL_SEND_TIMEOUT_MS),
|
|
199
231
|
method: actualRequest.method,
|
|
200
232
|
headers: {
|
|
201
233
|
"Content-Type": "application/json",
|
|
@@ -203,13 +235,14 @@ export async function sendTelegramMessage(
|
|
|
203
235
|
body: JSON.stringify(actualRequest.body),
|
|
204
236
|
});
|
|
205
237
|
} catch (error) {
|
|
238
|
+
const failure = classifySendRequestError(error);
|
|
206
239
|
const errorMessage = (error instanceof Error ? error.message : String(error)).replace(token, "<redacted>");
|
|
207
240
|
|
|
208
241
|
return {
|
|
209
242
|
ok: false,
|
|
210
|
-
retryable:
|
|
211
|
-
code:
|
|
212
|
-
message: `Telegram sendMessage request
|
|
243
|
+
retryable: failure.retryable,
|
|
244
|
+
code: failure.code,
|
|
245
|
+
message: `Telegram sendMessage request ${failure.reason}: ${errorMessage}`,
|
|
213
246
|
providerRequestId: request.idempotencyKey,
|
|
214
247
|
providerMetadata: {
|
|
215
248
|
method: request.method,
|
|
@@ -247,11 +280,26 @@ export async function sendTelegramMessage(
|
|
|
247
280
|
const result = isRecord(payload.result) ? payload.result : undefined;
|
|
248
281
|
const messageId = readNumberishString(result ?? {}, "message_id");
|
|
249
282
|
|
|
283
|
+
if (!messageId) {
|
|
284
|
+
return {
|
|
285
|
+
ok: false,
|
|
286
|
+
retryable: false,
|
|
287
|
+
code: "channel_send_failed",
|
|
288
|
+
message: "Telegram sendMessage did not return result.message_id.",
|
|
289
|
+
providerRequestId: request.idempotencyKey,
|
|
290
|
+
providerMetadata: {
|
|
291
|
+
method: request.method,
|
|
292
|
+
url: request.url,
|
|
293
|
+
body: request.body,
|
|
294
|
+
},
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
250
298
|
return {
|
|
251
299
|
ok: true,
|
|
252
300
|
status: "sent",
|
|
253
301
|
providerRequestId: request.idempotencyKey,
|
|
254
|
-
|
|
302
|
+
providerMessageId: messageId,
|
|
255
303
|
providerMetadata: {
|
|
256
304
|
method: request.method,
|
|
257
305
|
url: request.url,
|
|
@@ -260,6 +308,155 @@ export async function sendTelegramMessage(
|
|
|
260
308
|
};
|
|
261
309
|
}
|
|
262
310
|
|
|
311
|
+
export async function downloadTelegramMedia(
|
|
312
|
+
input: ChannelMediaDownloadInput,
|
|
313
|
+
fetcher: TelegramFetch = fetch,
|
|
314
|
+
): Promise<ChannelMediaDownloadResult> {
|
|
315
|
+
const token = input.secrets.TELEGRAM_BOT_TOKEN;
|
|
316
|
+
|
|
317
|
+
if (!token) {
|
|
318
|
+
return {
|
|
319
|
+
ok: false,
|
|
320
|
+
retryable: false,
|
|
321
|
+
code: "channel_secret_missing",
|
|
322
|
+
message: "Secret TELEGRAM_BOT_TOKEN is not set for this channel.",
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const audio = input.message.audio;
|
|
327
|
+
|
|
328
|
+
if (!audio?.providerMediaId) {
|
|
329
|
+
return {
|
|
330
|
+
ok: false,
|
|
331
|
+
retryable: false,
|
|
332
|
+
code: "channel_audio_download_unavailable",
|
|
333
|
+
message: "Telegram audio message is missing file_id.",
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
let fileResponse: Response;
|
|
338
|
+
|
|
339
|
+
try {
|
|
340
|
+
const url = new URL(`/bot${token}/getFile`, telegramApiBaseUrl(input.secrets));
|
|
341
|
+
url.searchParams.set("file_id", audio.providerMediaId);
|
|
342
|
+
fileResponse = await fetcher(url.href, {
|
|
343
|
+
method: "GET",
|
|
344
|
+
signal: AbortSignal.timeout(CHANNEL_DOWNLOAD_TIMEOUT_MS),
|
|
345
|
+
});
|
|
346
|
+
} catch (error) {
|
|
347
|
+
return {
|
|
348
|
+
ok: false,
|
|
349
|
+
retryable: isRetryableReadRequestError(error),
|
|
350
|
+
code: "channel_provider_unavailable",
|
|
351
|
+
message: `Telegram getFile request failed before a response: ${redactToken(
|
|
352
|
+
error instanceof Error ? error.message : String(error),
|
|
353
|
+
token,
|
|
354
|
+
)}`,
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const filePayload = await fileResponse.json().catch(() => null);
|
|
359
|
+
|
|
360
|
+
if (!fileResponse.ok || !isRecord(filePayload) || filePayload.ok !== true || !isRecord(filePayload.result)) {
|
|
361
|
+
const description =
|
|
362
|
+
isRecord(filePayload) && typeof filePayload.description === "string"
|
|
363
|
+
? filePayload.description
|
|
364
|
+
: `Telegram getFile returned HTTP ${fileResponse.status}.`;
|
|
365
|
+
|
|
366
|
+
return {
|
|
367
|
+
ok: false,
|
|
368
|
+
retryable: fileResponse.status === 429 || fileResponse.status >= 500,
|
|
369
|
+
code: fileResponse.status === 429 || fileResponse.status >= 500
|
|
370
|
+
? "channel_provider_unavailable"
|
|
371
|
+
: "channel_media_download_failed",
|
|
372
|
+
message: redactToken(description, token),
|
|
373
|
+
providerMetadata: {
|
|
374
|
+
status: fileResponse.status,
|
|
375
|
+
},
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const filePath = readString(filePayload.result, "file_path");
|
|
380
|
+
|
|
381
|
+
if (!filePath) {
|
|
382
|
+
return {
|
|
383
|
+
ok: false,
|
|
384
|
+
retryable: false,
|
|
385
|
+
code: "channel_media_download_failed",
|
|
386
|
+
message: "Telegram getFile did not return result.file_path.",
|
|
387
|
+
providerMetadata: {
|
|
388
|
+
status: fileResponse.status,
|
|
389
|
+
},
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
const maxDownloadBytes = Math.min(input.maxBytes ?? TELEGRAM_MAX_DOWNLOAD_BYTES, TELEGRAM_MAX_DOWNLOAD_BYTES);
|
|
394
|
+
const fileSize = readNumber(filePayload.result, "file_size") ?? audio.sizeBytes;
|
|
395
|
+
|
|
396
|
+
if (fileSize !== undefined && fileSize > maxDownloadBytes) {
|
|
397
|
+
return {
|
|
398
|
+
ok: false,
|
|
399
|
+
retryable: false,
|
|
400
|
+
code: "channel_audio_too_large",
|
|
401
|
+
message: `Telegram audio is ${fileSize} bytes, which exceeds the configured ${maxDownloadBytes} byte limit.`,
|
|
402
|
+
providerMetadata: {
|
|
403
|
+
sizeBytes: fileSize,
|
|
404
|
+
},
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
let mediaResponse: Response;
|
|
409
|
+
|
|
410
|
+
try {
|
|
411
|
+
mediaResponse = await fetcher(`${telegramApiBaseUrl(input.secrets)}/file/bot${token}/${filePath}`, {
|
|
412
|
+
method: "GET",
|
|
413
|
+
signal: AbortSignal.timeout(CHANNEL_DOWNLOAD_TIMEOUT_MS),
|
|
414
|
+
});
|
|
415
|
+
} catch (error) {
|
|
416
|
+
return {
|
|
417
|
+
ok: false,
|
|
418
|
+
retryable: isRetryableReadRequestError(error),
|
|
419
|
+
code: "channel_provider_unavailable",
|
|
420
|
+
message: `Telegram file download failed before a response: ${redactToken(
|
|
421
|
+
error instanceof Error ? error.message : String(error),
|
|
422
|
+
token,
|
|
423
|
+
)}`,
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if (!mediaResponse.ok) {
|
|
428
|
+
return {
|
|
429
|
+
ok: false,
|
|
430
|
+
retryable: mediaResponse.status === 429 || mediaResponse.status >= 500,
|
|
431
|
+
code: mediaResponse.status === 429 || mediaResponse.status >= 500
|
|
432
|
+
? "channel_provider_unavailable"
|
|
433
|
+
: "channel_media_download_failed",
|
|
434
|
+
message: `Telegram file download returned HTTP ${mediaResponse.status}.`,
|
|
435
|
+
providerMetadata: {
|
|
436
|
+
status: mediaResponse.status,
|
|
437
|
+
},
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const bytes = await readResponseBytesWithLimit(mediaResponse, maxDownloadBytes);
|
|
442
|
+
|
|
443
|
+
if (!bytes.ok) {
|
|
444
|
+
return bytes;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
return {
|
|
448
|
+
ok: true,
|
|
449
|
+
audio: bytes.audio,
|
|
450
|
+
filename: audio.filename ?? basename(filePath) ?? `${audio.providerMediaId}.ogg`,
|
|
451
|
+
...(audio.mimeType ? { mimeType: audio.mimeType } : {}),
|
|
452
|
+
...(audio.durationSeconds !== undefined ? { durationSeconds: audio.durationSeconds } : {}),
|
|
453
|
+
sizeBytes: bytes.audio.byteLength,
|
|
454
|
+
providerMetadata: {
|
|
455
|
+
fileSize: fileSize ?? bytes.audio.byteLength,
|
|
456
|
+
},
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
|
|
263
460
|
function isSyntheticTelegramChatId(chatId: string): boolean {
|
|
264
461
|
return chatId === "456" || chatId.startsWith("agentkit-synthetic-");
|
|
265
462
|
}
|
|
@@ -282,6 +479,48 @@ function buildTelegramSendMessageRequestInternal(input: ChannelSendInput, token:
|
|
|
282
479
|
};
|
|
283
480
|
}
|
|
284
481
|
|
|
482
|
+
function normalizeTelegramAudio(message: Record<string, unknown>) {
|
|
483
|
+
const voice = readRecord(message, "voice");
|
|
484
|
+
|
|
485
|
+
if (Object.keys(voice).length > 0) {
|
|
486
|
+
const fileId = readString(voice, "file_id");
|
|
487
|
+
|
|
488
|
+
if (!fileId) {
|
|
489
|
+
return null;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
return {
|
|
493
|
+
providerMediaId: fileId,
|
|
494
|
+
kind: "voice" as const,
|
|
495
|
+
mimeType: readString(voice, "mime_type") ?? "audio/ogg",
|
|
496
|
+
filename: `${fileId}.ogg`,
|
|
497
|
+
...(readNumber(voice, "duration") !== undefined ? { durationSeconds: readNumber(voice, "duration") } : {}),
|
|
498
|
+
...(readNumber(voice, "file_size") !== undefined ? { sizeBytes: readNumber(voice, "file_size") } : {}),
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
const audio = readRecord(message, "audio");
|
|
503
|
+
|
|
504
|
+
if (Object.keys(audio).length > 0) {
|
|
505
|
+
const fileId = readString(audio, "file_id");
|
|
506
|
+
|
|
507
|
+
if (!fileId) {
|
|
508
|
+
return null;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
return {
|
|
512
|
+
providerMediaId: fileId,
|
|
513
|
+
kind: "audio" as const,
|
|
514
|
+
...(readString(audio, "mime_type") ? { mimeType: readString(audio, "mime_type") } : {}),
|
|
515
|
+
filename: readString(audio, "file_name") ?? `${fileId}.mp3`,
|
|
516
|
+
...(readNumber(audio, "duration") !== undefined ? { durationSeconds: readNumber(audio, "duration") } : {}),
|
|
517
|
+
...(readNumber(audio, "file_size") !== undefined ? { sizeBytes: readNumber(audio, "file_size") } : {}),
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
return null;
|
|
522
|
+
}
|
|
523
|
+
|
|
285
524
|
export function parseTelegramWebhookInfo(input: unknown, checkedAt: string): ChannelStatusResult {
|
|
286
525
|
if (!isRecord(input) || input.ok !== true || !isRecord(input.result)) {
|
|
287
526
|
return {
|
|
@@ -347,6 +586,131 @@ function readNumberishString(value: Record<string, unknown>, key: string): strin
|
|
|
347
586
|
return undefined;
|
|
348
587
|
}
|
|
349
588
|
|
|
589
|
+
function telegramApiBaseUrl(env: Record<string, string | undefined>): string {
|
|
590
|
+
return env.AGENTKIT_TELEGRAM_API_BASE_URL ?? "https://api.telegram.org";
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
function basename(path: string): string | null {
|
|
594
|
+
const name = path.split("/").filter(Boolean).at(-1);
|
|
595
|
+
return name && name.length > 0 ? name : null;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
async function readResponseBytesWithLimit(
|
|
599
|
+
response: Response,
|
|
600
|
+
maxBytes: number,
|
|
601
|
+
): Promise<
|
|
602
|
+
| {
|
|
603
|
+
ok: true;
|
|
604
|
+
audio: Uint8Array;
|
|
605
|
+
}
|
|
606
|
+
| {
|
|
607
|
+
ok: false;
|
|
608
|
+
retryable: boolean;
|
|
609
|
+
code: "channel_audio_too_large" | "channel_provider_unavailable";
|
|
610
|
+
message: string;
|
|
611
|
+
providerMetadata?: Record<string, unknown>;
|
|
612
|
+
}
|
|
613
|
+
> {
|
|
614
|
+
const contentLength = response.headers.get("content-length");
|
|
615
|
+
const declaredBytes = contentLength ? Number(contentLength) : undefined;
|
|
616
|
+
|
|
617
|
+
if (declaredBytes !== undefined && Number.isFinite(declaredBytes) && declaredBytes > maxBytes) {
|
|
618
|
+
return {
|
|
619
|
+
ok: false,
|
|
620
|
+
retryable: false,
|
|
621
|
+
code: "channel_audio_too_large",
|
|
622
|
+
message: `Audio response declares ${declaredBytes} bytes, which exceeds the configured ${maxBytes} byte limit.`,
|
|
623
|
+
providerMetadata: {
|
|
624
|
+
sizeBytes: declaredBytes,
|
|
625
|
+
},
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
if (!response.body) {
|
|
630
|
+
let audio: Uint8Array;
|
|
631
|
+
|
|
632
|
+
try {
|
|
633
|
+
audio = new Uint8Array(await response.arrayBuffer());
|
|
634
|
+
} catch (error) {
|
|
635
|
+
return {
|
|
636
|
+
ok: false,
|
|
637
|
+
retryable: true,
|
|
638
|
+
code: "channel_provider_unavailable",
|
|
639
|
+
message: `Media download response could not be read: ${error instanceof Error ? error.message : String(error)}`,
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
if (audio.byteLength > maxBytes) {
|
|
644
|
+
return {
|
|
645
|
+
ok: false,
|
|
646
|
+
retryable: false,
|
|
647
|
+
code: "channel_audio_too_large",
|
|
648
|
+
message: `Audio response is ${audio.byteLength} bytes, which exceeds the configured ${maxBytes} byte limit.`,
|
|
649
|
+
providerMetadata: {
|
|
650
|
+
sizeBytes: audio.byteLength,
|
|
651
|
+
},
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
return { ok: true, audio };
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
const reader = response.body.getReader();
|
|
659
|
+
const chunks: Uint8Array[] = [];
|
|
660
|
+
let totalBytes = 0;
|
|
661
|
+
|
|
662
|
+
try {
|
|
663
|
+
while (true) {
|
|
664
|
+
const chunk = await reader.read();
|
|
665
|
+
|
|
666
|
+
if (chunk.done) {
|
|
667
|
+
break;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
totalBytes += chunk.value.byteLength;
|
|
671
|
+
|
|
672
|
+
if (totalBytes > maxBytes) {
|
|
673
|
+
await reader.cancel().catch(() => undefined);
|
|
674
|
+
return {
|
|
675
|
+
ok: false,
|
|
676
|
+
retryable: false,
|
|
677
|
+
code: "channel_audio_too_large",
|
|
678
|
+
message: `Audio response exceeded the configured ${maxBytes} byte limit while downloading.`,
|
|
679
|
+
providerMetadata: {
|
|
680
|
+
sizeBytes: totalBytes,
|
|
681
|
+
},
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
chunks.push(chunk.value);
|
|
686
|
+
}
|
|
687
|
+
} catch (error) {
|
|
688
|
+
await reader.cancel().catch(() => undefined);
|
|
689
|
+
return {
|
|
690
|
+
ok: false,
|
|
691
|
+
retryable: true,
|
|
692
|
+
code: "channel_provider_unavailable",
|
|
693
|
+
message: `Media download response could not be read: ${error instanceof Error ? error.message : String(error)}`,
|
|
694
|
+
};
|
|
695
|
+
} finally {
|
|
696
|
+
reader.releaseLock();
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
const audio = new Uint8Array(totalBytes);
|
|
700
|
+
let offset = 0;
|
|
701
|
+
|
|
702
|
+
for (const chunk of chunks) {
|
|
703
|
+
audio.set(chunk, offset);
|
|
704
|
+
offset += chunk.byteLength;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
return { ok: true, audio };
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
function redactToken(value: string, token: string): string {
|
|
711
|
+
return token ? value.replaceAll(token, "<redacted>") : value;
|
|
712
|
+
}
|
|
713
|
+
|
|
350
714
|
function redactId(value: string): string {
|
|
351
715
|
if (value.length <= 2) {
|
|
352
716
|
return "*".repeat(value.length);
|