@tangle-network/agent-integrations 0.13.0 → 0.15.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 +63 -0
- package/dist/index.d.ts +554 -54
- package/dist/index.js +1271 -190
- package/dist/index.js.map +1 -1
- package/docs/architecture.md +7 -0
- package/docs/production-completion-checklist.md +63 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import { createHmac as createHmac3, randomUUID as
|
|
2
|
+
import { createHmac as createHmac3, randomUUID as randomUUID3, timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
3
3
|
|
|
4
4
|
// src/activepieces-catalog.ts
|
|
5
5
|
import { readFileSync } from "fs";
|
|
@@ -128,21 +128,24 @@ function buildActivepiecesConnectors(options = {}) {
|
|
|
128
128
|
const override = getActivepiecesOverride(entry.id);
|
|
129
129
|
const category = override?.category ?? entry.category;
|
|
130
130
|
const scopes = [`${entry.id}.read`, `${entry.id}.write`];
|
|
131
|
-
const
|
|
131
|
+
const catalogActions = entry.actions.length > 0 ? entry.actions.map((action) => toAction(applyActionOverride(action, override), scopes, dataClassFor(category))) : defaultActions(entry.id, scopes, dataClassFor(category));
|
|
132
|
+
const catalogTriggers = entry.triggers.map((trigger) => toTrigger(trigger, scopes, dataClassFor(category)));
|
|
132
133
|
return {
|
|
133
134
|
id: entry.id,
|
|
134
135
|
providerId,
|
|
135
136
|
title: entry.title,
|
|
136
137
|
category,
|
|
137
138
|
auth: entry.auth,
|
|
138
|
-
scopes,
|
|
139
|
-
actions,
|
|
140
|
-
triggers:
|
|
139
|
+
scopes: options.includeCatalogActions ? scopes : [],
|
|
140
|
+
actions: options.includeCatalogActions ? catalogActions : [],
|
|
141
|
+
triggers: options.includeCatalogActions ? catalogTriggers : void 0,
|
|
141
142
|
metadata: {
|
|
142
143
|
source: "activepieces-community",
|
|
143
144
|
executable: false,
|
|
144
145
|
runtime: "activepieces-piece",
|
|
145
146
|
catalogOnly: true,
|
|
147
|
+
catalogActionCount: catalogActions.length,
|
|
148
|
+
catalogTriggerCount: catalogTriggers.length,
|
|
146
149
|
npmPackage: entry.npmPackage,
|
|
147
150
|
version: entry.version,
|
|
148
151
|
license: entry.source.license,
|
|
@@ -490,15 +493,15 @@ function actionPack(pack, scopes) {
|
|
|
490
493
|
}
|
|
491
494
|
function triggersFor(pack, scopes) {
|
|
492
495
|
const readScope = scopes.find((scope) => scope.endsWith(".read")) ?? scopes[0];
|
|
493
|
-
const
|
|
494
|
-
if (pack === "email") return [{ id: "message.received", title: "Message received", requiredScopes, dataClass: "private" }];
|
|
495
|
-
if (pack === "calendar") return [{ id: "event.changed", title: "Event changed", requiredScopes, dataClass: "private" }];
|
|
496
|
-
if (pack === "chat") return [{ id: "message.posted", title: "Message posted", requiredScopes, dataClass: "private" }];
|
|
497
|
-
if (pack === "crm") return [{ id: "record.changed", title: "Record changed", requiredScopes, dataClass: "private" }];
|
|
498
|
-
if (pack === "support") return [{ id: "ticket.changed", title: "Ticket changed", requiredScopes, dataClass: "private" }];
|
|
499
|
-
if (pack === "commerce") return [{ id: "order.changed", title: "Order changed", requiredScopes, dataClass: "sensitive" }];
|
|
500
|
-
if (pack === "finance") return [{ id: "transaction.changed", title: "Transaction changed", requiredScopes, dataClass: "sensitive" }];
|
|
501
|
-
if (pack === "workflow" || pack === "webhook") return [{ id: "event.received", title: "Event received", requiredScopes, dataClass: "internal" }];
|
|
496
|
+
const requiredScopes2 = readScope ? [readScope] : [];
|
|
497
|
+
if (pack === "email") return [{ id: "message.received", title: "Message received", requiredScopes: requiredScopes2, dataClass: "private" }];
|
|
498
|
+
if (pack === "calendar") return [{ id: "event.changed", title: "Event changed", requiredScopes: requiredScopes2, dataClass: "private" }];
|
|
499
|
+
if (pack === "chat") return [{ id: "message.posted", title: "Message posted", requiredScopes: requiredScopes2, dataClass: "private" }];
|
|
500
|
+
if (pack === "crm") return [{ id: "record.changed", title: "Record changed", requiredScopes: requiredScopes2, dataClass: "private" }];
|
|
501
|
+
if (pack === "support") return [{ id: "ticket.changed", title: "Ticket changed", requiredScopes: requiredScopes2, dataClass: "private" }];
|
|
502
|
+
if (pack === "commerce") return [{ id: "order.changed", title: "Order changed", requiredScopes: requiredScopes2, dataClass: "sensitive" }];
|
|
503
|
+
if (pack === "finance") return [{ id: "transaction.changed", title: "Transaction changed", requiredScopes: requiredScopes2, dataClass: "sensitive" }];
|
|
504
|
+
if (pack === "workflow" || pack === "webhook") return [{ id: "event.received", title: "Event received", requiredScopes: requiredScopes2, dataClass: "internal" }];
|
|
502
505
|
return void 0;
|
|
503
506
|
}
|
|
504
507
|
function scopesFor(id, pack) {
|
|
@@ -1175,6 +1178,8 @@ function inferIntegrationSupportTier(connector) {
|
|
|
1175
1178
|
if (metadata.source === "first-party-adapter" || connector.providerId === "first-party") return "firstPartyExecutable";
|
|
1176
1179
|
if (metadata.source === "gateway-catalog" && metadata.executable === true) return "gatewayExecutable";
|
|
1177
1180
|
if (metadata.source === "integration-spec") return "setupReady";
|
|
1181
|
+
if (metadata.source === "coverage-catalog" || metadata.source === "activepieces-community" || metadata.catalogOnly === true) return "catalogOnly";
|
|
1182
|
+
if (connector.actions.length > 0) return "gatewayExecutable";
|
|
1178
1183
|
return "catalogOnly";
|
|
1179
1184
|
}
|
|
1180
1185
|
function registryEntry(canonicalId, candidates, precedence, aliases) {
|
|
@@ -1221,7 +1226,7 @@ function registryEntry(canonicalId, candidates, precedence, aliases) {
|
|
|
1221
1226
|
sources,
|
|
1222
1227
|
conflicts,
|
|
1223
1228
|
toolBindable: actions.length > 0,
|
|
1224
|
-
catalogOnlyActionCount: ordered.filter((candidate) => candidate.supportTier === "catalogOnly").reduce((sum, candidate) => sum + candidate.connector
|
|
1229
|
+
catalogOnlyActionCount: ordered.filter((candidate) => candidate.supportTier === "catalogOnly").reduce((sum, candidate) => sum + catalogActionCount(candidate.connector), 0)
|
|
1225
1230
|
}
|
|
1226
1231
|
}
|
|
1227
1232
|
}
|
|
@@ -1252,6 +1257,10 @@ function toolBindableCandidates(candidates) {
|
|
|
1252
1257
|
const bindable = candidates.filter((candidate) => candidate.supportTier !== "catalogOnly");
|
|
1253
1258
|
return bindable.length > 0 ? bindable : [];
|
|
1254
1259
|
}
|
|
1260
|
+
function catalogActionCount(connector) {
|
|
1261
|
+
const value = connector.metadata?.catalogActionCount;
|
|
1262
|
+
return typeof value === "number" ? value : connector.actions.length;
|
|
1263
|
+
}
|
|
1255
1264
|
function conflictDiagnostics(candidates) {
|
|
1256
1265
|
return [
|
|
1257
1266
|
conflictFor("auth", candidates.map((candidate) => ({
|
|
@@ -1271,14 +1280,877 @@ function conflictFor(field, values) {
|
|
|
1271
1280
|
if (uniqueValues.size <= 1) return void 0;
|
|
1272
1281
|
return { field, values };
|
|
1273
1282
|
}
|
|
1274
|
-
function isSupportTier(value) {
|
|
1275
|
-
return value === "catalogOnly" || value === "setupReady" || value === "gatewayExecutable" || value === "firstPartyExecutable" || value === "sandboxExecutable";
|
|
1283
|
+
function isSupportTier(value) {
|
|
1284
|
+
return value === "catalogOnly" || value === "setupReady" || value === "gatewayExecutable" || value === "firstPartyExecutable" || value === "sandboxExecutable";
|
|
1285
|
+
}
|
|
1286
|
+
function slug(value) {
|
|
1287
|
+
return value.trim().toLowerCase().replace(/&/g, "and").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
1288
|
+
}
|
|
1289
|
+
function unique(values) {
|
|
1290
|
+
return [...new Set(values)];
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
// src/audit.ts
|
|
1294
|
+
import { randomUUID } from "crypto";
|
|
1295
|
+
var InMemoryIntegrationAuditStore = class {
|
|
1296
|
+
events = [];
|
|
1297
|
+
record(event) {
|
|
1298
|
+
this.events.push(event);
|
|
1299
|
+
}
|
|
1300
|
+
list(filter = {}) {
|
|
1301
|
+
return this.events.filter((event) => matchesFilter(event, filter));
|
|
1302
|
+
}
|
|
1303
|
+
};
|
|
1304
|
+
function createIntegrationAuditEvent(input) {
|
|
1305
|
+
const occurredAt = input.occurredAt instanceof Date ? input.occurredAt.toISOString() : input.occurredAt ?? (input.now?.() ?? /* @__PURE__ */ new Date()).toISOString();
|
|
1306
|
+
return {
|
|
1307
|
+
...input,
|
|
1308
|
+
id: input.id ?? `audit_${randomUUID()}`,
|
|
1309
|
+
occurredAt,
|
|
1310
|
+
metadata: input.metadata ? redactUnknown(input.metadata) : void 0
|
|
1311
|
+
};
|
|
1312
|
+
}
|
|
1313
|
+
function createAuditingActionGuard(options) {
|
|
1314
|
+
const now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
1315
|
+
return {
|
|
1316
|
+
async invokeAction(ctx, proceed) {
|
|
1317
|
+
const startedAt = now();
|
|
1318
|
+
try {
|
|
1319
|
+
const result = await proceed();
|
|
1320
|
+
await options.sink.record(actionEvent({
|
|
1321
|
+
ctx,
|
|
1322
|
+
request: ctx.request,
|
|
1323
|
+
result,
|
|
1324
|
+
type: result.ok ? "action.invoked" : "action.failed",
|
|
1325
|
+
subject: options.subject,
|
|
1326
|
+
occurredAt: startedAt,
|
|
1327
|
+
includeInputPreview: options.includeInputPreview
|
|
1328
|
+
}));
|
|
1329
|
+
return result;
|
|
1330
|
+
} catch (error) {
|
|
1331
|
+
await options.sink.record(actionEvent({
|
|
1332
|
+
ctx,
|
|
1333
|
+
request: ctx.request,
|
|
1334
|
+
type: "action.failed",
|
|
1335
|
+
subject: options.subject,
|
|
1336
|
+
occurredAt: startedAt,
|
|
1337
|
+
includeInputPreview: options.includeInputPreview,
|
|
1338
|
+
message: error instanceof Error ? error.message : "Integration action failed."
|
|
1339
|
+
}));
|
|
1340
|
+
throw error;
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
};
|
|
1344
|
+
}
|
|
1345
|
+
function sanitizeAuditConnection(connection) {
|
|
1346
|
+
return {
|
|
1347
|
+
id: connection.id,
|
|
1348
|
+
owner: connection.owner,
|
|
1349
|
+
providerId: connection.providerId,
|
|
1350
|
+
connectorId: connection.connectorId,
|
|
1351
|
+
status: connection.status,
|
|
1352
|
+
grantedScopes: connection.grantedScopes,
|
|
1353
|
+
account: connection.account,
|
|
1354
|
+
hasSecretRef: Boolean(connection.secretRef),
|
|
1355
|
+
createdAt: connection.createdAt,
|
|
1356
|
+
updatedAt: connection.updatedAt,
|
|
1357
|
+
expiresAt: connection.expiresAt,
|
|
1358
|
+
lastUsedAt: connection.lastUsedAt
|
|
1359
|
+
};
|
|
1360
|
+
}
|
|
1361
|
+
function actionEvent(input) {
|
|
1362
|
+
return createIntegrationAuditEvent({
|
|
1363
|
+
type: input.type,
|
|
1364
|
+
occurredAt: input.occurredAt,
|
|
1365
|
+
actor: input.subject ?? input.ctx.connection.owner,
|
|
1366
|
+
connectionId: input.ctx.connection.id,
|
|
1367
|
+
providerId: input.ctx.connection.providerId,
|
|
1368
|
+
connectorId: input.ctx.connection.connectorId,
|
|
1369
|
+
action: input.request.action,
|
|
1370
|
+
risk: input.ctx.action?.risk,
|
|
1371
|
+
dataClass: input.ctx.action?.dataClass,
|
|
1372
|
+
ok: input.result?.ok ?? false,
|
|
1373
|
+
message: input.message,
|
|
1374
|
+
metadata: {
|
|
1375
|
+
idempotencyKey: input.request.idempotencyKey,
|
|
1376
|
+
dryRun: input.request.dryRun,
|
|
1377
|
+
externalId: input.result?.externalId,
|
|
1378
|
+
warnings: input.result?.warnings,
|
|
1379
|
+
inputPreview: input.includeInputPreview ? redactUnknown(input.request.input) : void 0
|
|
1380
|
+
}
|
|
1381
|
+
});
|
|
1382
|
+
}
|
|
1383
|
+
function matchesFilter(event, filter) {
|
|
1384
|
+
if (filter.type && event.type !== filter.type) return false;
|
|
1385
|
+
if (filter.actor && (!event.actor || event.actor.type !== filter.actor.type || event.actor.id !== filter.actor.id)) return false;
|
|
1386
|
+
if (filter.connectionId && event.connectionId !== filter.connectionId) return false;
|
|
1387
|
+
if (filter.providerId && event.providerId !== filter.providerId) return false;
|
|
1388
|
+
if (filter.connectorId && event.connectorId !== filter.connectorId) return false;
|
|
1389
|
+
if (filter.action && event.action !== filter.action) return false;
|
|
1390
|
+
return true;
|
|
1391
|
+
}
|
|
1392
|
+
function redactUnknown(value) {
|
|
1393
|
+
if (Array.isArray(value)) return value.map(redactUnknown);
|
|
1394
|
+
if (!value || typeof value !== "object") return value;
|
|
1395
|
+
const out = {};
|
|
1396
|
+
for (const [key, child] of Object.entries(value)) {
|
|
1397
|
+
if (/token|secret|password|authorization|api[_-]?key|credential|refresh/i.test(key)) {
|
|
1398
|
+
out[key] = "[REDACTED]";
|
|
1399
|
+
} else {
|
|
1400
|
+
out[key] = redactUnknown(child);
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
return out;
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
// src/approval.ts
|
|
1407
|
+
import { createHash } from "crypto";
|
|
1408
|
+
var InMemoryIntegrationApprovalStore = class {
|
|
1409
|
+
records = /* @__PURE__ */ new Map();
|
|
1410
|
+
get(approvalId) {
|
|
1411
|
+
return this.records.get(approvalId);
|
|
1412
|
+
}
|
|
1413
|
+
put(record) {
|
|
1414
|
+
this.records.set(record.id, record);
|
|
1415
|
+
}
|
|
1416
|
+
list(filter = {}) {
|
|
1417
|
+
return [...this.records.values()].filter((record) => matchesFilter2(record, filter));
|
|
1418
|
+
}
|
|
1419
|
+
};
|
|
1420
|
+
var ApprovalBackedPolicyEngine = class {
|
|
1421
|
+
base;
|
|
1422
|
+
store;
|
|
1423
|
+
audit;
|
|
1424
|
+
now;
|
|
1425
|
+
approvalTtlMs;
|
|
1426
|
+
constructor(options) {
|
|
1427
|
+
this.base = options.base;
|
|
1428
|
+
this.store = options.store;
|
|
1429
|
+
this.audit = options.audit;
|
|
1430
|
+
this.now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
1431
|
+
this.approvalTtlMs = options.approvalTtlMs;
|
|
1432
|
+
}
|
|
1433
|
+
async decide(ctx) {
|
|
1434
|
+
const approved = await this.findApprovedRecord(ctx);
|
|
1435
|
+
if (approved) return { decision: "allow", reason: `Approved by ${approved.resolvedBy?.type ?? "actor"} ${approved.resolvedBy?.id ?? "unknown"}.`, metadata: { approvalId: approved.id } };
|
|
1436
|
+
const decision = await this.base.decide(ctx);
|
|
1437
|
+
if (decision.decision !== "require_approval") return decision;
|
|
1438
|
+
const requestedAt = decision.approval.requestedAt;
|
|
1439
|
+
const expiresAt = this.approvalTtlMs ? new Date(Date.parse(requestedAt) + this.approvalTtlMs).toISOString() : void 0;
|
|
1440
|
+
const record = {
|
|
1441
|
+
id: decision.approval.id,
|
|
1442
|
+
request: decision.approval,
|
|
1443
|
+
status: "pending",
|
|
1444
|
+
requestedAt,
|
|
1445
|
+
expiresAt,
|
|
1446
|
+
metadata: { ...decision.metadata ?? {}, inputHash: approvalInputHash(ctx.request.input) }
|
|
1447
|
+
};
|
|
1448
|
+
await this.store.put(record);
|
|
1449
|
+
await this.audit?.record(createIntegrationAuditEvent({
|
|
1450
|
+
type: "approval.requested",
|
|
1451
|
+
actor: ctx.subject,
|
|
1452
|
+
connectionId: ctx.connection.id,
|
|
1453
|
+
providerId: ctx.connection.providerId,
|
|
1454
|
+
connectorId: ctx.connection.connectorId,
|
|
1455
|
+
action: ctx.request.action,
|
|
1456
|
+
risk: ctx.action?.risk,
|
|
1457
|
+
dataClass: ctx.action?.dataClass,
|
|
1458
|
+
message: decision.reason,
|
|
1459
|
+
metadata: { approvalId: record.id },
|
|
1460
|
+
now: this.now
|
|
1461
|
+
}));
|
|
1462
|
+
return decision;
|
|
1463
|
+
}
|
|
1464
|
+
async findApprovedRecord(ctx) {
|
|
1465
|
+
const approvalId = typeof ctx.request.metadata?.approvalId === "string" ? ctx.request.metadata.approvalId : void 0;
|
|
1466
|
+
if (!approvalId) return void 0;
|
|
1467
|
+
const record = await this.store.get(approvalId);
|
|
1468
|
+
if (!record || record.status !== "approved") return void 0;
|
|
1469
|
+
if (record.expiresAt && Date.parse(record.expiresAt) <= this.now().getTime()) {
|
|
1470
|
+
await this.store.put({ ...record, status: "expired" });
|
|
1471
|
+
return void 0;
|
|
1472
|
+
}
|
|
1473
|
+
if (!approvalMatches(record, ctx)) return void 0;
|
|
1474
|
+
return record;
|
|
1475
|
+
}
|
|
1476
|
+
};
|
|
1477
|
+
function createApprovalBackedPolicyEngine(options) {
|
|
1478
|
+
return new ApprovalBackedPolicyEngine(options);
|
|
1479
|
+
}
|
|
1480
|
+
async function resolveIntegrationApproval(input) {
|
|
1481
|
+
const record = await input.store.get(input.approvalId);
|
|
1482
|
+
if (!record) throw new Error(`Approval ${input.approvalId} not found.`);
|
|
1483
|
+
const now = input.now ?? (() => /* @__PURE__ */ new Date());
|
|
1484
|
+
const next = {
|
|
1485
|
+
...record,
|
|
1486
|
+
status: input.approved ? "approved" : "denied",
|
|
1487
|
+
resolvedAt: now().toISOString(),
|
|
1488
|
+
resolvedBy: input.resolvedBy,
|
|
1489
|
+
reason: input.reason,
|
|
1490
|
+
metadata: { ...record.metadata ?? {}, ...input.metadata ?? {} }
|
|
1491
|
+
};
|
|
1492
|
+
await input.store.put(next);
|
|
1493
|
+
await input.audit?.record(createIntegrationAuditEvent({
|
|
1494
|
+
type: "approval.resolved",
|
|
1495
|
+
actor: input.resolvedBy,
|
|
1496
|
+
connectionId: record.request.connectionId,
|
|
1497
|
+
providerId: record.request.providerId,
|
|
1498
|
+
connectorId: record.request.connectorId,
|
|
1499
|
+
action: record.request.action,
|
|
1500
|
+
risk: record.request.risk,
|
|
1501
|
+
dataClass: record.request.dataClass,
|
|
1502
|
+
ok: input.approved,
|
|
1503
|
+
message: input.reason,
|
|
1504
|
+
metadata: { approvalId: record.id, status: next.status },
|
|
1505
|
+
now
|
|
1506
|
+
}));
|
|
1507
|
+
return next;
|
|
1508
|
+
}
|
|
1509
|
+
function approvalMatches(record, ctx) {
|
|
1510
|
+
return record.request.connectionId === ctx.connection.id && record.request.providerId === ctx.connection.providerId && record.request.connectorId === ctx.connection.connectorId && record.request.action === ctx.request.action && record.request.actor.type === ctx.subject.type && record.request.actor.id === ctx.subject.id && record.metadata?.inputHash === approvalInputHash(ctx.request.input);
|
|
1511
|
+
}
|
|
1512
|
+
function matchesFilter2(record, filter) {
|
|
1513
|
+
if (filter.status && record.status !== filter.status) return false;
|
|
1514
|
+
if (filter.connectionId && record.request.connectionId !== filter.connectionId) return false;
|
|
1515
|
+
if (filter.connectorId && record.request.connectorId !== filter.connectorId) return false;
|
|
1516
|
+
if (filter.action && record.request.action !== filter.action) return false;
|
|
1517
|
+
if (filter.actor && (record.request.actor.type !== filter.actor.type || record.request.actor.id !== filter.actor.id)) return false;
|
|
1518
|
+
return true;
|
|
1519
|
+
}
|
|
1520
|
+
function approvalInputHash(input) {
|
|
1521
|
+
return createHash("sha256").update(JSON.stringify(input ?? null)).digest("base64url");
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
// src/bridge.ts
|
|
1525
|
+
var DEFAULT_INTEGRATION_BRIDGE_ENV = "TANGLE_INTEGRATION_BUNDLE";
|
|
1526
|
+
function buildIntegrationBridgePayload(bundle) {
|
|
1527
|
+
return {
|
|
1528
|
+
version: 1,
|
|
1529
|
+
manifestId: bundle.manifestId,
|
|
1530
|
+
subject: bundle.subject,
|
|
1531
|
+
expiresAt: bundle.expiresAt,
|
|
1532
|
+
tools: bundle.tools.flatMap((tool) => {
|
|
1533
|
+
const binding = bundle.capabilities.find(
|
|
1534
|
+
(candidate) => candidate.connectorId === tool.connectorId && candidate.connectionId && candidate.allowedActions.includes(tool.action.id)
|
|
1535
|
+
);
|
|
1536
|
+
if (!binding) return [];
|
|
1537
|
+
return [{
|
|
1538
|
+
name: tool.name,
|
|
1539
|
+
title: tool.title,
|
|
1540
|
+
connectorId: tool.connectorId,
|
|
1541
|
+
connectionId: binding.connectionId,
|
|
1542
|
+
action: tool.action.id,
|
|
1543
|
+
risk: tool.risk,
|
|
1544
|
+
dataClass: tool.dataClass,
|
|
1545
|
+
requiredScopes: tool.requiredScopes,
|
|
1546
|
+
capabilityToken: binding.capability.token
|
|
1547
|
+
}];
|
|
1548
|
+
})
|
|
1549
|
+
};
|
|
1550
|
+
}
|
|
1551
|
+
function encodeIntegrationBridgePayload(payload) {
|
|
1552
|
+
return Buffer.from(JSON.stringify(payload), "utf8").toString("base64url");
|
|
1553
|
+
}
|
|
1554
|
+
function decodeIntegrationBridgePayload(encoded) {
|
|
1555
|
+
const parsed = JSON.parse(Buffer.from(encoded, "base64url").toString("utf8"));
|
|
1556
|
+
assertBridgePayload(parsed);
|
|
1557
|
+
return parsed;
|
|
1558
|
+
}
|
|
1559
|
+
function buildIntegrationBridgeEnvironment(bundle, options = {}) {
|
|
1560
|
+
const envVar = options.envVar ?? DEFAULT_INTEGRATION_BRIDGE_ENV;
|
|
1561
|
+
return {
|
|
1562
|
+
[envVar]: encodeIntegrationBridgePayload(buildIntegrationBridgePayload(bundle))
|
|
1563
|
+
};
|
|
1564
|
+
}
|
|
1565
|
+
function parseIntegrationBridgeEnvironment(env, options = {}) {
|
|
1566
|
+
const envVar = options.envVar ?? DEFAULT_INTEGRATION_BRIDGE_ENV;
|
|
1567
|
+
const encoded = env[envVar];
|
|
1568
|
+
if (!encoded) throw new Error(`Missing ${envVar}.`);
|
|
1569
|
+
return decodeIntegrationBridgePayload(encoded);
|
|
1570
|
+
}
|
|
1571
|
+
function redactIntegrationBridgePayload(payload) {
|
|
1572
|
+
return {
|
|
1573
|
+
...payload,
|
|
1574
|
+
tools: payload.tools.map((tool) => ({
|
|
1575
|
+
...tool,
|
|
1576
|
+
capabilityToken: "[REDACTED]"
|
|
1577
|
+
}))
|
|
1578
|
+
};
|
|
1579
|
+
}
|
|
1580
|
+
function assertBridgePayload(value) {
|
|
1581
|
+
if (!value || typeof value !== "object") throw new Error("Invalid integration bridge payload.");
|
|
1582
|
+
const payload = value;
|
|
1583
|
+
if (payload.version !== 1) throw new Error("Unsupported integration bridge payload version.");
|
|
1584
|
+
if (typeof payload.manifestId !== "string") throw new Error("Invalid integration bridge manifestId.");
|
|
1585
|
+
if (typeof payload.expiresAt !== "string") throw new Error("Invalid integration bridge expiresAt.");
|
|
1586
|
+
if (!Array.isArray(payload.tools)) throw new Error("Invalid integration bridge tools.");
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
// src/adapter-provider.ts
|
|
1590
|
+
function createConnectorAdapterProvider(options) {
|
|
1591
|
+
const providerId = options.id ?? "first-party";
|
|
1592
|
+
const now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
1593
|
+
const adapters = /* @__PURE__ */ new Map();
|
|
1594
|
+
for (const adapter of options.adapters) {
|
|
1595
|
+
adapters.set(adapter.manifest.kind, adapter);
|
|
1596
|
+
}
|
|
1597
|
+
return {
|
|
1598
|
+
id: providerId,
|
|
1599
|
+
kind: options.kind ?? "first_party",
|
|
1600
|
+
listConnectors: () => [...adapters.values()].map((adapter) => manifestToConnector(providerId, adapter)),
|
|
1601
|
+
async invokeAction(connection, request) {
|
|
1602
|
+
const adapter = adapters.get(connection.connectorId);
|
|
1603
|
+
if (!adapter) {
|
|
1604
|
+
throw new IntegrationError(`Connector adapter ${connection.connectorId} not found.`, "connector_not_found");
|
|
1605
|
+
}
|
|
1606
|
+
const capability = adapter.manifest.capabilities.find((candidate) => candidate.name === request.action);
|
|
1607
|
+
if (!capability) {
|
|
1608
|
+
throw new IntegrationError(`Capability ${request.action} is not defined by ${connection.connectorId}.`, "action_not_found");
|
|
1609
|
+
}
|
|
1610
|
+
const source = await options.resolveDataSource(connection);
|
|
1611
|
+
const invocation = {
|
|
1612
|
+
source,
|
|
1613
|
+
capabilityName: request.action,
|
|
1614
|
+
args: toRecord(request.input),
|
|
1615
|
+
idempotencyKey: request.idempotencyKey ?? `idem_${connection.id}_${request.action}_${now().getTime()}`,
|
|
1616
|
+
expectedEtag: typeof request.metadata?.expectedEtag === "string" ? request.metadata.expectedEtag : void 0,
|
|
1617
|
+
callSessionId: typeof request.metadata?.callSessionId === "string" ? request.metadata.callSessionId : void 0
|
|
1618
|
+
};
|
|
1619
|
+
if (capability.class === "read") {
|
|
1620
|
+
if (!adapter.executeRead) {
|
|
1621
|
+
throw new IntegrationError(`Connector ${connection.connectorId} does not implement reads.`, "action_not_found");
|
|
1622
|
+
}
|
|
1623
|
+
const result = await adapter.executeRead(invocation);
|
|
1624
|
+
return readResultToAction(request, result);
|
|
1625
|
+
}
|
|
1626
|
+
if (capability.class === "mutation") {
|
|
1627
|
+
if (!adapter.executeMutation) {
|
|
1628
|
+
throw new IntegrationError(`Connector ${connection.connectorId} does not implement mutations.`, "action_not_found");
|
|
1629
|
+
}
|
|
1630
|
+
const result = await adapter.executeMutation(invocation);
|
|
1631
|
+
return mutationResultToAction(request, result);
|
|
1632
|
+
}
|
|
1633
|
+
throw new IntegrationError(`Capability ${request.action} is not invokable as an action.`, "action_not_found");
|
|
1634
|
+
}
|
|
1635
|
+
};
|
|
1636
|
+
}
|
|
1637
|
+
function manifestToConnector(providerId, adapter) {
|
|
1638
|
+
const manifest = adapter.manifest;
|
|
1639
|
+
return {
|
|
1640
|
+
id: manifest.kind,
|
|
1641
|
+
providerId,
|
|
1642
|
+
title: manifest.displayName,
|
|
1643
|
+
category: mapCategory(manifest.category),
|
|
1644
|
+
auth: mapAuth(manifest.auth.kind),
|
|
1645
|
+
scopes: manifest.auth.kind === "oauth2" ? manifest.auth.scopes : [],
|
|
1646
|
+
actions: manifest.capabilities.filter((capability) => capability.class === "read" || capability.class === "mutation").map((capability) => ({
|
|
1647
|
+
id: capability.name,
|
|
1648
|
+
title: titleFromName(capability.name),
|
|
1649
|
+
risk: capability.class === "read" ? "read" : capability.externalEffect ? "destructive" : "write",
|
|
1650
|
+
requiredScopes: capability.requiredScopes ?? [],
|
|
1651
|
+
dataClass: inferDataClass(manifest.category),
|
|
1652
|
+
description: capability.description,
|
|
1653
|
+
approvalRequired: capability.class === "mutation",
|
|
1654
|
+
inputSchema: capability.parameters
|
|
1655
|
+
})),
|
|
1656
|
+
metadata: {
|
|
1657
|
+
source: "first-party-adapter",
|
|
1658
|
+
supportTier: "firstPartyExecutable",
|
|
1659
|
+
executable: true
|
|
1660
|
+
}
|
|
1661
|
+
};
|
|
1662
|
+
}
|
|
1663
|
+
function readResultToAction(request, result) {
|
|
1664
|
+
return {
|
|
1665
|
+
ok: true,
|
|
1666
|
+
action: request.action,
|
|
1667
|
+
output: result.data,
|
|
1668
|
+
metadata: {
|
|
1669
|
+
etag: result.etag,
|
|
1670
|
+
fetchedAt: result.fetchedAt
|
|
1671
|
+
}
|
|
1672
|
+
};
|
|
1673
|
+
}
|
|
1674
|
+
function mutationResultToAction(request, result) {
|
|
1675
|
+
if (result.status === "committed") {
|
|
1676
|
+
return {
|
|
1677
|
+
ok: true,
|
|
1678
|
+
action: request.action,
|
|
1679
|
+
output: result.data,
|
|
1680
|
+
metadata: {
|
|
1681
|
+
etagAfter: result.etagAfter,
|
|
1682
|
+
committedAt: result.committedAt,
|
|
1683
|
+
idempotentReplay: result.idempotentReplay
|
|
1684
|
+
}
|
|
1685
|
+
};
|
|
1686
|
+
}
|
|
1687
|
+
if (result.status === "conflict") {
|
|
1688
|
+
return {
|
|
1689
|
+
ok: false,
|
|
1690
|
+
action: request.action,
|
|
1691
|
+
output: {
|
|
1692
|
+
conflict: true,
|
|
1693
|
+
message: result.message,
|
|
1694
|
+
alternatives: result.alternatives,
|
|
1695
|
+
currentState: result.currentState
|
|
1696
|
+
}
|
|
1697
|
+
};
|
|
1698
|
+
}
|
|
1699
|
+
return {
|
|
1700
|
+
ok: false,
|
|
1701
|
+
action: request.action,
|
|
1702
|
+
output: {
|
|
1703
|
+
rateLimited: true,
|
|
1704
|
+
retryAfterMs: result.retryAfterMs,
|
|
1705
|
+
message: result.message
|
|
1706
|
+
}
|
|
1707
|
+
};
|
|
1708
|
+
}
|
|
1709
|
+
function mapAuth(kind) {
|
|
1710
|
+
if (kind === "oauth2") return "oauth2";
|
|
1711
|
+
if (kind === "api-key") return "api_key";
|
|
1712
|
+
if (kind === "none") return "none";
|
|
1713
|
+
return "custom";
|
|
1714
|
+
}
|
|
1715
|
+
function mapCategory(category) {
|
|
1716
|
+
if (category === "comms") return "chat";
|
|
1717
|
+
if (category === "spreadsheet") return "database";
|
|
1718
|
+
if (category === "doc") return "docs";
|
|
1719
|
+
if (category === "commerce") return "workflow";
|
|
1720
|
+
return category === "other" ? "other" : category;
|
|
1721
|
+
}
|
|
1722
|
+
function inferDataClass(category) {
|
|
1723
|
+
if (category === "commerce") return "sensitive";
|
|
1724
|
+
if (category === "webhook") return "internal";
|
|
1725
|
+
return "private";
|
|
1726
|
+
}
|
|
1727
|
+
function titleFromName(name) {
|
|
1728
|
+
return name.split(/[._-]/g).filter(Boolean).map((part) => part.slice(0, 1).toUpperCase() + part.slice(1)).join(" ");
|
|
1729
|
+
}
|
|
1730
|
+
function toRecord(input) {
|
|
1731
|
+
if (input && typeof input === "object" && !Array.isArray(input)) return input;
|
|
1732
|
+
return {};
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
// src/credentials.ts
|
|
1736
|
+
var InMemoryIntegrationSecretStore = class {
|
|
1737
|
+
secrets = /* @__PURE__ */ new Map();
|
|
1738
|
+
get(ref) {
|
|
1739
|
+
return this.secrets.get(secretKey(ref));
|
|
1740
|
+
}
|
|
1741
|
+
put(ref, credentials) {
|
|
1742
|
+
this.secrets.set(secretKey(ref), credentials);
|
|
1743
|
+
}
|
|
1744
|
+
delete(ref) {
|
|
1745
|
+
this.secrets.delete(secretKey(ref));
|
|
1746
|
+
}
|
|
1747
|
+
};
|
|
1748
|
+
function createConnectionCredentialResolver(options) {
|
|
1749
|
+
const now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
1750
|
+
return async function resolveDataSource(connection) {
|
|
1751
|
+
const credentials = await resolveConnectionCredentials(connection, {
|
|
1752
|
+
secrets: options.secrets,
|
|
1753
|
+
connections: options.connections,
|
|
1754
|
+
adapters: options.adapters,
|
|
1755
|
+
now,
|
|
1756
|
+
markConnectionError: options.markConnectionError
|
|
1757
|
+
});
|
|
1758
|
+
return {
|
|
1759
|
+
id: connection.id,
|
|
1760
|
+
projectId: String(connection.metadata?.projectId ?? connection.owner.id),
|
|
1761
|
+
publishedAgentId: typeof connection.metadata?.publishedAgentId === "string" ? connection.metadata.publishedAgentId : null,
|
|
1762
|
+
kind: connection.connectorId,
|
|
1763
|
+
label: connection.account?.displayName ?? connection.account?.email ?? connection.connectorId,
|
|
1764
|
+
consistencyModel: typeof connection.metadata?.consistencyModel === "string" ? connection.metadata.consistencyModel : "authoritative",
|
|
1765
|
+
scopes: connection.grantedScopes,
|
|
1766
|
+
metadata: connection.metadata ?? {},
|
|
1767
|
+
credentials,
|
|
1768
|
+
status: connection.status === "active" ? "active" : connection.status === "revoked" ? "revoked" : "error"
|
|
1769
|
+
};
|
|
1770
|
+
};
|
|
1771
|
+
}
|
|
1772
|
+
async function resolveConnectionCredentials(input, options) {
|
|
1773
|
+
if (input.status !== "active") throw new Error(`Connection ${input.id} is ${input.status}.`);
|
|
1774
|
+
if (!input.secretRef) return { kind: "none" };
|
|
1775
|
+
const current = await options.secrets.get(input.secretRef);
|
|
1776
|
+
if (!current) throw new Error(`Secret ${input.secretRef.provider}/${input.secretRef.id} not found.`);
|
|
1777
|
+
if (!isExpiredOauth(current, options.now ?? (() => /* @__PURE__ */ new Date()))) return current;
|
|
1778
|
+
const adapter = options.adapters?.find((candidate) => candidate.manifest.kind === input.connectorId);
|
|
1779
|
+
if (!adapter?.refreshToken) return current;
|
|
1780
|
+
try {
|
|
1781
|
+
const refreshed = await adapter.refreshToken(current);
|
|
1782
|
+
await options.secrets.put(input.secretRef, refreshed);
|
|
1783
|
+
if (options.connections) {
|
|
1784
|
+
await options.connections.put({
|
|
1785
|
+
...input,
|
|
1786
|
+
status: "active",
|
|
1787
|
+
updatedAt: (options.now?.() ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
1788
|
+
expiresAt: refreshed.kind === "oauth2" && refreshed.expiresAt ? new Date(refreshed.expiresAt).toISOString() : input.expiresAt
|
|
1789
|
+
});
|
|
1790
|
+
}
|
|
1791
|
+
return refreshed;
|
|
1792
|
+
} catch (error) {
|
|
1793
|
+
const err = error instanceof Error ? error : new Error("Credential refresh failed.");
|
|
1794
|
+
await options.markConnectionError?.(input, err);
|
|
1795
|
+
if (options.connections) {
|
|
1796
|
+
await options.connections.put({
|
|
1797
|
+
...input,
|
|
1798
|
+
status: "expired",
|
|
1799
|
+
updatedAt: (options.now?.() ?? /* @__PURE__ */ new Date()).toISOString()
|
|
1800
|
+
});
|
|
1801
|
+
}
|
|
1802
|
+
throw err;
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1805
|
+
function createCredentialBackedAdapterProvider(options) {
|
|
1806
|
+
return createConnectorAdapterProvider({
|
|
1807
|
+
...options,
|
|
1808
|
+
resolveDataSource: createConnectionCredentialResolver(options)
|
|
1809
|
+
});
|
|
1810
|
+
}
|
|
1811
|
+
async function revokeConnection(input) {
|
|
1812
|
+
if (input.connection.secretRef) await input.secrets?.delete?.(input.connection.secretRef);
|
|
1813
|
+
const revoked = {
|
|
1814
|
+
...input.connection,
|
|
1815
|
+
status: "revoked",
|
|
1816
|
+
updatedAt: (input.now?.() ?? /* @__PURE__ */ new Date()).toISOString()
|
|
1817
|
+
};
|
|
1818
|
+
await input.connections?.put(revoked);
|
|
1819
|
+
return revoked;
|
|
1820
|
+
}
|
|
1821
|
+
function isExpiredOauth(credentials, now) {
|
|
1822
|
+
return credentials.kind === "oauth2" && typeof credentials.expiresAt === "number" && credentials.expiresAt <= now().getTime() && Boolean(credentials.refreshToken);
|
|
1823
|
+
}
|
|
1824
|
+
function secretKey(ref) {
|
|
1825
|
+
return `${ref.provider}:${ref.id}`;
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1828
|
+
// src/events.ts
|
|
1829
|
+
var InMemoryIntegrationEventStore = class {
|
|
1830
|
+
events = /* @__PURE__ */ new Map();
|
|
1831
|
+
providerIds = /* @__PURE__ */ new Set();
|
|
1832
|
+
put(event) {
|
|
1833
|
+
this.events.set(event.id, event);
|
|
1834
|
+
if (event.providerEventId) this.providerIds.add(providerKey(event.sourceId, event.providerEventId));
|
|
1835
|
+
}
|
|
1836
|
+
hasProviderEvent(sourceId, providerEventId) {
|
|
1837
|
+
return this.providerIds.has(providerKey(sourceId, providerEventId));
|
|
1838
|
+
}
|
|
1839
|
+
list() {
|
|
1840
|
+
return [...this.events.values()];
|
|
1841
|
+
}
|
|
1842
|
+
};
|
|
1843
|
+
async function receiveIntegrationWebhook(input) {
|
|
1844
|
+
if (!input.adapter.handleInboundEvent) {
|
|
1845
|
+
return { status: 405, body: { ok: false, error: "Connector does not support inbound webhooks." }, received: [], duplicates: [] };
|
|
1846
|
+
}
|
|
1847
|
+
const signature = input.adapter.verifySignature?.({
|
|
1848
|
+
rawBody: input.rawBody,
|
|
1849
|
+
headers: input.headers,
|
|
1850
|
+
source: input.source
|
|
1851
|
+
});
|
|
1852
|
+
if (signature && !signature.valid) {
|
|
1853
|
+
return { status: 401, body: { ok: false, error: signature.reason ?? "Invalid webhook signature." }, received: [], duplicates: [] };
|
|
1854
|
+
}
|
|
1855
|
+
const handled = await input.adapter.handleInboundEvent({
|
|
1856
|
+
source: input.source,
|
|
1857
|
+
rawBody: input.rawBody,
|
|
1858
|
+
headers: input.headers
|
|
1859
|
+
});
|
|
1860
|
+
const received = [];
|
|
1861
|
+
const duplicates = [];
|
|
1862
|
+
for (const inbound of handled.events) {
|
|
1863
|
+
const event = storedEvent(input.source, inbound, input.now ?? (() => /* @__PURE__ */ new Date()));
|
|
1864
|
+
if (event.providerEventId && await input.store.hasProviderEvent(event.sourceId, event.providerEventId)) {
|
|
1865
|
+
duplicates.push(event);
|
|
1866
|
+
continue;
|
|
1867
|
+
}
|
|
1868
|
+
await input.store.put(event);
|
|
1869
|
+
received.push(event);
|
|
1870
|
+
await dispatchStoredEvent(event, input.source, input.workflowRuntime);
|
|
1871
|
+
}
|
|
1872
|
+
return {
|
|
1873
|
+
status: handled.response?.status ?? 200,
|
|
1874
|
+
body: handled.response?.body ?? { received: true, count: received.length, duplicateCount: duplicates.length },
|
|
1875
|
+
headers: handled.response?.headers,
|
|
1876
|
+
received,
|
|
1877
|
+
duplicates
|
|
1878
|
+
};
|
|
1879
|
+
}
|
|
1880
|
+
function storedEventToTriggerEvent(event, source) {
|
|
1881
|
+
return {
|
|
1882
|
+
id: event.id,
|
|
1883
|
+
providerId: String(source.metadata.providerId ?? "first-party"),
|
|
1884
|
+
connectorId: event.connectorId,
|
|
1885
|
+
connectionId: source.id,
|
|
1886
|
+
trigger: event.eventType,
|
|
1887
|
+
occurredAt: event.receivedAt,
|
|
1888
|
+
payload: event.payload,
|
|
1889
|
+
metadata: {
|
|
1890
|
+
providerEventId: event.providerEventId,
|
|
1891
|
+
sourceId: event.sourceId,
|
|
1892
|
+
...event.metadata
|
|
1893
|
+
}
|
|
1894
|
+
};
|
|
1895
|
+
}
|
|
1896
|
+
async function dispatchStoredEvent(event, source, workflowRuntime) {
|
|
1897
|
+
if (!workflowRuntime) return;
|
|
1898
|
+
await workflowRuntime.dispatchEvent(storedEventToTriggerEvent(event, source), () => void 0);
|
|
1899
|
+
}
|
|
1900
|
+
function storedEvent(source, event, now) {
|
|
1901
|
+
return {
|
|
1902
|
+
id: `evt_${source.id}_${event.providerEventId ?? `${event.eventType}_${now().getTime()}`}`,
|
|
1903
|
+
sourceId: source.id,
|
|
1904
|
+
connectorId: source.kind,
|
|
1905
|
+
eventType: event.eventType,
|
|
1906
|
+
providerEventId: event.providerEventId,
|
|
1907
|
+
receivedAt: now().toISOString(),
|
|
1908
|
+
payload: event.payload
|
|
1909
|
+
};
|
|
1910
|
+
}
|
|
1911
|
+
function providerKey(sourceId, providerEventId) {
|
|
1912
|
+
return `${sourceId}:${providerEventId}`;
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1915
|
+
// src/guard.ts
|
|
1916
|
+
import { createHash as createHash2 } from "crypto";
|
|
1917
|
+
var InMemoryIntegrationIdempotencyStore = class {
|
|
1918
|
+
records = /* @__PURE__ */ new Map();
|
|
1919
|
+
get(key) {
|
|
1920
|
+
return this.records.get(key);
|
|
1921
|
+
}
|
|
1922
|
+
put(record) {
|
|
1923
|
+
this.records.set(record.key, record);
|
|
1924
|
+
}
|
|
1925
|
+
};
|
|
1926
|
+
var DefaultIntegrationActionGuard = class {
|
|
1927
|
+
idempotency;
|
|
1928
|
+
audit;
|
|
1929
|
+
rateLimiter;
|
|
1930
|
+
now;
|
|
1931
|
+
constructor(options = {}) {
|
|
1932
|
+
this.idempotency = options.idempotency;
|
|
1933
|
+
this.audit = options.audit;
|
|
1934
|
+
this.rateLimiter = options.rateLimiter;
|
|
1935
|
+
this.now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
1936
|
+
}
|
|
1937
|
+
async invokeAction(ctx, proceed) {
|
|
1938
|
+
const idempotencyKey = ctx.request.idempotencyKey;
|
|
1939
|
+
const requestHash = hashRequest(ctx);
|
|
1940
|
+
if (idempotencyKey && this.idempotency) {
|
|
1941
|
+
const existing = await this.idempotency.get(idempotencyKey);
|
|
1942
|
+
if (existing) {
|
|
1943
|
+
if (existing.requestHash !== requestHash) {
|
|
1944
|
+
return {
|
|
1945
|
+
ok: false,
|
|
1946
|
+
action: ctx.request.action,
|
|
1947
|
+
output: { idempotencyConflict: true, message: "Idempotency key was reused with different integration input." }
|
|
1948
|
+
};
|
|
1949
|
+
}
|
|
1950
|
+
return {
|
|
1951
|
+
...existing.result,
|
|
1952
|
+
metadata: { ...existing.result.metadata ?? {}, idempotentReplay: true }
|
|
1953
|
+
};
|
|
1954
|
+
}
|
|
1955
|
+
}
|
|
1956
|
+
if (ctx.request.dryRun && ctx.action?.risk !== "read") {
|
|
1957
|
+
const result = {
|
|
1958
|
+
ok: true,
|
|
1959
|
+
action: ctx.request.action,
|
|
1960
|
+
output: { dryRun: true },
|
|
1961
|
+
metadata: { dryRun: true }
|
|
1962
|
+
};
|
|
1963
|
+
await this.writeIdempotency(idempotencyKey, requestHash, result);
|
|
1964
|
+
return result;
|
|
1965
|
+
}
|
|
1966
|
+
const rateLimit = await this.rateLimiter?.check(ctx);
|
|
1967
|
+
if (rateLimit && !rateLimit.allowed) {
|
|
1968
|
+
return {
|
|
1969
|
+
ok: false,
|
|
1970
|
+
action: ctx.request.action,
|
|
1971
|
+
output: { rateLimited: true, retryAfterMs: rateLimit.retryAfterMs, message: rateLimit.reason ?? "Integration rate limit exceeded." }
|
|
1972
|
+
};
|
|
1973
|
+
}
|
|
1974
|
+
try {
|
|
1975
|
+
const result = await proceed();
|
|
1976
|
+
await this.writeIdempotency(idempotencyKey, requestHash, result);
|
|
1977
|
+
await this.audit?.record(createIntegrationAuditEvent({
|
|
1978
|
+
type: result.ok ? "action.invoked" : "action.failed",
|
|
1979
|
+
actor: ctx.connection.owner,
|
|
1980
|
+
connectionId: ctx.connection.id,
|
|
1981
|
+
providerId: ctx.connection.providerId,
|
|
1982
|
+
connectorId: ctx.connection.connectorId,
|
|
1983
|
+
action: ctx.request.action,
|
|
1984
|
+
risk: ctx.action?.risk,
|
|
1985
|
+
dataClass: ctx.action?.dataClass,
|
|
1986
|
+
ok: result.ok,
|
|
1987
|
+
metadata: { idempotencyKey, externalId: result.externalId, warnings: result.warnings },
|
|
1988
|
+
now: this.now
|
|
1989
|
+
}));
|
|
1990
|
+
return result;
|
|
1991
|
+
} catch (error) {
|
|
1992
|
+
await this.audit?.record(createIntegrationAuditEvent({
|
|
1993
|
+
type: "action.failed",
|
|
1994
|
+
actor: ctx.connection.owner,
|
|
1995
|
+
connectionId: ctx.connection.id,
|
|
1996
|
+
providerId: ctx.connection.providerId,
|
|
1997
|
+
connectorId: ctx.connection.connectorId,
|
|
1998
|
+
action: ctx.request.action,
|
|
1999
|
+
risk: ctx.action?.risk,
|
|
2000
|
+
dataClass: ctx.action?.dataClass,
|
|
2001
|
+
ok: false,
|
|
2002
|
+
message: error instanceof Error ? error.message : "Integration action failed.",
|
|
2003
|
+
metadata: { idempotencyKey },
|
|
2004
|
+
now: this.now
|
|
2005
|
+
}));
|
|
2006
|
+
throw error;
|
|
2007
|
+
}
|
|
2008
|
+
}
|
|
2009
|
+
async writeIdempotency(key, requestHash, result) {
|
|
2010
|
+
if (!key || !this.idempotency) return;
|
|
2011
|
+
await this.idempotency.put({
|
|
2012
|
+
key,
|
|
2013
|
+
requestHash,
|
|
2014
|
+
result,
|
|
2015
|
+
createdAt: this.now().toISOString()
|
|
2016
|
+
});
|
|
2017
|
+
}
|
|
2018
|
+
};
|
|
2019
|
+
function createDefaultIntegrationActionGuard(options = {}) {
|
|
2020
|
+
return new DefaultIntegrationActionGuard(options);
|
|
2021
|
+
}
|
|
2022
|
+
function hashRequest(ctx) {
|
|
2023
|
+
return createHash2("sha256").update(JSON.stringify({
|
|
2024
|
+
connectionId: ctx.connection.id,
|
|
2025
|
+
action: ctx.request.action,
|
|
2026
|
+
input: ctx.request.input ?? null,
|
|
2027
|
+
dryRun: ctx.request.dryRun ?? false
|
|
2028
|
+
})).digest("base64url");
|
|
2029
|
+
}
|
|
2030
|
+
|
|
2031
|
+
// src/healthcheck.ts
|
|
2032
|
+
var InMemoryIntegrationHealthcheckStore = class {
|
|
2033
|
+
results = /* @__PURE__ */ new Map();
|
|
2034
|
+
put(result) {
|
|
2035
|
+
this.results.set(result.connectionId, result);
|
|
2036
|
+
}
|
|
2037
|
+
get(connectionId) {
|
|
2038
|
+
return this.results.get(connectionId);
|
|
2039
|
+
}
|
|
2040
|
+
list() {
|
|
2041
|
+
return [...this.results.values()];
|
|
2042
|
+
}
|
|
2043
|
+
};
|
|
2044
|
+
async function runIntegrationHealthcheck(input) {
|
|
2045
|
+
const now = input.now ?? (() => /* @__PURE__ */ new Date());
|
|
2046
|
+
const checkedAt = now().toISOString();
|
|
2047
|
+
const connector = input.connector ?? input.registry?.byId.get(input.connection.connectorId)?.connector;
|
|
2048
|
+
const checks = [];
|
|
2049
|
+
checks.push(connectionStatusCheck(input.connection, now));
|
|
2050
|
+
if (!connector) {
|
|
2051
|
+
checks.push({ id: "connector-known", status: "unknown", message: `Connector ${input.connection.connectorId} is not in the registry.` });
|
|
2052
|
+
} else {
|
|
2053
|
+
checks.push(connectorExecutableCheck(connector));
|
|
2054
|
+
checks.push(scopeShapeCheck(input.connection, connector));
|
|
2055
|
+
if (input.test && input.connection.status === "active") {
|
|
2056
|
+
checks.push(await liveHealthcheck(input.connection, connector, input.test));
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
const result = {
|
|
2060
|
+
connectionId: input.connection.id,
|
|
2061
|
+
providerId: input.connection.providerId,
|
|
2062
|
+
connectorId: input.connection.connectorId,
|
|
2063
|
+
status: rollupHealthStatus(checks),
|
|
2064
|
+
checkedAt,
|
|
2065
|
+
checks
|
|
2066
|
+
};
|
|
2067
|
+
await input.audit?.record(createIntegrationAuditEvent({
|
|
2068
|
+
type: "healthcheck.completed",
|
|
2069
|
+
actor: input.connection.owner,
|
|
2070
|
+
connectionId: input.connection.id,
|
|
2071
|
+
providerId: input.connection.providerId,
|
|
2072
|
+
connectorId: input.connection.connectorId,
|
|
2073
|
+
ok: result.status === "healthy",
|
|
2074
|
+
message: result.status,
|
|
2075
|
+
metadata: { checks: checks.map((check) => ({ id: check.id, status: check.status, message: check.message })) },
|
|
2076
|
+
occurredAt: checkedAt
|
|
2077
|
+
}));
|
|
2078
|
+
return result;
|
|
2079
|
+
}
|
|
2080
|
+
async function runIntegrationHealthchecks(input) {
|
|
2081
|
+
const results = [];
|
|
2082
|
+
for (const connection of input.connections) {
|
|
2083
|
+
const result = await runIntegrationHealthcheck({
|
|
2084
|
+
connection,
|
|
2085
|
+
registry: input.registry,
|
|
2086
|
+
test: input.test,
|
|
2087
|
+
audit: input.audit,
|
|
2088
|
+
now: input.now
|
|
2089
|
+
});
|
|
2090
|
+
await input.store?.put(result);
|
|
2091
|
+
results.push(result);
|
|
2092
|
+
}
|
|
2093
|
+
return results;
|
|
2094
|
+
}
|
|
2095
|
+
function healthcheckRequest(action = "healthcheck") {
|
|
2096
|
+
return {
|
|
2097
|
+
connectionId: "__healthcheck__",
|
|
2098
|
+
action,
|
|
2099
|
+
input: {},
|
|
2100
|
+
dryRun: true,
|
|
2101
|
+
metadata: { healthcheck: true }
|
|
2102
|
+
};
|
|
2103
|
+
}
|
|
2104
|
+
function connectionStatusCheck(connection, now) {
|
|
2105
|
+
if (connection.status !== "active") {
|
|
2106
|
+
return { id: "connection-active", status: "unhealthy", message: `Connection is ${connection.status}.` };
|
|
2107
|
+
}
|
|
2108
|
+
if (connection.expiresAt && Date.parse(connection.expiresAt) <= now().getTime()) {
|
|
2109
|
+
return { id: "connection-active", status: "unhealthy", message: "Connection credentials are expired." };
|
|
2110
|
+
}
|
|
2111
|
+
return { id: "connection-active", status: "healthy", message: "Connection is active." };
|
|
2112
|
+
}
|
|
2113
|
+
function connectorExecutableCheck(connector) {
|
|
2114
|
+
const executable = connector.actions.length > 0 || (connector.triggers?.length ?? 0) > 0;
|
|
2115
|
+
if (!executable) {
|
|
2116
|
+
return { id: "connector-executable", status: "degraded", message: `${connector.title} is catalog-only.` };
|
|
2117
|
+
}
|
|
2118
|
+
return { id: "connector-executable", status: "healthy", message: `${connector.title} has executable actions or triggers.` };
|
|
1276
2119
|
}
|
|
1277
|
-
function
|
|
1278
|
-
|
|
2120
|
+
function scopeShapeCheck(connection, connector) {
|
|
2121
|
+
const declaredScopes = new Set(connector.scopes);
|
|
2122
|
+
const undeclared = connection.grantedScopes.filter((scope) => !declaredScopes.has(scope));
|
|
2123
|
+
if (connector.scopes.length === 0 && connection.grantedScopes.length > 0) {
|
|
2124
|
+
return { id: "scope-shape", status: "unknown", message: "Connector does not declare a scope catalog.", metadata: { grantedScopes: connection.grantedScopes } };
|
|
2125
|
+
}
|
|
2126
|
+
if (undeclared.length > 0) {
|
|
2127
|
+
return { id: "scope-shape", status: "degraded", message: "Connection has scopes not declared by the connector.", metadata: { undeclared } };
|
|
2128
|
+
}
|
|
2129
|
+
return { id: "scope-shape", status: "healthy", message: "Granted scopes match the connector shape." };
|
|
1279
2130
|
}
|
|
1280
|
-
function
|
|
1281
|
-
|
|
2131
|
+
async function liveHealthcheck(connection, connector, test) {
|
|
2132
|
+
try {
|
|
2133
|
+
const result = await test(connection, connector);
|
|
2134
|
+
const ok = typeof result === "boolean" ? result : result.ok;
|
|
2135
|
+
return {
|
|
2136
|
+
id: "provider-live-test",
|
|
2137
|
+
status: ok ? "healthy" : "unhealthy",
|
|
2138
|
+
message: ok ? "Provider live test passed." : "Provider live test failed.",
|
|
2139
|
+
metadata: typeof result === "boolean" ? void 0 : { action: result.action, warnings: result.warnings }
|
|
2140
|
+
};
|
|
2141
|
+
} catch (error) {
|
|
2142
|
+
return {
|
|
2143
|
+
id: "provider-live-test",
|
|
2144
|
+
status: "unhealthy",
|
|
2145
|
+
message: error instanceof Error ? error.message : "Provider live test failed."
|
|
2146
|
+
};
|
|
2147
|
+
}
|
|
2148
|
+
}
|
|
2149
|
+
function rollupHealthStatus(checks) {
|
|
2150
|
+
if (checks.some((check) => check.status === "unhealthy")) return "unhealthy";
|
|
2151
|
+
if (checks.some((check) => check.status === "degraded")) return "degraded";
|
|
2152
|
+
if (checks.some((check) => check.status === "unknown")) return "unknown";
|
|
2153
|
+
return "healthy";
|
|
1282
2154
|
}
|
|
1283
2155
|
|
|
1284
2156
|
// src/connectors/types.ts
|
|
@@ -1335,7 +2207,7 @@ function assertValidConnectorManifest(manifest) {
|
|
|
1335
2207
|
}
|
|
1336
2208
|
|
|
1337
2209
|
// src/connectors/oauth.ts
|
|
1338
|
-
import { createHash, randomBytes } from "crypto";
|
|
2210
|
+
import { createHash as createHash3, randomBytes } from "crypto";
|
|
1339
2211
|
var PENDING_TTL_MS = 10 * 60 * 1e3;
|
|
1340
2212
|
var InMemoryOAuthFlowStore = class {
|
|
1341
2213
|
pendingFlows = /* @__PURE__ */ new Map();
|
|
@@ -1363,7 +2235,7 @@ function startOAuthFlow(input) {
|
|
|
1363
2235
|
const now = input.now ?? Date.now();
|
|
1364
2236
|
store.sweep?.(now);
|
|
1365
2237
|
const codeVerifier = base64Url(randomBytes(48));
|
|
1366
|
-
const codeChallenge = base64Url(
|
|
2238
|
+
const codeChallenge = base64Url(createHash3("sha256").update(codeVerifier).digest());
|
|
1367
2239
|
const state = base64Url(randomBytes(24));
|
|
1368
2240
|
store.put(state, {
|
|
1369
2241
|
codeVerifier,
|
|
@@ -1828,7 +2700,7 @@ function readMetaString(meta, key) {
|
|
|
1828
2700
|
}
|
|
1829
2701
|
|
|
1830
2702
|
// src/connectors/adapters/google-sheets.ts
|
|
1831
|
-
import { createHash as
|
|
2703
|
+
import { createHash as createHash4 } from "crypto";
|
|
1832
2704
|
var SCOPES2 = ["https://www.googleapis.com/auth/spreadsheets"];
|
|
1833
2705
|
var AUTH_URL2 = "https://accounts.google.com/o/oauth2/v2/auth";
|
|
1834
2706
|
var TOKEN_URL2 = "https://oauth2.googleapis.com/token";
|
|
@@ -2102,7 +2974,7 @@ async function fetchAllRows(accessToken, meta) {
|
|
|
2102
2974
|
function rowFingerprint(values) {
|
|
2103
2975
|
const keys = Object.keys(values).sort();
|
|
2104
2976
|
const blob = keys.map((k) => `${k}=${values[k]}`).join("");
|
|
2105
|
-
return
|
|
2977
|
+
return createHash4("sha256").update(blob).digest("hex").slice(0, 16);
|
|
2106
2978
|
}
|
|
2107
2979
|
function matchesPredicate(row, predicate) {
|
|
2108
2980
|
for (const [k, v] of Object.entries(predicate)) {
|
|
@@ -4588,7 +5460,7 @@ function unique2(values) {
|
|
|
4588
5460
|
}
|
|
4589
5461
|
|
|
4590
5462
|
// src/policy.ts
|
|
4591
|
-
import { randomUUID } from "crypto";
|
|
5463
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
4592
5464
|
var StaticIntegrationPolicyEngine = class {
|
|
4593
5465
|
rules;
|
|
4594
5466
|
defaultReadEffect;
|
|
@@ -4631,7 +5503,7 @@ function buildApprovalRequest(ctx, reason, requestedAt) {
|
|
|
4631
5503
|
throw new Error("Cannot build approval request without an action descriptor.");
|
|
4632
5504
|
}
|
|
4633
5505
|
return {
|
|
4634
|
-
id: `approval_${
|
|
5506
|
+
id: `approval_${randomUUID2()}`,
|
|
4635
5507
|
connectionId: ctx.connection.id,
|
|
4636
5508
|
providerId: ctx.connection.providerId,
|
|
4637
5509
|
connectorId: ctx.connection.connectorId,
|
|
@@ -4647,7 +5519,7 @@ function buildApprovalRequest(ctx, reason, requestedAt) {
|
|
|
4647
5519
|
function redactApprovalRequest(request) {
|
|
4648
5520
|
return {
|
|
4649
5521
|
...request,
|
|
4650
|
-
inputPreview:
|
|
5522
|
+
inputPreview: redactUnknown2(request.inputPreview)
|
|
4651
5523
|
};
|
|
4652
5524
|
}
|
|
4653
5525
|
function ruleMatches(rule, ctx) {
|
|
@@ -4671,17 +5543,17 @@ function defaultReason(effect, risk) {
|
|
|
4671
5543
|
return `${risk} integration action requires approval by default policy.`;
|
|
4672
5544
|
}
|
|
4673
5545
|
function previewInput(input) {
|
|
4674
|
-
return
|
|
5546
|
+
return redactUnknown2(input);
|
|
4675
5547
|
}
|
|
4676
|
-
function
|
|
4677
|
-
if (Array.isArray(value)) return value.map(
|
|
5548
|
+
function redactUnknown2(value) {
|
|
5549
|
+
if (Array.isArray(value)) return value.map(redactUnknown2);
|
|
4678
5550
|
if (!value || typeof value !== "object") return value;
|
|
4679
5551
|
const out = {};
|
|
4680
5552
|
for (const [key, child] of Object.entries(value)) {
|
|
4681
5553
|
if (/token|secret|password|authorization|api[_-]?key|credential/i.test(key)) {
|
|
4682
5554
|
out[key] = "[REDACTED]";
|
|
4683
5555
|
} else {
|
|
4684
|
-
out[key] =
|
|
5556
|
+
out[key] = redactUnknown2(child);
|
|
4685
5557
|
}
|
|
4686
5558
|
}
|
|
4687
5559
|
return out;
|
|
@@ -4745,13 +5617,13 @@ function redactInvocationEnvelope(envelope) {
|
|
|
4745
5617
|
return {
|
|
4746
5618
|
...envelope,
|
|
4747
5619
|
capabilityToken: "[REDACTED]",
|
|
4748
|
-
input:
|
|
5620
|
+
input: redactUnknown3(envelope.input)
|
|
4749
5621
|
};
|
|
4750
5622
|
}
|
|
4751
5623
|
function redactCapability(capability) {
|
|
4752
5624
|
return {
|
|
4753
5625
|
...capability,
|
|
4754
|
-
metadata:
|
|
5626
|
+
metadata: redactUnknown3(capability.metadata)
|
|
4755
5627
|
};
|
|
4756
5628
|
}
|
|
4757
5629
|
function normalizeIntegrationResult(result) {
|
|
@@ -4779,15 +5651,40 @@ function normalizeIntegrationResult(result) {
|
|
|
4779
5651
|
metadata: result.metadata
|
|
4780
5652
|
};
|
|
4781
5653
|
}
|
|
4782
|
-
function
|
|
4783
|
-
|
|
5654
|
+
async function dispatchIntegrationInvocation(envelope, options) {
|
|
5655
|
+
try {
|
|
5656
|
+
validateIntegrationInvocationEnvelope(envelope, options);
|
|
5657
|
+
const result = await options.hub.invokeWithCapability(
|
|
5658
|
+
envelope.capabilityToken,
|
|
5659
|
+
invocationRequestFromEnvelope(envelope)
|
|
5660
|
+
);
|
|
5661
|
+
return normalizeIntegrationResult(result);
|
|
5662
|
+
} catch (error) {
|
|
5663
|
+
return {
|
|
5664
|
+
status: "failed",
|
|
5665
|
+
action: typeof envelope?.action === "string" ? envelope.action : "unknown",
|
|
5666
|
+
error: error instanceof Error ? error.message : "Integration invocation failed."
|
|
5667
|
+
};
|
|
5668
|
+
}
|
|
5669
|
+
}
|
|
5670
|
+
var IntegrationSandboxHost = class {
|
|
5671
|
+
options;
|
|
5672
|
+
constructor(options) {
|
|
5673
|
+
this.options = options;
|
|
5674
|
+
}
|
|
5675
|
+
dispatch(envelope) {
|
|
5676
|
+
return dispatchIntegrationInvocation(envelope, this.options);
|
|
5677
|
+
}
|
|
5678
|
+
};
|
|
5679
|
+
function redactUnknown3(value) {
|
|
5680
|
+
if (Array.isArray(value)) return value.map(redactUnknown3);
|
|
4784
5681
|
if (!value || typeof value !== "object") return value;
|
|
4785
5682
|
const out = {};
|
|
4786
5683
|
for (const [key, child] of Object.entries(value)) {
|
|
4787
5684
|
if (/token|secret|password|authorization|api[_-]?key|credential/i.test(key)) {
|
|
4788
5685
|
out[key] = "[REDACTED]";
|
|
4789
5686
|
} else {
|
|
4790
|
-
out[key] =
|
|
5687
|
+
out[key] = redactUnknown3(child);
|
|
4791
5688
|
}
|
|
4792
5689
|
}
|
|
4793
5690
|
return out;
|
|
@@ -4799,152 +5696,6 @@ function isPlainRecord(value) {
|
|
|
4799
5696
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
4800
5697
|
}
|
|
4801
5698
|
|
|
4802
|
-
// src/adapter-provider.ts
|
|
4803
|
-
function createConnectorAdapterProvider(options) {
|
|
4804
|
-
const providerId = options.id ?? "first-party";
|
|
4805
|
-
const now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
4806
|
-
const adapters = /* @__PURE__ */ new Map();
|
|
4807
|
-
for (const adapter of options.adapters) {
|
|
4808
|
-
adapters.set(adapter.manifest.kind, adapter);
|
|
4809
|
-
}
|
|
4810
|
-
return {
|
|
4811
|
-
id: providerId,
|
|
4812
|
-
kind: options.kind ?? "first_party",
|
|
4813
|
-
listConnectors: () => [...adapters.values()].map((adapter) => manifestToConnector(providerId, adapter)),
|
|
4814
|
-
async invokeAction(connection, request) {
|
|
4815
|
-
const adapter = adapters.get(connection.connectorId);
|
|
4816
|
-
if (!adapter) {
|
|
4817
|
-
throw new IntegrationError(`Connector adapter ${connection.connectorId} not found.`, "connector_not_found");
|
|
4818
|
-
}
|
|
4819
|
-
const capability = adapter.manifest.capabilities.find((candidate) => candidate.name === request.action);
|
|
4820
|
-
if (!capability) {
|
|
4821
|
-
throw new IntegrationError(`Capability ${request.action} is not defined by ${connection.connectorId}.`, "action_not_found");
|
|
4822
|
-
}
|
|
4823
|
-
const source = await options.resolveDataSource(connection);
|
|
4824
|
-
const invocation = {
|
|
4825
|
-
source,
|
|
4826
|
-
capabilityName: request.action,
|
|
4827
|
-
args: toRecord(request.input),
|
|
4828
|
-
idempotencyKey: request.idempotencyKey ?? `idem_${connection.id}_${request.action}_${now().getTime()}`,
|
|
4829
|
-
expectedEtag: typeof request.metadata?.expectedEtag === "string" ? request.metadata.expectedEtag : void 0,
|
|
4830
|
-
callSessionId: typeof request.metadata?.callSessionId === "string" ? request.metadata.callSessionId : void 0
|
|
4831
|
-
};
|
|
4832
|
-
if (capability.class === "read") {
|
|
4833
|
-
if (!adapter.executeRead) {
|
|
4834
|
-
throw new IntegrationError(`Connector ${connection.connectorId} does not implement reads.`, "action_not_found");
|
|
4835
|
-
}
|
|
4836
|
-
const result = await adapter.executeRead(invocation);
|
|
4837
|
-
return readResultToAction(request, result);
|
|
4838
|
-
}
|
|
4839
|
-
if (capability.class === "mutation") {
|
|
4840
|
-
if (!adapter.executeMutation) {
|
|
4841
|
-
throw new IntegrationError(`Connector ${connection.connectorId} does not implement mutations.`, "action_not_found");
|
|
4842
|
-
}
|
|
4843
|
-
const result = await adapter.executeMutation(invocation);
|
|
4844
|
-
return mutationResultToAction(request, result);
|
|
4845
|
-
}
|
|
4846
|
-
throw new IntegrationError(`Capability ${request.action} is not invokable as an action.`, "action_not_found");
|
|
4847
|
-
}
|
|
4848
|
-
};
|
|
4849
|
-
}
|
|
4850
|
-
function manifestToConnector(providerId, adapter) {
|
|
4851
|
-
const manifest = adapter.manifest;
|
|
4852
|
-
return {
|
|
4853
|
-
id: manifest.kind,
|
|
4854
|
-
providerId,
|
|
4855
|
-
title: manifest.displayName,
|
|
4856
|
-
category: mapCategory(manifest.category),
|
|
4857
|
-
auth: mapAuth(manifest.auth.kind),
|
|
4858
|
-
scopes: manifest.auth.kind === "oauth2" ? manifest.auth.scopes : [],
|
|
4859
|
-
actions: manifest.capabilities.filter((capability) => capability.class === "read" || capability.class === "mutation").map((capability) => ({
|
|
4860
|
-
id: capability.name,
|
|
4861
|
-
title: titleFromName(capability.name),
|
|
4862
|
-
risk: capability.class === "read" ? "read" : capability.externalEffect ? "destructive" : "write",
|
|
4863
|
-
requiredScopes: capability.requiredScopes ?? [],
|
|
4864
|
-
dataClass: inferDataClass(manifest.category),
|
|
4865
|
-
description: capability.description,
|
|
4866
|
-
approvalRequired: capability.class === "mutation",
|
|
4867
|
-
inputSchema: capability.parameters
|
|
4868
|
-
})),
|
|
4869
|
-
metadata: {
|
|
4870
|
-
source: "first-party-adapter",
|
|
4871
|
-
supportTier: "firstPartyExecutable",
|
|
4872
|
-
executable: true
|
|
4873
|
-
}
|
|
4874
|
-
};
|
|
4875
|
-
}
|
|
4876
|
-
function readResultToAction(request, result) {
|
|
4877
|
-
return {
|
|
4878
|
-
ok: true,
|
|
4879
|
-
action: request.action,
|
|
4880
|
-
output: result.data,
|
|
4881
|
-
metadata: {
|
|
4882
|
-
etag: result.etag,
|
|
4883
|
-
fetchedAt: result.fetchedAt
|
|
4884
|
-
}
|
|
4885
|
-
};
|
|
4886
|
-
}
|
|
4887
|
-
function mutationResultToAction(request, result) {
|
|
4888
|
-
if (result.status === "committed") {
|
|
4889
|
-
return {
|
|
4890
|
-
ok: true,
|
|
4891
|
-
action: request.action,
|
|
4892
|
-
output: result.data,
|
|
4893
|
-
metadata: {
|
|
4894
|
-
etagAfter: result.etagAfter,
|
|
4895
|
-
committedAt: result.committedAt,
|
|
4896
|
-
idempotentReplay: result.idempotentReplay
|
|
4897
|
-
}
|
|
4898
|
-
};
|
|
4899
|
-
}
|
|
4900
|
-
if (result.status === "conflict") {
|
|
4901
|
-
return {
|
|
4902
|
-
ok: false,
|
|
4903
|
-
action: request.action,
|
|
4904
|
-
output: {
|
|
4905
|
-
conflict: true,
|
|
4906
|
-
message: result.message,
|
|
4907
|
-
alternatives: result.alternatives,
|
|
4908
|
-
currentState: result.currentState
|
|
4909
|
-
}
|
|
4910
|
-
};
|
|
4911
|
-
}
|
|
4912
|
-
return {
|
|
4913
|
-
ok: false,
|
|
4914
|
-
action: request.action,
|
|
4915
|
-
output: {
|
|
4916
|
-
rateLimited: true,
|
|
4917
|
-
retryAfterMs: result.retryAfterMs,
|
|
4918
|
-
message: result.message
|
|
4919
|
-
}
|
|
4920
|
-
};
|
|
4921
|
-
}
|
|
4922
|
-
function mapAuth(kind) {
|
|
4923
|
-
if (kind === "oauth2") return "oauth2";
|
|
4924
|
-
if (kind === "api-key") return "api_key";
|
|
4925
|
-
if (kind === "none") return "none";
|
|
4926
|
-
return "custom";
|
|
4927
|
-
}
|
|
4928
|
-
function mapCategory(category) {
|
|
4929
|
-
if (category === "comms") return "chat";
|
|
4930
|
-
if (category === "spreadsheet") return "database";
|
|
4931
|
-
if (category === "doc") return "docs";
|
|
4932
|
-
if (category === "commerce") return "workflow";
|
|
4933
|
-
return category === "other" ? "other" : category;
|
|
4934
|
-
}
|
|
4935
|
-
function inferDataClass(category) {
|
|
4936
|
-
if (category === "commerce") return "sensitive";
|
|
4937
|
-
if (category === "webhook") return "internal";
|
|
4938
|
-
return "private";
|
|
4939
|
-
}
|
|
4940
|
-
function titleFromName(name) {
|
|
4941
|
-
return name.split(/[._-]/g).filter(Boolean).map((part) => part.slice(0, 1).toUpperCase() + part.slice(1)).join(" ");
|
|
4942
|
-
}
|
|
4943
|
-
function toRecord(input) {
|
|
4944
|
-
if (input && typeof input === "object" && !Array.isArray(input)) return input;
|
|
4945
|
-
return {};
|
|
4946
|
-
}
|
|
4947
|
-
|
|
4948
5699
|
// src/importers.ts
|
|
4949
5700
|
var HTTP_METHODS = /* @__PURE__ */ new Set(["get", "post", "put", "patch", "delete"]);
|
|
4950
5701
|
function importOpenApiConnector(document, options) {
|
|
@@ -5244,6 +5995,295 @@ function unique4(values) {
|
|
|
5244
5995
|
return [...new Set(values)];
|
|
5245
5996
|
}
|
|
5246
5997
|
|
|
5998
|
+
// src/runtime.ts
|
|
5999
|
+
var InMemoryIntegrationGrantStore = class {
|
|
6000
|
+
grants = /* @__PURE__ */ new Map();
|
|
6001
|
+
get(grantId) {
|
|
6002
|
+
return this.grants.get(grantId);
|
|
6003
|
+
}
|
|
6004
|
+
put(grant) {
|
|
6005
|
+
this.grants.set(grant.id, grant);
|
|
6006
|
+
}
|
|
6007
|
+
listByManifest(manifestId, grantee) {
|
|
6008
|
+
return [...this.grants.values()].filter(
|
|
6009
|
+
(grant) => grant.manifestId === manifestId && (!grantee || sameActor(grant.grantee, grantee))
|
|
6010
|
+
);
|
|
6011
|
+
}
|
|
6012
|
+
listByGrantee(grantee) {
|
|
6013
|
+
return [...this.grants.values()].filter((grant) => sameActor(grant.grantee, grantee));
|
|
6014
|
+
}
|
|
6015
|
+
delete(grantId) {
|
|
6016
|
+
this.grants.delete(grantId);
|
|
6017
|
+
}
|
|
6018
|
+
};
|
|
6019
|
+
var IntegrationRuntime = class {
|
|
6020
|
+
hub;
|
|
6021
|
+
grants;
|
|
6022
|
+
now;
|
|
6023
|
+
constructor(options) {
|
|
6024
|
+
this.hub = options.hub;
|
|
6025
|
+
this.grants = options.grants ?? new InMemoryIntegrationGrantStore();
|
|
6026
|
+
this.now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
6027
|
+
}
|
|
6028
|
+
async registry() {
|
|
6029
|
+
return this.hub.listRegistry();
|
|
6030
|
+
}
|
|
6031
|
+
async resolveManifest(manifest, owner) {
|
|
6032
|
+
const registry = await this.registry();
|
|
6033
|
+
const connections = await this.hub.listConnections(owner);
|
|
6034
|
+
const resolutions = manifest.requirements.map(
|
|
6035
|
+
(requirement) => resolveRequirement(requirement, owner, registry, connections)
|
|
6036
|
+
);
|
|
6037
|
+
return {
|
|
6038
|
+
manifest,
|
|
6039
|
+
owner,
|
|
6040
|
+
ready: resolutions.filter((resolution) => resolution.status === "ready"),
|
|
6041
|
+
missing: resolutions.filter((resolution) => resolution.status !== "ready" && !resolution.requirement.optional),
|
|
6042
|
+
optionalMissing: resolutions.filter((resolution) => resolution.status !== "ready" && resolution.requirement.optional === true)
|
|
6043
|
+
};
|
|
6044
|
+
}
|
|
6045
|
+
async createGrants(input) {
|
|
6046
|
+
const resolution = await this.resolveManifest(input.manifest, input.owner);
|
|
6047
|
+
if (resolution.missing.length > 0) {
|
|
6048
|
+
throw new Error(`Cannot create integration grants; missing requirements: ${resolution.missing.map((r) => r.requirement.id).join(", ")}`);
|
|
6049
|
+
}
|
|
6050
|
+
const now = this.now().toISOString();
|
|
6051
|
+
const grants = resolution.ready.map((ready) => ({
|
|
6052
|
+
id: `grant_${input.manifest.id}_${ready.requirement.id}_${ready.connection.id}`,
|
|
6053
|
+
manifestId: input.manifest.id,
|
|
6054
|
+
requirementId: ready.requirement.id,
|
|
6055
|
+
owner: input.owner,
|
|
6056
|
+
grantee: input.grantee,
|
|
6057
|
+
connectionId: ready.connection.id,
|
|
6058
|
+
connectorId: ready.connector.id,
|
|
6059
|
+
scopes: requiredScopes(ready.requirement, ready.connector),
|
|
6060
|
+
allowedActions: requiredActions(ready.requirement, ready.connector),
|
|
6061
|
+
allowedTriggers: requiredTriggers(ready.requirement, ready.connector),
|
|
6062
|
+
status: "active",
|
|
6063
|
+
createdAt: now,
|
|
6064
|
+
updatedAt: now,
|
|
6065
|
+
metadata: input.metadata
|
|
6066
|
+
}));
|
|
6067
|
+
for (const grant of grants) await this.grants.put(grant);
|
|
6068
|
+
return grants;
|
|
6069
|
+
}
|
|
6070
|
+
async buildSandboxBundle(input) {
|
|
6071
|
+
const grants = (await this.grants.listByManifest(input.manifestId, input.grantee)).filter((grant) => grant.status === "active");
|
|
6072
|
+
const registry = await this.registry();
|
|
6073
|
+
const bindings = [];
|
|
6074
|
+
const connectors = [];
|
|
6075
|
+
let expiresAt = "";
|
|
6076
|
+
for (const grant of grants) {
|
|
6077
|
+
const entry = registry.byId.get(grant.connectorId);
|
|
6078
|
+
if (!entry) continue;
|
|
6079
|
+
const connector = {
|
|
6080
|
+
...entry.connector,
|
|
6081
|
+
actions: entry.connector.actions.filter((action) => grant.allowedActions.includes(action.id)),
|
|
6082
|
+
triggers: entry.connector.triggers?.filter((trigger) => grant.allowedTriggers.includes(trigger.id)),
|
|
6083
|
+
scopes: entry.connector.scopes.filter((scope) => grant.scopes.includes(scope))
|
|
6084
|
+
};
|
|
6085
|
+
const capability = await this.hub.issueCapability({
|
|
6086
|
+
subject: input.subject,
|
|
6087
|
+
connectionId: grant.connectionId,
|
|
6088
|
+
scopes: grant.scopes,
|
|
6089
|
+
allowedActions: grant.allowedActions,
|
|
6090
|
+
ttlMs: input.ttlMs,
|
|
6091
|
+
metadata: {
|
|
6092
|
+
manifestId: grant.manifestId,
|
|
6093
|
+
grantId: grant.id,
|
|
6094
|
+
requirementId: grant.requirementId
|
|
6095
|
+
}
|
|
6096
|
+
});
|
|
6097
|
+
bindings.push({
|
|
6098
|
+
requirementId: grant.requirementId,
|
|
6099
|
+
connectorId: grant.connectorId,
|
|
6100
|
+
connectionId: grant.connectionId,
|
|
6101
|
+
grantId: grant.id,
|
|
6102
|
+
scopes: grant.scopes,
|
|
6103
|
+
allowedActions: grant.allowedActions,
|
|
6104
|
+
allowedTriggers: grant.allowedTriggers,
|
|
6105
|
+
capability
|
|
6106
|
+
});
|
|
6107
|
+
connectors.push(connector);
|
|
6108
|
+
expiresAt = capability.capability.expiresAt;
|
|
6109
|
+
}
|
|
6110
|
+
return {
|
|
6111
|
+
manifestId: input.manifestId,
|
|
6112
|
+
subject: input.subject,
|
|
6113
|
+
capabilities: bindings,
|
|
6114
|
+
connectors,
|
|
6115
|
+
tools: buildIntegrationToolCatalog(connectors),
|
|
6116
|
+
expiresAt
|
|
6117
|
+
};
|
|
6118
|
+
}
|
|
6119
|
+
};
|
|
6120
|
+
function createIntegrationRuntime(options) {
|
|
6121
|
+
return new IntegrationRuntime(options);
|
|
6122
|
+
}
|
|
6123
|
+
function resolveRequirement(requirement, owner, registry, connections) {
|
|
6124
|
+
const entry = registry.byId.get(requirement.connectorId);
|
|
6125
|
+
if (!entry) {
|
|
6126
|
+
return missing(requirement, "unknown_connector", `Unknown connector ${requirement.connectorId}.`);
|
|
6127
|
+
}
|
|
6128
|
+
const connector = entry.connector;
|
|
6129
|
+
if (connector.actions.length === 0 && (connector.triggers?.length ?? 0) === 0) {
|
|
6130
|
+
return missing(requirement, "not_executable", `${connector.title} is catalog-only and cannot be invoked yet.`, connector, entry);
|
|
6131
|
+
}
|
|
6132
|
+
const scopes = requiredScopes(requirement, connector);
|
|
6133
|
+
const actions = requiredActions(requirement, connector);
|
|
6134
|
+
const triggers = requiredTriggers(requirement, connector);
|
|
6135
|
+
const connection = connections.find(
|
|
6136
|
+
(candidate) => sameActor(candidate.owner, owner) && candidate.status === "active" && (candidate.connectorId === connector.id || entry.aliases.includes(candidate.connectorId)) && scopes.every((scope) => candidate.grantedScopes.includes(scope))
|
|
6137
|
+
);
|
|
6138
|
+
if (!connection) {
|
|
6139
|
+
return {
|
|
6140
|
+
requirement,
|
|
6141
|
+
status: "missing_connection",
|
|
6142
|
+
connector,
|
|
6143
|
+
registryEntry: entry,
|
|
6144
|
+
missingScopes: scopes,
|
|
6145
|
+
missingActions: actions,
|
|
6146
|
+
missingTriggers: triggers,
|
|
6147
|
+
message: `${connector.title} needs an active user connection with the required scopes.`
|
|
6148
|
+
};
|
|
6149
|
+
}
|
|
6150
|
+
return {
|
|
6151
|
+
requirement,
|
|
6152
|
+
status: "ready",
|
|
6153
|
+
connector,
|
|
6154
|
+
registryEntry: entry,
|
|
6155
|
+
connection,
|
|
6156
|
+
missingScopes: [],
|
|
6157
|
+
missingActions: [],
|
|
6158
|
+
missingTriggers: [],
|
|
6159
|
+
message: `${connector.title} is ready.`
|
|
6160
|
+
};
|
|
6161
|
+
}
|
|
6162
|
+
function missing(requirement, status, message, connector, registryEntry2) {
|
|
6163
|
+
return {
|
|
6164
|
+
requirement,
|
|
6165
|
+
status,
|
|
6166
|
+
connector,
|
|
6167
|
+
registryEntry: registryEntry2,
|
|
6168
|
+
missingScopes: [],
|
|
6169
|
+
missingActions: [],
|
|
6170
|
+
missingTriggers: [],
|
|
6171
|
+
message
|
|
6172
|
+
};
|
|
6173
|
+
}
|
|
6174
|
+
function requiredActions(requirement, connector) {
|
|
6175
|
+
if (requirement.mode === "trigger") return [];
|
|
6176
|
+
if (requirement.requiredActions?.length) return unique5(requirement.requiredActions);
|
|
6177
|
+
const actions = connector.actions.filter((action) => {
|
|
6178
|
+
if (requirement.mode === "read") return action.risk === "read";
|
|
6179
|
+
if (requirement.mode === "write") return action.risk !== "read";
|
|
6180
|
+
return false;
|
|
6181
|
+
});
|
|
6182
|
+
return unique5(actions.map((action) => action.id));
|
|
6183
|
+
}
|
|
6184
|
+
function requiredTriggers(requirement, connector) {
|
|
6185
|
+
if (requirement.requiredTriggers?.length) return unique5(requirement.requiredTriggers);
|
|
6186
|
+
if (requirement.mode !== "trigger") return [];
|
|
6187
|
+
return unique5((connector.triggers ?? []).map((trigger) => trigger.id));
|
|
6188
|
+
}
|
|
6189
|
+
function requiredScopes(requirement, connector) {
|
|
6190
|
+
if (requirement.requiredScopes?.length) return unique5(requirement.requiredScopes);
|
|
6191
|
+
const actionIds = new Set(requiredActions(requirement, connector));
|
|
6192
|
+
const triggerIds = new Set(requiredTriggers(requirement, connector));
|
|
6193
|
+
return unique5([
|
|
6194
|
+
...connector.actions.filter((action) => actionIds.has(action.id)).flatMap((action) => action.requiredScopes),
|
|
6195
|
+
...(connector.triggers ?? []).filter((trigger) => triggerIds.has(trigger.id)).flatMap((trigger) => trigger.requiredScopes)
|
|
6196
|
+
]);
|
|
6197
|
+
}
|
|
6198
|
+
function sameActor(a, b) {
|
|
6199
|
+
return a.type === b.type && a.id === b.id;
|
|
6200
|
+
}
|
|
6201
|
+
function unique5(values) {
|
|
6202
|
+
return [...new Set(values)];
|
|
6203
|
+
}
|
|
6204
|
+
|
|
6205
|
+
// src/workflow.ts
|
|
6206
|
+
var InMemoryIntegrationWorkflowStore = class {
|
|
6207
|
+
workflows = /* @__PURE__ */ new Map();
|
|
6208
|
+
put(workflow) {
|
|
6209
|
+
this.workflows.set(workflow.id, workflow);
|
|
6210
|
+
}
|
|
6211
|
+
get(id) {
|
|
6212
|
+
return this.workflows.get(id);
|
|
6213
|
+
}
|
|
6214
|
+
list() {
|
|
6215
|
+
return [...this.workflows.values()];
|
|
6216
|
+
}
|
|
6217
|
+
listByWorkflow(workflowId) {
|
|
6218
|
+
return [...this.workflows.values()].filter((workflow) => workflow.workflowId === workflowId);
|
|
6219
|
+
}
|
|
6220
|
+
listByOwner(owner) {
|
|
6221
|
+
return [...this.workflows.values()].filter((workflow) => sameActor2(workflow.owner, owner));
|
|
6222
|
+
}
|
|
6223
|
+
};
|
|
6224
|
+
var IntegrationWorkflowRuntime = class {
|
|
6225
|
+
runtime;
|
|
6226
|
+
hub;
|
|
6227
|
+
grants;
|
|
6228
|
+
store;
|
|
6229
|
+
now;
|
|
6230
|
+
constructor(options) {
|
|
6231
|
+
this.runtime = options.runtime;
|
|
6232
|
+
this.hub = options.hub;
|
|
6233
|
+
this.grants = options.grants;
|
|
6234
|
+
this.store = options.store ?? new InMemoryIntegrationWorkflowStore();
|
|
6235
|
+
this.now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
6236
|
+
}
|
|
6237
|
+
async install(input) {
|
|
6238
|
+
const grants = await this.runtime.createGrants({
|
|
6239
|
+
manifest: input.workflow.manifest,
|
|
6240
|
+
owner: input.owner,
|
|
6241
|
+
grantee: input.grantee,
|
|
6242
|
+
metadata: { workflowId: input.workflow.id }
|
|
6243
|
+
});
|
|
6244
|
+
const triggerGrant = findTriggerGrant(grants, input.workflow.trigger.requirementId, input.workflow.trigger.triggerId);
|
|
6245
|
+
const subscription = await this.hub.subscribeTrigger(
|
|
6246
|
+
triggerGrant.connectionId,
|
|
6247
|
+
input.workflow.trigger.triggerId,
|
|
6248
|
+
input.workflow.trigger.targetUrl
|
|
6249
|
+
);
|
|
6250
|
+
const installed = {
|
|
6251
|
+
id: `workflow_${input.workflow.id}_${triggerGrant.id}`,
|
|
6252
|
+
workflowId: input.workflow.id,
|
|
6253
|
+
manifestId: input.workflow.manifest.id,
|
|
6254
|
+
owner: input.owner,
|
|
6255
|
+
grantee: input.grantee,
|
|
6256
|
+
triggerGrantId: triggerGrant.id,
|
|
6257
|
+
subscription,
|
|
6258
|
+
status: "active",
|
|
6259
|
+
createdAt: this.now().toISOString(),
|
|
6260
|
+
metadata: input.workflow.metadata
|
|
6261
|
+
};
|
|
6262
|
+
await this.store.put(installed);
|
|
6263
|
+
return installed;
|
|
6264
|
+
}
|
|
6265
|
+
async dispatchEvent(event, handler) {
|
|
6266
|
+
const workflows = (await this.store.list()).filter(
|
|
6267
|
+
(workflow) => workflow.status === "active" && workflow.subscription.connectionId === event.connectionId && workflow.subscription.trigger === event.trigger
|
|
6268
|
+
);
|
|
6269
|
+
await handler({ event, workflows });
|
|
6270
|
+
return { matched: workflows };
|
|
6271
|
+
}
|
|
6272
|
+
};
|
|
6273
|
+
function createIntegrationWorkflowRuntime(options) {
|
|
6274
|
+
return new IntegrationWorkflowRuntime(options);
|
|
6275
|
+
}
|
|
6276
|
+
function findTriggerGrant(grants, requirementId, triggerId) {
|
|
6277
|
+
const grant = grants.find(
|
|
6278
|
+
(candidate) => candidate.requirementId === requirementId && candidate.allowedTriggers.includes(triggerId)
|
|
6279
|
+
);
|
|
6280
|
+
if (!grant) throw new Error(`Missing trigger grant ${requirementId}/${triggerId}.`);
|
|
6281
|
+
return grant;
|
|
6282
|
+
}
|
|
6283
|
+
function sameActor2(a, b) {
|
|
6284
|
+
return a.type === b.type && a.id === b.id;
|
|
6285
|
+
}
|
|
6286
|
+
|
|
5247
6287
|
// src/specs/types.ts
|
|
5248
6288
|
function specAuthToConnectorAuth(auth) {
|
|
5249
6289
|
if (auth.mode === "api_key") return "api_key";
|
|
@@ -5433,17 +6473,20 @@ var IntegrationHub = class {
|
|
|
5433
6473
|
await this.store.put(connection);
|
|
5434
6474
|
return connection;
|
|
5435
6475
|
}
|
|
6476
|
+
async listConnections(owner) {
|
|
6477
|
+
return this.store.listByOwner(owner);
|
|
6478
|
+
}
|
|
5436
6479
|
async issueCapability(request) {
|
|
5437
6480
|
const connection = await this.requireConnection(request.connectionId);
|
|
5438
6481
|
this.assertConnectionActive(connection);
|
|
5439
6482
|
assertScopes(connection, request.scopes);
|
|
5440
6483
|
const now = this.now();
|
|
5441
6484
|
const capability = {
|
|
5442
|
-
id: `cap_${
|
|
6485
|
+
id: `cap_${randomUUID3()}`,
|
|
5443
6486
|
subject: request.subject,
|
|
5444
6487
|
connectionId: request.connectionId,
|
|
5445
|
-
scopes:
|
|
5446
|
-
allowedActions:
|
|
6488
|
+
scopes: unique6(request.scopes),
|
|
6489
|
+
allowedActions: unique6(request.allowedActions),
|
|
5447
6490
|
issuedAt: now.toISOString(),
|
|
5448
6491
|
expiresAt: new Date(now.getTime() + request.ttlMs).toISOString(),
|
|
5449
6492
|
metadata: request.metadata
|
|
@@ -5671,9 +6714,9 @@ async function postJson(fetcher, url, body, bearer) {
|
|
|
5671
6714
|
if (!response.ok) throw new IntegrationError(`Integration provider returned HTTP ${response.status}.`, "provider_not_found");
|
|
5672
6715
|
return response.json();
|
|
5673
6716
|
}
|
|
5674
|
-
function assertScopes(connection,
|
|
5675
|
-
const
|
|
5676
|
-
if (
|
|
6717
|
+
function assertScopes(connection, requiredScopes2) {
|
|
6718
|
+
const missing2 = requiredScopes2.filter((scope) => !connection.grantedScopes.includes(scope));
|
|
6719
|
+
if (missing2.length > 0) throw new IntegrationError(`Missing integration scopes: ${missing2.join(", ")}`, "scope_denied");
|
|
5677
6720
|
}
|
|
5678
6721
|
function hmac(payload, secret) {
|
|
5679
6722
|
return createHmac3("sha256", secret).update(payload).digest("base64url");
|
|
@@ -5689,18 +6732,32 @@ function base64UrlEncode(value) {
|
|
|
5689
6732
|
function base64UrlDecode(value) {
|
|
5690
6733
|
return Buffer.from(value, "base64url").toString("utf8");
|
|
5691
6734
|
}
|
|
5692
|
-
function
|
|
6735
|
+
function unique6(values) {
|
|
5693
6736
|
return [...new Set(values)];
|
|
5694
6737
|
}
|
|
5695
6738
|
export {
|
|
5696
6739
|
ACTIVEPIECES_OVERRIDES,
|
|
6740
|
+
ApprovalBackedPolicyEngine,
|
|
5697
6741
|
CredentialsExpired,
|
|
6742
|
+
DEFAULT_INTEGRATION_BRIDGE_ENV,
|
|
5698
6743
|
DEFAULT_SIGNATURE_TOLERANCE_SECONDS,
|
|
6744
|
+
DefaultIntegrationActionGuard,
|
|
5699
6745
|
INTEGRATION_FAMILIES,
|
|
5700
6746
|
InMemoryConnectionStore,
|
|
6747
|
+
InMemoryIntegrationApprovalStore,
|
|
6748
|
+
InMemoryIntegrationAuditStore,
|
|
6749
|
+
InMemoryIntegrationEventStore,
|
|
6750
|
+
InMemoryIntegrationGrantStore,
|
|
6751
|
+
InMemoryIntegrationHealthcheckStore,
|
|
6752
|
+
InMemoryIntegrationIdempotencyStore,
|
|
6753
|
+
InMemoryIntegrationSecretStore,
|
|
6754
|
+
InMemoryIntegrationWorkflowStore,
|
|
5701
6755
|
InMemoryOAuthFlowStore,
|
|
5702
6756
|
IntegrationError,
|
|
5703
6757
|
IntegrationHub,
|
|
6758
|
+
IntegrationRuntime,
|
|
6759
|
+
IntegrationSandboxHost,
|
|
6760
|
+
IntegrationWorkflowRuntime,
|
|
5704
6761
|
ResourceContention,
|
|
5705
6762
|
StaticIntegrationPolicyEngine,
|
|
5706
6763
|
_resetPendingFlowsForTests,
|
|
@@ -5712,6 +6769,8 @@ export {
|
|
|
5712
6769
|
buildApprovalRequest,
|
|
5713
6770
|
buildDefaultIntegrationRegistry,
|
|
5714
6771
|
buildHealthcheckPlan,
|
|
6772
|
+
buildIntegrationBridgeEnvironment,
|
|
6773
|
+
buildIntegrationBridgePayload,
|
|
5715
6774
|
buildIntegrationCoverageConnectors,
|
|
5716
6775
|
buildIntegrationInvocationEnvelope,
|
|
5717
6776
|
buildIntegrationToolCatalog,
|
|
@@ -5719,12 +6778,23 @@ export {
|
|
|
5719
6778
|
composeIntegrationRegistry,
|
|
5720
6779
|
consoleStepsToText,
|
|
5721
6780
|
consumePendingFlow,
|
|
6781
|
+
createApprovalBackedPolicyEngine,
|
|
6782
|
+
createAuditingActionGuard,
|
|
6783
|
+
createConnectionCredentialResolver,
|
|
5722
6784
|
createConnectorAdapterProvider,
|
|
6785
|
+
createCredentialBackedAdapterProvider,
|
|
6786
|
+
createDefaultIntegrationActionGuard,
|
|
5723
6787
|
createDefaultIntegrationPolicyEngine,
|
|
5724
6788
|
createGatewayCatalogProvider,
|
|
5725
6789
|
createHttpIntegrationProvider,
|
|
6790
|
+
createIntegrationAuditEvent,
|
|
6791
|
+
createIntegrationRuntime,
|
|
6792
|
+
createIntegrationWorkflowRuntime,
|
|
5726
6793
|
createMockIntegrationProvider,
|
|
5727
6794
|
declarativeRestConnector,
|
|
6795
|
+
decodeIntegrationBridgePayload,
|
|
6796
|
+
dispatchIntegrationInvocation,
|
|
6797
|
+
encodeIntegrationBridgePayload,
|
|
5728
6798
|
exchangeAuthorizationCode,
|
|
5729
6799
|
firstHeader,
|
|
5730
6800
|
getActivepiecesOverride,
|
|
@@ -5734,6 +6804,7 @@ export {
|
|
|
5734
6804
|
gitlabConnector,
|
|
5735
6805
|
googleCalendar,
|
|
5736
6806
|
googleSheets,
|
|
6807
|
+
healthcheckRequest,
|
|
5737
6808
|
hubspot,
|
|
5738
6809
|
importGraphqlConnector,
|
|
5739
6810
|
importMcpConnector,
|
|
@@ -5752,16 +6823,25 @@ export {
|
|
|
5752
6823
|
normalizeGatewayCatalog,
|
|
5753
6824
|
normalizeIntegrationResult,
|
|
5754
6825
|
notionDatabase,
|
|
6826
|
+
parseIntegrationBridgeEnvironment,
|
|
5755
6827
|
parseIntegrationToolName,
|
|
5756
6828
|
parseStripeSignatureHeader,
|
|
6829
|
+
receiveIntegrationWebhook,
|
|
5757
6830
|
redactApprovalRequest,
|
|
5758
6831
|
redactCapability,
|
|
6832
|
+
redactIntegrationBridgePayload,
|
|
5759
6833
|
redactInvocationEnvelope,
|
|
5760
6834
|
refreshAccessToken,
|
|
5761
6835
|
renderAgentToolDescription,
|
|
5762
6836
|
renderConsoleSteps,
|
|
5763
6837
|
renderRunbookMarkdown,
|
|
6838
|
+
resolveConnectionCredentials,
|
|
6839
|
+
resolveIntegrationApproval,
|
|
6840
|
+
revokeConnection,
|
|
6841
|
+
runIntegrationHealthcheck,
|
|
6842
|
+
runIntegrationHealthchecks,
|
|
5764
6843
|
salesforceConnector,
|
|
6844
|
+
sanitizeAuditConnection,
|
|
5765
6845
|
sanitizeConnection,
|
|
5766
6846
|
searchIntegrationTools,
|
|
5767
6847
|
signCapability,
|
|
@@ -5769,6 +6849,7 @@ export {
|
|
|
5769
6849
|
slackEventsConnector,
|
|
5770
6850
|
specAuthToConnectorAuth,
|
|
5771
6851
|
startOAuthFlow,
|
|
6852
|
+
storedEventToTriggerEvent,
|
|
5772
6853
|
stripePackConnector,
|
|
5773
6854
|
stripeWebhookReceiverConnector,
|
|
5774
6855
|
summarizeIntegrationRegistry,
|