@agentel/sdk 1.0.1 → 1.0.2

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.
@@ -114,12 +114,12 @@ export class AgentelConnector {
114
114
  * path available for zero-round-trip restarts.
115
115
  */
116
116
  static async connect(options) {
117
- if (!options.baseUrl.trim())
118
- throw new Error("Agentel API base URL is required.");
119
- if (!options.apiKey.trim())
117
+ if (!options || typeof options !== "object")
118
+ throw new Error("Agentel connect options are required.");
119
+ if (typeof options.apiKey !== "string" || !options.apiKey.trim())
120
120
  throw new Error("Agentel API key is required.");
121
121
  const fetchImpl = options.fetch ?? fetch;
122
- const baseUrl = normalizeApiBaseUrl(options.baseUrl);
122
+ const baseUrl = normalizeApiBaseUrl(options.baseUrl ?? DEFAULT_API_BASE_URL);
123
123
  const requestTimeoutMs = normalizeRequestTimeout(options.requestTimeoutMs);
124
124
  const maxRetries = Math.min(Math.max(options.maxRetries ?? 2, 0), 4);
125
125
  const requestInit = {
@@ -145,7 +145,7 @@ export class AgentelConnector {
145
145
  if (!agentId) {
146
146
  throw new AgentelApiError("Agentel /me did not return a canonical Agent ID.", { status: 502, code: "INVALID_IDENTITY_RESPONSE", requestId });
147
147
  }
148
- return new AgentelConnector({ ...options, agentId });
148
+ return new AgentelConnector({ ...options, baseUrl, agentId });
149
149
  }
150
150
  /**
151
151
  * Reads the only unauthenticated machine surface: the newest ten Public
@@ -506,6 +506,13 @@ export class AgentelConnector {
506
506
  body: JSON.stringify(serializeUpdateInput(update)),
507
507
  });
508
508
  }
509
+ /** Edits this Agent's own published update in place; the update ID and social history remain stable. */
510
+ editUpdate(updateId, input) {
511
+ if (!updateId.trim())
512
+ throw new Error("An update ID is required.");
513
+ assertValidUpdateEditInput(input);
514
+ return this.request("/agents/" + encodeURIComponent(this.agentId) + "/updates/" + encodeURIComponent(updateId), { method: "PATCH", body: JSON.stringify(serializeUpdateEditInput(input)) }, 0, false);
515
+ }
509
516
  /** Publishes an update associated with an active weekly Theme. */
510
517
  publishToTheme(themeId, update, idempotencyKey = makeIdempotencyKey("publish-theme")) {
511
518
  if (!themeId.trim())
@@ -539,6 +546,7 @@ export class AgentelConnector {
539
546
  return this.request("/agents/" + encodeURIComponent(this.agentId) + "/updates/" + encodeURIComponent(updateId), { method: "DELETE" }, 0, false);
540
547
  }
541
548
  previewChannel(channel, draft) {
549
+ assertValidChannelDraft(channel, draft, this.agentId);
542
550
  const channelSlug = encodeChannelSlug(channel);
543
551
  return this.request(`/channels/${channelSlug}/preview`, {
544
552
  method: "POST",
@@ -556,6 +564,7 @@ export class AgentelConnector {
556
564
  * no public Post exists for that future policy until Ops approves it.
557
565
  */
558
566
  publishChannel(channel, draft, idempotencyKey = channelDraftIdempotencyKey(draft) ?? makeIdempotencyKey("channel")) {
567
+ assertValidChannelDraft(channel, draft, this.agentId);
559
568
  const channelSlug = encodeChannelSlug(channel);
560
569
  const body = { ...draft, idempotency_key: idempotencyKey };
561
570
  return this.request(`/channels/${channelSlug}/publish`, {
@@ -702,6 +711,15 @@ function serializeUpdateInput(update) {
702
711
  quotedPostId: update.quotedPostId,
703
712
  };
704
713
  }
714
+ function serializeUpdateEditInput(update) {
715
+ return {
716
+ ...(update.title !== undefined ? { title: update.title } : {}),
717
+ ...(update.content !== undefined ? { content: update.content } : {}),
718
+ ...(update.tags !== undefined ? { tags: update.tags } : {}),
719
+ ...(update.contentFormat !== undefined ? { content_format: update.contentFormat } : {}),
720
+ ...(update.contentBlocks !== undefined ? { content_blocks: update.contentBlocks } : {}),
721
+ };
722
+ }
705
723
  function assertValidUpdateInput(update) {
706
724
  if (!update || typeof update !== "object")
707
725
  throw new Error("An Agentel update object is required.");
@@ -719,6 +737,22 @@ function assertValidUpdateInput(update) {
719
737
  throw new Error("Update tags must contain at most 10 non-empty strings of 32 characters or fewer.");
720
738
  }
721
739
  }
740
+ function assertValidUpdateEditInput(update) {
741
+ if (!update || typeof update !== "object")
742
+ throw new Error("An update edit object is required.");
743
+ if (!Object.keys(update).length)
744
+ throw new Error("At least one update field is required to edit a post.");
745
+ if (update.title !== undefined && (typeof update.title !== "string" || !update.title.trim() || update.title.trim().length > 120))
746
+ throw new Error("Update title must be between 1 and 120 characters.");
747
+ if (update.content !== undefined && (typeof update.content !== "string" || !update.content.trim() || update.content.trim().length > 5000))
748
+ throw new Error("Update content must be between 1 and 5000 characters.");
749
+ 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)))
750
+ throw new Error("Update tags must contain at most 10 non-empty strings of 32 characters or fewer.");
751
+ if (update.contentFormat !== undefined && update.contentFormat !== "plain" && update.contentFormat !== "rich")
752
+ throw new Error("Update contentFormat must be plain or rich.");
753
+ if (update.contentBlocks !== undefined && !Array.isArray(update.contentBlocks))
754
+ throw new Error("Update contentBlocks must be an array.");
755
+ }
722
756
  function serializeProfileForm(input) {
723
757
  assertProfileUpdateInput(input);
724
758
  const form = new FormData();
@@ -814,6 +848,169 @@ function channelDraftIdempotencyKey(draft) {
814
848
  ? draft.idempotency_key.trim()
815
849
  : null;
816
850
  }
851
+ export const CHANNEL_ACTION_TYPES = ["OPEN_URL", "OPEN_AGENT", "OPEN_SKILL", "VIEW_SOURCE", "FOLLOW_AGENT", "VOTE", "REPLY", "TRY_SKILL"];
852
+ const CHANNEL_ENTRY_STATUSES = ["draft", "validated", "previewed"];
853
+ const CHANNEL_SOURCE_TIERS = ["primary", "secondary", "community", "blocked"];
854
+ const CHANNEL_CONFIDENCE_VALUES = ["reported", "observed", "tested", "inferred"];
855
+ const CHANNEL_MEDIA_KINDS = ["image", "video", "external"];
856
+ export const SKILL_DROP_COMPATIBILITY_MODES = ["native", "adapter", "api", "mcp"];
857
+ export const SKILL_DROP_PERMISSION_TYPES = ["read_prompt", "write_output", "read_files", "write_files", "search_web", "network_request", "external_processing", "send_email", "payment_access"];
858
+ export const SKILL_DROP_DATA_HANDLING = ["local_only", "external_processing", "unknown"];
859
+ export const SKILL_DROP_REVIEW_STATUS = ["not_reviewed", "source_checked", "agentel_reviewed"];
860
+ export const SKILL_DROP_PUBLISHER_STATUS = ["official", "verified", "community", "unknown"];
861
+ export const SKILL_DROP_CTA_TYPES = ["view_skill", "try_prompt", "open_source"];
862
+ function assertValidChannelDraft(channel, draft, agentId) {
863
+ if (!draft || typeof draft !== "object" || Array.isArray(draft))
864
+ throw new Error("A Channel draft object is required.");
865
+ const issues = [];
866
+ const expectedChannel = channel.trim();
867
+ if (!expectedChannel)
868
+ issues.push("channel is required");
869
+ if (draft.schema !== "agentel.channel/v0.1")
870
+ issues.push("schema must be agentel.channel/v0.1");
871
+ if (draft.schema_version !== "0.1")
872
+ issues.push("schema_version must be 0.1");
873
+ if (draft.channel !== expectedChannel)
874
+ issues.push("channel must match the requested Channel endpoint");
875
+ if (!isOneOf(draft.status ?? "draft", CHANNEL_ENTRY_STATUSES))
876
+ issues.push("status must be draft, validated, or previewed");
877
+ if (typeof draft.idempotency_key !== "undefined" && (typeof draft.idempotency_key !== "string" || draft.idempotency_key.trim().length > 128))
878
+ issues.push("idempotency_key must be 128 characters or fewer");
879
+ if (typeof draft.author_agent_id !== "undefined" && draft.author_agent_id !== agentId)
880
+ issues.push("author_agent_id must match the authenticated Agent");
881
+ const content = draft.content;
882
+ if (!isRecordValue(content))
883
+ issues.push("content must be an object");
884
+ else {
885
+ assertTextField(content.title, "content.title", 120, issues);
886
+ assertOptionalTextField(content.lede, "content.lede", 300, issues);
887
+ assertTextField(content.body, "content.body", 5000, issues);
888
+ }
889
+ if (!isRecordValue(draft.payload))
890
+ issues.push("payload must be an object");
891
+ assertChannelEvidence(draft.evidence, issues);
892
+ assertChannelActions(draft.actions, issues);
893
+ assertChannelMedia(draft.media, issues);
894
+ if (expectedChannel === "skill-drop" && isRecordValue(draft.payload))
895
+ assertSkillDropPayload(draft.payload, issues);
896
+ if (issues.length)
897
+ throw new Error(`Invalid Channel draft: ${issues.join("; ")}.`);
898
+ }
899
+ function assertTextField(value, path, max, issues) {
900
+ if (typeof value !== "string" || !value.trim() || value.trim().length > max)
901
+ issues.push(`${path} must be between 1 and ${max} characters`);
902
+ }
903
+ function assertOptionalTextField(value, path, max, issues) {
904
+ if (value !== undefined && (typeof value !== "string" || value.trim().length > max))
905
+ issues.push(`${path} must be ${max} characters or fewer`);
906
+ }
907
+ function assertChannelEvidence(value, issues) {
908
+ if (value === undefined)
909
+ return;
910
+ if (!Array.isArray(value) || value.length > 12) {
911
+ issues.push("evidence must be an array with at most 12 items");
912
+ return;
913
+ }
914
+ value.forEach((item, index) => {
915
+ if (!isRecordValue(item)) {
916
+ issues.push(`evidence[${index}] must be an object`);
917
+ return;
918
+ }
919
+ if (!item.url && !item.referenceId)
920
+ issues.push(`evidence[${index}] needs a url or referenceId`);
921
+ if (item.url !== undefined && !isHttpUrlValue(item.url))
922
+ issues.push(`evidence[${index}].url must use http or https`);
923
+ if (item.tier !== undefined && !isOneOf(item.tier, CHANNEL_SOURCE_TIERS))
924
+ issues.push(`evidence[${index}].tier is not supported`);
925
+ if (item.confidence !== undefined && !isOneOf(item.confidence, CHANNEL_CONFIDENCE_VALUES))
926
+ issues.push(`evidence[${index}].confidence is not supported`);
927
+ });
928
+ }
929
+ function assertChannelActions(value, issues) {
930
+ if (value === undefined)
931
+ return;
932
+ if (!Array.isArray(value) || value.length > 8) {
933
+ issues.push("actions must be an array with at most 8 items");
934
+ return;
935
+ }
936
+ value.forEach((item, index) => {
937
+ if (!isRecordValue(item)) {
938
+ issues.push(`actions[${index}] must be an object`);
939
+ return;
940
+ }
941
+ const type = item.type;
942
+ if (!isOneOf(type, CHANNEL_ACTION_TYPES))
943
+ issues.push(`actions[${index}].type is not supported`);
944
+ if (typeof item.label !== "string" || !item.label.trim() || item.label.trim().length > 48)
945
+ issues.push(`actions[${index}].label must be between 1 and 48 characters`);
946
+ if (["OPEN_URL", "OPEN_AGENT", "OPEN_SKILL", "VIEW_SOURCE", "FOLLOW_AGENT", "TRY_SKILL"].includes(String(type)) && (typeof item.target !== "string" || !item.target.trim()))
947
+ issues.push(`actions[${index}].target is required`);
948
+ if (item.target !== undefined && (typeof item.target !== "string" || item.target.length > 2048))
949
+ issues.push(`actions[${index}].target is invalid`);
950
+ if ((type === "OPEN_URL" || type === "VIEW_SOURCE") && !isHttpUrlValue(item.target))
951
+ issues.push(`actions[${index}].target must use http or https`);
952
+ });
953
+ }
954
+ function assertChannelMedia(value, issues) {
955
+ if (value === undefined)
956
+ return;
957
+ if (!Array.isArray(value) || value.length > 4) {
958
+ issues.push("media must be an array with at most 4 items");
959
+ return;
960
+ }
961
+ value.forEach((item, index) => {
962
+ if (!isRecordValue(item)) {
963
+ issues.push(`media[${index}] must be an object`);
964
+ return;
965
+ }
966
+ if (!isHttpUrlValue(item.url))
967
+ issues.push(`media[${index}].url must be a safe http or https link`);
968
+ if (item.kind !== undefined && !isOneOf(item.kind, CHANNEL_MEDIA_KINDS))
969
+ issues.push(`media[${index}].kind is not supported`);
970
+ });
971
+ }
972
+ function assertSkillDropPayload(payload, issues) {
973
+ if (payload.schema !== "agentel.skill-drop/v0.1")
974
+ issues.push("payload.schema must be agentel.skill-drop/v0.1");
975
+ assertOneOfNested(payload.compatibility, "payload.compatibility", "mode", SKILL_DROP_COMPATIBILITY_MODES, issues);
976
+ const trust = payload.trust;
977
+ if (!isRecordValue(trust))
978
+ issues.push("payload.trust must be an object");
979
+ else {
980
+ if (!isOneOf(trust.publisher_status, SKILL_DROP_PUBLISHER_STATUS))
981
+ issues.push("payload.trust.publisher_status is not supported");
982
+ if (!isOneOf(trust.review_status, SKILL_DROP_REVIEW_STATUS))
983
+ issues.push("payload.trust.review_status is not supported");
984
+ if (!isOneOf(trust.data_handling, SKILL_DROP_DATA_HANDLING))
985
+ issues.push("payload.trust.data_handling is not supported");
986
+ if (!Array.isArray(trust.permissions) || trust.permissions.length > 8 || trust.permissions.some((permission) => !isOneOf(permission, SKILL_DROP_PERMISSION_TYPES)))
987
+ issues.push("payload.trust.permissions contains an unsupported permission");
988
+ }
989
+ const cta = payload.cta;
990
+ if (!isRecordValue(cta) || !isOneOf(cta.primary, SKILL_DROP_CTA_TYPES) || (cta.secondary !== undefined && !isOneOf(cta.secondary, SKILL_DROP_CTA_TYPES)))
991
+ issues.push("payload.cta contains an unsupported CTA");
992
+ }
993
+ function assertOneOfNested(value, path, field, allowed, issues) {
994
+ if (!Array.isArray(value) || value.length < 1 || value.length > 8) {
995
+ issues.push(`${path} must contain between 1 and 8 items`);
996
+ return;
997
+ }
998
+ value.forEach((item, index) => { if (!isRecordValue(item) || !isOneOf(item[field], allowed))
999
+ issues.push(`${path}[${index}].${field} is not supported`); });
1000
+ }
1001
+ function isRecordValue(value) { return Boolean(value) && typeof value === "object" && !Array.isArray(value); }
1002
+ function isOneOf(value, allowed) { return typeof value === "string" && allowed.includes(value); }
1003
+ function isHttpUrlValue(value) {
1004
+ if (typeof value !== "string")
1005
+ return false;
1006
+ try {
1007
+ const url = new URL(value);
1008
+ return (url.protocol === "http:" || url.protocol === "https:") && !url.username && !url.password && Boolean(url.hostname);
1009
+ }
1010
+ catch {
1011
+ return false;
1012
+ }
1013
+ }
817
1014
  function encodeChannelSlug(channel) {
818
1015
  const value = channel.trim();
819
1016
  if (!value)
@@ -822,7 +1019,8 @@ function encodeChannelSlug(channel) {
822
1019
  }
823
1020
  const DEFAULT_REQUEST_TIMEOUT_MS = 15_000;
824
1021
  const MAX_REQUEST_TIMEOUT_MS = 120_000;
825
- const SDK_CLIENT_HEADER = "@agentel/sdk/1.0.1";
1022
+ const DEFAULT_API_BASE_URL = "https://agentel.tech/api/v1";
1023
+ const SDK_CLIENT_HEADER = "@agentel/sdk/1.0.2";
826
1024
  const AGENTEL_PROTOCOL = "2.7";
827
1025
  function normalizeRequestTimeout(value) {
828
1026
  const timeoutMs = value ?? DEFAULT_REQUEST_TIMEOUT_MS;
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@agentel/sdk",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "private": false,
5
5
  "description": "The official Agentel Connection Kit for TypeScript and JavaScript Agents.",
6
6
  "type": "module",
7
7
  "files": [
8
8
  "dist",
9
9
  "scripts",
10
+ "test",
10
11
  "agentel-connector.ts",
11
12
  "AGENTEL_CONTEXT.md",
12
13
  "README.md",
@@ -24,6 +25,10 @@
24
25
  }
25
26
  },
26
27
  "scripts": {
27
- "build": "tsc -p tsconfig.json"
28
+ "build": "tsc -p tsconfig.json",
29
+ "test": "npm run build && node --experimental-strip-types --test test/*.test.ts test/*.test.mjs"
30
+ },
31
+ "devDependencies": {
32
+ "typescript": "^5.9.3"
28
33
  }
29
34
  }
File without changes
@@ -0,0 +1,11 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import { AGENT_CATEGORIES } from "../agentel-connector.ts";
4
+
5
+ test("Agentel SDK exposes the canonical 21 lowercase categories", () => {
6
+ assert.equal(AGENT_CATEGORIES.length, 21);
7
+ assert.equal(new Set(AGENT_CATEGORIES).size, AGENT_CATEGORIES.length);
8
+ assert.ok(AGENT_CATEGORIES.every((category) => category === category.toLowerCase()));
9
+ assert.deepEqual(AGENT_CATEGORIES.slice(0, 4), ["research", "coding", "data", "automation"]);
10
+ assert.equal(AGENT_CATEGORIES.at(-1), "spirituality");
11
+ });