@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 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. The action entry includes the service,
118
- HTTP method/path, required permission, input/output schemas, read-only and
119
- destructive hints, and the stable MCP/agent tool name.
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 TopoloAuthError(
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
- res = await this.fetchImpl(urlStr, {
2221
- method,
2222
- headers,
2223
- body: opts.body !== void 0 ? JSON.stringify(opts.body) : null,
2224
- signal
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 catalog = await this.listServices();
2304
- const match = catalog.find((entry) => catalogEntryMatches(entry, serviceKey));
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 listServices() {
2380
- const now = Date.now();
2381
- if (this.serviceCatalogCache && this.serviceCatalogCache.expiresAt > now) {
2382
- return this.serviceCatalogCache.entries;
2383
- }
2384
- const res = await this.request({ service: "developers", method: "GET", path: "/api/services/catalog" });
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
- this.serviceCatalogCache = {
2419
- entries,
2420
- expiresAt: now + CATALOG_CACHE_TTL_MS
2421
- };
2422
- return entries;
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 listActions(options = {}) {
2431
- const now = Date.now();
2432
- if (!options.service && this.actionCatalogCache && this.actionCatalogCache.expiresAt > now) {
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
- ...options.service ? { query: { service: options.service } } : {}
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
- if (!options.service) {
2450
- this.actionCatalogCache = {
2451
- entries,
2452
- expiresAt: now + CATALOG_CACHE_TTL_MS
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 entries;
2515
+ return (await this.searchActions(options)).actions;
2456
2516
  }
2457
2517
  async getAction(action) {
2458
- const needle = normalizeActionLookupKey(action);
2459
- const actions = await this.listActions();
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
- `Unknown Topolo action "${action}". Run action discovery first and pass an action id, name, or tool name.`
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 readonly credential;
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
- listServices(): Promise<TopoloServiceCatalogEntry[]>;
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
- listActions(options?: {
217
- service?: string;
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 };