@topolo/sdk 0.9.3 → 0.9.5
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 +365 -39
- package/dist/index.d.cts +65 -29
- package/dist/index.d.ts +65 -29
- package/dist/index.js +363 -39
- package/package.json +8 -5
package/dist/index.cjs
CHANGED
|
@@ -37,6 +37,7 @@ __export(index_exports, {
|
|
|
37
37
|
TopoloOAuth: () => TopoloOAuth,
|
|
38
38
|
TopoloPermissionError: () => TopoloPermissionError,
|
|
39
39
|
TopoloSdkError: () => TopoloSdkError,
|
|
40
|
+
actionContractCompleteness: () => actionContractCompleteness,
|
|
40
41
|
applicationRequirementScopes: () => applicationRequirementScopes,
|
|
41
42
|
auditAllApplicationRequirements: () => auditAllApplicationRequirements,
|
|
42
43
|
auditApplicationRequirements: () => auditApplicationRequirements,
|
|
@@ -49,6 +50,7 @@ __export(index_exports, {
|
|
|
49
50
|
defineTopoloRecentActivityWidget: () => defineTopoloRecentActivityWidget,
|
|
50
51
|
defineTopoloStatsWidget: () => defineTopoloStatsWidget,
|
|
51
52
|
digestTopoloActionCatalog: () => digestTopoloActionCatalog,
|
|
53
|
+
enrichActionContract: () => enrichActionContract,
|
|
52
54
|
requirementsForApplication: () => requirementsForApplication,
|
|
53
55
|
resolveCatalogServiceUrl: () => resolveCatalogServiceUrl,
|
|
54
56
|
resolveServiceUrl: () => resolveServiceUrl,
|
|
@@ -191,7 +193,9 @@ var APPLICATIONS = {
|
|
|
191
193
|
"domains": [
|
|
192
194
|
"admin.topolo.app"
|
|
193
195
|
],
|
|
194
|
-
"routes": [
|
|
196
|
+
"routes": [
|
|
197
|
+
"admin.topolo.app"
|
|
198
|
+
],
|
|
195
199
|
"appId": null
|
|
196
200
|
},
|
|
197
201
|
{
|
|
@@ -239,7 +243,7 @@ var APPLICATIONS = {
|
|
|
239
243
|
"appDir": "TopoloAuth",
|
|
240
244
|
"name": "topolo-auth",
|
|
241
245
|
"packageName": "topolo-auth",
|
|
242
|
-
"productionUrl": "https://auth.topolo.app
|
|
246
|
+
"productionUrl": "https://auth.topolo.app",
|
|
243
247
|
"services": [
|
|
244
248
|
"auth"
|
|
245
249
|
],
|
|
@@ -275,7 +279,9 @@ var APPLICATIONS = {
|
|
|
275
279
|
"domains": [
|
|
276
280
|
"backup.topolo.app"
|
|
277
281
|
],
|
|
278
|
-
"routes": [
|
|
282
|
+
"routes": [
|
|
283
|
+
"backup.topolo.app"
|
|
284
|
+
],
|
|
279
285
|
"appId": "backup"
|
|
280
286
|
}
|
|
281
287
|
]
|
|
@@ -327,9 +333,7 @@ var APPLICATIONS = {
|
|
|
327
333
|
"domains": [
|
|
328
334
|
"brand.topolo.app"
|
|
329
335
|
],
|
|
330
|
-
"routes": [
|
|
331
|
-
"brand.topolo.app"
|
|
332
|
-
],
|
|
336
|
+
"routes": [],
|
|
333
337
|
"appId": "brand"
|
|
334
338
|
},
|
|
335
339
|
{
|
|
@@ -381,7 +385,9 @@ var APPLICATIONS = {
|
|
|
381
385
|
"domains": [
|
|
382
386
|
"bytes.topolo.app"
|
|
383
387
|
],
|
|
384
|
-
"routes": [
|
|
388
|
+
"routes": [
|
|
389
|
+
"bytes.topolo.app"
|
|
390
|
+
],
|
|
385
391
|
"appId": "bytes"
|
|
386
392
|
},
|
|
387
393
|
{
|
|
@@ -411,7 +417,9 @@ var APPLICATIONS = {
|
|
|
411
417
|
"domains": [
|
|
412
418
|
"calendar.topolo.app"
|
|
413
419
|
],
|
|
414
|
-
"routes": [
|
|
420
|
+
"routes": [
|
|
421
|
+
"calendar.topolo.app"
|
|
422
|
+
],
|
|
415
423
|
"appId": "calendar"
|
|
416
424
|
},
|
|
417
425
|
{
|
|
@@ -441,9 +449,7 @@ var APPLICATIONS = {
|
|
|
441
449
|
"domains": [
|
|
442
450
|
"campaigns.topolo.app"
|
|
443
451
|
],
|
|
444
|
-
"routes": [
|
|
445
|
-
"campaigns.topolo.app"
|
|
446
|
-
],
|
|
452
|
+
"routes": [],
|
|
447
453
|
"appId": "campaigns"
|
|
448
454
|
},
|
|
449
455
|
{
|
|
@@ -533,7 +539,9 @@ var APPLICATIONS = {
|
|
|
533
539
|
"domains": [
|
|
534
540
|
"guest.topolo.app"
|
|
535
541
|
],
|
|
536
|
-
"routes": [
|
|
542
|
+
"routes": [
|
|
543
|
+
"guest.topolo.app"
|
|
544
|
+
],
|
|
537
545
|
"appId": null
|
|
538
546
|
},
|
|
539
547
|
{
|
|
@@ -543,7 +551,9 @@ var APPLICATIONS = {
|
|
|
543
551
|
"domains": [
|
|
544
552
|
"commerce.topolo.app"
|
|
545
553
|
],
|
|
546
|
-
"routes": [
|
|
554
|
+
"routes": [
|
|
555
|
+
"commerce.topolo.app"
|
|
556
|
+
],
|
|
547
557
|
"appId": "commerce"
|
|
548
558
|
},
|
|
549
559
|
{
|
|
@@ -553,7 +563,9 @@ var APPLICATIONS = {
|
|
|
553
563
|
"domains": [
|
|
554
564
|
"station-commerce.topolo.app"
|
|
555
565
|
],
|
|
556
|
-
"routes": [
|
|
566
|
+
"routes": [
|
|
567
|
+
"station-commerce.topolo.app"
|
|
568
|
+
],
|
|
557
569
|
"appId": null
|
|
558
570
|
}
|
|
559
571
|
]
|
|
@@ -731,7 +743,9 @@ var APPLICATIONS = {
|
|
|
731
743
|
"domains": [
|
|
732
744
|
"docs.topolo.app"
|
|
733
745
|
],
|
|
734
|
-
"routes": [
|
|
746
|
+
"routes": [
|
|
747
|
+
"docs.topolo.app"
|
|
748
|
+
],
|
|
735
749
|
"appId": null
|
|
736
750
|
},
|
|
737
751
|
{
|
|
@@ -761,7 +775,9 @@ var APPLICATIONS = {
|
|
|
761
775
|
"domains": [
|
|
762
776
|
"feed-analytics-api.topolo.app"
|
|
763
777
|
],
|
|
764
|
-
"routes": [
|
|
778
|
+
"routes": [
|
|
779
|
+
"feed-analytics-api.topolo.app"
|
|
780
|
+
],
|
|
765
781
|
"appId": null
|
|
766
782
|
},
|
|
767
783
|
{
|
|
@@ -771,7 +787,9 @@ var APPLICATIONS = {
|
|
|
771
787
|
"domains": [
|
|
772
788
|
"feed-api.topolo.app"
|
|
773
789
|
],
|
|
774
|
-
"routes": [
|
|
790
|
+
"routes": [
|
|
791
|
+
"feed-api.topolo.app"
|
|
792
|
+
],
|
|
775
793
|
"appId": null
|
|
776
794
|
},
|
|
777
795
|
{
|
|
@@ -781,7 +799,9 @@ var APPLICATIONS = {
|
|
|
781
799
|
"domains": [
|
|
782
800
|
"feed.topolo.app"
|
|
783
801
|
],
|
|
784
|
-
"routes": [
|
|
802
|
+
"routes": [
|
|
803
|
+
"feed.topolo.app"
|
|
804
|
+
],
|
|
785
805
|
"appId": "feed"
|
|
786
806
|
}
|
|
787
807
|
]
|
|
@@ -893,7 +913,9 @@ var APPLICATIONS = {
|
|
|
893
913
|
"domains": [
|
|
894
914
|
"home.topolo.app"
|
|
895
915
|
],
|
|
896
|
-
"routes": [
|
|
916
|
+
"routes": [
|
|
917
|
+
"home.topolo.app"
|
|
918
|
+
],
|
|
897
919
|
"appId": "home"
|
|
898
920
|
},
|
|
899
921
|
{
|
|
@@ -983,7 +1005,9 @@ var APPLICATIONS = {
|
|
|
983
1005
|
"domains": [
|
|
984
1006
|
"learn.topolo.app"
|
|
985
1007
|
],
|
|
986
|
-
"routes": [
|
|
1008
|
+
"routes": [
|
|
1009
|
+
"learn.topolo.app"
|
|
1010
|
+
],
|
|
987
1011
|
"appId": "learn"
|
|
988
1012
|
},
|
|
989
1013
|
{
|
|
@@ -1075,7 +1099,9 @@ var APPLICATIONS = {
|
|
|
1075
1099
|
"domains": [
|
|
1076
1100
|
"mdm.topolo.app"
|
|
1077
1101
|
],
|
|
1078
|
-
"routes": [
|
|
1102
|
+
"routes": [
|
|
1103
|
+
"mdm.topolo.app"
|
|
1104
|
+
],
|
|
1079
1105
|
"appId": "mdm"
|
|
1080
1106
|
}
|
|
1081
1107
|
]
|
|
@@ -1097,7 +1123,9 @@ var APPLICATIONS = {
|
|
|
1097
1123
|
"domains": [
|
|
1098
1124
|
"messages.topolo.app"
|
|
1099
1125
|
],
|
|
1100
|
-
"routes": [
|
|
1126
|
+
"routes": [
|
|
1127
|
+
"messages.topolo.app"
|
|
1128
|
+
],
|
|
1101
1129
|
"appId": "messages"
|
|
1102
1130
|
},
|
|
1103
1131
|
{
|
|
@@ -1131,11 +1159,13 @@ var APPLICATIONS = {
|
|
|
1131
1159
|
{
|
|
1132
1160
|
"name": "topolo-nexus-dashboard",
|
|
1133
1161
|
"kind": "worker",
|
|
1134
|
-
"apiUrl":
|
|
1162
|
+
"apiUrl": "https://nexus.topolo.app",
|
|
1135
1163
|
"domains": [
|
|
1136
1164
|
"nexus.topolo.app"
|
|
1137
1165
|
],
|
|
1138
|
-
"routes": [
|
|
1166
|
+
"routes": [
|
|
1167
|
+
"nexus.topolo.app"
|
|
1168
|
+
],
|
|
1139
1169
|
"appId": null
|
|
1140
1170
|
},
|
|
1141
1171
|
{
|
|
@@ -1263,7 +1293,9 @@ var APPLICATIONS = {
|
|
|
1263
1293
|
"domains": [
|
|
1264
1294
|
"p2p.topolo.app"
|
|
1265
1295
|
],
|
|
1266
|
-
"routes": [
|
|
1296
|
+
"routes": [
|
|
1297
|
+
"p2p.topolo.app"
|
|
1298
|
+
],
|
|
1267
1299
|
"appId": "p2p"
|
|
1268
1300
|
},
|
|
1269
1301
|
{
|
|
@@ -1460,7 +1492,9 @@ var APPLICATIONS = {
|
|
|
1460
1492
|
"domains": [
|
|
1461
1493
|
"studio.topolo.app"
|
|
1462
1494
|
],
|
|
1463
|
-
"routes": [
|
|
1495
|
+
"routes": [
|
|
1496
|
+
"studio.topolo.app"
|
|
1497
|
+
],
|
|
1464
1498
|
"appId": "social_studio"
|
|
1465
1499
|
},
|
|
1466
1500
|
{
|
|
@@ -1498,7 +1532,9 @@ var APPLICATIONS = {
|
|
|
1498
1532
|
"domains": [
|
|
1499
1533
|
"socialize.topolo.app"
|
|
1500
1534
|
],
|
|
1501
|
-
"routes": [
|
|
1535
|
+
"routes": [
|
|
1536
|
+
"socialize.topolo.app"
|
|
1537
|
+
],
|
|
1502
1538
|
"appId": "socialize"
|
|
1503
1539
|
},
|
|
1504
1540
|
{
|
|
@@ -1508,7 +1544,9 @@ var APPLICATIONS = {
|
|
|
1508
1544
|
"domains": [
|
|
1509
1545
|
"socialize-api.topolo.app"
|
|
1510
1546
|
],
|
|
1511
|
-
"routes": [
|
|
1547
|
+
"routes": [
|
|
1548
|
+
"socialize-api.topolo.app"
|
|
1549
|
+
],
|
|
1512
1550
|
"appId": null
|
|
1513
1551
|
},
|
|
1514
1552
|
{
|
|
@@ -1568,7 +1606,9 @@ var APPLICATIONS = {
|
|
|
1568
1606
|
"domains": [
|
|
1569
1607
|
"status.topolo.app"
|
|
1570
1608
|
],
|
|
1571
|
-
"routes": [
|
|
1609
|
+
"routes": [
|
|
1610
|
+
"status.topolo.app"
|
|
1611
|
+
],
|
|
1572
1612
|
"appId": "status"
|
|
1573
1613
|
}
|
|
1574
1614
|
]
|
|
@@ -1684,7 +1724,9 @@ var APPLICATIONS = {
|
|
|
1684
1724
|
"domains": [
|
|
1685
1725
|
"voice.topolo.app"
|
|
1686
1726
|
],
|
|
1687
|
-
"routes": [
|
|
1727
|
+
"routes": [
|
|
1728
|
+
"voice.topolo.app"
|
|
1729
|
+
],
|
|
1688
1730
|
"appId": "voice"
|
|
1689
1731
|
},
|
|
1690
1732
|
{
|
|
@@ -1735,7 +1777,9 @@ var APPLICATIONS = {
|
|
|
1735
1777
|
"domains": [
|
|
1736
1778
|
"web.topolo.app"
|
|
1737
1779
|
],
|
|
1738
|
-
"routes": [
|
|
1780
|
+
"routes": [
|
|
1781
|
+
"web.topolo.app"
|
|
1782
|
+
],
|
|
1739
1783
|
"appId": "web"
|
|
1740
1784
|
}
|
|
1741
1785
|
]
|
|
@@ -1814,7 +1858,7 @@ var APPLICATION_REQUIREMENTS = [
|
|
|
1814
1858
|
"Use TopoloAppMetricCard, TopoloAppEmptyState, TopoloAppStatusBadge, TopoloAppGrid, and TopoloAppSection for repeated app body patterns",
|
|
1815
1859
|
"Keep visual QA fixtures available through visualQa=1 or __devpreview=1 so screenshot checks do not depend on live API state",
|
|
1816
1860
|
"Keep mobile and desktop shell behavior aligned with the platform UI kit",
|
|
1817
|
-
"Constrain app
|
|
1861
|
+
"Constrain the app workspace-view layout wrappers with min-width: 0, width: auto, and viewport-aware max-width so shell padding cannot create page-level horizontal overflow"
|
|
1818
1862
|
]
|
|
1819
1863
|
},
|
|
1820
1864
|
{
|
|
@@ -3445,8 +3489,9 @@ async function digestTopoloActionCatalog(actions, appId) {
|
|
|
3445
3489
|
return {
|
|
3446
3490
|
actionId: action.actionId || `${appId}.${action.name}`,
|
|
3447
3491
|
authorizationMode,
|
|
3448
|
-
|
|
3492
|
+
allowedServices: authorizationMode === "service" || authorizationMode === "runtime" ? allowedServices : [],
|
|
3449
3493
|
inputSchema: action.inputSchema,
|
|
3494
|
+
agentContract: action.agentContract ?? {},
|
|
3450
3495
|
method: action.method,
|
|
3451
3496
|
outputSchema: action.outputSchema,
|
|
3452
3497
|
path: action.path,
|
|
@@ -3461,6 +3506,285 @@ async function digestTopoloActionCatalog(actions, appId) {
|
|
|
3461
3506
|
return [...new Uint8Array(digest)].map((value) => value.toString(16).padStart(2, "0")).join("");
|
|
3462
3507
|
}
|
|
3463
3508
|
|
|
3509
|
+
// src/action-contract-enrichment.mjs
|
|
3510
|
+
var OPERATION_TOKENS = /* @__PURE__ */ new Set([
|
|
3511
|
+
"add",
|
|
3512
|
+
"archive",
|
|
3513
|
+
"build",
|
|
3514
|
+
"call",
|
|
3515
|
+
"cancel",
|
|
3516
|
+
"claim",
|
|
3517
|
+
"complete",
|
|
3518
|
+
"create",
|
|
3519
|
+
"delete",
|
|
3520
|
+
"disable",
|
|
3521
|
+
"enable",
|
|
3522
|
+
"execute",
|
|
3523
|
+
"export",
|
|
3524
|
+
"get",
|
|
3525
|
+
"import",
|
|
3526
|
+
"ingest",
|
|
3527
|
+
"invite",
|
|
3528
|
+
"list",
|
|
3529
|
+
"move",
|
|
3530
|
+
"preview",
|
|
3531
|
+
"publish",
|
|
3532
|
+
"read",
|
|
3533
|
+
"refresh",
|
|
3534
|
+
"remove",
|
|
3535
|
+
"restore",
|
|
3536
|
+
"retry",
|
|
3537
|
+
"review",
|
|
3538
|
+
"run",
|
|
3539
|
+
"scaffold",
|
|
3540
|
+
"search",
|
|
3541
|
+
"send",
|
|
3542
|
+
"set",
|
|
3543
|
+
"submit",
|
|
3544
|
+
"sync",
|
|
3545
|
+
"transfer",
|
|
3546
|
+
"unpublish",
|
|
3547
|
+
"update",
|
|
3548
|
+
"upload",
|
|
3549
|
+
"validate"
|
|
3550
|
+
]);
|
|
3551
|
+
var INVERSE_OPERATIONS = /* @__PURE__ */ new Map([
|
|
3552
|
+
["archive", "restore"],
|
|
3553
|
+
["create", "delete"],
|
|
3554
|
+
["disable", "enable"],
|
|
3555
|
+
["enable", "disable"],
|
|
3556
|
+
["publish", "unpublish"],
|
|
3557
|
+
["remove", "add"],
|
|
3558
|
+
["restore", "archive"],
|
|
3559
|
+
["unpublish", "publish"]
|
|
3560
|
+
]);
|
|
3561
|
+
function isRecord3(value) {
|
|
3562
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
3563
|
+
}
|
|
3564
|
+
function nonEmptyStrings(value) {
|
|
3565
|
+
return Array.isArray(value) && value.length > 0 && value.every((item) => typeof item === "string" && item.trim());
|
|
3566
|
+
}
|
|
3567
|
+
function slugify(value) {
|
|
3568
|
+
return String(value || "").trim().replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
3569
|
+
}
|
|
3570
|
+
function singularize(value) {
|
|
3571
|
+
if (value.endsWith("ies") && value.length > 3) return `${value.slice(0, -3)}y`;
|
|
3572
|
+
if (value.endsWith("sses")) return value.slice(0, -2);
|
|
3573
|
+
if (value.endsWith("s") && !value.endsWith("ss")) return value.slice(0, -1);
|
|
3574
|
+
return value || "resource";
|
|
3575
|
+
}
|
|
3576
|
+
function operationFor(action) {
|
|
3577
|
+
const tokens = String(action.name || "").split(/[._:-]+/).filter(Boolean);
|
|
3578
|
+
return [...tokens].reverse().find((token) => OPERATION_TOKENS.has(token)) || String(action.method || "GET").toLowerCase();
|
|
3579
|
+
}
|
|
3580
|
+
function resourceFor(action) {
|
|
3581
|
+
const nameTokens = String(action.name || "").split(/[._:-]+/).filter(Boolean);
|
|
3582
|
+
const named = nameTokens.find((token) => !OPERATION_TOKENS.has(token));
|
|
3583
|
+
if (named) return singularize(slugify(named).replaceAll("-", "_"));
|
|
3584
|
+
const pathToken = String(action.path || "").split("/").filter((token) => token && token !== "api" && !token.startsWith("{")).find((token) => !OPERATION_TOKENS.has(slugify(token)));
|
|
3585
|
+
return singularize(slugify(pathToken || "resource").replaceAll("-", "_"));
|
|
3586
|
+
}
|
|
3587
|
+
function isConcreteOutputSchema(schema) {
|
|
3588
|
+
if (!isRecord3(schema)) return false;
|
|
3589
|
+
if (typeof schema.$ref === "string" && schema.$ref.trim()) return true;
|
|
3590
|
+
if (schema.type === "array" && isRecord3(schema.items) && Object.keys(schema.items).length > 0) return true;
|
|
3591
|
+
if (isRecord3(schema.properties) && Object.keys(schema.properties).length > 0) return true;
|
|
3592
|
+
return ["oneOf", "anyOf", "allOf"].some((key) => Array.isArray(schema[key]) && schema[key].some(isConcreteOutputSchema));
|
|
3593
|
+
}
|
|
3594
|
+
function resourceSchema(resource) {
|
|
3595
|
+
return {
|
|
3596
|
+
type: "object",
|
|
3597
|
+
title: `${resource.replaceAll("_", " ")} record`,
|
|
3598
|
+
properties: {
|
|
3599
|
+
id: {
|
|
3600
|
+
type: "string",
|
|
3601
|
+
description: `Stable identifier when the ${resource.replaceAll("_", " ")} exposes one.`
|
|
3602
|
+
}
|
|
3603
|
+
},
|
|
3604
|
+
additionalProperties: true
|
|
3605
|
+
};
|
|
3606
|
+
}
|
|
3607
|
+
function derivedOutputSchema(action) {
|
|
3608
|
+
const resource = resourceFor(action);
|
|
3609
|
+
const operation = operationFor(action);
|
|
3610
|
+
const record = resourceSchema(resource);
|
|
3611
|
+
const records = { type: "array", items: record };
|
|
3612
|
+
const provenance = {
|
|
3613
|
+
"x-topolo-contract-source": "derived-action-metadata",
|
|
3614
|
+
"x-topolo-contract-precision": "structural"
|
|
3615
|
+
};
|
|
3616
|
+
if (operation === "list" || operation === "search") {
|
|
3617
|
+
return {
|
|
3618
|
+
title: `${action.title || action.name} response`,
|
|
3619
|
+
description: `Collection response returned by ${action.name}. The service may return the collection directly or in an envelope.`,
|
|
3620
|
+
oneOf: [
|
|
3621
|
+
records,
|
|
3622
|
+
{
|
|
3623
|
+
type: "object",
|
|
3624
|
+
properties: {
|
|
3625
|
+
data: { oneOf: [records, { type: "object", additionalProperties: true }] },
|
|
3626
|
+
items: records,
|
|
3627
|
+
[resource.endsWith("s") ? resource : `${resource}s`]: records,
|
|
3628
|
+
count: { type: "integer", minimum: 0 },
|
|
3629
|
+
nextCursor: { type: ["string", "null"] }
|
|
3630
|
+
},
|
|
3631
|
+
additionalProperties: true
|
|
3632
|
+
}
|
|
3633
|
+
],
|
|
3634
|
+
...provenance
|
|
3635
|
+
};
|
|
3636
|
+
}
|
|
3637
|
+
if (operation === "get" || operation === "read") {
|
|
3638
|
+
return {
|
|
3639
|
+
title: `${action.title || action.name} response`,
|
|
3640
|
+
description: `Resource response returned by ${action.name}. The service may return the resource directly or in an envelope.`,
|
|
3641
|
+
oneOf: [
|
|
3642
|
+
record,
|
|
3643
|
+
{
|
|
3644
|
+
type: "object",
|
|
3645
|
+
properties: {
|
|
3646
|
+
data: record,
|
|
3647
|
+
[resource]: record
|
|
3648
|
+
},
|
|
3649
|
+
additionalProperties: true
|
|
3650
|
+
}
|
|
3651
|
+
],
|
|
3652
|
+
...provenance
|
|
3653
|
+
};
|
|
3654
|
+
}
|
|
3655
|
+
return {
|
|
3656
|
+
type: "object",
|
|
3657
|
+
title: `${action.title || action.name} response`,
|
|
3658
|
+
description: `Operation result returned by ${action.name}.`,
|
|
3659
|
+
properties: {
|
|
3660
|
+
success: { type: "boolean" },
|
|
3661
|
+
id: { type: "string" },
|
|
3662
|
+
data: { oneOf: [record, { type: "object", additionalProperties: true }] },
|
|
3663
|
+
result: { type: "object", additionalProperties: true },
|
|
3664
|
+
message: { type: "string" }
|
|
3665
|
+
},
|
|
3666
|
+
additionalProperties: true,
|
|
3667
|
+
...provenance
|
|
3668
|
+
};
|
|
3669
|
+
}
|
|
3670
|
+
function sampleForSchema(schema, depth = 0) {
|
|
3671
|
+
if (!isRecord3(schema) || depth > 5) return null;
|
|
3672
|
+
if (schema.const !== void 0) return schema.const;
|
|
3673
|
+
if (Array.isArray(schema.enum) && schema.enum.length > 0) return schema.enum[0];
|
|
3674
|
+
if (Array.isArray(schema.oneOf) && schema.oneOf.length > 0) return sampleForSchema(schema.oneOf[0], depth + 1);
|
|
3675
|
+
if (Array.isArray(schema.anyOf) && schema.anyOf.length > 0) return sampleForSchema(schema.anyOf[0], depth + 1);
|
|
3676
|
+
if (schema.type === "array") return [];
|
|
3677
|
+
if (schema.type === "boolean") return false;
|
|
3678
|
+
if (schema.type === "integer" || schema.type === "number") return schema.minimum ?? 0;
|
|
3679
|
+
if (schema.type === "string") {
|
|
3680
|
+
if (schema.format === "email") return "agent@example.com";
|
|
3681
|
+
if (schema.format === "uri" || schema.format === "url") return "https://example.com";
|
|
3682
|
+
return schema.minLength > 1 ? "example".padEnd(schema.minLength, "x") : "example";
|
|
3683
|
+
}
|
|
3684
|
+
if (schema.type === "null") return null;
|
|
3685
|
+
if (schema.type === "object" || isRecord3(schema.properties)) {
|
|
3686
|
+
const properties = isRecord3(schema.properties) ? schema.properties : {};
|
|
3687
|
+
const required = new Set(Array.isArray(schema.required) ? schema.required : []);
|
|
3688
|
+
const entries = Object.entries(properties).filter(([key]) => required.has(key)).map(([key, child]) => [key, sampleForSchema(child, depth + 1)]);
|
|
3689
|
+
return Object.fromEntries(entries);
|
|
3690
|
+
}
|
|
3691
|
+
return null;
|
|
3692
|
+
}
|
|
3693
|
+
function actionPrefix(name, operation) {
|
|
3694
|
+
const tokens = String(name || "").split(/[._:-]+/).filter(Boolean);
|
|
3695
|
+
const index = tokens.lastIndexOf(operation);
|
|
3696
|
+
if (index === -1) return tokens.join(".");
|
|
3697
|
+
return tokens.filter((_, tokenIndex) => tokenIndex !== index).join(".");
|
|
3698
|
+
}
|
|
3699
|
+
function findInverseActionId(action, allActions, appId) {
|
|
3700
|
+
const operation = operationFor(action);
|
|
3701
|
+
if (operation === "update" || operation === "set" || operation === "move") {
|
|
3702
|
+
return `${appId}.${action.name}`;
|
|
3703
|
+
}
|
|
3704
|
+
const inverse = INVERSE_OPERATIONS.get(operation);
|
|
3705
|
+
if (!inverse) return null;
|
|
3706
|
+
const prefix = actionPrefix(action.name, operation);
|
|
3707
|
+
const match = allActions.find((candidate) => operationFor(candidate) === inverse && actionPrefix(candidate.name, inverse) === prefix);
|
|
3708
|
+
return match ? `${appId}.${match.name}` : null;
|
|
3709
|
+
}
|
|
3710
|
+
function recoveryFor(action, allActions, appId) {
|
|
3711
|
+
const readOnly = action.readOnly !== void 0 ? action.readOnly === true : action.method === "GET";
|
|
3712
|
+
if (readOnly) {
|
|
3713
|
+
return {
|
|
3714
|
+
mode: "not_applicable",
|
|
3715
|
+
instructions: "This action does not change service state."
|
|
3716
|
+
};
|
|
3717
|
+
}
|
|
3718
|
+
const actionId = findInverseActionId(action, allActions, appId);
|
|
3719
|
+
if (actionId) {
|
|
3720
|
+
return {
|
|
3721
|
+
mode: "compensating_action",
|
|
3722
|
+
actionId,
|
|
3723
|
+
requiresSnapshot: actionId === `${appId}.${action.name}`,
|
|
3724
|
+
instructions: actionId === `${appId}.${action.name}` ? "Capture the current resource before the call, then invoke this action with the captured values to restore it." : `Invoke ${actionId} with the affected resource identifier after confirming the rollback.`
|
|
3725
|
+
};
|
|
3726
|
+
}
|
|
3727
|
+
return {
|
|
3728
|
+
mode: "irreversible",
|
|
3729
|
+
instructions: "No safe automatic inverse is published. Confirm the change and retain any required export or snapshot before calling."
|
|
3730
|
+
};
|
|
3731
|
+
}
|
|
3732
|
+
function defaultErrors(action) {
|
|
3733
|
+
const errors = [
|
|
3734
|
+
{ status: 401, code: "authentication_required", recovery: "Authenticate again or provide a valid Topolo API key." },
|
|
3735
|
+
{ status: 403, code: "permission_denied", recovery: `Grant the credential permission ${action.permission || action.permissionName || "required by this action"} and the required resource binding.` },
|
|
3736
|
+
{ status: 422, code: "invalid_input", recovery: "Correct the payload using inputSchema, then validate before retrying." }
|
|
3737
|
+
];
|
|
3738
|
+
if (action.method !== "GET") {
|
|
3739
|
+
errors.push({ status: 409, code: "state_conflict", recovery: "Read the current resource state, reconcile the intended change, and retry only if still valid." });
|
|
3740
|
+
}
|
|
3741
|
+
return errors;
|
|
3742
|
+
}
|
|
3743
|
+
function enrichActionContract(manifest, action, allActions = manifest.actions || []) {
|
|
3744
|
+
const appId = String(manifest.appId || manifest.app_id || "").trim();
|
|
3745
|
+
const service = slugify(manifest.slug || appId.replace(/^app_/, "").replaceAll("_", "-") || manifest.name);
|
|
3746
|
+
const outputSchema = isConcreteOutputSchema(action.outputSchema) ? structuredClone(action.outputSchema) : derivedOutputSchema(action);
|
|
3747
|
+
const resource = resourceFor(action);
|
|
3748
|
+
const readOnly = action.readOnly !== void 0 ? action.readOnly === true : action.method === "GET";
|
|
3749
|
+
const existing = isRecord3(action.agentContract) ? structuredClone(action.agentContract) : {};
|
|
3750
|
+
const recovery = isRecord3(existing.recovery) ? existing.recovery : recoveryFor(action, allActions, appId);
|
|
3751
|
+
const rollbackActionId = typeof existing.rollbackActionId === "string" && existing.rollbackActionId.trim() ? existing.rollbackActionId.trim() : recovery.mode === "compensating_action" ? recovery.actionId : void 0;
|
|
3752
|
+
const inputExample = sampleForSchema(action.inputSchema);
|
|
3753
|
+
const examples = Array.isArray(existing.examples) && existing.examples.length > 0 ? existing.examples : [{ input: inputExample ?? {}, expected: `A response matching the published outputSchema for ${action.name}.` }];
|
|
3754
|
+
const docsUrl = `https://docs.topolo.app/reference/actions/?service=${encodeURIComponent(service)}&action=${encodeURIComponent(action.name)}`;
|
|
3755
|
+
const existingDocsUrl = typeof existing.docsUrl === "string" && existing.docsUrl.trim() ? existing.docsUrl.trim() : void 0;
|
|
3756
|
+
return {
|
|
3757
|
+
outputSchema,
|
|
3758
|
+
agentContract: {
|
|
3759
|
+
...existing,
|
|
3760
|
+
docsUrl,
|
|
3761
|
+
...existingDocsUrl && existingDocsUrl !== docsUrl ? { guideUrl: existingDocsUrl } : {},
|
|
3762
|
+
resource: isRecord3(existing.resource) ? existing.resource : { kind: resource, idPath: readOnly && ["list", "search"].includes(operationFor(action)) ? "$.items[].id" : "$.id" },
|
|
3763
|
+
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.`],
|
|
3764
|
+
errors: Array.isArray(existing.errors) && existing.errors.length > 0 ? existing.errors : defaultErrors(action),
|
|
3765
|
+
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"}.`],
|
|
3766
|
+
examples,
|
|
3767
|
+
recovery,
|
|
3768
|
+
...rollbackActionId ? { rollbackActionId } : {},
|
|
3769
|
+
contractSource: Object.hasOwn(outputSchema, "x-topolo-contract-source") ? "derived-action-metadata" : "application-declared"
|
|
3770
|
+
}
|
|
3771
|
+
};
|
|
3772
|
+
}
|
|
3773
|
+
function actionContractCompleteness(action) {
|
|
3774
|
+
const contract = action?.agentContract;
|
|
3775
|
+
return {
|
|
3776
|
+
inputSchema: isRecord3(action?.inputSchema),
|
|
3777
|
+
outputSchema: isConcreteOutputSchema(action?.outputSchema),
|
|
3778
|
+
effects: nonEmptyStrings(contract?.effects),
|
|
3779
|
+
errors: Array.isArray(contract?.errors) && contract.errors.length > 0,
|
|
3780
|
+
docs: typeof contract?.docsUrl === "string" && contract.docsUrl.startsWith("https://"),
|
|
3781
|
+
examples: Array.isArray(contract?.examples) && contract.examples.length > 0,
|
|
3782
|
+
verification: nonEmptyStrings(contract?.verify),
|
|
3783
|
+
recovery: isRecord3(contract?.recovery) && ["not_applicable", "compensating_action", "irreversible"].includes(contract.recovery.mode),
|
|
3784
|
+
agentContract: isRecord3(contract) && isRecord3(contract.resource) && nonEmptyStrings(contract.effects) && Array.isArray(contract.errors) && contract.errors.length > 0
|
|
3785
|
+
};
|
|
3786
|
+
}
|
|
3787
|
+
|
|
3464
3788
|
// src/modules/crm.ts
|
|
3465
3789
|
var CrmModule = class {
|
|
3466
3790
|
constructor(client) {
|
|
@@ -3862,7 +4186,7 @@ function createTopoloWidgetError(error) {
|
|
|
3862
4186
|
}
|
|
3863
4187
|
function validateTopoloWidgetResponse(response) {
|
|
3864
4188
|
const errors = [];
|
|
3865
|
-
if (!
|
|
4189
|
+
if (!isRecord4(response)) {
|
|
3866
4190
|
return { ok: false, errors: ["response must be an object"] };
|
|
3867
4191
|
}
|
|
3868
4192
|
if (typeof response.success !== "boolean") {
|
|
@@ -3881,7 +4205,7 @@ function validateTopoloWidgetResponse(response) {
|
|
|
3881
4205
|
return { ok: errors.length === 0, errors };
|
|
3882
4206
|
}
|
|
3883
4207
|
function validateWidget(widget, path, errors) {
|
|
3884
|
-
if (!
|
|
4208
|
+
if (!isRecord4(widget)) {
|
|
3885
4209
|
errors.push(`${path} must be an object`);
|
|
3886
4210
|
return;
|
|
3887
4211
|
}
|
|
@@ -3908,7 +4232,7 @@ function validateWidget(widget, path, errors) {
|
|
|
3908
4232
|
case "chart":
|
|
3909
4233
|
validateNonEmptyString(widget.chartType, `${path}.chartType`, errors);
|
|
3910
4234
|
validateNonEmptyString(widget.title, `${path}.title`, errors);
|
|
3911
|
-
if (!
|
|
4235
|
+
if (!isRecord4(widget.data)) {
|
|
3912
4236
|
errors.push(`${path}.data must be an object`);
|
|
3913
4237
|
} else {
|
|
3914
4238
|
validateArray2(widget.data.labels, `${path}.data.labels`, errors);
|
|
@@ -3924,7 +4248,7 @@ function validateSnapshotMetadata(snapshot, path, errors) {
|
|
|
3924
4248
|
errors.push(`${path} must be an object`);
|
|
3925
4249
|
return;
|
|
3926
4250
|
}
|
|
3927
|
-
if (!
|
|
4251
|
+
if (!isRecord4(snapshot)) {
|
|
3928
4252
|
errors.push(`${path} must be an object`);
|
|
3929
4253
|
return;
|
|
3930
4254
|
}
|
|
@@ -3944,7 +4268,7 @@ function validateSnapshotMetadata(snapshot, path, errors) {
|
|
|
3944
4268
|
}
|
|
3945
4269
|
snapshot.availableMetrics.forEach((metric, index) => {
|
|
3946
4270
|
const metricPath = `${path}.availableMetrics[${index}]`;
|
|
3947
|
-
if (!
|
|
4271
|
+
if (!isRecord4(metric)) {
|
|
3948
4272
|
errors.push(`${metricPath} must be an object`);
|
|
3949
4273
|
return;
|
|
3950
4274
|
}
|
|
@@ -3965,7 +4289,7 @@ function validateArray2(value, path, errors) {
|
|
|
3965
4289
|
errors.push(`${path} must be an array`);
|
|
3966
4290
|
}
|
|
3967
4291
|
}
|
|
3968
|
-
function
|
|
4292
|
+
function isRecord4(value) {
|
|
3969
4293
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3970
4294
|
}
|
|
3971
4295
|
function toIsoTimestamp(input) {
|
|
@@ -4003,6 +4327,7 @@ function createTopolo(options) {
|
|
|
4003
4327
|
TopoloOAuth,
|
|
4004
4328
|
TopoloPermissionError,
|
|
4005
4329
|
TopoloSdkError,
|
|
4330
|
+
actionContractCompleteness,
|
|
4006
4331
|
applicationRequirementScopes,
|
|
4007
4332
|
auditAllApplicationRequirements,
|
|
4008
4333
|
auditApplicationRequirements,
|
|
@@ -4015,6 +4340,7 @@ function createTopolo(options) {
|
|
|
4015
4340
|
defineTopoloRecentActivityWidget,
|
|
4016
4341
|
defineTopoloStatsWidget,
|
|
4017
4342
|
digestTopoloActionCatalog,
|
|
4343
|
+
enrichActionContract,
|
|
4018
4344
|
requirementsForApplication,
|
|
4019
4345
|
resolveCatalogServiceUrl,
|
|
4020
4346
|
resolveServiceUrl,
|