@topolo/sdk 0.14.4 → 0.16.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/README.md +74 -0
- package/dist/index.cjs +301 -29
- package/dist/index.d.cts +67 -1
- package/dist/index.d.ts +67 -1
- package/dist/index.js +292 -29
- package/package.json +9 -4
package/dist/index.js
CHANGED
|
@@ -164,10 +164,10 @@ var TopoloPermissionError = class extends TopoloSdkError {
|
|
|
164
164
|
};
|
|
165
165
|
function extractTopoloServiceErrorCode(body) {
|
|
166
166
|
if (!body || typeof body !== "object" || Array.isArray(body)) return null;
|
|
167
|
-
const
|
|
168
|
-
const topLevel = canonicalTopoloErrorCode(
|
|
167
|
+
const record2 = body;
|
|
168
|
+
const topLevel = canonicalTopoloErrorCode(record2.code);
|
|
169
169
|
if (topLevel) return topLevel;
|
|
170
|
-
const nested =
|
|
170
|
+
const nested = record2.error;
|
|
171
171
|
const nestedString = canonicalTopoloErrorCode(nested);
|
|
172
172
|
if (nestedString) return nestedString;
|
|
173
173
|
if (nested && typeof nested === "object" && !Array.isArray(nested)) {
|
|
@@ -182,14 +182,14 @@ function serviceErrorCode(body, fallback) {
|
|
|
182
182
|
}
|
|
183
183
|
function extractTopoloServiceErrorDetails(body) {
|
|
184
184
|
if (!body || typeof body !== "object" || Array.isArray(body)) return void 0;
|
|
185
|
-
const
|
|
186
|
-
let candidate =
|
|
187
|
-
const nested =
|
|
185
|
+
const record2 = body;
|
|
186
|
+
let candidate = record2.details;
|
|
187
|
+
const nested = record2.error;
|
|
188
188
|
if (nested && typeof nested === "object" && !Array.isArray(nested)) {
|
|
189
189
|
const nestedRecord = nested;
|
|
190
190
|
if (candidate === void 0) candidate = nestedRecord.details ?? nestedRecord;
|
|
191
191
|
}
|
|
192
|
-
if (candidate === void 0) candidate =
|
|
192
|
+
if (candidate === void 0) candidate = record2;
|
|
193
193
|
if (Array.isArray(candidate)) {
|
|
194
194
|
const paths = canonicalTopoloValidationPaths(candidate);
|
|
195
195
|
return paths.length ? { paths } : void 0;
|
|
@@ -241,8 +241,8 @@ function canonicalTopoloValidationPaths(issues) {
|
|
|
241
241
|
const paths = [];
|
|
242
242
|
for (const issue of issues) {
|
|
243
243
|
if (!issue || typeof issue !== "object" || Array.isArray(issue)) continue;
|
|
244
|
-
const
|
|
245
|
-
const path = canonicalTopoloValidationPath(
|
|
244
|
+
const record2 = issue;
|
|
245
|
+
const path = canonicalTopoloValidationPath(record2.field) ?? canonicalTopoloValidationPath(record2.path);
|
|
246
246
|
if (path && !paths.includes(path)) paths.push(path);
|
|
247
247
|
if (paths.length === 32) break;
|
|
248
248
|
}
|
|
@@ -843,6 +843,242 @@ function isMultipleOf(value, divisor) {
|
|
|
843
843
|
return Math.abs(quotient - Math.round(quotient)) < Number.EPSILON * Math.max(1, Math.abs(quotient)) * 4;
|
|
844
844
|
}
|
|
845
845
|
|
|
846
|
+
// src/actor-envelope.ts
|
|
847
|
+
var ACTOR_ENVELOPE_VERSION = 1;
|
|
848
|
+
var TOPOLO_ACTOR_ENVELOPE_HEADER = "X-Topolo-Actor-Envelope";
|
|
849
|
+
var PRINCIPAL_TYPES = /* @__PURE__ */ new Set([
|
|
850
|
+
"human",
|
|
851
|
+
"managed_agent",
|
|
852
|
+
"external_agent",
|
|
853
|
+
"service",
|
|
854
|
+
"browser_automation"
|
|
855
|
+
]);
|
|
856
|
+
var ACCOUNTABLE_SUBJECT_TYPES = /* @__PURE__ */ new Set(["human", "organization"]);
|
|
857
|
+
var CHANNELS = /* @__PURE__ */ new Set(["web", "mobile", "cli", "mcp", "api", "workflow", "browser_automation"]);
|
|
858
|
+
var ASSURANCE_LEVELS = /* @__PURE__ */ new Set(["authenticated", "workload_identity", "user_verified"]);
|
|
859
|
+
var HASH_PATTERN = /^sha256:[a-f0-9]{64}$/;
|
|
860
|
+
var ActorEnvelopeValidationError = class extends Error {
|
|
861
|
+
path;
|
|
862
|
+
constructor(path, message) {
|
|
863
|
+
super(`${path}: ${message}`);
|
|
864
|
+
this.name = "ActorEnvelopeValidationError";
|
|
865
|
+
this.path = path;
|
|
866
|
+
}
|
|
867
|
+
};
|
|
868
|
+
function parseActorEnvelope(value) {
|
|
869
|
+
const envelope = record(value, "actorEnvelope");
|
|
870
|
+
exactKeys(envelope, [
|
|
871
|
+
"schemaVersion",
|
|
872
|
+
"organizationId",
|
|
873
|
+
"accountableSubject",
|
|
874
|
+
"performer",
|
|
875
|
+
"delegation",
|
|
876
|
+
"channel",
|
|
877
|
+
"assurance",
|
|
878
|
+
"action",
|
|
879
|
+
"issuedAt",
|
|
880
|
+
"expiresAt"
|
|
881
|
+
], "actorEnvelope");
|
|
882
|
+
if (envelope.schemaVersion !== ACTOR_ENVELOPE_VERSION) {
|
|
883
|
+
fail("actorEnvelope.schemaVersion", `must equal ${ACTOR_ENVELOPE_VERSION}`);
|
|
884
|
+
}
|
|
885
|
+
const accountableSubject = parseAccountableSubject(envelope.accountableSubject);
|
|
886
|
+
const performer = parseIdentity(envelope.performer, "actorEnvelope.performer");
|
|
887
|
+
const organizationId = text(envelope.organizationId, "actorEnvelope.organizationId");
|
|
888
|
+
const channel = member(envelope.channel, CHANNELS, "actorEnvelope.channel");
|
|
889
|
+
const issuedAt = timestamp(envelope.issuedAt, "actorEnvelope.issuedAt");
|
|
890
|
+
const expiresAt = envelope.expiresAt === void 0 ? void 0 : timestamp(envelope.expiresAt, "actorEnvelope.expiresAt");
|
|
891
|
+
if (expiresAt && Date.parse(expiresAt) <= Date.parse(issuedAt)) {
|
|
892
|
+
fail("actorEnvelope.expiresAt", "must be later than issuedAt");
|
|
893
|
+
}
|
|
894
|
+
const assuranceValue = record(envelope.assurance, "actorEnvelope.assurance");
|
|
895
|
+
exactKeys(assuranceValue, ["level", "methods"], "actorEnvelope.assurance");
|
|
896
|
+
const assurance = {
|
|
897
|
+
level: member(assuranceValue.level, ASSURANCE_LEVELS, "actorEnvelope.assurance.level"),
|
|
898
|
+
methods: textArray(assuranceValue.methods, "actorEnvelope.assurance.methods")
|
|
899
|
+
};
|
|
900
|
+
const actionValue = record(envelope.action, "actorEnvelope.action");
|
|
901
|
+
exactKeys(actionValue, ["requestId", "actionId", "resourceType", "resourceId", "inputHash"], "actorEnvelope.action");
|
|
902
|
+
const inputHash = text(actionValue.inputHash, "actorEnvelope.action.inputHash");
|
|
903
|
+
if (!HASH_PATTERN.test(inputHash)) fail("actorEnvelope.action.inputHash", "must be a lowercase sha256 digest");
|
|
904
|
+
const action = {
|
|
905
|
+
requestId: text(actionValue.requestId, "actorEnvelope.action.requestId"),
|
|
906
|
+
actionId: text(actionValue.actionId, "actorEnvelope.action.actionId"),
|
|
907
|
+
...actionValue.resourceType === void 0 ? {} : { resourceType: text(actionValue.resourceType, "actorEnvelope.action.resourceType") },
|
|
908
|
+
...actionValue.resourceId === void 0 ? {} : { resourceId: text(actionValue.resourceId, "actorEnvelope.action.resourceId") },
|
|
909
|
+
inputHash
|
|
910
|
+
};
|
|
911
|
+
if (action.resourceType === void 0 !== (action.resourceId === void 0)) {
|
|
912
|
+
fail("actorEnvelope.action", "resourceType and resourceId must be provided together");
|
|
913
|
+
}
|
|
914
|
+
const delegation = parseDelegation(envelope.delegation);
|
|
915
|
+
const directHuman = performer.principalType === "human" && performer.principalId === accountableSubject.principalId;
|
|
916
|
+
if (directHuman && delegation !== null) fail("actorEnvelope.delegation", "must be null for direct human action");
|
|
917
|
+
if (!directHuman && delegation === null) fail("actorEnvelope.delegation", "is required when performer differs from accountable subject");
|
|
918
|
+
if (delegation) {
|
|
919
|
+
const finalHop = delegation.lineage.at(-1);
|
|
920
|
+
if (!finalHop || finalHop.principalId !== performer.principalId || finalHop.principalType !== performer.principalType) {
|
|
921
|
+
fail("actorEnvelope.delegation.lineage", "must end with the performer");
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
if (performer.principalType === "external_agent" && (!performer.installationId || !performer.provider)) {
|
|
925
|
+
fail("actorEnvelope.performer", "external_agent requires installationId and provider");
|
|
926
|
+
}
|
|
927
|
+
if (performer.principalType === "managed_agent" && !performer.agentInstanceId) {
|
|
928
|
+
fail("actorEnvelope.performer.agentInstanceId", "is required for managed_agent");
|
|
929
|
+
}
|
|
930
|
+
if (performer.principalType === "browser_automation" && (!performer.installationId || !performer.browserSessionId)) {
|
|
931
|
+
fail("actorEnvelope.performer", "browser_automation requires installationId and browserSessionId");
|
|
932
|
+
}
|
|
933
|
+
return {
|
|
934
|
+
schemaVersion: ACTOR_ENVELOPE_VERSION,
|
|
935
|
+
organizationId,
|
|
936
|
+
accountableSubject,
|
|
937
|
+
performer,
|
|
938
|
+
delegation,
|
|
939
|
+
channel,
|
|
940
|
+
assurance,
|
|
941
|
+
action,
|
|
942
|
+
issuedAt,
|
|
943
|
+
...expiresAt ? { expiresAt } : {}
|
|
944
|
+
};
|
|
945
|
+
}
|
|
946
|
+
function canonicalizeActorEnvelope(value) {
|
|
947
|
+
return canonicalJson(parseActorEnvelope(value));
|
|
948
|
+
}
|
|
949
|
+
function encodeActorEnvelopeHeader(value) {
|
|
950
|
+
const bytes = new TextEncoder().encode(canonicalizeActorEnvelope(value));
|
|
951
|
+
const binary = Array.from(bytes, (byte) => String.fromCharCode(byte)).join("");
|
|
952
|
+
return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
|
|
953
|
+
}
|
|
954
|
+
function decodeActorEnvelopeHeader(value) {
|
|
955
|
+
if (!/^[A-Za-z0-9_-]+$/.test(value)) {
|
|
956
|
+
fail("actorEnvelopeHeader", "must be unpadded base64url");
|
|
957
|
+
}
|
|
958
|
+
const padded = value.replaceAll("-", "+").replaceAll("_", "/") + "=".repeat((4 - value.length % 4) % 4);
|
|
959
|
+
let bytes;
|
|
960
|
+
try {
|
|
961
|
+
bytes = Uint8Array.from(atob(padded), (character) => character.charCodeAt(0));
|
|
962
|
+
} catch {
|
|
963
|
+
fail("actorEnvelopeHeader", "must be valid base64url");
|
|
964
|
+
}
|
|
965
|
+
try {
|
|
966
|
+
return parseActorEnvelope(JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes)));
|
|
967
|
+
} catch (error) {
|
|
968
|
+
if (error instanceof ActorEnvelopeValidationError) throw error;
|
|
969
|
+
fail("actorEnvelopeHeader", "must contain a valid UTF-8 actor envelope");
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
function canonicalJson(value) {
|
|
973
|
+
if (value === null) return "null";
|
|
974
|
+
if (typeof value === "string" || typeof value === "boolean") return JSON.stringify(value);
|
|
975
|
+
if (typeof value === "number") {
|
|
976
|
+
if (!Number.isFinite(value)) throw new TypeError("Canonical JSON accepts finite numbers only.");
|
|
977
|
+
return JSON.stringify(value);
|
|
978
|
+
}
|
|
979
|
+
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
|
|
980
|
+
if (typeof value !== "object" || !value || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) {
|
|
981
|
+
throw new TypeError("Canonical JSON accepts JSON objects, arrays, and primitives only.");
|
|
982
|
+
}
|
|
983
|
+
const entries = Object.entries(value).filter(([, item]) => item !== void 0).sort(([left], [right]) => left.localeCompare(right));
|
|
984
|
+
return `{${entries.map(([key, item]) => `${JSON.stringify(key)}:${canonicalJson(item)}`).join(",")}}`;
|
|
985
|
+
}
|
|
986
|
+
function parseAccountableSubject(value) {
|
|
987
|
+
const path = "actorEnvelope.accountableSubject";
|
|
988
|
+
const subject = record(value, path);
|
|
989
|
+
exactKeys(subject, ["principalType", "principalId", "displayName"], path);
|
|
990
|
+
return {
|
|
991
|
+
principalType: member(subject.principalType, ACCOUNTABLE_SUBJECT_TYPES, `${path}.principalType`),
|
|
992
|
+
principalId: text(subject.principalId, `${path}.principalId`),
|
|
993
|
+
...subject.displayName === void 0 ? {} : { displayName: text(subject.displayName, `${path}.displayName`) }
|
|
994
|
+
};
|
|
995
|
+
}
|
|
996
|
+
async function sha256CanonicalValue(value) {
|
|
997
|
+
const bytes = new TextEncoder().encode(canonicalJson(value));
|
|
998
|
+
const digest = await crypto.subtle.digest("SHA-256", bytes);
|
|
999
|
+
return `sha256:${Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
|
|
1000
|
+
}
|
|
1001
|
+
function parseIdentity(value, path) {
|
|
1002
|
+
const identity = record(value, path);
|
|
1003
|
+
exactKeys(identity, [
|
|
1004
|
+
"principalType",
|
|
1005
|
+
"principalId",
|
|
1006
|
+
"displayName",
|
|
1007
|
+
"installationId",
|
|
1008
|
+
"agentInstanceId",
|
|
1009
|
+
"browserSessionId",
|
|
1010
|
+
"provider",
|
|
1011
|
+
"model"
|
|
1012
|
+
], path);
|
|
1013
|
+
return {
|
|
1014
|
+
principalType: member(identity.principalType, PRINCIPAL_TYPES, `${path}.principalType`),
|
|
1015
|
+
principalId: text(identity.principalId, `${path}.principalId`),
|
|
1016
|
+
...optionalTextFields(identity, path, [
|
|
1017
|
+
"displayName",
|
|
1018
|
+
"installationId",
|
|
1019
|
+
"agentInstanceId",
|
|
1020
|
+
"browserSessionId",
|
|
1021
|
+
"provider",
|
|
1022
|
+
"model"
|
|
1023
|
+
])
|
|
1024
|
+
};
|
|
1025
|
+
}
|
|
1026
|
+
function parseDelegation(value) {
|
|
1027
|
+
if (value === null) return null;
|
|
1028
|
+
const delegation = record(value, "actorEnvelope.delegation");
|
|
1029
|
+
exactKeys(delegation, ["taskId", "runId", "lineage"], "actorEnvelope.delegation");
|
|
1030
|
+
if (!Array.isArray(delegation.lineage) || delegation.lineage.length === 0 || delegation.lineage.length > 16) {
|
|
1031
|
+
fail("actorEnvelope.delegation.lineage", "must contain between 1 and 16 hops");
|
|
1032
|
+
}
|
|
1033
|
+
return {
|
|
1034
|
+
taskId: text(delegation.taskId, "actorEnvelope.delegation.taskId"),
|
|
1035
|
+
runId: text(delegation.runId, "actorEnvelope.delegation.runId"),
|
|
1036
|
+
lineage: delegation.lineage.map((value2, index) => {
|
|
1037
|
+
const hop = record(value2, `actorEnvelope.delegation.lineage[${index}]`);
|
|
1038
|
+
exactKeys(hop, ["principalType", "principalId"], `actorEnvelope.delegation.lineage[${index}]`);
|
|
1039
|
+
return {
|
|
1040
|
+
principalType: member(hop.principalType, PRINCIPAL_TYPES, `actorEnvelope.delegation.lineage[${index}].principalType`),
|
|
1041
|
+
principalId: text(hop.principalId, `actorEnvelope.delegation.lineage[${index}].principalId`)
|
|
1042
|
+
};
|
|
1043
|
+
})
|
|
1044
|
+
};
|
|
1045
|
+
}
|
|
1046
|
+
function record(value, path) {
|
|
1047
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) fail(path, "must be an object");
|
|
1048
|
+
return value;
|
|
1049
|
+
}
|
|
1050
|
+
function exactKeys(value, allowed, path) {
|
|
1051
|
+
const unknown = Object.keys(value).filter((key) => !allowed.includes(key));
|
|
1052
|
+
if (unknown.length) fail(path, `contains unknown field ${unknown[0]}`);
|
|
1053
|
+
}
|
|
1054
|
+
function text(value, path) {
|
|
1055
|
+
if (typeof value !== "string" || value.trim() !== value || value.length === 0 || value.length > 256) {
|
|
1056
|
+
fail(path, "must be a non-empty trimmed string of at most 256 characters");
|
|
1057
|
+
}
|
|
1058
|
+
return value;
|
|
1059
|
+
}
|
|
1060
|
+
function timestamp(value, path) {
|
|
1061
|
+
const result = text(value, path);
|
|
1062
|
+
if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/.test(result) || Number.isNaN(Date.parse(result))) {
|
|
1063
|
+
fail(path, "must be an RFC 3339 UTC timestamp");
|
|
1064
|
+
}
|
|
1065
|
+
return result;
|
|
1066
|
+
}
|
|
1067
|
+
function member(value, values, path) {
|
|
1068
|
+
if (typeof value !== "string" || !values.has(value)) fail(path, "contains an unsupported value");
|
|
1069
|
+
return value;
|
|
1070
|
+
}
|
|
1071
|
+
function textArray(value, path) {
|
|
1072
|
+
if (!Array.isArray(value) || value.length === 0 || value.length > 16) fail(path, "must contain between 1 and 16 values");
|
|
1073
|
+
return value.map((item, index) => text(item, `${path}[${index}]`));
|
|
1074
|
+
}
|
|
1075
|
+
function optionalTextFields(value, path, keys) {
|
|
1076
|
+
return Object.fromEntries(keys.flatMap((key) => value[key] === void 0 ? [] : [[key, text(value[key], `${path}.${key}`)]]));
|
|
1077
|
+
}
|
|
1078
|
+
function fail(path, message) {
|
|
1079
|
+
throw new ActorEnvelopeValidationError(path, message);
|
|
1080
|
+
}
|
|
1081
|
+
|
|
846
1082
|
// src/action-upload.ts
|
|
847
1083
|
function parseTopoloActionUploadContract(value) {
|
|
848
1084
|
if (!isRecord2(value) || value.profile !== "inline-base64" && value.profile !== "url-session") {
|
|
@@ -1133,11 +1369,15 @@ var TopoloClient = class _TopoloClient {
|
|
|
1133
1369
|
const platformAppId = resolveConfiguredAppId(resolvedService.serviceKey, this.appIds);
|
|
1134
1370
|
if (platformAppId) headers.set("X-App-ID", platformAppId);
|
|
1135
1371
|
applyAuthHeaders(headers, this.credential);
|
|
1136
|
-
const
|
|
1372
|
+
const actorEnvelope = opts.actorEnvelope === void 0 ? void 0 : parseActorEnvelope(opts.actorEnvelope);
|
|
1373
|
+
const requestId = actorEnvelope?.action.requestId ?? generateRequestId();
|
|
1137
1374
|
applyAuditHeaders(headers, this.agent, requestId);
|
|
1138
1375
|
if (opts.headers) {
|
|
1139
1376
|
for (const [k, v] of Object.entries(opts.headers)) headers.set(k, v);
|
|
1140
1377
|
}
|
|
1378
|
+
if (actorEnvelope) {
|
|
1379
|
+
headers.set(TOPOLO_ACTOR_ENVELOPE_HEADER, encodeActorEnvelopeHeader(actorEnvelope));
|
|
1380
|
+
}
|
|
1141
1381
|
if (opts.resource) {
|
|
1142
1382
|
const resourceType = opts.resource.resourceType.trim();
|
|
1143
1383
|
const resourceId = opts.resource.resourceId.trim();
|
|
@@ -1584,14 +1824,14 @@ var TopoloClient = class _TopoloClient {
|
|
|
1584
1824
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
1585
1825
|
throw new TopoloSdkError("action_catalog_summary_unavailable", "Action catalog summary is unavailable.");
|
|
1586
1826
|
}
|
|
1587
|
-
const
|
|
1588
|
-
if (
|
|
1827
|
+
const record2 = raw;
|
|
1828
|
+
if (record2.algorithm !== "sha256-v1" || typeof record2.digest !== "string" || !/^[a-f0-9]{64}$/.test(record2.digest) || !Number.isSafeInteger(record2.actionCount) || Number(record2.actionCount) < 0) {
|
|
1589
1829
|
throw new TopoloSdkError("action_catalog_summary_unavailable", "Action catalog summary is invalid.");
|
|
1590
1830
|
}
|
|
1591
1831
|
return {
|
|
1592
1832
|
algorithm: "sha256-v1",
|
|
1593
|
-
digest:
|
|
1594
|
-
actionCount: Number(
|
|
1833
|
+
digest: record2.digest,
|
|
1834
|
+
actionCount: Number(record2.actionCount)
|
|
1595
1835
|
};
|
|
1596
1836
|
}
|
|
1597
1837
|
/** @deprecated Prefer searchActions() so callers retain the continuation cursor. */
|
|
@@ -1791,6 +2031,19 @@ var TopoloClient = class _TopoloClient {
|
|
|
1791
2031
|
);
|
|
1792
2032
|
}
|
|
1793
2033
|
const mapped = mapActionInput(entry, input);
|
|
2034
|
+
if (options.actorEnvelope) {
|
|
2035
|
+
const actorEnvelope = parseActorEnvelope(options.actorEnvelope);
|
|
2036
|
+
if (actorEnvelope.action.actionId !== entry.actionId) {
|
|
2037
|
+
throw new TopoloSdkError("actor_envelope_action_mismatch", "Actor envelope actionId does not match the executed action.");
|
|
2038
|
+
}
|
|
2039
|
+
if (actorEnvelope.action.inputHash !== await sha256CanonicalValue(input)) {
|
|
2040
|
+
throw new TopoloSdkError("actor_envelope_input_mismatch", "Actor envelope inputHash does not match the executed action input.");
|
|
2041
|
+
}
|
|
2042
|
+
const envelopeResource = actorEnvelope.action.resourceType && actorEnvelope.action.resourceId ? { resourceType: actorEnvelope.action.resourceType, resourceId: actorEnvelope.action.resourceId } : void 0;
|
|
2043
|
+
if (envelopeResource?.resourceType !== options.resource?.resourceType || envelopeResource?.resourceId !== options.resource?.resourceId) {
|
|
2044
|
+
throw new TopoloSdkError("actor_envelope_resource_mismatch", "Actor envelope resource does not match the executed action resource.");
|
|
2045
|
+
}
|
|
2046
|
+
}
|
|
1794
2047
|
const service = entry.appSlug || entry.appId;
|
|
1795
2048
|
const result = await this.request({
|
|
1796
2049
|
service,
|
|
@@ -1800,6 +2053,7 @@ var TopoloClient = class _TopoloClient {
|
|
|
1800
2053
|
...mapped.query ? { query: mapped.query } : {},
|
|
1801
2054
|
...mapped.body !== void 0 ? { body: mapped.body } : {},
|
|
1802
2055
|
...options.resource ? { resource: options.resource } : {},
|
|
2056
|
+
...options.actorEnvelope ? { actorEnvelope: options.actorEnvelope } : {},
|
|
1803
2057
|
headers: {
|
|
1804
2058
|
[TOPOLO_ACTION_ID_HEADER]: entry.actionId,
|
|
1805
2059
|
[TOPOLO_ACTION_NAME_HEADER]: entry.name
|
|
@@ -2224,10 +2478,10 @@ function extractRequired(body) {
|
|
|
2224
2478
|
if (Array.isArray(maybe.required)) return canonicalTopoloRequired(maybe.required);
|
|
2225
2479
|
for (const nested of [maybe.details, maybe.error]) {
|
|
2226
2480
|
if (!nested || typeof nested !== "object" || Array.isArray(nested)) continue;
|
|
2227
|
-
const
|
|
2228
|
-
if (Array.isArray(
|
|
2229
|
-
if (
|
|
2230
|
-
const required =
|
|
2481
|
+
const record2 = nested;
|
|
2482
|
+
if (Array.isArray(record2.required)) return canonicalTopoloRequired(record2.required);
|
|
2483
|
+
if (record2.details && typeof record2.details === "object" && !Array.isArray(record2.details)) {
|
|
2484
|
+
const required = record2.details.required;
|
|
2231
2485
|
if (Array.isArray(required)) return canonicalTopoloRequired(required);
|
|
2232
2486
|
}
|
|
2233
2487
|
}
|
|
@@ -2304,8 +2558,8 @@ function compactResultValue(value, depth, budget) {
|
|
|
2304
2558
|
return value.length > ACTION_RESULT_MAX_ITEMS ? { count: value.length, items, omittedItems: value.length - ACTION_RESULT_MAX_ITEMS } : items;
|
|
2305
2559
|
}
|
|
2306
2560
|
if (typeof value === "object") {
|
|
2307
|
-
const
|
|
2308
|
-
const allKeys = Object.keys(
|
|
2561
|
+
const record2 = value;
|
|
2562
|
+
const allKeys = Object.keys(record2);
|
|
2309
2563
|
const ranked = allKeys.sort((left, right) => resultKeyPriority(left) - resultKeyPriority(right));
|
|
2310
2564
|
const prioritized = ranked.filter((key) => resultKeyPriority(key) < 2);
|
|
2311
2565
|
const ordinary = ranked.filter((key) => resultKeyPriority(key) === 2);
|
|
@@ -2314,7 +2568,7 @@ function compactResultValue(value, depth, budget) {
|
|
|
2314
2568
|
...prioritized,
|
|
2315
2569
|
...ordinary.slice(0, ordinaryLimit)
|
|
2316
2570
|
].slice(0, ACTION_RESULT_MAX_KEYS);
|
|
2317
|
-
const compact = Object.fromEntries(keys.map((key) => [key, compactResultValue(
|
|
2571
|
+
const compact = Object.fromEntries(keys.map((key) => [key, compactResultValue(record2[key], depth + 1, budget)]));
|
|
2318
2572
|
if (allKeys.length > keys.length) compact["_omittedKeys"] = allKeys.length - keys.length;
|
|
2319
2573
|
return compact;
|
|
2320
2574
|
}
|
|
@@ -2430,8 +2684,8 @@ function canonicalPermission(value, appId) {
|
|
|
2430
2684
|
function stableJson(value) {
|
|
2431
2685
|
if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
|
|
2432
2686
|
if (value && typeof value === "object") {
|
|
2433
|
-
const
|
|
2434
|
-
return `{${Object.keys(
|
|
2687
|
+
const record2 = value;
|
|
2688
|
+
return `{${Object.keys(record2).sort().map((key) => `${JSON.stringify(key)}:${stableJson(record2[key])}`).join(",")}}`;
|
|
2435
2689
|
}
|
|
2436
2690
|
return JSON.stringify(value);
|
|
2437
2691
|
}
|
|
@@ -2572,8 +2826,8 @@ function resourceSchema(resource) {
|
|
|
2572
2826
|
function derivedOutputSchema(action) {
|
|
2573
2827
|
const resource = resourceFor(action);
|
|
2574
2828
|
const operation = operationFor(action);
|
|
2575
|
-
const
|
|
2576
|
-
const records = { type: "array", items:
|
|
2829
|
+
const record2 = resourceSchema(resource);
|
|
2830
|
+
const records = { type: "array", items: record2 };
|
|
2577
2831
|
const provenance = {
|
|
2578
2832
|
"x-topolo-contract-source": "derived-action-metadata",
|
|
2579
2833
|
"x-topolo-contract-precision": "structural"
|
|
@@ -2605,12 +2859,12 @@ function derivedOutputSchema(action) {
|
|
|
2605
2859
|
title: `${action.title || action.name} response`,
|
|
2606
2860
|
description: `Resource response returned by ${action.name}. The service may return the resource directly or in an envelope.`,
|
|
2607
2861
|
oneOf: [
|
|
2608
|
-
|
|
2862
|
+
record2,
|
|
2609
2863
|
{
|
|
2610
2864
|
type: "object",
|
|
2611
2865
|
properties: {
|
|
2612
|
-
data:
|
|
2613
|
-
[resource]:
|
|
2866
|
+
data: record2,
|
|
2867
|
+
[resource]: record2
|
|
2614
2868
|
},
|
|
2615
2869
|
additionalProperties: true
|
|
2616
2870
|
}
|
|
@@ -2625,7 +2879,7 @@ function derivedOutputSchema(action) {
|
|
|
2625
2879
|
properties: {
|
|
2626
2880
|
success: { type: "boolean" },
|
|
2627
2881
|
id: { type: "string" },
|
|
2628
|
-
data: { oneOf: [
|
|
2882
|
+
data: { oneOf: [record2, { type: "object", additionalProperties: true }] },
|
|
2629
2883
|
result: { type: "object", additionalProperties: true },
|
|
2630
2884
|
message: { type: "string" }
|
|
2631
2885
|
},
|
|
@@ -4414,10 +4668,13 @@ function createTopolo(options) {
|
|
|
4414
4668
|
}
|
|
4415
4669
|
export {
|
|
4416
4670
|
ACTION_CATALOG_DIGEST_ALGORITHM,
|
|
4671
|
+
ACTOR_ENVELOPE_VERSION,
|
|
4672
|
+
ActorEnvelopeValidationError,
|
|
4417
4673
|
CrmModule,
|
|
4418
4674
|
DEFAULT_APP_URLS,
|
|
4419
4675
|
IdentityModule,
|
|
4420
4676
|
P2PModule,
|
|
4677
|
+
TOPOLO_ACTOR_ENVELOPE_HEADER,
|
|
4421
4678
|
TOPOLO_ENVIRONMENT_SERVICE_URLS,
|
|
4422
4679
|
TOPOLO_RUNTIME_ENVIRONMENTS,
|
|
4423
4680
|
TOPOLO_WIDGET_CONTRACT_VERSION,
|
|
@@ -4435,10 +4692,12 @@ export {
|
|
|
4435
4692
|
actionContractCompleteness,
|
|
4436
4693
|
applicationEntryFromCatalog,
|
|
4437
4694
|
assertTopoloServiceUrlEnvironment,
|
|
4695
|
+
canonicalJson,
|
|
4438
4696
|
canonicalTopoloErrorCode,
|
|
4439
4697
|
canonicalTopoloRequestId,
|
|
4440
4698
|
canonicalTopoloRequired,
|
|
4441
4699
|
canonicalTopoloRetryAfter,
|
|
4700
|
+
canonicalizeActorEnvelope,
|
|
4442
4701
|
compactActionCatalogEntry,
|
|
4443
4702
|
compactActionPlan,
|
|
4444
4703
|
compactActionPreparation,
|
|
@@ -4448,12 +4707,14 @@ export {
|
|
|
4448
4707
|
createTopoloAgentExecutionContext,
|
|
4449
4708
|
createTopoloWidgetError,
|
|
4450
4709
|
createTopoloWidgetResponse,
|
|
4710
|
+
decodeActorEnvelopeHeader,
|
|
4451
4711
|
defineTopoloActionsWidget,
|
|
4452
4712
|
defineTopoloActivityWidget,
|
|
4453
4713
|
defineTopoloChartWidget,
|
|
4454
4714
|
defineTopoloNotificationsWidget,
|
|
4455
4715
|
defineTopoloStatsWidget,
|
|
4456
4716
|
digestTopoloActionCatalog,
|
|
4717
|
+
encodeActorEnvelopeHeader,
|
|
4457
4718
|
encodeTopoloActionBase64,
|
|
4458
4719
|
enrichActionContract,
|
|
4459
4720
|
extractTopoloServiceErrorCode,
|
|
@@ -4462,6 +4723,7 @@ export {
|
|
|
4462
4723
|
friendlyAppId,
|
|
4463
4724
|
isTopoloWidgetOutputSchema,
|
|
4464
4725
|
listApplicationDirectory,
|
|
4726
|
+
parseActorEnvelope,
|
|
4465
4727
|
parseTopoloActionUploadContract,
|
|
4466
4728
|
preferExactActionMatches,
|
|
4467
4729
|
prepareTopoloActionBase64UploadInput,
|
|
@@ -4470,6 +4732,7 @@ export {
|
|
|
4470
4732
|
resolveApplicationDirectoryEntry,
|
|
4471
4733
|
resolveCatalogServiceUrl,
|
|
4472
4734
|
resolveServiceUrl,
|
|
4735
|
+
sha256CanonicalValue,
|
|
4473
4736
|
topoloErrorHint,
|
|
4474
4737
|
topoloServiceOrigins,
|
|
4475
4738
|
validateTopoloActionInput,
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@topolo/sdk",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
3
|
+
"packageManager": "pnpm@10.17.1",
|
|
4
|
+
"version": "0.16.0",
|
|
5
|
+
"topoloPublishedVersion": "0.16.0",
|
|
5
6
|
"description": "Typed client SDK for the Topolo platform. Used by TopoloCli, TopoloMCP, and third-party agents.",
|
|
6
7
|
"license": "UNLICENSED",
|
|
7
8
|
"author": "Topolo",
|
|
@@ -32,7 +33,7 @@
|
|
|
32
33
|
"test": "vitest run",
|
|
33
34
|
"test:watch": "vitest",
|
|
34
35
|
"prepublishOnly": "npm run build && npm run verify:package",
|
|
35
|
-
"verify:package": "
|
|
36
|
+
"verify:package": "npm pack --dry-run",
|
|
36
37
|
"coverage": "vitest run --coverage"
|
|
37
38
|
},
|
|
38
39
|
"topolo_quality": {
|
|
@@ -50,7 +51,7 @@
|
|
|
50
51
|
"tsconfig.json",
|
|
51
52
|
"vitest.config.ts"
|
|
52
53
|
],
|
|
53
|
-
"digest": "
|
|
54
|
+
"digest": "764b584c653234937a1ba11f34a898941c62ebd8bf973db6ed0869b7f4a11951"
|
|
54
55
|
}
|
|
55
56
|
}
|
|
56
57
|
]
|
|
@@ -64,5 +65,9 @@
|
|
|
64
65
|
},
|
|
65
66
|
"engines": {
|
|
66
67
|
"node": ">=20"
|
|
68
|
+
},
|
|
69
|
+
"repository": {
|
|
70
|
+
"type": "git",
|
|
71
|
+
"url": "git+https://github.com/Topolo-io/topolo-sdk.git"
|
|
67
72
|
}
|
|
68
73
|
}
|