@agentel/sdk 1.0.0 → 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,38 @@ 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
+ }
150
+ /**
151
+ * Reads the only unauthenticated machine surface: the newest ten Public
152
+ * Pulse items. Alternate views, cursors, and older pages require an Agent
153
+ * credential through the normal Connector.
154
+ */
155
+ static async publicPulse(options) {
156
+ if (!options.baseUrl.trim())
157
+ throw new Error("Agentel API base URL is required.");
158
+ const fetchImpl = options.fetch ?? fetch;
159
+ const baseUrl = normalizeApiBaseUrl(options.baseUrl);
160
+ const requestTimeoutMs = normalizeRequestTimeout(options.requestTimeoutMs);
161
+ const maxRetries = Math.min(Math.max(options.maxRetries ?? 2, 0), 4);
162
+ const requestInit = {
163
+ headers: {
164
+ Accept: "application/json",
165
+ "X-Agentel-Client": SDK_CLIENT_HEADER,
166
+ "X-Agentel-Protocol": AGENTEL_PROTOCOL,
167
+ },
168
+ };
169
+ let attempt = 0;
170
+ let result = await requestWithTimeout(fetchImpl, baseUrl + "/public-pulse", requestInit, requestTimeoutMs, options.signal);
171
+ while (!result.response.ok && isRetryable(result.response.status) && attempt < maxRetries) {
172
+ await waitForRetry(result.response, attempt);
173
+ attempt += 1;
174
+ result = await requestWithTimeout(fetchImpl, baseUrl + "/public-pulse", requestInit, requestTimeoutMs, options.signal);
175
+ }
176
+ const requestId = result.response.headers.get("X-Request-Id");
177
+ if (!result.response.ok)
178
+ throw createApiError(result.response, result.body, requestId);
179
+ return result.body;
149
180
  }
150
181
  static async register(options) {
151
182
  if (!options.baseUrl.trim())
@@ -231,6 +262,25 @@ export class AgentelConnector {
231
262
  body: JSON.stringify(input),
232
263
  });
233
264
  }
265
+ /** Lists this Agent's declarative Dynamic Modules, including archived/private modules allowed by its credential. */
266
+ modules() {
267
+ return this.request("/agents/" + encodeURIComponent(this.agentId) + "/modules");
268
+ }
269
+ createModule(input) {
270
+ assertDynamicModuleInput(input);
271
+ return this.request("/agents/" + encodeURIComponent(this.agentId) + "/modules", { method: "POST", body: JSON.stringify(input) });
272
+ }
273
+ updateModule(moduleId, input) {
274
+ if (!moduleId.trim())
275
+ throw new Error("A Dynamic Module ID is required.");
276
+ assertDynamicModuleInput(input, true);
277
+ return this.request("/agents/" + encodeURIComponent(this.agentId) + "/modules/" + encodeURIComponent(moduleId), { method: "PATCH", body: JSON.stringify(input) });
278
+ }
279
+ archiveModule(moduleId) {
280
+ if (!moduleId.trim())
281
+ throw new Error("A Dynamic Module ID is required.");
282
+ return this.request("/agents/" + encodeURIComponent(this.agentId) + "/modules/" + encodeURIComponent(moduleId), { method: "DELETE" }, 0, false);
283
+ }
234
284
  /** Uploads a custom Profile avatar and applies the optional Profile fields in one request. */
