@topolo/sdk 0.6.0 → 0.8.0
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 +187 -53
- package/dist/index.d.cts +52 -8
- package/dist/index.d.ts +52 -8
- package/dist/index.js +186 -53
- package/package.json +1 -2
- package/src/applications.generated.ts +1 -0
- package/src/client.test.ts +294 -31
- package/src/client.ts +280 -59
- package/src/errors.ts +7 -0
- package/src/index.ts +1 -0
- package/src/modules/crm.test.ts +1 -3
- package/src/modules/p2p.test.ts +1 -3
- package/src/node/credential-store.test.ts +337 -0
- package/src/node/credential-store.ts +86 -5
- package/dist/node/credential-store.cjs +0 -440
- package/dist/node/credential-store.d.cts +0 -81
- package/dist/node/credential-store.d.ts +0 -81
- package/dist/node/credential-store.js +0 -399
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
|
@@ -31,6 +31,7 @@ __export(index_exports, {
|
|
|
31
31
|
TOPOLO_WIDGET_REFRESH_INTERVAL_MS: () => TOPOLO_WIDGET_REFRESH_INTERVAL_MS,
|
|
32
32
|
TopoloAuthError: () => TopoloAuthError,
|
|
33
33
|
TopoloClient: () => TopoloClient,
|
|
34
|
+
TopoloConfirmationRequiredError: () => TopoloConfirmationRequiredError,
|
|
34
35
|
TopoloHttpError: () => TopoloHttpError,
|
|
35
36
|
TopoloOAuth: () => TopoloOAuth,
|
|
36
37
|
TopoloPermissionError: () => TopoloPermissionError,
|
|
@@ -96,6 +97,12 @@ var TopoloPermissionError = class extends TopoloSdkError {
|
|
|
96
97
|
this.required = required;
|
|
97
98
|
}
|
|
98
99
|
};
|
|
100
|
+
var TopoloConfirmationRequiredError = class extends TopoloSdkError {
|
|
101
|
+
constructor(message) {
|
|
102
|
+
super("confirmation_required", message);
|
|
103
|
+
this.name = "TopoloConfirmationRequiredError";
|
|
104
|
+
}
|
|
105
|
+
};
|
|
99
106
|
var TopoloHttpError = class extends TopoloSdkError {
|
|
100
107
|
status;
|
|
101
108
|
body;
|
|
@@ -1162,6 +1169,7 @@ var APPLICATIONS = {
|
|
|
1162
1169
|
"topolo.io"
|
|
1163
1170
|
],
|
|
1164
1171
|
"routes": [
|
|
1172
|
+
"one.topolo.io",
|
|
1165
1173
|
"topolo.io",
|
|
1166
1174
|
"www.topolo.io"
|
|
1167
1175
|
],
|
|
@@ -2121,7 +2129,6 @@ var TOPOLO_RESOURCE_TYPE_HEADER = "X-Topolo-Resource-Type";
|
|
|
2121
2129
|
var TOPOLO_RESOURCE_ID_HEADER = "X-Topolo-Resource-ID";
|
|
2122
2130
|
var POSITIVE_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
2123
2131
|
var NEGATIVE_CACHE_TTL_MS = 60 * 1e3;
|
|
2124
|
-
var CATALOG_CACHE_TTL_MS = 60 * 1e3;
|
|
2125
2132
|
var TopoloClient = class _TopoloClient {
|
|
2126
2133
|
credential;
|
|
2127
2134
|
agent;
|
|
@@ -2130,9 +2137,9 @@ var TopoloClient = class _TopoloClient {
|
|
|
2130
2137
|
requireConfirmForWrites;
|
|
2131
2138
|
timeoutMs;
|
|
2132
2139
|
fetchImpl;
|
|
2140
|
+
refreshCredential;
|
|
2141
|
+
refreshInFlight = null;
|
|
2133
2142
|
debug;
|
|
2134
|
-
serviceCatalogCache = null;
|
|
2135
|
-
actionCatalogCache = null;
|
|
2136
2143
|
/**
|
|
2137
2144
|
* Process-wide cache for slug → app-id lookups. Keyed by
|
|
2138
2145
|
* `${authBaseUrl}|${slug}` so multiple `TopoloClient` instances pointing at
|
|
@@ -2157,6 +2164,7 @@ var TopoloClient = class _TopoloClient {
|
|
|
2157
2164
|
this.requireConfirmForWrites = options.requireConfirmForWrites !== false;
|
|
2158
2165
|
this.timeoutMs = options.timeoutMs ?? 3e4;
|
|
2159
2166
|
this.fetchImpl = options.fetch ?? fetch;
|
|
2167
|
+
this.refreshCredential = options.refreshCredential;
|
|
2160
2168
|
this.debug = options.debug;
|
|
2161
2169
|
}
|
|
2162
2170
|
emit(event) {
|
|
@@ -2174,11 +2182,11 @@ var TopoloClient = class _TopoloClient {
|
|
|
2174
2182
|
async request(opts) {
|
|
2175
2183
|
const method = opts.method ?? "GET";
|
|
2176
2184
|
if (this.requireConfirmForWrites && WRITE_METHODS.has(method) && !opts.confirm) {
|
|
2177
|
-
throw new
|
|
2185
|
+
throw new TopoloConfirmationRequiredError(
|
|
2178
2186
|
`Mutating ${method} requests require { confirm: true }. This guardrail prevents agents from issuing writes without an explicit human-in-the-loop acknowledgement.`
|
|
2179
2187
|
);
|
|
2180
2188
|
}
|
|
2181
|
-
const resolvedService = await this.resolveRequestService(opts.service);
|
|
2189
|
+
const resolvedService = opts.baseUrl ? { serviceKey: normalizeLookupKey(opts.service), baseUrl: requireHttpOrigin(opts.baseUrl) } : await this.resolveRequestService(opts.service);
|
|
2182
2190
|
const baseUrl = resolvedService.baseUrl;
|
|
2183
2191
|
const url = new URL(opts.path, baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`);
|
|
2184
2192
|
if (opts.query) {
|
|
@@ -2209,20 +2217,36 @@ var TopoloClient = class _TopoloClient {
|
|
|
2209
2217
|
headers.set(TOPOLO_RESOURCE_TYPE_HEADER, resourceType);
|
|
2210
2218
|
headers.set(TOPOLO_RESOURCE_ID_HEADER, resourceId);
|
|
2211
2219
|
}
|
|
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
2220
|
const urlStr = url.toString();
|
|
2216
2221
|
const startedAt = Date.now();
|
|
2217
2222
|
this.emit({ phase: "request", method, service: resolvedService.serviceKey, path: opts.path, url: urlStr, requestId });
|
|
2223
|
+
const fetchOnce = async () => {
|
|
2224
|
+
const controller = new AbortController();
|
|
2225
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
2226
|
+
const signal = opts.signal ? mergeSignals(opts.signal, controller.signal) : controller.signal;
|
|
2227
|
+
try {
|
|
2228
|
+
return await this.fetchImpl(urlStr, {
|
|
2229
|
+
method,
|
|
2230
|
+
headers: new Headers(headers),
|
|
2231
|
+
body: opts.body !== void 0 ? JSON.stringify(opts.body) : null,
|
|
2232
|
+
signal
|
|
2233
|
+
});
|
|
2234
|
+
} finally {
|
|
2235
|
+
clearTimeout(timeoutId);
|
|
2236
|
+
}
|
|
2237
|
+
};
|
|
2218
2238
|
let res;
|
|
2219
2239
|
try {
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2240
|
+
const attemptedCredential = this.credential;
|
|
2241
|
+
res = await fetchOnce();
|
|
2242
|
+
if (res.status === 401 && attemptedCredential.kind === "access_token" && this.refreshCredential) {
|
|
2243
|
+
await res.body?.cancel().catch(() => void 0);
|
|
2244
|
+
const refreshed = await this.refreshRejectedAccessToken(attemptedCredential.accessToken);
|
|
2245
|
+
if (refreshed) {
|
|
2246
|
+
applyAuthHeaders(headers, refreshed);
|
|
2247
|
+
res = await fetchOnce();
|
|
2248
|
+
}
|
|
2249
|
+
}
|
|
2226
2250
|
} catch (err) {
|
|
2227
2251
|
this.emit({
|
|
2228
2252
|
phase: "error",
|
|
@@ -2235,8 +2259,6 @@ var TopoloClient = class _TopoloClient {
|
|
|
2235
2259
|
error: err instanceof Error ? err.message : String(err)
|
|
2236
2260
|
});
|
|
2237
2261
|
throw err;
|
|
2238
|
-
} finally {
|
|
2239
|
-
clearTimeout(timeoutId);
|
|
2240
2262
|
}
|
|
2241
2263
|
const contentType = res.headers.get("Content-Type") ?? "";
|
|
2242
2264
|
const parsed = contentType.includes("application/json") ? await res.json().catch(() => null) : await res.text().catch(() => null);
|
|
@@ -2275,6 +2297,19 @@ var TopoloClient = class _TopoloClient {
|
|
|
2275
2297
|
});
|
|
2276
2298
|
return parsed;
|
|
2277
2299
|
}
|
|
2300
|
+
async refreshRejectedAccessToken(attemptedAccessToken) {
|
|
2301
|
+
if (this.credential.kind === "access_token" && this.credential.accessToken !== attemptedAccessToken) {
|
|
2302
|
+
return this.credential;
|
|
2303
|
+
}
|
|
2304
|
+
if (!this.refreshCredential) return null;
|
|
2305
|
+
this.refreshInFlight ??= this.refreshCredential().then((credential) => {
|
|
2306
|
+
if (credential) this.credential = credential;
|
|
2307
|
+
return credential;
|
|
2308
|
+
}).catch(() => null).finally(() => {
|
|
2309
|
+
this.refreshInFlight = null;
|
|
2310
|
+
});
|
|
2311
|
+
return this.refreshInFlight;
|
|
2312
|
+
}
|
|
2278
2313
|
async resolveRequestService(service) {
|
|
2279
2314
|
const serviceKey = normalizeLookupKey(service);
|
|
2280
2315
|
if (!serviceKey) {
|
|
@@ -2300,9 +2335,8 @@ var TopoloClient = class _TopoloClient {
|
|
|
2300
2335
|
);
|
|
2301
2336
|
}
|
|
2302
2337
|
async resolveLiveCallableService(serviceKey) {
|
|
2303
|
-
const
|
|
2304
|
-
|
|
2305
|
-
if (!match) return null;
|
|
2338
|
+
const match = await this.getService(serviceKey).catch(() => null);
|
|
2339
|
+
if (!match || !catalogEntryMatches(match, serviceKey)) return null;
|
|
2306
2340
|
const baseUrl = resolveCatalogServiceUrl(match, {
|
|
2307
2341
|
serviceKey,
|
|
2308
2342
|
overrides: this.serviceUrls
|
|
@@ -2376,12 +2410,13 @@ var TopoloClient = class _TopoloClient {
|
|
|
2376
2410
|
* The live Auth catalog is the authority because it is scoped to the caller's
|
|
2377
2411
|
* organization and effective permissions.
|
|
2378
2412
|
*/
|
|
2379
|
-
async
|
|
2380
|
-
const
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2413
|
+
async searchServices(options = {}) {
|
|
2414
|
+
const res = await this.request({
|
|
2415
|
+
service: "developers",
|
|
2416
|
+
method: "GET",
|
|
2417
|
+
path: "/api/services/catalog",
|
|
2418
|
+
query: catalogSearchQuery(options)
|
|
2419
|
+
});
|
|
2385
2420
|
const raw = res.data?.services ?? res.services;
|
|
2386
2421
|
if (!Array.isArray(raw)) {
|
|
2387
2422
|
throw new TopoloSdkError(
|
|
@@ -2391,6 +2426,7 @@ var TopoloClient = class _TopoloClient {
|
|
|
2391
2426
|
}
|
|
2392
2427
|
const entries = [];
|
|
2393
2428
|
for (const item of raw) {
|
|
2429
|
+
if (!isRecord(item)) continue;
|
|
2394
2430
|
const s = item;
|
|
2395
2431
|
const appId = typeof s.app_id === "string" ? s.app_id : typeof s.id === "string" ? s.id : null;
|
|
2396
2432
|
if (!appId) continue;
|
|
@@ -2415,11 +2451,30 @@ var TopoloClient = class _TopoloClient {
|
|
|
2415
2451
|
apiKeyResources
|
|
2416
2452
|
});
|
|
2417
2453
|
}
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
};
|
|
2422
|
-
|
|
2454
|
+
const page = normalizeCatalogPage(
|
|
2455
|
+
res.data?.page ?? res.page
|
|
2456
|
+
);
|
|
2457
|
+
return { services: entries, page };
|
|
2458
|
+
}
|
|
2459
|
+
/** @deprecated Prefer searchServices() so callers retain the continuation cursor. */
|
|
2460
|
+
async listServices(options = {}) {
|
|
2461
|
+
return (await this.searchServices(options)).services;
|
|
2462
|
+
}
|
|
2463
|
+
async getService(application) {
|
|
2464
|
+
const needle = application.trim();
|
|
2465
|
+
if (!needle) throw new TopoloSdkError("unknown_service", "application is required");
|
|
2466
|
+
const res = await this.request({
|
|
2467
|
+
service: "developers",
|
|
2468
|
+
method: "GET",
|
|
2469
|
+
path: "/api/services/catalog",
|
|
2470
|
+
query: { application: needle }
|
|
2471
|
+
});
|
|
2472
|
+
const raw = res.data?.application ?? res.application;
|
|
2473
|
+
const entry = normalizeServiceCatalogEntry(raw);
|
|
2474
|
+
if (!entry) {
|
|
2475
|
+
throw new TopoloSdkError("unknown_service", `Unknown Topolo application "${application}".`);
|
|
2476
|
+
}
|
|
2477
|
+
return entry;
|
|
2423
2478
|
}
|
|
2424
2479
|
/**
|
|
2425
2480
|
* List callable actions from the live registry-canonical catalog (definitions
|
|
@@ -2427,16 +2482,15 @@ var TopoloClient = class _TopoloClient {
|
|
|
2427
2482
|
* caller's organization and effective permissions; no bundled/static app
|
|
2428
2483
|
* action list is used as an authorization source.
|
|
2429
2484
|
*/
|
|
2430
|
-
async
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
return this.actionCatalogCache.entries;
|
|
2485
|
+
async searchActions(options) {
|
|
2486
|
+
if (!options.service?.trim()) {
|
|
2487
|
+
throw new TopoloSdkError("service_required", "service is required for bounded action discovery");
|
|
2434
2488
|
}
|
|
2435
2489
|
const res = await this.request({
|
|
2436
2490
|
service: "developers",
|
|
2437
2491
|
method: "GET",
|
|
2438
2492
|
path: "/api/actions/catalog",
|
|
2439
|
-
|
|
2493
|
+
query: { service: options.service, ...catalogSearchQuery(options) }
|
|
2440
2494
|
});
|
|
2441
2495
|
const raw = res.data?.actions ?? res.actions;
|
|
2442
2496
|
if (!Array.isArray(raw)) {
|
|
@@ -2446,36 +2500,63 @@ var TopoloClient = class _TopoloClient {
|
|
|
2446
2500
|
);
|
|
2447
2501
|
}
|
|
2448
2502
|
const entries = raw.map((item) => normalizeActionCatalogEntry(item)).filter((entry) => Boolean(entry));
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2503
|
+
return {
|
|
2504
|
+
actions: entries,
|
|
2505
|
+
page: normalizeCatalogPage(
|
|
2506
|
+
res.data?.page ?? res.page
|
|
2507
|
+
)
|
|
2508
|
+
};
|
|
2509
|
+
}
|
|
2510
|
+
/** @deprecated Prefer searchActions() so callers retain the continuation cursor. */
|
|
2511
|
+
async listActions(options) {
|
|
2512
|
+
if (!options) {
|
|
2513
|
+
throw new TopoloSdkError("service_required", "service is required for bounded action discovery");
|
|
2454
2514
|
}
|
|
2455
|
-
return
|
|
2515
|
+
return (await this.searchActions(options)).actions;
|
|
2456
2516
|
}
|
|
2457
2517
|
async getAction(action) {
|
|
2458
|
-
const
|
|
2459
|
-
|
|
2460
|
-
const match = actions.find((entry) => actionEntryMatches(entry, needle));
|
|
2461
|
-
if (!match) {
|
|
2518
|
+
const actionId = action.trim();
|
|
2519
|
+
if (!actionId) {
|
|
2462
2520
|
throw new TopoloSdkError(
|
|
2463
2521
|
"unknown_action",
|
|
2464
|
-
|
|
2522
|
+
"action id is required"
|
|
2465
2523
|
);
|
|
2466
2524
|
}
|
|
2525
|
+
const res = await this.request({
|
|
2526
|
+
service: "developers",
|
|
2527
|
+
method: "GET",
|
|
2528
|
+
path: "/api/actions/catalog",
|
|
2529
|
+
query: { actionId }
|
|
2530
|
+
});
|
|
2531
|
+
const raw = res.data?.action ?? res.action;
|
|
2532
|
+
const match = normalizeActionCatalogEntry(raw);
|
|
2533
|
+
if (!match) throw new TopoloSdkError("unknown_action", `Unknown Topolo action "${action}".`);
|
|
2467
2534
|
return match;
|
|
2468
2535
|
}
|
|
2469
2536
|
async callAction(action, input = {}, options = {}) {
|
|
2470
2537
|
const entry = await this.getAction(action);
|
|
2538
|
+
const agentAccess = entry.agentAccess ?? (entry.destructive ? "off" : entry.readOnly && !entry.requiresConfirmation ? "auto" : "confirm");
|
|
2539
|
+
if (agentAccess === "off") {
|
|
2540
|
+
throw new TopoloPermissionError(
|
|
2541
|
+
`Topolo action "${entry.actionId}" is not available for agent invocation.`,
|
|
2542
|
+
entry.requiredPermission ? [entry.requiredPermission] : []
|
|
2543
|
+
);
|
|
2544
|
+
}
|
|
2545
|
+
if ((agentAccess === "confirm" || entry.requiresConfirmation || entry.destructive) && options.confirm !== true) {
|
|
2546
|
+
throw new TopoloConfirmationRequiredError(
|
|
2547
|
+
`Topolo action "${entry.actionId}" requires { confirm: true } before invocation.`
|
|
2548
|
+
);
|
|
2549
|
+
}
|
|
2471
2550
|
const mapped = mapActionInput(entry, input);
|
|
2472
2551
|
const service = entry.appSlug || entry.appId;
|
|
2473
2552
|
return this.request({
|
|
2474
2553
|
service,
|
|
2554
|
+
...entry.apiBaseUrl ? { baseUrl: entry.apiBaseUrl } : {},
|
|
2475
2555
|
method: entry.method,
|
|
2476
2556
|
path: mapped.path,
|
|
2477
2557
|
...mapped.query ? { query: mapped.query } : {},
|
|
2478
2558
|
...mapped.body !== void 0 ? { body: mapped.body } : {},
|
|
2559
|
+
...options.resource ? { resource: options.resource } : {},
|
|
2479
2560
|
headers: {
|
|
2480
2561
|
[TOPOLO_ACTION_ID_HEADER]: entry.actionId,
|
|
2481
2562
|
[TOPOLO_ACTION_NAME_HEADER]: entry.name
|
|
@@ -2613,6 +2694,18 @@ function serviceKeyCandidates2(slug) {
|
|
|
2613
2694
|
function normalizeLookupKey(value) {
|
|
2614
2695
|
return String(value || "").trim().toLowerCase().replace(/\s+/g, "-");
|
|
2615
2696
|
}
|
|
2697
|
+
function requireHttpOrigin(value) {
|
|
2698
|
+
let url;
|
|
2699
|
+
try {
|
|
2700
|
+
url = new URL(value);
|
|
2701
|
+
} catch {
|
|
2702
|
+
throw new TopoloSdkError("invalid_action_target", `Invalid action API origin "${value}".`);
|
|
2703
|
+
}
|
|
2704
|
+
if (url.protocol !== "https:" && url.protocol !== "http:" || url.username || url.password) {
|
|
2705
|
+
throw new TopoloSdkError("invalid_action_target", `Invalid action API origin "${value}".`);
|
|
2706
|
+
}
|
|
2707
|
+
return url.origin;
|
|
2708
|
+
}
|
|
2616
2709
|
function deriveDevelopersFromAuth(authUrl) {
|
|
2617
2710
|
if (!authUrl) return null;
|
|
2618
2711
|
try {
|
|
@@ -2639,6 +2732,47 @@ function catalogEntryLookupKeys(entry) {
|
|
|
2639
2732
|
}
|
|
2640
2733
|
return [...keys];
|
|
2641
2734
|
}
|
|
2735
|
+
function catalogSearchQuery(options) {
|
|
2736
|
+
const query = {};
|
|
2737
|
+
if (typeof options.query === "string" && options.query.trim()) query.query = options.query.trim();
|
|
2738
|
+
if (typeof options.cursor === "string" && options.cursor.trim()) query.cursor = options.cursor.trim();
|
|
2739
|
+
if (typeof options.limit === "number" && Number.isFinite(options.limit)) query.limit = Math.trunc(options.limit);
|
|
2740
|
+
return query;
|
|
2741
|
+
}
|
|
2742
|
+
function normalizeCatalogPage(value) {
|
|
2743
|
+
if (!isRecord(value)) return { nextCursor: null, hasMore: false };
|
|
2744
|
+
return {
|
|
2745
|
+
nextCursor: typeof value.nextCursor === "string" && value.nextCursor ? value.nextCursor : null,
|
|
2746
|
+
hasMore: value.hasMore === true
|
|
2747
|
+
};
|
|
2748
|
+
}
|
|
2749
|
+
function normalizeServiceCatalogEntry(item) {
|
|
2750
|
+
if (!isRecord(item)) return null;
|
|
2751
|
+
const appId = typeof item.app_id === "string" ? item.app_id : typeof item.id === "string" ? item.id : null;
|
|
2752
|
+
if (!appId) return null;
|
|
2753
|
+
const permissions = Array.isArray(item.granted_permissions) ? item.granted_permissions.filter(
|
|
2754
|
+
(permission) => typeof permission === "string" && permission.trim().length > 0
|
|
2755
|
+
) : [];
|
|
2756
|
+
const catalogApiKeyResources = normalizeApiKeyResourceMetadataList(
|
|
2757
|
+
item.api_key_resources ?? item.apiKeyResources
|
|
2758
|
+
);
|
|
2759
|
+
const apiKeyResource = normalizeApiKeyResourceMetadata(item.api_key_resource ?? item.apiKeyResource) ?? catalogApiKeyResources[0] ?? null;
|
|
2760
|
+
const apiKeyResources = catalogApiKeyResources.length > 0 ? catalogApiKeyResources : apiKeyResource ? [apiKeyResource] : [];
|
|
2761
|
+
return {
|
|
2762
|
+
appId,
|
|
2763
|
+
slug: typeof item.app_slug === "string" ? item.app_slug : null,
|
|
2764
|
+
name: typeof item.name === "string" ? item.name : null,
|
|
2765
|
+
description: typeof item.description === "string" ? item.description : null,
|
|
2766
|
+
apiBaseUrl: typeof item.api_base_url === "string" ? item.api_base_url : typeof item.apiBaseUrl === "string" ? item.apiBaseUrl : null,
|
|
2767
|
+
launchUrl: typeof item.launch_url === "string" ? item.launch_url : typeof item.launchUrl === "string" ? item.launchUrl : null,
|
|
2768
|
+
status: typeof item.status === "string" ? item.status : null,
|
|
2769
|
+
surfaceKind: typeof item.surface_kind === "string" ? item.surface_kind : null,
|
|
2770
|
+
launchable: item.launchable === true,
|
|
2771
|
+
permissions,
|
|
2772
|
+
apiKeyResource,
|
|
2773
|
+
apiKeyResources
|
|
2774
|
+
};
|
|
2775
|
+
}
|
|
2642
2776
|
function normalizeActionCatalogEntry(item) {
|
|
2643
2777
|
if (!item || typeof item !== "object" || Array.isArray(item)) return null;
|
|
2644
2778
|
const a = item;
|
|
@@ -2652,6 +2786,8 @@ function normalizeActionCatalogEntry(item) {
|
|
|
2652
2786
|
}
|
|
2653
2787
|
return {
|
|
2654
2788
|
actionId,
|
|
2789
|
+
target: pickActionString(a.target_key) ?? pickActionString(a.target) ?? null,
|
|
2790
|
+
apiBaseUrl: pickActionString(a.api_base_url) ?? pickActionString(a.apiBaseUrl) ?? null,
|
|
2655
2791
|
name,
|
|
2656
2792
|
toolName: pickActionString(a.tool_name) ?? pickActionString(a.toolName) ?? null,
|
|
2657
2793
|
title: pickActionString(a.title) ?? name,
|
|
@@ -2663,6 +2799,7 @@ function normalizeActionCatalogEntry(item) {
|
|
|
2663
2799
|
path,
|
|
2664
2800
|
permissionName: pickActionString(a.permission_name) ?? pickActionString(a.permissionName) ?? "",
|
|
2665
2801
|
requiredPermission: pickActionString(a.required_permission) ?? pickActionString(a.requiredPermission) ?? "",
|
|
2802
|
+
authorizationMode: pickAuthorizationMode(a.authorization_mode ?? a.authorizationMode),
|
|
2666
2803
|
inputSchema: plainObject(a.input_schema ?? a.inputSchema),
|
|
2667
2804
|
outputSchema: plainObject(a.output_schema ?? a.outputSchema),
|
|
2668
2805
|
readOnly: Boolean(a.read_only ?? a.readOnly),
|
|
@@ -2682,16 +2819,12 @@ function pickActionString(value) {
|
|
|
2682
2819
|
function pickAgentAccess(value) {
|
|
2683
2820
|
return value === "auto" || value === "confirm" || value === "off" ? value : null;
|
|
2684
2821
|
}
|
|
2822
|
+
function pickAuthorizationMode(value) {
|
|
2823
|
+
return value === "runtime" ? "runtime" : "static";
|
|
2824
|
+
}
|
|
2685
2825
|
function plainObject(value) {
|
|
2686
2826
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
2687
2827
|
}
|
|
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
2828
|
function mapActionInput(action, input) {
|
|
2696
2829
|
const remaining = { ...input || {} };
|
|
2697
2830
|
const path = action.path.replace(/\{([a-zA-Z0-9_]+)\}/g, (_match, key) => {
|
|
@@ -3306,6 +3439,7 @@ function createTopolo(options) {
|
|
|
3306
3439
|
TOPOLO_WIDGET_REFRESH_INTERVAL_MS,
|
|
3307
3440
|
TopoloAuthError,
|
|
3308
3441
|
TopoloClient,
|
|
3442
|
+
TopoloConfirmationRequiredError,
|
|
3309
3443
|
TopoloHttpError,
|
|
3310
3444
|
TopoloOAuth,
|
|
3311
3445
|
TopoloPermissionError,
|
package/dist/index.d.cts
CHANGED
|
@@ -47,6 +47,12 @@ interface TopoloClientOptions {
|
|
|
47
47
|
timeoutMs?: number;
|
|
48
48
|
/** Injected fetch, for testing. Defaults to global fetch. */
|
|
49
49
|
fetch?: typeof fetch;
|
|
50
|
+
/**
|
|
51
|
+
* Refresh a rejected access-token credential. When supplied, the client
|
|
52
|
+
* retries one 401 response with the returned credential. Concurrent 401s
|
|
53
|
+
* share one refresh operation. API keys are never refreshed.
|
|
54
|
+
*/
|
|
55
|
+
refreshCredential?: () => Promise<TopoloCredential | null>;
|
|
50
56
|
/**
|
|
51
57
|
* Observability hook. Fires once per request lifecycle (`request` at start,
|
|
52
58
|
* then exactly one of `response` or `error`). Safe to leave unset — off by
|
|
@@ -84,6 +90,8 @@ type TopoloDebugEvent = {
|
|
|
84
90
|
};
|
|
85
91
|
interface RequestOptions {
|
|
86
92
|
service: string;
|
|
93
|
+
/** Catalog-resolved origin for an action target. */
|
|
94
|
+
baseUrl?: string;
|
|
87
95
|
path: string;
|
|
88
96
|
method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
89
97
|
query?: Record<string, string | number | boolean | undefined>;
|
|
@@ -129,6 +137,8 @@ interface TopoloApiKeyResourceMetadata {
|
|
|
129
137
|
}
|
|
130
138
|
interface TopoloActionCatalogEntry {
|
|
131
139
|
actionId: string;
|
|
140
|
+
target: string | null;
|
|
141
|
+
apiBaseUrl: string | null;
|
|
132
142
|
name: string;
|
|
133
143
|
toolName: string | null;
|
|
134
144
|
title: string;
|
|
@@ -140,6 +150,8 @@ interface TopoloActionCatalogEntry {
|
|
|
140
150
|
path: string;
|
|
141
151
|
permissionName: string;
|
|
142
152
|
requiredPermission: string;
|
|
153
|
+
/** Static catalog permission or target-enforced input-dependent authorization. */
|
|
154
|
+
authorizationMode?: 'static' | 'runtime';
|
|
143
155
|
inputSchema: Record<string, unknown>;
|
|
144
156
|
outputSchema: Record<string, unknown>;
|
|
145
157
|
readOnly: boolean;
|
|
@@ -150,17 +162,34 @@ interface TopoloActionCatalogEntry {
|
|
|
150
162
|
status: string | null;
|
|
151
163
|
sourceRevision: string | null;
|
|
152
164
|
}
|
|
165
|
+
interface TopoloCatalogPage {
|
|
166
|
+
nextCursor: string | null;
|
|
167
|
+
hasMore: boolean;
|
|
168
|
+
}
|
|
169
|
+
interface TopoloServiceCatalogPage {
|
|
170
|
+
services: TopoloServiceCatalogEntry[];
|
|
171
|
+
page: TopoloCatalogPage;
|
|
172
|
+
}
|
|
173
|
+
interface TopoloActionCatalogPage {
|
|
174
|
+
actions: TopoloActionCatalogEntry[];
|
|
175
|
+
page: TopoloCatalogPage;
|
|
176
|
+
}
|
|
177
|
+
interface TopoloCatalogSearchOptions {
|
|
178
|
+
query?: string;
|
|
179
|
+
cursor?: string;
|
|
180
|
+
limit?: number;
|
|
181
|
+
}
|
|
153
182
|
declare class TopoloClient {
|
|
154
|
-
private
|
|
183
|
+
private credential;
|
|
155
184
|
private readonly agent;
|
|
156
185
|
private readonly serviceUrls?;
|
|
157
186
|
private readonly appIds?;
|
|
158
187
|
private readonly requireConfirmForWrites;
|
|
159
188
|
private readonly timeoutMs;
|
|
160
189
|
private readonly fetchImpl;
|
|
190
|
+
private readonly refreshCredential?;
|
|
191
|
+
private refreshInFlight;
|
|
161
192
|
private readonly debug?;
|
|
162
|
-
private serviceCatalogCache;
|
|
163
|
-
private actionCatalogCache;
|
|
164
193
|
/**
|
|
165
194
|
* Process-wide cache for slug → app-id lookups. Keyed by
|
|
166
195
|
* `${authBaseUrl}|${slug}` so multiple `TopoloClient` instances pointing at
|
|
@@ -181,6 +210,7 @@ declare class TopoloClient {
|
|
|
181
210
|
* use and for the generic `topolo api` / MCP passthrough tool.
|
|
182
211
|
*/
|
|
183
212
|
request<T = unknown>(opts: RequestOptions): Promise<T>;
|
|
213
|
+
private refreshRejectedAccessToken;
|
|
184
214
|
private resolveRequestService;
|
|
185
215
|
private resolveLiveCallableService;
|
|
186
216
|
/**
|
|
@@ -206,19 +236,30 @@ declare class TopoloClient {
|
|
|
206
236
|
* The live Auth catalog is the authority because it is scoped to the caller's
|
|
207
237
|
* organization and effective permissions.
|
|
208
238
|
*/
|
|
209
|
-
|
|
239
|
+
searchServices(options?: TopoloCatalogSearchOptions): Promise<TopoloServiceCatalogPage>;
|
|
240
|
+
/** @deprecated Prefer searchServices() so callers retain the continuation cursor. */
|
|
241
|
+
listServices(options?: TopoloCatalogSearchOptions): Promise<TopoloServiceCatalogEntry[]>;
|
|
242
|
+
getService(application: string): Promise<TopoloServiceCatalogEntry>;
|
|
210
243
|
/**
|
|
211
244
|
* List callable actions from the live registry-canonical catalog (definitions
|
|
212
245
|
* from the Catalog registry, scoping from Auth). The response is scoped to the
|
|
213
246
|
* caller's organization and effective permissions; no bundled/static app
|
|
214
247
|
* action list is used as an authorization source.
|
|
215
248
|
*/
|
|
216
|
-
|
|
217
|
-
service
|
|
249
|
+
searchActions(options: TopoloCatalogSearchOptions & {
|
|
250
|
+
service: string;
|
|
251
|
+
}): Promise<TopoloActionCatalogPage>;
|
|
252
|
+
/** @deprecated Prefer searchActions() so callers retain the continuation cursor. */
|
|
253
|
+
listActions(options?: TopoloCatalogSearchOptions & {
|
|
254
|
+
service: string;
|
|
218
255
|
}): Promise<TopoloActionCatalogEntry[]>;
|
|
219
256
|
getAction(action: string): Promise<TopoloActionCatalogEntry>;
|
|
220
257
|
callAction<T = unknown>(action: string, input?: Record<string, unknown>, options?: {
|
|
221
258
|
confirm?: boolean;
|
|
259
|
+
resource?: {
|
|
260
|
+
resourceType: string;
|
|
261
|
+
resourceId: string;
|
|
262
|
+
};
|
|
222
263
|
}): Promise<T>;
|
|
223
264
|
/**
|
|
224
265
|
* Bundled generated registry for tooling that audits platform packaging, not
|
|
@@ -1052,7 +1093,7 @@ declare const APPLICATIONS: {
|
|
|
1052
1093
|
readonly kind: "worker";
|
|
1053
1094
|
readonly apiUrl: "https://topolo.io";
|
|
1054
1095
|
readonly domains: readonly ["topolo.io"];
|
|
1055
|
-
readonly routes: readonly ["topolo.io", "www.topolo.io"];
|
|
1096
|
+
readonly routes: readonly ["one.topolo.io", "topolo.io", "www.topolo.io"];
|
|
1056
1097
|
readonly appId: null;
|
|
1057
1098
|
}];
|
|
1058
1099
|
};
|
|
@@ -1601,6 +1642,9 @@ declare class TopoloPermissionError extends TopoloSdkError {
|
|
|
1601
1642
|
readonly required: string[];
|
|
1602
1643
|
constructor(message: string, required: string[]);
|
|
1603
1644
|
}
|
|
1645
|
+
declare class TopoloConfirmationRequiredError extends TopoloSdkError {
|
|
1646
|
+
constructor(message: string);
|
|
1647
|
+
}
|
|
1604
1648
|
declare class TopoloHttpError extends TopoloSdkError {
|
|
1605
1649
|
readonly status: number;
|
|
1606
1650
|
readonly body: unknown;
|
|
@@ -2092,4 +2136,4 @@ declare function createTopolo(options: TopoloClientOptions): {
|
|
|
2092
2136
|
};
|
|
2093
2137
|
type Topolo = ReturnType<typeof createTopolo>;
|
|
2094
2138
|
|
|
2095
|
-
export { APPLICATIONS, APPLICATION_REQUIREMENTS, APPLICATION_REQUIREMENTS_VERSION, type AgentIdentity, type AppApiId, type ApplicationCatalogEntry, type ApplicationDeployTarget, type ApplicationId, type ApplicationRequirement, type ApplicationRequirementAudit, type ApplicationRequirementFinding, type ApplicationRequirementMigrationItem, type ApplicationRequirementScope, type ApplicationRequirementScore, type ApplicationRequirementStatus, type ApplicationRequirementsAuditReport, type CredentialIntrospection, type CrmContactSummary, CrmModule, DEFAULT_APP_URLS, type DeviceAuthorizationResponse, IdentityModule, type ListContactsOptions, type ListContactsResult, type OAuthHelperOptions, type P2PActionDetail, type P2PActionRequestInput, type P2PActionRequestResult, type P2PActionRow, type P2PActionRun, type P2PCapability, type P2PConnection, type P2PHold, type P2PLedgerEntry, P2PModule, type P2PPolicy, type P2PPricingModel, type P2PSettlementBatch, type P2PSettlementGroup, type PublishCapabilityInput, type RequestOptions, type ServiceCatalogUrlEntry, TOPOLO_WIDGET_ENDPOINT, TOPOLO_WIDGET_REFRESH_INTERVAL_MS, type TokenResponse, type Topolo, type TopoloActionCatalogEntry, type TopoloApiKeyResourceMetadata, TopoloAuthError, type TopoloChartWidget, TopoloClient, type TopoloClientOptions, type TopoloCredential, type TopoloDebugEvent, TopoloHttpError, type TopoloNotificationsWidget, TopoloOAuth, TopoloPermissionError, type TopoloQuickActionsWidget, type TopoloRecentActivityWidget, TopoloSdkError, type TopoloServiceCatalogEntry, type TopoloStatsWidget, type TopoloWidget, type TopoloWidgetActionItem, type TopoloWidgetActivityItem, type TopoloWidgetApiResponse, type TopoloWidgetBase, type TopoloWidgetConfig, type TopoloWidgetDraft, type TopoloWidgetMetric, type TopoloWidgetNotificationItem, type TopoloWidgetResponseOptions, type TopoloWidgetSnapshotMetadata, type TopoloWidgetSnapshotMetric, type TopoloWidgetType, type TopoloWidgetValidationResult, applicationRequirementScopes, auditAllApplicationRequirements, auditApplicationRequirements, createTopolo, createTopoloWidgetError, createTopoloWidgetResponse, defineTopoloChartWidget, defineTopoloNotificationsWidget, defineTopoloQuickActionsWidget, defineTopoloRecentActivityWidget, defineTopoloStatsWidget, requirementsForApplication, resolveCatalogServiceUrl, resolveServiceUrl, validateTopoloWidgetResponse };
|
|
2139
|
+
export { APPLICATIONS, APPLICATION_REQUIREMENTS, APPLICATION_REQUIREMENTS_VERSION, type AgentIdentity, type AppApiId, type ApplicationCatalogEntry, type ApplicationDeployTarget, type ApplicationId, type ApplicationRequirement, type ApplicationRequirementAudit, type ApplicationRequirementFinding, type ApplicationRequirementMigrationItem, type ApplicationRequirementScope, type ApplicationRequirementScore, type ApplicationRequirementStatus, type ApplicationRequirementsAuditReport, type CredentialIntrospection, type CrmContactSummary, CrmModule, DEFAULT_APP_URLS, type DeviceAuthorizationResponse, IdentityModule, type ListContactsOptions, type ListContactsResult, type OAuthHelperOptions, type P2PActionDetail, type P2PActionRequestInput, type P2PActionRequestResult, type P2PActionRow, type P2PActionRun, type P2PCapability, type P2PConnection, type P2PHold, type P2PLedgerEntry, P2PModule, type P2PPolicy, type P2PPricingModel, type P2PSettlementBatch, type P2PSettlementGroup, type PublishCapabilityInput, type RequestOptions, type ServiceCatalogUrlEntry, TOPOLO_WIDGET_ENDPOINT, TOPOLO_WIDGET_REFRESH_INTERVAL_MS, type TokenResponse, type Topolo, type TopoloActionCatalogEntry, type TopoloApiKeyResourceMetadata, TopoloAuthError, type TopoloChartWidget, TopoloClient, type TopoloClientOptions, TopoloConfirmationRequiredError, type TopoloCredential, type TopoloDebugEvent, TopoloHttpError, type TopoloNotificationsWidget, TopoloOAuth, TopoloPermissionError, type TopoloQuickActionsWidget, type TopoloRecentActivityWidget, TopoloSdkError, type TopoloServiceCatalogEntry, type TopoloStatsWidget, type TopoloWidget, type TopoloWidgetActionItem, type TopoloWidgetActivityItem, type TopoloWidgetApiResponse, type TopoloWidgetBase, type TopoloWidgetConfig, type TopoloWidgetDraft, type TopoloWidgetMetric, type TopoloWidgetNotificationItem, type TopoloWidgetResponseOptions, type TopoloWidgetSnapshotMetadata, type TopoloWidgetSnapshotMetric, type TopoloWidgetType, type TopoloWidgetValidationResult, applicationRequirementScopes, auditAllApplicationRequirements, auditApplicationRequirements, createTopolo, createTopoloWidgetError, createTopoloWidgetResponse, defineTopoloChartWidget, defineTopoloNotificationsWidget, defineTopoloQuickActionsWidget, defineTopoloRecentActivityWidget, defineTopoloStatsWidget, requirementsForApplication, resolveCatalogServiceUrl, resolveServiceUrl, validateTopoloWidgetResponse };
|