@topolo/sdk 0.9.2 → 0.9.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 +383 -51
- package/dist/index.d.cts +69 -31
- package/dist/index.d.ts +69 -31
- package/dist/index.js +381 -51
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -132,7 +132,9 @@ var APPLICATIONS = {
|
|
|
132
132
|
"domains": [
|
|
133
133
|
"admin.topolo.app"
|
|
134
134
|
],
|
|
135
|
-
"routes": [
|
|
135
|
+
"routes": [
|
|
136
|
+
"admin.topolo.app"
|
|
137
|
+
],
|
|
136
138
|
"appId": null
|
|
137
139
|
},
|
|
138
140
|
{
|
|
@@ -180,7 +182,7 @@ var APPLICATIONS = {
|
|
|
180
182
|
"appDir": "TopoloAuth",
|
|
181
183
|
"name": "topolo-auth",
|
|
182
184
|
"packageName": "topolo-auth",
|
|
183
|
-
"productionUrl": "https://auth.topolo.app
|
|
185
|
+
"productionUrl": "https://auth.topolo.app",
|
|
184
186
|
"services": [
|
|
185
187
|
"auth"
|
|
186
188
|
],
|
|
@@ -216,7 +218,9 @@ var APPLICATIONS = {
|
|
|
216
218
|
"domains": [
|
|
217
219
|
"backup.topolo.app"
|
|
218
220
|
],
|
|
219
|
-
"routes": [
|
|
221
|
+
"routes": [
|
|
222
|
+
"backup.topolo.app"
|
|
223
|
+
],
|
|
220
224
|
"appId": "backup"
|
|
221
225
|
}
|
|
222
226
|
]
|
|
@@ -268,9 +272,7 @@ var APPLICATIONS = {
|
|
|
268
272
|
"domains": [
|
|
269
273
|
"brand.topolo.app"
|
|
270
274
|
],
|
|
271
|
-
"routes": [
|
|
272
|
-
"brand.topolo.app"
|
|
273
|
-
],
|
|
275
|
+
"routes": [],
|
|
274
276
|
"appId": "brand"
|
|
275
277
|
},
|
|
276
278
|
{
|
|
@@ -322,7 +324,9 @@ var APPLICATIONS = {
|
|
|
322
324
|
"domains": [
|
|
323
325
|
"bytes.topolo.app"
|
|
324
326
|
],
|
|
325
|
-
"routes": [
|
|
327
|
+
"routes": [
|
|
328
|
+
"bytes.topolo.app"
|
|
329
|
+
],
|
|
326
330
|
"appId": "bytes"
|
|
327
331
|
},
|
|
328
332
|
{
|
|
@@ -352,7 +356,9 @@ var APPLICATIONS = {
|
|
|
352
356
|
"domains": [
|
|
353
357
|
"calendar.topolo.app"
|
|
354
358
|
],
|
|
355
|
-
"routes": [
|
|
359
|
+
"routes": [
|
|
360
|
+
"calendar.topolo.app"
|
|
361
|
+
],
|
|
356
362
|
"appId": "calendar"
|
|
357
363
|
},
|
|
358
364
|
{
|
|
@@ -382,9 +388,7 @@ var APPLICATIONS = {
|
|
|
382
388
|
"domains": [
|
|
383
389
|
"campaigns.topolo.app"
|
|
384
390
|
],
|
|
385
|
-
"routes": [
|
|
386
|
-
"campaigns.topolo.app"
|
|
387
|
-
],
|
|
391
|
+
"routes": [],
|
|
388
392
|
"appId": "campaigns"
|
|
389
393
|
},
|
|
390
394
|
{
|
|
@@ -474,7 +478,9 @@ var APPLICATIONS = {
|
|
|
474
478
|
"domains": [
|
|
475
479
|
"guest.topolo.app"
|
|
476
480
|
],
|
|
477
|
-
"routes": [
|
|
481
|
+
"routes": [
|
|
482
|
+
"guest.topolo.app"
|
|
483
|
+
],
|
|
478
484
|
"appId": null
|
|
479
485
|
},
|
|
480
486
|
{
|
|
@@ -484,7 +490,9 @@ var APPLICATIONS = {
|
|
|
484
490
|
"domains": [
|
|
485
491
|
"commerce.topolo.app"
|
|
486
492
|
],
|
|
487
|
-
"routes": [
|
|
493
|
+
"routes": [
|
|
494
|
+
"commerce.topolo.app"
|
|
495
|
+
],
|
|
488
496
|
"appId": "commerce"
|
|
489
497
|
},
|
|
490
498
|
{
|
|
@@ -494,7 +502,9 @@ var APPLICATIONS = {
|
|
|
494
502
|
"domains": [
|
|
495
503
|
"station-commerce.topolo.app"
|
|
496
504
|
],
|
|
497
|
-
"routes": [
|
|
505
|
+
"routes": [
|
|
506
|
+
"station-commerce.topolo.app"
|
|
507
|
+
],
|
|
498
508
|
"appId": null
|
|
499
509
|
}
|
|
500
510
|
]
|
|
@@ -672,7 +682,9 @@ var APPLICATIONS = {
|
|
|
672
682
|
"domains": [
|
|
673
683
|
"docs.topolo.app"
|
|
674
684
|
],
|
|
675
|
-
"routes": [
|
|
685
|
+
"routes": [
|
|
686
|
+
"docs.topolo.app"
|
|
687
|
+
],
|
|
676
688
|
"appId": null
|
|
677
689
|
},
|
|
678
690
|
{
|
|
@@ -702,7 +714,9 @@ var APPLICATIONS = {
|
|
|
702
714
|
"domains": [
|
|
703
715
|
"feed-analytics-api.topolo.app"
|
|
704
716
|
],
|
|
705
|
-
"routes": [
|
|
717
|
+
"routes": [
|
|
718
|
+
"feed-analytics-api.topolo.app"
|
|
719
|
+
],
|
|
706
720
|
"appId": null
|
|
707
721
|
},
|
|
708
722
|
{
|
|
@@ -712,7 +726,9 @@ var APPLICATIONS = {
|
|
|
712
726
|
"domains": [
|
|
713
727
|
"feed-api.topolo.app"
|
|
714
728
|
],
|
|
715
|
-
"routes": [
|
|
729
|
+
"routes": [
|
|
730
|
+
"feed-api.topolo.app"
|
|
731
|
+
],
|
|
716
732
|
"appId": null
|
|
717
733
|
},
|
|
718
734
|
{
|
|
@@ -722,7 +738,9 @@ var APPLICATIONS = {
|
|
|
722
738
|
"domains": [
|
|
723
739
|
"feed.topolo.app"
|
|
724
740
|
],
|
|
725
|
-
"routes": [
|
|
741
|
+
"routes": [
|
|
742
|
+
"feed.topolo.app"
|
|
743
|
+
],
|
|
726
744
|
"appId": "feed"
|
|
727
745
|
}
|
|
728
746
|
]
|
|
@@ -834,7 +852,9 @@ var APPLICATIONS = {
|
|
|
834
852
|
"domains": [
|
|
835
853
|
"home.topolo.app"
|
|
836
854
|
],
|
|
837
|
-
"routes": [
|
|
855
|
+
"routes": [
|
|
856
|
+
"home.topolo.app"
|
|
857
|
+
],
|
|
838
858
|
"appId": "home"
|
|
839
859
|
},
|
|
840
860
|
{
|
|
@@ -924,7 +944,9 @@ var APPLICATIONS = {
|
|
|
924
944
|
"domains": [
|
|
925
945
|
"learn.topolo.app"
|
|
926
946
|
],
|
|
927
|
-
"routes": [
|
|
947
|
+
"routes": [
|
|
948
|
+
"learn.topolo.app"
|
|
949
|
+
],
|
|
928
950
|
"appId": "learn"
|
|
929
951
|
},
|
|
930
952
|
{
|
|
@@ -1016,7 +1038,9 @@ var APPLICATIONS = {
|
|
|
1016
1038
|
"domains": [
|
|
1017
1039
|
"mdm.topolo.app"
|
|
1018
1040
|
],
|
|
1019
|
-
"routes": [
|
|
1041
|
+
"routes": [
|
|
1042
|
+
"mdm.topolo.app"
|
|
1043
|
+
],
|
|
1020
1044
|
"appId": "mdm"
|
|
1021
1045
|
}
|
|
1022
1046
|
]
|
|
@@ -1038,7 +1062,9 @@ var APPLICATIONS = {
|
|
|
1038
1062
|
"domains": [
|
|
1039
1063
|
"messages.topolo.app"
|
|
1040
1064
|
],
|
|
1041
|
-
"routes": [
|
|
1065
|
+
"routes": [
|
|
1066
|
+
"messages.topolo.app"
|
|
1067
|
+
],
|
|
1042
1068
|
"appId": "messages"
|
|
1043
1069
|
},
|
|
1044
1070
|
{
|
|
@@ -1072,11 +1098,13 @@ var APPLICATIONS = {
|
|
|
1072
1098
|
{
|
|
1073
1099
|
"name": "topolo-nexus-dashboard",
|
|
1074
1100
|
"kind": "worker",
|
|
1075
|
-
"apiUrl":
|
|
1101
|
+
"apiUrl": "https://nexus.topolo.app",
|
|
1076
1102
|
"domains": [
|
|
1077
1103
|
"nexus.topolo.app"
|
|
1078
1104
|
],
|
|
1079
|
-
"routes": [
|
|
1105
|
+
"routes": [
|
|
1106
|
+
"nexus.topolo.app"
|
|
1107
|
+
],
|
|
1080
1108
|
"appId": null
|
|
1081
1109
|
},
|
|
1082
1110
|
{
|
|
@@ -1204,7 +1232,9 @@ var APPLICATIONS = {
|
|
|
1204
1232
|
"domains": [
|
|
1205
1233
|
"p2p.topolo.app"
|
|
1206
1234
|
],
|
|
1207
|
-
"routes": [
|
|
1235
|
+
"routes": [
|
|
1236
|
+
"p2p.topolo.app"
|
|
1237
|
+
],
|
|
1208
1238
|
"appId": "p2p"
|
|
1209
1239
|
},
|
|
1210
1240
|
{
|
|
@@ -1401,7 +1431,9 @@ var APPLICATIONS = {
|
|
|
1401
1431
|
"domains": [
|
|
1402
1432
|
"studio.topolo.app"
|
|
1403
1433
|
],
|
|
1404
|
-
"routes": [
|
|
1434
|
+
"routes": [
|
|
1435
|
+
"studio.topolo.app"
|
|
1436
|
+
],
|
|
1405
1437
|
"appId": "social_studio"
|
|
1406
1438
|
},
|
|
1407
1439
|
{
|
|
@@ -1439,7 +1471,9 @@ var APPLICATIONS = {
|
|
|
1439
1471
|
"domains": [
|
|
1440
1472
|
"socialize.topolo.app"
|
|
1441
1473
|
],
|
|
1442
|
-
"routes": [
|
|
1474
|
+
"routes": [
|
|
1475
|
+
"socialize.topolo.app"
|
|
1476
|
+
],
|
|
1443
1477
|
"appId": "socialize"
|
|
1444
1478
|
},
|
|
1445
1479
|
{
|
|
@@ -1449,7 +1483,9 @@ var APPLICATIONS = {
|
|
|
1449
1483
|
"domains": [
|
|
1450
1484
|
"socialize-api.topolo.app"
|
|
1451
1485
|
],
|
|
1452
|
-
"routes": [
|
|
1486
|
+
"routes": [
|
|
1487
|
+
"socialize-api.topolo.app"
|
|
1488
|
+
],
|
|
1453
1489
|
"appId": null
|
|
1454
1490
|
},
|
|
1455
1491
|
{
|
|
@@ -1509,7 +1545,9 @@ var APPLICATIONS = {
|
|
|
1509
1545
|
"domains": [
|
|
1510
1546
|
"status.topolo.app"
|
|
1511
1547
|
],
|
|
1512
|
-
"routes": [
|
|
1548
|
+
"routes": [
|
|
1549
|
+
"status.topolo.app"
|
|
1550
|
+
],
|
|
1513
1551
|
"appId": "status"
|
|
1514
1552
|
}
|
|
1515
1553
|
]
|
|
@@ -1625,7 +1663,9 @@ var APPLICATIONS = {
|
|
|
1625
1663
|
"domains": [
|
|
1626
1664
|
"voice.topolo.app"
|
|
1627
1665
|
],
|
|
1628
|
-
"routes": [
|
|
1666
|
+
"routes": [
|
|
1667
|
+
"voice.topolo.app"
|
|
1668
|
+
],
|
|
1629
1669
|
"appId": "voice"
|
|
1630
1670
|
},
|
|
1631
1671
|
{
|
|
@@ -1676,7 +1716,9 @@ var APPLICATIONS = {
|
|
|
1676
1716
|
"domains": [
|
|
1677
1717
|
"web.topolo.app"
|
|
1678
1718
|
],
|
|
1679
|
-
"routes": [
|
|
1719
|
+
"routes": [
|
|
1720
|
+
"web.topolo.app"
|
|
1721
|
+
],
|
|
1680
1722
|
"appId": "web"
|
|
1681
1723
|
}
|
|
1682
1724
|
]
|
|
@@ -1755,7 +1797,7 @@ var APPLICATION_REQUIREMENTS = [
|
|
|
1755
1797
|
"Use TopoloAppMetricCard, TopoloAppEmptyState, TopoloAppStatusBadge, TopoloAppGrid, and TopoloAppSection for repeated app body patterns",
|
|
1756
1798
|
"Keep visual QA fixtures available through visualQa=1 or __devpreview=1 so screenshot checks do not depend on live API state",
|
|
1757
1799
|
"Keep mobile and desktop shell behavior aligned with the platform UI kit",
|
|
1758
|
-
"Constrain app
|
|
1800
|
+
"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"
|
|
1759
1801
|
]
|
|
1760
1802
|
},
|
|
1761
1803
|
{
|
|
@@ -3250,6 +3292,7 @@ function normalizeActionCatalogEntry(item) {
|
|
|
3250
3292
|
permissionName: pickActionString(a.permission_name) ?? pickActionString(a.permissionName) ?? "",
|
|
3251
3293
|
requiredPermission: pickActionString(a.required_permission) ?? pickActionString(a.requiredPermission) ?? "",
|
|
3252
3294
|
authorizationMode: pickAuthorizationMode(a.authorization_mode ?? a.authorizationMode),
|
|
3295
|
+
allowedServices: stringArray(a.allowed_services ?? a.allowedServices),
|
|
3253
3296
|
inputSchema: plainObject(a.input_schema ?? a.inputSchema),
|
|
3254
3297
|
outputSchema: plainObject(a.output_schema ?? a.outputSchema),
|
|
3255
3298
|
agentContract: plainObject(a.agent_contract ?? a.agentContract),
|
|
@@ -3271,7 +3314,7 @@ function pickAgentAccess(value) {
|
|
|
3271
3314
|
return value === "auto" || value === "confirm" || value === "off" ? value : null;
|
|
3272
3315
|
}
|
|
3273
3316
|
function pickAuthorizationMode(value) {
|
|
3274
|
-
return value === "runtime"
|
|
3317
|
+
return value === "runtime" || value === "public" || value === "service" ? value : "static";
|
|
3275
3318
|
}
|
|
3276
3319
|
function plainObject(value) {
|
|
3277
3320
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
@@ -3379,23 +3422,308 @@ function stableJson(value) {
|
|
|
3379
3422
|
return JSON.stringify(value);
|
|
3380
3423
|
}
|
|
3381
3424
|
async function digestTopoloActionCatalog(actions, appId) {
|
|
3382
|
-
const canonical = actions.filter((action) => action.agentAccess !== "off").map((action) =>
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3425
|
+
const canonical = actions.filter((action) => action.agentAccess !== "off").map((action) => {
|
|
3426
|
+
const authorizationMode = ["runtime", "public", "service"].includes(action.authorizationMode || "") ? action.authorizationMode : "static";
|
|
3427
|
+
const allowedServices = [...action.allowedServices || []].map((service) => service.trim()).filter(Boolean).sort();
|
|
3428
|
+
return {
|
|
3429
|
+
actionId: action.actionId || `${appId}.${action.name}`,
|
|
3430
|
+
authorizationMode,
|
|
3431
|
+
allowedServices: authorizationMode === "service" ? allowedServices : [],
|
|
3432
|
+
inputSchema: action.inputSchema,
|
|
3433
|
+
agentContract: action.agentContract ?? {},
|
|
3434
|
+
method: action.method,
|
|
3435
|
+
outputSchema: action.outputSchema,
|
|
3436
|
+
path: action.path,
|
|
3437
|
+
permission: canonicalPermission(action.permission ?? action.permissionName ?? "", appId),
|
|
3438
|
+
readOnly: action.readOnly,
|
|
3439
|
+
requiresConfirmation: action.requiresConfirmation,
|
|
3440
|
+
target: action.target ?? ""
|
|
3441
|
+
};
|
|
3442
|
+
}).sort((left, right) => left.actionId.localeCompare(right.actionId));
|
|
3394
3443
|
const bytes = new TextEncoder().encode(canonical.map(stableJson).join("\n"));
|
|
3395
3444
|
const digest = await crypto.subtle.digest("SHA-256", bytes);
|
|
3396
3445
|
return [...new Uint8Array(digest)].map((value) => value.toString(16).padStart(2, "0")).join("");
|
|
3397
3446
|
}
|
|
3398
3447
|
|
|
3448
|
+
// src/action-contract-enrichment.mjs
|
|
3449
|
+
var OPERATION_TOKENS = /* @__PURE__ */ new Set([
|
|
3450
|
+
"add",
|
|
3451
|
+
"archive",
|
|
3452
|
+
"build",
|
|
3453
|
+
"call",
|
|
3454
|
+
"cancel",
|
|
3455
|
+
"claim",
|
|
3456
|
+
"complete",
|
|
3457
|
+
"create",
|
|
3458
|
+
"delete",
|
|
3459
|
+
"disable",
|
|
3460
|
+
"enable",
|
|
3461
|
+
"execute",
|
|
3462
|
+
"export",
|
|
3463
|
+
"get",
|
|
3464
|
+
"import",
|
|
3465
|
+
"ingest",
|
|
3466
|
+
"invite",
|
|
3467
|
+
"list",
|
|
3468
|
+
"move",
|
|
3469
|
+
"preview",
|
|
3470
|
+
"publish",
|
|
3471
|
+
"read",
|
|
3472
|
+
"refresh",
|
|
3473
|
+
"remove",
|
|
3474
|
+
"restore",
|
|
3475
|
+
"retry",
|
|
3476
|
+
"review",
|
|
3477
|
+
"run",
|
|
3478
|
+
"scaffold",
|
|
3479
|
+
"search",
|
|
3480
|
+
"send",
|
|
3481
|
+
"set",
|
|
3482
|
+
"submit",
|
|
3483
|
+
"sync",
|
|
3484
|
+
"transfer",
|
|
3485
|
+
"unpublish",
|
|
3486
|
+
"update",
|
|
3487
|
+
"upload",
|
|
3488
|
+
"validate"
|
|
3489
|
+
]);
|
|
3490
|
+
var INVERSE_OPERATIONS = /* @__PURE__ */ new Map([
|
|
3491
|
+
["archive", "restore"],
|
|
3492
|
+
["create", "delete"],
|
|
3493
|
+
["disable", "enable"],
|
|
3494
|
+
["enable", "disable"],
|
|
3495
|
+
["publish", "unpublish"],
|
|
3496
|
+
["remove", "add"],
|
|
3497
|
+
["restore", "archive"],
|
|
3498
|
+
["unpublish", "publish"]
|
|
3499
|
+
]);
|
|
3500
|
+
function isRecord3(value) {
|
|
3501
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
3502
|
+
}
|
|
3503
|
+
function nonEmptyStrings(value) {
|
|
3504
|
+
return Array.isArray(value) && value.length > 0 && value.every((item) => typeof item === "string" && item.trim());
|
|
3505
|
+
}
|
|
3506
|
+
function slugify(value) {
|
|
3507
|
+
return String(value || "").trim().replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
3508
|
+
}
|
|
3509
|
+
function singularize(value) {
|
|
3510
|
+
if (value.endsWith("ies") && value.length > 3) return `${value.slice(0, -3)}y`;
|
|
3511
|
+
if (value.endsWith("sses")) return value.slice(0, -2);
|
|
3512
|
+
if (value.endsWith("s") && !value.endsWith("ss")) return value.slice(0, -1);
|
|
3513
|
+
return value || "resource";
|
|
3514
|
+
}
|
|
3515
|
+
function operationFor(action) {
|
|
3516
|
+
const tokens = String(action.name || "").split(/[._:-]+/).filter(Boolean);
|
|
3517
|
+
return [...tokens].reverse().find((token) => OPERATION_TOKENS.has(token)) || String(action.method || "GET").toLowerCase();
|
|
3518
|
+
}
|
|
3519
|
+
function resourceFor(action) {
|
|
3520
|
+
const nameTokens = String(action.name || "").split(/[._:-]+/).filter(Boolean);
|
|
3521
|
+
const named = nameTokens.find((token) => !OPERATION_TOKENS.has(token));
|
|
3522
|
+
if (named) return singularize(slugify(named).replaceAll("-", "_"));
|
|
3523
|
+
const pathToken = String(action.path || "").split("/").filter((token) => token && token !== "api" && !token.startsWith("{")).find((token) => !OPERATION_TOKENS.has(slugify(token)));
|
|
3524
|
+
return singularize(slugify(pathToken || "resource").replaceAll("-", "_"));
|
|
3525
|
+
}
|
|
3526
|
+
function isConcreteOutputSchema(schema) {
|
|
3527
|
+
if (!isRecord3(schema)) return false;
|
|
3528
|
+
if (typeof schema.$ref === "string" && schema.$ref.trim()) return true;
|
|
3529
|
+
if (schema.type === "array" && isRecord3(schema.items) && Object.keys(schema.items).length > 0) return true;
|
|
3530
|
+
if (isRecord3(schema.properties) && Object.keys(schema.properties).length > 0) return true;
|
|
3531
|
+
return ["oneOf", "anyOf", "allOf"].some((key) => Array.isArray(schema[key]) && schema[key].some(isConcreteOutputSchema));
|
|
3532
|
+
}
|
|
3533
|
+
function resourceSchema(resource) {
|
|
3534
|
+
return {
|
|
3535
|
+
type: "object",
|
|
3536
|
+
title: `${resource.replaceAll("_", " ")} record`,
|
|
3537
|
+
properties: {
|
|
3538
|
+
id: {
|
|
3539
|
+
type: "string",
|
|
3540
|
+
description: `Stable identifier when the ${resource.replaceAll("_", " ")} exposes one.`
|
|
3541
|
+
}
|
|
3542
|
+
},
|
|
3543
|
+
additionalProperties: true
|
|
3544
|
+
};
|
|
3545
|
+
}
|
|
3546
|
+
function derivedOutputSchema(action) {
|
|
3547
|
+
const resource = resourceFor(action);
|
|
3548
|
+
const operation = operationFor(action);
|
|
3549
|
+
const record = resourceSchema(resource);
|
|
3550
|
+
const records = { type: "array", items: record };
|
|
3551
|
+
const provenance = {
|
|
3552
|
+
"x-topolo-contract-source": "derived-action-metadata",
|
|
3553
|
+
"x-topolo-contract-precision": "structural"
|
|
3554
|
+
};
|
|
3555
|
+
if (operation === "list" || operation === "search") {
|
|
3556
|
+
return {
|
|
3557
|
+
title: `${action.title || action.name} response`,
|
|
3558
|
+
description: `Collection response returned by ${action.name}. The service may return the collection directly or in an envelope.`,
|
|
3559
|
+
oneOf: [
|
|
3560
|
+
records,
|
|
3561
|
+
{
|
|
3562
|
+
type: "object",
|
|
3563
|
+
properties: {
|
|
3564
|
+
data: { oneOf: [records, { type: "object", additionalProperties: true }] },
|
|
3565
|
+
items: records,
|
|
3566
|
+
[resource.endsWith("s") ? resource : `${resource}s`]: records,
|
|
3567
|
+
count: { type: "integer", minimum: 0 },
|
|
3568
|
+
nextCursor: { type: ["string", "null"] }
|
|
3569
|
+
},
|
|
3570
|
+
additionalProperties: true
|
|
3571
|
+
}
|
|
3572
|
+
],
|
|
3573
|
+
...provenance
|
|
3574
|
+
};
|
|
3575
|
+
}
|
|
3576
|
+
if (operation === "get" || operation === "read") {
|
|
3577
|
+
return {
|
|
3578
|
+
title: `${action.title || action.name} response`,
|
|
3579
|
+
description: `Resource response returned by ${action.name}. The service may return the resource directly or in an envelope.`,
|
|
3580
|
+
oneOf: [
|
|
3581
|
+
record,
|
|
3582
|
+
{
|
|
3583
|
+
type: "object",
|
|
3584
|
+
properties: {
|
|
3585
|
+
data: record,
|
|
3586
|
+
[resource]: record
|
|
3587
|
+
},
|
|
3588
|
+
additionalProperties: true
|
|
3589
|
+
}
|
|
3590
|
+
],
|
|
3591
|
+
...provenance
|
|
3592
|
+
};
|
|
3593
|
+
}
|
|
3594
|
+
return {
|
|
3595
|
+
type: "object",
|
|
3596
|
+
title: `${action.title || action.name} response`,
|
|
3597
|
+
description: `Operation result returned by ${action.name}.`,
|
|
3598
|
+
properties: {
|
|
3599
|
+
success: { type: "boolean" },
|
|
3600
|
+
id: { type: "string" },
|
|
3601
|
+
data: { oneOf: [record, { type: "object", additionalProperties: true }] },
|
|
3602
|
+
result: { type: "object", additionalProperties: true },
|
|
3603
|
+
message: { type: "string" }
|
|
3604
|
+
},
|
|
3605
|
+
additionalProperties: true,
|
|
3606
|
+
...provenance
|
|
3607
|
+
};
|
|
3608
|
+
}
|
|
3609
|
+
function sampleForSchema(schema, depth = 0) {
|
|
3610
|
+
if (!isRecord3(schema) || depth > 5) return null;
|
|
3611
|
+
if (schema.const !== void 0) return schema.const;
|
|
3612
|
+
if (Array.isArray(schema.enum) && schema.enum.length > 0) return schema.enum[0];
|
|
3613
|
+
if (Array.isArray(schema.oneOf) && schema.oneOf.length > 0) return sampleForSchema(schema.oneOf[0], depth + 1);
|
|
3614
|
+
if (Array.isArray(schema.anyOf) && schema.anyOf.length > 0) return sampleForSchema(schema.anyOf[0], depth + 1);
|
|
3615
|
+
if (schema.type === "array") return [];
|
|
3616
|
+
if (schema.type === "boolean") return false;
|
|
3617
|
+
if (schema.type === "integer" || schema.type === "number") return schema.minimum ?? 0;
|
|
3618
|
+
if (schema.type === "string") {
|
|
3619
|
+
if (schema.format === "email") return "agent@example.com";
|
|
3620
|
+
if (schema.format === "uri" || schema.format === "url") return "https://example.com";
|
|
3621
|
+
return schema.minLength > 1 ? "example".padEnd(schema.minLength, "x") : "example";
|
|
3622
|
+
}
|
|
3623
|
+
if (schema.type === "null") return null;
|
|
3624
|
+
if (schema.type === "object" || isRecord3(schema.properties)) {
|
|
3625
|
+
const properties = isRecord3(schema.properties) ? schema.properties : {};
|
|
3626
|
+
const required = new Set(Array.isArray(schema.required) ? schema.required : []);
|
|
3627
|
+
const entries = Object.entries(properties).filter(([key]) => required.has(key)).map(([key, child]) => [key, sampleForSchema(child, depth + 1)]);
|
|
3628
|
+
return Object.fromEntries(entries);
|
|
3629
|
+
}
|
|
3630
|
+
return null;
|
|
3631
|
+
}
|
|
3632
|
+
function actionPrefix(name, operation) {
|
|
3633
|
+
const tokens = String(name || "").split(/[._:-]+/).filter(Boolean);
|
|
3634
|
+
const index = tokens.lastIndexOf(operation);
|
|
3635
|
+
if (index === -1) return tokens.join(".");
|
|
3636
|
+
return tokens.filter((_, tokenIndex) => tokenIndex !== index).join(".");
|
|
3637
|
+
}
|
|
3638
|
+
function findInverseActionId(action, allActions, appId) {
|
|
3639
|
+
const operation = operationFor(action);
|
|
3640
|
+
if (operation === "update" || operation === "set" || operation === "move") {
|
|
3641
|
+
return `${appId}.${action.name}`;
|
|
3642
|
+
}
|
|
3643
|
+
const inverse = INVERSE_OPERATIONS.get(operation);
|
|
3644
|
+
if (!inverse) return null;
|
|
3645
|
+
const prefix = actionPrefix(action.name, operation);
|
|
3646
|
+
const match = allActions.find((candidate) => operationFor(candidate) === inverse && actionPrefix(candidate.name, inverse) === prefix);
|
|
3647
|
+
return match ? `${appId}.${match.name}` : null;
|
|
3648
|
+
}
|
|
3649
|
+
function recoveryFor(action, allActions, appId) {
|
|
3650
|
+
const readOnly = action.readOnly !== void 0 ? action.readOnly === true : action.method === "GET";
|
|
3651
|
+
if (readOnly) {
|
|
3652
|
+
return {
|
|
3653
|
+
mode: "not_applicable",
|
|
3654
|
+
instructions: "This action does not change service state."
|
|
3655
|
+
};
|
|
3656
|
+
}
|
|
3657
|
+
const actionId = findInverseActionId(action, allActions, appId);
|
|
3658
|
+
if (actionId) {
|
|
3659
|
+
return {
|
|
3660
|
+
mode: "compensating_action",
|
|
3661
|
+
actionId,
|
|
3662
|
+
requiresSnapshot: actionId === `${appId}.${action.name}`,
|
|
3663
|
+
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.`
|
|
3664
|
+
};
|
|
3665
|
+
}
|
|
3666
|
+
return {
|
|
3667
|
+
mode: "irreversible",
|
|
3668
|
+
instructions: "No safe automatic inverse is published. Confirm the change and retain any required export or snapshot before calling."
|
|
3669
|
+
};
|
|
3670
|
+
}
|
|
3671
|
+
function defaultErrors(action) {
|
|
3672
|
+
const errors = [
|
|
3673
|
+
{ status: 401, code: "authentication_required", recovery: "Authenticate again or provide a valid Topolo API key." },
|
|
3674
|
+
{ status: 403, code: "permission_denied", recovery: `Grant the credential permission ${action.permission || action.permissionName || "required by this action"} and the required resource binding.` },
|
|
3675
|
+
{ status: 422, code: "invalid_input", recovery: "Correct the payload using inputSchema, then validate before retrying." }
|
|
3676
|
+
];
|
|
3677
|
+
if (action.method !== "GET") {
|
|
3678
|
+
errors.push({ status: 409, code: "state_conflict", recovery: "Read the current resource state, reconcile the intended change, and retry only if still valid." });
|
|
3679
|
+
}
|
|
3680
|
+
return errors;
|
|
3681
|
+
}
|
|
3682
|
+
function enrichActionContract(manifest, action, allActions = manifest.actions || []) {
|
|
3683
|
+
const appId = String(manifest.appId || manifest.app_id || "").trim();
|
|
3684
|
+
const service = slugify(manifest.slug || appId.replace(/^app_/, "").replaceAll("_", "-") || manifest.name);
|
|
3685
|
+
const outputSchema = isConcreteOutputSchema(action.outputSchema) ? structuredClone(action.outputSchema) : derivedOutputSchema(action);
|
|
3686
|
+
const resource = resourceFor(action);
|
|
3687
|
+
const readOnly = action.readOnly !== void 0 ? action.readOnly === true : action.method === "GET";
|
|
3688
|
+
const existing = isRecord3(action.agentContract) ? structuredClone(action.agentContract) : {};
|
|
3689
|
+
const recovery = isRecord3(existing.recovery) ? existing.recovery : recoveryFor(action, allActions, appId);
|
|
3690
|
+
const rollbackActionId = typeof existing.rollbackActionId === "string" && existing.rollbackActionId.trim() ? existing.rollbackActionId.trim() : recovery.mode === "compensating_action" ? recovery.actionId : void 0;
|
|
3691
|
+
const inputExample = sampleForSchema(action.inputSchema);
|
|
3692
|
+
const examples = Array.isArray(existing.examples) && existing.examples.length > 0 ? existing.examples : [{ input: inputExample ?? {}, expected: `A response matching the published outputSchema for ${action.name}.` }];
|
|
3693
|
+
const docsUrl = `https://docs.topolo.app/reference/actions/?service=${encodeURIComponent(service)}&action=${encodeURIComponent(action.name)}`;
|
|
3694
|
+
const existingDocsUrl = typeof existing.docsUrl === "string" && existing.docsUrl.trim() ? existing.docsUrl.trim() : void 0;
|
|
3695
|
+
return {
|
|
3696
|
+
outputSchema,
|
|
3697
|
+
agentContract: {
|
|
3698
|
+
...existing,
|
|
3699
|
+
docsUrl,
|
|
3700
|
+
...existingDocsUrl && existingDocsUrl !== docsUrl ? { guideUrl: existingDocsUrl } : {},
|
|
3701
|
+
resource: isRecord3(existing.resource) ? existing.resource : { kind: resource, idPath: readOnly && ["list", "search"].includes(operationFor(action)) ? "$.items[].id" : "$.id" },
|
|
3702
|
+
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.`],
|
|
3703
|
+
errors: Array.isArray(existing.errors) && existing.errors.length > 0 ? existing.errors : defaultErrors(action),
|
|
3704
|
+
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"}.`],
|
|
3705
|
+
examples,
|
|
3706
|
+
recovery,
|
|
3707
|
+
...rollbackActionId ? { rollbackActionId } : {},
|
|
3708
|
+
contractSource: Object.hasOwn(outputSchema, "x-topolo-contract-source") ? "derived-action-metadata" : "application-declared"
|
|
3709
|
+
}
|
|
3710
|
+
};
|
|
3711
|
+
}
|
|
3712
|
+
function actionContractCompleteness(action) {
|
|
3713
|
+
const contract = action?.agentContract;
|
|
3714
|
+
return {
|
|
3715
|
+
inputSchema: isRecord3(action?.inputSchema),
|
|
3716
|
+
outputSchema: isConcreteOutputSchema(action?.outputSchema),
|
|
3717
|
+
effects: nonEmptyStrings(contract?.effects),
|
|
3718
|
+
errors: Array.isArray(contract?.errors) && contract.errors.length > 0,
|
|
3719
|
+
docs: typeof contract?.docsUrl === "string" && contract.docsUrl.startsWith("https://"),
|
|
3720
|
+
examples: Array.isArray(contract?.examples) && contract.examples.length > 0,
|
|
3721
|
+
verification: nonEmptyStrings(contract?.verify),
|
|
3722
|
+
recovery: isRecord3(contract?.recovery) && ["not_applicable", "compensating_action", "irreversible"].includes(contract.recovery.mode),
|
|
3723
|
+
agentContract: isRecord3(contract) && isRecord3(contract.resource) && nonEmptyStrings(contract.effects) && Array.isArray(contract.errors) && contract.errors.length > 0
|
|
3724
|
+
};
|
|
3725
|
+
}
|
|
3726
|
+
|
|
3399
3727
|
// src/modules/crm.ts
|
|
3400
3728
|
var CrmModule = class {
|
|
3401
3729
|
constructor(client) {
|
|
@@ -3797,7 +4125,7 @@ function createTopoloWidgetError(error) {
|
|
|
3797
4125
|
}
|
|
3798
4126
|
function validateTopoloWidgetResponse(response) {
|
|
3799
4127
|
const errors = [];
|
|
3800
|
-
if (!
|
|
4128
|
+
if (!isRecord4(response)) {
|
|
3801
4129
|
return { ok: false, errors: ["response must be an object"] };
|
|
3802
4130
|
}
|
|
3803
4131
|
if (typeof response.success !== "boolean") {
|
|
@@ -3816,7 +4144,7 @@ function validateTopoloWidgetResponse(response) {
|
|
|
3816
4144
|
return { ok: errors.length === 0, errors };
|
|
3817
4145
|
}
|
|
3818
4146
|
function validateWidget(widget, path, errors) {
|
|
3819
|
-
if (!
|
|
4147
|
+
if (!isRecord4(widget)) {
|
|
3820
4148
|
errors.push(`${path} must be an object`);
|
|
3821
4149
|
return;
|
|
3822
4150
|
}
|
|
@@ -3843,7 +4171,7 @@ function validateWidget(widget, path, errors) {
|
|
|
3843
4171
|
case "chart":
|
|
3844
4172
|
validateNonEmptyString(widget.chartType, `${path}.chartType`, errors);
|
|
3845
4173
|
validateNonEmptyString(widget.title, `${path}.title`, errors);
|
|
3846
|
-
if (!
|
|
4174
|
+
if (!isRecord4(widget.data)) {
|
|
3847
4175
|
errors.push(`${path}.data must be an object`);
|
|
3848
4176
|
} else {
|
|
3849
4177
|
validateArray2(widget.data.labels, `${path}.data.labels`, errors);
|
|
@@ -3859,7 +4187,7 @@ function validateSnapshotMetadata(snapshot, path, errors) {
|
|
|
3859
4187
|
errors.push(`${path} must be an object`);
|
|
3860
4188
|
return;
|
|
3861
4189
|
}
|
|
3862
|
-
if (!
|
|
4190
|
+
if (!isRecord4(snapshot)) {
|
|
3863
4191
|
errors.push(`${path} must be an object`);
|
|
3864
4192
|
return;
|
|
3865
4193
|
}
|
|
@@ -3879,7 +4207,7 @@ function validateSnapshotMetadata(snapshot, path, errors) {
|
|
|
3879
4207
|
}
|
|
3880
4208
|
snapshot.availableMetrics.forEach((metric, index) => {
|
|
3881
4209
|
const metricPath = `${path}.availableMetrics[${index}]`;
|
|
3882
|
-
if (!
|
|
4210
|
+
if (!isRecord4(metric)) {
|
|
3883
4211
|
errors.push(`${metricPath} must be an object`);
|
|
3884
4212
|
return;
|
|
3885
4213
|
}
|
|
@@ -3900,7 +4228,7 @@ function validateArray2(value, path, errors) {
|
|
|
3900
4228
|
errors.push(`${path} must be an array`);
|
|
3901
4229
|
}
|
|
3902
4230
|
}
|
|
3903
|
-
function
|
|
4231
|
+
function isRecord4(value) {
|
|
3904
4232
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3905
4233
|
}
|
|
3906
4234
|
function toIsoTimestamp(input) {
|
|
@@ -3937,6 +4265,7 @@ export {
|
|
|
3937
4265
|
TopoloOAuth,
|
|
3938
4266
|
TopoloPermissionError,
|
|
3939
4267
|
TopoloSdkError,
|
|
4268
|
+
actionContractCompleteness,
|
|
3940
4269
|
applicationRequirementScopes,
|
|
3941
4270
|
auditAllApplicationRequirements,
|
|
3942
4271
|
auditApplicationRequirements,
|
|
@@ -3949,6 +4278,7 @@ export {
|
|
|
3949
4278
|
defineTopoloRecentActivityWidget,
|
|
3950
4279
|
defineTopoloStatsWidget,
|
|
3951
4280
|
digestTopoloActionCatalog,
|
|
4281
|
+
enrichActionContract,
|
|
3952
4282
|
requirementsForApplication,
|
|
3953
4283
|
resolveCatalogServiceUrl,
|
|
3954
4284
|
resolveServiceUrl,
|