@tangle-network/agent-integrations 0.29.0 → 0.30.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.
Files changed (43) hide show
  1. package/dist/bin/tangle-catalog-runtime.js +7 -7
  2. package/dist/catalog.d.ts +2 -2
  3. package/dist/catalog.js +11 -7
  4. package/dist/{chunk-SVQ4PHDZ.js → chunk-5ASL5XNX.js} +2 -2
  5. package/dist/{chunk-P24T3MLM.js → chunk-DACSERTI.js} +2 -2
  6. package/dist/{chunk-YOKNZY2N.js → chunk-FDZIQVK7.js} +2 -2
  7. package/dist/{chunk-JU25UDN2.js → chunk-JOILC44P.js} +11 -5
  8. package/dist/chunk-JOILC44P.js.map +1 -0
  9. package/dist/{chunk-TUX6MJJ4.js → chunk-M2RFFAMB.js} +559 -411
  10. package/dist/chunk-M2RFFAMB.js.map +1 -0
  11. package/dist/{chunk-4JQ754PA.js → chunk-VVC7U7W7.js} +28 -1
  12. package/dist/{chunk-4JQ754PA.js.map → chunk-VVC7U7W7.js.map} +1 -1
  13. package/dist/{chunk-ATYHZXLL.js → chunk-Y6O3MIBW.js} +1 -1
  14. package/dist/chunk-Y6O3MIBW.js.map +1 -0
  15. package/dist/connect/index.d.ts +1 -1
  16. package/dist/connect/index.js +2 -2
  17. package/dist/connectors/adapters/index.d.ts +2 -2
  18. package/dist/connectors/adapters/index.js +2 -2
  19. package/dist/connectors/index.d.ts +1 -1
  20. package/dist/connectors/index.js +2 -2
  21. package/dist/consumer.d.ts +2 -2
  22. package/dist/consumer.js +2 -2
  23. package/dist/index.d.ts +2 -2
  24. package/dist/index.js +17 -7
  25. package/dist/middleware/index.d.ts +1 -1
  26. package/dist/middleware/index.js +2 -2
  27. package/dist/registry.d.ts +200 -47
  28. package/dist/registry.js +9 -7
  29. package/dist/runtime.d.ts +2 -2
  30. package/dist/runtime.js +7 -7
  31. package/dist/specs.d.ts +2 -2
  32. package/dist/specs.js +3 -1
  33. package/dist/tangle-catalog-runtime.d.ts +2 -2
  34. package/dist/tangle-catalog-runtime.js +7 -7
  35. package/dist/{tangle-id-CTU4kGId.d.ts → tangle-id-C6s2NT2r.d.ts} +7 -0
  36. package/docs/integration-execution-audit.md +1 -1
  37. package/package.json +7 -1
  38. package/dist/chunk-ATYHZXLL.js.map +0 -1
  39. package/dist/chunk-JU25UDN2.js.map +0 -1
  40. package/dist/chunk-TUX6MJJ4.js.map +0 -1
  41. /package/dist/{chunk-SVQ4PHDZ.js.map → chunk-5ASL5XNX.js.map} +0 -0
  42. /package/dist/{chunk-P24T3MLM.js.map → chunk-DACSERTI.js.map} +0 -0
  43. /package/dist/{chunk-YOKNZY2N.js.map → chunk-FDZIQVK7.js.map} +0 -0
@@ -5,7 +5,7 @@ import {
5
5
  import {
6
6
  integrationSpecToConnector,
7
7
  listIntegrationSpecs
8
- } from "./chunk-4JQ754PA.js";
8
+ } from "./chunk-VVC7U7W7.js";
9
9
 
10
10
  // src/activepieces-catalog.ts
11
11
  import { readFileSync } from "fs";