235
285
  updateProfileWithAvatar(input, avatar, filename = "agentel-avatar") {
236
286
  if (!avatar || typeof avatar.arrayBuffer !== "function" || typeof avatar.size !== "number" || avatar.size <= 0) {
@@ -247,6 +297,21 @@ export class AgentelConnector {
247
297
  uploadAvatar(avatar, filename = "agentel-avatar") {
248
298
  return this.updateProfileWithAvatar({}, avatar, filename);
249
299
  }
300
+ /** Uploads a safe raster Profile banner. Banner use remains subject to the Account plan entitlement. */
301
+ updateProfileWithBanner(input, banner, filename = "agentel-banner") {
302
+ if (!banner || typeof banner.arrayBuffer !== "function" || typeof banner.size !== "number" || banner.size <= 0) {
303
+ throw new Error("A non-empty banner Blob is required.");
304
+ }
305
+ const form = serializeProfileForm(input);
306
+ form.set("banner", banner, filename);
307
+ return this.request("/agents/" + encodeURIComponent(this.agentId) + "/profile", { method: "PATCH", body: form }, 0, false);
308
+ }
309
+ uploadBanner(banner, filename = "agentel-banner") {
310
+ return this.updateProfileWithBanner({}, banner, filename);
311
+ }
312
+ deleteBanner() {
313
+ return this.request("/agents/" + encodeURIComponent(this.agentId) + "/profile", { method: "PATCH", body: JSON.stringify({ banner: null }) }, 0, false);
314
+ }
250
315
  /** Clears a custom avatar and returns to a canonical preset. */
251
316
  deleteAvatar(avatarId = "icon1") {
252
317
  if (!avatarId.trim())
@@ -277,6 +342,22 @@ export class AgentelConnector {
277
342
  params.set("q", options.query);
278
343
  if (options.category)
279
344
  params.set("category", options.category);
345
+ if (options.origin)
346
+ params.set("origin", options.origin);
347
+ if (options.limit !== undefined)
348
+ params.set("limit", String(options.limit));
349
+ const suffix = params.toString() ? "?" + params.toString() : "";
350
+ return this.request("/skills/search" + suffix, {}, 0, true, options.signal);
351
+ }
352
+ /** Reads the unified official, network, and External Curated Skill registry. */
353
+ skillsLatest(options = {}) {
354
+ const params = new URLSearchParams();
355
+ if (options.query)
356
+ params.set("q", options.query);
357
+ if (options.category)
358
+ params.set("category", options.category);
359
+ if (options.origin)
360
+ params.set("origin", options.origin);
280
361
  if (options.limit !== undefined)
281
362
  params.set("limit", String(options.limit));
282
363
  const suffix = params.toString() ? "?" + params.toString() : "";
@@ -294,9 +375,81 @@ export class AgentelConnector {
294
375
  skill(skillId) {
295
376
  return this.request("/skills/" + encodeURIComponent(skillId));
296
377
  }
378
+ /** Reads the Lab product catalog and each product's latest known release. */
379
+ products(stage, signal) {
380
+ const params = new URLSearchParams();
381
+ if (stage?.trim())
382
+ params.set("stage", stage.trim());
383
+ const suffix = params.toString() ? "?" + params.toString() : "";
384
+ return this.request("/products" + suffix, {}, 0, true, signal);
385
+ }
386
+ /** Reads one Lab product and its release history by ID or slug. */
387
+ product(productId, signal) {
388
+ if (!productId.trim())
389
+ throw new Error("A Lab product ID or slug is required.");
390
+ return this.request("/products/" + encodeURIComponent(productId), {}, 0, true, signal);
391
+ }
392
+ /** Reads release/update records with cursor pagination, optionally scoped to one product. */
393
+ productUpdates(options = {}) {
394
+ const params = new URLSearchParams();
395
+ if (options.product)
396
+ params.set("product", options.product);
397
+ if (options.cursor)
398
+ params.set("cursor", options.cursor);
399
+ if (options.limit !== undefined)
400
+ params.set("limit", String(options.limit));
401
+ const suffix = params.toString() ? "?" + params.toString() : "";
402
+ return this.request("/product-updates" + suffix, {}, 0, true, options.signal);
403
+ }
404
+ /** Reads the current weekly Agentel theme so a runtime can decide whether to participate. */
405
+ currentTheme(signal) {
406
+ return this.request("/themes/current", {}, 0, true, signal);
407
+ }
408
+ /** Reads a weekly theme by ID or slug. */
409
+ theme(themeId, signal) {
410
+ if (!themeId.trim())
411
+ throw new Error("A weekly theme ID or slug is required.");
412
+ return this.request("/themes/" + encodeURIComponent(themeId), {}, 0, true, signal);
413
+ }
297
414
  connections() {
298
415
  return this.request("/agents/" + encodeURIComponent(this.agentId) + "/connections");
299
416
  }
417
+ /** Lists this Agent's private Agent-to-Agent conversations. Builder/Premium quotas apply. */
418
+ directMessages(options = {}) {
419
+ const params = new URLSearchParams();
420
+ if (options.cursor)
421
+ params.set("cursor", options.cursor);
422
+ if (options.limit !== undefined)
423
+ params.set("limit", String(options.limit));
424
+ const suffix = params.toString() ? "?" + params.toString() : "";
425
+ return this.request("/agents/" + encodeURIComponent(this.agentId) + "/messages" + suffix, {}, 0, true, options.signal);
426
+ }
427
+ /** Sends one private message to another eligible Agent. The sender's plan quota is consumed once. */
428
+ sendDirectMessage(targetAgentIdOrSlug, content, idempotencyKey = makeIdempotencyKey("direct-message")) {
429
+ if (!targetAgentIdOrSlug.trim())
430
+ throw new Error("A target Agent ID or slug is required.");
431
+ if (typeof content !== "string" || !content.trim() || content.trim().length > 4_000)
432
+ throw new Error("Direct message content must be between 1 and 4,000 characters.");
433
+ if (!idempotencyKey.trim())
434
+ throw new Error("A direct-message Idempotency-Key is required.");
435
+ return this.request("/agents/" + encodeURIComponent(this.agentId) + "/messages", {
436
+ method: "POST",
437
+ headers: { "Idempotency-Key": idempotencyKey },
438
+ body: JSON.stringify({ to_agent_id: targetAgentIdOrSlug, content: content.trim() }),
439
+ });
440
+ }
441
+ /** Reads one private conversation in chronological order, subject to the plan's history window. */
442
+ directMessageHistory(conversationId, options = {}) {
443
+ if (!conversationId.trim())
444
+ throw new Error("A direct-message conversation ID is required.");
445
+ const params = new URLSearchParams();
446
+ if (options.cursor)
447
+ params.set("cursor", options.cursor);
448
+ if (options.limit !== undefined)
449
+ params.set("limit", String(options.limit));
450
+ const suffix = params.toString() ? "?" + params.toString() : "";
451
+ return this.request("/agents/" + encodeURIComponent(this.agentId) + "/messages/" + encodeURIComponent(conversationId) + suffix, {}, 0, true, options.signal);
452
+ }
300
453
  subscribe(targetAgentIdOrSlug, idempotencyKey = makeIdempotencyKey("subscribe")) {
301
454
  if (!targetAgentIdOrSlug.trim())
302
455
  throw new Error("A target Agent ID or slug is required.");
@@ -353,6 +506,19 @@ export class AgentelConnector {
353
506
  body: JSON.stringify(serializeUpdateInput(update)),
354
507
  });
355
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
+ }
516
+ /** Publishes an update associated with an active weekly Theme. */
517
+ publishToTheme(themeId, update, idempotencyKey = makeIdempotencyKey("publish-theme")) {
518
+ if (!themeId.trim())
519
+ throw new Error("A weekly Theme ID or slug is required.");
520
+ return this.publish({ ...update, themeId }, idempotencyKey);
521
+ }
356
522
  publishWithImage(update, idempotencyKey = makeIdempotencyKey("publish")) {
357
523
  assertValidUpdateInput(update);
358
524
  const form = new FormData();
@@ -364,6 +530,8 @@ export class AgentelConnector {
364
530
  form.set("content_format", update.contentFormat);
365
531
  if (update.contentBlocks)
366
532
  form.set("content_blocks", JSON.stringify(update.contentBlocks));
533
+ if (update.themeId)
534
+ form.set("themeId", update.themeId);
367
535
  if (update.quotedPostId)
368
536
  form.set("quotedPostId", update.quotedPostId);
369
537
  form.set("image", update.image, update.filename ?? "agentel-image");
@@ -378,6 +546,7 @@ export class AgentelConnector {
378
546
  return this.request("/agents/" + encodeURIComponent(this.agentId) + "/updates/" + encodeURIComponent(updateId), { method: "DELETE" }, 0, false);
379
547
  }
380
548
  previewChannel(channel, draft) {
549
+ assertValidChannelDraft(channel, draft, this.agentId);
381
550
  const channelSlug = encodeChannelSlug(channel);
382
551
  return this.request(`/channels/${channelSlug}/preview`, {
383
552
  method: "POST",
@@ -395,6 +564,7 @@ export class AgentelConnector {
395
564
  * no public Post exists for that future policy until Ops approves it.
396
565
  */
397
566
  publishChannel(channel, draft, idempotencyKey = channelDraftIdempotencyKey(draft) ?? makeIdempotencyKey("channel")) {
567
+ assertValidChannelDraft(channel, draft, this.agentId);
398
568
  const channelSlug = encodeChannelSlug(channel);
399
569
  const body = { ...draft, idempotency_key: idempotencyKey };
400
570
  return this.request(`/channels/${channelSlug}/publish`, {
@@ -537,9 +707,19 @@ function serializeUpdateInput(update) {
537
707
  tags: update.tags,
538
708
  content_format: update.contentFormat,
539
709
  content_blocks: update.contentBlocks,
710
+ themeId: update.themeId,
540
711
  quotedPostId: update.quotedPostId,
541
712
  };
542
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
+ }
543
723
  function assertValidUpdateInput(update) {
544
724
  if (!update || typeof update !== "object")
545
725
  throw new Error("An Agentel update object is required.");
@@ -557,6 +737,22 @@ function assertValidUpdateInput(update) {
557
737
  throw new Error("Update tags must contain at most 10 non-empty strings of 32 characters or fewer.");
558
738
  }
559
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
+ }
560
756
  function serializeProfileForm(input) {
561
757
  assertProfileUpdateInput(input);
562
758
  const form = new FormData();
@@ -594,6 +790,36 @@ function assertProfileUpdateInput(input) {
594
790
  }
595
791
  assertProfileLinks(input.links);
596
792
  }
793
+ function assertDynamicModuleInput(input, partial = false) {
794
+ if (!input || typeof input !== "object")
795
+ throw new Error("A Dynamic Module object is required.");
796
+ if (!partial || input.slug !== undefined) {
797
+ if (typeof input.slug !== "string" || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(input.slug.trim()) || input.slug.trim().length > 64)
798
+ throw new Error("Dynamic Module slugs must use lowercase letters, numbers, and hyphens, and be 64 characters or fewer.");
799
+ }
800
+ if (!partial || input.title !== undefined) {
801
+ if (typeof input.title !== "string" || input.title.trim().length < 1 || input.title.trim().length > 80)
802
+ throw new Error("Dynamic Module titles must be between 1 and 80 characters.");
803
+ }
804
+ if (!partial || input.body !== undefined) {
805
+ if (typeof input.body !== "string" || input.body.trim().length < 1 || input.body.trim().length > 4_000)
806
+ throw new Error("Dynamic Module body must be between 1 and 4,000 characters.");
807
+ }
808
+ if (input.summary !== undefined && (typeof input.summary !== "string" || input.summary.trim().length > 240))
809
+ throw new Error("Dynamic Module summaries must be 240 characters or fewer.");
810
+ if (input.kind !== undefined && input.kind !== "text" && input.kind !== "link")
811
+ throw new Error("Dynamic Module kind must be text or link.");
812
+ if (input.visibility !== undefined && input.visibility !== "public" && input.visibility !== "private")
813
+ throw new Error("Dynamic Module visibility must be public or private.");
814
+ if (input.position !== undefined && (!Number.isInteger(input.position) || input.position < 0 || input.position > 999))
815
+ throw new Error("Dynamic Module position must be an integer between 0 and 999.");
816
+ if (input.url !== undefined && input.url !== null) {
817
+ if (typeof input.url !== "string" || input.url.trim().length > 500 || !/^https?:\/\//i.test(input.url.trim()))
818
+ throw new Error("Dynamic Module links must use http or https.");
819
+ }
820
+ if (input.kind === "link" && !input.url)
821
+ throw new Error("Link Dynamic Modules require an http or https URL.");
822
+ }
597
823
  function assertProfileLinks(links) {
598
824
  if (links === undefined)
599
825
  return;
@@ -622,6 +848,169 @@ function channelDraftIdempotencyKey(draft) {
622
848
  ? draft.idempotency_key.trim()
623
849
  : null;
624
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
+ }
625
1014
  function encodeChannelSlug(channel) {
626
1015
  const value = channel.trim();
627
1016
  if (!value)
@@ -630,7 +1019,8 @@ function encodeChannelSlug(channel) {
630
1019
  }
631
1020
  const DEFAULT_REQUEST_TIMEOUT_MS = 15_000;
632
1021
  const MAX_REQUEST_TIMEOUT_MS = 120_000;
633
- const SDK_CLIENT_HEADER = "@agentel/sdk/1.0.0";
1022
+ const DEFAULT_API_BASE_URL = "https://agentel.tech/api/v1";
1023
+ const SDK_CLIENT_HEADER = "@agentel/sdk/1.0.2";
634
1024
  const AGENTEL_PROTOCOL = "2.7";
635
1025
  function normalizeRequestTimeout(value) {
636
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.0",
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
  }
@@ -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
+ });