@topolo/sdk 0.15.0 → 0.17.1
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 +14 -0
- package/dist/index.cjs +333 -214
- package/dist/index.d.cts +83 -59
- package/dist/index.d.ts +83 -59
- package/dist/index.js +330 -214
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -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") {
|
|
@@ -1050,6 +1286,7 @@ var NEGATIVE_CACHE_TTL_MS = 60 * 1e3;
|
|
|
1050
1286
|
var TopoloClient = class _TopoloClient {
|
|
1051
1287
|
credential;
|
|
1052
1288
|
agent;
|
|
1289
|
+
actorContext;
|
|
1053
1290
|
serviceUrls;
|
|
1054
1291
|
environment;
|
|
1055
1292
|
appIds;
|
|
@@ -1080,6 +1317,11 @@ var TopoloClient = class _TopoloClient {
|
|
|
1080
1317
|
if (!options.agent?.clientName) throw new TopoloAuthError("agent.clientName is required");
|
|
1081
1318
|
this.credential = options.credential;
|
|
1082
1319
|
this.agent = options.agent;
|
|
1320
|
+
this.actorContext = options.actorContext ? {
|
|
1321
|
+
channel: options.actorContext.channel,
|
|
1322
|
+
taskId: options.actorContext.taskId ?? `task_${generateRequestId()}`,
|
|
1323
|
+
runId: options.actorContext.runId ?? `run_${generateRequestId()}`
|
|
1324
|
+
} : void 0;
|
|
1083
1325
|
this.environment = options.environment;
|
|
1084
1326
|
this.serviceUrls = options.environment ? topoloServiceOrigins(options.serviceUrls, options.environment) : options.serviceUrls;
|
|
1085
1327
|
this.appIds = options.appIds;
|
|
@@ -1133,11 +1375,15 @@ var TopoloClient = class _TopoloClient {
|
|
|
1133
1375
|
const platformAppId = resolveConfiguredAppId(resolvedService.serviceKey, this.appIds);
|
|
1134
1376
|
if (platformAppId) headers.set("X-App-ID", platformAppId);
|
|
1135
1377
|
applyAuthHeaders(headers, this.credential);
|
|
1136
|
-
const
|
|
1378
|
+
const actorEnvelope = opts.actorEnvelope === void 0 ? void 0 : parseActorEnvelope(opts.actorEnvelope);
|
|
1379
|
+
const requestId = actorEnvelope?.action.requestId ?? generateRequestId();
|
|
1137
1380
|
applyAuditHeaders(headers, this.agent, requestId);
|
|
1138
1381
|
if (opts.headers) {
|
|
1139
1382
|
for (const [k, v] of Object.entries(opts.headers)) headers.set(k, v);
|
|
1140
1383
|
}
|
|
1384
|
+
if (actorEnvelope) {
|
|
1385
|
+
headers.set(TOPOLO_ACTOR_ENVELOPE_HEADER, encodeActorEnvelopeHeader(actorEnvelope));
|
|
1386
|
+
}
|
|
1141
1387
|
if (opts.resource) {
|
|
1142
1388
|
const resourceType = opts.resource.resourceType.trim();
|
|
1143
1389
|
const resourceId = opts.resource.resourceId.trim();
|
|
@@ -1791,6 +2037,20 @@ var TopoloClient = class _TopoloClient {
|
|
|
1791
2037
|
);
|
|
1792
2038
|
}
|
|
1793
2039
|
const mapped = mapActionInput(entry, input);
|
|
2040
|
+
const resolvedActorEnvelope = options.actorEnvelope ?? await this.buildVerifiedAgentActorEnvelope(entry.actionId, input, options.resource);
|
|
2041
|
+
if (resolvedActorEnvelope) {
|
|
2042
|
+
const actorEnvelope = parseActorEnvelope(resolvedActorEnvelope);
|
|
2043
|
+
if (actorEnvelope.action.actionId !== entry.actionId) {
|
|
2044
|
+
throw new TopoloSdkError("actor_envelope_action_mismatch", "Actor envelope actionId does not match the executed action.");
|
|
2045
|
+
}
|
|
2046
|
+
if (actorEnvelope.action.inputHash !== await sha256CanonicalValue(input)) {
|
|
2047
|
+
throw new TopoloSdkError("actor_envelope_input_mismatch", "Actor envelope inputHash does not match the executed action input.");
|
|
2048
|
+
}
|
|
2049
|
+
const envelopeResource = actorEnvelope.action.resourceType && actorEnvelope.action.resourceId ? { resourceType: actorEnvelope.action.resourceType, resourceId: actorEnvelope.action.resourceId } : void 0;
|
|
2050
|
+
if (envelopeResource?.resourceType !== options.resource?.resourceType || envelopeResource?.resourceId !== options.resource?.resourceId) {
|
|
2051
|
+
throw new TopoloSdkError("actor_envelope_resource_mismatch", "Actor envelope resource does not match the executed action resource.");
|
|
2052
|
+
}
|
|
2053
|
+
}
|
|
1794
2054
|
const service = entry.appSlug || entry.appId;
|
|
1795
2055
|
const result = await this.request({
|
|
1796
2056
|
service,
|
|
@@ -1800,6 +2060,7 @@ var TopoloClient = class _TopoloClient {
|
|
|
1800
2060
|
...mapped.query ? { query: mapped.query } : {},
|
|
1801
2061
|
...mapped.body !== void 0 ? { body: mapped.body } : {},
|
|
1802
2062
|
...options.resource ? { resource: options.resource } : {},
|
|
2063
|
+
...resolvedActorEnvelope ? { actorEnvelope: resolvedActorEnvelope } : {},
|
|
1803
2064
|
headers: {
|
|
1804
2065
|
[TOPOLO_ACTION_ID_HEADER]: entry.actionId,
|
|
1805
2066
|
[TOPOLO_ACTION_NAME_HEADER]: entry.name
|
|
@@ -1808,6 +2069,56 @@ var TopoloClient = class _TopoloClient {
|
|
|
1808
2069
|
});
|
|
1809
2070
|
return redactActionResult(result);
|
|
1810
2071
|
}
|
|
2072
|
+
async buildVerifiedAgentActorEnvelope(actionId, input, resource) {
|
|
2073
|
+
if (!this.actorContext) return void 0;
|
|
2074
|
+
const identity = await this.introspect();
|
|
2075
|
+
const principalType = identity.user.principalType;
|
|
2076
|
+
if (principalType !== "agent_employee" && principalType !== "external_agent") return void 0;
|
|
2077
|
+
if (!identity.organization || !identity.user.accountableUserId || !identity.user.principalId) {
|
|
2078
|
+
throw new TopoloSdkError(
|
|
2079
|
+
"incomplete_agent_identity",
|
|
2080
|
+
"Verified agent credentials must resolve an organization, accountable user, and principal id."
|
|
2081
|
+
);
|
|
2082
|
+
}
|
|
2083
|
+
const performer = principalType === "external_agent" ? {
|
|
2084
|
+
principalType: "external_agent",
|
|
2085
|
+
principalId: identity.user.principalId,
|
|
2086
|
+
installationId: identity.user.installationId ?? void 0,
|
|
2087
|
+
provider: identity.user.provider ?? void 0,
|
|
2088
|
+
model: identity.user.model ?? void 0
|
|
2089
|
+
} : {
|
|
2090
|
+
principalType: "managed_agent",
|
|
2091
|
+
principalId: identity.user.principalId,
|
|
2092
|
+
agentInstanceId: identity.user.agentInstanceId ?? void 0,
|
|
2093
|
+
model: identity.user.model ?? void 0
|
|
2094
|
+
};
|
|
2095
|
+
if (performer.principalType === "external_agent" && (!performer.installationId || !performer.provider) || performer.principalType === "managed_agent" && !performer.agentInstanceId) {
|
|
2096
|
+
throw new TopoloSdkError(
|
|
2097
|
+
"incomplete_agent_identity",
|
|
2098
|
+
"Verified agent credentials are missing the performer identity required for provenance."
|
|
2099
|
+
);
|
|
2100
|
+
}
|
|
2101
|
+
return parseActorEnvelope({
|
|
2102
|
+
schemaVersion: 1,
|
|
2103
|
+
organizationId: identity.organization.id,
|
|
2104
|
+
accountableSubject: { principalType: "human", principalId: identity.user.accountableUserId },
|
|
2105
|
+
performer,
|
|
2106
|
+
delegation: {
|
|
2107
|
+
taskId: this.actorContext.taskId,
|
|
2108
|
+
runId: this.actorContext.runId,
|
|
2109
|
+
lineage: [{ principalType: performer.principalType, principalId: performer.principalId }]
|
|
2110
|
+
},
|
|
2111
|
+
channel: this.actorContext.channel,
|
|
2112
|
+
assurance: { level: "workload_identity", methods: ["client_credentials"] },
|
|
2113
|
+
action: {
|
|
2114
|
+
requestId: generateRequestId(),
|
|
2115
|
+
actionId,
|
|
2116
|
+
...resource ? resource : {},
|
|
2117
|
+
inputHash: await sha256CanonicalValue(input)
|
|
2118
|
+
},
|
|
2119
|
+
issuedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2120
|
+
});
|
|
2121
|
+
}
|
|
1811
2122
|
/**
|
|
1812
2123
|
* Introspect the current credential. Returns the caller's resolved identity,
|
|
1813
2124
|
* organization, and permission set. Used by CLI `whoami` and by MCP to gate
|
|
@@ -1835,7 +2146,18 @@ var TopoloClient = class _TopoloClient {
|
|
|
1835
2146
|
email: user.email,
|
|
1836
2147
|
name: user.name ?? null,
|
|
1837
2148
|
role: user.role ?? (kind === "api_key" ? "service" : "member"),
|
|
1838
|
-
permissions: payload.permissions ?? user.permissions ?? []
|
|
2149
|
+
permissions: payload.permissions ?? user.permissions ?? [],
|
|
2150
|
+
...normalizeAgentPrincipalType(user.principalType) ? {
|
|
2151
|
+
principalId: readOptionalString(user.principalId),
|
|
2152
|
+
principalType: normalizeAgentPrincipalType(user.principalType),
|
|
2153
|
+
accountableUserId: readOptionalString(user.accountableUserId),
|
|
2154
|
+
agentInstanceId: readOptionalString(user.agentInstanceId),
|
|
2155
|
+
installationId: readOptionalString(user.installationId),
|
|
2156
|
+
provider: readOptionalString(user.provider),
|
|
2157
|
+
model: readOptionalString(user.model),
|
|
2158
|
+
serviceScopes: stringArray(user.serviceScopes),
|
|
2159
|
+
policyRef: readOptionalString(user.policyRef)
|
|
2160
|
+
} : {}
|
|
1839
2161
|
},
|
|
1840
2162
|
organization: organization ? {
|
|
1841
2163
|
id: organization.id,
|
|
@@ -2200,6 +2522,9 @@ function normalizeCredentialAuthorizationMode(value, kind) {
|
|
|
2200
2522
|
if (value === "context_rbac" || value === "oauth_scopes" || value === "session_rbac") return value;
|
|
2201
2523
|
return "unknown";
|
|
2202
2524
|
}
|
|
2525
|
+
function normalizeAgentPrincipalType(value) {
|
|
2526
|
+
return value === "agent_employee" || value === "external_agent" ? value : null;
|
|
2527
|
+
}
|
|
2203
2528
|
function describeError(body, fallback) {
|
|
2204
2529
|
if (body && typeof body === "object") {
|
|
2205
2530
|
const maybe = body;
|
|
@@ -2924,218 +3249,6 @@ var IdentityModule = class {
|
|
|
2924
3249
|
}
|
|
2925
3250
|
};
|
|
2926
3251
|
|
|
2927
|
-
// src/actor-envelope.ts
|
|
2928
|
-
var ACTOR_ENVELOPE_VERSION = 1;
|
|
2929
|
-
var PRINCIPAL_TYPES = /* @__PURE__ */ new Set([
|
|
2930
|
-
"human",
|
|
2931
|
-
"managed_agent",
|
|
2932
|
-
"external_agent",
|
|
2933
|
-
"service",
|
|
2934
|
-
"browser_automation"
|
|
2935
|
-
]);
|
|
2936
|
-
var ACCOUNTABLE_SUBJECT_TYPES = /* @__PURE__ */ new Set(["human", "organization"]);
|
|
2937
|
-
var CHANNELS = /* @__PURE__ */ new Set(["web", "mobile", "cli", "mcp", "api", "workflow", "browser_automation"]);
|
|
2938
|
-
var ASSURANCE_LEVELS = /* @__PURE__ */ new Set(["authenticated", "workload_identity", "user_verified"]);
|
|
2939
|
-
var HASH_PATTERN = /^sha256:[a-f0-9]{64}$/;
|
|
2940
|
-
var ActorEnvelopeValidationError = class extends Error {
|
|
2941
|
-
path;
|
|
2942
|
-
constructor(path, message) {
|
|
2943
|
-
super(`${path}: ${message}`);
|
|
2944
|
-
this.name = "ActorEnvelopeValidationError";
|
|
2945
|
-
this.path = path;
|
|
2946
|
-
}
|
|
2947
|
-
};
|
|
2948
|
-
function parseActorEnvelope(value) {
|
|
2949
|
-
const envelope = record(value, "actorEnvelope");
|
|
2950
|
-
exactKeys(envelope, [
|
|
2951
|
-
"schemaVersion",
|
|
2952
|
-
"organizationId",
|
|
2953
|
-
"accountableSubject",
|
|
2954
|
-
"performer",
|
|
2955
|
-
"delegation",
|
|
2956
|
-
"channel",
|
|
2957
|
-
"assurance",
|
|
2958
|
-
"action",
|
|
2959
|
-
"issuedAt",
|
|
2960
|
-
"expiresAt"
|
|
2961
|
-
], "actorEnvelope");
|
|
2962
|
-
if (envelope.schemaVersion !== ACTOR_ENVELOPE_VERSION) {
|
|
2963
|
-
fail("actorEnvelope.schemaVersion", `must equal ${ACTOR_ENVELOPE_VERSION}`);
|
|
2964
|
-
}
|
|
2965
|
-
const accountableSubject = parseAccountableSubject(envelope.accountableSubject);
|
|
2966
|
-
const performer = parseIdentity(envelope.performer, "actorEnvelope.performer");
|
|
2967
|
-
const organizationId = text(envelope.organizationId, "actorEnvelope.organizationId");
|
|
2968
|
-
const channel = member(envelope.channel, CHANNELS, "actorEnvelope.channel");
|
|
2969
|
-
const issuedAt = timestamp(envelope.issuedAt, "actorEnvelope.issuedAt");
|
|
2970
|
-
const expiresAt = envelope.expiresAt === void 0 ? void 0 : timestamp(envelope.expiresAt, "actorEnvelope.expiresAt");
|
|
2971
|
-
if (expiresAt && Date.parse(expiresAt) <= Date.parse(issuedAt)) {
|
|
2972
|
-
fail("actorEnvelope.expiresAt", "must be later than issuedAt");
|
|
2973
|
-
}
|
|
2974
|
-
const assuranceValue = record(envelope.assurance, "actorEnvelope.assurance");
|
|
2975
|
-
exactKeys(assuranceValue, ["level", "methods"], "actorEnvelope.assurance");
|
|
2976
|
-
const assurance = {
|
|
2977
|
-
level: member(assuranceValue.level, ASSURANCE_LEVELS, "actorEnvelope.assurance.level"),
|
|
2978
|
-
methods: textArray(assuranceValue.methods, "actorEnvelope.assurance.methods")
|
|
2979
|
-
};
|
|
2980
|
-
const actionValue = record(envelope.action, "actorEnvelope.action");
|
|
2981
|
-
exactKeys(actionValue, ["requestId", "actionId", "resourceType", "resourceId", "inputHash"], "actorEnvelope.action");
|
|
2982
|
-
const inputHash = text(actionValue.inputHash, "actorEnvelope.action.inputHash");
|
|
2983
|
-
if (!HASH_PATTERN.test(inputHash)) fail("actorEnvelope.action.inputHash", "must be a lowercase sha256 digest");
|
|
2984
|
-
const action = {
|
|
2985
|
-
requestId: text(actionValue.requestId, "actorEnvelope.action.requestId"),
|
|
2986
|
-
actionId: text(actionValue.actionId, "actorEnvelope.action.actionId"),
|
|
2987
|
-
...actionValue.resourceType === void 0 ? {} : { resourceType: text(actionValue.resourceType, "actorEnvelope.action.resourceType") },
|
|
2988
|
-
...actionValue.resourceId === void 0 ? {} : { resourceId: text(actionValue.resourceId, "actorEnvelope.action.resourceId") },
|
|
2989
|
-
inputHash
|
|
2990
|
-
};
|
|
2991
|
-
if (action.resourceType === void 0 !== (action.resourceId === void 0)) {
|
|
2992
|
-
fail("actorEnvelope.action", "resourceType and resourceId must be provided together");
|
|
2993
|
-
}
|
|
2994
|
-
const delegation = parseDelegation(envelope.delegation);
|
|
2995
|
-
const directHuman = performer.principalType === "human" && performer.principalId === accountableSubject.principalId;
|
|
2996
|
-
if (directHuman && delegation !== null) fail("actorEnvelope.delegation", "must be null for direct human action");
|
|
2997
|
-
if (!directHuman && delegation === null) fail("actorEnvelope.delegation", "is required when performer differs from accountable subject");
|
|
2998
|
-
if (delegation) {
|
|
2999
|
-
const finalHop = delegation.lineage.at(-1);
|
|
3000
|
-
if (!finalHop || finalHop.principalId !== performer.principalId || finalHop.principalType !== performer.principalType) {
|
|
3001
|
-
fail("actorEnvelope.delegation.lineage", "must end with the performer");
|
|
3002
|
-
}
|
|
3003
|
-
}
|
|
3004
|
-
if (performer.principalType === "external_agent" && (!performer.installationId || !performer.provider)) {
|
|
3005
|
-
fail("actorEnvelope.performer", "external_agent requires installationId and provider");
|
|
3006
|
-
}
|
|
3007
|
-
if (performer.principalType === "managed_agent" && !performer.agentInstanceId) {
|
|
3008
|
-
fail("actorEnvelope.performer.agentInstanceId", "is required for managed_agent");
|
|
3009
|
-
}
|
|
3010
|
-
if (performer.principalType === "browser_automation" && (!performer.installationId || !performer.browserSessionId)) {
|
|
3011
|
-
fail("actorEnvelope.performer", "browser_automation requires installationId and browserSessionId");
|
|
3012
|
-
}
|
|
3013
|
-
return {
|
|
3014
|
-
schemaVersion: ACTOR_ENVELOPE_VERSION,
|
|
3015
|
-
organizationId,
|
|
3016
|
-
accountableSubject,
|
|
3017
|
-
performer,
|
|
3018
|
-
delegation,
|
|
3019
|
-
channel,
|
|
3020
|
-
assurance,
|
|
3021
|
-
action,
|
|
3022
|
-
issuedAt,
|
|
3023
|
-
...expiresAt ? { expiresAt } : {}
|
|
3024
|
-
};
|
|
3025
|
-
}
|
|
3026
|
-
function canonicalizeActorEnvelope(value) {
|
|
3027
|
-
return canonicalJson(parseActorEnvelope(value));
|
|
3028
|
-
}
|
|
3029
|
-
function canonicalJson(value) {
|
|
3030
|
-
if (value === null) return "null";
|
|
3031
|
-
if (typeof value === "string" || typeof value === "boolean") return JSON.stringify(value);
|
|
3032
|
-
if (typeof value === "number") {
|
|
3033
|
-
if (!Number.isFinite(value)) throw new TypeError("Canonical JSON accepts finite numbers only.");
|
|
3034
|
-
return JSON.stringify(value);
|
|
3035
|
-
}
|
|
3036
|
-
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
|
|
3037
|
-
if (typeof value !== "object" || !value || ![Object.prototype, null].includes(Object.getPrototypeOf(value))) {
|
|
3038
|
-
throw new TypeError("Canonical JSON accepts JSON objects, arrays, and primitives only.");
|
|
3039
|
-
}
|
|
3040
|
-
const entries = Object.entries(value).filter(([, item]) => item !== void 0).sort(([left], [right]) => left.localeCompare(right));
|
|
3041
|
-
return `{${entries.map(([key, item]) => `${JSON.stringify(key)}:${canonicalJson(item)}`).join(",")}}`;
|
|
3042
|
-
}
|
|
3043
|
-
function parseAccountableSubject(value) {
|
|
3044
|
-
const path = "actorEnvelope.accountableSubject";
|
|
3045
|
-
const subject = record(value, path);
|
|
3046
|
-
exactKeys(subject, ["principalType", "principalId", "displayName"], path);
|
|
3047
|
-
return {
|
|
3048
|
-
principalType: member(subject.principalType, ACCOUNTABLE_SUBJECT_TYPES, `${path}.principalType`),
|
|
3049
|
-
principalId: text(subject.principalId, `${path}.principalId`),
|
|
3050
|
-
...subject.displayName === void 0 ? {} : { displayName: text(subject.displayName, `${path}.displayName`) }
|
|
3051
|
-
};
|
|
3052
|
-
}
|
|
3053
|
-
async function sha256CanonicalValue(value) {
|
|
3054
|
-
const bytes = new TextEncoder().encode(canonicalJson(value));
|
|
3055
|
-
const digest = await crypto.subtle.digest("SHA-256", bytes);
|
|
3056
|
-
return `sha256:${Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
|
|
3057
|
-
}
|
|
3058
|
-
function parseIdentity(value, path) {
|
|
3059
|
-
const identity = record(value, path);
|
|
3060
|
-
exactKeys(identity, [
|
|
3061
|
-
"principalType",
|
|
3062
|
-
"principalId",
|
|
3063
|
-
"displayName",
|
|
3064
|
-
"installationId",
|
|
3065
|
-
"agentInstanceId",
|
|
3066
|
-
"browserSessionId",
|
|
3067
|
-
"provider",
|
|
3068
|
-
"model"
|
|
3069
|
-
], path);
|
|
3070
|
-
return {
|
|
3071
|
-
principalType: member(identity.principalType, PRINCIPAL_TYPES, `${path}.principalType`),
|
|
3072
|
-
principalId: text(identity.principalId, `${path}.principalId`),
|
|
3073
|
-
...optionalTextFields(identity, path, [
|
|
3074
|
-
"displayName",
|
|
3075
|
-
"installationId",
|
|
3076
|
-
"agentInstanceId",
|
|
3077
|
-
"browserSessionId",
|
|
3078
|
-
"provider",
|
|
3079
|
-
"model"
|
|
3080
|
-
])
|
|
3081
|
-
};
|
|
3082
|
-
}
|
|
3083
|
-
function parseDelegation(value) {
|
|
3084
|
-
if (value === null) return null;
|
|
3085
|
-
const delegation = record(value, "actorEnvelope.delegation");
|
|
3086
|
-
exactKeys(delegation, ["taskId", "runId", "lineage"], "actorEnvelope.delegation");
|
|
3087
|
-
if (!Array.isArray(delegation.lineage) || delegation.lineage.length === 0 || delegation.lineage.length > 16) {
|
|
3088
|
-
fail("actorEnvelope.delegation.lineage", "must contain between 1 and 16 hops");
|
|
3089
|
-
}
|
|
3090
|
-
return {
|
|
3091
|
-
taskId: text(delegation.taskId, "actorEnvelope.delegation.taskId"),
|
|
3092
|
-
runId: text(delegation.runId, "actorEnvelope.delegation.runId"),
|
|
3093
|
-
lineage: delegation.lineage.map((value2, index) => {
|
|
3094
|
-
const hop = record(value2, `actorEnvelope.delegation.lineage[${index}]`);
|
|
3095
|
-
exactKeys(hop, ["principalType", "principalId"], `actorEnvelope.delegation.lineage[${index}]`);
|
|
3096
|
-
return {
|
|
3097
|
-
principalType: member(hop.principalType, PRINCIPAL_TYPES, `actorEnvelope.delegation.lineage[${index}].principalType`),
|
|
3098
|
-
principalId: text(hop.principalId, `actorEnvelope.delegation.lineage[${index}].principalId`)
|
|
3099
|
-
};
|
|
3100
|
-
})
|
|
3101
|
-
};
|
|
3102
|
-
}
|
|
3103
|
-
function record(value, path) {
|
|
3104
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) fail(path, "must be an object");
|
|
3105
|
-
return value;
|
|
3106
|
-
}
|
|
3107
|
-
function exactKeys(value, allowed, path) {
|
|
3108
|
-
const unknown = Object.keys(value).filter((key) => !allowed.includes(key));
|
|
3109
|
-
if (unknown.length) fail(path, `contains unknown field ${unknown[0]}`);
|
|
3110
|
-
}
|
|
3111
|
-
function text(value, path) {
|
|
3112
|
-
if (typeof value !== "string" || value.trim() !== value || value.length === 0 || value.length > 256) {
|
|
3113
|
-
fail(path, "must be a non-empty trimmed string of at most 256 characters");
|
|
3114
|
-
}
|
|
3115
|
-
return value;
|
|
3116
|
-
}
|
|
3117
|
-
function timestamp(value, path) {
|
|
3118
|
-
const result = text(value, path);
|
|
3119
|
-
if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/.test(result) || Number.isNaN(Date.parse(result))) {
|
|
3120
|
-
fail(path, "must be an RFC 3339 UTC timestamp");
|
|
3121
|
-
}
|
|
3122
|
-
return result;
|
|
3123
|
-
}
|
|
3124
|
-
function member(value, values, path) {
|
|
3125
|
-
if (typeof value !== "string" || !values.has(value)) fail(path, "contains an unsupported value");
|
|
3126
|
-
return value;
|
|
3127
|
-
}
|
|
3128
|
-
function textArray(value, path) {
|
|
3129
|
-
if (!Array.isArray(value) || value.length === 0 || value.length > 16) fail(path, "must contain between 1 and 16 values");
|
|
3130
|
-
return value.map((item, index) => text(item, `${path}[${index}]`));
|
|
3131
|
-
}
|
|
3132
|
-
function optionalTextFields(value, path, keys) {
|
|
3133
|
-
return Object.fromEntries(keys.flatMap((key) => value[key] === void 0 ? [] : [[key, text(value[key], `${path}.${key}`)]]));
|
|
3134
|
-
}
|
|
3135
|
-
function fail(path, message) {
|
|
3136
|
-
throw new ActorEnvelopeValidationError(path, message);
|
|
3137
|
-
}
|
|
3138
|
-
|
|
3139
3252
|
// src/modules/workspaces.ts
|
|
3140
3253
|
var ACTIONS = {
|
|
3141
3254
|
list: "app_topolo_auth.workspaces.list",
|
|
@@ -4632,6 +4745,7 @@ export {
|
|
|
4632
4745
|
DEFAULT_APP_URLS,
|
|
4633
4746
|
IdentityModule,
|
|
4634
4747
|
P2PModule,
|
|
4748
|
+
TOPOLO_ACTOR_ENVELOPE_HEADER,
|
|
4635
4749
|
TOPOLO_ENVIRONMENT_SERVICE_URLS,
|
|
4636
4750
|
TOPOLO_RUNTIME_ENVIRONMENTS,
|
|
4637
4751
|
TOPOLO_WIDGET_CONTRACT_VERSION,
|
|
@@ -4664,12 +4778,14 @@ export {
|
|
|
4664
4778
|
createTopoloAgentExecutionContext,
|
|
4665
4779
|
createTopoloWidgetError,
|
|
4666
4780
|
createTopoloWidgetResponse,
|
|
4781
|
+
decodeActorEnvelopeHeader,
|
|
4667
4782
|
defineTopoloActionsWidget,
|
|
4668
4783
|
defineTopoloActivityWidget,
|
|
4669
4784
|
defineTopoloChartWidget,
|
|
4670
4785
|
defineTopoloNotificationsWidget,
|
|
4671
4786
|
defineTopoloStatsWidget,
|
|
4672
4787
|
digestTopoloActionCatalog,
|
|
4788
|
+
encodeActorEnvelopeHeader,
|
|
4673
4789
|
encodeTopoloActionBase64,
|
|
4674
4790
|
enrichActionContract,
|
|
4675
4791
|
extractTopoloServiceErrorCode,
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@topolo/sdk",
|
|
3
3
|
"packageManager": "pnpm@10.17.1",
|
|
4
|
-
"version": "0.
|
|
5
|
-
"topoloPublishedVersion": "0.
|
|
4
|
+
"version": "0.17.1",
|
|
5
|
+
"topoloPublishedVersion": "0.17.1",
|
|
6
6
|
"description": "Typed client SDK for the Topolo platform. Used by TopoloCli, TopoloMCP, and third-party agents.",
|
|
7
7
|
"license": "UNLICENSED",
|
|
8
8
|
"author": "Topolo",
|