@@ -135,8 +135,9 @@ function buildActivepiecesConnectors(options = {}) {
135
135
  const override = getActivepiecesOverride(entry.id);
136
136
  const category = override?.category ?? entry.category;
137
137
  const scopes = [`${entry.id}.read`, `${entry.id}.write`];
138
- const catalogActions = entry.actions.length > 0 ? entry.actions.map((action) => toAction(applyActionOverride(action, override), scopes, dataClassFor(category))) : defaultActions(entry.id, scopes, dataClassFor(category));
138
+ const catalogActions = entry.actions.map((action) => toAction(applyActionOverride(action, override), scopes, dataClassFor(category)));
139
139
  const catalogTriggers = entry.triggers.map((trigger2) => toTrigger(trigger2, scopes, dataClassFor(category)));
140
+ const entryExecutable = executable && catalogActions.length > 0;
140
141
  return {
141
142
  id: entry.id,
142
143
  providerId,
@@ -148,10 +149,10 @@ function buildActivepiecesConnectors(options = {}) {
148
149
  triggers: options.includeCatalogActions ? catalogTriggers : void 0,
149
150
  metadata: {
150
151
  source: "activepieces-community",
151
- executable,
152
+ executable: entryExecutable,
152
153
  runtime: "activepieces-piece",
153
- catalogOnly: !executable,
154
- supportTier: executable ? "gatewayExecutable" : "catalogOnly",
154
+ catalogOnly: !entryExecutable,
155
+ supportTier: entryExecutable ? "gatewayExecutable" : "catalogOnly",
155
156
  catalogActionCount: catalogActions.length,
156
157
  catalogTriggerCount: catalogTriggers.length,
157
158
  npmPackage: entry.npmPackage,
@@ -190,28 +191,6 @@ function toTrigger(trigger2, scopes, dataClass) {
190
191
  payloadSchema: { type: "object", additionalProperties: true, properties: {} }
191
192
  };
192
193
  }
193
- function defaultActions(id, scopes, dataClass) {
194
- return [
195
- {
196
- id: "records.search",
197
- title: "Search records",
198
- risk: "read",
199
- requiredScopes: [scopes[0]],
200
- dataClass,
201
- inputSchema: { type: "object", additionalProperties: true, properties: {} }
202
- },
203
- {
204
- id: "records.upsert",
205
- title: "Upsert record",
206
- risk: "write",
207
- requiredScopes: [scopes[1]],
208
- dataClass,
209
- approvalRequired: true,
210
- inputSchema: { type: "object", additionalProperties: true, properties: {} },
211
- description: `Create or update a ${id} record through a catalog-backed connector.`
212
- }
213
- ];
214
- }
215
194
  function dataClassFor(category) {
216
195
  if (category === "database" || category === "storage" || category === "email") return "private";
217
196
  if (category === "crm" || category === "chat" || category === "docs") return "private";
@@ -222,6 +201,300 @@ function dataClassFor(category) {
222
201
  // src/index.ts
223
202
  import { createHmac as createHmac2, randomUUID as randomUUID5, timingSafeEqual as timingSafeEqual2 } from "crypto";
224
203
 
204
+ // src/adapter-provider.ts
205
+ function createConnectorAdapterProvider(options) {
206
+ const providerId = options.id ?? "first-party";
207
+ const now = options.now ?? (() => /* @__PURE__ */ new Date());
208
+ const adapters = /* @__PURE__ */ new Map();
209
+ for (const adapter of options.adapters) {
210
+ adapters.set(adapter.manifest.kind, adapter);
211
+ }
212
+ return {
213
+ id: providerId,
214
+ kind: options.kind ?? "first_party",
215
+ listConnectors: () => [...adapters.values()].map((adapter) => manifestToConnector(providerId, adapter)),
216
+ async invokeAction(connection, request) {
217
+ const adapter = adapters.get(connection.connectorId);
218
+ if (!adapter) {
219
+ throw new IntegrationError(`Connector adapter ${connection.connectorId} not found.`, "connector_not_found");
220
+ }
221
+ const capability = adapter.manifest.capabilities.find((candidate) => candidate.name === request.action);
222
+ if (!capability) {
223
+ throw new IntegrationError(`Capability ${request.action} is not defined by ${connection.connectorId}.`, "action_not_found");
224
+ }
225
+ const source = await options.resolveDataSource(connection);
226
+ let rotated;
227
+ const invocation = {
228
+ source,
229
+ capabilityName: request.action,
230
+ args: toRecord(request.input),
231
+ idempotencyKey: request.idempotencyKey ?? `idem_${connection.id}_${request.action}_${now().getTime()}`,
232
+ expectedEtag: typeof request.metadata?.expectedEtag === "string" ? request.metadata.expectedEtag : void 0,
233
+ callSessionId: typeof request.metadata?.callSessionId === "string" ? request.metadata.callSessionId : void 0,
234
+ onCredentialsRotated: options.onCredentialsRotated ? (credentials) => {
235
+ rotated = credentials;
236
+ } : void 0
237
+ };
238
+ const persistRotation = async () => {
239
+ if (rotated && options.onCredentialsRotated) {
240
+ await options.onCredentialsRotated({ connection, credentials: rotated });
241
+ }
242
+ };
243
+ if (capability.class === "read") {
244
+ if (!adapter.executeRead) {
245
+ throw new IntegrationError(`Connector ${connection.connectorId} does not implement reads.`, "action_not_found");
246
+ }
247
+ const result = await adapter.executeRead(invocation);
248
+ await persistRotation();
249
+ return readResultToAction(request, result);
250
+ }
251
+ if (capability.class === "mutation") {
252
+ if (!adapter.executeMutation) {
253
+ throw new IntegrationError(`Connector ${connection.connectorId} does not implement mutations.`, "action_not_found");
254
+ }
255
+ const result = await adapter.executeMutation(invocation);
256
+ await persistRotation();
257
+ return mutationResultToAction(request, result);
258
+ }
259
+ throw new IntegrationError(`Capability ${request.action} is not invokable as an action.`, "action_not_found");
260
+ }
261
+ };
262
+ }
263
+ function adapterManifestsToConnectors(adapters, providerId = "first-party") {
264
+ return adapters.map((adapter) => manifestToConnector(providerId, adapter));
265
+ }
266
+ function createConnectorAdapterCatalogSource(options) {
267
+ const sourceId = options.id ?? "first-party";
268
+ return {
269
+ id: sourceId,
270
+ precedence: options.precedence,
271
+ connectors: adapterManifestsToConnectors(options.adapters, options.providerId ?? sourceId)
272
+ };
273
+ }
274
+ function manifestToConnector(providerId, adapter) {
275
+ const manifest = adapter.manifest;
276
+ return {
277
+ id: manifest.kind,
278
+ providerId,
279
+ title: manifest.displayName,
280
+ category: mapCategory(manifest.category),
281
+ auth: mapAuth(manifest.auth.kind),
282
+ scopes: manifest.auth.kind === "oauth2" ? manifest.auth.scopes : [],
283
+ actions: manifest.capabilities.filter((capability) => capability.class === "read" || capability.class === "mutation").map((capability) => ({
284
+ id: capability.name,
285
+ title: titleFromName(capability.name),
286
+ risk: capability.class === "read" ? "read" : capability.externalEffect ? "destructive" : "write",
287
+ requiredScopes: capability.requiredScopes ?? [],
288
+ dataClass: inferDataClass(manifest.category),
289
+ description: capability.description,
290
+ approvalRequired: capability.class === "mutation",
291
+ inputSchema: capability.parameters
292
+ })),
293
+ metadata: {
294
+ source: "first-party-adapter",
295
+ supportTier: "firstPartyExecutable",
296
+ executable: true
297
+ }
298
+ };
299
+ }
300
+ function readResultToAction(request, result) {
301
+ return {
302
+ ok: true,
303
+ action: request.action,
304
+ output: result.data,
305
+ metadata: {
306
+ etag: result.etag,
307
+ fetchedAt: result.fetchedAt
308
+ }
309
+ };
310
+ }
311
+ function mutationResultToAction(request, result) {
312
+ if (result.status === "committed") {
313
+ return {
314
+ ok: true,
315
+ action: request.action,
316
+ output: result.data,
317
+ metadata: {
318
+ etagAfter: result.etagAfter,
319
+ committedAt: result.committedAt,
320
+ idempotentReplay: result.idempotentReplay
321
+ }
322
+ };
323
+ }
324
+ if (result.status === "conflict") {
325
+ return {
326
+ ok: false,
327
+ action: request.action,
328
+ output: {
329
+ conflict: true,
330
+ message: result.message,
331
+ alternatives: result.alternatives,
332
+ currentState: result.currentState
333
+ }
334
+ };
335
+ }
336
+ return {
337
+ ok: false,
338
+ action: request.action,
339
+ output: {
340
+ rateLimited: true,
341
+ retryAfterMs: result.retryAfterMs,
342
+ message: result.message
343
+ }
344
+ };
345
+ }
346
+ function mapAuth(kind) {
347
+ if (kind === "oauth2") return "oauth2";
348
+ if (kind === "api-key") return "api_key";
349
+ if (kind === "none") return "none";
350
+ return "custom";
351
+ }
352
+ function mapCategory(category) {
353
+ if (category === "comms") return "chat";
354
+ if (category === "spreadsheet") return "database";
355
+ if (category === "doc") return "docs";
356
+ if (category === "commerce") return "workflow";
357
+ return category === "other" ? "other" : category;
358
+ }
359
+ function inferDataClass(category) {
360
+ if (category === "commerce") return "sensitive";
361
+ if (category === "webhook") return "internal";
362
+ return "private";
363
+ }
364
+ function titleFromName(name) {
365
+ return name.split(/[._-]/g).filter(Boolean).map((part) => part.slice(0, 1).toUpperCase() + part.slice(1)).join(" ");
366
+ }
367
+ function toRecord(input) {
368
+ if (input && typeof input === "object" && !Array.isArray(input)) return input;
369
+ return {};
370
+ }
371
+
372
+ // src/credentials.ts
373
+ var InMemoryIntegrationSecretStore = class {
374
+ secrets = /* @__PURE__ */ new Map();
375
+ get(ref) {
376
+ return this.secrets.get(secretKey(ref));
377
+ }
378
+ put(ref, credentials) {
379
+ this.secrets.set(secretKey(ref), credentials);
380
+ }
381
+ delete(ref) {
382
+ this.secrets.delete(secretKey(ref));
383
+ }
384
+ };
385
+ var InMemoryIntegrationOAuthStateStore = class {
386
+ states = /* @__PURE__ */ new Map();
387
+ put(state) {
388
+ this.states.set(state.state, state);
389
+ }
390
+ consume(state) {
391
+ const record = this.states.get(state);
392
+ this.states.delete(state);
393
+ if (!record) return { ok: false, reason: "unknown" };
394
+ if (record.expiresAt <= Date.now()) return { ok: false, reason: "expired" };
395
+ return { ok: true, state: record };
396
+ }
397
+ sweep(now) {
398
+ for (const [key, record] of this.states) {
399
+ if (record.expiresAt <= now) this.states.delete(key);
400
+ }
401
+ }
402
+ };
403
+ function createConnectionCredentialResolver(options) {
404
+ const now = options.now ?? (() => /* @__PURE__ */ new Date());
405
+ return async function resolveDataSource(connection) {
406
+ const credentials = await resolveConnectionCredentials(connection, {
407
+ secrets: options.secrets,
408
+ connections: options.connections,
409
+ adapters: options.adapters,
410
+ now,
411
+ markConnectionError: options.markConnectionError
412
+ });
413
+ return {
414
+ id: connection.id,
415
+ projectId: String(connection.metadata?.projectId ?? connection.owner.id),
416
+ publishedAgentId: typeof connection.metadata?.publishedAgentId === "string" ? connection.metadata.publishedAgentId : null,
417
+ kind: connection.connectorId,
418
+ label: connection.account?.displayName ?? connection.account?.email ?? connection.connectorId,
419
+ consistencyModel: typeof connection.metadata?.consistencyModel === "string" ? connection.metadata.consistencyModel : "authoritative",
420
+ scopes: connection.grantedScopes,
421
+ metadata: connection.metadata ?? {},
422
+ credentials,
423
+ status: connection.status === "active" ? "active" : connection.status === "revoked" ? "revoked" : "error"
424
+ };
425
+ };
426
+ }
427
+ async function resolveConnectionCredentials(input, options) {
428
+ if (input.status !== "active") throw new Error(`Connection ${input.id} is ${input.status}.`);
429
+ if (!input.secretRef) return { kind: "none" };
430
+ const current = await options.secrets.get(input.secretRef);
431
+ if (!current) throw new Error(`Secret ${input.secretRef.provider}/${input.secretRef.id} not found.`);
432
+ if (!isExpiredOauth(current, options.now ?? (() => /* @__PURE__ */ new Date()))) return current;
433
+ const adapter = options.adapters?.find((candidate) => candidate.manifest.kind === input.connectorId);
434
+ if (!adapter?.refreshToken) return current;
435
+ try {
436
+ const refreshed = await adapter.refreshToken(current);
437
+ await options.secrets.put(input.secretRef, refreshed);
438
+ if (options.connections) {
439
+ await options.connections.put({
440
+ ...input,
441
+ status: "active",
442
+ updatedAt: (options.now?.() ?? /* @__PURE__ */ new Date()).toISOString(),
443
+ expiresAt: refreshed.kind === "oauth2" && refreshed.expiresAt ? new Date(refreshed.expiresAt).toISOString() : input.expiresAt
444
+ });
445
+ }
446
+ return refreshed;
447
+ } catch (error) {
448
+ const err = error instanceof Error ? error : new Error("Credential refresh failed.");
449
+ await options.markConnectionError?.(input, err);
450
+ if (options.connections) {
451
+ await options.connections.put({
452
+ ...input,
453
+ status: "expired",
454
+ updatedAt: (options.now?.() ?? /* @__PURE__ */ new Date()).toISOString()
455
+ });
456
+ }
457
+ throw err;
458
+ }
459
+ }
460
+ function createCredentialBackedAdapterProvider(options) {
461
+ const now = options.now ?? (() => /* @__PURE__ */ new Date());
462
+ return createConnectorAdapterProvider({
463
+ ...options,
464
+ resolveDataSource: createConnectionCredentialResolver(options),
465
+ onCredentialsRotated: async ({ connection, credentials }) => {
466
+ if (connection.secretRef) {
467
+ await options.secrets.put(connection.secretRef, credentials);
468
+ }
469
+ if (options.connections) {
470
+ await options.connections.put({
471
+ ...connection,
472
+ status: "active",
473
+ updatedAt: now().toISOString(),
474
+ expiresAt: credentials.kind === "oauth2" && credentials.expiresAt ? new Date(credentials.expiresAt).toISOString() : connection.expiresAt
475
+ });
476
+ }
477
+ await options.onCredentialsRotated?.({ connection, secretRef: connection.secretRef, credentials });
478
+ }
479
+ });
480
+ }
481
+ async function revokeConnection(input) {
482
+ if (input.connection.secretRef) await input.secrets?.delete?.(input.connection.secretRef);
483
+ const revoked = {
484
+ ...input.connection,
485
+ status: "revoked",
486
+ updatedAt: (input.now?.() ?? /* @__PURE__ */ new Date()).toISOString()
487
+ };
488
+ await input.connections?.put(revoked);
489
+ return revoked;
490
+ }
491
+ function isExpiredOauth(credentials, now) {
492
+ return credentials.kind === "oauth2" && typeof credentials.expiresAt === "number" && credentials.expiresAt <= now().getTime() && Boolean(credentials.refreshToken);
493
+ }
494
+ function secretKey(ref) {
495
+ return `${ref.provider}:${ref.id}`;
496
+ }
497
+
225
498
  // src/audit.ts
226
499
  import { randomUUID } from "crypto";
227
500
  var InMemoryIntegrationAuditStore = class {
@@ -798,340 +1071,90 @@ var TangleIntegrationsClient = class {
798
1071
  const normalized = normalizeIntegrationError(error);
799
1072
  return { status: "failed", action: input.tool, error: normalized.message, metadata: { code: normalized.code, userAction: normalized.userAction } };
800
1073
  }
801
- }
802
- };
803
- function createTangleIntegrationsClient(options) {
804
- return new TangleIntegrationsClient(options);
805
- }
806
- function defaultIdempotencyKey(action) {
807
- return `${action}:${Date.now()}:${Math.random().toString(36).slice(2)}`;
808
- }
809
- function readProcessEnv() {
810
- if (typeof process !== "undefined" && process.env) return process.env;
811
- return {};
812
- }
813
-
814
- // src/consent.ts
815
- function renderConsentSummary(manifestOrResolution, options = {}) {
816
- const manifest = "manifest" in manifestOrResolution ? manifestOrResolution.manifest : manifestOrResolution;
817
- const appName = options.appName ?? manifest.title ?? manifest.id;
818
- const requirements = manifest.requirements;
819
- const risk = aggregateRisk(requirements, options.connectors);
820
- const connectorIds = unique(requirements.map((requirement) => requirement.connectorId));
821
- const first = requirements[0];
822
- const body = first ? sentenceForRequirement(appName, first) : `${appName} does not request integrations.`;
823
- return {
824
- title: `${appName} wants to use ${humanList(connectorIds.map(titleize))}`,
825
- body,
826
- bullets: requirements.map((requirement) => bulletForRequirement(requirement, options.connectors)),
827
- primaryAction: risk === "read" ? "Allow access" : risk === "write" ? "Review and allow" : "Review destructive access",
828
- risk,
829
- connectorIds
830
- };
831
- }
832
- function renderApprovalCopy(input) {
833
- return {
834
- title: `${input.appName} wants to ${input.action.title.toLowerCase()}`,
835
- body: `${input.appName} is requesting permission to run "${input.action.title}" on ${input.connectorTitle}.`,
836
- bullets: [
837
- `Risk: ${input.action.risk}`,
838
- `Data: ${input.action.dataClass}`,
839
- ...input.approvalId ? [`Approval id: ${input.approvalId}`] : []
840
- ],
841
- primaryAction: input.action.risk === "read" ? "Allow" : "Approve action",
842
- risk: input.action.risk,
843
- connectorIds: []
844
- };
845
- }
846
- function sentenceForRequirement(appName, requirement) {
847
- if (requirement.connectorId === "google-calendar" && requirement.mode === "read") {
848
- return `${appName} wants to read your Google Calendar to find schedule-aware recommendations.`;
849
- }
850
- if (requirement.connectorId === "google-calendar" && requirement.mode === "write") {
851
- return `${appName} wants to create or update Google Calendar events after your approval.`;
852
- }
853
- if (requirement.mode === "read") return `${appName} wants to read ${titleize(requirement.connectorId)} data.`;
854
- if (requirement.mode === "write") return `${appName} wants to write ${titleize(requirement.connectorId)} data after approval.`;
855
- return `${appName} wants to subscribe to ${titleize(requirement.connectorId)} events.`;
856
- }
857
- function bulletForRequirement(requirement, connectors = []) {
858
- const connector = connectors.find((candidate) => candidate.id === requirement.connectorId);
859
- const actions = requirement.requiredActions?.length ? requirement.requiredActions.map((id) => connector?.actions.find((action) => action.id === id)?.title ?? id) : requirement.requiredTriggers ?? [];
860
- return `${titleize(requirement.connectorId)}: ${requirement.reason}${actions.length ? ` (${actions.join(", ")})` : ""}`;
861
- }
862
- function aggregateRisk(requirements, connectors = []) {
863
- let rank = 0;
864
- for (const requirement of requirements) {
865
- if (requirement.mode === "write") rank = Math.max(rank, 1);
866
- const connector = connectors.find((candidate) => candidate.id === requirement.connectorId);
867
- for (const actionId of requirement.requiredActions ?? []) {
868
- const risk = connector?.actions.find((action) => action.id === actionId)?.risk;
869
- if (risk === "write") rank = Math.max(rank, 1);
870
- if (risk === "destructive") rank = Math.max(rank, 2);
871
- }
872
- }
873
- return rank === 2 ? "destructive" : rank === 1 ? "write" : "read";
874
- }
875
- function humanList(values) {
876
- if (values.length <= 1) return values[0] ?? "integrations";
877
- if (values.length === 2) return `${values[0]} and ${values[1]}`;
878
- return `${values.slice(0, -1).join(", ")}, and ${values.at(-1)}`;
879
- }
880
- function titleize(value) {
881
- return value.split(/[-_.]/g).filter(Boolean).map((part) => part[0].toUpperCase() + part.slice(1)).join(" ");
882
- }
883
- function unique(values) {
884
- return [...new Set(values)];
885
- }
886
-
887
- // src/adapter-provider.ts
888
- function createConnectorAdapterProvider(options) {
889
- const providerId = options.id ?? "first-party";
890
- const now = options.now ?? (() => /* @__PURE__ */ new Date());
891
- const adapters = /* @__PURE__ */ new Map();
892
- for (const adapter of options.adapters) {
893
- adapters.set(adapter.manifest.kind, adapter);
894
- }
895
- return {
896
- id: providerId,
897
- kind: options.kind ?? "first_party",
898
- listConnectors: () => [...adapters.values()].map((adapter) => manifestToConnector(providerId, adapter)),
899
- async invokeAction(connection, request) {
900
- const adapter = adapters.get(connection.connectorId);
901
- if (!adapter) {
902
- throw new IntegrationError(`Connector adapter ${connection.connectorId} not found.`, "connector_not_found");
903
- }
904
- const capability = adapter.manifest.capabilities.find((candidate) => candidate.name === request.action);
905
- if (!capability) {
906
- throw new IntegrationError(`Capability ${request.action} is not defined by ${connection.connectorId}.`, "action_not_found");
907
- }
908
- const source = await options.resolveDataSource(connection);
909
- const invocation = {
910
- source,
911
- capabilityName: request.action,
912
- args: toRecord(request.input),
913
- idempotencyKey: request.idempotencyKey ?? `idem_${connection.id}_${request.action}_${now().getTime()}`,
914
- expectedEtag: typeof request.metadata?.expectedEtag === "string" ? request.metadata.expectedEtag : void 0,
915
- callSessionId: typeof request.metadata?.callSessionId === "string" ? request.metadata.callSessionId : void 0
916
- };
917
- if (capability.class === "read") {
918
- if (!adapter.executeRead) {
919
- throw new IntegrationError(`Connector ${connection.connectorId} does not implement reads.`, "action_not_found");
920
- }
921
- const result = await adapter.executeRead(invocation);
922
- return readResultToAction(request, result);
923
- }
924
- if (capability.class === "mutation") {
925
- if (!adapter.executeMutation) {
926
- throw new IntegrationError(`Connector ${connection.connectorId} does not implement mutations.`, "action_not_found");
927
- }
928
- const result = await adapter.executeMutation(invocation);
929
- return mutationResultToAction(request, result);
930
- }
931
- throw new IntegrationError(`Capability ${request.action} is not invokable as an action.`, "action_not_found");
932
- }
933
- };
934
- }
935
- function adapterManifestsToConnectors(adapters, providerId = "first-party") {
936
- return adapters.map((adapter) => manifestToConnector(providerId, adapter));
937
- }
938
- function createConnectorAdapterCatalogSource(options) {
939
- const sourceId = options.id ?? "first-party";
940
- return {
941
- id: sourceId,
942
- precedence: options.precedence,
943
- connectors: adapterManifestsToConnectors(options.adapters, options.providerId ?? sourceId)
944
- };
945
- }
946
- function manifestToConnector(providerId, adapter) {
947
- const manifest = adapter.manifest;
948
- return {
949
- id: manifest.kind,
950
- providerId,
951
- title: manifest.displayName,
952
- category: mapCategory(manifest.category),
953
- auth: mapAuth(manifest.auth.kind),
954
- scopes: manifest.auth.kind === "oauth2" ? manifest.auth.scopes : [],
955
- actions: manifest.capabilities.filter((capability) => capability.class === "read" || capability.class === "mutation").map((capability) => ({
956
- id: capability.name,
957
- title: titleFromName(capability.name),
958
- risk: capability.class === "read" ? "read" : capability.externalEffect ? "destructive" : "write",
959
- requiredScopes: capability.requiredScopes ?? [],
960
- dataClass: inferDataClass(manifest.category),
961
- description: capability.description,
962
- approvalRequired: capability.class === "mutation",
963
- inputSchema: capability.parameters
964
- })),
965
- metadata: {
966
- source: "first-party-adapter",
967
- supportTier: "firstPartyExecutable",
968
- executable: true
969
- }
970
- };
971
- }
972
- function readResultToAction(request, result) {
973
- return {
974
- ok: true,
975
- action: request.action,
976
- output: result.data,
977
- metadata: {
978
- etag: result.etag,
979
- fetchedAt: result.fetchedAt
980
- }
981
- };
982
- }
983
- function mutationResultToAction(request, result) {
984
- if (result.status === "committed") {
985
- return {
986
- ok: true,
987
- action: request.action,
988
- output: result.data,
989
- metadata: {
990
- etagAfter: result.etagAfter,
991
- committedAt: result.committedAt,
992
- idempotentReplay: result.idempotentReplay
993
- }
994
- };
995
- }
996
- if (result.status === "conflict") {
997
- return {
998
- ok: false,
999
- action: request.action,
1000
- output: {
1001
- conflict: true,
1002
- message: result.message,
1003
- alternatives: result.alternatives,
1004
- currentState: result.currentState
1005
- }
1006
- };
1007
- }
1008
- return {
1009
- ok: false,
1010
- action: request.action,
1011
- output: {
1012
- rateLimited: true,
1013
- retryAfterMs: result.retryAfterMs,
1014
- message: result.message
1015
- }
1016
- };
1017
- }
1018
- function mapAuth(kind) {
1019
- if (kind === "oauth2") return "oauth2";
1020
- if (kind === "api-key") return "api_key";
1021
- if (kind === "none") return "none";
1022
- return "custom";
1023
- }
1024
- function mapCategory(category) {
1025
- if (category === "comms") return "chat";
1026
- if (category === "spreadsheet") return "database";
1027
- if (category === "doc") return "docs";
1028
- if (category === "commerce") return "workflow";
1029
- return category === "other" ? "other" : category;
1030
- }
1031
- function inferDataClass(category) {
1032
- if (category === "commerce") return "sensitive";
1033
- if (category === "webhook") return "internal";
1034
- return "private";
1074
+ }
1075
+ };
1076
+ function createTangleIntegrationsClient(options) {
1077
+ return new TangleIntegrationsClient(options);
1035
1078
  }
1036
- function titleFromName(name) {
1037
- return name.split(/[._-]/g).filter(Boolean).map((part) => part.slice(0, 1).toUpperCase() + part.slice(1)).join(" ");
1079
+ function defaultIdempotencyKey(action) {
1080
+ return `${action}:${Date.now()}:${Math.random().toString(36).slice(2)}`;
1038
1081
  }
1039
- function toRecord(input) {
1040
- if (input && typeof input === "object" && !Array.isArray(input)) return input;
1082
+ function readProcessEnv() {
1083
+ if (typeof process !== "undefined" && process.env) return process.env;
1041
1084
  return {};
1042
1085
  }
1043
1086
 
1044
- // src/credentials.ts
1045
- var InMemoryIntegrationSecretStore = class {
1046
- secrets = /* @__PURE__ */ new Map();
1047
- get(ref) {
1048
- return this.secrets.get(secretKey(ref));
1049
- }
1050
- put(ref, credentials) {
1051
- this.secrets.set(secretKey(ref), credentials);
1087
+ // src/consent.ts
1088
+ function renderConsentSummary(manifestOrResolution, options = {}) {
1089
+ const manifest = "manifest" in manifestOrResolution ? manifestOrResolution.manifest : manifestOrResolution;
1090
+ const appName = options.appName ?? manifest.title ?? manifest.id;
1091
+ const requirements = manifest.requirements;
1092
+ const risk = aggregateRisk(requirements, options.connectors);
1093
+ const connectorIds = unique(requirements.map((requirement) => requirement.connectorId));
1094
+ const first = requirements[0];
1095
+ const body = first ? sentenceForRequirement(appName, first) : `${appName} does not request integrations.`;
1096
+ return {
1097
+ title: `${appName} wants to use ${humanList(connectorIds.map(titleize))}`,
1098
+ body,
1099
+ bullets: requirements.map((requirement) => bulletForRequirement(requirement, options.connectors)),
1100
+ primaryAction: risk === "read" ? "Allow access" : risk === "write" ? "Review and allow" : "Review destructive access",
1101
+ risk,
1102
+ connectorIds
1103
+ };
1104
+ }
1105
+ function renderApprovalCopy(input) {
1106
+ return {
1107
+ title: `${input.appName} wants to ${input.action.title.toLowerCase()}`,
1108
+ body: `${input.appName} is requesting permission to run "${input.action.title}" on ${input.connectorTitle}.`,
1109
+ bullets: [
1110
+ `Risk: ${input.action.risk}`,
1111
+ `Data: ${input.action.dataClass}`,
1112
+ ...input.approvalId ? [`Approval id: ${input.approvalId}`] : []
1113
+ ],
1114
+ primaryAction: input.action.risk === "read" ? "Allow" : "Approve action",
1115
+ risk: input.action.risk,
1116
+ connectorIds: []
1117
+ };
1118
+ }
1119
+ function sentenceForRequirement(appName, requirement) {
1120
+ if (requirement.connectorId === "google-calendar" && requirement.mode === "read") {
1121
+ return `${appName} wants to read your Google Calendar to find schedule-aware recommendations.`;
1052
1122
  }
1053
- delete(ref) {
1054
- this.secrets.delete(secretKey(ref));
1123
+ if (requirement.connectorId === "google-calendar" && requirement.mode === "write") {
1124
+ return `${appName} wants to create or update Google Calendar events after your approval.`;
1055
1125
  }
1056
- };
1057
- function createConnectionCredentialResolver(options) {
1058
- const now = options.now ?? (() => /* @__PURE__ */ new Date());
1059
- return async function resolveDataSource(connection) {
1060
- const credentials = await resolveConnectionCredentials(connection, {
1061
- secrets: options.secrets,
1062
- connections: options.connections,
1063
- adapters: options.adapters,
1064
- now,
1065
- markConnectionError: options.markConnectionError
1066
- });
1067
- return {
1068
- id: connection.id,
1069
- projectId: String(connection.metadata?.projectId ?? connection.owner.id),
1070
- publishedAgentId: typeof connection.metadata?.publishedAgentId === "string" ? connection.metadata.publishedAgentId : null,
1071
- kind: connection.connectorId,
1072
- label: connection.account?.displayName ?? connection.account?.email ?? connection.connectorId,
1073
- consistencyModel: typeof connection.metadata?.consistencyModel === "string" ? connection.metadata.consistencyModel : "authoritative",
1074
- scopes: connection.grantedScopes,
1075
- metadata: connection.metadata ?? {},
1076
- credentials,
1077
- status: connection.status === "active" ? "active" : connection.status === "revoked" ? "revoked" : "error"
1078
- };
1079
- };
1126
+ if (requirement.mode === "read") return `${appName} wants to read ${titleize(requirement.connectorId)} data.`;
1127
+ if (requirement.mode === "write") return `${appName} wants to write ${titleize(requirement.connectorId)} data after approval.`;
1128
+ return `${appName} wants to subscribe to ${titleize(requirement.connectorId)} events.`;
1080
1129
  }
1081
- async function resolveConnectionCredentials(input, options) {
1082
- if (input.status !== "active") throw new Error(`Connection ${input.id} is ${input.status}.`);
1083
- if (!input.secretRef) return { kind: "none" };
1084
- const current = await options.secrets.get(input.secretRef);
1085
- if (!current) throw new Error(`Secret ${input.secretRef.provider}/${input.secretRef.id} not found.`);
1086
- if (!isExpiredOauth(current, options.now ?? (() => /* @__PURE__ */ new Date()))) return current;
1087
- const adapter = options.adapters?.find((candidate) => candidate.manifest.kind === input.connectorId);
1088
- if (!adapter?.refreshToken) return current;
1089
- try {
1090
- const refreshed = await adapter.refreshToken(current);
1091
- await options.secrets.put(input.secretRef, refreshed);
1092
- if (options.connections) {
1093
- await options.connections.put({
1094
- ...input,
1095
- status: "active",
1096
- updatedAt: (options.now?.() ?? /* @__PURE__ */ new Date()).toISOString(),
1097
- expiresAt: refreshed.kind === "oauth2" && refreshed.expiresAt ? new Date(refreshed.expiresAt).toISOString() : input.expiresAt
1098
- });
1099
- }
1100
- return refreshed;
1101
- } catch (error) {
1102
- const err = error instanceof Error ? error : new Error("Credential refresh failed.");
1103
- await options.markConnectionError?.(input, err);
1104
- if (options.connections) {
1105
- await options.connections.put({
1106
- ...input,
1107
- status: "expired",
1108
- updatedAt: (options.now?.() ?? /* @__PURE__ */ new Date()).toISOString()
1109
- });
1130
+ function bulletForRequirement(requirement, connectors = []) {
1131
+ const connector = connectors.find((candidate) => candidate.id === requirement.connectorId);
1132
+ const actions = requirement.requiredActions?.length ? requirement.requiredActions.map((id) => connector?.actions.find((action) => action.id === id)?.title ?? id) : requirement.requiredTriggers ?? [];
1133
+ return `${titleize(requirement.connectorId)}: ${requirement.reason}${actions.length ? ` (${actions.join(", ")})` : ""}`;
1134
+ }
1135
+ function aggregateRisk(requirements, connectors = []) {
1136
+ let rank = 0;
1137
+ for (const requirement of requirements) {
1138
+ if (requirement.mode === "write") rank = Math.max(rank, 1);
1139
+ const connector = connectors.find((candidate) => candidate.id === requirement.connectorId);
1140
+ for (const actionId of requirement.requiredActions ?? []) {
1141
+ const risk = connector?.actions.find((action) => action.id === actionId)?.risk;
1142
+ if (risk === "write") rank = Math.max(rank, 1);
1143
+ if (risk === "destructive") rank = Math.max(rank, 2);
1110
1144
  }
1111
- throw err;
1112
1145
  }
1146
+ return rank === 2 ? "destructive" : rank === 1 ? "write" : "read";
1113
1147
  }
1114
- function createCredentialBackedAdapterProvider(options) {
1115
- return createConnectorAdapterProvider({
1116
- ...options,
1117
- resolveDataSource: createConnectionCredentialResolver(options)
1118
- });
1119
- }
1120
- async function revokeConnection(input) {
1121
- if (input.connection.secretRef) await input.secrets?.delete?.(input.connection.secretRef);
1122
- const revoked = {
1123
- ...input.connection,
1124
- status: "revoked",
1125
- updatedAt: (input.now?.() ?? /* @__PURE__ */ new Date()).toISOString()
1126
- };
1127
- await input.connections?.put(revoked);
1128
- return revoked;
1148
+ function humanList(values) {
1149
+ if (values.length <= 1) return values[0] ?? "integrations";
1150
+ if (values.length === 2) return `${values[0]} and ${values[1]}`;
1151
+ return `${values.slice(0, -1).join(", ")}, and ${values.at(-1)}`;
1129
1152
  }
1130
- function isExpiredOauth(credentials, now) {
1131
- return credentials.kind === "oauth2" && typeof credentials.expiresAt === "number" && credentials.expiresAt <= now().getTime() && Boolean(credentials.refreshToken);
1153
+ function titleize(value) {
1154
+ return value.split(/[-_.]/g).filter(Boolean).map((part) => part[0].toUpperCase() + part.slice(1)).join(" ");
1132
1155
  }
1133
- function secretKey(ref) {
1134
- return `${ref.provider}:${ref.id}`;
1156
+ function unique(values) {
1157
+ return [...new Set(values)];
1135
1158
  }
1136
1159
 
1137
1160
  // src/discovery.ts
@@ -2604,12 +2627,12 @@ async function auditTangleCatalogRuntimePackages(options = {}) {
2604
2627
  action.id,
2605
2628
  action.title,
2606
2629
  action.upstreamName
2607
- ])).length,
2630
+ ], entry.id)).length,
2608
2631
  triggerMappingsFound: entry.triggers.filter((trigger2) => hasRuntimeName(triggers, [
2609
2632
  trigger2.id,
2610
2633
  trigger2.title,
2611
2634
  trigger2.upstreamName
2612
- ])).length,
2635
+ ], entry.id)).length,
2613
2636
  triggerHostingSupported: entry.triggers.length === 0 || triggers.length > 0
2614
2637
  });
2615
2638
  }
@@ -2699,28 +2722,93 @@ function findPieceExport(moduleValue, connectorId) {
2699
2722
  mod[connectorId],
2700
2723
  ...Object.values(mod)
2701
2724
  ];
2702
- return values.find((value) => Boolean(value) && typeof value === "object" && Array.isArray(value.actions));
2725
+ for (const value of values) {
2726
+ const resolved = resolvePiece(value);
2727
+ if (resolved) return resolved;
2728
+ }
2729
+ return void 0;
2730
+ }
2731
+ function resolvePiece(value) {
2732
+ if (!value || typeof value !== "object" && typeof value !== "function") return void 0;
2733
+ const candidate = value;
2734
+ const ctorName = candidate.constructor?.name;
2735
+ const looksLikePiece = ctorName === "Piece" || "_actions" in candidate || "_triggers" in candidate || "actions" in candidate || "triggers" in candidate;
2736
+ if (!looksLikePiece) return void 0;
2737
+ const actions = readPieceMembers(candidate, "_actions", "actions");
2738
+ const triggers = readPieceMembers(candidate, "_triggers", "triggers");
2739
+ if (!actions && !triggers) return void 0;
2740
+ return { actions: actions ?? [], triggers: triggers ?? [] };
2741
+ }
2742
+ function readPieceMembers(piece, privateKey, publicKey) {
2743
+ const raw = coerceMemberCollection(piece[privateKey]) ?? coerceMemberCollection(readPublicMember(piece, publicKey));
2744
+ if (!raw) return void 0;
2745
+ return raw.filter((member) => Boolean(member) && typeof member === "object");
2746
+ }
2747
+ function readPublicMember(piece, key) {
2748
+ const value = piece[key];
2749
+ if (typeof value !== "function") return value;
2750
+ try {
2751
+ return value.call(piece);
2752
+ } catch {
2753
+ return void 0;
2754
+ }
2755
+ }
2756
+ function coerceMemberCollection(value) {
2757
+ if (Array.isArray(value)) return value;
2758
+ if (value && typeof value === "object") return Object.values(value);
2759
+ return void 0;
2703
2760
  }
2704
- function hasRuntimeName(values, candidates) {
2705
- const expected = new Set(candidates.filter((value) => Boolean(value)));
2706
- return values.filter((value) => Boolean(value) && typeof value === "object").some((value) => [value.name, value.displayName].some((name) => typeof name === "string" && expected.has(name)));
2761
+ function hasRuntimeName(members, candidates, connectorId) {
2762
+ const expected = normalizeNameSet(candidates, connectorId);
2763
+ return members.some((member) => {
2764
+ const names = normalizeNameSet([member.name, member.displayName], connectorId);
2765
+ for (const name of names) if (expected.has(name)) return true;
2766
+ return false;
2767
+ });
2707
2768
  }
2708
2769
  function findRuntimeAction(piece, invocation, aliases = {}, allowFuzzyActionMatch = false) {
2709
- const actions = (piece.actions ?? []).filter((action) => Boolean(action) && typeof action === "object");
2770
+ const actions = piece.actions;
2710
2771
  const explicit = aliases[invocation.connector.id]?.[invocation.action.id];
2711
- const candidates = new Set([
2772
+ if (explicit) {
2773
+ const exact = actions.find((action) => action.name === explicit || action.displayName === explicit);
2774
+ if (exact) return exact;
2775
+ }
2776
+ const connectorId = invocation.connector.id;
2777
+ const candidates = normalizeNameSet([
2712
2778
  invocation.action.id,
2713
2779
  invocation.action.title,
2714
2780
  invocation.request.piece.upstreamActionName,
2715
2781
  explicit
2716
- ].filter((value) => Boolean(value)));
2782
+ ], connectorId);
2717
2783
  for (const action of actions) {
2718
- const names = [action.name, action.displayName].filter((value) => Boolean(value));
2719
- if (names.some((name) => candidates.has(name))) return action;
2720
- if (allowFuzzyActionMatch && names.some((name) => [...candidates].some((candidate) => comparable(name) === comparable(candidate)))) return action;
2784
+ const names = normalizeNameSet([action.name, action.displayName], connectorId);
2785
+ for (const name of names) if (candidates.has(name)) return action;
2721
2786
  }
2787
+ void allowFuzzyActionMatch;
2722
2788
  return void 0;
2723
2789
  }
2790
+ function normalizeNameSet(values, connectorId) {
2791
+ const out = /* @__PURE__ */ new Set();
2792
+ const conn = comparable(connectorId);
2793
+ for (const value of values) {
2794
+ if (!value) continue;
2795
+ for (const variant of nameVariants(comparable(value), conn)) {
2796
+ if (variant) out.add(variant);
2797
+ }
2798
+ }
2799
+ return out;
2800
+ }
2801
+ function nameVariants(cmp, conn) {
2802
+ const base = [cmp, cmp.replace(/(action|trigger)$/, "")];
2803
+ const variants = [];
2804
+ for (const value of base) {
2805
+ variants.push(value);
2806
+ if (conn && value.startsWith(conn) && value.length > conn.length) {
2807
+ variants.push(value.slice(conn.length));
2808
+ }
2809
+ }
2810
+ return variants;
2811
+ }
2724
2812
  function runtimeFailure(action, code, message) {
2725
2813
  return {
2726
2814
  ok: false,
@@ -2912,16 +3000,6 @@ async function auditIntegrationCatalogFreshness(options = {}) {
2912
3000
  `Activepieces catalog has ${activepiecesConnectors.length} connectors, below floor ${minActivepiecesConnectors}.`
2913
3001
  );
2914
3002
  }
2915
- if (unsupportedExecutableConnectorIds.length > 0) {
2916
- warnings.push(
2917
- `Activepieces executable provider has ${unsupportedExecutableConnectorIds.length} connectors without actions.`
2918
- );
2919
- }
2920
- if (executableTools.length < activepiecesEntries.length) {
2921
- warnings.push(
2922
- `Activepieces executable provider produced only ${executableTools.length} tool definitions for ${activepiecesEntries.length} entries.`
2923
- );
2924
- }
2925
3003
  let upstream;
2926
3004
  if (options.liveActivepieces) {
2927
3005
  upstream = await checkActivepiecesPublicCatalog({
@@ -3372,6 +3450,12 @@ var IntegrationHub = class {
3372
3450
  capabilitySecret;
3373
3451
  guard;
3374
3452
  policy;
3453
+ /** Host-injected (or in-memory default) secret store. The hub re-persists
3454
+ * rotated credentials here when a connection carries a secretRef. */
3455
+ secretStore;
3456
+ oauthStateStore;
3457
+ oauthStateTtlMs;
3458
+ credentialsRotated;
3375
3459
  now;
3376
3460
  constructor(options) {
3377
3461
  if (!options.capabilitySecret) {
@@ -3382,6 +3466,10 @@ var IntegrationHub = class {
3382
3466
  this.capabilitySecret = options.capabilitySecret;
3383
3467
  this.guard = options.guard;
3384
3468
  this.policy = options.policy;
3469
+ this.secretStore = options.secretStore ?? new InMemoryIntegrationSecretStore();
3470
+ this.oauthStateStore = options.oauthStateStore ?? new InMemoryIntegrationOAuthStateStore();
3471
+ this.oauthStateTtlMs = options.oauthStateTtlMs ?? 10 * 60 * 1e3;
3472
+ this.credentialsRotated = options.credentialsRotated;
3385
3473
  this.now = options.now ?? (() => /* @__PURE__ */ new Date());
3386
3474
  }
3387
3475
  async listConnectors() {
@@ -3399,11 +3487,33 @@ var IntegrationHub = class {
3399
3487
  const provider = this.requireProvider(providerId);
3400
3488
  if (!provider.startAuth) throw new IntegrationError(`Provider ${providerId} does not support auth start.`, "auth_not_supported");
3401
3489
  await this.requireConnector(provider, request.connectorId);
3402
- return provider.startAuth(request);
3490
+ const result = await provider.startAuth(request);
3491
+ const record = {
3492
+ state: result.state,
3493
+ providerId,
3494
+ connectorId: request.connectorId,
3495
+ owner: request.owner,
3496
+ requestedScopes: request.requestedScopes,
3497
+ redirectUri: request.redirectUri,
3498
+ expiresAt: this.now().getTime() + this.oauthStateTtlMs,
3499
+ metadata: request.metadata
3500
+ };
3501
+ await this.oauthStateStore.put(record);
3502
+ return result;
3403
3503
  }
3404
3504
  async completeAuth(providerId, request) {
3405
3505
  const provider = this.requireProvider(providerId);
3406
3506
  if (!provider.completeAuth) throw new IntegrationError(`Provider ${providerId} does not support auth completion.`, "auth_not_supported");
3507
+ const outcome = await this.oauthStateStore.consume(request.state);
3508
+ if (!outcome.ok) {
3509
+ throw new IntegrationError(`Integration OAuth state ${outcome.reason}: possible CSRF, replay, or stale flow.`, "capability_invalid");
3510
+ }
3511
+ if (outcome.state.providerId !== providerId || outcome.state.connectorId !== request.connectorId) {
3512
+ throw new IntegrationError("Integration OAuth state does not match completion request.", "capability_invalid");
3513
+ }
3514
+ if (outcome.state.redirectUri !== request.redirectUri) {
3515
+ throw new IntegrationError("Integration OAuth redirect URI does not match the start request.", "capability_invalid");
3516
+ }
3407
3517
  const connection = await provider.completeAuth(request);
3408
3518
  await this.store.put(connection);
3409
3519
  return connection;
@@ -4026,6 +4136,24 @@ function buildDefaultIntegrationRegistry(options = {}) {
4026
4136
  }
4027
4137
  return composeIntegrationRegistry(sources);
4028
4138
  }
4139
+ function classifyIntegrationCatalogExecutability(registry = buildDefaultIntegrationRegistry()) {
4140
+ const packageByConnector = new Map(
4141
+ listActivepiecesCatalogEntries().filter((entry) => entry.npmPackage).map((entry) => [entry.id, entry.npmPackage])
4142
+ );
4143
+ return registry.entries.map((entry) => {
4144
+ const runtimePackage = packageByConnector.get(entry.connector.id);
4145
+ const tierExecutable = entry.supportTier === "firstPartyExecutable" || entry.supportTier === "sandboxExecutable" || entry.supportTier === "gatewayExecutable";
4146
+ return {
4147
+ canonicalId: entry.canonicalId,
4148
+ executable: tierExecutable && entry.connector.actions.length > 0,
4149
+ supportTier: entry.supportTier,
4150
+ authKind: entry.connector.auth,
4151
+ runtimePackage,
4152
+ actionCount: entry.connector.actions.length,
4153
+ triggerCount: entry.connector.triggers?.length ?? 0
4154
+ };
4155
+ });
4156
+ }
4029
4157
  function composeIntegrationRegistry(sources, options = {}) {
4030
4158
  const aliases = { ...DEFAULT_ALIASES, ...options.aliases ?? {} };
4031
4159
  const precedence = { ...DEFAULT_SOURCE_PRECEDENCE, ...options.sourcePrecedence ?? {} };
@@ -4227,40 +4355,56 @@ function buildIntegrationToolCatalog(connectors) {
4227
4355
  const tools = [];
4228
4356
  for (const connector of connectors) {
4229
4357
  for (const action of connector.actions) {
4230
- const tags = unique8([
4231
- connector.id,
4232
- connector.providerId,
4233
- connector.title,
4234
- connector.category,
4235
- action.id,
4236
- action.title,
4237
- action.risk,
4238
- action.dataClass,
4239
- ...connector.scopes ?? [],
4240
- ...action.requiredScopes ?? []
4241
- ].flatMap(tokenize));
4242
- tools.push({
4243
- name: integrationToolName(connector.providerId, connector.id, action.id),
4244
- title: `${connector.title}: ${action.title}`,
4245
- description: action.description ?? `${action.risk} action ${action.id} on ${connector.title}`,
4246
- providerId: connector.providerId,
4247
- connectorId: connector.id,
4248
- connectorTitle: connector.title,
4249
- category: connector.category,
4250
- action,
4251
- risk: action.risk,
4252
- dataClass: action.dataClass,
4253
- requiredScopes: action.requiredScopes,
4254
- inputSchema: action.inputSchema,
4255
- outputSchema: action.outputSchema,
4256
- tags,
4257
- supportTier: toolSupportTier(connector),
4258
- runnable: toolRunnable(connector)
4259
- });
4358
+ tools.push(flattenIntegrationToolDefinition(connector, action));
4260
4359
  }
4261
4360
  }
4262
4361
  return tools;
4263
4362
  }
4363
+ function flattenIntegrationToolDefinition(connector, action) {
4364
+ const tags = unique8([
4365
+ connector.id,
4366
+ connector.providerId,
4367
+ connector.title,
4368
+ connector.category,
4369
+ action.id,
4370
+ action.title,
4371
+ action.risk,
4372
+ action.dataClass,
4373
+ ...connector.scopes ?? [],
4374
+ ...action.requiredScopes ?? []
4375
+ ].flatMap(tokenize));
4376
+ return {
4377
+ name: integrationToolName(connector.providerId, connector.id, action.id),
4378
+ title: `${connector.title}: ${action.title}`,
4379
+ description: action.description ?? `${action.risk} action ${action.id} on ${connector.title}`,
4380
+ providerId: connector.providerId,
4381
+ connectorId: connector.id,
4382
+ connectorTitle: connector.title,
4383
+ category: connector.category,
4384
+ action,
4385
+ risk: action.risk,
4386
+ dataClass: action.dataClass,
4387
+ requiredScopes: action.requiredScopes,
4388
+ inputSchema: action.inputSchema,
4389
+ outputSchema: action.outputSchema,
4390
+ tags,
4391
+ supportTier: toolSupportTier(connector),
4392
+ runnable: toolRunnable(connector)
4393
+ };
4394
+ }
4395
+ function describeIntegrationTool(registry, toolName) {
4396
+ let parsed;
4397
+ try {
4398
+ parsed = parseIntegrationToolName(toolName);
4399
+ } catch {
4400
+ return void 0;
4401
+ }
4402
+ const entry = registry.byId.get(parsed.connectorId);
4403
+ if (!entry) return void 0;
4404
+ const action = entry.connector.actions.find((candidate) => candidate.id === parsed.actionId);
4405
+ if (!action) return void 0;
4406
+ return flattenIntegrationToolDefinition(entry.connector, action);
4407
+ }
4264
4408
  function buildIntegrationCatalogView(input) {
4265
4409
  const runtimeConnectors = input.executableRegistry?.connectors ?? input.discoveryRegistry.connectors.filter((connector) => toolRunnable(connector));
4266
4410
  const runtimeTools = buildIntegrationToolCatalog(runtimeConnectors);
@@ -4361,6 +4505,8 @@ export {
4361
4505
  integrationToolName,
4362
4506
  parseIntegrationToolName,
4363
4507
  buildIntegrationToolCatalog,
4508
+ flattenIntegrationToolDefinition,
4509
+ describeIntegrationTool,
4364
4510
  buildIntegrationCatalogView,
4365
4511
  searchIntegrationTools,
4366
4512
  toMcpTools,
@@ -4389,10 +4535,21 @@ export {
4389
4535
  extractExternalCatalogPublicCount,
4390
4536
  auditTangleIntegrationCatalogFreshness,
4391
4537
  buildDefaultIntegrationRegistry,
4538
+ classifyIntegrationCatalogExecutability,
4392
4539
  composeIntegrationRegistry,
4393
4540
  summarizeIntegrationRegistry,
4394
4541
  canonicalConnectorId,
4395
4542
  inferIntegrationSupportTier,
4543
+ createConnectorAdapterProvider,
4544
+ adapterManifestsToConnectors,
4545
+ createConnectorAdapterCatalogSource,
4546
+ manifestToConnector,
4547
+ InMemoryIntegrationSecretStore,
4548
+ InMemoryIntegrationOAuthStateStore,
4549
+ createConnectionCredentialResolver,
4550
+ resolveConnectionCredentials,
4551
+ createCredentialBackedAdapterProvider,
4552
+ revokeConnection,
4396
4553
  InMemoryIntegrationAuditStore,
4397
4554
  createIntegrationAuditEvent,
4398
4555
  createAuditingActionGuard,
@@ -4415,15 +4572,6 @@ export {
4415
4572
  createTangleIntegrationsClient,
4416
4573
  renderConsentSummary,
4417
4574
  renderApprovalCopy,
4418
- createConnectorAdapterProvider,
4419
- adapterManifestsToConnectors,
4420
- createConnectorAdapterCatalogSource,
4421
- manifestToConnector,
4422
- InMemoryIntegrationSecretStore,
4423
- createConnectionCredentialResolver,
4424
- resolveConnectionCredentials,
4425
- createCredentialBackedAdapterProvider,
4426
- revokeConnection,
4427
4575
  discoverWorkspaceCapabilities,
4428
4576
  filterDiscoveryByWorkspaceScopes,
4429
4577
  InMemoryIntegrationEventStore,
@@ -4484,4 +4632,4 @@ export {
4484
4632
  signCapability,
4485
4633
  verifyCapabilityToken
4486
4634
  };
4487
- //# sourceMappingURL=chunk-TUX6MJJ4.js.map
4635
+ //# sourceMappingURL=chunk-M2RFFAMB.js.map