@andreprado/agentkit 0.1.0-alpha.18 → 0.1.0-alpha.19

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 (56) hide show
  1. package/README.md +3 -0
  2. package/docs/guides/add-channel.md +12 -6
  3. package/docs/guides/add-knowledge.md +10 -0
  4. package/docs/guides/add-managed-composio.md +4 -2
  5. package/docs/guides/channel-security.md +26 -2
  6. package/docs/guides/connect-discord.md +178 -0
  7. package/docs/guides/create-agent.md +13 -0
  8. package/docs/guides/debug-channel.md +8 -2
  9. package/docs/guides/improve-from-production.md +151 -0
  10. package/docs/guides/prepare-deploy.md +29 -8
  11. package/docs/guides/replay-production-traces.md +72 -0
  12. package/docs/guides/run-evals.md +18 -0
  13. package/docs/guides/security-rules.md +5 -5
  14. package/docs/guides/use-provider.md +11 -1
  15. package/docs/llms-full.txt +100 -11
  16. package/docs/llms.txt +17 -1
  17. package/package.json +1 -1
  18. package/src/cli/args.ts +23 -2
  19. package/src/cli/cloud-client.ts +63 -0
  20. package/src/cli/commands/channels.ts +139 -14
  21. package/src/cli/deploy-readiness.ts +5 -2
  22. package/src/cli/help.ts +26 -2
  23. package/src/cli/index.ts +382 -17
  24. package/src/create-project.ts +13 -2
  25. package/src/index.ts +42 -3
  26. package/src/providers/pi.ts +49 -15
  27. package/src/runtime/channel-test-harness.ts +4 -1
  28. package/src/runtime/channels/discord.ts +887 -0
  29. package/src/runtime/channels.ts +15 -0
  30. package/src/runtime/config.ts +35 -3
  31. package/src/runtime/dev-server.ts +149 -8
  32. package/src/runtime/evals.ts +27 -6
  33. package/src/runtime/improve.ts +868 -0
  34. package/src/runtime/knowledge/retrieve.ts +25 -5
  35. package/src/runtime/knowledge/schema.ts +45 -1
  36. package/src/runtime/runtime-contract.ts +54 -0
  37. package/src/runtime/targets/cloudflare/build.ts +248 -193
  38. package/src/runtime/targets/vps/deploy.ts +1 -1
  39. package/src/storage/sqlite.ts +7 -2
  40. package/src/templates/skills/agentkit-capsule/SKILL.md +8 -1
  41. package/src/templates/skills/agentkit-capsule/references/docs-router.md +1 -2
  42. package/src/templates/skills/agentkit-channels/SKILL.md +6 -1
  43. package/src/templates/skills/agentkit-channels/references/channel-debugging.md +2 -1
  44. package/src/templates/skills/agentkit-channels/references/discord.md +93 -0
  45. package/src/templates/skills/agentkit-deploy/SKILL.md +6 -0
  46. package/src/templates/skills/agentkit-evals/SKILL.md +12 -3
  47. package/src/templates/skills/agentkit-improve/SKILL.md +86 -0
  48. package/src/templates/skills/agentkit-improve/references/replay-side-effects.md +18 -0
  49. package/src/templates/skills/agentkit-improve/references/trace-packets.md +22 -0
  50. package/src/templates/skills/agentkit-improve/templates/regression.eval.md +18 -0
  51. package/src/templates/skills/agentkit-integrations/SKILL.md +1 -0
  52. package/src/templates/skills/agentkit-knowledge/SKILL.md +4 -1
  53. package/src/templates/skills/agentkit-provider/SKILL.md +4 -1
  54. package/src/templates/skills/agentkit-security/SKILL.md +3 -2
  55. package/src/templates/skills/agentkit-troubleshooting/SKILL.md +9 -0
  56. package/src/templates/support.ts +4 -2
