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