@andreprado/agentkit 0.1.0-alpha.9 → 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.
Files changed (117) hide show
  1. package/README.md +18 -1
  2. package/docs/guides/add-channel.md +251 -7
  3. package/docs/guides/add-knowledge.md +10 -0
  4. package/docs/guides/add-managed-composio.md +165 -0
  5. package/docs/guides/add-tool.md +10 -3
  6. package/docs/guides/channel-security.md +162 -32
  7. package/docs/guides/connect-discord.md +178 -0
  8. package/docs/guides/connect-slack.md +126 -0
  9. package/docs/guides/connect-telegram.md +61 -1
  10. package/docs/guides/connect-whatsapp-evolution.md +121 -0
  11. package/docs/guides/connect-whatsapp-uazapi.md +139 -0
  12. package/docs/guides/connect-whatsapp-zapster.md +119 -16
  13. package/docs/guides/create-agent.md +31 -4
  14. package/docs/guides/debug-channel.md +159 -0
  15. package/docs/guides/improve-from-production.md +151 -0
  16. package/docs/guides/prepare-deploy.md +32 -14
  17. package/docs/guides/replay-production-traces.md +72 -0
  18. package/docs/guides/run-evals.md +95 -25
  19. package/docs/guides/security-rules.md +9 -5
  20. package/docs/guides/send-feedback.md +135 -0
  21. package/docs/guides/use-jev.md +67 -0
  22. package/docs/guides/use-provider.md +70 -3
  23. package/docs/llms-full.txt +295 -25
  24. package/docs/llms.txt +54 -7
  25. package/package.json +3 -7
  26. package/src/cli/args.ts +23 -2
  27. package/src/cli/cloud-client.ts +121 -9
  28. package/src/cli/commands/channels.ts +856 -36
  29. package/src/cli/commands/feedback.ts +438 -0
  30. package/src/cli/commands/provider.ts +47 -0
  31. package/src/cli/commands/transcribe.ts +171 -0
  32. package/src/cli/deploy-chat-ui.ts +232 -18
  33. package/src/cli/deploy-readiness.ts +227 -14
  34. package/src/cli/help.ts +67 -9
  35. package/src/cli/index.ts +740 -35
  36. package/src/cli/new-command.ts +41 -0
  37. package/src/cloud/client.ts +4 -3
  38. package/src/cloud/contracts.ts +1 -1
  39. package/src/create-project.ts +18 -35
  40. package/src/index.ts +565 -11
  41. package/src/providers/codex-auth.ts +111 -0
  42. package/src/providers/pi.ts +88 -19
  43. package/src/providers/test.ts +36 -0
  44. package/src/providers/types.ts +8 -0
  45. package/src/runtime/channel-test-harness.ts +21 -1
  46. package/src/runtime/channels/discord.ts +896 -0
  47. package/src/runtime/channels/generic-webhook.ts +974 -0
  48. package/src/runtime/channels/slack.ts +646 -0
  49. package/src/runtime/channels/telegram.ts +343 -12
  50. package/src/runtime/channels/whatsapp-evolution.ts +1357 -0
  51. package/src/runtime/channels/whatsapp-meta.ts +9 -0
  52. package/src/runtime/channels/whatsapp-uazapi.ts +1323 -0
  53. package/src/runtime/channels/whatsapp-zapster.ts +674 -40
  54. package/src/runtime/channels.ts +83 -3
  55. package/src/runtime/chat.ts +70 -44
  56. package/src/runtime/config.ts +489 -20
  57. package/src/runtime/core/manifest.ts +75 -5
  58. package/src/runtime/core/targets.ts +5 -5
  59. package/src/runtime/deploy-readiness.ts +34 -4
  60. package/src/runtime/dev-server.ts +639 -39
  61. package/src/runtime/env.ts +8 -3
  62. package/src/runtime/evals.ts +445 -74
  63. package/src/runtime/improve.ts +868 -0
  64. package/src/runtime/inspect.ts +173 -4
  65. package/src/runtime/integrations/composio.ts +425 -0
  66. package/src/runtime/knowledge/retrieve.ts +25 -5
  67. package/src/runtime/knowledge/schema.ts +45 -1
  68. package/src/runtime/prompt-context.ts +141 -0
  69. package/src/runtime/runtime-contract.ts +71 -7
  70. package/src/runtime/skills.ts +95 -0
  71. package/src/runtime/targets/cloudflare/build.ts +1010 -208
  72. package/src/runtime/targets/container/server.ts +1 -1
  73. package/src/runtime/targets/vps/deploy.ts +26 -9
  74. package/src/runtime/tool-runner.ts +9 -1
  75. package/src/runtime/tools.ts +26 -2
  76. package/src/runtime/transcription.ts +483 -0
  77. package/src/storage/sqlite.ts +7 -2
  78. package/src/templates/blank.ts +37 -9
  79. package/src/templates/dentista.ts +40 -14
  80. package/src/templates/skills/agentkit-build-agent/SKILL.md +34 -5
  81. package/src/templates/skills/agentkit-build-agent/templates/appointment-intake.instructions.md +2 -1
  82. package/src/templates/skills/agentkit-capsule/SKILL.md +32 -3
  83. package/src/templates/skills/agentkit-capsule/references/docs-router.md +2 -2
  84. package/src/templates/skills/agentkit-channels/SKILL.md +66 -1
  85. package/src/templates/skills/agentkit-channels/references/channel-buffering.md +8 -1
  86. package/src/templates/skills/agentkit-channels/references/channel-debugging.md +28 -3
  87. package/src/templates/skills/agentkit-channels/references/discord.md +93 -0
  88. package/src/templates/skills/agentkit-channels/references/slack.md +56 -0
  89. package/src/templates/skills/agentkit-channels/references/telegram.md +34 -0
  90. package/src/templates/skills/agentkit-channels/references/whatsapp-evolution.md +57 -0
  91. package/src/templates/skills/agentkit-channels/references/whatsapp-uazapi.md +54 -0
  92. package/src/templates/skills/agentkit-channels/references/whatsapp-zapster.md +42 -8
  93. package/src/templates/skills/agentkit-database/SKILL.md +11 -0
  94. package/src/templates/skills/agentkit-deploy/SKILL.md +9 -1
  95. package/src/templates/skills/agentkit-evals/SKILL.md +77 -13
  96. package/src/templates/skills/agentkit-evals/templates/multi-turn.eval.md +13 -6
  97. package/src/templates/skills/agentkit-evals/templates/no-leak.eval.md +8 -4
  98. package/src/templates/skills/agentkit-evals/templates/smoke.eval.md +8 -4
  99. package/src/templates/skills/agentkit-evals/templates/tool-call.eval.md +16 -7
  100. package/src/templates/skills/agentkit-improve/SKILL.md +96 -0
  101. package/src/templates/skills/agentkit-improve/references/replay-side-effects.md +18 -0
  102. package/src/templates/skills/agentkit-improve/references/trace-packets.md +22 -0
  103. package/src/templates/skills/agentkit-improve/templates/regression.eval.md +18 -0
  104. package/src/templates/skills/agentkit-integrations/SKILL.md +98 -0
  105. package/src/templates/skills/agentkit-knowledge/SKILL.md +4 -1
  106. package/src/templates/skills/agentkit-prompts/SKILL.md +3 -1
  107. package/src/templates/skills/agentkit-provider/SKILL.md +29 -4
  108. package/src/templates/skills/agentkit-security/SKILL.md +5 -2
  109. package/src/templates/skills/agentkit-tools/SKILL.md +8 -1
  110. package/src/templates/skills/agentkit-tools/examples/eval-safe-external-action.tool.md +8 -8
  111. package/src/templates/skills/agentkit-tools/examples/jev-service-fit.tool.md +110 -0
  112. package/src/templates/skills/agentkit-troubleshooting/SKILL.md +25 -1
  113. package/src/templates/support.ts +42 -12
  114. package/docs/guides/agentkit-skills-architecture.md +0 -471
  115. package/docs/guides/channels-implementation-map.md +0 -243
  116. package/docs/guides/channels-production-handoff.md +0 -101
  117. 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 (!text) {
91
+ if (text) {
90
92
  return [
91
93
  {
92
94
  ...base,
93
- kind: "unsupported",
95
+ kind: "message",
94
96
  externalIdentity: identity,
95
- unsupportedReason: "unsupported_message_type:non_text",
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",
114
+ externalIdentity: identity,
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,17 +126,15 @@ export const telegramChannelAdapter: ChannelAdapter = {
100
126
  return [
101
127
  {
102
128
  ...base,
103
- kind: "message",
129
+ kind: "unsupported",
104
130
  externalIdentity: identity,
105
- message: {
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
139
  return sendTelegramMessage(input);
116
140
  },
@@ -142,6 +166,7 @@ export type TelegramSendMessageRequest = {
142
166
  };
143
167
 
144
168
  type TelegramFetch = (url: string, init: RequestInit) => Promise<Response>;
169
+ const TELEGRAM_MAX_DOWNLOAD_BYTES = 20_000_000;
145
170
 
146
171
  export function buildTelegramSendMessageRequest(input: ChannelSendInput): TelegramSendMessageRequest {
147
172
  return buildTelegramSendMessageRequestInternal(input, "<redacted>");
@@ -156,7 +181,7 @@ export async function sendTelegramMessage(
156
181
 
157
182
  return {
158
183
  ok: true,
159
- status: "sent",
184
+ status: "stubbed",
160
185
  providerRequestId: request.idempotencyKey,
161
186
  providerMetadata: {
162
187
  method: request.method,
@@ -247,11 +272,26 @@ export async function sendTelegramMessage(
247
272
  const result = isRecord(payload.result) ? payload.result : undefined;
248
273
  const messageId = readNumberishString(result ?? {}, "message_id");
249
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
+
250
290
  return {
251
291
  ok: true,
252
292
  status: "sent",
253
293
  providerRequestId: request.idempotencyKey,
254
- ...(messageId ? { providerMessageId: messageId } : {}),
294
+ providerMessageId: messageId,
255
295
  providerMetadata: {
256
296
  method: request.method,
257
297
  url: request.url,
@@ -260,6 +300,149 @@ export async function sendTelegramMessage(
260
300
  };
261
301
  }
262
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
+
263
446
  function isSyntheticTelegramChatId(chatId: string): boolean {
264
447
  return chatId === "456" || chatId.startsWith("agentkit-synthetic-");
265
448
  }
@@ -282,6 +465,48 @@ function buildTelegramSendMessageRequestInternal(input: ChannelSendInput, token:
282
465
  };
283
466
  }
284
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
+
285
510
  export function parseTelegramWebhookInfo(input: unknown, checkedAt: string): ChannelStatusResult {
286
511
  if (!isRecord(input) || input.ok !== true || !isRecord(input.result)) {
287
512
  return {
@@ -347,6 +572,112 @@ function readNumberishString(value: Record<string, unknown>, key: string): strin
347
572
  return undefined;
348
573
  }
349
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
+
350
681
  function redactId(value: string): string {
351
682
  if (value.length <= 2) {
352
683
  return "*".repeat(value.length);