package/src/cli/index.ts CHANGED
@@ -21,6 +21,7 @@ import {
21
21
  import { runEvalsFromCwd, writeEvalFromConversation } from "../runtime/evals";
22
22
  import { findAgentKitDocs } from "../runtime/docs";
23
23
  import { listCapsuleEnv, setCapsuleEnv, unsetCapsuleEnv } from "../runtime/env";
24
+ import { collectDeployImproveBundle, collectLocalImproveBundle, generateImproveEvals, replayImproveBundle } from "../runtime/improve";
24
25
  import { checkAgentSpec, initializeAgentSpec } from "../runtime/spec";
25
26
  import { checkDefaultSkillStatus, syncDefaultSkillFiles } from "../runtime/skills";
26
27
  import { initializeSyncScaffold, runCapsuleSync } from "../runtime/sync";
@@ -44,6 +45,12 @@ import {
44
45
  resolveCloudApiUrl,
45
46
  verifyCloudLogin,
46
47
  writeCloudAuth,
48
+ type AccountApiTokenCreateResponse,
49
+ type AccountApiTokenListResponse,
50
+ type BillingCheckoutIntentResponse,
51
+ type BillingCheckoutResponse,
52
+ type BillingClaimResponse,
53
+ type BillingPortalResponse,
47
54
  type CloudMeResponse,
48
55
  type DeployAccessTokenCreateResponse,
49
56
  type DeployAccessTokenListResponse,
@@ -70,6 +77,8 @@ import { handleChannelsCommand } from "./commands/channels";
70
77
  import { handleKnowledgeCommand } from "./commands/knowledge";
71
78
  import { handleTranscribeCommand } from "./commands/transcribe";
72
79
 
80
+ const hostedDeployEntitlement = "agentkit_cloud_deploy";
81
+
73
82
  async function main() {
74
83
  const args = parseArgs(process.argv.slice(2));
75
84
 
@@ -336,6 +345,121 @@ async function main() {
336
345
  return;
337
346
  }
338
347
 
348
+ if (args.command === "improve") {
349
+ const [subcommand, bundlePath] = args.positional;
350
+
351
+ if (subcommand === "collect") {
352
+ const since = parseOptionalStringFlag(args.flags.since, "since", 'agentkit improve collect --since "24h"');
353
+ const conversationId = parseOptionalStringFlag(
354
+ args.flags["conversation-id"] ?? args.flags.conversation,
355
+ "conversation id",
356
+ "agentkit improve collect --conversation-id <id>",
357
+ );
358
+ const out = parseOptionalStringFlag(args.flags.out, "out", "agentkit improve collect --out <directory>");
359
+ const deployState = args.flags.deploy === true ? await requireLocalDeployState() : null;
360
+ const apiUrl = args.flags.deploy === true ? await resolveCloudApiUrl(args.flags.api) : null;
361
+ const cloudAuth = apiUrl ? await readCloudAuthForApiUrl(apiUrl) : null;
362
+ const result = args.flags.deploy === true
363
+ ? await collectDeployImproveBundle(process.cwd(), {
364
+ deployId: deployState?.deploy_id ?? "",
365
+ since,
366
+ conversationId,
367
+ out,
368
+ collector: {
369
+ ...(cloudAuth && apiUrl && deployState
370
+ ? {
371
+ exportEvidence: (input: { deployId: string; since?: string; conversationId?: string }) =>
372
+ fetchDeployImproveEvidence(apiUrl, input),
373
+ }
374
+ : {}),
375
+ listConversations: () => fetchDeployConversationJson(process.cwd(), "/v1/conversations"),
376
+ getTrace: (id: string) =>
377
+ fetchDeployConversationJson(process.cwd(), `/v1/conversations/${encodeURIComponent(id)}/trace`),
378
+ },
379
+ })
380
+ : await collectLocalImproveBundle(process.cwd(), {
381
+ since,
382
+ conversationId,
383
+ out,
384
+ });
385
+
386
+ console.log("Improve bundle collected");
387
+ console.log(`Source: ${result.source}`);
388
+ console.log(`Directory: ${relativePath(result.root, result.directory)}`);
389
+ console.log(`Bundle: ${relativePath(result.root, result.bundlePath)}`);
390
+ console.log(`Report: ${relativePath(result.root, result.reportPath)}`);
391
+ console.log(`Traces: ${result.traceCount}`);
392
+ console.log("");
393
+ console.log(`Next: agentkit improve evals ${relativePath(result.root, result.directory)}`);
394
+ console.log(`Replay: agentkit replay ${relativePath(result.root, result.directory)} --against local`);
395
+ return;
396
+ }
397
+
398
+ if (subcommand === "evals") {
399
+ if (!bundlePath) {
400
+ throw new Error("Missing bundle path. Usage: agentkit improve evals <bundle-dir-or-json> [--force]");
401
+ }
402
+
403
+ const result = await generateImproveEvals(process.cwd(), {
404
+ bundle: bundlePath,
405
+ force: args.flags.force === true,
406
+ });
407
+
408
+ console.log("Improve evals generated");
409
+ console.log(`Bundle: ${relativePath(result.root, result.bundlePath)}`);
410
+ console.log(`Files: ${result.files.length}`);
411
+ for (const file of result.files) {
412
+ console.log(` ${relativePath(result.root, file)}`);
413
+ }
414
+ if (result.skipped.length > 0) {
415
+ console.log(`Skipped: ${result.skipped.length}`);
416
+ for (const skipped of result.skipped) {
417
+ console.log(` ${skipped.trace_id}: ${skipped.reason}`);
418
+ }
419
+ }
420
+ return;
421
+ }
422
+
423
+ throw new Error("Usage: agentkit improve collect [--deploy] [--since <duration|iso>] [--conversation-id <id>] [--out <directory>] | agentkit improve evals <bundle-dir-or-json> [--force]");
424
+ }
425
+
426
+ if (args.command === "replay") {
427
+ const [bundlePath] = args.positional;
428
+
429
+ if (!bundlePath) {
430
+ throw new Error("Missing bundle path. Usage: agentkit replay <bundle-dir-or-json> --against local");
431
+ }
432
+
433
+ const against = parseOptionalStringFlag(args.flags.against, "against", "agentkit replay <bundle> --against local") ?? "local";
434
+
435
+ if (against !== "local") {
436
+ throw new Error("Usage: agentkit replay <bundle-dir-or-json> --against local");
437
+ }
438
+
439
+ const result = await replayImproveBundle(process.cwd(), {
440
+ bundle: bundlePath,
441
+ against,
442
+ });
443
+
444
+ for (const item of result.results) {
445
+ console.log(`${item.status.toUpperCase()} ${item.conversation_id} (${item.turns} turns)`);
446
+ if (item.error) {
447
+ console.log(` - ${item.error}`);
448
+ }
449
+ }
450
+
451
+ console.log("");
452
+ console.log(`Replay passed: ${result.passed}`);
453
+ console.log(`Replay failed: ${result.failed}`);
454
+ console.log(`Replay skipped: ${result.skipped}`);
455
+
456
+ if (result.failed > 0) {
457
+ process.exitCode = 1;
458
+ }
459
+
460
+ return;
461
+ }
462
+
339
463
  if (args.command === "conversations") {
340
464
  const [subcommand, conversationId] = args.positional;
341
465
  const fromDeploy = args.flags.deploy === true;
@@ -517,13 +641,15 @@ async function main() {
517
641
  const email = verified.me.account?.email;
518
642
  console.log(email ? `Logged in to AgentKit Cloud as ${email}.` : "AgentKit Cloud token verified and stored.");
519
643
 
520
- const hasCloudflareAlpha = (verified.capabilities.capabilities ?? []).some(
521
- (capability) => capability.name === "cloudflare_deploy_alpha" && capability.status === "active",
644
+ const hasHostedDeploy = (verified.capabilities.capabilities ?? []).some(
645
+ (capability) =>
646
+ (capability.name === "cloudflare_deploy_alpha" || capability.name === hostedDeployEntitlement) &&
647
+ capability.status === "active",
522
648
  );
523
649
  console.log(
524
- hasCloudflareAlpha
525
- ? "Entitlement: cloudflare_deploy_alpha active."
526
- : "Warning: cloudflare_deploy_alpha entitlement was not found for this account.",
650
+ hasHostedDeploy
651
+ ? "Entitlement: hosted deploy active."
652
+ : `Warning: cloudflare_deploy_alpha or ${hostedDeployEntitlement} entitlement was not found for this account.`,
527
653
  );
528
654
 
529
655
  if (process.env.AGENTKIT_CLOUD_API_TOKEN && process.env.AGENTKIT_CLOUD_API_TOKEN !== token) {
@@ -539,6 +665,208 @@ async function main() {
539
665
  return;
540
666
  }
541
667
 
668
+ if (args.command === "account") {
669
+ const [noun, verb, value] = args.positional;
670
+
671
+ if (noun !== "token") {
672
+ throw new Error("Usage: agentkit account token create <name> [--use] [--out <path>] | list | revoke <token-id> [--api <url>]");
673
+ }
674
+
675
+ const apiUrl = await resolveCloudApiUrl(args.flags.api);
676
+
677
+ if (verb === "create" && value) {
678
+ const payload = (await cloudApiRequest(apiUrl, "/v1/account/tokens", {
679
+ method: "POST",
680
+ body: JSON.stringify({ name: value }),
681
+ })) as AccountApiTokenCreateResponse;
682
+ const token = payload.token;
683
+
684
+ if (!token?.token || !token.id) {
685
+ throw new AgentKitError("account_token_invalid", "AgentKit Cloud returned an invalid account token payload.");
686
+ }
687
+
688
+ const outPath = parseOptionalStringFlag(
689
+ args.flags.out,
690
+ "out",
691
+ "agentkit account token create <name> [--api <url>] [--use] [--out <path>]",
692
+ );
693
+
694
+ if (outPath) {
695
+ const absolutePath = resolve(process.cwd(), outPath);
696
+ await writeSecretJson(absolutePath, payload);
697
+ console.log(`Account token written to ${absolutePath}`);
698
+ } else {
699
+ printJson(payload);
700
+ }
701
+
702
+ if (args.flags.use === true) {
703
+ await writeCloudAuth({ apiUrl, token: token.token });
704
+ console.log(`Saved ${token.id} as the current AgentKit Cloud login.`);
705
+ }
706
+
707
+ return;
708
+ }
709
+
710
+ if (verb === "list" && !value) {
711
+ const payload = (await cloudApiRequest(apiUrl, "/v1/account/tokens", {
712
+ method: "GET",
713
+ })) as AccountApiTokenListResponse;
714
+ printJson(payload);
715
+ return;
716
+ }
717
+
718
+ if (verb === "revoke" && value) {
719
+ const payload = await cloudApiRequest(apiUrl, `/v1/account/tokens/${encodeURIComponent(value)}/revoke`, {
720
+ method: "POST",
721
+ });
722
+ printJson(payload);
723
+ return;
724
+ }
725
+
726
+ throw new Error("Usage: agentkit account token create <name> [--use] [--out <path>] | list | revoke <token-id> [--api <url>]");
727
+ }
728
+
729
+ if (args.command === "billing") {
730
+ const [subcommand, intentId] = args.positional;
731
+ const apiUrl = await resolveCloudApiUrl(args.flags.api);
732
+
733
+ if (subcommand === "checkout") {
734
+ const slots = parsePositiveIntegerFlag(
735
+ args.flags.slots,
736
+ "slots",
737
+ "agentkit billing checkout --slots <count> [--email <email>] [--api <url>]",
738
+ );
739
+ const email = parseOptionalStringFlag(
740
+ args.flags.email,
741
+ "email",
742
+ "agentkit billing checkout --slots <count> --email <email>",
743
+ );
744
+ const payload = (await cloudApiRequest(apiUrl, "/v1/billing/checkout", {
745
+ method: "POST",
746
+ body: JSON.stringify({
747
+ slots,
748
+ ...(email ? { email } : {}),
749
+ }),
750
+ })) as BillingCheckoutResponse;
751
+ const checkoutUrl = payload.checkout?.url;
752
+ const billingIntentId = payload.billing_intent?.id;
753
+ const secret = payload.billing_intent?.secret;
754
+
755
+ if (!checkoutUrl || !billingIntentId || !secret) {
756
+ throw new AgentKitError("billing_checkout_invalid", "AgentKit Cloud returned an invalid billing checkout payload.");
757
+ }
758
+
759
+ if (args.flags.json === true) {
760
+ printJson(payload);
761
+ } else {
762
+ console.log("AgentKit Cloud checkout");
763
+ console.log("");
764
+ console.log(`URL: ${checkoutUrl}`);
765
+ console.log(`Intent: ${billingIntentId}`);
766
+ console.log(`Secret: ${secret}`);
767
+ console.log("");
768
+ console.log(`After payment: agentkit billing claim ${billingIntentId} --secret ${secret} --api ${apiUrl}`);
769
+ }
770
+
771
+ if (args.flags.open === true) {
772
+ await openLocalUrl(checkoutUrl);
773
+ }
774
+
775
+ return;
776
+ }
777
+
778
+ if (subcommand === "status" && intentId) {
779
+ const secret = parseRequiredStringFlag(
780
+ args.flags.secret,
781
+ "secret",
782
+ "agentkit billing status <billing-intent-id> --secret <secret> [--api <url>]",
783
+ );
784
+ const payload = (await cloudApiRequest(
785
+ apiUrl,
786
+ `/v1/billing/checkout-intents/${encodeURIComponent(intentId)}?secret=${encodeURIComponent(secret)}`,
787
+ { method: "GET" },
788
+ )) as BillingCheckoutIntentResponse;
789
+ printJson(payload);
790
+ return;
791
+ }
792
+
793
+ if (subcommand === "claim" && intentId) {
794
+ const secret = parseRequiredStringFlag(
795
+ args.flags.secret,
796
+ "secret",
797
+ "agentkit billing claim <billing-intent-id> --secret <secret> [--token-name <name>] [--api <url>]",
798
+ );
799
+ const tokenName = parseOptionalStringFlag(
800
+ args.flags["token-name"],
801
+ "token-name",
802
+ "agentkit billing claim <billing-intent-id> --secret <secret> --token-name <name>",
803
+ );
804
+ const payload = (await cloudApiRequest(apiUrl, `/v1/billing/checkout-intents/${encodeURIComponent(intentId)}/claim`, {
805
+ method: "POST",
806
+ body: JSON.stringify({
807
+ secret,
808
+ ...(tokenName ? { token_name: tokenName } : {}),
809
+ }),
810
+ })) as BillingClaimResponse;
811
+ const token = payload.token;
812
+
813
+ if (!token?.token || !token.id) {
814
+ throw new AgentKitError("billing_claim_invalid", "AgentKit Cloud returned an invalid billing claim payload.");
815
+ }
816
+
817
+ await writeCloudAuth({ apiUrl, token: token.token });
818
+
819
+ const outPath = parseOptionalStringFlag(
820
+ args.flags.out,
821
+ "out",
822
+ "agentkit billing claim <billing-intent-id> --secret <secret> [--out <path>]",
823
+ );
824
+
825
+ if (outPath) {
826
+ const absolutePath = resolve(process.cwd(), outPath);
827
+ await writeSecretJson(absolutePath, payload);
828
+ console.log(`Account token written to ${absolutePath}`);
829
+ } else if (args.flags.json === true) {
830
+ printJson(payload);
831
+ } else {
832
+ console.log("AgentKit Cloud account token claimed and saved.");
833
+ console.log(`Token: ${token.id}`);
834
+ console.log(`API: ${apiUrl}`);
835
+ }
836
+
837
+ return;
838
+ }
839
+
840
+ if (subcommand === "portal") {
841
+ const payload = (await cloudApiRequest(apiUrl, "/v1/billing/portal", {
842
+ method: "POST",
843
+ body: JSON.stringify({}),
844
+ })) as BillingPortalResponse;
845
+ const portalUrl = payload.portal?.url;
846
+
847
+ if (!portalUrl) {
848
+ throw new AgentKitError("billing_portal_invalid", "AgentKit Cloud returned an invalid billing portal payload.");
849
+ }
850
+
851
+ if (args.flags.json === true) {
852
+ printJson(payload);
853
+ } else {
854
+ console.log("AgentKit Cloud billing portal");
855
+ console.log(`URL: ${portalUrl}`);
856
+ }
857
+
858
+ if (args.flags.open === true) {
859
+ await openLocalUrl(portalUrl);
860
+ }
861
+
862
+ return;
863
+ }
864
+
865
+ throw new Error(
866
+ "Usage: agentkit billing checkout --slots <count> [--email <email>] | status <intent-id> --secret <secret> | claim <intent-id> --secret <secret> | portal [--api <url>]",
867
+ );
868
+ }
869
+
542
870
  if (args.command === "deploy") {
543
871
  const [deploySubcommand] = args.positional;
544
872
 
@@ -547,7 +875,7 @@ async function main() {
547
875
  parseOptionalStringFlag(args.flags.message ?? args.flags.smoke, "message", 'agentkit deploy smoke --message "hello"') ??
548
876
  "hello";
549
877
  const state = await requireLocalDeployState();
550
- const token = await readDeployAccessTokenForSmoke(process.cwd(), state);
878
+ const token = await readDeployAccessTokenForHostedRequest(process.cwd());
551
879
  await runDeploySmoke({
552
880
  deployUrl: requireDeployUrl(state),
553
881
  message,
@@ -646,7 +974,7 @@ async function main() {
646
974
  apiUrl,
647
975
  root: result.root,
648
976
  deployId: deploy.deploy.id,
649
- skip: anonymous || !auth?.token || deploy.deploy.access.mode === "public",
977
+ skip: anonymous || !auth?.token,
650
978
  });
651
979
 
652
980
  console.log("");
@@ -659,7 +987,7 @@ async function main() {
659
987
  if (chatAccess?.status === "ready") {
660
988
  console.log(`Chat auth: ${relativePath(process.cwd(), chatAccess.path)}`);
661
989
  } else if (chatAccess?.status === "skipped") {
662
- console.log("Chat auth: public or anonymous deploy; no local chat token was created.");
990
+ console.log("Chat auth: anonymous deploy; no local chat token was created.");
663
991
  } else if (chatAccess?.status === "failed") {
664
992
  console.log(`Chat auth: could not create local chat token. ${formatReadinessError(chatAccess.error)}`);
665
993
  console.log(`Run: agentkit access token create ${defaultDeployChatAccessTokenName} --out ${defaultDeployChatAccessTokenPath}`);
@@ -874,6 +1202,7 @@ async function main() {
874
1202
  redirect_url?: string;
875
1203
  expires_at?: string;
876
1204
  connected_account_id?: string;
1205
+ auth_config_source?: string;
877
1206
  };
878
1207
  };
879
1208
 
@@ -886,6 +1215,9 @@ async function main() {
886
1215
  console.log("Composio connect link");
887
1216
  console.log(`Toolkit: ${link.toolkit ?? toolkit ?? "unknown"}`);
888
1217
  console.log(`User: ${link.user_id ?? "unknown"}`);
1218
+ if (link.auth_config_source) {
1219
+ console.log(`Auth: ${link.auth_config_source}`);
1220
+ }
889
1221
  console.log(`URL: ${link.redirect_url}`);
890
1222
 
891
1223
  if (link.expires_at) {
@@ -1161,11 +1493,7 @@ function requireDeployUrl(state: Pick<LocalDeployState, "deploy_id" | "url">): s
1161
1493
  return state.url;
1162
1494
  }
1163
1495
 
1164
- async function readDeployAccessTokenForSmoke(cwd: string, state: LocalDeployState): Promise<string | undefined> {
1165
- if (state.access?.mode === "public") {
1166
- return undefined;
1167
- }
1168
-
1496
+ async function readDeployAccessTokenForHostedRequest(cwd: string): Promise<string> {
1169
1497
  const root = await findAgentCapsuleRoot(cwd);
1170
1498
  return readDeployAccessTokenForUi(join(root, defaultDeployChatAccessTokenPath));
1171
1499
  }
@@ -1215,12 +1543,10 @@ async function runDeploySmoke(input: { deployUrl: string; message: string; token
1215
1543
  async function fetchDeployConversationJson(cwd: string, path: string): Promise<unknown> {
1216
1544
  const state = await requireLocalDeployState();
1217
1545
  const deployUrl = requireDeployUrl(state);
1218
- const token = await readDeployAccessTokenForSmoke(cwd, state);
1546
+ const token = await readDeployAccessTokenForHostedRequest(cwd);
1219
1547
  const headers = new Headers();
1220
1548
 
1221
- if (token) {
1222
- headers.set("Authorization", `Bearer ${token}`);
1223
- }
1549
+ headers.set("Authorization", `Bearer ${token}`);
1224
1550
 
1225
1551
  let response: Response;
1226
1552
 
@@ -1245,6 +1571,31 @@ async function fetchDeployConversationJson(cwd: string, path: string): Promise<u
1245
1571
  return payload;
1246
1572
  }
1247
1573
 
1574
+ async function fetchDeployImproveEvidence(
1575
+ apiUrl: string,
1576
+ input: { deployId: string; since?: string; conversationId?: string },
1577
+ ): Promise<unknown | null> {
1578
+ try {
1579
+ return await cloudApiRequest(apiUrl, `/v1/deploys/${encodeURIComponent(input.deployId)}/improve/export`, {
1580
+ method: "POST",
1581
+ body: JSON.stringify({
1582
+ status: "all",
1583
+ ...(input.since ? { since: input.since } : {}),
1584
+ ...(input.conversationId ? { conversation_id: input.conversationId } : {}),
1585
+ }),
1586
+ });
1587
+ } catch (error) {
1588
+ if (
1589
+ isAgentKitError(error) &&
1590
+ (error.code === "improve_evidence_store_not_configured" || error.code === "not_found")
1591
+ ) {
1592
+ return null;
1593
+ }
1594
+
1595
+ throw error;
1596
+ }
1597
+ }
1598
+
1248
1599
  function printJson(value: unknown): void {
1249
1600
  console.log(JSON.stringify(value, null, 2));
1250
1601
  }
@@ -1286,6 +1637,20 @@ async function printJsonOrWrite(value: unknown, outFlag: string | boolean | unde
1286
1637
  console.log(`Deploy access token written to ${absolutePath}`);
1287
1638
  }
1288
1639
 
1640
+ function parsePositiveIntegerFlag(value: string | boolean | undefined, name: string, usage: string): number {
1641
+ if (typeof value !== "string") {
1642
+ throw new Error(`Missing ${name}. Usage: ${usage}`);
1643
+ }
1644
+
1645
+ const parsed = Number(value);
1646
+
1647
+ if (!Number.isInteger(parsed) || parsed <= 0) {
1648
+ throw new Error(`Invalid ${name}. Usage: ${usage}`);
1649
+ }
1650
+
1651
+ return parsed;
1652
+ }
1653
+
1289
1654
  function relativePath(root: string, path: string): string {
1290
1655
  const output = relative(root, path);
1291
1656
  return output.startsWith("..") ? path : output || ".";
@@ -42,8 +42,7 @@ export async function createProject(options: CreateProjectOptions): Promise<Crea
42
42
  }
43
43
 
44
44
  for (const file of files) {
45
- const absolutePath = join(projectPath, file.path);
46
- const directory = absolutePath.slice(0, absolutePath.lastIndexOf("/"));
45
+ const { absolutePath, directory } = resolveProjectFilePath(projectPath, file.path);
47
46
 
48
47
  await mkdir(directory, { recursive: true });
49
48
  await writeFile(absolutePath, file.contents);
@@ -58,6 +57,18 @@ export async function createProject(options: CreateProjectOptions): Promise<Crea
58
57
  };
59
58
  }
60
59
 
60
+ export function resolveProjectFilePath(
61
+ projectPath: string,
62
+ filePath: string,
63
+ pathApi: Pick<typeof import("node:path"), "dirname" | "join"> = { dirname, join },
64
+ ): { absolutePath: string; directory: string } {
65
+ const absolutePath = pathApi.join(projectPath, filePath);
66
+ return {
67
+ absolutePath,
68
+ directory: pathApi.dirname(absolutePath),
69
+ };
70
+ }
71
+
61
72
  function resolveProjectTarget(options: CreateProjectOptions): { name: string; path: string; currentDirectory: boolean } {
62
73
  if (options.name === ".") {
63
74
  const path = resolve(options.cwd);
package/src/index.ts CHANGED
@@ -2,9 +2,10 @@ export type AgentRuntime = "local" | "edge";
2
2
  export type AgentProviderName = "test" | "openai" | "anthropic" | "openrouter" | "custom";
3
3
  export type AccessMode = "private" | "token" | "public";
4
4
  export type ToolVisibility = "user" | "internal";
5
- export type ChannelType = "website" | "telegram" | "whatsapp";
6
- export type ChannelProvider = "agentkit" | "telegram" | "zapster" | "meta";
5
+ export type ChannelType = "website" | "telegram" | "whatsapp" | "discord";
6
+ export type ChannelProvider = "agentkit" | "telegram" | "zapster" | "meta" | "discord";
7
7
  export type WhatsappChannelProvider = "zapster" | "meta";
8
+ export type DiscordChannelMode = "interactions" | "bot";
8
9
  export type TelegramAllowedUpdate = "message";
9
10
  export type ChannelAudioMode = "off" | "reject" | "transcribe";
10
11
  export type TranscriptionProviderName = "test" | "openai" | "groq";
@@ -143,7 +144,26 @@ export type WhatsappChannelConfig = {
143
144
  audio?: ChannelAudioConfig;
144
145
  };
145
146
 
146
- export type AgentChannel = WebsiteChannelConfig | TelegramChannelConfig | WhatsappChannelConfig;
147
+ export type DiscordChannelInput = {
148
+ name: string;
149
+ mode?: DiscordChannelMode;
150
+ secrets?: string[];
151
+ limits?: ChannelLimits;
152
+ buffer?: ChannelBufferInput;
153
+ };
154
+
155
+ export type DiscordChannelConfig = {
156
+ type: "discord";
157
+ provider: "discord";
158
+ name: string;
159
+ mode: DiscordChannelMode;
160
+ secrets: string[];
161
+ limits?: ChannelLimits;
162
+ buffer?: ChannelBufferConfig;
163
+ audio?: never;
164
+ };
165
+
166
+ export type AgentChannel = WebsiteChannelConfig | TelegramChannelConfig | WhatsappChannelConfig | DiscordChannelConfig;
147
167
 
148
168
  export type IntegrationTagsConfig = {
149
169
  enable?: string[];
@@ -428,6 +448,24 @@ export function whatsappChannel(input: WhatsappChannelInput): WhatsappChannelCon
428
448
  };
429
449
  }
430
450
 
451
+ export function discordChannel(input: DiscordChannelInput): DiscordChannelConfig {
452
+ const mode = input.mode ?? "interactions";
453
+
454
+ return {
455
+ type: "discord",
456
+ provider: "discord",
457
+ name: input.name,
458
+ mode,
459
+ secrets: normalizeChannelSecrets(input.secrets ?? defaultDiscordChannelSecrets(mode), "discord channel"),
460
+ ...(input.limits ? { limits: input.limits } : {}),
461
+ ...(input.buffer ? { buffer: normalizeChannelBuffer(input.buffer, "discord channel") } : {}),
462
+ };
463
+ }
464
+
465
+ function defaultDiscordChannelSecrets(mode: DiscordChannelMode): string[] {
466
+ return mode === "bot" ? ["DISCORD_BOT_TOKEN"] : ["DISCORD_PUBLIC_KEY"];
467
+ }
468
+
431
469
  function defaultWhatsappChannelSecrets(provider: WhatsappChannelProvider): string[] {
432
470
  if (provider === "zapster") {
433
471
  return ["ZAPSTER_API_KEY", "ZAPSTER_INSTANCE_ID", "ZAPSTER_WEBHOOK_ID"];
@@ -649,6 +687,7 @@ export type {
649
687
  ChannelMediaDownloadResult,
650
688
  ChannelMessageContentType,
651
689
  ChannelMessageRole,
690
+ ChannelReplyTarget,
652
691
  ChannelSendInput,
653
692
  ChannelSendResult,
654
693
  ChannelStatusInput,
@@ -19,6 +19,9 @@ import type { ProviderAdapter, ProviderRunInput, ProviderRunResult } from "./typ
19
19
 
20
20
  const PI_PROVIDER_NAMES = ["openai", "anthropic", "openrouter"] as const satisfies AgentProviderName[];
21
21
  const MAX_TOOL_TURNS = 8;
22
+ const OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1";
23
+ const UNKNOWN_OPENROUTER_CONTEXT_WINDOW = 128000;
24
+ const UNKNOWN_OPENROUTER_MAX_TOKENS = 8192;
22
25
 
23
26
  export const piProviderAdapters: ProviderAdapter[] = PI_PROVIDER_NAMES.map((name) => ({
24
27
  name,
@@ -189,25 +192,56 @@ function formatSecretList(names: string[]): string {
189
192
  return `one of: ${names.join(", ")}`;
190
193
  }
191
194
 
192
- function resolveModel(provider: Provider, modelId: string): Model<any> {
193
- const modelExists = getModels(provider as any).some((model) => model.id === modelId);
195
+ export function resolveModel(provider: Provider, modelId: string): Model<any> {
196
+ const model = getModel(provider as any, modelId as any) as Model<any> | undefined;
194
197
 
195
- if (!modelExists) {
196
- throw new AgentKitError(
197
- "provider_model_unsupported",
198
- `Pi SDK could not resolve provider "${provider}" model "${modelId}".`,
199
- );
198
+ if (model) {
199
+ return model;
200
200
  }
201
201
 
202
- try {
203
- return getModel(provider as any, modelId as any) as Model<any>;
204
- } catch (error) {
205
- throw new AgentKitError(
206
- "provider_model_unsupported",
207
- `Pi SDK could not resolve provider "${provider}" model "${modelId}".`,
208
- { cause: error },
209
- );
202
+ if (provider === "openrouter") {
203
+ return createOpenRouterPassThroughModel(modelId);
210
204
  }
205
+
206
+ throw new AgentKitError(
207
+ "provider_model_unsupported",
208
+ `Pi SDK could not resolve provider "${provider}" model "${modelId}". ${knownModelHint(provider)}`,
209
+ );
210
+ }
211
+
212
+ function createOpenRouterPassThroughModel(modelId: string): Model<"openai-completions"> {
213
+ return {
214
+ id: modelId,
215
+ name: `OpenRouter ${modelId}`,
216
+ api: "openai-completions",
217
+ provider: "openrouter",
218
+ baseUrl: OPENROUTER_BASE_URL,
219
+ reasoning: false,
220
+ input: ["text"],
221
+ cost: {
222
+ input: 0,
223
+ output: 0,
224
+ cacheRead: 0,
225
+ cacheWrite: 0,
226
+ },
227
+ contextWindow: UNKNOWN_OPENROUTER_CONTEXT_WINDOW,
228
+ maxTokens: UNKNOWN_OPENROUTER_MAX_TOKENS,
229
+ compat: {
230
+ openRouterRouting: {},
231
+ },
232
+ };
233
+ }
234
+
235
+ function knownModelHint(provider: Provider): string {
236
+ const examples = getModels(provider as any)
237
+ .slice(0, 5)
238
+ .map((model) => model.id);
239
+
240
+ if (examples.length === 0) {
241
+ return "Use a model id supported by the installed Pi SDK for that provider.";
242
+ }
243
+
244
+ return `Use a model id supported by the installed Pi SDK for that provider. Examples: ${examples.join(", ")}.`;
211
245
  }
212
246
 
213
247
  function toPiMessages(messages: ProviderRunInput["messages"]): Message[] {