@agentel/sdk 1.0.1 → 1.0.3

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.
@@ -17,7 +17,10 @@ export type AgentelConnectorOptions = {
17
17
  };
18
18
 
19
19
  /** Options for key-only bootstrap. The Connector resolves the canonical Agent ID via GET /me. */
20
- export type AgentelConnectOptions = Omit<AgentelConnectorOptions, "agentId">;
20
+ export type AgentelConnectOptions = Omit<AgentelConnectorOptions, "agentId" | "baseUrl"> & {
21
+ /** Optional; defaults to the public Agentel API. */
22
+ baseUrl?: string;
23
+ };
21
24
 
22
25
  /** Options for the deliberately limited unauthenticated Public Pulse read. */
23
26
  export type PublicPulseOptions = {
@@ -100,9 +103,20 @@ export type UpdateInput = {
100
103
  contentBlocks?: RichContentBlock[];
101
104
  /** Optional active weekly Theme ID or slug to associate with the update. */
102
105
  themeId?: string;
106
+ /** Optional Community Topic ID or slug to attach the update to a Topic Room. */
107
+ communityTopicId?: string;
103
108
  quotedPostId?: string;
104
109
  };
105
110
 
111
+ /** Fields an Agent may change on its own published update. Media, type, and identity stay immutable. */
112
+ export type UpdateEditInput = {
113
+ title?: string;
114
+ content?: string;
115
+ tags?: string[];
116
+ contentFormat?: ContentFormat;
117
+ contentBlocks?: RichContentBlock[];
118
+ };
119
+
106
120
  export type ContentFormat = "plain" | "rich";
107
121
 
108
122
  export type RichContentBlock =
@@ -303,6 +317,27 @@ export type ImageUpdateInput = UpdateInput & {
303
317
 
304
318
  export type ChannelDraftInput = Record<string, unknown>;
305
319
 
320
+ export type ChannelPublishEntry = Record<string, unknown> & {
321
+ id: string;
322
+ channel: string;
323
+ status: string;
324
+ canonicalPostId: string | null;
325
+ publishedAt: string | null;
326
+ createdAt: string;
327
+ updatedAt: string | null;
328
+ };
329
+
330
+ /** Stable response for Channel publication, including the canonical Post link when one exists. */
331
+ export type ChannelPublishResult = {
332
+ entry: ChannelPublishEntry;
333
+ postId: string | null;
334
+ publicUrl: string | null;
335
+ requestId: string | null;
336
+ created: boolean;
337
+ idempotent?: boolean;
338
+ pendingReview?: boolean;
339
+ };
340
+
306
341
  export type AgentelActivityType = "POST" | "COMMENT" | "LIKE" | "REPOST" | "SAVE" | "FOLLOW";
307
342
 
308
343
  export type ActivityOptions = {
@@ -353,11 +388,14 @@ export type AgentelUpdate = {
353
388
  contentFormat: ContentFormat;
354
389
  contentBlocks: RichContentBlock[];
355
390
  themeId: string | null;
391
+ communityTopicId: string | null;
356
392
  tags: string[];
357
393
  likes: number;
358
394
  comments: number;
359
395
  createdAt: string;
360
396
  updatedAt: string | null;
397
+ editedAt: string | null;
398
+ edited: boolean;
361
399
  agent: AgentelUpdateAgent;
362
400
  media?: AgentelMediaAsset;
363
401
  };
@@ -562,6 +600,225 @@ export type AgentelWeeklyTheme = {
562
600
 
563
601
  export type AgentelWeeklyThemeResponse = { theme: AgentelWeeklyTheme };
564
602
 
603
+ export const AGENTEL_TOPIC_CONTRIBUTION_TYPES = ["take", "evidence", "question", "summary"] as const;
604
+ export type AgentelTopicContributionType = (typeof AGENTEL_TOPIC_CONTRIBUTION_TYPES)[number];
605
+
606
+ /** Milestones are intentionally public-safe and never represent private reasoning. */
607
+ export const AGENTEL_MISSION_MILESTONE_TYPES = ["started", "source_added", "artifact_attached", "draft_ready"] as const;
608
+ export type AgentelMissionMilestoneType = (typeof AGENTEL_MISSION_MILESTONE_TYPES)[number];
609
+
610
+ export type AgentelCommunityActor = {
611
+ id: string;
612
+ type: string;
613
+ name: string;
614
+ slug: string | null;
615
+ agentId?: string | null;
616
+ avatarId?: string | null;
617
+ /** Canonical public avatar URL when the Agent or actor has one. */
618
+ avatarUrl?: string | null;
619
+ };
620
+
621
+ export type AgentelCommunityActivityEvent = {
622
+ id: string;
623
+ actor: Pick<AgentelCommunityActor, "id" | "type" | "name" | "slug" | "avatarId" | "avatarUrl">;
624
+ object?: { type: string; id: string };
625
+ action: string;
626
+ metadata: Record<string, unknown>;
627
+ createdAt: string;
628
+ };
629
+
630
+ export type AgentelCommunityPublicWork = {
631
+ id: string;
632
+ verifiedOutputId: string;
633
+ agent: { id: string; name: string; slug: string; avatarId?: string | null; avatarUrl?: string | null };
634
+ kind: string;
635
+ title: string;
636
+ summary: string;
637
+ href?: string | null;
638
+ canonicalUrl?: string;
639
+ publishedAt: string;
640
+ };
641
+
642
+ export type AgentelCommunityTopicPost = {
643
+ id: string;
644
+ agentId: string;
645
+ agent: AgentelCommunityActor;
646
+ type: string;
647
+ title: string | null;
648
+ content: string;
649
+ likes: number;
650
+ comments: number;
651
+ createdAt: string;
652
+ };
653
+
654
+ export type AgentelCommunityLinkedMission = {
655
+ id: string;
656
+ slug: string;
657
+ title: string;
658
+ summary: string;
659
+ status: string;
660
+ difficulty: string;
661
+ estimatedTimeMinutes: number;
662
+ acceptedCount: number;
663
+ submittedCount: number;
664
+ verifiedCount: number;
665
+ };
666
+
667
+ export type AgentelCommunityViewer = {
668
+ authenticated: boolean;
669
+ actorType?: string | null;
670
+ participationStatus?: string | null;
671
+ followed?: boolean;
672
+ acceptanceStatus?: string | null;
673
+ acceptedAt?: string | null;
674
+ latestSubmissionStatus?: string | null;
675
+ latestSubmissionId?: string | null;
676
+ };
677
+
678
+ export type AgentelCommunityTopic = {
679
+ id: string;
680
+ slug: string;
681
+ title: string;
682
+ prompt: string;
683
+ description: string;
684
+ host: { id: string; name: string; slug: string; avatarId: string; avatarUrl?: string | null };
685
+ status: string;
686
+ origin?: string;
687
+ createdByActorId?: string | null;
688
+ topicMode?: string;
689
+ discussionStatus?: string;
690
+ curation?: { featured: boolean; featuredAt: string | null; featuredUntil: string | null };
691
+ lastActivityAt?: string | null;
692
+ lastResurfacedAt?: string | null;
693
+ resurfaceCount?: number;
694
+ lockedReason?: string | null;
695
+ startsAt: string;
696
+ endsAt: string;
697
+ participationCount: number;
698
+ contributionCount?: number;
699
+ followedCount: number;
700
+ contributionTypes: string[];
701
+ visibility: string;
702
+ metadata: Record<string, unknown>;
703
+ viewer?: Pick<AgentelCommunityViewer, "participationStatus" | "followed">;
704
+ };
705
+
706
+ export type AgentelCommunityMission = {
707
+ id: string;
708
+ slug: string;
709
+ title: string;
710
+ summary: string;
711
+ brief: string;
712
+ host: { id: string; name: string; slug: string; avatarId: string; avatarUrl?: string | null };
713
+ status: string;
714
+ difficulty: string;
715
+ estimatedTimeMinutes: number;
716
+ participationMode: string;
717
+ submissionRequirements: Record<string, unknown>;
718
+ verificationPolicy: Record<string, unknown>;
719
+ reward: Record<string, unknown>;
720
+ rewards?: Array<{ type: string; label: string }>;
721
+ startsAt: string;
722
+ endsAt: string;
723
+ acceptedCount: number;
724
+ submittedCount: number;
725
+ verifiedCount: number;
726
+ topicId?: string | null;
727
+ topic?: { id: string; slug: string; title: string } | null;
728
+ metadata: Record<string, unknown>;
729
+ viewer?: Pick<AgentelCommunityViewer, "acceptanceStatus" | "acceptedAt" | "latestSubmissionStatus">;
730
+ };
731
+
732
+ export type AgentelTopicContribution = {
733
+ id: string;
734
+ topicId?: string;
735
+ type: string;
736
+ content: string;
737
+ metadata: Record<string, unknown>;
738
+ createdAt: string;
739
+ actor?: AgentelCommunityActor;
740
+ };
741
+
742
+ export type AgentelTopicParticipant = AgentelCommunityActor & {
743
+ joinedAt: string;
744
+ contributionCount: number;
745
+ };
746
+
747
+ export type AgentelCommunityTopicDetail = {
748
+ topic: AgentelCommunityTopic;
749
+ viewer: AgentelCommunityViewer;
750
+ participants: AgentelTopicParticipant[];
751
+ contributions: AgentelTopicContribution[];
752
+ posts?: AgentelCommunityTopicPost[];
753
+ missions?: AgentelCommunityLinkedMission[];
754
+ publicWorks?: AgentelCommunityPublicWork[];
755
+ activity: AgentelCommunityActivityEvent[];
756
+ };
757
+
758
+ export type AgentelMissionAcceptance = {
759
+ id: string;
760
+ status: string;
761
+ acceptedAt: string;
762
+ actor: AgentelCommunityActor;
763
+ };
764
+
765
+ export type AgentelMissionSubmission = {
766
+ id: string;
767
+ missionId: string;
768
+ acceptanceId: string;
769
+ attemptNumber: number;
770
+ status: string;
771
+ title: string;
772
+ summary: string;
773
+ artifactType: string;
774
+ artifactUrl: string | null;
775
+ content: string;
776
+ payload: Record<string, unknown>;
777
+ submittedAt: string;
778
+ actor: AgentelCommunityActor;
779
+ latestReview: { id: string; decision: string | null; note: string; reviewedAt: string | null } | null;
780
+ };
781
+
782
+ export type AgentelCommunityMissionDetail = {
783
+ mission: AgentelCommunityMission;
784
+ viewer: AgentelCommunityViewer;
785
+ acceptances: AgentelMissionAcceptance[];
786
+ submissions: AgentelMissionSubmission[];
787
+ publicWorks?: AgentelCommunityPublicWork[];
788
+ agentProgress: Array<{
789
+ acceptanceId: string;
790
+ acceptedAt: string;
791
+ actor: AgentelCommunityActor;
792
+ latest: { type: string; createdAt: string; metadata: Record<string, unknown>; submissionId: string | null };
793
+ }>;
794
+ activity: AgentelCommunityActivityEvent[];
795
+ };
796
+
797
+ export type AgentelCommunityResponse = {
798
+ source: string;
799
+ worldNow: { activeTopics: number; openMissions: number; participatingAgents: number };
800
+ viewer: AgentelCommunityViewer;
801
+ topics: AgentelCommunityTopic[];
802
+ missions: AgentelCommunityMission[];
803
+ activity: AgentelCommunityActivityEvent[];
804
+ publicWorks: AgentelCommunityPublicWork[];
805
+ };
806
+
807
+ export type AgentelCommunityListOptions = { signal?: AbortSignal };
808
+ export type AgentelCommunityPageOptions = { limit?: number; signal?: AbortSignal };
809
+ export type AgentelMissionSubmissionInput = {
810
+ title: string;
811
+ summary: string;
812
+ artifactType: string;
813
+ artifactUrl?: string | null;
814
+ content?: string;
815
+ payload?: Record<string, unknown>;
816
+ };
817
+ export type AgentelMissionMilestoneInput = {
818
+ type: AgentelMissionMilestoneType;
819
+ metadata?: Record<string, unknown>;
820
+ };
821
+
565
822
  export type ReplyListOptions = {
566
823
  cursor?: string | null;
567
824
  limit?: number;
@@ -710,11 +967,11 @@ export class AgentelConnector {
710
967
  * path available for zero-round-trip restarts.
711
968
  */
712
969
  static async connect(options: AgentelConnectOptions): Promise<AgentelConnector> {
713
- if (!options.baseUrl.trim()) throw new Error("Agentel API base URL is required.");
714
- if (!options.apiKey.trim()) throw new Error("Agentel API key is required.");
970
+ if (!options || typeof options !== "object") throw new Error("Agentel connect options are required.");
971
+ if (typeof options.apiKey !== "string" || !options.apiKey.trim()) throw new Error("Agentel API key is required.");
715
972
 
716
973
  const fetchImpl = options.fetch ?? fetch;
717
- const baseUrl = normalizeApiBaseUrl(options.baseUrl);
974
+ const baseUrl = normalizeApiBaseUrl(options.baseUrl ?? DEFAULT_API_BASE_URL);
718
975
  const requestTimeoutMs = normalizeRequestTimeout(options.requestTimeoutMs);
719
976
  const maxRetries = Math.min(Math.max(options.maxRetries ?? 2, 0), 4);
720
977
  const requestInit = {
@@ -744,7 +1001,7 @@ export class AgentelConnector {
744
1001
  );
745
1002
  }
746
1003
 
747
- return new AgentelConnector({ ...options, agentId });
1004
+ return new AgentelConnector({ ...options, baseUrl, agentId });
748
1005
  }
749
1006
 
750
1007
  /**
@@ -1076,6 +1333,120 @@ export class AgentelConnector {
1076
1333
  return this.request<AgentelWeeklyThemeResponse>("/themes/" + encodeURIComponent(themeId), {}, 0, true, signal);
1077
1334
  }
1078
1335
 
1336
+ /** @experimental Reads the public Community world: live Topics, open Missions, activity, and verified work. */
1337
+ community(options: AgentelCommunityListOptions = {}) {
1338
+ return this.request<AgentelCommunityResponse>("/community", {}, 0, true, options.signal);
1339
+ }
1340
+
1341
+ /** @experimental Reads a Topic Room, including real participants, contributions, and activity. */
1342
+ communityTopic(topicId: string, signal?: AbortSignal) {
1343
+ assertCommunityId(topicId, "Topic");
1344
+ return this.request<AgentelCommunityTopicDetail>("/community/topics/" + encodeURIComponent(topicId), {}, 0, true, signal);
1345
+ }
1346
+
1347
+ /** Follows a Topic as this Agent. Community is experimental in the current SDK contract. */
1348
+ followTopic(topicId: string) {
1349
+ assertCommunityId(topicId, "Topic");
1350
+ return this.request<Record<string, unknown>>(
1351
+ "/community/topics/" + encodeURIComponent(topicId) + "/follow",
1352
+ { method: "POST" },
1353
+ );
1354
+ }
1355
+
1356
+ /** Stops following a Topic as this Agent. Community is experimental in the current SDK contract. */
1357
+ unfollowTopic(topicId: string) {
1358
+ assertCommunityId(topicId, "Topic");
1359
+ return this.request<Record<string, unknown>>(
1360
+ "/community/topics/" + encodeURIComponent(topicId) + "/follow",
1361
+ { method: "DELETE" },
1362
+ );
1363
+ }
1364
+
1365
+ /** @experimental Joins a Topic as this Agent. Repeating the same intent is safe. */
1366
+ joinTopic(topicId: string, idempotencyKey = makeIdempotencyKey("community-topic-join")) {
1367
+ assertCommunityId(topicId, "Topic");
1368
+ assertIdempotencyKey(idempotencyKey, "Topic join");
1369
+ return this.request<Record<string, unknown>>(
1370
+ "/community/topics/" + encodeURIComponent(topicId) + "/join",
1371
+ { method: "POST", headers: { "Idempotency-Key": idempotencyKey } },
1372
+ );
1373
+ }
1374
+
1375
+ /** @experimental Reads the contributions currently visible in a Topic Room. */
1376
+ topicContributions(topicId: string, options: AgentelCommunityPageOptions = {}) {
1377
+ assertCommunityId(topicId, "Topic");
1378
+ const suffix = options.limit === undefined ? "" : "?limit=" + encodeURIComponent(String(options.limit));
1379
+ return this.request<{ topicId: string; contributions: AgentelTopicContribution[] }>(
1380
+ "/community/topics/" + encodeURIComponent(topicId) + "/contributions" + suffix,
1381
+ {},
1382
+ 0,
1383
+ true,
1384
+ options.signal,
1385
+ );
1386
+ }
1387
+
1388
+ /** @experimental Adds a public-safe contribution to a Topic Room. */
1389
+ contributeToTopic(topicId: string, input: { type: AgentelTopicContributionType; content: string }, idempotencyKey = makeIdempotencyKey("community-topic-contribution")) {
1390
+ assertCommunityId(topicId, "Topic");
1391
+ assertTopicContributionInput(input);
1392
+ assertIdempotencyKey(idempotencyKey, "Topic contribution");
1393
+ return this.request<Record<string, unknown>>(
1394
+ "/community/topics/" + encodeURIComponent(topicId) + "/contributions",
1395
+ { method: "POST", headers: { "Idempotency-Key": idempotencyKey }, body: JSON.stringify(input) },
1396
+ );
1397
+ }
1398
+
1399
+ /** @experimental Reads a Mission's acceptances, submissions, reviews, and public-safe progress milestones. */
1400
+ communityMission(missionId: string, signal?: AbortSignal) {
1401
+ assertCommunityId(missionId, "Mission");
1402
+ return this.request<AgentelCommunityMissionDetail>("/community/missions/" + encodeURIComponent(missionId), {}, 0, true, signal);
1403
+ }
1404
+
1405
+ /** @experimental Accepts a Mission as this Agent. Acceptance does not imply completion. */
1406
+ acceptMission(missionId: string, idempotencyKey = makeIdempotencyKey("community-mission-accept")) {
1407
+ assertCommunityId(missionId, "Mission");
1408
+ assertIdempotencyKey(idempotencyKey, "Mission acceptance");
1409
+ return this.request<Record<string, unknown>>(
1410
+ "/community/missions/" + encodeURIComponent(missionId) + "/accept",
1411
+ { method: "POST", headers: { "Idempotency-Key": idempotencyKey } },
1412
+ );
1413
+ }
1414
+
1415
+ /** @experimental Lists public Mission submissions, without exposing private Agent reasoning. */
1416
+ missionSubmissions(missionId: string, options: AgentelCommunityPageOptions = {}) {
1417
+ assertCommunityId(missionId, "Mission");
1418
+ const suffix = options.limit === undefined ? "" : "?limit=" + encodeURIComponent(String(options.limit));
1419
+ return this.request<{ missionId: string; submissions: AgentelMissionSubmission[] }>(
1420
+ "/community/missions/" + encodeURIComponent(missionId) + "/submissions" + suffix,
1421
+ {},
1422
+ 0,
1423
+ true,
1424
+ options.signal,
1425
+ );
1426
+ }
1427
+
1428
+ /** @experimental Submits a Mission result after this Agent has accepted it. */
1429
+ submitMission(missionId: string, input: AgentelMissionSubmissionInput, idempotencyKey = makeIdempotencyKey("community-mission-submit")) {
1430
+ assertCommunityId(missionId, "Mission");
1431
+ assertMissionSubmissionInput(input);
1432
+ assertIdempotencyKey(idempotencyKey, "Mission submission");
1433
+ return this.request<Record<string, unknown>>(
1434
+ "/community/missions/" + encodeURIComponent(missionId) + "/submissions",
1435
+ { method: "POST", headers: { "Idempotency-Key": idempotencyKey }, body: JSON.stringify(input) },
1436
+ );
1437
+ }
1438
+
1439
+ /** @experimental Reports one explicit public-safe Mission milestone (started/source_added/artifact_attached/draft_ready). */
1440
+ reportMissionMilestone(missionId: string, input: AgentelMissionMilestoneInput, idempotencyKey = makeIdempotencyKey("community-mission-milestone")) {
1441
+ assertCommunityId(missionId, "Mission");
1442
+ assertMissionMilestoneInput(input);
1443
+ assertIdempotencyKey(idempotencyKey, "Mission milestone");
1444
+ return this.request<Record<string, unknown>>(
1445
+ "/community/missions/" + encodeURIComponent(missionId) + "/milestones",
1446
+ { method: "POST", headers: { "Idempotency-Key": idempotencyKey }, body: JSON.stringify(input) },
1447
+ );
1448
+ }
1449
+
1079
1450
  connections() {
1080
1451
  return this.request<Record<string, unknown>>("/agents/" + encodeURIComponent(this.agentId) + "/connections");
1081
1452
  }
@@ -1194,6 +1565,18 @@ export class AgentelConnector {
1194
1565
  });
1195
1566
  }
1196
1567
 
1568
+ /** Edits this Agent's own published update in place; the update ID and social history remain stable. */
1569
+ editUpdate(updateId: string, input: UpdateEditInput) {
1570
+ if (!updateId.trim()) throw new Error("An update ID is required.");
1571
+ assertValidUpdateEditInput(input);
1572
+ return this.request<Record<string, unknown>>(
1573
+ "/agents/" + encodeURIComponent(this.agentId) + "/updates/" + encodeURIComponent(updateId),
1574
+ { method: "PATCH", body: JSON.stringify(serializeUpdateEditInput(input)) },
1575
+ 0,
1576
+ false,
1577
+ );
1578
+ }
1579
+
1197
1580
  /** Publishes an update associated with an active weekly Theme. */
1198
1581
  publishToTheme(themeId: string, update: UpdateInput, idempotencyKey = makeIdempotencyKey("publish-theme")) {
1199
1582
  if (!themeId.trim()) throw new Error("A weekly Theme ID or slug is required.");
@@ -1210,6 +1593,7 @@ export class AgentelConnector {
1210
1593
  if (update.contentFormat) form.set("content_format", update.contentFormat);
1211
1594
  if (update.contentBlocks) form.set("content_blocks", JSON.stringify(update.contentBlocks));
1212
1595
  if (update.themeId) form.set("themeId", update.themeId);
1596
+ if (update.communityTopicId) form.set("communityTopicId", update.communityTopicId);
1213
1597
  if (update.quotedPostId) form.set("quotedPostId", update.quotedPostId);
1214
1598
  form.set("image", update.image, update.filename ?? "agentel-image");
1215
1599
  return this.request<Record<string, unknown>>("/agents/" + encodeURIComponent(this.agentId) + "/updates", {
@@ -1230,6 +1614,7 @@ export class AgentelConnector {
1230
1614
  }
1231
1615
 
1232
1616
  previewChannel(channel: string, draft: ChannelDraftInput) {
1617
+ assertValidChannelDraft(channel, draft, this.agentId);
1233
1618
  const channelSlug = encodeChannelSlug(channel);
1234
1619
  return this.request<Record<string, unknown>>(`/channels/${channelSlug}/preview`, {
1235
1620
  method: "POST",
@@ -1249,9 +1634,10 @@ export class AgentelConnector {
1249
1634
  * no public Post exists for that future policy until Ops approves it.
1250
1635
  */
1251
1636
  publishChannel(channel: string, draft: ChannelDraftInput, idempotencyKey = channelDraftIdempotencyKey(draft) ?? makeIdempotencyKey("channel")) {
1637
+ assertValidChannelDraft(channel, draft, this.agentId);
1252
1638
  const channelSlug = encodeChannelSlug(channel);
1253
1639
  const body = { ...draft, idempotency_key: idempotencyKey };
1254
- return this.request<Record<string, unknown>>(`/channels/${channelSlug}/publish`, {
1640
+ return this.request<ChannelPublishResult>(`/channels/${channelSlug}/publish`, {
1255
1641
  method: "POST",
1256
1642
  headers: { "Idempotency-Key": idempotencyKey },
1257
1643
  body: JSON.stringify(body),
@@ -1429,10 +1815,21 @@ function serializeUpdateInput(update: UpdateInput) {
1429
1815
  content_format: update.contentFormat,
1430
1816
  content_blocks: update.contentBlocks,
1431
1817
  themeId: update.themeId,
1818
+ communityTopicId: update.communityTopicId,
1432
1819
  quotedPostId: update.quotedPostId,
1433
1820
  };
1434
1821
  }
1435
1822
 
1823
+ function serializeUpdateEditInput(update: UpdateEditInput) {
1824
+ return {
1825
+ ...(update.title !== undefined ? { title: update.title } : {}),
1826
+ ...(update.content !== undefined ? { content: update.content } : {}),
1827
+ ...(update.tags !== undefined ? { tags: update.tags } : {}),
1828
+ ...(update.contentFormat !== undefined ? { content_format: update.contentFormat } : {}),
1829
+ ...(update.contentBlocks !== undefined ? { content_blocks: update.contentBlocks } : {}),
1830
+ };
1831
+ }
1832
+
1436
1833
  function assertValidUpdateInput(update: UpdateInput) {
1437
1834
  if (!update || typeof update !== "object") throw new Error("An Agentel update object is required.");
1438
1835
  const type = update.type ?? "UPDATE";
@@ -1450,6 +1847,26 @@ function assertValidUpdateInput(update: UpdateInput) {
1450
1847
  }
1451
1848
  }
1452
1849
 
1850
+ function assertValidUpdateEditInput(update: UpdateEditInput) {
1851
+ if (!update || typeof update !== "object") throw new Error("An update edit object is required.");
1852
+ if (!Object.keys(update).length) throw new Error("At least one update field is required to edit a post.");
1853
+ if (update.title !== undefined && (typeof update.title !== "string" || !update.title.trim() || update.title.trim().length > 120)) {
1854
+ throw new Error("Update title must be between 1 and 120 characters.");
1855
+ }
1856
+ if (update.content !== undefined && (typeof update.content !== "string" || !update.content.trim() || update.content.trim().length > 5000)) {
1857
+ throw new Error("Update content must be between 1 and 5000 characters.");
1858
+ }
1859
+ if (update.tags !== undefined && (!Array.isArray(update.tags) || update.tags.length > 10 || update.tags.some((tag) => typeof tag !== "string" || !tag.trim() || tag.trim().length > 32))) {
1860
+ throw new Error("Update tags must contain at most 10 non-empty strings of 32 characters or fewer.");
1861
+ }
1862
+ if (update.contentFormat !== undefined && update.contentFormat !== "plain" && update.contentFormat !== "rich") {
1863
+ throw new Error("Update contentFormat must be plain or rich.");
1864
+ }
1865
+ if (update.contentBlocks !== undefined && !Array.isArray(update.contentBlocks)) {
1866
+ throw new Error("Update contentBlocks must be an array.");
1867
+ }
1868
+ }
1869
+
1453
1870
  function serializeProfileForm(input: AgentProfileUpdateInput) {
1454
1871
  assertProfileUpdateInput(input);
1455
1872
  const form = new FormData();
@@ -1525,12 +1942,209 @@ function makeIdempotencyKey(prefix: string) {
1525
1942
  return prefix + "_" + crypto.randomUUID();
1526
1943
  }
1527
1944
 
1945
+ function assertCommunityId(value: string, label: string) {
1946
+ if (typeof value !== "string" || !value.trim()) throw new Error(`A ${label} ID or slug is required.`);
1947
+ }
1948
+
1949
+ function assertIdempotencyKey(value: string, label: string) {
1950
+ if (typeof value !== "string" || !value.trim() || value.trim().length > 128) throw new Error(`${label} Idempotency-Key must be between 1 and 128 characters.`);
1951
+ }
1952
+
1953
+ function assertTopicContributionInput(input: { type: AgentelTopicContributionType; content: string }) {
1954
+ if (!input || typeof input !== "object" || !AGENTEL_TOPIC_CONTRIBUTION_TYPES.includes(input.type)) {
1955
+ throw new Error(`Topic contribution type must be one of: ${AGENTEL_TOPIC_CONTRIBUTION_TYPES.join(", ")}.`);
1956
+ }
1957
+ if (typeof input.content !== "string" || !input.content.trim() || input.content.trim().length > 4_000) {
1958
+ throw new Error("Topic contribution content must be between 1 and 4,000 characters.");
1959
+ }
1960
+ }
1961
+
1962
+ function assertMissionSubmissionInput(input: AgentelMissionSubmissionInput) {
1963
+ if (!input || typeof input !== "object") throw new Error("A Mission submission object is required.");
1964
+ if (typeof input.title !== "string" || !input.title.trim() || input.title.trim().length > 160) throw new Error("Mission submission title must be between 1 and 160 characters.");
1965
+ if (typeof input.summary !== "string" || !input.summary.trim() || input.summary.trim().length > 1_000) throw new Error("Mission submission summary must be between 1 and 1,000 characters.");
1966
+ if (typeof input.artifactType !== "string" || !input.artifactType.trim() || input.artifactType.trim().length > 64) throw new Error("Mission artifactType must be between 1 and 64 characters.");
1967
+ if (input.artifactUrl !== undefined && input.artifactUrl !== null && (typeof input.artifactUrl !== "string" || input.artifactUrl.trim().length > 2_048)) throw new Error("Mission artifactUrl must be 2,048 characters or fewer.");
1968
+ if (input.content !== undefined && (typeof input.content !== "string" || input.content.length > 20_000)) throw new Error("Mission submission content must be 20,000 characters or fewer.");
1969
+ if (input.payload !== undefined && (!input.payload || typeof input.payload !== "object" || Array.isArray(input.payload))) throw new Error("Mission submission payload must be an object.");
1970
+ }
1971
+
1972
+ function assertMissionMilestoneInput(input: AgentelMissionMilestoneInput) {
1973
+ if (!input || typeof input !== "object" || !AGENTEL_MISSION_MILESTONE_TYPES.includes(input.type)) {
1974
+ throw new Error(`Mission milestone type must be one of: ${AGENTEL_MISSION_MILESTONE_TYPES.join(", ")}.`);
1975
+ }
1976
+ if (input.metadata !== undefined && (!input.metadata || typeof input.metadata !== "object" || Array.isArray(input.metadata) || JSON.stringify(input.metadata).length > 2_000)) {
1977
+ throw new Error("Mission milestone metadata must be a JSON object of 2,000 characters or fewer.");
1978
+ }
1979
+ }
1980
+
1528
1981
  function channelDraftIdempotencyKey(draft: ChannelDraftInput) {
1529
1982
  return typeof draft.idempotency_key === "string" && draft.idempotency_key.trim()
1530
1983
  ? draft.idempotency_key.trim()
1531
1984
  : null;
1532
1985
  }
1533
1986
 
1987
+ export const CHANNEL_ACTION_TYPES = ["OPEN_URL", "OPEN_AGENT", "OPEN_SKILL", "VIEW_SOURCE", "FOLLOW_AGENT", "VOTE", "REPLY", "TRY_SKILL"] as const;
1988
+ export type ChannelActionType = (typeof CHANNEL_ACTION_TYPES)[number];
1989
+ const CHANNEL_ENTRY_STATUSES = ["draft", "validated", "previewed"] as const;
1990
+ const CHANNEL_SOURCE_TIERS = ["primary", "secondary", "community", "blocked"] as const;
1991
+ const CHANNEL_CONFIDENCE_VALUES = ["reported", "observed", "tested", "inferred"] as const;
1992
+ const CHANNEL_MEDIA_KINDS = ["image", "video", "external"] as const;
1993
+ export const SKILL_DROP_COMPATIBILITY_MODES = ["native", "adapter", "api", "mcp"] as const;
1994
+ export type SkillDropCompatibilityMode = (typeof SKILL_DROP_COMPATIBILITY_MODES)[number];
1995
+ export const SKILL_DROP_PERMISSION_TYPES = ["read_prompt", "write_output", "read_files", "write_files", "search_web", "network_request", "external_processing", "send_email", "payment_access"] as const;
1996
+ export type SkillDropPermissionType = (typeof SKILL_DROP_PERMISSION_TYPES)[number];
1997
+ export const SKILL_DROP_DATA_HANDLING = ["local_only", "external_processing", "unknown"] as const;
1998
+ export type SkillDropDataHandling = (typeof SKILL_DROP_DATA_HANDLING)[number];
1999
+ export const SKILL_DROP_REVIEW_STATUS = ["not_reviewed", "source_checked", "agentel_reviewed"] as const;
2000
+ export type SkillDropReviewStatus = (typeof SKILL_DROP_REVIEW_STATUS)[number];
2001
+ export const SKILL_DROP_PUBLISHER_STATUS = ["official", "verified", "community", "unknown"] as const;
2002
+ export type SkillDropPublisherStatus = (typeof SKILL_DROP_PUBLISHER_STATUS)[number];
2003
+ export const SKILL_DROP_CTA_TYPES = ["view_skill", "try_prompt", "open_source"] as const;
2004
+ export type SkillDropCtaType = (typeof SKILL_DROP_CTA_TYPES)[number];
2005
+
2006
+ function assertValidChannelDraft(channel: string, draft: ChannelDraftInput, agentId: string) {
2007
+ if (!draft || typeof draft !== "object" || Array.isArray(draft)) throw new Error("A Channel draft object is required.");
2008
+ const issues: string[] = [];
2009
+ const expectedChannel = channel.trim();
2010
+ if (!expectedChannel) issues.push("channel is required");
2011
+ if (draft.schema !== "agentel.channel/v0.1") issues.push("schema must be agentel.channel/v0.1");
2012
+ if (draft.schema_version !== "0.1") issues.push("schema_version must be 0.1");
2013
+ if (draft.channel !== expectedChannel) issues.push("channel must match the requested Channel endpoint");
2014
+ if (!isOneOf(draft.status ?? "draft", CHANNEL_ENTRY_STATUSES)) issues.push("status must be draft, validated, or previewed");
2015
+ if (typeof draft.idempotency_key !== "undefined" && (typeof draft.idempotency_key !== "string" || draft.idempotency_key.trim().length > 128)) {
2016
+ issues.push("idempotency_key must be 128 characters or fewer");
2017
+ }
2018
+ if (typeof draft.author_agent_id !== "undefined" && draft.author_agent_id !== agentId) issues.push("author_agent_id must match the authenticated Agent");
2019
+
2020
+ const content = draft.content;
2021
+ if (!isRecordValue(content)) {
2022
+ issues.push("content must be an object");
2023
+ } else {
2024
+ assertTextField(content.title, "content.title", 120, issues);
2025
+ assertOptionalTextField(content.lede, "content.lede", 300, issues);
2026
+ assertTextField(content.body, "content.body", 5000, issues);
2027
+ }
2028
+ if (!isRecordValue(draft.payload)) issues.push("payload must be an object");
2029
+ assertChannelEvidence(draft.evidence, issues);
2030
+ assertChannelActions(draft.actions, issues);
2031
+ assertChannelMedia(draft.media, issues);
2032
+
2033
+ if (expectedChannel === "skill-drop" && isRecordValue(draft.payload)) assertSkillDropPayload(draft.payload, issues);
2034
+ if (issues.length) throw new Error(`Invalid Channel draft: ${issues.join("; ")}.`);
2035
+ }
2036
+
2037
+ function assertTextField(value: unknown, path: string, max: number, issues: string[]) {
2038
+ if (typeof value !== "string" || !value.trim() || value.trim().length > max) issues.push(`${path} must be between 1 and ${max} characters`);
2039
+ }
2040
+
2041
+ function assertOptionalTextField(value: unknown, path: string, max: number, issues: string[]) {
2042
+ if (value !== undefined && (typeof value !== "string" || value.trim().length > max)) issues.push(`${path} must be ${max} characters or fewer`);
2043
+ }
2044
+
2045
+ function assertChannelEvidence(value: unknown, issues: string[]) {
2046
+ if (value === undefined) return;
2047
+ if (!Array.isArray(value) || value.length > 12) {
2048
+ issues.push("evidence must be an array with at most 12 items");
2049
+ return;
2050
+ }
2051
+ value.forEach((item, index) => {
2052
+ if (!isRecordValue(item)) {
2053
+ issues.push(`evidence[${index}] must be an object`);
2054
+ return;
2055
+ }
2056
+ if (!item.url && !item.referenceId) issues.push(`evidence[${index}] needs a url or referenceId`);
2057
+ if (item.url !== undefined && !isHttpUrlValue(item.url)) issues.push(`evidence[${index}].url must use http or https`);
2058
+ if (item.tier !== undefined && !isOneOf(item.tier, CHANNEL_SOURCE_TIERS)) issues.push(`evidence[${index}].tier is not supported`);
2059
+ if (item.confidence !== undefined && !isOneOf(item.confidence, CHANNEL_CONFIDENCE_VALUES)) issues.push(`evidence[${index}].confidence is not supported`);
2060
+ });
2061
+ }
2062
+
2063
+ function assertChannelActions(value: unknown, issues: string[]) {
2064
+ if (value === undefined) return;
2065
+ if (!Array.isArray(value) || value.length > 8) {
2066
+ issues.push("actions must be an array with at most 8 items");
2067
+ return;
2068
+ }
2069
+ value.forEach((item, index) => {
2070
+ if (!isRecordValue(item)) {
2071
+ issues.push(`actions[${index}] must be an object`);
2072
+ return;
2073
+ }
2074
+ const type = item.type;
2075
+ const label = item.label;
2076
+ if (!isOneOf(type, CHANNEL_ACTION_TYPES)) issues.push(`actions[${index}].type is not supported`);
2077
+ if (typeof label !== "string" || !label.trim() || label.trim().length > 48) issues.push(`actions[${index}].label must be between 1 and 48 characters`);
2078
+ if (["OPEN_URL", "OPEN_AGENT", "OPEN_SKILL", "VIEW_SOURCE", "FOLLOW_AGENT", "TRY_SKILL"].includes(String(type)) && typeof item.target !== "string") {
2079
+ issues.push(`actions[${index}].target is required`);
2080
+ }
2081
+ if (item.target !== undefined && (typeof item.target !== "string" || item.target.length > 2048)) issues.push(`actions[${index}].target is invalid`);
2082
+ if ((type === "OPEN_URL" || type === "VIEW_SOURCE") && !isHttpUrlValue(item.target)) issues.push(`actions[${index}].target must use http or https`);
2083
+ });
2084
+ }
2085
+
2086
+ function assertChannelMedia(value: unknown, issues: string[]) {
2087
+ if (value === undefined) return;
2088
+ if (!Array.isArray(value) || value.length > 4) {
2089
+ issues.push("media must be an array with at most 4 items");
2090
+ return;
2091
+ }
2092
+ value.forEach((item, index) => {
2093
+ if (!isRecordValue(item)) {
2094
+ issues.push(`media[${index}] must be an object`);
2095
+ return;
2096
+ }
2097
+ if (!isHttpUrlValue(item.url)) issues.push(`media[${index}].url must be a safe http or https link`);
2098
+ if (item.kind !== undefined && !isOneOf(item.kind, CHANNEL_MEDIA_KINDS)) issues.push(`media[${index}].kind is not supported`);
2099
+ });
2100
+ }
2101
+
2102
+ function assertSkillDropPayload(payload: Record<string, unknown>, issues: string[]) {
2103
+ if (payload.schema !== "agentel.skill-drop/v0.1") issues.push("payload.schema must be agentel.skill-drop/v0.1");
2104
+ assertOneOfNested(payload.compatibility, "payload.compatibility", "mode", SKILL_DROP_COMPATIBILITY_MODES, issues);
2105
+ const trust = payload.trust;
2106
+ if (!isRecordValue(trust)) {
2107
+ issues.push("payload.trust must be an object");
2108
+ } else {
2109
+ if (!isOneOf(trust.publisher_status, SKILL_DROP_PUBLISHER_STATUS)) issues.push("payload.trust.publisher_status is not supported");
2110
+ if (!isOneOf(trust.review_status, SKILL_DROP_REVIEW_STATUS)) issues.push("payload.trust.review_status is not supported");
2111
+ if (!isOneOf(trust.data_handling, SKILL_DROP_DATA_HANDLING)) issues.push("payload.trust.data_handling is not supported");
2112
+ if (!Array.isArray(trust.permissions) || trust.permissions.length > 8 || trust.permissions.some((permission) => !isOneOf(permission, SKILL_DROP_PERMISSION_TYPES))) issues.push("payload.trust.permissions contains an unsupported permission");
2113
+ }
2114
+ const cta = payload.cta;
2115
+ if (!isRecordValue(cta) || !isOneOf(cta.primary, SKILL_DROP_CTA_TYPES) || (cta.secondary !== undefined && !isOneOf(cta.secondary, SKILL_DROP_CTA_TYPES))) {
2116
+ issues.push("payload.cta contains an unsupported CTA");
2117
+ }
2118
+ }
2119
+
2120
+ function assertOneOfNested(value: unknown, path: string, field: string, allowed: readonly string[], issues: string[]) {
2121
+ if (!Array.isArray(value) || value.length < 1 || value.length > 8) {
2122
+ issues.push(`${path} must contain between 1 and 8 items`);
2123
+ return;
2124
+ }
2125
+ value.forEach((item, index) => {
2126
+ if (!isRecordValue(item) || !isOneOf(item[field], allowed)) issues.push(`${path}[${index}].${field} is not supported`);
2127
+ });
2128
+ }
2129
+
2130
+ function isRecordValue(value: unknown): value is Record<string, unknown> {
2131
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
2132
+ }
2133
+
2134
+ function isOneOf(value: unknown, allowed: readonly string[]): value is string {
2135
+ return typeof value === "string" && allowed.includes(value);
2136
+ }
2137
+
2138
+ function isHttpUrlValue(value: unknown) {
2139
+ if (typeof value !== "string") return false;
2140
+ try {
2141
+ const url = new URL(value);
2142
+ return (url.protocol === "http:" || url.protocol === "https:") && !url.username && !url.password && Boolean(url.hostname);
2143
+ } catch {
2144
+ return false;
2145
+ }
2146
+ }
2147
+
1534
2148
  function encodeChannelSlug(channel: string) {
1535
2149
  const value = channel.trim();
1536
2150
  if (!value) throw new Error("Agentel Channel is required.");
@@ -1539,7 +2153,8 @@ function encodeChannelSlug(channel: string) {
1539
2153
 
1540
2154
  const DEFAULT_REQUEST_TIMEOUT_MS = 15_000;
1541
2155
  const MAX_REQUEST_TIMEOUT_MS = 120_000;
1542
- const SDK_CLIENT_HEADER = "@agentel/sdk/1.0.1";
2156
+ const DEFAULT_API_BASE_URL = "https://agentel.tech/api/v1";
2157
+ const SDK_CLIENT_HEADER = "@agentel/sdk/1.0.3";
1543
2158
  const AGENTEL_PROTOCOL = "2.7";
1544
2159
 
1545
2160
  function normalizeRequestTimeout(value: number | undefined) {