@topolo/sdk 0.7.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -3
- package/dist/index.cjs +258 -53
- package/dist/index.d.cts +76 -8
- package/dist/index.d.ts +76 -8
- package/dist/index.js +255 -53
- package/dist/node/credential-store.cjs +16 -5
- package/dist/node/credential-store.d.cts +3 -1
- package/dist/node/credential-store.d.ts +3 -1
- package/dist/node/credential-store.js +16 -5
- package/package.json +5 -4
- package/src/application-requirements.ts +0 -517
- package/src/applications.generated.ts +0 -1509
- package/src/auth.ts +0 -48
- package/src/client.test.ts +0 -894
- package/src/client.ts +0 -1025
- package/src/errors.ts +0 -39
- package/src/index.test.ts +0 -69
- package/src/index.ts +0 -125
- package/src/modules/crm.test.ts +0 -208
- package/src/modules/crm.ts +0 -110
- package/src/modules/identity.test.ts +0 -165
- package/src/modules/identity.ts +0 -18
- package/src/modules/p2p.test.ts +0 -540
- package/src/modules/p2p.ts +0 -448
- package/src/node/credential-store.ts +0 -378
- package/src/oauth.test.ts +0 -216
- package/src/oauth.ts +0 -138
- package/src/services.generated.ts +0 -55
- package/src/services.test.ts +0 -243
- package/src/services.ts +0 -138
- package/src/widgets.test.ts +0 -416
- package/src/widgets.ts +0 -362
package/README.md
CHANGED
|
@@ -55,6 +55,11 @@ accepts an `orgId` input. Every request derives the org from the credential —
|
|
|
55
55
|
a JWT `orgId` claim for access tokens, or the `organization_id` bound to an
|
|
56
56
|
API key at issuance.
|
|
57
57
|
|
|
58
|
+
OAuth context is credential-bound: refresh rotation preserves the token's
|
|
59
|
+
personal/org selection and never re-resolves it from the user's mutable web
|
|
60
|
+
preference. Separate CLI/MCP credentials can therefore remain in different
|
|
61
|
+
contexts concurrently.
|
|
62
|
+
|
|
58
63
|
This is enforced at every layer:
|
|
59
64
|
|
|
60
65
|
1. `@topolo/sdk` — no typed method takes an `orgId` arg.
|
|
@@ -114,9 +119,12 @@ generated snapshot. Do not use it to decide what an end user or agent may call.
|
|
|
114
119
|
`TopoloClient.listActions()` reads TopoloAuth's live action catalog for the
|
|
115
120
|
current credential. Applications publish their action definitions through
|
|
116
121
|
Topolo Developers, and Auth returns only actions whose existing service
|
|
117
|
-
permission is granted to the credential.
|
|
118
|
-
|
|
119
|
-
|
|
122
|
+
permission is granted to the credential. Actions with input-dependent policy
|
|
123
|
+
use `authorizationMode: runtime`; they remain typed and discoverable while the
|
|
124
|
+
target service evaluates the supplied organization, application, and resource
|
|
125
|
+
against current RBAC on every call. The action entry includes the service, HTTP
|
|
126
|
+
method/path, authorization mode, required permission when static, input/output
|
|
127
|
+
schemas, read-only and destructive hints, and the stable MCP/agent tool name.
|
|
120
128
|
|
|
121
129
|
`TopoloClient.callAction(actionRef, input, options)` resolves an action by
|
|
122
130
|
`actionId`, `name`, or `toolName`, maps path parameters and query/body values
|
package/dist/index.cjs
CHANGED
|
@@ -20,6 +20,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
ACTION_CATALOG_DIGEST_ALGORITHM: () => ACTION_CATALOG_DIGEST_ALGORITHM,
|
|
23
24
|
APPLICATIONS: () => APPLICATIONS,
|
|
24
25
|
APPLICATION_REQUIREMENTS: () => APPLICATION_REQUIREMENTS,
|
|
25
26
|
APPLICATION_REQUIREMENTS_VERSION: () => APPLICATION_REQUIREMENTS_VERSION,
|
|
@@ -31,6 +32,7 @@ __export(index_exports, {
|
|
|
31
32
|
TOPOLO_WIDGET_REFRESH_INTERVAL_MS: () => TOPOLO_WIDGET_REFRESH_INTERVAL_MS,
|
|
32
33
|
TopoloAuthError: () => TopoloAuthError,
|
|
33
34
|
TopoloClient: () => TopoloClient,
|
|
35
|
+
TopoloConfirmationRequiredError: () => TopoloConfirmationRequiredError,
|
|
34
36
|
TopoloHttpError: () => TopoloHttpError,
|
|
35
37
|
TopoloOAuth: () => TopoloOAuth,
|
|
36
38
|
TopoloPermissionError: () => TopoloPermissionError,
|
|
@@ -46,6 +48,7 @@ __export(index_exports, {
|
|
|
46
48
|
defineTopoloQuickActionsWidget: () => defineTopoloQuickActionsWidget,
|
|
47
49
|
defineTopoloRecentActivityWidget: () => defineTopoloRecentActivityWidget,
|
|
48
50
|
defineTopoloStatsWidget: () => defineTopoloStatsWidget,
|
|
51
|
+
digestTopoloActionCatalog: () => digestTopoloActionCatalog,
|
|
49
52
|
requirementsForApplication: () => requirementsForApplication,
|
|
50
53
|
resolveCatalogServiceUrl: () => resolveCatalogServiceUrl,
|
|
51
54
|
resolveServiceUrl: () => resolveServiceUrl,
|
|
@@ -96,6 +99,12 @@ var TopoloPermissionError = class extends TopoloSdkError {
|
|
|
96
99
|
this.required = required;
|
|
97
100
|
}
|
|
98
101
|
};
|
|
102
|
+
var TopoloConfirmationRequiredError = class extends TopoloSdkError {
|
|
103
|
+
constructor(message) {
|
|
104
|
+
super("confirmation_required", message);
|
|
105
|
+
this.name = "TopoloConfirmationRequiredError";
|
|
106
|
+
}
|
|
107
|
+
};
|
|
99
108
|
var TopoloHttpError = class extends TopoloSdkError {
|
|
100
109
|
status;
|
|
101
110
|
body;
|
|
@@ -1162,6 +1171,7 @@ var APPLICATIONS = {
|
|
|
1162
1171
|
"topolo.io"
|
|
1163
1172
|
],
|
|
1164
1173
|
"routes": [
|
|
1174
|
+
"one.topolo.io",
|
|
1165
1175
|
"topolo.io",
|
|
1166
1176
|
"www.topolo.io"
|
|
1167
1177
|
],
|
|
@@ -2121,7 +2131,6 @@ var TOPOLO_RESOURCE_TYPE_HEADER = "X-Topolo-Resource-Type";
|
|
|
2121
2131
|
var TOPOLO_RESOURCE_ID_HEADER = "X-Topolo-Resource-ID";
|
|
2122
2132
|
var POSITIVE_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
2123
2133
|
var NEGATIVE_CACHE_TTL_MS = 60 * 1e3;
|
|
2124
|
-
var CATALOG_CACHE_TTL_MS = 60 * 1e3;
|
|
2125
2134
|
var TopoloClient = class _TopoloClient {
|
|
2126
2135
|
credential;
|
|
2127
2136
|
agent;
|
|
@@ -2130,9 +2139,9 @@ var TopoloClient = class _TopoloClient {
|
|
|
2130
2139
|
requireConfirmForWrites;
|
|
2131
2140
|
timeoutMs;
|
|
2132
2141
|
fetchImpl;
|
|
2142
|
+
refreshCredential;
|
|
2143
|
+
refreshInFlight = null;
|
|
2133
2144
|
debug;
|
|
2134
|
-
serviceCatalogCache = null;
|
|
2135
|
-
actionCatalogCache = null;
|
|
2136
2145
|
/**
|
|
2137
2146
|
* Process-wide cache for slug → app-id lookups. Keyed by
|
|
2138
2147
|
* `${authBaseUrl}|${slug}` so multiple `TopoloClient` instances pointing at
|
|
@@ -2157,6 +2166,7 @@ var TopoloClient = class _TopoloClient {
|
|
|
2157
2166
|
this.requireConfirmForWrites = options.requireConfirmForWrites !== false;
|
|
2158
2167
|
this.timeoutMs = options.timeoutMs ?? 3e4;
|
|
2159
2168
|
this.fetchImpl = options.fetch ?? fetch;
|
|
2169
|
+
this.refreshCredential = options.refreshCredential;
|
|
2160
2170
|
this.debug = options.debug;
|
|
2161
2171
|
}
|
|
2162
2172
|
emit(event) {
|
|
@@ -2174,11 +2184,11 @@ var TopoloClient = class _TopoloClient {
|
|
|
2174
2184
|
async request(opts) {
|
|
2175
2185
|
const method = opts.method ?? "GET";
|
|
2176
2186
|
if (this.requireConfirmForWrites && WRITE_METHODS.has(method) && !opts.confirm) {
|
|
2177
|
-
throw new
|
|
2187
|
+
throw new TopoloConfirmationRequiredError(
|
|
2178
2188
|
`Mutating ${method} requests require { confirm: true }. This guardrail prevents agents from issuing writes without an explicit human-in-the-loop acknowledgement.`
|
|
2179
2189
|
);
|
|
2180
2190
|
}
|
|
2181
|
-
const resolvedService = await this.resolveRequestService(opts.service);
|
|
2191
|
+
const resolvedService = opts.baseUrl ? { serviceKey: normalizeLookupKey(opts.service), baseUrl: requireHttpOrigin(opts.baseUrl) } : await this.resolveRequestService(opts.service);
|
|
2182
2192
|
const baseUrl = resolvedService.baseUrl;
|
|
2183
2193
|
const url = new URL(opts.path, baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`);
|
|
2184
2194
|
if (opts.query) {
|
|
@@ -2209,20 +2219,36 @@ var TopoloClient = class _TopoloClient {
|
|
|
2209
2219
|
headers.set(TOPOLO_RESOURCE_TYPE_HEADER, resourceType);
|
|
2210
2220
|
headers.set(TOPOLO_RESOURCE_ID_HEADER, resourceId);
|
|
2211
2221
|
}
|
|
2212
|
-
const controller = new AbortController();
|
|
2213
|
-
const timeoutId = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
2214
|
-
const signal = opts.signal ? mergeSignals(opts.signal, controller.signal) : controller.signal;
|
|
2215
2222
|
const urlStr = url.toString();
|
|
2216
2223
|
const startedAt = Date.now();
|
|
2217
2224
|
this.emit({ phase: "request", method, service: resolvedService.serviceKey, path: opts.path, url: urlStr, requestId });
|
|
2225
|
+
const fetchOnce = async () => {
|
|
2226
|
+
const controller = new AbortController();
|
|
2227
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
2228
|
+
const signal = opts.signal ? mergeSignals(opts.signal, controller.signal) : controller.signal;
|
|
2229
|
+
try {
|
|
2230
|
+
return await this.fetchImpl(urlStr, {
|
|
2231
|
+
method,
|
|
2232
|
+
headers: new Headers(headers),
|
|
2233
|
+
body: opts.body !== void 0 ? JSON.stringify(opts.body) : null,
|
|
2234
|
+
signal
|
|
2235
|
+
});
|
|
2236
|
+
} finally {
|
|
2237
|
+
clearTimeout(timeoutId);
|
|
2238
|
+
}
|
|
2239
|
+
};
|
|
2218
2240
|
let res;
|
|
2219
2241
|
try {
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2242
|
+
const attemptedCredential = this.credential;
|
|
2243
|
+
res = await fetchOnce();
|
|
2244
|
+
if (res.status === 401 && attemptedCredential.kind === "access_token" && this.refreshCredential) {
|
|
2245
|
+
await res.body?.cancel().catch(() => void 0);
|
|
2246
|
+
const refreshed = await this.refreshRejectedAccessToken(attemptedCredential.accessToken);
|
|
2247
|
+
if (refreshed) {
|
|
2248
|
+
applyAuthHeaders(headers, refreshed);
|
|
2249
|
+
res = await fetchOnce();
|
|
2250
|
+
}
|
|
2251
|
+
}
|
|
2226
2252
|
} catch (err) {
|
|
2227
2253
|
this.emit({
|
|
2228
2254
|
phase: "error",
|
|
@@ -2235,8 +2261,6 @@ var TopoloClient = class _TopoloClient {
|
|
|
2235
2261
|
error: err instanceof Error ? err.message : String(err)
|
|
2236
2262
|
});
|
|
2237
2263
|
throw err;
|
|
2238
|
-
} finally {
|
|
2239
|
-
clearTimeout(timeoutId);
|
|
2240
2264
|
}
|
|
2241
2265
|
const contentType = res.headers.get("Content-Type") ?? "";
|
|
2242
2266
|
const parsed = contentType.includes("application/json") ? await res.json().catch(() => null) : await res.text().catch(() => null);
|
|
@@ -2275,6 +2299,19 @@ var TopoloClient = class _TopoloClient {
|
|
|
2275
2299
|
});
|
|
2276
2300
|
return parsed;
|
|
2277
2301
|
}
|
|
2302
|
+
async refreshRejectedAccessToken(attemptedAccessToken) {
|
|
2303
|
+
if (this.credential.kind === "access_token" && this.credential.accessToken !== attemptedAccessToken) {
|
|
2304
|
+
return this.credential;
|
|
2305
|
+
}
|
|
2306
|
+
if (!this.refreshCredential) return null;
|
|
2307
|
+
this.refreshInFlight ??= this.refreshCredential().then((credential) => {
|
|
2308
|
+
if (credential) this.credential = credential;
|
|
2309
|
+
return credential;
|
|
2310
|
+
}).catch(() => null).finally(() => {
|
|
2311
|
+
this.refreshInFlight = null;
|
|
2312
|
+
});
|
|
2313
|
+
return this.refreshInFlight;
|
|
2314
|
+
}
|
|
2278
2315
|
async resolveRequestService(service) {
|
|
2279
2316
|
const serviceKey = normalizeLookupKey(service);
|
|
2280
2317
|
if (!serviceKey) {
|
|
@@ -2300,9 +2337,8 @@ var TopoloClient = class _TopoloClient {
|
|
|
2300
2337
|
);
|
|
2301
2338
|
}
|
|
2302
2339
|
async resolveLiveCallableService(serviceKey) {
|
|
2303
|
-
const
|
|
2304
|
-
|
|
2305
|
-
if (!match) return null;
|
|
2340
|
+
const match = await this.getService(serviceKey).catch(() => null);
|
|
2341
|
+
if (!match || !catalogEntryMatches(match, serviceKey)) return null;
|
|
2306
2342
|
const baseUrl = resolveCatalogServiceUrl(match, {
|
|
2307
2343
|
serviceKey,
|
|
2308
2344
|
overrides: this.serviceUrls
|
|
@@ -2376,12 +2412,13 @@ var TopoloClient = class _TopoloClient {
|
|
|
2376
2412
|
* The live Auth catalog is the authority because it is scoped to the caller's
|
|
2377
2413
|
* organization and effective permissions.
|
|
2378
2414
|
*/
|
|
2379
|
-
async
|
|
2380
|
-
const
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2415
|
+
async searchServices(options = {}) {
|
|
2416
|
+
const res = await this.request({
|
|
2417
|
+
service: "developers",
|
|
2418
|
+
method: "GET",
|
|
2419
|
+
path: "/api/services/catalog",
|
|
2420
|
+
query: catalogSearchQuery(options)
|
|
2421
|
+
});
|
|
2385
2422
|
const raw = res.data?.services ?? res.services;
|
|
2386
2423
|
if (!Array.isArray(raw)) {
|
|
2387
2424
|
throw new TopoloSdkError(
|
|
@@ -2391,6 +2428,7 @@ var TopoloClient = class _TopoloClient {
|
|
|
2391
2428
|
}
|
|
2392
2429
|
const entries = [];
|
|
2393
2430
|
for (const item of raw) {
|
|
2431
|
+
if (!isRecord(item)) continue;
|
|
2394
2432
|
const s = item;
|
|
2395
2433
|
const appId = typeof s.app_id === "string" ? s.app_id : typeof s.id === "string" ? s.id : null;
|
|
2396
2434
|
if (!appId) continue;
|
|
@@ -2415,11 +2453,30 @@ var TopoloClient = class _TopoloClient {
|
|
|
2415
2453
|
apiKeyResources
|
|
2416
2454
|
});
|
|
2417
2455
|
}
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
};
|
|
2422
|
-
|
|
2456
|
+
const page = normalizeCatalogPage(
|
|
2457
|
+
res.data?.page ?? res.page
|
|
2458
|
+
);
|
|
2459
|
+
return { services: entries, page };
|
|
2460
|
+
}
|
|
2461
|
+
/** @deprecated Prefer searchServices() so callers retain the continuation cursor. */
|
|
2462
|
+
async listServices(options = {}) {
|
|
2463
|
+
return (await this.searchServices(options)).services;
|
|
2464
|
+
}
|
|
2465
|
+
async getService(application) {
|
|
2466
|
+
const needle = application.trim();
|
|
2467
|
+
if (!needle) throw new TopoloSdkError("unknown_service", "application is required");
|
|
2468
|
+
const res = await this.request({
|
|
2469
|
+
service: "developers",
|
|
2470
|
+
method: "GET",
|
|
2471
|
+
path: "/api/services/catalog",
|
|
2472
|
+
query: { application: needle }
|
|
2473
|
+
});
|
|
2474
|
+
const raw = res.data?.application ?? res.application;
|
|
2475
|
+
const entry = normalizeServiceCatalogEntry(raw);
|
|
2476
|
+
if (!entry) {
|
|
2477
|
+
throw new TopoloSdkError("unknown_service", `Unknown Topolo application "${application}".`);
|
|
2478
|
+
}
|
|
2479
|
+
return entry;
|
|
2423
2480
|
}
|
|
2424
2481
|
/**
|
|
2425
2482
|
* List callable actions from the live registry-canonical catalog (definitions
|
|
@@ -2427,16 +2484,15 @@ var TopoloClient = class _TopoloClient {
|
|
|
2427
2484
|
* caller's organization and effective permissions; no bundled/static app
|
|
2428
2485
|
* action list is used as an authorization source.
|
|
2429
2486
|
*/
|
|
2430
|
-
async
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
return this.actionCatalogCache.entries;
|
|
2487
|
+
async searchActions(options) {
|
|
2488
|
+
if (!options.service?.trim()) {
|
|
2489
|
+
throw new TopoloSdkError("service_required", "service is required for bounded action discovery");
|
|
2434
2490
|
}
|
|
2435
2491
|
const res = await this.request({
|
|
2436
2492
|
service: "developers",
|
|
2437
2493
|
method: "GET",
|
|
2438
2494
|
path: "/api/actions/catalog",
|
|
2439
|
-
|
|
2495
|
+
query: { service: options.service, ...catalogSearchQuery(options) }
|
|
2440
2496
|
});
|
|
2441
2497
|
const raw = res.data?.actions ?? res.actions;
|
|
2442
2498
|
if (!Array.isArray(raw)) {
|
|
@@ -2446,36 +2502,96 @@ var TopoloClient = class _TopoloClient {
|
|
|
2446
2502
|
);
|
|
2447
2503
|
}
|
|
2448
2504
|
const entries = raw.map((item) => normalizeActionCatalogEntry(item)).filter((entry) => Boolean(entry));
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2505
|
+
return {
|
|
2506
|
+
actions: entries,
|
|
2507
|
+
page: normalizeCatalogPage(
|
|
2508
|
+
res.data?.page ?? res.page
|
|
2509
|
+
)
|
|
2510
|
+
};
|
|
2511
|
+
}
|
|
2512
|
+
async getActionCatalogSummary(service) {
|
|
2513
|
+
const boundedService = service.trim();
|
|
2514
|
+
if (!boundedService) {
|
|
2515
|
+
throw new TopoloSdkError("service_required", "service is required for bounded action discovery");
|
|
2454
2516
|
}
|
|
2455
|
-
|
|
2517
|
+
const res = await this.request({
|
|
2518
|
+
service: "developers",
|
|
2519
|
+
method: "GET",
|
|
2520
|
+
path: "/api/actions/catalog",
|
|
2521
|
+
query: { service: boundedService, summary: "true" }
|
|
2522
|
+
});
|
|
2523
|
+
const raw = res.data?.summary ?? res.summary;
|
|
2524
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
2525
|
+
throw new TopoloSdkError("action_catalog_summary_unavailable", "Action catalog summary is unavailable.");
|
|
2526
|
+
}
|
|
2527
|
+
const record = raw;
|
|
2528
|
+
if (record.algorithm !== "sha256-v1" || typeof record.digest !== "string" || !/^[a-f0-9]{64}$/.test(record.digest) || !Number.isSafeInteger(record.actionCount) || Number(record.actionCount) < 0) {
|
|
2529
|
+
throw new TopoloSdkError("action_catalog_summary_unavailable", "Action catalog summary is invalid.");
|
|
2530
|
+
}
|
|
2531
|
+
return {
|
|
2532
|
+
algorithm: "sha256-v1",
|
|
2533
|
+
digest: record.digest,
|
|
2534
|
+
actionCount: Number(record.actionCount)
|
|
2535
|
+
};
|
|
2536
|
+
}
|
|
2537
|
+
/** @deprecated Prefer searchActions() so callers retain the continuation cursor. */
|
|
2538
|
+
async listActions(options) {
|
|
2539
|
+
if (!options) {
|
|
2540
|
+
throw new TopoloSdkError("service_required", "service is required for bounded action discovery");
|
|
2541
|
+
}
|
|
2542
|
+
return (await this.searchActions(options)).actions;
|
|
2456
2543
|
}
|
|
2457
2544
|
async getAction(action) {
|
|
2458
|
-
const
|
|
2459
|
-
|
|
2460
|
-
const match = actions.find((entry) => actionEntryMatches(entry, needle));
|
|
2461
|
-
if (!match) {
|
|
2545
|
+
const actionId = action.trim();
|
|
2546
|
+
if (!actionId) {
|
|
2462
2547
|
throw new TopoloSdkError(
|
|
2463
2548
|
"unknown_action",
|
|
2464
|
-
|
|
2549
|
+
"action id is required"
|
|
2465
2550
|
);
|
|
2466
2551
|
}
|
|
2552
|
+
let res;
|
|
2553
|
+
try {
|
|
2554
|
+
res = await this.request({
|
|
2555
|
+
service: "developers",
|
|
2556
|
+
method: "GET",
|
|
2557
|
+
path: "/api/actions/catalog",
|
|
2558
|
+
query: { actionId }
|
|
2559
|
+
});
|
|
2560
|
+
} catch (error) {
|
|
2561
|
+
if (error instanceof TopoloHttpError && error.status === 404) {
|
|
2562
|
+
throw new TopoloSdkError("unknown_action", `Unknown or unavailable Topolo action "${action}".`);
|
|
2563
|
+
}
|
|
2564
|
+
throw error;
|
|
2565
|
+
}
|
|
2566
|
+
const raw = res.data?.action ?? res.action;
|
|
2567
|
+
const match = normalizeActionCatalogEntry(raw);
|
|
2568
|
+
if (!match) throw new TopoloSdkError("unknown_action", `Unknown or unavailable Topolo action "${action}".`);
|
|
2467
2569
|
return match;
|
|
2468
2570
|
}
|
|
2469
2571
|
async callAction(action, input = {}, options = {}) {
|
|
2470
2572
|
const entry = await this.getAction(action);
|
|
2573
|
+
const agentAccess = entry.agentAccess ?? (entry.destructive ? "off" : entry.readOnly && !entry.requiresConfirmation ? "auto" : "confirm");
|
|
2574
|
+
if (agentAccess === "off") {
|
|
2575
|
+
throw new TopoloPermissionError(
|
|
2576
|
+
`Topolo action "${entry.actionId}" is not available for agent invocation.`,
|
|
2577
|
+
entry.requiredPermission ? [entry.requiredPermission] : []
|
|
2578
|
+
);
|
|
2579
|
+
}
|
|
2580
|
+
if ((agentAccess === "confirm" || entry.requiresConfirmation || entry.destructive) && options.confirm !== true) {
|
|
2581
|
+
throw new TopoloConfirmationRequiredError(
|
|
2582
|
+
`Topolo action "${entry.actionId}" requires { confirm: true } before invocation.`
|
|
2583
|
+
);
|
|
2584
|
+
}
|
|
2471
2585
|
const mapped = mapActionInput(entry, input);
|
|
2472
2586
|
const service = entry.appSlug || entry.appId;
|
|
2473
2587
|
return this.request({
|
|
2474
2588
|
service,
|
|
2589
|
+
...entry.apiBaseUrl ? { baseUrl: entry.apiBaseUrl } : {},
|
|
2475
2590
|
method: entry.method,
|
|
2476
2591
|
path: mapped.path,
|
|
2477
2592
|
...mapped.query ? { query: mapped.query } : {},
|
|
2478
2593
|
...mapped.body !== void 0 ? { body: mapped.body } : {},
|
|
2594
|
+
...options.resource ? { resource: options.resource } : {},
|
|
2479
2595
|
headers: {
|
|
2480
2596
|
[TOPOLO_ACTION_ID_HEADER]: entry.actionId,
|
|
2481
2597
|
[TOPOLO_ACTION_NAME_HEADER]: entry.name
|
|
@@ -2613,6 +2729,18 @@ function serviceKeyCandidates2(slug) {
|
|
|
2613
2729
|
function normalizeLookupKey(value) {
|
|
2614
2730
|
return String(value || "").trim().toLowerCase().replace(/\s+/g, "-");
|
|
2615
2731
|
}
|
|
2732
|
+
function requireHttpOrigin(value) {
|
|
2733
|
+
let url;
|
|
2734
|
+
try {
|
|
2735
|
+
url = new URL(value);
|
|
2736
|
+
} catch {
|
|
2737
|
+
throw new TopoloSdkError("invalid_action_target", `Invalid action API origin "${value}".`);
|
|
2738
|
+
}
|
|
2739
|
+
if (url.protocol !== "https:" && url.protocol !== "http:" || url.username || url.password) {
|
|
2740
|
+
throw new TopoloSdkError("invalid_action_target", `Invalid action API origin "${value}".`);
|
|
2741
|
+
}
|
|
2742
|
+
return url.origin;
|
|
2743
|
+
}
|
|
2616
2744
|
function deriveDevelopersFromAuth(authUrl) {
|
|
2617
2745
|
if (!authUrl) return null;
|
|
2618
2746
|
try {
|
|
@@ -2639,6 +2767,47 @@ function catalogEntryLookupKeys(entry) {
|
|
|
2639
2767
|
}
|
|
2640
2768
|
return [...keys];
|
|
2641
2769
|
}
|
|
2770
|
+
function catalogSearchQuery(options) {
|
|
2771
|
+
const query = {};
|
|
2772
|
+
if (typeof options.query === "string" && options.query.trim()) query.query = options.query.trim();
|
|
2773
|
+
if (typeof options.cursor === "string" && options.cursor.trim()) query.cursor = options.cursor.trim();
|
|
2774
|
+
if (typeof options.limit === "number" && Number.isFinite(options.limit)) query.limit = Math.trunc(options.limit);
|
|
2775
|
+
return query;
|
|
2776
|
+
}
|
|
2777
|
+
function normalizeCatalogPage(value) {
|
|
2778
|
+
if (!isRecord(value)) return { nextCursor: null, hasMore: false };
|
|
2779
|
+
return {
|
|
2780
|
+
nextCursor: typeof value.nextCursor === "string" && value.nextCursor ? value.nextCursor : null,
|
|
2781
|
+
hasMore: value.hasMore === true
|
|
2782
|
+
};
|
|
2783
|
+
}
|
|
2784
|
+
function normalizeServiceCatalogEntry(item) {
|
|
2785
|
+
if (!isRecord(item)) return null;
|
|
2786
|
+
const appId = typeof item.app_id === "string" ? item.app_id : typeof item.id === "string" ? item.id : null;
|
|
2787
|
+
if (!appId) return null;
|
|
2788
|
+
const permissions = Array.isArray(item.granted_permissions) ? item.granted_permissions.filter(
|
|
2789
|
+
(permission) => typeof permission === "string" && permission.trim().length > 0
|
|
2790
|
+
) : [];
|
|
2791
|
+
const catalogApiKeyResources = normalizeApiKeyResourceMetadataList(
|
|
2792
|
+
item.api_key_resources ?? item.apiKeyResources
|
|
2793
|
+
);
|
|
2794
|
+
const apiKeyResource = normalizeApiKeyResourceMetadata(item.api_key_resource ?? item.apiKeyResource) ?? catalogApiKeyResources[0] ?? null;
|
|
2795
|
+
const apiKeyResources = catalogApiKeyResources.length > 0 ? catalogApiKeyResources : apiKeyResource ? [apiKeyResource] : [];
|
|
2796
|
+
return {
|
|
2797
|
+
appId,
|
|
2798
|
+
slug: typeof item.app_slug === "string" ? item.app_slug : null,
|
|
2799
|
+
name: typeof item.name === "string" ? item.name : null,
|
|
2800
|
+
description: typeof item.description === "string" ? item.description : null,
|
|
2801
|
+
apiBaseUrl: typeof item.api_base_url === "string" ? item.api_base_url : typeof item.apiBaseUrl === "string" ? item.apiBaseUrl : null,
|
|
2802
|
+
launchUrl: typeof item.launch_url === "string" ? item.launch_url : typeof item.launchUrl === "string" ? item.launchUrl : null,
|
|
2803
|
+
status: typeof item.status === "string" ? item.status : null,
|
|
2804
|
+
surfaceKind: typeof item.surface_kind === "string" ? item.surface_kind : null,
|
|
2805
|
+
launchable: item.launchable === true,
|
|
2806
|
+
permissions,
|
|
2807
|
+
apiKeyResource,
|
|
2808
|
+
apiKeyResources
|
|
2809
|
+
};
|
|
2810
|
+
}
|
|
2642
2811
|
function normalizeActionCatalogEntry(item) {
|
|
2643
2812
|
if (!item || typeof item !== "object" || Array.isArray(item)) return null;
|
|
2644
2813
|
const a = item;
|
|
@@ -2652,6 +2821,8 @@ function normalizeActionCatalogEntry(item) {
|
|
|
2652
2821
|
}
|
|
2653
2822
|
return {
|
|
2654
2823
|
actionId,
|
|
2824
|
+
target: pickActionString(a.target_key) ?? pickActionString(a.target) ?? null,
|
|
2825
|
+
apiBaseUrl: pickActionString(a.api_base_url) ?? pickActionString(a.apiBaseUrl) ?? null,
|
|
2655
2826
|
name,
|
|
2656
2827
|
toolName: pickActionString(a.tool_name) ?? pickActionString(a.toolName) ?? null,
|
|
2657
2828
|
title: pickActionString(a.title) ?? name,
|
|
@@ -2663,6 +2834,7 @@ function normalizeActionCatalogEntry(item) {
|
|
|
2663
2834
|
path,
|
|
2664
2835
|
permissionName: pickActionString(a.permission_name) ?? pickActionString(a.permissionName) ?? "",
|
|
2665
2836
|
requiredPermission: pickActionString(a.required_permission) ?? pickActionString(a.requiredPermission) ?? "",
|
|
2837
|
+
authorizationMode: pickAuthorizationMode(a.authorization_mode ?? a.authorizationMode),
|
|
2666
2838
|
inputSchema: plainObject(a.input_schema ?? a.inputSchema),
|
|
2667
2839
|
outputSchema: plainObject(a.output_schema ?? a.outputSchema),
|
|
2668
2840
|
readOnly: Boolean(a.read_only ?? a.readOnly),
|
|
@@ -2682,16 +2854,12 @@ function pickActionString(value) {
|
|
|
2682
2854
|
function pickAgentAccess(value) {
|
|
2683
2855
|
return value === "auto" || value === "confirm" || value === "off" ? value : null;
|
|
2684
2856
|
}
|
|
2857
|
+
function pickAuthorizationMode(value) {
|
|
2858
|
+
return value === "runtime" ? "runtime" : "static";
|
|
2859
|
+
}
|
|
2685
2860
|
function plainObject(value) {
|
|
2686
2861
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
2687
2862
|
}
|
|
2688
|
-
function normalizeActionLookupKey(value) {
|
|
2689
|
-
return String(value || "").trim().toLowerCase();
|
|
2690
|
-
}
|
|
2691
|
-
function actionEntryMatches(entry, needle) {
|
|
2692
|
-
if (!needle) return false;
|
|
2693
|
-
return [entry.actionId, entry.name, entry.toolName].filter((value) => typeof value === "string" && value.length > 0).some((value) => value.toLowerCase() === needle);
|
|
2694
|
-
}
|
|
2695
2863
|
function mapActionInput(action, input) {
|
|
2696
2864
|
const remaining = { ...input || {} };
|
|
2697
2865
|
const path = action.path.replace(/\{([a-zA-Z0-9_]+)\}/g, (_match, key) => {
|
|
@@ -2770,6 +2938,40 @@ function mergeSignals(a, b) {
|
|
|
2770
2938
|
return controller.signal;
|
|
2771
2939
|
}
|
|
2772
2940
|
|
|
2941
|
+
// src/action-catalog-digest.ts
|
|
2942
|
+
var ACTION_CATALOG_DIGEST_ALGORITHM = "sha256-v1";
|
|
2943
|
+
function canonicalPermission(value, appId) {
|
|
2944
|
+
let permission = String(value || "").trim().toLowerCase().replace(/\s+/g, "");
|
|
2945
|
+
const prefix = `${String(appId || "").trim().toLowerCase()}.`;
|
|
2946
|
+
if (prefix !== "." && permission.startsWith(prefix)) permission = permission.slice(prefix.length);
|
|
2947
|
+
return permission.replace(/\./g, ":");
|
|
2948
|
+
}
|
|
2949
|
+
function stableJson(value) {
|
|
2950
|
+
if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
|
|
2951
|
+
if (value && typeof value === "object") {
|
|
2952
|
+
const record = value;
|
|
2953
|
+
return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableJson(record[key])}`).join(",")}}`;
|
|
2954
|
+
}
|
|
2955
|
+
return JSON.stringify(value);
|
|
2956
|
+
}
|
|
2957
|
+
async function digestTopoloActionCatalog(actions, appId) {
|
|
2958
|
+
const canonical = actions.filter((action) => action.agentAccess !== "off").map((action) => ({
|
|
2959
|
+
actionId: action.actionId || `${appId}.${action.name}`,
|
|
2960
|
+
authorizationMode: action.authorizationMode === "runtime" ? "runtime" : "static",
|
|
2961
|
+
inputSchema: action.inputSchema,
|
|
2962
|
+
method: action.method,
|
|
2963
|
+
outputSchema: action.outputSchema,
|
|
2964
|
+
path: action.path,
|
|
2965
|
+
permission: canonicalPermission(action.permission ?? action.permissionName ?? "", appId),
|
|
2966
|
+
readOnly: action.readOnly,
|
|
2967
|
+
requiresConfirmation: action.requiresConfirmation,
|
|
2968
|
+
target: action.target ?? ""
|
|
2969
|
+
})).sort((left, right) => left.actionId.localeCompare(right.actionId));
|
|
2970
|
+
const bytes = new TextEncoder().encode(canonical.map(stableJson).join("\n"));
|
|
2971
|
+
const digest = await crypto.subtle.digest("SHA-256", bytes);
|
|
2972
|
+
return [...new Uint8Array(digest)].map((value) => value.toString(16).padStart(2, "0")).join("");
|
|
2973
|
+
}
|
|
2974
|
+
|
|
2773
2975
|
// src/modules/crm.ts
|
|
2774
2976
|
var CrmModule = class {
|
|
2775
2977
|
constructor(client) {
|
|
@@ -3295,6 +3497,7 @@ function createTopolo(options) {
|
|
|
3295
3497
|
}
|
|
3296
3498
|
// Annotate the CommonJS export names for ESM import in node:
|
|
3297
3499
|
0 && (module.exports = {
|
|
3500
|
+
ACTION_CATALOG_DIGEST_ALGORITHM,
|
|
3298
3501
|
APPLICATIONS,
|
|
3299
3502
|
APPLICATION_REQUIREMENTS,
|
|
3300
3503
|
APPLICATION_REQUIREMENTS_VERSION,
|
|
@@ -3306,6 +3509,7 @@ function createTopolo(options) {
|
|
|
3306
3509
|
TOPOLO_WIDGET_REFRESH_INTERVAL_MS,
|
|
3307
3510
|
TopoloAuthError,
|
|
3308
3511
|
TopoloClient,
|
|
3512
|
+
TopoloConfirmationRequiredError,
|
|
3309
3513
|
TopoloHttpError,
|
|
3310
3514
|
TopoloOAuth,
|
|
3311
3515
|
TopoloPermissionError,
|
|
@@ -3321,6 +3525,7 @@ function createTopolo(options) {
|
|
|
3321
3525
|
defineTopoloQuickActionsWidget,
|
|
3322
3526
|
defineTopoloRecentActivityWidget,
|
|
3323
3527
|
defineTopoloStatsWidget,
|
|
3528
|
+
digestTopoloActionCatalog,
|
|
3324
3529
|
requirementsForApplication,
|
|
3325
3530
|
resolveCatalogServiceUrl,
|
|
3326
3531
|
resolveServiceUrl,
|