@topolo/sdk 0.21.2 → 0.21.3
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/dist/index.cjs +246 -171
- package/dist/index.d.cts +11 -1
- package/dist/index.d.ts +11 -1
- package/dist/index.js +246 -171
- package/dist/node/credential-store.cjs +133 -74
- package/dist/node/credential-store.d.cts +51 -1
- package/dist/node/credential-store.d.ts +51 -1
- package/dist/node/credential-store.js +130 -72
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -117,60 +117,6 @@ function generateRequestId() {
|
|
|
117
117
|
return `req_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
-
// src/dpop.ts
|
|
121
|
-
var encoder = new TextEncoder();
|
|
122
|
-
function encodeBase64Url(value) {
|
|
123
|
-
const bytes = value instanceof Uint8Array ? value : new Uint8Array(value);
|
|
124
|
-
let binary = "";
|
|
125
|
-
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
126
|
-
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
127
|
-
}
|
|
128
|
-
function encodeJson(value) {
|
|
129
|
-
return encodeBase64Url(encoder.encode(JSON.stringify(value)));
|
|
130
|
-
}
|
|
131
|
-
async function sha256Base64Url(value) {
|
|
132
|
-
return encodeBase64Url(await crypto.subtle.digest("SHA-256", encoder.encode(value)));
|
|
133
|
-
}
|
|
134
|
-
function publicJwk(privateJwk) {
|
|
135
|
-
const { crv, kty, x, y } = privateJwk;
|
|
136
|
-
if (kty !== "EC" || crv !== "P-256" || !x || !y || !privateJwk.d) {
|
|
137
|
-
throw new TypeError("A private P-256 JSON Web Key is required for DPoP signing.");
|
|
138
|
-
}
|
|
139
|
-
return { crv, kty, x, y };
|
|
140
|
-
}
|
|
141
|
-
function canonicalTargetUrl(value) {
|
|
142
|
-
const url = new URL(value);
|
|
143
|
-
return `${url.origin}${url.pathname}`;
|
|
144
|
-
}
|
|
145
|
-
async function calculateDpopJkt(privateJwk) {
|
|
146
|
-
const jwk = publicJwk(privateJwk);
|
|
147
|
-
return sha256Base64Url(JSON.stringify({ crv: jwk.crv, kty: jwk.kty, x: jwk.x, y: jwk.y }));
|
|
148
|
-
}
|
|
149
|
-
async function createDpopProof(input) {
|
|
150
|
-
const jwk = publicJwk(input.privateJwk);
|
|
151
|
-
const key = await crypto.subtle.importKey(
|
|
152
|
-
"jwk",
|
|
153
|
-
input.privateJwk,
|
|
154
|
-
{ name: "ECDSA", namedCurve: "P-256" },
|
|
155
|
-
false,
|
|
156
|
-
["sign"]
|
|
157
|
-
);
|
|
158
|
-
const header = encodeJson({ alg: "ES256", typ: "dpop+jwt", jwk });
|
|
159
|
-
const claims = encodeJson({
|
|
160
|
-
jti: crypto.randomUUID(),
|
|
161
|
-
iat: Math.floor(Date.now() / 1e3),
|
|
162
|
-
htm: input.method.toUpperCase(),
|
|
163
|
-
htu: canonicalTargetUrl(input.targetUrl),
|
|
164
|
-
ath: await sha256Base64Url(input.accessToken)
|
|
165
|
-
});
|
|
166
|
-
const signature = await crypto.subtle.sign(
|
|
167
|
-
{ name: "ECDSA", hash: "SHA-256" },
|
|
168
|
-
key,
|
|
169
|
-
encoder.encode(`${header}.${claims}`)
|
|
170
|
-
);
|
|
171
|
-
return `${header}.${claims}.${encodeBase64Url(signature)}`;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
120
|
// src/errors.ts
|
|
175
121
|
var TopoloSdkError = class extends Error {
|
|
176
122
|
code;
|
|
@@ -494,6 +440,139 @@ var TopoloHttpError = class extends TopoloSdkError {
|
|
|
494
440
|
}
|
|
495
441
|
};
|
|
496
442
|
|
|
443
|
+
// src/action-secrets.ts
|
|
444
|
+
var ACTION_SECRET_PREFIX = "topolo-secret:";
|
|
445
|
+
function isActionSecretReference(value) {
|
|
446
|
+
return typeof value === "string" && value.startsWith(ACTION_SECRET_PREFIX);
|
|
447
|
+
}
|
|
448
|
+
function isRecord(value) {
|
|
449
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
450
|
+
}
|
|
451
|
+
function sensitive(key, value) {
|
|
452
|
+
const normalized = key.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
453
|
+
if (normalized === "capability") {
|
|
454
|
+
if (typeof value !== "string") return value !== null && value !== void 0 && typeof value !== "boolean" && typeof value !== "number";
|
|
455
|
+
const text2 = value.trim();
|
|
456
|
+
return Boolean(text2) && !/^[a-z][a-z0-9_-]{0,31}$/.test(text2);
|
|
457
|
+
}
|
|
458
|
+
return ["authorization", "cookie", "setcookie", "proxyauthorization"].includes(normalized) || /(token|secret|password|privatekey|apikey)$/.test(normalized);
|
|
459
|
+
}
|
|
460
|
+
function maskActionSecrets(value, secrets) {
|
|
461
|
+
let masked = value;
|
|
462
|
+
for (const [secret, reference] of secrets) {
|
|
463
|
+
const encoded = encodeURIComponent(secret);
|
|
464
|
+
const variants = [.../* @__PURE__ */ new Set([secret, encoded])].map((item) => item.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
|
|
465
|
+
masked = masked.replace(new RegExp(variants.join("|"), "g"), (match) => match === secret ? reference : encodeURIComponent(reference));
|
|
466
|
+
}
|
|
467
|
+
return masked;
|
|
468
|
+
}
|
|
469
|
+
async function resolveActionSecrets(value, store, secrets) {
|
|
470
|
+
if (isActionSecretReference(value)) {
|
|
471
|
+
if (!store) throw new TopoloSdkError("action_secret_store_required", "Protected action references require a secret store.");
|
|
472
|
+
const resolved = await store.load(value);
|
|
473
|
+
if (typeof resolved === "string" && resolved) secrets.set(resolved, value);
|
|
474
|
+
return resolved;
|
|
475
|
+
}
|
|
476
|
+
if (Array.isArray(value)) return Promise.all(value.map((item) => resolveActionSecrets(item, store, secrets)));
|
|
477
|
+
if (!isRecord(value)) return value;
|
|
478
|
+
return Object.fromEntries(await Promise.all(Object.entries(value).map(async ([key, item]) => [key, await resolveActionSecrets(item, store, secrets)])));
|
|
479
|
+
}
|
|
480
|
+
async function protectActionResult(value, store) {
|
|
481
|
+
const secrets = /* @__PURE__ */ new Map();
|
|
482
|
+
async function protect(item, key = "") {
|
|
483
|
+
if (isActionSecretReference(item)) return item;
|
|
484
|
+
if (sensitive(key, item)) {
|
|
485
|
+
const reference = store ? await store.save(item) : "[REDACTED]";
|
|
486
|
+
if (typeof item === "string" && item) secrets.set(item, reference);
|
|
487
|
+
return reference;
|
|
488
|
+
}
|
|
489
|
+
if (Array.isArray(item)) return Promise.all(item.map((child) => protect(child)));
|
|
490
|
+
if (!isRecord(item)) return item;
|
|
491
|
+
return Object.fromEntries(await Promise.all(Object.entries(item).map(async ([name, child]) => [name, await protect(child, name)])));
|
|
492
|
+
}
|
|
493
|
+
const protectedValue = await protect(value);
|
|
494
|
+
async function protectEmbedded(item) {
|
|
495
|
+
if (typeof item === "string" && !isActionSecretReference(item) && maskActionSecrets(item, secrets) !== item) {
|
|
496
|
+
return store ? store.save(item) : maskActionSecrets(item, secrets);
|
|
497
|
+
}
|
|
498
|
+
if (Array.isArray(item)) return Promise.all(item.map(protectEmbedded));
|
|
499
|
+
if (!isRecord(item)) return item;
|
|
500
|
+
return Object.fromEntries(await Promise.all(Object.entries(item).map(async ([key, child]) => [key, await protectEmbedded(child)])));
|
|
501
|
+
}
|
|
502
|
+
return protectEmbedded(protectedValue);
|
|
503
|
+
}
|
|
504
|
+
function maskActionSecretValue(value, secrets) {
|
|
505
|
+
if (value instanceof Error) return maskActionSecretError(value, secrets);
|
|
506
|
+
if (typeof value === "string") return maskActionSecrets(value, secrets);
|
|
507
|
+
if (Array.isArray(value)) return value.map((item) => maskActionSecretValue(item, secrets));
|
|
508
|
+
if (!isRecord(value)) return value;
|
|
509
|
+
return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, maskActionSecretValue(item, secrets)]));
|
|
510
|
+
}
|
|
511
|
+
function maskActionSecretError(error, secrets) {
|
|
512
|
+
if (!(error instanceof Error)) return maskActionSecretValue(error, secrets);
|
|
513
|
+
const masked = Object.create(Object.getPrototypeOf(error));
|
|
514
|
+
for (const key of Object.getOwnPropertyNames(error)) {
|
|
515
|
+
const descriptor = Object.getOwnPropertyDescriptor(error, key);
|
|
516
|
+
if ("value" in descriptor) descriptor.value = maskActionSecretValue(descriptor.value, secrets);
|
|
517
|
+
Object.defineProperty(masked, key, descriptor);
|
|
518
|
+
}
|
|
519
|
+
return masked;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// src/dpop.ts
|
|
523
|
+
var encoder = new TextEncoder();
|
|
524
|
+
function encodeBase64Url(value) {
|
|
525
|
+
const bytes = value instanceof Uint8Array ? value : new Uint8Array(value);
|
|
526
|
+
let binary = "";
|
|
527
|
+
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
528
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
529
|
+
}
|
|
530
|
+
function encodeJson(value) {
|
|
531
|
+
return encodeBase64Url(encoder.encode(JSON.stringify(value)));
|
|
532
|
+
}
|
|
533
|
+
async function sha256Base64Url(value) {
|
|
534
|
+
return encodeBase64Url(await crypto.subtle.digest("SHA-256", encoder.encode(value)));
|
|
535
|
+
}
|
|
536
|
+
function publicJwk(privateJwk) {
|
|
537
|
+
const { crv, kty, x, y } = privateJwk;
|
|
538
|
+
if (kty !== "EC" || crv !== "P-256" || !x || !y || !privateJwk.d) {
|
|
539
|
+
throw new TypeError("A private P-256 JSON Web Key is required for DPoP signing.");
|
|
540
|
+
}
|
|
541
|
+
return { crv, kty, x, y };
|
|
542
|
+
}
|
|
543
|
+
function canonicalTargetUrl(value) {
|
|
544
|
+
const url = new URL(value);
|
|
545
|
+
return `${url.origin}${url.pathname}`;
|
|
546
|
+
}
|
|
547
|
+
async function calculateDpopJkt(privateJwk) {
|
|
548
|
+
const jwk = publicJwk(privateJwk);
|
|
549
|
+
return sha256Base64Url(JSON.stringify({ crv: jwk.crv, kty: jwk.kty, x: jwk.x, y: jwk.y }));
|
|
550
|
+
}
|
|
551
|
+
async function createDpopProof(input) {
|
|
552
|
+
const jwk = publicJwk(input.privateJwk);
|
|
553
|
+
const key = await crypto.subtle.importKey(
|
|
554
|
+
"jwk",
|
|
555
|
+
input.privateJwk,
|
|
556
|
+
{ name: "ECDSA", namedCurve: "P-256" },
|
|
557
|
+
false,
|
|
558
|
+
["sign"]
|
|
559
|
+
);
|
|
560
|
+
const header = encodeJson({ alg: "ES256", typ: "dpop+jwt", jwk });
|
|
561
|
+
const claims = encodeJson({
|
|
562
|
+
jti: crypto.randomUUID(),
|
|
563
|
+
iat: Math.floor(Date.now() / 1e3),
|
|
564
|
+
htm: input.method.toUpperCase(),
|
|
565
|
+
htu: canonicalTargetUrl(input.targetUrl),
|
|
566
|
+
ath: await sha256Base64Url(input.accessToken)
|
|
567
|
+
});
|
|
568
|
+
const signature = await crypto.subtle.sign(
|
|
569
|
+
{ name: "ECDSA", hash: "SHA-256" },
|
|
570
|
+
key,
|
|
571
|
+
encoder.encode(`${header}.${claims}`)
|
|
572
|
+
);
|
|
573
|
+
return `${header}.${claims}.${encodeBase64Url(signature)}`;
|
|
574
|
+
}
|
|
575
|
+
|
|
497
576
|
// src/environment.ts
|
|
498
577
|
var TOPOLO_RUNTIME_ENVIRONMENTS = ["production", "staging", "development", "local"];
|
|
499
578
|
var TOPOLO_ENVIRONMENT_SERVICE_URLS = {
|
|
@@ -799,9 +878,9 @@ function inspectSchema(schema, path, root, depth) {
|
|
|
799
878
|
return { path, code: "unsupported_schema_ref", message: `Cannot resolve schema reference ${schema.$ref}.` };
|
|
800
879
|
}
|
|
801
880
|
const children = [];
|
|
802
|
-
if (
|
|
803
|
-
if (
|
|
804
|
-
if (
|
|
881
|
+
if (isRecord2(schema.properties)) children.push(...Object.entries(schema.properties));
|
|
882
|
+
if (isRecord2(schema.$defs)) children.push(...Object.entries(schema.$defs));
|
|
883
|
+
if (isRecord2(schema.definitions)) children.push(...Object.entries(schema.definitions));
|
|
805
884
|
children.push(
|
|
806
885
|
["additionalProperties", schema.additionalProperties],
|
|
807
886
|
["propertyNames", schema.propertyNames],
|
|
@@ -811,7 +890,7 @@ function inspectSchema(schema, path, root, depth) {
|
|
|
811
890
|
...arraySchemaEntries("oneOf", schema.oneOf)
|
|
812
891
|
);
|
|
813
892
|
for (const [key, child] of children) {
|
|
814
|
-
if (!
|
|
893
|
+
if (!isRecord2(child)) continue;
|
|
815
894
|
const issue = inspectSchema(child, `${path}.${key}`, root, depth + 1);
|
|
816
895
|
if (issue) return issue;
|
|
817
896
|
}
|
|
@@ -835,7 +914,7 @@ function validateNode(schema, value, path, root, errors, depth) {
|
|
|
835
914
|
}
|
|
836
915
|
if (Array.isArray(schema.allOf)) {
|
|
837
916
|
for (const candidate of schema.allOf) {
|
|
838
|
-
if (
|
|
917
|
+
if (isRecord2(candidate)) validateNode(candidate, value, path, root, errors, depth + 1);
|
|
839
918
|
}
|
|
840
919
|
}
|
|
841
920
|
if (Array.isArray(schema.anyOf) && !schema.anyOf.some((candidate) => candidateMatches(candidate, value, root, depth))) {
|
|
@@ -865,7 +944,7 @@ function validateNode(schema, value, path, root, errors, depth) {
|
|
|
865
944
|
if (typeof value === "string") validateString(schema, value, path, errors);
|
|
866
945
|
if (typeof value === "number") validateNumber(schema, value, path, errors);
|
|
867
946
|
if (Array.isArray(value)) validateArray(schema, value, path, root, errors, depth);
|
|
868
|
-
if (
|
|
947
|
+
if (isRecord2(value)) validateObject(schema, value, path, root, errors, depth);
|
|
869
948
|
}
|
|
870
949
|
function validateObject(schema, value, path, root, errors, depth) {
|
|
871
950
|
if (typeof schema.minProperties === "number" && Object.keys(value).length < schema.minProperties) {
|
|
@@ -874,25 +953,25 @@ function validateObject(schema, value, path, root, errors, depth) {
|
|
|
874
953
|
if (typeof schema.maxProperties === "number" && Object.keys(value).length > schema.maxProperties) {
|
|
875
954
|
errors.push({ path, code: "max_properties", message: `Expected at most ${schema.maxProperties} properties.` });
|
|
876
955
|
}
|
|
877
|
-
const properties =
|
|
956
|
+
const properties = isRecord2(schema.properties) ? schema.properties : {};
|
|
878
957
|
const required = Array.isArray(schema.required) ? schema.required.map(String) : [];
|
|
879
958
|
for (const key of required) {
|
|
880
959
|
if (!Object.prototype.hasOwnProperty.call(value, key)) {
|
|
881
960
|
errors.push({ path: `${path}.${key}`, code: "required", message: "Required property is missing." });
|
|
882
961
|
}
|
|
883
962
|
}
|
|
884
|
-
if (
|
|
963
|
+
if (isRecord2(schema.propertyNames)) {
|
|
885
964
|
for (const key of Object.keys(value)) {
|
|
886
965
|
validateNode(schema.propertyNames, key, `${path}.${key}`, root, errors, depth + 1);
|
|
887
966
|
}
|
|
888
967
|
}
|
|
889
968
|
for (const [key, child] of Object.entries(value)) {
|
|
890
969
|
const childSchema = properties[key];
|
|
891
|
-
if (
|
|
970
|
+
if (isRecord2(childSchema)) {
|
|
892
971
|
validateNode(childSchema, child, `${path}.${key}`, root, errors, depth + 1);
|
|
893
972
|
} else if (schema.additionalProperties === false) {
|
|
894
973
|
errors.push({ path: `${path}.${key}`, code: "additional_property", message: "Property is not declared by the action schema." });
|
|
895
|
-
} else if (
|
|
974
|
+
} else if (isRecord2(schema.additionalProperties)) {
|
|
896
975
|
validateNode(schema.additionalProperties, child, `${path}.${key}`, root, errors, depth + 1);
|
|
897
976
|
}
|
|
898
977
|
}
|
|
@@ -908,7 +987,7 @@ function validateArray(schema, value, path, root, errors, depth) {
|
|
|
908
987
|
const unique = new Set(value.map((item) => JSON.stringify(item)));
|
|
909
988
|
if (unique.size !== value.length) errors.push({ path, code: "unique_items", message: "Expected every item to be unique." });
|
|
910
989
|
}
|
|
911
|
-
if (
|
|
990
|
+
if (isRecord2(schema.items)) {
|
|
912
991
|
value.forEach((item, index) => validateNode(schema.items, item, `${path}[${index}]`, root, errors, depth + 1));
|
|
913
992
|
}
|
|
914
993
|
}
|
|
@@ -946,7 +1025,7 @@ function validateNumber(schema, value, path, errors) {
|
|
|
946
1025
|
}
|
|
947
1026
|
}
|
|
948
1027
|
function candidateMatches(candidate, value, root, depth) {
|
|
949
|
-
if (!
|
|
1028
|
+
if (!isRecord2(candidate)) return false;
|
|
950
1029
|
const issues = [];
|
|
951
1030
|
validateNode(candidate, value, "$", root, issues, depth + 1);
|
|
952
1031
|
return issues.length === 0;
|
|
@@ -960,24 +1039,24 @@ function resolveLocalReference(root, reference) {
|
|
|
960
1039
|
current = current[Number(token)];
|
|
961
1040
|
continue;
|
|
962
1041
|
}
|
|
963
|
-
if (!
|
|
1042
|
+
if (!isRecord2(current) || !Object.prototype.hasOwnProperty.call(current, token)) return null;
|
|
964
1043
|
current = current[token];
|
|
965
1044
|
}
|
|
966
|
-
return
|
|
1045
|
+
return isRecord2(current) ? current : null;
|
|
967
1046
|
}
|
|
968
1047
|
function matchesType(type, value) {
|
|
969
1048
|
if (type === void 0) return true;
|
|
970
1049
|
if (Array.isArray(type)) return type.some((candidate) => matchesType(candidate, value));
|
|
971
1050
|
if (type === "null") return value === null;
|
|
972
1051
|
if (type === "array") return Array.isArray(value);
|
|
973
|
-
if (type === "object") return
|
|
1052
|
+
if (type === "object") return isRecord2(value);
|
|
974
1053
|
if (type === "integer") return typeof value === "number" && Number.isInteger(value);
|
|
975
1054
|
return typeof value === type;
|
|
976
1055
|
}
|
|
977
1056
|
function sameJsonValue(left, right) {
|
|
978
1057
|
return JSON.stringify(left) === JSON.stringify(right);
|
|
979
1058
|
}
|
|
980
|
-
function
|
|
1059
|
+
function isRecord2(value) {
|
|
981
1060
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
982
1061
|
}
|
|
983
1062
|
function isUrl(value) {
|
|
@@ -1234,7 +1313,7 @@ function fail(path, message) {
|
|
|
1234
1313
|
|
|
1235
1314
|
// src/action-upload.ts
|
|
1236
1315
|
function parseTopoloActionUploadContract(value) {
|
|
1237
|
-
if (!
|
|
1316
|
+
if (!isRecord3(value) || value.profile !== "inline-base64" && value.profile !== "url-session") {
|
|
1238
1317
|
throw new TopoloSdkError("upload_contract_unavailable", "This action does not publish a supported upload contract.");
|
|
1239
1318
|
}
|
|
1240
1319
|
const contentTypeField = requiredField(value.contentTypeField, "contentTypeField");
|
|
@@ -1313,17 +1392,17 @@ function prepareTopoloUrlSessionUploadInput(contractValue, byteLength, input, op
|
|
|
1313
1392
|
};
|
|
1314
1393
|
}
|
|
1315
1394
|
function topoloUploadResponseValue(response, path) {
|
|
1316
|
-
return path.split(".").reduce((value, segment) =>
|
|
1395
|
+
return path.split(".").reduce((value, segment) => isRecord3(value) ? value[segment] : void 0, response);
|
|
1317
1396
|
}
|
|
1318
1397
|
function redactTopoloUrlSessionResult(response, uploadUrlPath) {
|
|
1319
1398
|
const redacted = cloneUploadValue(response);
|
|
1320
1399
|
let target = redacted;
|
|
1321
1400
|
const segments = uploadUrlPath.split(".");
|
|
1322
1401
|
for (const segment of segments.slice(0, -1)) {
|
|
1323
|
-
target =
|
|
1402
|
+
target = isRecord3(target) ? target[segment] : void 0;
|
|
1324
1403
|
}
|
|
1325
1404
|
const finalSegment = segments.at(-1);
|
|
1326
|
-
if (finalSegment &&
|
|
1405
|
+
if (finalSegment && isRecord3(target) && finalSegment in target) target[finalSegment] = "[REDACTED]";
|
|
1327
1406
|
redactUploadHeaders(redacted);
|
|
1328
1407
|
return redacted;
|
|
1329
1408
|
}
|
|
@@ -1347,7 +1426,7 @@ function mapUploadInput(contract, contentBase64, input, options) {
|
|
|
1347
1426
|
}
|
|
1348
1427
|
function cloneUploadValue(value) {
|
|
1349
1428
|
if (Array.isArray(value)) return value.map(cloneUploadValue);
|
|
1350
|
-
if (!
|
|
1429
|
+
if (!isRecord3(value)) return value;
|
|
1351
1430
|
return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, cloneUploadValue(item)]));
|
|
1352
1431
|
}
|
|
1353
1432
|
function redactUploadHeaders(value) {
|
|
@@ -1355,9 +1434,9 @@ function redactUploadHeaders(value) {
|
|
|
1355
1434
|
value.forEach(redactUploadHeaders);
|
|
1356
1435
|
return;
|
|
1357
1436
|
}
|
|
1358
|
-
if (!
|
|
1437
|
+
if (!isRecord3(value)) return;
|
|
1359
1438
|
for (const [key, item] of Object.entries(value)) {
|
|
1360
|
-
if (key.toLowerCase() === "headers" &&
|
|
1439
|
+
if (key.toLowerCase() === "headers" && isRecord3(item)) {
|
|
1361
1440
|
for (const header of Object.keys(item)) {
|
|
1362
1441
|
if (["authorization", "cookie", "set-cookie", "proxy-authorization", "x-api-key"].includes(header.toLowerCase())) {
|
|
1363
1442
|
item[header] = "[REDACTED]";
|
|
@@ -1386,7 +1465,7 @@ function decodedBase64Size(contentBase64) {
|
|
|
1386
1465
|
return contentBase64.length / 4 * 3 - padding;
|
|
1387
1466
|
}
|
|
1388
1467
|
function parseCompleteContract(value) {
|
|
1389
|
-
if (!
|
|
1468
|
+
if (!isRecord3(value)) throw new TopoloSdkError("invalid_upload_contract", "The action upload completion contract is invalid.");
|
|
1390
1469
|
return {
|
|
1391
1470
|
actionId: requiredPath(value.actionId, "complete.actionId"),
|
|
1392
1471
|
inputField: requiredField(value.inputField, "complete.inputField"),
|
|
@@ -1424,7 +1503,7 @@ function encodeTopoloActionBase64(bytes) {
|
|
|
1424
1503
|
}
|
|
1425
1504
|
return btoa(binary);
|
|
1426
1505
|
}
|
|
1427
|
-
function
|
|
1506
|
+
function isRecord3(value) {
|
|
1428
1507
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
1429
1508
|
}
|
|
1430
1509
|
|
|
@@ -1437,6 +1516,8 @@ var TOPOLO_RESOURCE_ID_HEADER = "X-Topolo-Resource-ID";
|
|
|
1437
1516
|
var POSITIVE_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
1438
1517
|
var NEGATIVE_CACHE_TTL_MS = 60 * 1e3;
|
|
1439
1518
|
var TopoloClient = class _TopoloClient {
|
|
1519
|
+
actionSecretStore;
|
|
1520
|
+
actionSecrets = /* @__PURE__ */ new Map();
|
|
1440
1521
|
credential;
|
|
1441
1522
|
agent;
|
|
1442
1523
|
actorContext;
|
|
@@ -1466,6 +1547,7 @@ var TopoloClient = class _TopoloClient {
|
|
|
1466
1547
|
_TopoloClient.appIdCache.clear();
|
|
1467
1548
|
}
|
|
1468
1549
|
constructor(options) {
|
|
1550
|
+
this.actionSecretStore = options.actionSecretStore;
|
|
1469
1551
|
if (!options.credential) throw new TopoloAuthError("credential is required");
|
|
1470
1552
|
if (!options.agent?.clientName) throw new TopoloAuthError("agent.clientName is required");
|
|
1471
1553
|
this.credential = options.credential;
|
|
@@ -1487,6 +1569,7 @@ var TopoloClient = class _TopoloClient {
|
|
|
1487
1569
|
}
|
|
1488
1570
|
emit(event) {
|
|
1489
1571
|
if (!this.debug) return;
|
|
1572
|
+
event = maskActionSecretValue(event, this.actionSecrets);
|
|
1490
1573
|
try {
|
|
1491
1574
|
this.debug(event);
|
|
1492
1575
|
} catch {
|
|
@@ -1872,7 +1955,7 @@ var TopoloClient = class _TopoloClient {
|
|
|
1872
1955
|
}
|
|
1873
1956
|
const entries = [];
|
|
1874
1957
|
for (const item of raw) {
|
|
1875
|
-
if (!
|
|
1958
|
+
if (!isRecord4(item)) continue;
|
|
1876
1959
|
const s = item;
|
|
1877
1960
|
const appId = typeof s.app_id === "string" ? s.app_id : typeof s.id === "string" ? s.id : null;
|
|
1878
1961
|
if (!appId) continue;
|
|
@@ -1947,8 +2030,8 @@ var TopoloClient = class _TopoloClient {
|
|
|
1947
2030
|
"app_topolo_auth.service_api_key_resources.list",
|
|
1948
2031
|
{ appId: service.appId }
|
|
1949
2032
|
);
|
|
1950
|
-
const payload =
|
|
1951
|
-
if (!
|
|
2033
|
+
const payload = isRecord4(response) && isRecord4(response.data) ? response.data : response;
|
|
2034
|
+
if (!isRecord4(payload) || !Array.isArray(payload.resources)) {
|
|
1952
2035
|
throw new TopoloSdkError("resource_catalog_unavailable", "TopoloAuth did not return bindable resources.");
|
|
1953
2036
|
}
|
|
1954
2037
|
return {
|
|
@@ -2129,17 +2212,20 @@ var TopoloClient = class _TopoloClient {
|
|
|
2129
2212
|
}
|
|
2130
2213
|
async validateActionInput(action, input = {}) {
|
|
2131
2214
|
const entry = await this.getAction(action);
|
|
2132
|
-
|
|
2215
|
+
const resolved = await resolveActionSecrets(input, this.actionSecretStore, this.actionSecrets);
|
|
2216
|
+
return { action: entry, validation: validateTopoloActionInput(entry.inputSchema, resolved) };
|
|
2133
2217
|
}
|
|
2134
2218
|
async planAction(action, input = {}, options = {}) {
|
|
2135
2219
|
const entry = await this.getAction(action);
|
|
2136
|
-
|
|
2220
|
+
const resolved = await resolveActionSecrets(input, this.actionSecretStore, this.actionSecrets);
|
|
2221
|
+
return this.planResolvedAction(entry, resolved, options);
|
|
2137
2222
|
}
|
|
2138
2223
|
async prepareAction(action, input, options = {}) {
|
|
2139
2224
|
const entry = await this.getAction(action);
|
|
2140
2225
|
const example = (entry.agentContract?.examples ?? []).find((candidate) => Boolean(candidate?.input) && typeof candidate.input === "object" && !Array.isArray(candidate.input) && validateTopoloActionInput(entry.inputSchema, candidate.input).valid) ?? null;
|
|
2141
2226
|
const selectedInput = input ?? example?.input ?? {};
|
|
2142
|
-
const
|
|
2227
|
+
const resolved = await resolveActionSecrets(selectedInput, this.actionSecretStore, this.actionSecrets);
|
|
2228
|
+
const plan = this.planResolvedAction(entry, resolved, options);
|
|
2143
2229
|
return {
|
|
2144
2230
|
action: entry,
|
|
2145
2231
|
example,
|
|
@@ -2176,7 +2262,7 @@ var TopoloClient = class _TopoloClient {
|
|
|
2176
2262
|
confirmationRequired,
|
|
2177
2263
|
confirmationProvided: options.confirm === true,
|
|
2178
2264
|
executable: validation.valid && agentAccess !== "off" && (!confirmationRequired || options.confirm === true),
|
|
2179
|
-
request,
|
|
2265
|
+
request: maskActionSecretValue(request, this.actionSecrets),
|
|
2180
2266
|
verification: stringArray(entry.agentContract?.verify),
|
|
2181
2267
|
rollbackActionId: pickActionString(entry.agentContract?.rollbackActionId),
|
|
2182
2268
|
nextActionIds: stringArray(entry.agentContract?.nextActionIds)
|
|
@@ -2206,6 +2292,7 @@ var TopoloClient = class _TopoloClient {
|
|
|
2206
2292
|
`Topolo action "${entry.actionId}" requires { confirm: true } before invocation.`
|
|
2207
2293
|
);
|
|
2208
2294
|
}
|
|
2295
|
+
input = await resolveActionSecrets(input, this.actionSecretStore, this.actionSecrets);
|
|
2209
2296
|
const validation = validateTopoloActionInput(entry.inputSchema, input);
|
|
2210
2297
|
if (!validation.valid) {
|
|
2211
2298
|
throw new TopoloSdkError(
|
|
@@ -2236,22 +2323,31 @@ var TopoloClient = class _TopoloClient {
|
|
|
2236
2323
|
}
|
|
2237
2324
|
}
|
|
2238
2325
|
const service = entry.appSlug || entry.appId;
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2326
|
+
let result;
|
|
2327
|
+
try {
|
|
2328
|
+
result = await this.request({
|
|
2329
|
+
service,
|
|
2330
|
+
...entry.apiBaseUrl ? { baseUrl: entry.apiBaseUrl } : {},
|
|
2331
|
+
method: entry.method,
|
|
2332
|
+
path: mapped.path,
|
|
2333
|
+
...mapped.query ? { query: mapped.query } : {},
|
|
2334
|
+
...mapped.body !== void 0 ? { body: mapped.body } : {},
|
|
2335
|
+
...options.resource ? { resource: options.resource } : {},
|
|
2336
|
+
...resolvedActorEnvelope ? { actorEnvelope: resolvedActorEnvelope } : {},
|
|
2337
|
+
headers: {
|
|
2338
|
+
[TOPOLO_ACTION_ID_HEADER]: entry.actionId,
|
|
2339
|
+
[TOPOLO_ACTION_NAME_HEADER]: entry.name
|
|
2340
|
+
},
|
|
2341
|
+
confirm: options.confirm === true || entry.readOnly && !entry.destructive && !entry.requiresConfirmation
|
|
2342
|
+
});
|
|
2343
|
+
} catch (error) {
|
|
2344
|
+
throw maskActionSecretError(error, this.actionSecrets);
|
|
2345
|
+
}
|
|
2346
|
+
return preserveSensitiveResult ? result : await protectActionResult(result, this.actionSecretStore);
|
|
2347
|
+
}
|
|
2348
|
+
async releaseActionSecret(reference) {
|
|
2349
|
+
if (!this.actionSecretStore) throw new TopoloSdkError("action_secret_store_required", "Protected action references require a secret store.");
|
|
2350
|
+
await this.actionSecretStore.delete(reference);
|
|
2255
2351
|
}
|
|
2256
2352
|
async buildVerifiedAgentActorEnvelope(actionId, input, resource) {
|
|
2257
2353
|
if (!this.actorContext) return void 0;
|
|
@@ -2398,7 +2494,7 @@ function resolveConfiguredAppId(slug, appIds) {
|
|
|
2398
2494
|
return null;
|
|
2399
2495
|
}
|
|
2400
2496
|
function normalizeApiKeyResourceMetadata(input) {
|
|
2401
|
-
if (!
|
|
2497
|
+
if (!isRecord4(input)) return null;
|
|
2402
2498
|
const resourceType = readString(input.resourceType ?? input.resource_type);
|
|
2403
2499
|
const label = readString(input.label);
|
|
2404
2500
|
const emptyLabel = readString(input.emptyLabel ?? input.empty_label);
|
|
@@ -2432,7 +2528,7 @@ function normalizeApiKeyResourceMetadataList(input) {
|
|
|
2432
2528
|
return resources;
|
|
2433
2529
|
}
|
|
2434
2530
|
function normalizeBindableResource(input) {
|
|
2435
|
-
if (!
|
|
2531
|
+
if (!isRecord4(input)) return null;
|
|
2436
2532
|
const resourceType = readString(input.resourceType ?? input.resource_type);
|
|
2437
2533
|
const resourceId = readString(input.resourceId ?? input.resource_id);
|
|
2438
2534
|
if (!resourceType || !resourceId) return null;
|
|
@@ -2443,13 +2539,13 @@ function normalizeBindableResource(input) {
|
|
|
2443
2539
|
slug: readOptionalString(input.slug),
|
|
2444
2540
|
status: readOptionalString(input.status),
|
|
2445
2541
|
organizationId: readOptionalString(input.organizationId ?? input.organization_id),
|
|
2446
|
-
metadata:
|
|
2542
|
+
metadata: isRecord4(input.metadata) ? input.metadata : {}
|
|
2447
2543
|
};
|
|
2448
2544
|
}
|
|
2449
2545
|
function normalizeResourceBindings(input) {
|
|
2450
2546
|
if (!Array.isArray(input)) return [];
|
|
2451
2547
|
return input.flatMap((binding) => {
|
|
2452
|
-
if (!
|
|
2548
|
+
if (!isRecord4(binding)) return [];
|
|
2453
2549
|
const resourceType = readString(binding.resourceType ?? binding.resource_type);
|
|
2454
2550
|
const resourceId = readString(binding.resourceId ?? binding.resource_id);
|
|
2455
2551
|
return resourceType && resourceId ? [{ resourceType, resourceId }] : [];
|
|
@@ -2462,30 +2558,9 @@ function readOptionalString(value) {
|
|
|
2462
2558
|
const normalized = readString(value);
|
|
2463
2559
|
return normalized || null;
|
|
2464
2560
|
}
|
|
2465
|
-
function
|
|
2561
|
+
function isRecord4(value) {
|
|
2466
2562
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2467
2563
|
}
|
|
2468
|
-
function redactActionResult(value) {
|
|
2469
|
-
if (Array.isArray(value)) return value.map(redactActionResult);
|
|
2470
|
-
if (!isRecord3(value)) return value;
|
|
2471
|
-
return Object.fromEntries(Object.entries(value).map(([key, item]) => [
|
|
2472
|
-
key,
|
|
2473
|
-
isSensitiveActionResultKey(key, item) ? "[REDACTED]" : redactActionResult(item)
|
|
2474
|
-
]));
|
|
2475
|
-
}
|
|
2476
|
-
function isSensitiveActionResultKey(key, value) {
|
|
2477
|
-
const normalized = key.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
2478
|
-
if (normalized === "capability") return isCredentialShaped(value);
|
|
2479
|
-
return ["authorization", "cookie", "setcookie", "proxyauthorization"].includes(normalized) || /(token|secret|password|privatekey|apikey)$/.test(normalized);
|
|
2480
|
-
}
|
|
2481
|
-
var PLAIN_ENUM_VALUE = /^[a-z][a-z0-9_-]{0,31}$/;
|
|
2482
|
-
function isCredentialShaped(value) {
|
|
2483
|
-
if (typeof value !== "string") return value !== null && value !== void 0 && typeof value !== "boolean" && typeof value !== "number";
|
|
2484
|
-
const trimmed = value.trim();
|
|
2485
|
-
if (!trimmed) return false;
|
|
2486
|
-
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed)) return true;
|
|
2487
|
-
return !PLAIN_ENUM_VALUE.test(trimmed);
|
|
2488
|
-
}
|
|
2489
2564
|
function resolveConfiguredServiceUrl(slug, serviceUrls) {
|
|
2490
2565
|
for (const serviceKey of serviceKeyCandidates2(slug)) {
|
|
2491
2566
|
const configured = serviceUrls?.[serviceKey];
|
|
@@ -2555,14 +2630,14 @@ function catalogSearchQuery(options) {
|
|
|
2555
2630
|
return query;
|
|
2556
2631
|
}
|
|
2557
2632
|
function normalizeCatalogPage(value) {
|
|
2558
|
-
if (!
|
|
2633
|
+
if (!isRecord4(value)) return { nextCursor: null, hasMore: false };
|
|
2559
2634
|
return {
|
|
2560
2635
|
nextCursor: typeof value.nextCursor === "string" && value.nextCursor ? value.nextCursor : null,
|
|
2561
2636
|
hasMore: value.hasMore === true
|
|
2562
2637
|
};
|
|
2563
2638
|
}
|
|
2564
2639
|
function normalizeServiceCatalogEntry(item) {
|
|
2565
|
-
if (!
|
|
2640
|
+
if (!isRecord4(item)) return null;
|
|
2566
2641
|
const appId = typeof item.app_id === "string" ? item.app_id : typeof item.id === "string" ? item.id : null;
|
|
2567
2642
|
if (!appId) return null;
|
|
2568
2643
|
const permissions = Array.isArray(item.granted_permissions) ? item.granted_permissions.filter(
|
|
@@ -3027,7 +3102,7 @@ var INVERSE_OPERATIONS = /* @__PURE__ */ new Map([
|
|
|
3027
3102
|
["restore", "archive"],
|
|
3028
3103
|
["unpublish", "publish"]
|
|
3029
3104
|
]);
|
|
3030
|
-
function
|
|
3105
|
+
function isRecord5(value) {
|
|
3031
3106
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
3032
3107
|
}
|
|
3033
3108
|
function nonEmptyStrings(value) {
|
|
@@ -3065,10 +3140,10 @@ function collectionFor(action, resource) {
|
|
|
3065
3140
|
return resource.endsWith("s") ? resource : `${resource}s`;
|
|
3066
3141
|
}
|
|
3067
3142
|
function isConcreteOutputSchema(schema) {
|
|
3068
|
-
if (!
|
|
3143
|
+
if (!isRecord5(schema)) return false;
|
|
3069
3144
|
if (typeof schema.$ref === "string" && schema.$ref.trim()) return true;
|
|
3070
|
-
if (schema.type === "array" &&
|
|
3071
|
-
if (
|
|
3145
|
+
if (schema.type === "array" && isRecord5(schema.items) && Object.keys(schema.items).length > 0) return true;
|
|
3146
|
+
if (isRecord5(schema.properties) && Object.keys(schema.properties).length > 0) return true;
|
|
3072
3147
|
return ["oneOf", "anyOf", "allOf"].some((key) => Array.isArray(schema[key]) && schema[key].some(isConcreteOutputSchema));
|
|
3073
3148
|
}
|
|
3074
3149
|
function resourceSchema(resource) {
|
|
@@ -3195,10 +3270,10 @@ function isDynamicIdentifier(hint) {
|
|
|
3195
3270
|
return words === "id" || words.endsWith(" id");
|
|
3196
3271
|
}
|
|
3197
3272
|
function localReference(rootSchema, reference) {
|
|
3198
|
-
if (!
|
|
3273
|
+
if (!isRecord5(rootSchema) || typeof reference !== "string" || !reference.startsWith("#/")) return null;
|
|
3199
3274
|
return reference.slice(2).split("/").reduce((value, segment) => {
|
|
3200
3275
|
const key = segment.replaceAll("~1", "/").replaceAll("~0", "~");
|
|
3201
|
-
return
|
|
3276
|
+
return isRecord5(value) ? value[key] : void 0;
|
|
3202
3277
|
}, rootSchema);
|
|
3203
3278
|
}
|
|
3204
3279
|
function representativeNumber(schema, hint) {
|
|
@@ -3212,9 +3287,9 @@ function representativeNumber(schema, hint) {
|
|
|
3212
3287
|
return integer ? Math.round(value) : value;
|
|
3213
3288
|
}
|
|
3214
3289
|
function sampleForSchema(schema, depth = 0, hint = "value", rootSchema = schema) {
|
|
3215
|
-
if (!
|
|
3290
|
+
if (!isRecord5(schema) || depth > 20) return null;
|
|
3216
3291
|
const referenced = localReference(rootSchema, schema.$ref);
|
|
3217
|
-
if (
|
|
3292
|
+
if (isRecord5(referenced)) return sampleForSchema(referenced, depth + 1, hint, rootSchema);
|
|
3218
3293
|
if (schema.const !== void 0) return schema.const;
|
|
3219
3294
|
if (Array.isArray(schema.enum) && schema.enum.length > 0) return schema.enum[0];
|
|
3220
3295
|
if (Array.isArray(schema.oneOf) && schema.oneOf.length > 0) return sampleForSchema(schema.oneOf[0], depth + 1, hint, rootSchema);
|
|
@@ -3234,8 +3309,8 @@ function sampleForSchema(schema, depth = 0, hint = "value", rootSchema = schema)
|
|
|
3234
3309
|
}
|
|
3235
3310
|
if (schema.type === "string") return isDynamicIdentifier(hint) ? void 0 : sampleString(schema, hint);
|
|
3236
3311
|
if (schema.type === "null") return null;
|
|
3237
|
-
if (schema.type === "object" ||
|
|
3238
|
-
const properties =
|
|
3312
|
+
if (schema.type === "object" || isRecord5(schema.properties)) {
|
|
3313
|
+
const properties = isRecord5(schema.properties) ? schema.properties : {};
|
|
3239
3314
|
const required = new Set(Array.isArray(schema.required) ? schema.required : []);
|
|
3240
3315
|
const entries = Object.entries(properties).filter(([key]) => required.has(key)).map(([key, child]) => [key, sampleForSchema(child, depth + 1, key, rootSchema)]).filter(([, value]) => value !== void 0);
|
|
3241
3316
|
return Object.fromEntries(entries);
|
|
@@ -3243,9 +3318,9 @@ function sampleForSchema(schema, depth = 0, hint = "value", rootSchema = schema)
|
|
|
3243
3318
|
return null;
|
|
3244
3319
|
}
|
|
3245
3320
|
function hasRequiredDynamicIdentifier(schema, depth = 0, rootSchema = schema) {
|
|
3246
|
-
if (!
|
|
3321
|
+
if (!isRecord5(schema) || depth > 20) return false;
|
|
3247
3322
|
const referenced = localReference(rootSchema, schema.$ref);
|
|
3248
|
-
if (
|
|
3323
|
+
if (isRecord5(referenced)) return hasRequiredDynamicIdentifier(referenced, depth + 1, rootSchema);
|
|
3249
3324
|
if (Array.isArray(schema.oneOf)) {
|
|
3250
3325
|
return schema.oneOf.some((candidate) => hasRequiredDynamicIdentifier(candidate, depth + 1, rootSchema));
|
|
3251
3326
|
}
|
|
@@ -3255,10 +3330,10 @@ function hasRequiredDynamicIdentifier(schema, depth = 0, rootSchema = schema) {
|
|
|
3255
3330
|
if (schema.type === "array") {
|
|
3256
3331
|
return hasRequiredDynamicIdentifier(schema.items, depth + 1, rootSchema);
|
|
3257
3332
|
}
|
|
3258
|
-
if (schema.type !== "object" && !
|
|
3259
|
-
const properties =
|
|
3333
|
+
if (schema.type !== "object" && !isRecord5(schema.properties)) return false;
|
|
3334
|
+
const properties = isRecord5(schema.properties) ? schema.properties : {};
|
|
3260
3335
|
const required = new Set(Array.isArray(schema.required) ? schema.required : []);
|
|
3261
|
-
return Object.entries(properties).some(([key, child]) => required.has(key) && (
|
|
3336
|
+
return Object.entries(properties).some(([key, child]) => required.has(key) && (isRecord5(child) && child.type === "string" && isDynamicIdentifier(key) || hasRequiredDynamicIdentifier(child, depth + 1, rootSchema)));
|
|
3262
3337
|
}
|
|
3263
3338
|
function actionPrefix(name, operation) {
|
|
3264
3339
|
const tokens = String(name || "").split(/[._:-]+/).filter(Boolean);
|
|
@@ -3331,13 +3406,13 @@ function enrichActionContract(manifest, action, allActions = manifest.actions ||
|
|
|
3331
3406
|
const outputSchema = isConcreteOutputSchema(action.outputSchema) ? structuredClone(action.outputSchema) : derivedOutputSchema(action);
|
|
3332
3407
|
const resource = resourceFor(action);
|
|
3333
3408
|
const readOnly = action.readOnly !== void 0 ? action.readOnly === true : action.method === "GET";
|
|
3334
|
-
const existing =
|
|
3409
|
+
const existing = isRecord5(action.agentContract) ? structuredClone(action.agentContract) : {};
|
|
3335
3410
|
const declaredAgentContract = Object.keys(existing).length > 0;
|
|
3336
|
-
const recovery =
|
|
3411
|
+
const recovery = isRecord5(existing.recovery) ? existing.recovery : recoveryFor(action, allActions, appId);
|
|
3337
3412
|
const rollbackActionId = typeof existing.rollbackActionId === "string" && existing.rollbackActionId.trim() ? existing.rollbackActionId.trim() : recovery.mode === "compensating_action" ? recovery.actionId : void 0;
|
|
3338
3413
|
const inputExample = sampleForSchema(action.inputSchema);
|
|
3339
3414
|
const expectedExample = readOnly ? `A successful response conforming to outputSchema; verify the returned ${resource.replaceAll("_", " ")} belongs to the selected resource context.` : `A successful response conforming to outputSchema; re-read the affected ${resource.replaceAll("_", " ")} to verify the intended change.`;
|
|
3340
|
-
const examples = Array.isArray(existing.examples) && existing.examples.length > 0 ? existing.examples.map((example) =>
|
|
3415
|
+
const examples = Array.isArray(existing.examples) && existing.examples.length > 0 ? existing.examples.map((example) => isRecord5(example) && Object.hasOwn(example, "input") ? { ...example, expected: typeof example.expected === "string" && example.expected.trim() ? example.expected : expectedExample } : { input: example, expected: expectedExample }) : [{
|
|
3341
3416
|
input: inputExample ?? {},
|
|
3342
3417
|
expected: expectedExample
|
|
3343
3418
|
}];
|
|
@@ -3350,7 +3425,7 @@ function enrichActionContract(manifest, action, allActions = manifest.actions ||
|
|
|
3350
3425
|
...existing,
|
|
3351
3426
|
docsUrl,
|
|
3352
3427
|
...existingDocsUrl && existingDocsUrl !== docsUrl ? { guideUrl: existingDocsUrl } : {},
|
|
3353
|
-
resource:
|
|
3428
|
+
resource: isRecord5(existing.resource) ? existing.resource : { kind: resource, idPath: readOnly && ["list", "search"].includes(operationFor(action)) ? "$.items[].id" : "$.id" },
|
|
3354
3429
|
effects: nonEmptyStrings(existing.effects) ? existing.effects : [readOnly ? `Reads ${resource.replaceAll("_", " ")} data without changing service state.` : action.destructive === true || action.method === "DELETE" ? `Permanently removes or invalidates ${resource.replaceAll("_", " ")} state.` : `Changes ${resource.replaceAll("_", " ")} state in the selected Topolo resource context.`],
|
|
3355
3430
|
errors: mergeErrors(defaultErrors(action), existing.errors),
|
|
3356
3431
|
verify: nonEmptyStrings(existing.verify) ? existing.verify : [`Validate the response against outputSchema and ${readOnly ? "confirm it belongs to the selected resource context" : "re-read the affected resource to confirm the intended state"}.`],
|
|
@@ -3365,15 +3440,15 @@ function enrichActionContract(manifest, action, allActions = manifest.actions ||
|
|
|
3365
3440
|
function actionContractCompleteness(action) {
|
|
3366
3441
|
const contract = action?.agentContract;
|
|
3367
3442
|
return {
|
|
3368
|
-
inputSchema:
|
|
3443
|
+
inputSchema: isRecord5(action?.inputSchema),
|
|
3369
3444
|
outputSchema: isConcreteOutputSchema(action?.outputSchema),
|
|
3370
3445
|
effects: nonEmptyStrings(contract?.effects),
|
|
3371
3446
|
errors: Array.isArray(contract?.errors) && contract.errors.length > 0,
|
|
3372
3447
|
docs: typeof contract?.docsUrl === "string" && contract.docsUrl.startsWith("https://"),
|
|
3373
3448
|
examples: Array.isArray(contract?.examples) && contract.examples.length > 0,
|
|
3374
3449
|
verification: nonEmptyStrings(contract?.verify),
|
|
3375
|
-
recovery:
|
|
3376
|
-
agentContract:
|
|
3450
|
+
recovery: isRecord5(contract?.recovery) && ["not_applicable", "compensating_action", "irreversible"].includes(contract.recovery.mode),
|
|
3451
|
+
agentContract: isRecord5(contract) && isRecord5(contract.resource) && nonEmptyStrings(contract.effects) && Array.isArray(contract.errors) && contract.errors.length > 0
|
|
3377
3452
|
};
|
|
3378
3453
|
}
|
|
3379
3454
|
|
|
@@ -3850,7 +3925,7 @@ var DIMENSION_VALUE_TYPES = ["string", "number", "boolean", "date", "datetime"];
|
|
|
3850
3925
|
var SEMVER = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
3851
3926
|
function validateTopoloWidgetResponse(response) {
|
|
3852
3927
|
const errors = [];
|
|
3853
|
-
if (!
|
|
3928
|
+
if (!isRecord6(response)) return { ok: false, errors: ["response must be an object"] };
|
|
3854
3929
|
exactValue(response.contractVersion, CONTRACT_VERSION, "contractVersion", errors);
|
|
3855
3930
|
nonEmptyString(response.appId, "appId", errors);
|
|
3856
3931
|
nonEmptyString(response.serviceName, "serviceName", errors);
|
|
@@ -4046,7 +4121,7 @@ function validateContribution(value, path, widget, errors) {
|
|
|
4046
4121
|
nonEmptyString(value.workspaceSelector.resourceType, `${path}.workspaceSelector.resourceType`, errors);
|
|
4047
4122
|
nonEmptyString(value.workspaceSelector.label, `${path}.workspaceSelector.label`, errors);
|
|
4048
4123
|
booleanValue(value.workspaceSelector.supportsMultiple, `${path}.workspaceSelector.supportsMultiple`, errors);
|
|
4049
|
-
if (value.workspaceSelector.mode === "required" && requiresContent(widget) &&
|
|
4124
|
+
if (value.workspaceSelector.mode === "required" && requiresContent(widget) && isRecord6(widget.resource) && typeof widget.resource.resourceId !== "string") {
|
|
4050
4125
|
errors.push(`${path}.workspaceSelector requires a bound resourceId for ready or stale data`);
|
|
4051
4126
|
}
|
|
4052
4127
|
}
|
|
@@ -4198,36 +4273,36 @@ function widgetActionKeys(widget) {
|
|
|
4198
4273
|
const keys = /* @__PURE__ */ new Set();
|
|
4199
4274
|
for (const collection of [widget.controls, widget.actions]) {
|
|
4200
4275
|
if (!Array.isArray(collection)) continue;
|
|
4201
|
-
for (const action of collection) if (
|
|
4276
|
+
for (const action of collection) if (isRecord6(action) && typeof action.id === "string") keys.add(action.id);
|
|
4202
4277
|
}
|
|
4203
4278
|
return keys;
|
|
4204
4279
|
}
|
|
4205
4280
|
function validateRuntimeSemantics(widget, contributionPath, declared, errors) {
|
|
4206
4281
|
const metrics = Array.isArray(widget.metrics) ? widget.metrics : [];
|
|
4207
4282
|
for (const metric of metrics) {
|
|
4208
|
-
if (!
|
|
4283
|
+
if (!isRecord6(metric) || typeof metric.key !== "string") continue;
|
|
4209
4284
|
if (!declared.metricKeys.has(metric.key)) errors.push(`${contributionPath}.metricDefinitions is missing runtime metric ${metric.key}`);
|
|
4210
4285
|
validateRuntimeDimensions(metric.dimensions, declared.dimensionKeys, contributionPath, errors);
|
|
4211
|
-
if (
|
|
4286
|
+
if (isRecord6(metric.series)) {
|
|
4212
4287
|
if (typeof metric.series.id === "string" && !declared.seriesKeys.has(metric.series.id)) errors.push(`${contributionPath}.seriesDefinitions is missing runtime series ${metric.series.id}`);
|
|
4213
4288
|
validateSeriesDimensions(metric.series, declared.dimensionKeys, contributionPath, errors);
|
|
4214
4289
|
}
|
|
4215
4290
|
}
|
|
4216
4291
|
const series2 = Array.isArray(widget.series) ? widget.series : [];
|
|
4217
4292
|
for (const item of series2) {
|
|
4218
|
-
if (!
|
|
4293
|
+
if (!isRecord6(item)) continue;
|
|
4219
4294
|
if (typeof item.id === "string" && !declared.seriesKeys.has(item.id)) errors.push(`${contributionPath}.seriesDefinitions is missing runtime series ${item.id}`);
|
|
4220
4295
|
validateSeriesDimensions(item, declared.dimensionKeys, contributionPath, errors);
|
|
4221
4296
|
}
|
|
4222
4297
|
}
|
|
4223
4298
|
function validateSeriesDimensions(series2, known, path, errors) {
|
|
4224
4299
|
if (!Array.isArray(series2.points)) return;
|
|
4225
|
-
for (const point of series2.points) if (
|
|
4300
|
+
for (const point of series2.points) if (isRecord6(point)) validateRuntimeDimensions(point.dimensions, known, path, errors);
|
|
4226
4301
|
}
|
|
4227
4302
|
function validateRuntimeDimensions(value, known, path, errors) {
|
|
4228
4303
|
if (!Array.isArray(value)) return;
|
|
4229
4304
|
for (const dimension2 of value) {
|
|
4230
|
-
if (
|
|
4305
|
+
if (isRecord6(dimension2) && typeof dimension2.key === "string" && !known.has(dimension2.key)) {
|
|
4231
4306
|
errors.push(`${path}.dimensions is missing runtime dimension ${dimension2.key}`);
|
|
4232
4307
|
}
|
|
4233
4308
|
}
|
|
@@ -4329,10 +4404,10 @@ function validateNotifications(value, path, errors, requireOne) {
|
|
|
4329
4404
|
});
|
|
4330
4405
|
}
|
|
4331
4406
|
function requiresContent(widget) {
|
|
4332
|
-
return
|
|
4407
|
+
return isRecord6(widget.state) && (widget.state.status === "ready" || widget.state.status === "stale");
|
|
4333
4408
|
}
|
|
4334
4409
|
function recordValue(value, path, errors) {
|
|
4335
|
-
if (!
|
|
4410
|
+
if (!isRecord6(value)) {
|
|
4336
4411
|
errors.push(`${path} must be an object`);
|
|
4337
4412
|
return false;
|
|
4338
4413
|
}
|
|
@@ -4397,7 +4472,7 @@ function isIsoTimestamp(value) {
|
|
|
4397
4472
|
function isPositiveNumber(value) {
|
|
4398
4473
|
return typeof value === "number" && value > 0;
|
|
4399
4474
|
}
|
|
4400
|
-
function
|
|
4475
|
+
function isRecord6(value) {
|
|
4401
4476
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4402
4477
|
}
|
|
4403
4478
|
|
|
@@ -4939,19 +5014,19 @@ var TOPOLO_WIDGET_OUTPUT_SCHEMA = Object.freeze({
|
|
|
4939
5014
|
additionalProperties: false
|
|
4940
5015
|
});
|
|
4941
5016
|
function isTopoloWidgetOutputSchema(schema) {
|
|
4942
|
-
if (!
|
|
5017
|
+
if (!isRecord7(schema) || !isRecord7(schema.properties)) return false;
|
|
4943
5018
|
const properties = schema.properties;
|
|
4944
|
-
const widgetItemSchema =
|
|
4945
|
-
const widgetProperties = widgetItemSchema &&
|
|
4946
|
-
const contributionSchema = widgetProperties &&
|
|
4947
|
-
const contractVersion =
|
|
4948
|
-
return schema.type === "object" && contractVersion === "3.0.0" &&
|
|
5019
|
+
const widgetItemSchema = isRecord7(properties.widgets) && isRecord7(properties.widgets.items) ? properties.widgets.items : void 0;
|
|
5020
|
+
const widgetProperties = widgetItemSchema && isRecord7(widgetItemSchema.properties) ? widgetItemSchema.properties : void 0;
|
|
5021
|
+
const contributionSchema = widgetProperties && isRecord7(widgetProperties.contribution) ? widgetProperties.contribution : void 0;
|
|
5022
|
+
const contractVersion = isRecord7(properties.contractVersion) ? properties.contractVersion.const : void 0;
|
|
5023
|
+
return schema.type === "object" && contractVersion === "3.0.0" && isRecord7(properties.widgets) && properties.widgets.type === "array" && widgetItemSchema?.type === "object" && hasRequired(schema, "contractVersion") && hasRequired(schema, "widgets") && hasRequired(widgetItemSchema, "contribution") && hasRequired(contributionSchema, "datasets") && hasRequired(contributionSchema, "compatibleVisualizations") && hasRequired(contributionSchema, "defaultVisualization");
|
|
4949
5024
|
}
|
|
4950
|
-
function
|
|
5025
|
+
function isRecord7(value) {
|
|
4951
5026
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4952
5027
|
}
|
|
4953
5028
|
function hasRequired(schema, field) {
|
|
4954
|
-
return
|
|
5029
|
+
return isRecord7(schema) && Array.isArray(schema.required) && schema.required.includes(field);
|
|
4955
5030
|
}
|
|
4956
5031
|
|
|
4957
5032
|
// src/index.ts
|