@tangle-network/agent-integrations 0.28.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.
- package/README.md +40 -0
- package/dist/bin/tangle-catalog-runtime.js +7 -6
- package/dist/bin/tangle-catalog-runtime.js.map +1 -1
- package/dist/catalog.d.ts +2 -2
- package/dist/catalog.js +11 -6
- package/dist/{chunk-SVQ4PHDZ.js → chunk-5ASL5XNX.js} +2 -2
- package/dist/{chunk-P24T3MLM.js → chunk-DACSERTI.js} +2 -2
- package/dist/chunk-FDZIQVK7.js +284 -0
- package/dist/chunk-FDZIQVK7.js.map +1 -0
- package/dist/{chunk-JU25UDN2.js → chunk-JOILC44P.js} +11 -5
- package/dist/chunk-JOILC44P.js.map +1 -0
- package/dist/{chunk-UWRYFPJW.js → chunk-M2RFFAMB.js} +559 -411
- package/dist/chunk-M2RFFAMB.js.map +1 -0
- package/dist/{chunk-4JQ754PA.js → chunk-VVC7U7W7.js} +28 -1
- package/dist/{chunk-4JQ754PA.js.map → chunk-VVC7U7W7.js.map} +1 -1
- package/dist/{chunk-ATYHZXLL.js → chunk-Y6O3MIBW.js} +1 -1
- package/dist/chunk-Y6O3MIBW.js.map +1 -0
- package/dist/connect/index.d.ts +1 -1
- package/dist/connect/index.js +2 -2
- package/dist/connectors/adapters/index.d.ts +2 -2
- package/dist/connectors/adapters/index.js +2 -2
- package/dist/connectors/index.d.ts +1 -1
- package/dist/connectors/index.js +2 -2
- package/dist/consumer.d.ts +8 -0
- package/dist/consumer.js +12 -0
- package/dist/consumer.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +29 -11
- package/dist/middleware/index.d.ts +1 -1
- package/dist/middleware/index.js +2 -2
- package/dist/registry.d.ts +439 -92
- package/dist/registry.js +9 -6
- package/dist/runtime.d.ts +2 -2
- package/dist/runtime.js +7 -6
- package/dist/specs.d.ts +2 -2
- package/dist/specs.js +3 -1
- package/dist/tangle-catalog-runtime.d.ts +2 -2
- package/dist/tangle-catalog-runtime.js +7 -6
- package/dist/{tangle-id-CTU4kGId.d.ts → tangle-id-C6s2NT2r.d.ts} +7 -0
- package/docs/integration-execution-audit.md +1 -1
- package/package.json +23 -10
- package/dist/chunk-ATYHZXLL.js.map +0 -1
- package/dist/chunk-JU25UDN2.js.map +0 -1
- package/dist/chunk-UWRYFPJW.js.map +0 -1
- /package/dist/{chunk-SVQ4PHDZ.js.map → chunk-5ASL5XNX.js.map} +0 -0
- /package/dist/{chunk-P24T3MLM.js.map → chunk-DACSERTI.js.map} +0 -0
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
import {
|
|
6
6
|
integrationSpecToConnector,
|
|
7
7
|
listIntegrationSpecs
|
|
8
|
-
} from "./chunk-
|
|
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.
|
|
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: !
|
|
154
|
-
supportTier:
|
|
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
|
|
1037
|
-
return
|
|
1079
|
+
function defaultIdempotencyKey(action) {
|
|
1080
|
+
return `${action}:${Date.now()}:${Math.random().toString(36).slice(2)}`;
|
|
1038
1081
|
}
|
|
1039
|
-
function
|
|
1040
|
-
if (
|
|
1082
|
+
function readProcessEnv() {
|
|
1083
|
+
if (typeof process !== "undefined" && process.env) return process.env;
|
|
1041
1084
|
return {};
|
|
1042
1085
|
}
|
|
1043
1086
|
|
|
1044
|
-
// src/
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
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
|
-
|
|
1054
|
-
|
|
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
|
-
|
|
1058
|
-
|
|
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
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
const
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
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
|
|
1115
|
-
return
|
|
1116
|
-
|
|
1117
|
-
|
|
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
|
|
1131
|
-
return
|
|
1153
|
+
function titleize(value) {
|
|
1154
|
+
return value.split(/[-_.]/g).filter(Boolean).map((part) => part[0].toUpperCase() + part.slice(1)).join(" ");
|
|
1132
1155
|
}
|
|
1133
|
-
function
|
|
1134
|
-
return
|
|
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
|
-
|
|
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(
|
|
2705
|
-
const expected =
|
|
2706
|
-
return
|
|
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 =
|
|
2770
|
+
const actions = piece.actions;
|
|
2710
2771
|
const explicit = aliases[invocation.connector.id]?.[invocation.action.id];
|
|
2711
|
-
|
|
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
|
-
]
|
|
2782
|
+
], connectorId);
|
|
2717
2783
|
for (const action of actions) {
|
|
2718
|
-
const names = [action.name, action.displayName]
|
|
2719
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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-
|
|
4635
|
+
//# sourceMappingURL=chunk-M2RFFAMB.js.map
|