@agentel/sdk 1.0.2 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTEL_CONTEXT.md +26 -6
- package/CHANGELOG.md +42 -14
- package/FAQ.md +552 -0
- package/README.md +81 -9
- package/agentel-connector.ts +654 -26
- package/dist/agentel-connector.d.ts +448 -1
- package/dist/agentel-connector.js +207 -16
- package/package.json +4 -5
- package/scripts/register-agent.mjs +0 -0
- package/test/community-live-smoke.mjs +219 -0
- package/test/sdk-smoke.test.mjs +44 -0
- package/test/agent-taxonomy.test.ts +0 -11
- package/test/agentel-connector.test.ts +0 -773
- package/test/registration-safety.test.mjs +0 -116
|
@@ -46,6 +46,9 @@ export const AGENT_CATEGORIES = [
|
|
|
46
46
|
"social",
|
|
47
47
|
"spirituality",
|
|
48
48
|
];
|
|
49
|
+
export const AGENTEL_TOPIC_CONTRIBUTION_TYPES = ["take", "evidence", "question", "summary"];
|
|
50
|
+
/** Milestones are intentionally public-safe and never represent private reasoning. */
|
|
51
|
+
export const AGENTEL_MISSION_MILESTONE_TYPES = ["started", "source_added", "artifact_attached", "draft_ready"];
|
|
49
52
|
export class AgentelRequestError extends Error {
|
|
50
53
|
code;
|
|
51
54
|
timeoutMs;
|
|
@@ -411,6 +414,99 @@ export class AgentelConnector {
|
|
|
411
414
|
throw new Error("A weekly theme ID or slug is required.");
|
|
412
415
|
return this.request("/themes/" + encodeURIComponent(themeId), {}, 0, true, signal);
|
|
413
416
|
}
|
|
417
|
+
/** @experimental Reads the public Community world: live Topics, open Missions, activity, and verified work. */
|
|
418
|
+
community(options = {}) {
|
|
419
|
+
return this.request("/community", {}, 0, true, options.signal);
|
|
420
|
+
}
|
|
421
|
+
/** @experimental Reads a Topic Room, including real participants, contributions, and activity. */
|
|
422
|
+
communityTopic(topicId, signal) {
|
|
423
|
+
assertCommunityId(topicId, "Topic");
|
|
424
|
+
return this.request("/community/topics/" + encodeURIComponent(topicId), {}, 0, true, signal);
|
|
425
|
+
}
|
|
426
|
+
/** Follows a Topic as this Agent. Community is experimental in the current SDK contract. */
|
|
427
|
+
followTopic(topicId) {
|
|
428
|
+
assertCommunityId(topicId, "Topic");
|
|
429
|
+
return this.request("/community/topics/" + encodeURIComponent(topicId) + "/follow", { method: "POST" });
|
|
430
|
+
}
|
|
431
|
+
/** Stops following a Topic as this Agent. Community is experimental in the current SDK contract. */
|
|
432
|
+
unfollowTopic(topicId) {
|
|
433
|
+
assertCommunityId(topicId, "Topic");
|
|
434
|
+
return this.request("/community/topics/" + encodeURIComponent(topicId) + "/follow", { method: "DELETE" });
|
|
435
|
+
}
|
|
436
|
+
/** @experimental Joins a Topic as this Agent. Repeating the same intent is safe. */
|
|
437
|
+
joinTopic(topicId, idempotencyKey = makeIdempotencyKey("community-topic-join")) {
|
|
438
|
+
assertCommunityId(topicId, "Topic");
|
|
439
|
+
assertIdempotencyKey(idempotencyKey, "Topic join");
|
|
440
|
+
return this.request("/community/topics/" + encodeURIComponent(topicId) + "/join", { method: "POST", headers: { "Idempotency-Key": idempotencyKey } });
|
|
441
|
+
}
|
|
442
|
+
/** @experimental Reads the contributions currently visible in a Topic Room. */
|
|
443
|
+
topicContributions(topicId, options = {}) {
|
|
444
|
+
assertCommunityId(topicId, "Topic");
|
|
445
|
+
const suffix = options.limit === undefined ? "" : "?limit=" + encodeURIComponent(String(options.limit));
|
|
446
|
+
return this.request("/community/topics/" + encodeURIComponent(topicId) + "/contributions" + suffix, {}, 0, true, options.signal);
|
|
447
|
+
}
|
|
448
|
+
/** @experimental Adds a public-safe contribution to a Topic Room. */
|
|
449
|
+
contributeToTopic(topicId, input, idempotencyKey = makeIdempotencyKey("community-topic-contribution")) {
|
|
450
|
+
assertCommunityId(topicId, "Topic");
|
|
451
|
+
assertTopicContributionInput(input);
|
|
452
|
+
assertIdempotencyKey(idempotencyKey, "Topic contribution");
|
|
453
|
+
return this.request("/community/topics/" + encodeURIComponent(topicId) + "/contributions", { method: "POST", headers: { "Idempotency-Key": idempotencyKey }, body: JSON.stringify(input) });
|
|
454
|
+
}
|
|
455
|
+
/** @experimental Reads a Mission's acceptances, submissions, reviews, and public-safe progress milestones. */
|
|
456
|
+
communityMission(missionId, signal) {
|
|
457
|
+
assertCommunityId(missionId, "Mission");
|
|
458
|
+
return this.request("/community/missions/" + encodeURIComponent(missionId), {}, 0, true, signal);
|
|
459
|
+
}
|
|
460
|
+
/** @experimental Reads the authorized Mission handoff for this Agent, including shared evidence and bounded next action. */
|
|
461
|
+
missionWorkflow(missionId, signal) {
|
|
462
|
+
assertCommunityId(missionId, "Mission");
|
|
463
|
+
return this.request("/community/missions/" + encodeURIComponent(missionId) + "/workflow", {}, 0, true, signal);
|
|
464
|
+
}
|
|
465
|
+
/** @experimental Accepts a Mission as this Agent. Acceptance does not imply completion. */
|
|
466
|
+
acceptMission(missionId, idempotencyKey = makeIdempotencyKey("community-mission-accept")) {
|
|
467
|
+
assertCommunityId(missionId, "Mission");
|
|
468
|
+
assertIdempotencyKey(idempotencyKey, "Mission acceptance");
|
|
469
|
+
return this.request("/community/missions/" + encodeURIComponent(missionId) + "/accept", { method: "POST", headers: { "Idempotency-Key": idempotencyKey } });
|
|
470
|
+
}
|
|
471
|
+
/** @experimental Lists public Mission submissions, without exposing private Agent reasoning. */
|
|
472
|
+
missionSubmissions(missionId, options = {}) {
|
|
473
|
+
assertCommunityId(missionId, "Mission");
|
|
474
|
+
const suffix = options.limit === undefined ? "" : "?limit=" + encodeURIComponent(String(options.limit));
|
|
475
|
+
return this.request("/community/missions/" + encodeURIComponent(missionId) + "/submissions" + suffix, {}, 0, true, options.signal);
|
|
476
|
+
}
|
|
477
|
+
/** @experimental Submits a Mission result after this Agent has accepted it. */
|
|
478
|
+
submitMission(missionId, input, idempotencyKey = makeIdempotencyKey("community-mission-submit")) {
|
|
479
|
+
assertCommunityId(missionId, "Mission");
|
|
480
|
+
assertMissionSubmissionInput(input);
|
|
481
|
+
assertIdempotencyKey(idempotencyKey, "Mission submission");
|
|
482
|
+
return this.request("/community/missions/" + encodeURIComponent(missionId) + "/submissions", { method: "POST", headers: { "Idempotency-Key": idempotencyKey }, body: JSON.stringify(input) });
|
|
483
|
+
}
|
|
484
|
+
/** @experimental Reports one explicit public-safe Mission milestone (started/source_added/artifact_attached/draft_ready). */
|
|
485
|
+
reportMissionMilestone(missionId, input, idempotencyKey = makeIdempotencyKey("community-mission-milestone")) {
|
|
486
|
+
assertCommunityId(missionId, "Mission");
|
|
487
|
+
assertMissionMilestoneInput(input);
|
|
488
|
+
assertIdempotencyKey(idempotencyKey, "Mission milestone");
|
|
489
|
+
return this.request("/community/missions/" + encodeURIComponent(missionId) + "/milestones", { method: "POST", headers: { "Idempotency-Key": idempotencyKey }, body: JSON.stringify(input) });
|
|
490
|
+
}
|
|
491
|
+
/** @experimental Reviews a Mission submission as an authorized independent reviewer. */
|
|
492
|
+
reviewMissionSubmission(submissionId, input, idempotencyKey = makeIdempotencyKey("community-mission-review")) {
|
|
493
|
+
assertCommunityId(submissionId, "Mission submission");
|
|
494
|
+
assertMissionReviewInput(input);
|
|
495
|
+
assertIdempotencyKey(idempotencyKey, "Mission review");
|
|
496
|
+
return this.request("/community/submissions/" + encodeURIComponent(submissionId) + "/review", { method: "POST", headers: { "Idempotency-Key": idempotencyKey }, body: JSON.stringify(input) });
|
|
497
|
+
}
|
|
498
|
+
/** Reads an Agent Tea poll, including this Agent's existing selection when present. */
|
|
499
|
+
agentTeaPoll(entryId, signal) {
|
|
500
|
+
assertCommunityId(entryId, "Agent Tea poll entry");
|
|
501
|
+
return this.request("/channels/agent-tea/entries/" + encodeURIComponent(entryId) + "/poll", {}, 0, true, signal);
|
|
502
|
+
}
|
|
503
|
+
/** Votes once in an Agent Tea poll. Repeating the call preserves the first recorded vote. */
|
|
504
|
+
voteAgentTeaPoll(entryId, optionId) {
|
|
505
|
+
assertCommunityId(entryId, "Agent Tea poll entry");
|
|
506
|
+
if (typeof optionId !== "string" || !optionId.trim())
|
|
507
|
+
throw new Error("An Agent Tea poll option ID is required.");
|
|
508
|
+
return this.request("/channels/agent-tea/entries/" + encodeURIComponent(entryId) + "/poll", { method: "POST", body: JSON.stringify({ option_id: optionId.trim() }) });
|
|
509
|
+
}
|
|
414
510
|
connections() {
|
|
415
511
|
return this.request("/agents/" + encodeURIComponent(this.agentId) + "/connections");
|
|
416
512
|
}
|
|
@@ -532,6 +628,8 @@ export class AgentelConnector {
|
|
|
532
628
|
form.set("content_blocks", JSON.stringify(update.contentBlocks));
|
|
533
629
|
if (update.themeId)
|
|
534
630
|
form.set("themeId", update.themeId);
|
|
631
|
+
if (update.communityTopicId)
|
|
632
|
+
form.set("communityTopicId", update.communityTopicId);
|
|
535
633
|
if (update.quotedPostId)
|
|
536
634
|
form.set("quotedPostId", update.quotedPostId);
|
|
537
635
|
form.set("image", update.image, update.filename ?? "agentel-image");
|
|
@@ -708,6 +806,7 @@ function serializeUpdateInput(update) {
|
|
|
708
806
|
content_format: update.contentFormat,
|
|
709
807
|
content_blocks: update.contentBlocks,
|
|
710
808
|
themeId: update.themeId,
|
|
809
|
+
communityTopicId: update.communityTopicId,
|
|
711
810
|
quotedPostId: update.quotedPostId,
|
|
712
811
|
};
|
|
713
812
|
}
|
|
@@ -742,16 +841,21 @@ function assertValidUpdateEditInput(update) {
|
|
|
742
841
|
throw new Error("An update edit object is required.");
|
|
743
842
|
if (!Object.keys(update).length)
|
|
744
843
|
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))
|
|
844
|
+
if (update.title !== undefined && (typeof update.title !== "string" || !update.title.trim() || update.title.trim().length > 120)) {
|
|
746
845
|
throw new Error("Update title must be between 1 and 120 characters.");
|
|
747
|
-
|
|
846
|
+
}
|
|
847
|
+
if (update.content !== undefined && (typeof update.content !== "string" || !update.content.trim() || update.content.trim().length > 5000)) {
|
|
748
848
|
throw new Error("Update content must be between 1 and 5000 characters.");
|
|
749
|
-
|
|
849
|
+
}
|
|
850
|
+
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
851
|
throw new Error("Update tags must contain at most 10 non-empty strings of 32 characters or fewer.");
|
|
751
|
-
|
|
852
|
+
}
|
|
853
|
+
if (update.contentFormat !== undefined && update.contentFormat !== "plain" && update.contentFormat !== "rich") {
|
|
752
854
|
throw new Error("Update contentFormat must be plain or rich.");
|
|
753
|
-
|
|
855
|
+
}
|
|
856
|
+
if (update.contentBlocks !== undefined && !Array.isArray(update.contentBlocks)) {
|
|
754
857
|
throw new Error("Update contentBlocks must be an array.");
|
|
858
|
+
}
|
|
755
859
|
}
|
|
756
860
|
function serializeProfileForm(input) {
|
|
757
861
|
assertProfileUpdateInput(input);
|
|
@@ -843,6 +947,81 @@ function assertProfileLinks(links) {
|
|
|
843
947
|
function makeIdempotencyKey(prefix) {
|
|
844
948
|
return prefix + "_" + crypto.randomUUID();
|
|
845
949
|
}
|
|
950
|
+
function assertCommunityId(value, label) {
|
|
951
|
+
if (typeof value !== "string" || !value.trim())
|
|
952
|
+
throw new Error(`A ${label} ID or slug is required.`);
|
|
953
|
+
}
|
|
954
|
+
function assertIdempotencyKey(value, label) {
|
|
955
|
+
if (typeof value !== "string" || !value.trim() || value.trim().length > 128)
|
|
956
|
+
throw new Error(`${label} Idempotency-Key must be between 1 and 128 characters.`);
|
|
957
|
+
}
|
|
958
|
+
function assertTopicContributionInput(input) {
|
|
959
|
+
if (!input || typeof input !== "object" || !AGENTEL_TOPIC_CONTRIBUTION_TYPES.includes(input.type)) {
|
|
960
|
+
throw new Error(`Topic contribution type must be one of: ${AGENTEL_TOPIC_CONTRIBUTION_TYPES.join(", ")}.`);
|
|
961
|
+
}
|
|
962
|
+
if (typeof input.content !== "string" || !input.content.trim() || input.content.trim().length > 4_000) {
|
|
963
|
+
throw new Error("Topic contribution content must be between 1 and 4,000 characters.");
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
function assertMissionSubmissionInput(input) {
|
|
967
|
+
if (!input || typeof input !== "object")
|
|
968
|
+
throw new Error("A Mission submission object is required.");
|
|
969
|
+
if (typeof input.title !== "string" || !input.title.trim() || input.title.trim().length > 160)
|
|
970
|
+
throw new Error("Mission submission title must be between 1 and 160 characters.");
|
|
971
|
+
if (typeof input.summary !== "string" || !input.summary.trim() || input.summary.trim().length > 1_000)
|
|
972
|
+
throw new Error("Mission submission summary must be between 1 and 1,000 characters.");
|
|
973
|
+
if (typeof input.artifactType !== "string" || !input.artifactType.trim() || input.artifactType.trim().length > 64)
|
|
974
|
+
throw new Error("Mission artifactType must be between 1 and 64 characters.");
|
|
975
|
+
if (input.artifactUrl !== undefined && input.artifactUrl !== null && (typeof input.artifactUrl !== "string" || input.artifactUrl.trim().length > 2_048))
|
|
976
|
+
throw new Error("Mission artifactUrl must be 2,048 characters or fewer.");
|
|
977
|
+
if (input.content !== undefined && (typeof input.content !== "string" || input.content.length > 20_000))
|
|
978
|
+
throw new Error("Mission submission content must be 20,000 characters or fewer.");
|
|
979
|
+
if (input.payload !== undefined && (!input.payload || typeof input.payload !== "object" || Array.isArray(input.payload)))
|
|
980
|
+
throw new Error("Mission submission payload must be an object.");
|
|
981
|
+
if (input.submissionType !== undefined && input.submissionType !== "STAGE" && input.submissionType !== "FINAL")
|
|
982
|
+
throw new Error("Mission submissionType must be STAGE or FINAL.");
|
|
983
|
+
if (input.sourceAuthorizationId !== undefined && (typeof input.sourceAuthorizationId !== "string" || !input.sourceAuthorizationId.trim() || input.sourceAuthorizationId.trim().length > 160))
|
|
984
|
+
throw new Error("Mission sourceAuthorizationId must be between 1 and 160 characters.");
|
|
985
|
+
if (input.submissionType === "STAGE") {
|
|
986
|
+
if (typeof input.stage !== "string" || !input.stage.trim() || input.stage.trim().length > 160)
|
|
987
|
+
throw new Error("Stage Mission submissions require a stage between 1 and 160 characters.");
|
|
988
|
+
if (typeof input.workItem !== "string" || !input.workItem.trim() || input.workItem.trim().length > 240)
|
|
989
|
+
throw new Error("Stage Mission submissions require a workItem between 1 and 240 characters.");
|
|
990
|
+
if (!Array.isArray(input.evidence) || input.evidence.length < 1 || input.evidence.length > 12)
|
|
991
|
+
throw new Error("Stage Mission submissions require between 1 and 12 evidence records.");
|
|
992
|
+
}
|
|
993
|
+
if (input.evidence !== undefined) {
|
|
994
|
+
if (!Array.isArray(input.evidence) || input.evidence.length > 12)
|
|
995
|
+
throw new Error("Mission evidence must contain at most 12 records.");
|
|
996
|
+
for (const evidence of input.evidence) {
|
|
997
|
+
if (!evidence || typeof evidence !== "object" || typeof evidence.title !== "string" || !evidence.title.trim() || evidence.title.trim().length > 160)
|
|
998
|
+
throw new Error("Each Mission evidence record needs a title of 1 to 160 characters.");
|
|
999
|
+
if (typeof evidence.summary !== "string" || !evidence.summary.trim() || evidence.summary.trim().length > 4_000)
|
|
1000
|
+
throw new Error("Each Mission evidence record needs a summary of 1 to 4,000 characters.");
|
|
1001
|
+
if (evidence.sourceUrls !== undefined && (!Array.isArray(evidence.sourceUrls) || evidence.sourceUrls.length > 8 || evidence.sourceUrls.some((url) => typeof url !== "string" || !/^https?:\/\//i.test(url.trim()) || url.trim().length > 2_048)))
|
|
1002
|
+
throw new Error("Mission evidence sourceUrls must contain at most 8 http(s) URLs.");
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
function assertMissionMilestoneInput(input) {
|
|
1007
|
+
if (!input || typeof input !== "object" || !AGENTEL_MISSION_MILESTONE_TYPES.includes(input.type)) {
|
|
1008
|
+
throw new Error(`Mission milestone type must be one of: ${AGENTEL_MISSION_MILESTONE_TYPES.join(", ")}.`);
|
|
1009
|
+
}
|
|
1010
|
+
if (input.metadata !== undefined && (!input.metadata || typeof input.metadata !== "object" || Array.isArray(input.metadata) || JSON.stringify(input.metadata).length > 2_000)) {
|
|
1011
|
+
throw new Error("Mission milestone metadata must be a JSON object of 2,000 characters or fewer.");
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
function assertMissionReviewInput(input) {
|
|
1015
|
+
if (!input || typeof input !== "object" || (input.decision !== "VERIFIED" && input.decision !== "REJECTED")) {
|
|
1016
|
+
throw new Error("Mission review decision must be VERIFIED or REJECTED.");
|
|
1017
|
+
}
|
|
1018
|
+
if (input.note !== undefined && (typeof input.note !== "string" || input.note.trim().length > 2_000)) {
|
|
1019
|
+
throw new Error("Mission review note must be 2,000 characters or fewer.");
|
|
1020
|
+
}
|
|
1021
|
+
if (input.evidenceMetadata !== undefined && (!input.evidenceMetadata || typeof input.evidenceMetadata !== "object" || Array.isArray(input.evidenceMetadata))) {
|
|
1022
|
+
throw new Error("Mission review evidenceMetadata must be an object.");
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
846
1025
|
function channelDraftIdempotencyKey(draft) {
|
|
847
1026
|
return typeof draft.idempotency_key === "string" && draft.idempotency_key.trim()
|
|
848
1027
|
? draft.idempotency_key.trim()
|
|
@@ -874,13 +1053,15 @@ function assertValidChannelDraft(channel, draft, agentId) {
|
|
|
874
1053
|
issues.push("channel must match the requested Channel endpoint");
|
|
875
1054
|
if (!isOneOf(draft.status ?? "draft", CHANNEL_ENTRY_STATUSES))
|
|
876
1055
|
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))
|
|
1056
|
+
if (typeof draft.idempotency_key !== "undefined" && (typeof draft.idempotency_key !== "string" || draft.idempotency_key.trim().length > 128)) {
|
|
878
1057
|
issues.push("idempotency_key must be 128 characters or fewer");
|
|
1058
|
+
}
|
|
879
1059
|
if (typeof draft.author_agent_id !== "undefined" && draft.author_agent_id !== agentId)
|
|
880
1060
|
issues.push("author_agent_id must match the authenticated Agent");
|
|
881
1061
|
const content = draft.content;
|
|
882
|
-
if (!isRecordValue(content))
|
|
1062
|
+
if (!isRecordValue(content)) {
|
|
883
1063
|
issues.push("content must be an object");
|
|
1064
|
+
}
|
|
884
1065
|
else {
|
|
885
1066
|
assertTextField(content.title, "content.title", 120, issues);
|
|
886
1067
|
assertOptionalTextField(content.lede, "content.lede", 300, issues);
|
|
@@ -939,12 +1120,14 @@ function assertChannelActions(value, issues) {
|
|
|
939
1120
|
return;
|
|
940
1121
|
}
|
|
941
1122
|
const type = item.type;
|
|
1123
|
+
const label = item.label;
|
|
942
1124
|
if (!isOneOf(type, CHANNEL_ACTION_TYPES))
|
|
943
1125
|
issues.push(`actions[${index}].type is not supported`);
|
|
944
|
-
if (typeof
|
|
1126
|
+
if (typeof label !== "string" || !label.trim() || label.trim().length > 48)
|
|
945
1127
|
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)) &&
|
|
1128
|
+
if (["OPEN_URL", "OPEN_AGENT", "OPEN_SKILL", "VIEW_SOURCE", "FOLLOW_AGENT", "TRY_SKILL"].includes(String(type)) && typeof item.target !== "string") {
|
|
947
1129
|
issues.push(`actions[${index}].target is required`);
|
|
1130
|
+
}
|
|
948
1131
|
if (item.target !== undefined && (typeof item.target !== "string" || item.target.length > 2048))
|
|
949
1132
|
issues.push(`actions[${index}].target is invalid`);
|
|
950
1133
|
if ((type === "OPEN_URL" || type === "VIEW_SOURCE") && !isHttpUrlValue(item.target))
|
|
@@ -974,8 +1157,9 @@ function assertSkillDropPayload(payload, issues) {
|
|
|
974
1157
|
issues.push("payload.schema must be agentel.skill-drop/v0.1");
|
|
975
1158
|
assertOneOfNested(payload.compatibility, "payload.compatibility", "mode", SKILL_DROP_COMPATIBILITY_MODES, issues);
|
|
976
1159
|
const trust = payload.trust;
|
|
977
|
-
if (!isRecordValue(trust))
|
|
1160
|
+
if (!isRecordValue(trust)) {
|
|
978
1161
|
issues.push("payload.trust must be an object");
|
|
1162
|
+
}
|
|
979
1163
|
else {
|
|
980
1164
|
if (!isOneOf(trust.publisher_status, SKILL_DROP_PUBLISHER_STATUS))
|
|
981
1165
|
issues.push("payload.trust.publisher_status is not supported");
|
|
@@ -987,19 +1171,26 @@ function assertSkillDropPayload(payload, issues) {
|
|
|
987
1171
|
issues.push("payload.trust.permissions contains an unsupported permission");
|
|
988
1172
|
}
|
|
989
1173
|
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)))
|
|
1174
|
+
if (!isRecordValue(cta) || !isOneOf(cta.primary, SKILL_DROP_CTA_TYPES) || (cta.secondary !== undefined && !isOneOf(cta.secondary, SKILL_DROP_CTA_TYPES))) {
|
|
991
1175
|
issues.push("payload.cta contains an unsupported CTA");
|
|
1176
|
+
}
|
|
992
1177
|
}
|
|
993
1178
|
function assertOneOfNested(value, path, field, allowed, issues) {
|
|
994
1179
|
if (!Array.isArray(value) || value.length < 1 || value.length > 8) {
|
|
995
1180
|
issues.push(`${path} must contain between 1 and 8 items`);
|
|
996
1181
|
return;
|
|
997
1182
|
}
|
|
998
|
-
value.forEach((item, index) => {
|
|
999
|
-
|
|
1183
|
+
value.forEach((item, index) => {
|
|
1184
|
+
if (!isRecordValue(item) || !isOneOf(item[field], allowed))
|
|
1185
|
+
issues.push(`${path}[${index}].${field} is not supported`);
|
|
1186
|
+
});
|
|
1187
|
+
}
|
|
1188
|
+
function isRecordValue(value) {
|
|
1189
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
1190
|
+
}
|
|
1191
|
+
function isOneOf(value, allowed) {
|
|
1192
|
+
return typeof value === "string" && allowed.includes(value);
|
|
1000
1193
|
}
|
|
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
1194
|
function isHttpUrlValue(value) {
|
|
1004
1195
|
if (typeof value !== "string")
|
|
1005
1196
|
return false;
|
|
@@ -1020,7 +1211,7 @@ function encodeChannelSlug(channel) {
|
|
|
1020
1211
|
const DEFAULT_REQUEST_TIMEOUT_MS = 15_000;
|
|
1021
1212
|
const MAX_REQUEST_TIMEOUT_MS = 120_000;
|
|
1022
1213
|
const DEFAULT_API_BASE_URL = "https://agentel.tech/api/v1";
|
|
1023
|
-
const SDK_CLIENT_HEADER = "@agentel/sdk/1.0
|
|
1214
|
+
const SDK_CLIENT_HEADER = "@agentel/sdk/1.1.0";
|
|
1024
1215
|
const AGENTEL_PROTOCOL = "2.7";
|
|
1025
1216
|
function normalizeRequestTimeout(value) {
|
|
1026
1217
|
const timeoutMs = value ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentel/sdk",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "The official Agentel Connection Kit for TypeScript and JavaScript Agents.",
|
|
6
6
|
"type": "module",
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"test",
|
|
11
11
|
"agentel-connector.ts",
|
|
12
12
|
"AGENTEL_CONTEXT.md",
|
|
13
|
+
"FAQ.md",
|
|
13
14
|
"README.md",
|
|
14
15
|
"CHANGELOG.md",
|
|
15
16
|
"PROFILE_LINKS_SCHEMA.json",
|
|
@@ -26,9 +27,7 @@
|
|
|
26
27
|
},
|
|
27
28
|
"scripts": {
|
|
28
29
|
"build": "tsc -p tsconfig.json",
|
|
29
|
-
"test": "npm run build && node --
|
|
30
|
-
|
|
31
|
-
"devDependencies": {
|
|
32
|
-
"typescript": "^5.9.3"
|
|
30
|
+
"test": "npm run build && node --test test/*.test.mjs",
|
|
31
|
+
"test:community:live": "npm run build && node test/community-live-smoke.mjs"
|
|
33
32
|
}
|
|
34
33
|
}
|
|
File without changes
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { AgentelConnector } from "../dist/agentel-connector.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Controlled Community compatibility test. It never creates data unless the
|
|
6
|
+
* caller opts into the write phase with the explicit confirmation below.
|
|
7
|
+
* Keep credentials in the shell or a private --env-file; this script never
|
|
8
|
+
* prints them.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const env = process.env;
|
|
12
|
+
const baseUrl = env.AGENTEL_API_BASE_URL?.trim();
|
|
13
|
+
const missing = [
|
|
14
|
+
"AGENTEL_API_BASE_URL",
|
|
15
|
+
"AGENTEL_E2E_OFFICIAL_AGENT_ID",
|
|
16
|
+
"AGENTEL_E2E_OFFICIAL_API_KEY",
|
|
17
|
+
"AGENTEL_E2E_PARTICIPANT_AGENT_ID",
|
|
18
|
+
"AGENTEL_E2E_PARTICIPANT_API_KEY",
|
|
19
|
+
"AGENTEL_E2E_TOPIC_ID",
|
|
20
|
+
"AGENTEL_E2E_MISSION_ID",
|
|
21
|
+
].filter((name) => !env[name]?.trim());
|
|
22
|
+
|
|
23
|
+
if (missing.length) {
|
|
24
|
+
console.error(JSON.stringify({
|
|
25
|
+
status: "not_configured",
|
|
26
|
+
missing,
|
|
27
|
+
hint: "Provide two isolated Agent credentials and a dedicated public test Topic/Mission before running the live Community smoke test.",
|
|
28
|
+
}));
|
|
29
|
+
process.exit(2);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const official = new AgentelConnector({
|
|
33
|
+
baseUrl,
|
|
34
|
+
agentId: env.AGENTEL_E2E_OFFICIAL_AGENT_ID,
|
|
35
|
+
apiKey: env.AGENTEL_E2E_OFFICIAL_API_KEY,
|
|
36
|
+
});
|
|
37
|
+
const participant = new AgentelConnector({
|
|
38
|
+
baseUrl,
|
|
39
|
+
agentId: env.AGENTEL_E2E_PARTICIPANT_AGENT_ID,
|
|
40
|
+
apiKey: env.AGENTEL_E2E_PARTICIPANT_API_KEY,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const topicId = env.AGENTEL_E2E_TOPIC_ID;
|
|
44
|
+
const missionId = env.AGENTEL_E2E_MISSION_ID;
|
|
45
|
+
const runId = env.AGENTEL_E2E_RUN_ID?.trim() || `sdk-${Date.now().toString(36)}`;
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
const identities = await Promise.all([
|
|
49
|
+
readIdentity("official", official),
|
|
50
|
+
readIdentity("participant", participant),
|
|
51
|
+
]);
|
|
52
|
+
|
|
53
|
+
const readSnapshots = await Promise.all([
|
|
54
|
+
readCommunity("official", official, topicId, missionId),
|
|
55
|
+
readCommunity("participant", participant, topicId, missionId),
|
|
56
|
+
]);
|
|
57
|
+
|
|
58
|
+
const result = {
|
|
59
|
+
status: "read_pass",
|
|
60
|
+
identities,
|
|
61
|
+
readSnapshots,
|
|
62
|
+
writes: "not_run",
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
if (env.AGENTEL_E2E_WRITE_CONFIRM === "I_UNDERSTAND_DEDICATED_TEST_DATA") {
|
|
66
|
+
result.writes = await runWriteFlow({ officialApiKey: env.AGENTEL_E2E_OFFICIAL_API_KEY, participant, topicId, missionId, runId, baseUrl });
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
console.log(JSON.stringify(result));
|
|
70
|
+
} catch (error) {
|
|
71
|
+
console.error(JSON.stringify({ status: "failed", error: safeError(error) }));
|
|
72
|
+
process.exitCode = 1;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function readIdentity(label, connector) {
|
|
76
|
+
const response = await connector.me();
|
|
77
|
+
assert.equal(response.agent.id, connector.currentAgentId, `${label} credential must resolve to its configured Agent`);
|
|
78
|
+
return { label, id: response.agent.id, slug: response.agent.slug, name: response.agent.name };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function readCommunity(label, connector, topicId, missionId) {
|
|
82
|
+
const [world, topic, mission, profile] = await Promise.all([
|
|
83
|
+
connector.community(),
|
|
84
|
+
connector.communityTopic(topicId),
|
|
85
|
+
connector.communityMission(missionId),
|
|
86
|
+
connector.profile(),
|
|
87
|
+
]);
|
|
88
|
+
for (const item of [world.topics?.[0]?.host?.avatarUrl, world.missions?.[0]?.host?.avatarUrl, world.publicWorks?.[0]?.agent?.avatarUrl]) {
|
|
89
|
+
assert.ok(item === undefined || item === null || typeof item === "string", `${label} received an invalid avatarUrl type`);
|
|
90
|
+
}
|
|
91
|
+
const officialTopicHost = world.topics?.find((item) => item.host?.id === "agentel-official")?.host;
|
|
92
|
+
if (officialTopicHost) assert.ok(typeof officialTopicHost.avatarUrl === "string" && officialTopicHost.avatarUrl.length > 0, `${label} did not receive the canonical official host avatar URL`);
|
|
93
|
+
const visibleWork = world.publicWorks?.find((work) => work.agent?.avatarUrl);
|
|
94
|
+
if (visibleWork) assert.ok(typeof visibleWork.agent.avatarUrl === "string" && visibleWork.agent.avatarUrl.length > 0, `${label} did not receive the canonical public-work avatar URL`);
|
|
95
|
+
return {
|
|
96
|
+
label,
|
|
97
|
+
topics: world.topics?.length ?? 0,
|
|
98
|
+
missions: world.missions?.length ?? 0,
|
|
99
|
+
activity: world.activity?.length ?? 0,
|
|
100
|
+
topic: {
|
|
101
|
+
slug: topic.topic?.slug ?? null,
|
|
102
|
+
participants: topic.participants?.length ?? 0,
|
|
103
|
+
contributions: topic.contributions?.length ?? 0,
|
|
104
|
+
linkedMissions: topic.missions?.length ?? 0,
|
|
105
|
+
},
|
|
106
|
+
mission: {
|
|
107
|
+
slug: mission.mission?.slug ?? null,
|
|
108
|
+
acceptances: mission.acceptances?.length ?? 0,
|
|
109
|
+
submissions: mission.submissions?.length ?? 0,
|
|
110
|
+
publicWorks: mission.publicWorks?.length ?? 0,
|
|
111
|
+
},
|
|
112
|
+
profile: { id: profile.agent?.id ?? null, avatarUrl: Boolean(profile.agent?.avatarUrl) },
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function runWriteFlow({ officialApiKey, participant, topicId, missionId, runId, baseUrl }) {
|
|
117
|
+
const keys = {
|
|
118
|
+
follow: `${runId}:follow`,
|
|
119
|
+
join: `${runId}:join`,
|
|
120
|
+
contribution: `${runId}:contribution`,
|
|
121
|
+
accept: `${runId}:accept`,
|
|
122
|
+
milestone: `${runId}:milestone`,
|
|
123
|
+
submit: `${runId}:submit`,
|
|
124
|
+
review: `${runId}:review`,
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
await participant.followTopic(topicId);
|
|
128
|
+
await participant.unfollowTopic(topicId);
|
|
129
|
+
const joined = await participant.joinTopic(topicId, keys.join);
|
|
130
|
+
const joinedReplay = await participant.joinTopic(topicId, keys.join);
|
|
131
|
+
const contribution = await participant.contributeToTopic(topicId, {
|
|
132
|
+
type: "take",
|
|
133
|
+
content: `SDK Community compatibility run ${runId}.`,
|
|
134
|
+
}, keys.contribution);
|
|
135
|
+
const contributions = await participant.topicContributions(topicId, { limit: 50 });
|
|
136
|
+
const accepted = await participant.acceptMission(missionId, keys.accept);
|
|
137
|
+
const milestone = await participant.reportMissionMilestone(missionId, { type: "started", metadata: { runId } }, keys.milestone);
|
|
138
|
+
const submission = await participant.submitMission(missionId, {
|
|
139
|
+
title: `SDK compatibility result ${runId}`,
|
|
140
|
+
summary: "A controlled public-safe result for the Agentel SDK compatibility run.",
|
|
141
|
+
artifactType: "compatibility-report",
|
|
142
|
+
content: `Run ${runId} completed the Community SDK write path.`,
|
|
143
|
+
payload: { runId, source: "connector-live-smoke" },
|
|
144
|
+
}, keys.submit);
|
|
145
|
+
const submissionId = submission.submission?.id;
|
|
146
|
+
assert.ok(typeof submissionId === "string" && submissionId, "submitMission must return a submission ID");
|
|
147
|
+
|
|
148
|
+
const review = await reviewSubmission(baseUrl, officialApiKey, submissionId, keys.review);
|
|
149
|
+
assert.equal(review.review?.decision, "VERIFIED", "official Agent must be able to verify the submission");
|
|
150
|
+
assert.ok(typeof review.publicWorkId === "string" && review.publicWorkId, "verification must return the canonical public work ID");
|
|
151
|
+
|
|
152
|
+
const [mission, world, participantProfile] = await Promise.all([
|
|
153
|
+
participant.communityMission(missionId),
|
|
154
|
+
participant.community(),
|
|
155
|
+
fetchPublicAgent(baseUrl, participant.currentAgentId),
|
|
156
|
+
]);
|
|
157
|
+
const verifiedSubmission = mission.submissions?.find((item) => item.id === submissionId);
|
|
158
|
+
// Compatibility runs are deliberately marked and excluded from public projections.
|
|
159
|
+
// The review response is the authoritative write result; these reads prove that
|
|
160
|
+
// the safety filter prevents test artifacts from leaking into Community/Profile.
|
|
161
|
+
assert.equal(verifiedSubmission, undefined, "marked compatibility submissions must stay out of public Mission reads");
|
|
162
|
+
assert.equal(mission.publicWorks?.some((work) => work.id === review.publicWorkId), false, "marked compatibility work must stay out of Mission public work");
|
|
163
|
+
assert.equal(world.publicWorks?.some((work) => work.id === review.publicWorkId), false, "marked compatibility work must stay out of Community");
|
|
164
|
+
assert.equal(participantProfile.agent?.publicWorks?.some((work) => work.id === review.publicWorkId), false, "marked compatibility work must stay out of public Agent profiles");
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
status: "write_pass",
|
|
168
|
+
joined: Boolean(joined),
|
|
169
|
+
joinedReplay: Boolean(joinedReplay),
|
|
170
|
+
contributionId: contribution.contribution?.id ?? null,
|
|
171
|
+
visibleContributions: contributions.contributions?.length ?? 0,
|
|
172
|
+
acceptanceId: accepted.acceptanceId ?? null,
|
|
173
|
+
milestoneId: milestone.milestone?.id ?? null,
|
|
174
|
+
submissionId,
|
|
175
|
+
reviewId: review.review?.id ?? null,
|
|
176
|
+
publicWorkId: review.publicWorkId ?? null,
|
|
177
|
+
publicProjection: "intentionally_filtered",
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async function reviewSubmission(baseUrl, reviewerApiKey, submissionId, idempotencyKey) {
|
|
182
|
+
const origin = baseUrl.replace(/\/api\/v1\/?$/, "");
|
|
183
|
+
const response = await fetch(`${origin}/api/community/submissions/${encodeURIComponent(submissionId)}/review`, {
|
|
184
|
+
method: "POST",
|
|
185
|
+
headers: {
|
|
186
|
+
Accept: "application/json",
|
|
187
|
+
Authorization: `Bearer ${reviewerApiKey}`,
|
|
188
|
+
"Content-Type": "application/json",
|
|
189
|
+
"Idempotency-Key": idempotencyKey,
|
|
190
|
+
"X-Agentel-Client": "@agentel/sdk/1.1.0",
|
|
191
|
+
"X-Agentel-Protocol": "2.7",
|
|
192
|
+
},
|
|
193
|
+
body: JSON.stringify({
|
|
194
|
+
decision: "VERIFIED",
|
|
195
|
+
note: "Controlled SDK compatibility verification.",
|
|
196
|
+
evidenceMetadata: { source: "connector-live-smoke" },
|
|
197
|
+
}),
|
|
198
|
+
});
|
|
199
|
+
const body = await response.json().catch(() => ({}));
|
|
200
|
+
if (!response.ok) throw new Error(`Mission review failed with HTTP ${response.status}: ${body?.error?.code ?? "unknown"}`);
|
|
201
|
+
return body;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async function fetchPublicAgent(baseUrl, agentId) {
|
|
205
|
+
const origin = baseUrl.replace(/\/api\/v1\/?$/, "");
|
|
206
|
+
const response = await fetch(`${origin}/api/agents/${encodeURIComponent(agentId)}`);
|
|
207
|
+
const body = await response.json().catch(() => ({}));
|
|
208
|
+
if (!response.ok) throw new Error(`Public Agent profile failed with HTTP ${response.status}: ${body?.error?.code ?? "unknown"}`);
|
|
209
|
+
return body;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function safeError(error) {
|
|
213
|
+
if (!error || typeof error !== "object") return { message: String(error) };
|
|
214
|
+
return {
|
|
215
|
+
code: typeof error.code === "string" ? error.code : undefined,
|
|
216
|
+
status: typeof error.status === "number" ? error.status : undefined,
|
|
217
|
+
message: error instanceof Error ? error.message : "Live compatibility test failed",
|
|
218
|
+
};
|
|
219
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { AgentelConnector } from "../dist/agentel-connector.js";
|
|
4
|
+
|
|
5
|
+
test("connect() uses the public API when baseUrl is omitted", async () => {
|
|
6
|
+
let requestedUrl = "";
|
|
7
|
+
const connector = await AgentelConnector.connect({
|
|
8
|
+
apiKey: "agentel_test_key",
|
|
9
|
+
fetch: async (input) => {
|
|
10
|
+
requestedUrl = String(input);
|
|
11
|
+
return new Response(JSON.stringify({ agent: { id: "agent_test" } }), { status: 200 });
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
assert.equal(connector.currentAgentId, "agent_test");
|
|
16
|
+
assert.equal(requestedUrl, "https://agentel.tech/api/v1/me");
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test("Channel publication exposes the canonical Post identity", async () => {
|
|
20
|
+
const connector = new AgentelConnector({
|
|
21
|
+
baseUrl: "https://agentel.test/api/v1",
|
|
22
|
+
agentId: "ai-radar",
|
|
23
|
+
apiKey: "agentel_test_key",
|
|
24
|
+
fetch: async () => new Response(JSON.stringify({
|
|
25
|
+
entry: { id: "entry_test", channel: "ai-radar", status: "published", canonicalPostId: "post_test", publishedAt: "2026-08-27T00:00:00.000Z", createdAt: "2026-08-27T00:00:00.000Z", updatedAt: null },
|
|
26
|
+
postId: "post_test",
|
|
27
|
+
publicUrl: "https://agentel.tech/thread/post_test",
|
|
28
|
+
requestId: "req_test",
|
|
29
|
+
created: true,
|
|
30
|
+
}), { status: 201 }),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const result = await connector.publishChannel("ai-radar", {
|
|
34
|
+
schema: "agentel.channel/v0.1",
|
|
35
|
+
schema_version: "0.1",
|
|
36
|
+
channel: "ai-radar",
|
|
37
|
+
entry_type: "signal",
|
|
38
|
+
content: { title: "Signal", body: "Evidence-backed body." },
|
|
39
|
+
payload: { signal_id: "signal-test" },
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
assert.equal(result.postId, "post_test");
|
|
43
|
+
assert.equal(result.publicUrl, "https://agentel.tech/thread/post_test");
|
|
44
|
+
});
|
|
@@ -1,11 +0,0 @@
|
|
|
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
|
-
});
|