@tangle-network/agent-integrations 0.14.0 → 0.16.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 +42 -0
- package/dist/index.d.ts +793 -323
- package/dist/index.js +1743 -366
- package/dist/index.js.map +1 -1
- package/docs/architecture.md +7 -0
- package/docs/production-completion-checklist.md +63 -0
- package/docs/repo-structure.md +47 -0
- package/examples/calendar-exercise-app.ts +78 -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";
|
|
@@ -129,7 +129,7 @@ function buildActivepiecesConnectors(options = {}) {
|
|
|
129
129
|
const category = override?.category ?? entry.category;
|
|
130
130
|
const scopes = [`${entry.id}.read`, `${entry.id}.write`];
|
|
131
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((
|
|
132
|
+
const catalogTriggers = entry.triggers.map((trigger2) => toTrigger(trigger2, scopes, dataClassFor(category)));
|
|
133
133
|
return {
|
|
134
134
|
id: entry.id,
|
|
135
135
|
providerId,
|
|
@@ -172,10 +172,10 @@ function toAction(action, scopes, dataClass) {
|
|
|
172
172
|
inputSchema: { type: "object", additionalProperties: true, properties: {} }
|
|
173
173
|
};
|
|
174
174
|
}
|
|
175
|
-
function toTrigger(
|
|
175
|
+
function toTrigger(trigger2, scopes, dataClass) {
|
|
176
176
|
return {
|
|
177
|
-
id:
|
|
178
|
-
title:
|
|
177
|
+
id: trigger2.id,
|
|
178
|
+
title: trigger2.title,
|
|
179
179
|
requiredScopes: [scopes[0]],
|
|
180
180
|
dataClass,
|
|
181
181
|
payloadSchema: { type: "object", additionalProperties: true, properties: {} }
|
|
@@ -1247,8 +1247,8 @@ function mergeActions(candidates) {
|
|
|
1247
1247
|
function mergeTriggers(candidates) {
|
|
1248
1248
|
const out = /* @__PURE__ */ new Map();
|
|
1249
1249
|
for (const candidate of toolBindableCandidates(candidates)) {
|
|
1250
|
-
for (const
|
|
1251
|
-
if (!out.has(
|
|
1250
|
+
for (const trigger2 of candidate.connector.triggers ?? []) {
|
|
1251
|
+
if (!out.has(trigger2.id)) out.set(trigger2.id, trigger2);
|
|
1252
1252
|
}
|
|
1253
1253
|
}
|
|
1254
1254
|
return out.size > 0 ? [...out.values()] : void 0;
|
|
@@ -1290,6 +1290,1554 @@ function unique(values) {
|
|
|
1290
1290
|
return [...new Set(values)];
|
|
1291
1291
|
}
|
|
1292
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/actions.ts
|
|
1525
|
+
var CANONICAL_INTEGRATION_ACTIONS = {
|
|
1526
|
+
googleCalendarEventsList: "google-calendar.events.list",
|
|
1527
|
+
googleCalendarEventsCreate: "google-calendar.events.create",
|
|
1528
|
+
gmailMessagesSearch: "gmail.messages.search",
|
|
1529
|
+
gmailMessagesSend: "gmail.messages.send",
|
|
1530
|
+
googleDriveFilesSearch: "google-drive.files.search",
|
|
1531
|
+
googleDriveFilesRead: "google-drive.files.read",
|
|
1532
|
+
githubRepositoriesGet: "github.repositories.get",
|
|
1533
|
+
githubIssuesSearch: "github.issues.search",
|
|
1534
|
+
githubIssuesCreate: "github.issues.create",
|
|
1535
|
+
githubPullRequestsComment: "github.pull-requests.comment",
|
|
1536
|
+
slackChannelsList: "slack.channels.list",
|
|
1537
|
+
slackMessagesSearch: "slack.messages.search",
|
|
1538
|
+
slackMessagesPost: "slack.messages.post",
|
|
1539
|
+
providerHttpRequest: "provider.http.request"
|
|
1540
|
+
};
|
|
1541
|
+
function buildCanonicalLaunchConnectors(options = {}) {
|
|
1542
|
+
const providerId = options.providerId ?? "tangle-platform";
|
|
1543
|
+
const connectors = [
|
|
1544
|
+
googleCalendarConnector(providerId),
|
|
1545
|
+
gmailConnector(providerId),
|
|
1546
|
+
googleDriveConnector(providerId),
|
|
1547
|
+
githubConnector(providerId),
|
|
1548
|
+
slackConnector(providerId)
|
|
1549
|
+
];
|
|
1550
|
+
if (!options.includeProviderPassthrough) return connectors;
|
|
1551
|
+
return connectors.map((connector) => ({
|
|
1552
|
+
...connector,
|
|
1553
|
+
actions: [...connector.actions, providerPassthroughAction(connector.id)]
|
|
1554
|
+
}));
|
|
1555
|
+
}
|
|
1556
|
+
function canonicalActionConnectorId(actionId) {
|
|
1557
|
+
if (actionId.startsWith("google-calendar.")) return "google-calendar";
|
|
1558
|
+
if (actionId.startsWith("gmail.")) return "gmail";
|
|
1559
|
+
if (actionId.startsWith("google-drive.")) return "google-drive";
|
|
1560
|
+
if (actionId.startsWith("github.")) return "github";
|
|
1561
|
+
if (actionId.startsWith("slack.")) return "slack";
|
|
1562
|
+
if (actionId === CANONICAL_INTEGRATION_ACTIONS.providerHttpRequest) return void 0;
|
|
1563
|
+
return actionId.split(".")[0];
|
|
1564
|
+
}
|
|
1565
|
+
function googleCalendarConnector(providerId) {
|
|
1566
|
+
return {
|
|
1567
|
+
id: "google-calendar",
|
|
1568
|
+
providerId,
|
|
1569
|
+
title: "Google Calendar",
|
|
1570
|
+
category: "calendar",
|
|
1571
|
+
auth: "oauth2",
|
|
1572
|
+
scopes: ["https://www.googleapis.com/auth/calendar.readonly", "https://www.googleapis.com/auth/calendar.events"],
|
|
1573
|
+
actions: [
|
|
1574
|
+
{
|
|
1575
|
+
id: CANONICAL_INTEGRATION_ACTIONS.googleCalendarEventsList,
|
|
1576
|
+
title: "List calendar events",
|
|
1577
|
+
risk: "read",
|
|
1578
|
+
requiredScopes: ["https://www.googleapis.com/auth/calendar.readonly"],
|
|
1579
|
+
dataClass: "private",
|
|
1580
|
+
description: "Read events from a Google Calendar over a bounded time range.",
|
|
1581
|
+
inputSchema: objectSchema2({
|
|
1582
|
+
calendarId: { type: "string", default: "primary" },
|
|
1583
|
+
timeMin: { type: "string", description: "RFC3339 lower bound." },
|
|
1584
|
+
timeMax: { type: "string", description: "RFC3339 upper bound." }
|
|
1585
|
+
}, ["timeMin", "timeMax"])
|
|
1586
|
+
},
|
|
1587
|
+
{
|
|
1588
|
+
id: CANONICAL_INTEGRATION_ACTIONS.googleCalendarEventsCreate,
|
|
1589
|
+
title: "Create calendar event",
|
|
1590
|
+
risk: "write",
|
|
1591
|
+
requiredScopes: ["https://www.googleapis.com/auth/calendar.events"],
|
|
1592
|
+
dataClass: "private",
|
|
1593
|
+
approvalRequired: true,
|
|
1594
|
+
description: "Create an event on a Google Calendar after user approval.",
|
|
1595
|
+
inputSchema: objectSchema2({
|
|
1596
|
+
calendarId: { type: "string", default: "primary" },
|
|
1597
|
+
start: { type: "string", description: "RFC3339 start time." },
|
|
1598
|
+
end: { type: "string", description: "RFC3339 end time." },
|
|
1599
|
+
summary: { type: "string" },
|
|
1600
|
+
description: { type: "string" },
|
|
1601
|
+
attendees: { type: "array", items: { type: "string" } }
|
|
1602
|
+
}, ["start", "end", "summary"])
|
|
1603
|
+
}
|
|
1604
|
+
],
|
|
1605
|
+
metadata: { source: "canonical-launch", supportTier: "setupReady" }
|
|
1606
|
+
};
|
|
1607
|
+
}
|
|
1608
|
+
function gmailConnector(providerId) {
|
|
1609
|
+
return {
|
|
1610
|
+
id: "gmail",
|
|
1611
|
+
providerId,
|
|
1612
|
+
title: "Gmail",
|
|
1613
|
+
category: "email",
|
|
1614
|
+
auth: "oauth2",
|
|
1615
|
+
scopes: ["https://www.googleapis.com/auth/gmail.readonly", "https://www.googleapis.com/auth/gmail.send"],
|
|
1616
|
+
actions: [
|
|
1617
|
+
{
|
|
1618
|
+
id: CANONICAL_INTEGRATION_ACTIONS.gmailMessagesSearch,
|
|
1619
|
+
title: "Search Gmail messages",
|
|
1620
|
+
risk: "read",
|
|
1621
|
+
requiredScopes: ["https://www.googleapis.com/auth/gmail.readonly"],
|
|
1622
|
+
dataClass: "private",
|
|
1623
|
+
description: "Search user Gmail messages and return bounded message metadata/snippets.",
|
|
1624
|
+
inputSchema: objectSchema2({ query: { type: "string" }, maxResults: { type: "integer", minimum: 1, maximum: 50 } }, ["query"])
|
|
1625
|
+
},
|
|
1626
|
+
{
|
|
1627
|
+
id: CANONICAL_INTEGRATION_ACTIONS.gmailMessagesSend,
|
|
1628
|
+
title: "Send Gmail message",
|
|
1629
|
+
risk: "write",
|
|
1630
|
+
requiredScopes: ["https://www.googleapis.com/auth/gmail.send"],
|
|
1631
|
+
dataClass: "private",
|
|
1632
|
+
approvalRequired: true,
|
|
1633
|
+
description: "Send an email from the user account after approval.",
|
|
1634
|
+
inputSchema: objectSchema2({
|
|
1635
|
+
to: { type: "array", items: { type: "string" } },
|
|
1636
|
+
subject: { type: "string" },
|
|
1637
|
+
body: { type: "string" }
|
|
1638
|
+
}, ["to", "subject", "body"])
|
|
1639
|
+
}
|
|
1640
|
+
],
|
|
1641
|
+
triggers: [{
|
|
1642
|
+
id: "gmail.messages.received",
|
|
1643
|
+
title: "Gmail message received",
|
|
1644
|
+
requiredScopes: ["https://www.googleapis.com/auth/gmail.readonly"],
|
|
1645
|
+
dataClass: "private",
|
|
1646
|
+
description: "Triggered when a new matching Gmail message is received."
|
|
1647
|
+
}],
|
|
1648
|
+
metadata: { source: "canonical-launch", supportTier: "setupReady" }
|
|
1649
|
+
};
|
|
1650
|
+
}
|
|
1651
|
+
function googleDriveConnector(providerId) {
|
|
1652
|
+
return {
|
|
1653
|
+
id: "google-drive",
|
|
1654
|
+
providerId,
|
|
1655
|
+
title: "Google Drive",
|
|
1656
|
+
category: "storage",
|
|
1657
|
+
auth: "oauth2",
|
|
1658
|
+
scopes: ["https://www.googleapis.com/auth/drive.readonly", "https://www.googleapis.com/auth/drive.file"],
|
|
1659
|
+
actions: [
|
|
1660
|
+
{
|
|
1661
|
+
id: CANONICAL_INTEGRATION_ACTIONS.googleDriveFilesSearch,
|
|
1662
|
+
title: "Search Drive files",
|
|
1663
|
+
risk: "read",
|
|
1664
|
+
requiredScopes: ["https://www.googleapis.com/auth/drive.readonly"],
|
|
1665
|
+
dataClass: "private",
|
|
1666
|
+
description: "Search user-visible Google Drive files.",
|
|
1667
|
+
inputSchema: objectSchema2({ query: { type: "string" }, maxResults: { type: "integer", minimum: 1, maximum: 50 } }, ["query"])
|
|
1668
|
+
},
|
|
1669
|
+
{
|
|
1670
|
+
id: CANONICAL_INTEGRATION_ACTIONS.googleDriveFilesRead,
|
|
1671
|
+
title: "Read Drive file",
|
|
1672
|
+
risk: "read",
|
|
1673
|
+
requiredScopes: ["https://www.googleapis.com/auth/drive.readonly"],
|
|
1674
|
+
dataClass: "private",
|
|
1675
|
+
description: "Read metadata and content for an authorized Drive file.",
|
|
1676
|
+
inputSchema: objectSchema2({ fileId: { type: "string" } }, ["fileId"])
|
|
1677
|
+
}
|
|
1678
|
+
],
|
|
1679
|
+
metadata: { source: "canonical-launch", supportTier: "setupReady" }
|
|
1680
|
+
};
|
|
1681
|
+
}
|
|
1682
|
+
function githubConnector(providerId) {
|
|
1683
|
+
return {
|
|
1684
|
+
id: "github",
|
|
1685
|
+
providerId,
|
|
1686
|
+
title: "GitHub",
|
|
1687
|
+
category: "workflow",
|
|
1688
|
+
auth: "oauth2",
|
|
1689
|
+
scopes: ["repo", "read:user"],
|
|
1690
|
+
actions: [
|
|
1691
|
+
readAction(CANONICAL_INTEGRATION_ACTIONS.githubRepositoriesGet, "Read repository metadata", ["repo"], objectSchema2({ owner: { type: "string" }, repo: { type: "string" } }, ["owner", "repo"])),
|
|
1692
|
+
readAction(CANONICAL_INTEGRATION_ACTIONS.githubIssuesSearch, "Search issues and pull requests", ["repo"], objectSchema2({ query: { type: "string" }, limit: { type: "integer", minimum: 1, maximum: 50 } }, ["query"])),
|
|
1693
|
+
writeAction(CANONICAL_INTEGRATION_ACTIONS.githubIssuesCreate, "Create issue", ["repo"], objectSchema2({ owner: { type: "string" }, repo: { type: "string" }, title: { type: "string" }, body: { type: "string" } }, ["owner", "repo", "title"])),
|
|
1694
|
+
writeAction(CANONICAL_INTEGRATION_ACTIONS.githubPullRequestsComment, "Comment on pull request", ["repo"], objectSchema2({ owner: { type: "string" }, repo: { type: "string" }, pullNumber: { type: "integer" }, body: { type: "string" } }, ["owner", "repo", "pullNumber", "body"]))
|
|
1695
|
+
],
|
|
1696
|
+
metadata: { source: "canonical-launch", supportTier: "setupReady" }
|
|
1697
|
+
};
|
|
1698
|
+
}
|
|
1699
|
+
function slackConnector(providerId) {
|
|
1700
|
+
return {
|
|
1701
|
+
id: "slack",
|
|
1702
|
+
providerId,
|
|
1703
|
+
title: "Slack",
|
|
1704
|
+
category: "chat",
|
|
1705
|
+
auth: "oauth2",
|
|
1706
|
+
scopes: ["channels:read", "search:read", "chat:write"],
|
|
1707
|
+
actions: [
|
|
1708
|
+
readAction(CANONICAL_INTEGRATION_ACTIONS.slackChannelsList, "List Slack channels", ["channels:read"], objectSchema2({ limit: { type: "integer", minimum: 1, maximum: 200 } })),
|
|
1709
|
+
readAction(CANONICAL_INTEGRATION_ACTIONS.slackMessagesSearch, "Search Slack messages", ["search:read"], objectSchema2({ query: { type: "string" }, count: { type: "integer", minimum: 1, maximum: 50 } }, ["query"])),
|
|
1710
|
+
writeAction(CANONICAL_INTEGRATION_ACTIONS.slackMessagesPost, "Post Slack message", ["chat:write"], objectSchema2({ channel: { type: "string" }, text: { type: "string" }, blocks: { type: "array" } }, ["channel", "text"]))
|
|
1711
|
+
],
|
|
1712
|
+
triggers: [trigger("slack.message.posted", "Slack message posted", ["channels:read"])],
|
|
1713
|
+
metadata: { source: "canonical-launch", supportTier: "setupReady" }
|
|
1714
|
+
};
|
|
1715
|
+
}
|
|
1716
|
+
function readAction(id, title, scopes, inputSchema) {
|
|
1717
|
+
return { id, title, risk: "read", requiredScopes: scopes, dataClass: "private", inputSchema };
|
|
1718
|
+
}
|
|
1719
|
+
function writeAction(id, title, scopes, inputSchema) {
|
|
1720
|
+
return { id, title, risk: "write", requiredScopes: scopes, dataClass: "private", approvalRequired: true, inputSchema };
|
|
1721
|
+
}
|
|
1722
|
+
function trigger(id, title, scopes) {
|
|
1723
|
+
return { id, title, requiredScopes: scopes, dataClass: "private" };
|
|
1724
|
+
}
|
|
1725
|
+
function providerPassthroughAction(connectorId) {
|
|
1726
|
+
return {
|
|
1727
|
+
id: CANONICAL_INTEGRATION_ACTIONS.providerHttpRequest,
|
|
1728
|
+
title: "Provider HTTP request",
|
|
1729
|
+
risk: "write",
|
|
1730
|
+
requiredScopes: [],
|
|
1731
|
+
dataClass: "sensitive",
|
|
1732
|
+
approvalRequired: true,
|
|
1733
|
+
description: `Controlled provider-native passthrough for ${connectorId}. Disabled by default by platform policy.`,
|
|
1734
|
+
inputSchema: objectSchema2({
|
|
1735
|
+
method: { type: "string", enum: ["GET", "POST", "PUT", "PATCH", "DELETE"] },
|
|
1736
|
+
path: { type: "string" },
|
|
1737
|
+
query: { type: "object" },
|
|
1738
|
+
body: { type: "object" }
|
|
1739
|
+
}, ["method", "path"])
|
|
1740
|
+
};
|
|
1741
|
+
}
|
|
1742
|
+
function objectSchema2(properties, required = []) {
|
|
1743
|
+
return { type: "object", additionalProperties: false, properties, required };
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
// src/bridge.ts
|
|
1747
|
+
var DEFAULT_INTEGRATION_BRIDGE_ENV = "TANGLE_INTEGRATION_BUNDLE";
|
|
1748
|
+
function buildIntegrationBridgePayload(bundle) {
|
|
1749
|
+
return {
|
|
1750
|
+
version: 1,
|
|
1751
|
+
manifestId: bundle.manifestId,
|
|
1752
|
+
subject: bundle.subject,
|
|
1753
|
+
expiresAt: bundle.expiresAt,
|
|
1754
|
+
tools: bundle.tools.flatMap((tool) => {
|
|
1755
|
+
const binding = bundle.capabilities.find(
|
|
1756
|
+
(candidate) => candidate.connectorId === tool.connectorId && candidate.connectionId && candidate.allowedActions.includes(tool.action.id)
|
|
1757
|
+
);
|
|
1758
|
+
if (!binding) return [];
|
|
1759
|
+
return [{
|
|
1760
|
+
name: tool.name,
|
|
1761
|
+
title: tool.title,
|
|
1762
|
+
connectorId: tool.connectorId,
|
|
1763
|
+
connectionId: binding.connectionId,
|
|
1764
|
+
action: tool.action.id,
|
|
1765
|
+
risk: tool.risk,
|
|
1766
|
+
dataClass: tool.dataClass,
|
|
1767
|
+
requiredScopes: tool.requiredScopes,
|
|
1768
|
+
capabilityToken: binding.capability.token
|
|
1769
|
+
}];
|
|
1770
|
+
})
|
|
1771
|
+
};
|
|
1772
|
+
}
|
|
1773
|
+
function encodeIntegrationBridgePayload(payload) {
|
|
1774
|
+
return Buffer.from(JSON.stringify(payload), "utf8").toString("base64url");
|
|
1775
|
+
}
|
|
1776
|
+
function decodeIntegrationBridgePayload(encoded) {
|
|
1777
|
+
const parsed = JSON.parse(Buffer.from(encoded, "base64url").toString("utf8"));
|
|
1778
|
+
assertBridgePayload(parsed);
|
|
1779
|
+
return parsed;
|
|
1780
|
+
}
|
|
1781
|
+
function buildIntegrationBridgeEnvironment(bundle, options = {}) {
|
|
1782
|
+
const envVar = options.envVar ?? DEFAULT_INTEGRATION_BRIDGE_ENV;
|
|
1783
|
+
return {
|
|
1784
|
+
[envVar]: encodeIntegrationBridgePayload(buildIntegrationBridgePayload(bundle))
|
|
1785
|
+
};
|
|
1786
|
+
}
|
|
1787
|
+
function parseIntegrationBridgeEnvironment(env, options = {}) {
|
|
1788
|
+
const envVar = options.envVar ?? DEFAULT_INTEGRATION_BRIDGE_ENV;
|
|
1789
|
+
const encoded = env[envVar];
|
|
1790
|
+
if (!encoded) throw new Error(`Missing ${envVar}.`);
|
|
1791
|
+
return decodeIntegrationBridgePayload(encoded);
|
|
1792
|
+
}
|
|
1793
|
+
function redactIntegrationBridgePayload(payload) {
|
|
1794
|
+
return {
|
|
1795
|
+
...payload,
|
|
1796
|
+
tools: payload.tools.map((tool) => ({
|
|
1797
|
+
...tool,
|
|
1798
|
+
capabilityToken: "[REDACTED]"
|
|
1799
|
+
}))
|
|
1800
|
+
};
|
|
1801
|
+
}
|
|
1802
|
+
function assertBridgePayload(value) {
|
|
1803
|
+
if (!value || typeof value !== "object") throw new Error("Invalid integration bridge payload.");
|
|
1804
|
+
const payload = value;
|
|
1805
|
+
if (payload.version !== 1) throw new Error("Unsupported integration bridge payload version.");
|
|
1806
|
+
if (typeof payload.manifestId !== "string") throw new Error("Invalid integration bridge manifestId.");
|
|
1807
|
+
if (typeof payload.expiresAt !== "string") throw new Error("Invalid integration bridge expiresAt.");
|
|
1808
|
+
if (!Array.isArray(payload.tools)) throw new Error("Invalid integration bridge tools.");
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
// src/errors.ts
|
|
1812
|
+
var IntegrationRuntimeError = class extends Error {
|
|
1813
|
+
code;
|
|
1814
|
+
status;
|
|
1815
|
+
userAction;
|
|
1816
|
+
metadata;
|
|
1817
|
+
constructor(input) {
|
|
1818
|
+
super(input.message);
|
|
1819
|
+
this.name = "IntegrationRuntimeError";
|
|
1820
|
+
this.code = input.code;
|
|
1821
|
+
this.status = input.status ?? statusForCode(input.code);
|
|
1822
|
+
this.userAction = input.userAction;
|
|
1823
|
+
this.metadata = input.metadata;
|
|
1824
|
+
}
|
|
1825
|
+
};
|
|
1826
|
+
function normalizeIntegrationError(error) {
|
|
1827
|
+
if (error instanceof IntegrationRuntimeError) {
|
|
1828
|
+
return {
|
|
1829
|
+
ok: false,
|
|
1830
|
+
code: error.code,
|
|
1831
|
+
message: error.message,
|
|
1832
|
+
status: error.status,
|
|
1833
|
+
userAction: error.userAction,
|
|
1834
|
+
metadata: redactUnknown2(error.metadata)
|
|
1835
|
+
};
|
|
1836
|
+
}
|
|
1837
|
+
const message = error instanceof Error ? error.message : String(error ?? "Unknown integration error.");
|
|
1838
|
+
return {
|
|
1839
|
+
ok: false,
|
|
1840
|
+
code: inferCode(message),
|
|
1841
|
+
message,
|
|
1842
|
+
status: 500
|
|
1843
|
+
};
|
|
1844
|
+
}
|
|
1845
|
+
function statusForCode(code) {
|
|
1846
|
+
if (code === "missing_connection" || code === "missing_grant") return 409;
|
|
1847
|
+
if (code === "approval_required") return 202;
|
|
1848
|
+
if (code === "approval_denied") return 403;
|
|
1849
|
+
if (code === "connection_revoked" || code === "connection_expired" || code === "provider_auth_failed") return 401;
|
|
1850
|
+
if (code === "scope_missing" || code === "action_denied" || code === "passthrough_disabled") return 403;
|
|
1851
|
+
if (code === "action_not_found" || code === "manifest_invalid" || code === "input_invalid") return 400;
|
|
1852
|
+
if (code === "provider_rate_limited") return 429;
|
|
1853
|
+
if (code === "provider_unavailable") return 503;
|
|
1854
|
+
if (code === "capability_expired" || code === "capability_invalid") return 401;
|
|
1855
|
+
return 500;
|
|
1856
|
+
}
|
|
1857
|
+
function inferCode(message) {
|
|
1858
|
+
if (/approval/i.test(message)) return "approval_required";
|
|
1859
|
+
if (/scope/i.test(message)) return "scope_missing";
|
|
1860
|
+
if (/expired/i.test(message)) return "connection_expired";
|
|
1861
|
+
if (/revoked/i.test(message)) return "connection_revoked";
|
|
1862
|
+
if (/rate.?limit|429/i.test(message)) return "provider_rate_limited";
|
|
1863
|
+
if (/unauth|forbidden|401|403/i.test(message)) return "provider_auth_failed";
|
|
1864
|
+
return "unknown";
|
|
1865
|
+
}
|
|
1866
|
+
function redactUnknown2(value) {
|
|
1867
|
+
if (Array.isArray(value)) return value.map(redactUnknown2);
|
|
1868
|
+
if (!value || typeof value !== "object") return value;
|
|
1869
|
+
const out = {};
|
|
1870
|
+
for (const [key, child] of Object.entries(value)) {
|
|
1871
|
+
if (/token|secret|password|authorization|api[_-]?key|credential|refresh/i.test(key)) {
|
|
1872
|
+
out[key] = "[REDACTED]";
|
|
1873
|
+
} else {
|
|
1874
|
+
out[key] = redactUnknown2(child);
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
return out;
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1880
|
+
// src/client.ts
|
|
1881
|
+
var TangleIntegrationsClient = class {
|
|
1882
|
+
endpoint;
|
|
1883
|
+
bridge;
|
|
1884
|
+
fetchImpl;
|
|
1885
|
+
getCapabilityToken;
|
|
1886
|
+
constructor(options) {
|
|
1887
|
+
this.endpoint = options.endpoint.replace(/\/$/, "");
|
|
1888
|
+
this.bridge = options.bridge ?? parseIntegrationBridgeEnvironment(
|
|
1889
|
+
options.env ?? readProcessEnv(),
|
|
1890
|
+
{ envVar: options.envVar ?? DEFAULT_INTEGRATION_BRIDGE_ENV }
|
|
1891
|
+
);
|
|
1892
|
+
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
1893
|
+
this.getCapabilityToken = options.getCapabilityToken ?? ((tool) => tool.capabilityToken);
|
|
1894
|
+
}
|
|
1895
|
+
tools() {
|
|
1896
|
+
return [...this.bridge.tools];
|
|
1897
|
+
}
|
|
1898
|
+
findTool(toolOrAction) {
|
|
1899
|
+
const found = this.bridge.tools.find(
|
|
1900
|
+
(tool) => tool.name === toolOrAction || tool.action === toolOrAction || `${tool.connectorId}.${tool.action}` === toolOrAction
|
|
1901
|
+
);
|
|
1902
|
+
if (!found) {
|
|
1903
|
+
throw new IntegrationRuntimeError({
|
|
1904
|
+
code: "action_not_found",
|
|
1905
|
+
message: `Integration tool ${toolOrAction} is not available in this runtime.`,
|
|
1906
|
+
metadata: { available: this.bridge.tools.map((tool) => ({ name: tool.name, action: tool.action, connectorId: tool.connectorId })) }
|
|
1907
|
+
});
|
|
1908
|
+
}
|
|
1909
|
+
return found;
|
|
1910
|
+
}
|
|
1911
|
+
async invoke(input) {
|
|
1912
|
+
try {
|
|
1913
|
+
const tool = this.findTool(input.tool);
|
|
1914
|
+
const token = await this.getCapabilityToken(tool);
|
|
1915
|
+
const response = await this.fetchImpl(`${this.endpoint}/v1/integrations/invoke`, {
|
|
1916
|
+
method: "POST",
|
|
1917
|
+
headers: {
|
|
1918
|
+
"content-type": "application/json",
|
|
1919
|
+
authorization: `Bearer ${token}`
|
|
1920
|
+
},
|
|
1921
|
+
body: JSON.stringify({
|
|
1922
|
+
action: tool.action,
|
|
1923
|
+
input: input.input,
|
|
1924
|
+
idempotencyKey: input.idempotencyKey ?? defaultIdempotencyKey(tool.action),
|
|
1925
|
+
dryRun: input.dryRun,
|
|
1926
|
+
metadata: input.metadata
|
|
1927
|
+
})
|
|
1928
|
+
});
|
|
1929
|
+
const json = await response.json().catch(() => void 0);
|
|
1930
|
+
if (!response.ok && !json) {
|
|
1931
|
+
return { status: "failed", action: tool.action, error: `Integration invoke failed with HTTP ${response.status}` };
|
|
1932
|
+
}
|
|
1933
|
+
return json ?? { status: "failed", action: tool.action, error: "Integration invoke returned an empty response." };
|
|
1934
|
+
} catch (error) {
|
|
1935
|
+
const normalized = normalizeIntegrationError(error);
|
|
1936
|
+
return { status: "failed", action: input.tool, error: normalized.message, metadata: { code: normalized.code, userAction: normalized.userAction } };
|
|
1937
|
+
}
|
|
1938
|
+
}
|
|
1939
|
+
};
|
|
1940
|
+
function createTangleIntegrationsClient(options) {
|
|
1941
|
+
return new TangleIntegrationsClient(options);
|
|
1942
|
+
}
|
|
1943
|
+
function defaultIdempotencyKey(action) {
|
|
1944
|
+
return `${action}:${Date.now()}:${Math.random().toString(36).slice(2)}`;
|
|
1945
|
+
}
|
|
1946
|
+
function readProcessEnv() {
|
|
1947
|
+
if (typeof process !== "undefined" && process.env) return process.env;
|
|
1948
|
+
return {};
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1951
|
+
// src/consent.ts
|
|
1952
|
+
function renderConsentSummary(manifestOrResolution, options = {}) {
|
|
1953
|
+
const manifest = "manifest" in manifestOrResolution ? manifestOrResolution.manifest : manifestOrResolution;
|
|
1954
|
+
const appName = options.appName ?? manifest.title ?? manifest.id;
|
|
1955
|
+
const requirements = manifest.requirements;
|
|
1956
|
+
const risk = aggregateRisk(requirements, options.connectors);
|
|
1957
|
+
const connectorIds = unique2(requirements.map((requirement) => requirement.connectorId));
|
|
1958
|
+
const first = requirements[0];
|
|
1959
|
+
const body = first ? sentenceForRequirement(appName, first) : `${appName} does not request integrations.`;
|
|
1960
|
+
return {
|
|
1961
|
+
title: `${appName} wants to use ${humanList(connectorIds.map(titleize))}`,
|
|
1962
|
+
body,
|
|
1963
|
+
bullets: requirements.map((requirement) => bulletForRequirement(requirement, options.connectors)),
|
|
1964
|
+
primaryAction: risk === "read" ? "Allow access" : risk === "write" ? "Review and allow" : "Review destructive access",
|
|
1965
|
+
risk,
|
|
1966
|
+
connectorIds
|
|
1967
|
+
};
|
|
1968
|
+
}
|
|
1969
|
+
function renderApprovalCopy(input) {
|
|
1970
|
+
return {
|
|
1971
|
+
title: `${input.appName} wants to ${input.action.title.toLowerCase()}`,
|
|
1972
|
+
body: `${input.appName} is requesting permission to run "${input.action.title}" on ${input.connectorTitle}.`,
|
|
1973
|
+
bullets: [
|
|
1974
|
+
`Risk: ${input.action.risk}`,
|
|
1975
|
+
`Data: ${input.action.dataClass}`,
|
|
1976
|
+
...input.approvalId ? [`Approval id: ${input.approvalId}`] : []
|
|
1977
|
+
],
|
|
1978
|
+
primaryAction: input.action.risk === "read" ? "Allow" : "Approve action",
|
|
1979
|
+
risk: input.action.risk,
|
|
1980
|
+
connectorIds: []
|
|
1981
|
+
};
|
|
1982
|
+
}
|
|
1983
|
+
function sentenceForRequirement(appName, requirement) {
|
|
1984
|
+
if (requirement.connectorId === "google-calendar" && requirement.mode === "read") {
|
|
1985
|
+
return `${appName} wants to read your Google Calendar to find schedule-aware recommendations.`;
|
|
1986
|
+
}
|
|
1987
|
+
if (requirement.connectorId === "google-calendar" && requirement.mode === "write") {
|
|
1988
|
+
return `${appName} wants to create or update Google Calendar events after your approval.`;
|
|
1989
|
+
}
|
|
1990
|
+
if (requirement.mode === "read") return `${appName} wants to read ${titleize(requirement.connectorId)} data.`;
|
|
1991
|
+
if (requirement.mode === "write") return `${appName} wants to write ${titleize(requirement.connectorId)} data after approval.`;
|
|
1992
|
+
return `${appName} wants to subscribe to ${titleize(requirement.connectorId)} events.`;
|
|
1993
|
+
}
|
|
1994
|
+
function bulletForRequirement(requirement, connectors = []) {
|
|
1995
|
+
const connector = connectors.find((candidate) => candidate.id === requirement.connectorId);
|
|
1996
|
+
const actions = requirement.requiredActions?.length ? requirement.requiredActions.map((id) => connector?.actions.find((action) => action.id === id)?.title ?? id) : requirement.requiredTriggers ?? [];
|
|
1997
|
+
return `${titleize(requirement.connectorId)}: ${requirement.reason}${actions.length ? ` (${actions.join(", ")})` : ""}`;
|
|
1998
|
+
}
|
|
1999
|
+
function aggregateRisk(requirements, connectors = []) {
|
|
2000
|
+
let rank = 0;
|
|
2001
|
+
for (const requirement of requirements) {
|
|
2002
|
+
if (requirement.mode === "write") rank = Math.max(rank, 1);
|
|
2003
|
+
const connector = connectors.find((candidate) => candidate.id === requirement.connectorId);
|
|
2004
|
+
for (const actionId of requirement.requiredActions ?? []) {
|
|
2005
|
+
const risk = connector?.actions.find((action) => action.id === actionId)?.risk;
|
|
2006
|
+
if (risk === "write") rank = Math.max(rank, 1);
|
|
2007
|
+
if (risk === "destructive") rank = Math.max(rank, 2);
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
return rank === 2 ? "destructive" : rank === 1 ? "write" : "read";
|
|
2011
|
+
}
|
|
2012
|
+
function humanList(values) {
|
|
2013
|
+
if (values.length <= 1) return values[0] ?? "integrations";
|
|
2014
|
+
if (values.length === 2) return `${values[0]} and ${values[1]}`;
|
|
2015
|
+
return `${values.slice(0, -1).join(", ")}, and ${values.at(-1)}`;
|
|
2016
|
+
}
|
|
2017
|
+
function titleize(value) {
|
|
2018
|
+
return value.split(/[-_.]/g).filter(Boolean).map((part) => part[0].toUpperCase() + part.slice(1)).join(" ");
|
|
2019
|
+
}
|
|
2020
|
+
function unique2(values) {
|
|
2021
|
+
return [...new Set(values)];
|
|
2022
|
+
}
|
|
2023
|
+
|
|
2024
|
+
// src/adapter-provider.ts
|
|
2025
|
+
function createConnectorAdapterProvider(options) {
|
|
2026
|
+
const providerId = options.id ?? "first-party";
|
|
2027
|
+
const now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
2028
|
+
const adapters = /* @__PURE__ */ new Map();
|
|
2029
|
+
for (const adapter of options.adapters) {
|
|
2030
|
+
adapters.set(adapter.manifest.kind, adapter);
|
|
2031
|
+
}
|
|
2032
|
+
return {
|
|
2033
|
+
id: providerId,
|
|
2034
|
+
kind: options.kind ?? "first_party",
|
|
2035
|
+
listConnectors: () => [...adapters.values()].map((adapter) => manifestToConnector(providerId, adapter)),
|
|
2036
|
+
async invokeAction(connection, request) {
|
|
2037
|
+
const adapter = adapters.get(connection.connectorId);
|
|
2038
|
+
if (!adapter) {
|
|
2039
|
+
throw new IntegrationError(`Connector adapter ${connection.connectorId} not found.`, "connector_not_found");
|
|
2040
|
+
}
|
|
2041
|
+
const capability = adapter.manifest.capabilities.find((candidate) => candidate.name === request.action);
|
|
2042
|
+
if (!capability) {
|
|
2043
|
+
throw new IntegrationError(`Capability ${request.action} is not defined by ${connection.connectorId}.`, "action_not_found");
|
|
2044
|
+
}
|
|
2045
|
+
const source = await options.resolveDataSource(connection);
|
|
2046
|
+
const invocation = {
|
|
2047
|
+
source,
|
|
2048
|
+
capabilityName: request.action,
|
|
2049
|
+
args: toRecord(request.input),
|
|
2050
|
+
idempotencyKey: request.idempotencyKey ?? `idem_${connection.id}_${request.action}_${now().getTime()}`,
|
|
2051
|
+
expectedEtag: typeof request.metadata?.expectedEtag === "string" ? request.metadata.expectedEtag : void 0,
|
|
2052
|
+
callSessionId: typeof request.metadata?.callSessionId === "string" ? request.metadata.callSessionId : void 0
|
|
2053
|
+
};
|
|
2054
|
+
if (capability.class === "read") {
|
|
2055
|
+
if (!adapter.executeRead) {
|
|
2056
|
+
throw new IntegrationError(`Connector ${connection.connectorId} does not implement reads.`, "action_not_found");
|
|
2057
|
+
}
|
|
2058
|
+
const result = await adapter.executeRead(invocation);
|
|
2059
|
+
return readResultToAction(request, result);
|
|
2060
|
+
}
|
|
2061
|
+
if (capability.class === "mutation") {
|
|
2062
|
+
if (!adapter.executeMutation) {
|
|
2063
|
+
throw new IntegrationError(`Connector ${connection.connectorId} does not implement mutations.`, "action_not_found");
|
|
2064
|
+
}
|
|
2065
|
+
const result = await adapter.executeMutation(invocation);
|
|
2066
|
+
return mutationResultToAction(request, result);
|
|
2067
|
+
}
|
|
2068
|
+
throw new IntegrationError(`Capability ${request.action} is not invokable as an action.`, "action_not_found");
|
|
2069
|
+
}
|
|
2070
|
+
};
|
|
2071
|
+
}
|
|
2072
|
+
function manifestToConnector(providerId, adapter) {
|
|
2073
|
+
const manifest = adapter.manifest;
|
|
2074
|
+
return {
|
|
2075
|
+
id: manifest.kind,
|
|
2076
|
+
providerId,
|
|
2077
|
+
title: manifest.displayName,
|
|
2078
|
+
category: mapCategory(manifest.category),
|
|
2079
|
+
auth: mapAuth(manifest.auth.kind),
|
|
2080
|
+
scopes: manifest.auth.kind === "oauth2" ? manifest.auth.scopes : [],
|
|
2081
|
+
actions: manifest.capabilities.filter((capability) => capability.class === "read" || capability.class === "mutation").map((capability) => ({
|
|
2082
|
+
id: capability.name,
|
|
2083
|
+
title: titleFromName(capability.name),
|
|
2084
|
+
risk: capability.class === "read" ? "read" : capability.externalEffect ? "destructive" : "write",
|
|
2085
|
+
requiredScopes: capability.requiredScopes ?? [],
|
|
2086
|
+
dataClass: inferDataClass(manifest.category),
|
|
2087
|
+
description: capability.description,
|
|
2088
|
+
approvalRequired: capability.class === "mutation",
|
|
2089
|
+
inputSchema: capability.parameters
|
|
2090
|
+
})),
|
|
2091
|
+
metadata: {
|
|
2092
|
+
source: "first-party-adapter",
|
|
2093
|
+
supportTier: "firstPartyExecutable",
|
|
2094
|
+
executable: true
|
|
2095
|
+
}
|
|
2096
|
+
};
|
|
2097
|
+
}
|
|
2098
|
+
function readResultToAction(request, result) {
|
|
2099
|
+
return {
|
|
2100
|
+
ok: true,
|
|
2101
|
+
action: request.action,
|
|
2102
|
+
output: result.data,
|
|
2103
|
+
metadata: {
|
|
2104
|
+
etag: result.etag,
|
|
2105
|
+
fetchedAt: result.fetchedAt
|
|
2106
|
+
}
|
|
2107
|
+
};
|
|
2108
|
+
}
|
|
2109
|
+
function mutationResultToAction(request, result) {
|
|
2110
|
+
if (result.status === "committed") {
|
|
2111
|
+
return {
|
|
2112
|
+
ok: true,
|
|
2113
|
+
action: request.action,
|
|
2114
|
+
output: result.data,
|
|
2115
|
+
metadata: {
|
|
2116
|
+
etagAfter: result.etagAfter,
|
|
2117
|
+
committedAt: result.committedAt,
|
|
2118
|
+
idempotentReplay: result.idempotentReplay
|
|
2119
|
+
}
|
|
2120
|
+
};
|
|
2121
|
+
}
|
|
2122
|
+
if (result.status === "conflict") {
|
|
2123
|
+
return {
|
|
2124
|
+
ok: false,
|
|
2125
|
+
action: request.action,
|
|
2126
|
+
output: {
|
|
2127
|
+
conflict: true,
|
|
2128
|
+
message: result.message,
|
|
2129
|
+
alternatives: result.alternatives,
|
|
2130
|
+
currentState: result.currentState
|
|
2131
|
+
}
|
|
2132
|
+
};
|
|
2133
|
+
}
|
|
2134
|
+
return {
|
|
2135
|
+
ok: false,
|
|
2136
|
+
action: request.action,
|
|
2137
|
+
output: {
|
|
2138
|
+
rateLimited: true,
|
|
2139
|
+
retryAfterMs: result.retryAfterMs,
|
|
2140
|
+
message: result.message
|
|
2141
|
+
}
|
|
2142
|
+
};
|
|
2143
|
+
}
|
|
2144
|
+
function mapAuth(kind) {
|
|
2145
|
+
if (kind === "oauth2") return "oauth2";
|
|
2146
|
+
if (kind === "api-key") return "api_key";
|
|
2147
|
+
if (kind === "none") return "none";
|
|
2148
|
+
return "custom";
|
|
2149
|
+
}
|
|
2150
|
+
function mapCategory(category) {
|
|
2151
|
+
if (category === "comms") return "chat";
|
|
2152
|
+
if (category === "spreadsheet") return "database";
|
|
2153
|
+
if (category === "doc") return "docs";
|
|
2154
|
+
if (category === "commerce") return "workflow";
|
|
2155
|
+
return category === "other" ? "other" : category;
|
|
2156
|
+
}
|
|
2157
|
+
function inferDataClass(category) {
|
|
2158
|
+
if (category === "commerce") return "sensitive";
|
|
2159
|
+
if (category === "webhook") return "internal";
|
|
2160
|
+
return "private";
|
|
2161
|
+
}
|
|
2162
|
+
function titleFromName(name) {
|
|
2163
|
+
return name.split(/[._-]/g).filter(Boolean).map((part) => part.slice(0, 1).toUpperCase() + part.slice(1)).join(" ");
|
|
2164
|
+
}
|
|
2165
|
+
function toRecord(input) {
|
|
2166
|
+
if (input && typeof input === "object" && !Array.isArray(input)) return input;
|
|
2167
|
+
return {};
|
|
2168
|
+
}
|
|
2169
|
+
|
|
2170
|
+
// src/credentials.ts
|
|
2171
|
+
var InMemoryIntegrationSecretStore = class {
|
|
2172
|
+
secrets = /* @__PURE__ */ new Map();
|
|
2173
|
+
get(ref) {
|
|
2174
|
+
return this.secrets.get(secretKey(ref));
|
|
2175
|
+
}
|
|
2176
|
+
put(ref, credentials) {
|
|
2177
|
+
this.secrets.set(secretKey(ref), credentials);
|
|
2178
|
+
}
|
|
2179
|
+
delete(ref) {
|
|
2180
|
+
this.secrets.delete(secretKey(ref));
|
|
2181
|
+
}
|
|
2182
|
+
};
|
|
2183
|
+
function createConnectionCredentialResolver(options) {
|
|
2184
|
+
const now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
2185
|
+
return async function resolveDataSource(connection) {
|
|
2186
|
+
const credentials = await resolveConnectionCredentials(connection, {
|
|
2187
|
+
secrets: options.secrets,
|
|
2188
|
+
connections: options.connections,
|
|
2189
|
+
adapters: options.adapters,
|
|
2190
|
+
now,
|
|
2191
|
+
markConnectionError: options.markConnectionError
|
|
2192
|
+
});
|
|
2193
|
+
return {
|
|
2194
|
+
id: connection.id,
|
|
2195
|
+
projectId: String(connection.metadata?.projectId ?? connection.owner.id),
|
|
2196
|
+
publishedAgentId: typeof connection.metadata?.publishedAgentId === "string" ? connection.metadata.publishedAgentId : null,
|
|
2197
|
+
kind: connection.connectorId,
|
|
2198
|
+
label: connection.account?.displayName ?? connection.account?.email ?? connection.connectorId,
|
|
2199
|
+
consistencyModel: typeof connection.metadata?.consistencyModel === "string" ? connection.metadata.consistencyModel : "authoritative",
|
|
2200
|
+
scopes: connection.grantedScopes,
|
|
2201
|
+
metadata: connection.metadata ?? {},
|
|
2202
|
+
credentials,
|
|
2203
|
+
status: connection.status === "active" ? "active" : connection.status === "revoked" ? "revoked" : "error"
|
|
2204
|
+
};
|
|
2205
|
+
};
|
|
2206
|
+
}
|
|
2207
|
+
async function resolveConnectionCredentials(input, options) {
|
|
2208
|
+
if (input.status !== "active") throw new Error(`Connection ${input.id} is ${input.status}.`);
|
|
2209
|
+
if (!input.secretRef) return { kind: "none" };
|
|
2210
|
+
const current = await options.secrets.get(input.secretRef);
|
|
2211
|
+
if (!current) throw new Error(`Secret ${input.secretRef.provider}/${input.secretRef.id} not found.`);
|
|
2212
|
+
if (!isExpiredOauth(current, options.now ?? (() => /* @__PURE__ */ new Date()))) return current;
|
|
2213
|
+
const adapter = options.adapters?.find((candidate) => candidate.manifest.kind === input.connectorId);
|
|
2214
|
+
if (!adapter?.refreshToken) return current;
|
|
2215
|
+
try {
|
|
2216
|
+
const refreshed = await adapter.refreshToken(current);
|
|
2217
|
+
await options.secrets.put(input.secretRef, refreshed);
|
|
2218
|
+
if (options.connections) {
|
|
2219
|
+
await options.connections.put({
|
|
2220
|
+
...input,
|
|
2221
|
+
status: "active",
|
|
2222
|
+
updatedAt: (options.now?.() ?? /* @__PURE__ */ new Date()).toISOString(),
|
|
2223
|
+
expiresAt: refreshed.kind === "oauth2" && refreshed.expiresAt ? new Date(refreshed.expiresAt).toISOString() : input.expiresAt
|
|
2224
|
+
});
|
|
2225
|
+
}
|
|
2226
|
+
return refreshed;
|
|
2227
|
+
} catch (error) {
|
|
2228
|
+
const err = error instanceof Error ? error : new Error("Credential refresh failed.");
|
|
2229
|
+
await options.markConnectionError?.(input, err);
|
|
2230
|
+
if (options.connections) {
|
|
2231
|
+
await options.connections.put({
|
|
2232
|
+
...input,
|
|
2233
|
+
status: "expired",
|
|
2234
|
+
updatedAt: (options.now?.() ?? /* @__PURE__ */ new Date()).toISOString()
|
|
2235
|
+
});
|
|
2236
|
+
}
|
|
2237
|
+
throw err;
|
|
2238
|
+
}
|
|
2239
|
+
}
|
|
2240
|
+
function createCredentialBackedAdapterProvider(options) {
|
|
2241
|
+
return createConnectorAdapterProvider({
|
|
2242
|
+
...options,
|
|
2243
|
+
resolveDataSource: createConnectionCredentialResolver(options)
|
|
2244
|
+
});
|
|
2245
|
+
}
|
|
2246
|
+
async function revokeConnection(input) {
|
|
2247
|
+
if (input.connection.secretRef) await input.secrets?.delete?.(input.connection.secretRef);
|
|
2248
|
+
const revoked = {
|
|
2249
|
+
...input.connection,
|
|
2250
|
+
status: "revoked",
|
|
2251
|
+
updatedAt: (input.now?.() ?? /* @__PURE__ */ new Date()).toISOString()
|
|
2252
|
+
};
|
|
2253
|
+
await input.connections?.put(revoked);
|
|
2254
|
+
return revoked;
|
|
2255
|
+
}
|
|
2256
|
+
function isExpiredOauth(credentials, now) {
|
|
2257
|
+
return credentials.kind === "oauth2" && typeof credentials.expiresAt === "number" && credentials.expiresAt <= now().getTime() && Boolean(credentials.refreshToken);
|
|
2258
|
+
}
|
|
2259
|
+
function secretKey(ref) {
|
|
2260
|
+
return `${ref.provider}:${ref.id}`;
|
|
2261
|
+
}
|
|
2262
|
+
|
|
2263
|
+
// src/events.ts
|
|
2264
|
+
var InMemoryIntegrationEventStore = class {
|
|
2265
|
+
events = /* @__PURE__ */ new Map();
|
|
2266
|
+
providerIds = /* @__PURE__ */ new Set();
|
|
2267
|
+
put(event) {
|
|
2268
|
+
this.events.set(event.id, event);
|
|
2269
|
+
if (event.providerEventId) this.providerIds.add(providerKey(event.sourceId, event.providerEventId));
|
|
2270
|
+
}
|
|
2271
|
+
hasProviderEvent(sourceId, providerEventId) {
|
|
2272
|
+
return this.providerIds.has(providerKey(sourceId, providerEventId));
|
|
2273
|
+
}
|
|
2274
|
+
list() {
|
|
2275
|
+
return [...this.events.values()];
|
|
2276
|
+
}
|
|
2277
|
+
};
|
|
2278
|
+
async function receiveIntegrationWebhook(input) {
|
|
2279
|
+
if (!input.adapter.handleInboundEvent) {
|
|
2280
|
+
return { status: 405, body: { ok: false, error: "Connector does not support inbound webhooks." }, received: [], duplicates: [] };
|
|
2281
|
+
}
|
|
2282
|
+
const signature = input.adapter.verifySignature?.({
|
|
2283
|
+
rawBody: input.rawBody,
|
|
2284
|
+
headers: input.headers,
|
|
2285
|
+
source: input.source
|
|
2286
|
+
});
|
|
2287
|
+
if (signature && !signature.valid) {
|
|
2288
|
+
return { status: 401, body: { ok: false, error: signature.reason ?? "Invalid webhook signature." }, received: [], duplicates: [] };
|
|
2289
|
+
}
|
|
2290
|
+
const handled = await input.adapter.handleInboundEvent({
|
|
2291
|
+
source: input.source,
|
|
2292
|
+
rawBody: input.rawBody,
|
|
2293
|
+
headers: input.headers
|
|
2294
|
+
});
|
|
2295
|
+
const received = [];
|
|
2296
|
+
const duplicates = [];
|
|
2297
|
+
for (const inbound of handled.events) {
|
|
2298
|
+
const event = storedEvent(input.source, inbound, input.now ?? (() => /* @__PURE__ */ new Date()));
|
|
2299
|
+
if (event.providerEventId && await input.store.hasProviderEvent(event.sourceId, event.providerEventId)) {
|
|
2300
|
+
duplicates.push(event);
|
|
2301
|
+
continue;
|
|
2302
|
+
}
|
|
2303
|
+
await input.store.put(event);
|
|
2304
|
+
received.push(event);
|
|
2305
|
+
await dispatchStoredEvent(event, input.source, input.workflowRuntime);
|
|
2306
|
+
}
|
|
2307
|
+
return {
|
|
2308
|
+
status: handled.response?.status ?? 200,
|
|
2309
|
+
body: handled.response?.body ?? { received: true, count: received.length, duplicateCount: duplicates.length },
|
|
2310
|
+
headers: handled.response?.headers,
|
|
2311
|
+
received,
|
|
2312
|
+
duplicates
|
|
2313
|
+
};
|
|
2314
|
+
}
|
|
2315
|
+
function storedEventToTriggerEvent(event, source) {
|
|
2316
|
+
return {
|
|
2317
|
+
id: event.id,
|
|
2318
|
+
providerId: String(source.metadata.providerId ?? "first-party"),
|
|
2319
|
+
connectorId: event.connectorId,
|
|
2320
|
+
connectionId: source.id,
|
|
2321
|
+
trigger: event.eventType,
|
|
2322
|
+
occurredAt: event.receivedAt,
|
|
2323
|
+
payload: event.payload,
|
|
2324
|
+
metadata: {
|
|
2325
|
+
providerEventId: event.providerEventId,
|
|
2326
|
+
sourceId: event.sourceId,
|
|
2327
|
+
...event.metadata
|
|
2328
|
+
}
|
|
2329
|
+
};
|
|
2330
|
+
}
|
|
2331
|
+
async function dispatchStoredEvent(event, source, workflowRuntime) {
|
|
2332
|
+
if (!workflowRuntime) return;
|
|
2333
|
+
await workflowRuntime.dispatchEvent(storedEventToTriggerEvent(event, source), () => void 0);
|
|
2334
|
+
}
|
|
2335
|
+
function storedEvent(source, event, now) {
|
|
2336
|
+
return {
|
|
2337
|
+
id: `evt_${source.id}_${event.providerEventId ?? `${event.eventType}_${now().getTime()}`}`,
|
|
2338
|
+
sourceId: source.id,
|
|
2339
|
+
connectorId: source.kind,
|
|
2340
|
+
eventType: event.eventType,
|
|
2341
|
+
providerEventId: event.providerEventId,
|
|
2342
|
+
receivedAt: now().toISOString(),
|
|
2343
|
+
payload: event.payload
|
|
2344
|
+
};
|
|
2345
|
+
}
|
|
2346
|
+
function providerKey(sourceId, providerEventId) {
|
|
2347
|
+
return `${sourceId}:${providerEventId}`;
|
|
2348
|
+
}
|
|
2349
|
+
|
|
2350
|
+
// src/guard.ts
|
|
2351
|
+
import { createHash as createHash2 } from "crypto";
|
|
2352
|
+
var InMemoryIntegrationIdempotencyStore = class {
|
|
2353
|
+
records = /* @__PURE__ */ new Map();
|
|
2354
|
+
get(key) {
|
|
2355
|
+
return this.records.get(key);
|
|
2356
|
+
}
|
|
2357
|
+
put(record) {
|
|
2358
|
+
this.records.set(record.key, record);
|
|
2359
|
+
}
|
|
2360
|
+
};
|
|
2361
|
+
var DefaultIntegrationActionGuard = class {
|
|
2362
|
+
idempotency;
|
|
2363
|
+
audit;
|
|
2364
|
+
rateLimiter;
|
|
2365
|
+
now;
|
|
2366
|
+
constructor(options = {}) {
|
|
2367
|
+
this.idempotency = options.idempotency;
|
|
2368
|
+
this.audit = options.audit;
|
|
2369
|
+
this.rateLimiter = options.rateLimiter;
|
|
2370
|
+
this.now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
2371
|
+
}
|
|
2372
|
+
async invokeAction(ctx, proceed) {
|
|
2373
|
+
const idempotencyKey = ctx.request.idempotencyKey;
|
|
2374
|
+
const requestHash = hashRequest(ctx);
|
|
2375
|
+
if (idempotencyKey && this.idempotency) {
|
|
2376
|
+
const existing = await this.idempotency.get(idempotencyKey);
|
|
2377
|
+
if (existing) {
|
|
2378
|
+
if (existing.requestHash !== requestHash) {
|
|
2379
|
+
return {
|
|
2380
|
+
ok: false,
|
|
2381
|
+
action: ctx.request.action,
|
|
2382
|
+
output: { idempotencyConflict: true, message: "Idempotency key was reused with different integration input." }
|
|
2383
|
+
};
|
|
2384
|
+
}
|
|
2385
|
+
return {
|
|
2386
|
+
...existing.result,
|
|
2387
|
+
metadata: { ...existing.result.metadata ?? {}, idempotentReplay: true }
|
|
2388
|
+
};
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2391
|
+
if (ctx.request.dryRun && ctx.action?.risk !== "read") {
|
|
2392
|
+
const result = {
|
|
2393
|
+
ok: true,
|
|
2394
|
+
action: ctx.request.action,
|
|
2395
|
+
output: { dryRun: true },
|
|
2396
|
+
metadata: { dryRun: true }
|
|
2397
|
+
};
|
|
2398
|
+
await this.writeIdempotency(idempotencyKey, requestHash, result);
|
|
2399
|
+
return result;
|
|
2400
|
+
}
|
|
2401
|
+
const rateLimit = await this.rateLimiter?.check(ctx);
|
|
2402
|
+
if (rateLimit && !rateLimit.allowed) {
|
|
2403
|
+
return {
|
|
2404
|
+
ok: false,
|
|
2405
|
+
action: ctx.request.action,
|
|
2406
|
+
output: { rateLimited: true, retryAfterMs: rateLimit.retryAfterMs, message: rateLimit.reason ?? "Integration rate limit exceeded." }
|
|
2407
|
+
};
|
|
2408
|
+
}
|
|
2409
|
+
try {
|
|
2410
|
+
const result = await proceed();
|
|
2411
|
+
await this.writeIdempotency(idempotencyKey, requestHash, result);
|
|
2412
|
+
await this.audit?.record(createIntegrationAuditEvent({
|
|
2413
|
+
type: result.ok ? "action.invoked" : "action.failed",
|
|
2414
|
+
actor: ctx.connection.owner,
|
|
2415
|
+
connectionId: ctx.connection.id,
|
|
2416
|
+
providerId: ctx.connection.providerId,
|
|
2417
|
+
connectorId: ctx.connection.connectorId,
|
|
2418
|
+
action: ctx.request.action,
|
|
2419
|
+
risk: ctx.action?.risk,
|
|
2420
|
+
dataClass: ctx.action?.dataClass,
|
|
2421
|
+
ok: result.ok,
|
|
2422
|
+
metadata: { idempotencyKey, externalId: result.externalId, warnings: result.warnings },
|
|
2423
|
+
now: this.now
|
|
2424
|
+
}));
|
|
2425
|
+
return result;
|
|
2426
|
+
} catch (error) {
|
|
2427
|
+
await this.audit?.record(createIntegrationAuditEvent({
|
|
2428
|
+
type: "action.failed",
|
|
2429
|
+
actor: ctx.connection.owner,
|
|
2430
|
+
connectionId: ctx.connection.id,
|
|
2431
|
+
providerId: ctx.connection.providerId,
|
|
2432
|
+
connectorId: ctx.connection.connectorId,
|
|
2433
|
+
action: ctx.request.action,
|
|
2434
|
+
risk: ctx.action?.risk,
|
|
2435
|
+
dataClass: ctx.action?.dataClass,
|
|
2436
|
+
ok: false,
|
|
2437
|
+
message: error instanceof Error ? error.message : "Integration action failed.",
|
|
2438
|
+
metadata: { idempotencyKey },
|
|
2439
|
+
now: this.now
|
|
2440
|
+
}));
|
|
2441
|
+
throw error;
|
|
2442
|
+
}
|
|
2443
|
+
}
|
|
2444
|
+
async writeIdempotency(key, requestHash, result) {
|
|
2445
|
+
if (!key || !this.idempotency) return;
|
|
2446
|
+
await this.idempotency.put({
|
|
2447
|
+
key,
|
|
2448
|
+
requestHash,
|
|
2449
|
+
result,
|
|
2450
|
+
createdAt: this.now().toISOString()
|
|
2451
|
+
});
|
|
2452
|
+
}
|
|
2453
|
+
};
|
|
2454
|
+
function createDefaultIntegrationActionGuard(options = {}) {
|
|
2455
|
+
return new DefaultIntegrationActionGuard(options);
|
|
2456
|
+
}
|
|
2457
|
+
function hashRequest(ctx) {
|
|
2458
|
+
return createHash2("sha256").update(JSON.stringify({
|
|
2459
|
+
connectionId: ctx.connection.id,
|
|
2460
|
+
action: ctx.request.action,
|
|
2461
|
+
input: ctx.request.input ?? null,
|
|
2462
|
+
dryRun: ctx.request.dryRun ?? false
|
|
2463
|
+
})).digest("base64url");
|
|
2464
|
+
}
|
|
2465
|
+
|
|
2466
|
+
// src/healthcheck.ts
|
|
2467
|
+
var InMemoryIntegrationHealthcheckStore = class {
|
|
2468
|
+
results = /* @__PURE__ */ new Map();
|
|
2469
|
+
put(result) {
|
|
2470
|
+
this.results.set(result.connectionId, result);
|
|
2471
|
+
}
|
|
2472
|
+
get(connectionId) {
|
|
2473
|
+
return this.results.get(connectionId);
|
|
2474
|
+
}
|
|
2475
|
+
list() {
|
|
2476
|
+
return [...this.results.values()];
|
|
2477
|
+
}
|
|
2478
|
+
};
|
|
2479
|
+
async function runIntegrationHealthcheck(input) {
|
|
2480
|
+
const now = input.now ?? (() => /* @__PURE__ */ new Date());
|
|
2481
|
+
const checkedAt = now().toISOString();
|
|
2482
|
+
const connector = input.connector ?? input.registry?.byId.get(input.connection.connectorId)?.connector;
|
|
2483
|
+
const checks = [];
|
|
2484
|
+
checks.push(connectionStatusCheck(input.connection, now));
|
|
2485
|
+
if (!connector) {
|
|
2486
|
+
checks.push({ id: "connector-known", status: "unknown", message: `Connector ${input.connection.connectorId} is not in the registry.` });
|
|
2487
|
+
} else {
|
|
2488
|
+
checks.push(connectorExecutableCheck(connector));
|
|
2489
|
+
checks.push(scopeShapeCheck(input.connection, connector));
|
|
2490
|
+
if (input.test && input.connection.status === "active") {
|
|
2491
|
+
checks.push(await liveHealthcheck(input.connection, connector, input.test));
|
|
2492
|
+
}
|
|
2493
|
+
}
|
|
2494
|
+
const result = {
|
|
2495
|
+
connectionId: input.connection.id,
|
|
2496
|
+
providerId: input.connection.providerId,
|
|
2497
|
+
connectorId: input.connection.connectorId,
|
|
2498
|
+
status: rollupHealthStatus(checks),
|
|
2499
|
+
checkedAt,
|
|
2500
|
+
checks
|
|
2501
|
+
};
|
|
2502
|
+
await input.audit?.record(createIntegrationAuditEvent({
|
|
2503
|
+
type: "healthcheck.completed",
|
|
2504
|
+
actor: input.connection.owner,
|
|
2505
|
+
connectionId: input.connection.id,
|
|
2506
|
+
providerId: input.connection.providerId,
|
|
2507
|
+
connectorId: input.connection.connectorId,
|
|
2508
|
+
ok: result.status === "healthy",
|
|
2509
|
+
message: result.status,
|
|
2510
|
+
metadata: { checks: checks.map((check) => ({ id: check.id, status: check.status, message: check.message })) },
|
|
2511
|
+
occurredAt: checkedAt
|
|
2512
|
+
}));
|
|
2513
|
+
return result;
|
|
2514
|
+
}
|
|
2515
|
+
async function runIntegrationHealthchecks(input) {
|
|
2516
|
+
const results = [];
|
|
2517
|
+
for (const connection of input.connections) {
|
|
2518
|
+
const result = await runIntegrationHealthcheck({
|
|
2519
|
+
connection,
|
|
2520
|
+
registry: input.registry,
|
|
2521
|
+
test: input.test,
|
|
2522
|
+
audit: input.audit,
|
|
2523
|
+
now: input.now
|
|
2524
|
+
});
|
|
2525
|
+
await input.store?.put(result);
|
|
2526
|
+
results.push(result);
|
|
2527
|
+
}
|
|
2528
|
+
return results;
|
|
2529
|
+
}
|
|
2530
|
+
function healthcheckRequest(action = "healthcheck") {
|
|
2531
|
+
return {
|
|
2532
|
+
connectionId: "__healthcheck__",
|
|
2533
|
+
action,
|
|
2534
|
+
input: {},
|
|
2535
|
+
dryRun: true,
|
|
2536
|
+
metadata: { healthcheck: true }
|
|
2537
|
+
};
|
|
2538
|
+
}
|
|
2539
|
+
function connectionStatusCheck(connection, now) {
|
|
2540
|
+
if (connection.status !== "active") {
|
|
2541
|
+
return { id: "connection-active", status: "unhealthy", message: `Connection is ${connection.status}.` };
|
|
2542
|
+
}
|
|
2543
|
+
if (connection.expiresAt && Date.parse(connection.expiresAt) <= now().getTime()) {
|
|
2544
|
+
return { id: "connection-active", status: "unhealthy", message: "Connection credentials are expired." };
|
|
2545
|
+
}
|
|
2546
|
+
return { id: "connection-active", status: "healthy", message: "Connection is active." };
|
|
2547
|
+
}
|
|
2548
|
+
function connectorExecutableCheck(connector) {
|
|
2549
|
+
const executable = connector.actions.length > 0 || (connector.triggers?.length ?? 0) > 0;
|
|
2550
|
+
if (!executable) {
|
|
2551
|
+
return { id: "connector-executable", status: "degraded", message: `${connector.title} is catalog-only.` };
|
|
2552
|
+
}
|
|
2553
|
+
return { id: "connector-executable", status: "healthy", message: `${connector.title} has executable actions or triggers.` };
|
|
2554
|
+
}
|
|
2555
|
+
function scopeShapeCheck(connection, connector) {
|
|
2556
|
+
const declaredScopes = new Set(connector.scopes);
|
|
2557
|
+
const undeclared = connection.grantedScopes.filter((scope) => !declaredScopes.has(scope));
|
|
2558
|
+
if (connector.scopes.length === 0 && connection.grantedScopes.length > 0) {
|
|
2559
|
+
return { id: "scope-shape", status: "unknown", message: "Connector does not declare a scope catalog.", metadata: { grantedScopes: connection.grantedScopes } };
|
|
2560
|
+
}
|
|
2561
|
+
if (undeclared.length > 0) {
|
|
2562
|
+
return { id: "scope-shape", status: "degraded", message: "Connection has scopes not declared by the connector.", metadata: { undeclared } };
|
|
2563
|
+
}
|
|
2564
|
+
return { id: "scope-shape", status: "healthy", message: "Granted scopes match the connector shape." };
|
|
2565
|
+
}
|
|
2566
|
+
async function liveHealthcheck(connection, connector, test) {
|
|
2567
|
+
try {
|
|
2568
|
+
const result = await test(connection, connector);
|
|
2569
|
+
const ok = typeof result === "boolean" ? result : result.ok;
|
|
2570
|
+
return {
|
|
2571
|
+
id: "provider-live-test",
|
|
2572
|
+
status: ok ? "healthy" : "unhealthy",
|
|
2573
|
+
message: ok ? "Provider live test passed." : "Provider live test failed.",
|
|
2574
|
+
metadata: typeof result === "boolean" ? void 0 : { action: result.action, warnings: result.warnings }
|
|
2575
|
+
};
|
|
2576
|
+
} catch (error) {
|
|
2577
|
+
return {
|
|
2578
|
+
id: "provider-live-test",
|
|
2579
|
+
status: "unhealthy",
|
|
2580
|
+
message: error instanceof Error ? error.message : "Provider live test failed."
|
|
2581
|
+
};
|
|
2582
|
+
}
|
|
2583
|
+
}
|
|
2584
|
+
function rollupHealthStatus(checks) {
|
|
2585
|
+
if (checks.some((check) => check.status === "unhealthy")) return "unhealthy";
|
|
2586
|
+
if (checks.some((check) => check.status === "degraded")) return "degraded";
|
|
2587
|
+
if (checks.some((check) => check.status === "unknown")) return "unknown";
|
|
2588
|
+
return "healthy";
|
|
2589
|
+
}
|
|
2590
|
+
|
|
2591
|
+
// src/manifest.ts
|
|
2592
|
+
function validateIntegrationManifest(manifest) {
|
|
2593
|
+
const issues = [];
|
|
2594
|
+
if (!manifest.id?.trim()) issues.push({ path: "id", message: "Manifest id is required." });
|
|
2595
|
+
if (!Array.isArray(manifest.requirements)) issues.push({ path: "requirements", message: "Requirements must be an array." });
|
|
2596
|
+
const ids = /* @__PURE__ */ new Set();
|
|
2597
|
+
for (const [index, requirement] of (manifest.requirements ?? []).entries()) {
|
|
2598
|
+
const path = `requirements[${index}]`;
|
|
2599
|
+
if (!requirement.id?.trim()) issues.push({ path: `${path}.id`, message: "Requirement id is required." });
|
|
2600
|
+
if (ids.has(requirement.id)) issues.push({ path: `${path}.id`, message: `Duplicate requirement id ${requirement.id}.` });
|
|
2601
|
+
ids.add(requirement.id);
|
|
2602
|
+
if (!requirement.connectorId?.trim()) issues.push({ path: `${path}.connectorId`, message: "Connector id is required." });
|
|
2603
|
+
if (!["read", "write", "trigger"].includes(requirement.mode)) issues.push({ path: `${path}.mode`, message: "Mode must be read, write, or trigger." });
|
|
2604
|
+
if (!requirement.reason?.trim()) issues.push({ path: `${path}.reason`, message: "Human-readable reason is required." });
|
|
2605
|
+
if (requirement.mode !== "trigger" && !requirement.requiredActions?.length) {
|
|
2606
|
+
issues.push({ path: `${path}.requiredActions`, message: "Non-trigger requirements should list required actions." });
|
|
2607
|
+
}
|
|
2608
|
+
if (requirement.mode === "trigger" && !requirement.requiredTriggers?.length) {
|
|
2609
|
+
issues.push({ path: `${path}.requiredTriggers`, message: "Trigger requirements should list required triggers." });
|
|
2610
|
+
}
|
|
2611
|
+
}
|
|
2612
|
+
return { ok: issues.length === 0, issues };
|
|
2613
|
+
}
|
|
2614
|
+
function assertValidIntegrationManifest(manifest) {
|
|
2615
|
+
const result = validateIntegrationManifest(manifest);
|
|
2616
|
+
if (!result.ok) {
|
|
2617
|
+
throw new Error(`Invalid integration manifest: ${result.issues.map((issue) => `${issue.path}: ${issue.message}`).join("; ")}`);
|
|
2618
|
+
}
|
|
2619
|
+
}
|
|
2620
|
+
function inferIntegrationManifestFromTools(options) {
|
|
2621
|
+
const byConnector = /* @__PURE__ */ new Map();
|
|
2622
|
+
for (const item of options.tools) {
|
|
2623
|
+
const action = typeof item === "string" ? item : item.action;
|
|
2624
|
+
const connectorId = typeof item === "string" ? canonicalActionConnectorId(action) : item.connectorId ?? canonicalActionConnectorId(action);
|
|
2625
|
+
if (!connectorId) continue;
|
|
2626
|
+
const mode = typeof item === "string" ? inferMode(action) : item.mode ?? inferMode(action);
|
|
2627
|
+
const id = `${connectorId}-${mode}`;
|
|
2628
|
+
const existing = byConnector.get(id);
|
|
2629
|
+
const reason = typeof item === "string" ? defaultReason(connectorId, mode) : item.reason ?? defaultReason(connectorId, mode);
|
|
2630
|
+
if (existing) {
|
|
2631
|
+
byConnector.set(id, {
|
|
2632
|
+
...existing,
|
|
2633
|
+
requiredActions: unique3([...existing.requiredActions ?? [], action]),
|
|
2634
|
+
requiredScopes: unique3([...existing.requiredScopes ?? [], ...typeof item === "string" ? [] : item.scopes ?? []])
|
|
2635
|
+
});
|
|
2636
|
+
} else {
|
|
2637
|
+
byConnector.set(id, {
|
|
2638
|
+
id,
|
|
2639
|
+
connectorId,
|
|
2640
|
+
mode,
|
|
2641
|
+
reason,
|
|
2642
|
+
requiredActions: mode === "trigger" ? void 0 : [action],
|
|
2643
|
+
requiredScopes: typeof item === "string" ? void 0 : item.scopes
|
|
2644
|
+
});
|
|
2645
|
+
}
|
|
2646
|
+
}
|
|
2647
|
+
const manifest = {
|
|
2648
|
+
id: options.manifestId,
|
|
2649
|
+
title: options.title,
|
|
2650
|
+
requirements: [...byConnector.values()],
|
|
2651
|
+
metadata: options.metadata
|
|
2652
|
+
};
|
|
2653
|
+
assertValidIntegrationManifest(manifest);
|
|
2654
|
+
return manifest;
|
|
2655
|
+
}
|
|
2656
|
+
function explainMissingRequirements(resolution) {
|
|
2657
|
+
return [...resolution.missing, ...resolution.optionalMissing].map((item) => ({
|
|
2658
|
+
requirementId: item.requirement.id,
|
|
2659
|
+
connectorId: item.requirement.connectorId,
|
|
2660
|
+
status: item.status,
|
|
2661
|
+
message: item.message,
|
|
2662
|
+
userAction: item.requirement.optional ? "ignore_optional" : item.status === "not_executable" ? "enable" : "connect"
|
|
2663
|
+
}));
|
|
2664
|
+
}
|
|
2665
|
+
function calendarExercisePlannerManifest(id = "exercise-calendar-planner") {
|
|
2666
|
+
return {
|
|
2667
|
+
id,
|
|
2668
|
+
title: "Exercise Calendar Planner",
|
|
2669
|
+
requirements: [{
|
|
2670
|
+
id: "calendar-read",
|
|
2671
|
+
connectorId: "google-calendar",
|
|
2672
|
+
mode: "read",
|
|
2673
|
+
reason: "Read busy and free calendar windows to recommend exercise sessions.",
|
|
2674
|
+
requiredActions: [CANONICAL_INTEGRATION_ACTIONS.googleCalendarEventsList],
|
|
2675
|
+
requiredScopes: ["https://www.googleapis.com/auth/calendar.readonly"]
|
|
2676
|
+
}]
|
|
2677
|
+
};
|
|
2678
|
+
}
|
|
2679
|
+
function inferMode(action) {
|
|
2680
|
+
if (/(create|send|post|update|delete|write|comment|request)$/i.test(action)) return "write";
|
|
2681
|
+
return "read";
|
|
2682
|
+
}
|
|
2683
|
+
function defaultReason(connectorId, mode) {
|
|
2684
|
+
if (connectorId === "google-calendar" && mode === "read") return "Read calendar availability for the generated app.";
|
|
2685
|
+
if (connectorId === "google-calendar" && mode === "write") return "Create or update calendar events after user approval.";
|
|
2686
|
+
return `${mode === "read" ? "Read from" : mode === "write" ? "Write to" : "Subscribe to"} ${connectorId} for this app.`;
|
|
2687
|
+
}
|
|
2688
|
+
function unique3(values) {
|
|
2689
|
+
return [...new Set(values)];
|
|
2690
|
+
}
|
|
2691
|
+
|
|
2692
|
+
// src/passthrough.ts
|
|
2693
|
+
var PROVIDER_PASSTHROUGH_ACTION = CANONICAL_INTEGRATION_ACTIONS.providerHttpRequest;
|
|
2694
|
+
function validateProviderPassthroughRequest(input, policy) {
|
|
2695
|
+
if (!policy.enabled) {
|
|
2696
|
+
throw new IntegrationRuntimeError({
|
|
2697
|
+
code: "passthrough_disabled",
|
|
2698
|
+
message: "Provider-native passthrough is disabled for this connector."
|
|
2699
|
+
});
|
|
2700
|
+
}
|
|
2701
|
+
if (!input.path.startsWith("/")) {
|
|
2702
|
+
throw new IntegrationRuntimeError({ code: "input_invalid", message: "Provider passthrough path must start with /." });
|
|
2703
|
+
}
|
|
2704
|
+
if (policy.allowedMethods?.length && !policy.allowedMethods.includes(input.method)) {
|
|
2705
|
+
throw new IntegrationRuntimeError({ code: "action_denied", message: `Provider passthrough method ${input.method} is not allowed.` });
|
|
2706
|
+
}
|
|
2707
|
+
if (policy.allowedPathPrefixes?.length && !policy.allowedPathPrefixes.some((prefix) => input.path.startsWith(prefix))) {
|
|
2708
|
+
throw new IntegrationRuntimeError({ code: "action_denied", message: `Provider passthrough path ${input.path} is not allowed.` });
|
|
2709
|
+
}
|
|
2710
|
+
const maxBodyBytes = policy.maxBodyBytes ?? 64 * 1024;
|
|
2711
|
+
const bodyBytes = Buffer.byteLength(JSON.stringify(input.body ?? null), "utf8");
|
|
2712
|
+
if (bodyBytes > maxBodyBytes) {
|
|
2713
|
+
throw new IntegrationRuntimeError({ code: "input_invalid", message: `Provider passthrough body exceeds ${maxBodyBytes} bytes.` });
|
|
2714
|
+
}
|
|
2715
|
+
for (const key of Object.keys(input.headers ?? {})) {
|
|
2716
|
+
if (/authorization|cookie|token|secret|api[_-]?key/i.test(key)) {
|
|
2717
|
+
throw new IntegrationRuntimeError({ code: "input_invalid", message: `Provider passthrough header ${key} is not caller-settable.` });
|
|
2718
|
+
}
|
|
2719
|
+
}
|
|
2720
|
+
}
|
|
2721
|
+
|
|
2722
|
+
// src/policy.ts
|
|
2723
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
2724
|
+
var StaticIntegrationPolicyEngine = class {
|
|
2725
|
+
rules;
|
|
2726
|
+
defaultReadEffect;
|
|
2727
|
+
defaultWriteEffect;
|
|
2728
|
+
defaultDestructiveEffect;
|
|
2729
|
+
now;
|
|
2730
|
+
constructor(options = {}) {
|
|
2731
|
+
this.rules = options.rules ?? [];
|
|
2732
|
+
this.defaultReadEffect = options.defaultReadEffect ?? "allow";
|
|
2733
|
+
this.defaultWriteEffect = options.defaultWriteEffect ?? "require_approval";
|
|
2734
|
+
this.defaultDestructiveEffect = options.defaultDestructiveEffect ?? "deny";
|
|
2735
|
+
this.now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
2736
|
+
}
|
|
2737
|
+
decide(ctx) {
|
|
2738
|
+
const action = ctx.action;
|
|
2739
|
+
if (!action) return { decision: "deny", reason: "Integration action is missing from connector catalog." };
|
|
2740
|
+
const matched = this.rules.find((rule) => ruleMatches(rule, ctx));
|
|
2741
|
+
const effect = matched?.effect ?? this.defaultEffect(action.risk);
|
|
2742
|
+
const reason = matched?.reason ?? defaultReason2(effect, action.risk);
|
|
2743
|
+
if (effect === "allow") return { decision: "allow", reason, metadata: matched ? { ruleId: matched.id } : void 0 };
|
|
2744
|
+
if (effect === "deny") return { decision: "deny", reason, metadata: matched ? { ruleId: matched.id } : void 0 };
|
|
2745
|
+
return {
|
|
2746
|
+
decision: "require_approval",
|
|
2747
|
+
reason,
|
|
2748
|
+
approval: buildApprovalRequest(ctx, reason, this.now()),
|
|
2749
|
+
metadata: matched ? { ruleId: matched.id } : void 0
|
|
2750
|
+
};
|
|
2751
|
+
}
|
|
2752
|
+
defaultEffect(risk) {
|
|
2753
|
+
if (risk === "read") return this.defaultReadEffect;
|
|
2754
|
+
if (risk === "write") return this.defaultWriteEffect;
|
|
2755
|
+
return this.defaultDestructiveEffect;
|
|
2756
|
+
}
|
|
2757
|
+
};
|
|
2758
|
+
function createDefaultIntegrationPolicyEngine(options = {}) {
|
|
2759
|
+
return new StaticIntegrationPolicyEngine(options);
|
|
2760
|
+
}
|
|
2761
|
+
function buildApprovalRequest(ctx, reason, requestedAt) {
|
|
2762
|
+
if (!ctx.action) {
|
|
2763
|
+
throw new Error("Cannot build approval request without an action descriptor.");
|
|
2764
|
+
}
|
|
2765
|
+
return {
|
|
2766
|
+
id: `approval_${randomUUID2()}`,
|
|
2767
|
+
connectionId: ctx.connection.id,
|
|
2768
|
+
providerId: ctx.connection.providerId,
|
|
2769
|
+
connectorId: ctx.connection.connectorId,
|
|
2770
|
+
action: ctx.request.action,
|
|
2771
|
+
actor: { type: ctx.subject.type, id: ctx.subject.id },
|
|
2772
|
+
risk: ctx.action.risk,
|
|
2773
|
+
dataClass: ctx.action.dataClass,
|
|
2774
|
+
reason,
|
|
2775
|
+
requestedAt: requestedAt.toISOString(),
|
|
2776
|
+
inputPreview: previewInput(ctx.request.input)
|
|
2777
|
+
};
|
|
2778
|
+
}
|
|
2779
|
+
function redactApprovalRequest(request) {
|
|
2780
|
+
return {
|
|
2781
|
+
...request,
|
|
2782
|
+
inputPreview: redactUnknown3(request.inputPreview)
|
|
2783
|
+
};
|
|
2784
|
+
}
|
|
2785
|
+
function ruleMatches(rule, ctx) {
|
|
2786
|
+
if (!ctx.action) return false;
|
|
2787
|
+
if (rule.providerId && rule.providerId !== ctx.connection.providerId) return false;
|
|
2788
|
+
if (rule.connectorId && rule.connectorId !== ctx.connection.connectorId) return false;
|
|
2789
|
+
if (rule.action && rule.action !== ctx.request.action) return false;
|
|
2790
|
+
if (rule.risk && rule.risk !== ctx.action.risk) return false;
|
|
2791
|
+
if (rule.maxRisk && riskRank(ctx.action.risk) > riskRank(rule.maxRisk)) return false;
|
|
2792
|
+
if (rule.dataClass && rule.dataClass !== ctx.action.dataClass) return false;
|
|
2793
|
+
return true;
|
|
2794
|
+
}
|
|
2795
|
+
function riskRank(risk) {
|
|
2796
|
+
if (risk === "read") return 0;
|
|
2797
|
+
if (risk === "write") return 1;
|
|
2798
|
+
return 2;
|
|
2799
|
+
}
|
|
2800
|
+
function defaultReason2(effect, risk) {
|
|
2801
|
+
if (effect === "allow") return `${risk} integration action allowed by default policy.`;
|
|
2802
|
+
if (effect === "deny") return `${risk} integration action denied by default policy.`;
|
|
2803
|
+
return `${risk} integration action requires approval by default policy.`;
|
|
2804
|
+
}
|
|
2805
|
+
function previewInput(input) {
|
|
2806
|
+
return redactUnknown3(input);
|
|
2807
|
+
}
|
|
2808
|
+
function redactUnknown3(value) {
|
|
2809
|
+
if (Array.isArray(value)) return value.map(redactUnknown3);
|
|
2810
|
+
if (!value || typeof value !== "object") return value;
|
|
2811
|
+
const out = {};
|
|
2812
|
+
for (const [key, child] of Object.entries(value)) {
|
|
2813
|
+
if (/token|secret|password|authorization|api[_-]?key|credential/i.test(key)) {
|
|
2814
|
+
out[key] = "[REDACTED]";
|
|
2815
|
+
} else {
|
|
2816
|
+
out[key] = redactUnknown3(child);
|
|
2817
|
+
}
|
|
2818
|
+
}
|
|
2819
|
+
return out;
|
|
2820
|
+
}
|
|
2821
|
+
|
|
2822
|
+
// src/presets.ts
|
|
2823
|
+
function createPlatformIntegrationPolicyPreset(options = {}) {
|
|
2824
|
+
return new StaticIntegrationPolicyEngine({
|
|
2825
|
+
...options,
|
|
2826
|
+
defaultReadEffect: "allow",
|
|
2827
|
+
defaultWriteEffect: options.allowWritesWithoutApproval ? "allow" : "require_approval",
|
|
2828
|
+
defaultDestructiveEffect: options.allowDestructiveActions ? "require_approval" : "deny",
|
|
2829
|
+
rules: [
|
|
2830
|
+
...options.allowProviderPassthrough ? [] : [{
|
|
2831
|
+
id: "deny-provider-native-passthrough",
|
|
2832
|
+
action: "provider.http.request",
|
|
2833
|
+
effect: "deny",
|
|
2834
|
+
reason: "Provider-native passthrough is disabled by default. Promote the connector action or enable passthrough explicitly."
|
|
2835
|
+
}],
|
|
2836
|
+
...options.rules ?? []
|
|
2837
|
+
]
|
|
2838
|
+
});
|
|
2839
|
+
}
|
|
2840
|
+
|
|
1293
2841
|
// src/connectors/types.ts
|
|
1294
2842
|
var ResourceContention = class extends Error {
|
|
1295
2843
|
constructor(message, alternatives = [], currentState) {
|
|
@@ -1344,7 +2892,7 @@ function assertValidConnectorManifest(manifest) {
|
|
|
1344
2892
|
}
|
|
1345
2893
|
|
|
1346
2894
|
// src/connectors/oauth.ts
|
|
1347
|
-
import { createHash, randomBytes } from "crypto";
|
|
2895
|
+
import { createHash as createHash3, randomBytes } from "crypto";
|
|
1348
2896
|
var PENDING_TTL_MS = 10 * 60 * 1e3;
|
|
1349
2897
|
var InMemoryOAuthFlowStore = class {
|
|
1350
2898
|
pendingFlows = /* @__PURE__ */ new Map();
|
|
@@ -1372,7 +2920,7 @@ function startOAuthFlow(input) {
|
|
|
1372
2920
|
const now = input.now ?? Date.now();
|
|
1373
2921
|
store.sweep?.(now);
|
|
1374
2922
|
const codeVerifier = base64Url(randomBytes(48));
|
|
1375
|
-
const codeChallenge = base64Url(
|
|
2923
|
+
const codeChallenge = base64Url(createHash3("sha256").update(codeVerifier).digest());
|
|
1376
2924
|
const state = base64Url(randomBytes(24));
|
|
1377
2925
|
store.put(state, {
|
|
1378
2926
|
codeVerifier,
|
|
@@ -1837,7 +3385,7 @@ function readMetaString(meta, key) {
|
|
|
1837
3385
|
}
|
|
1838
3386
|
|
|
1839
3387
|
// src/connectors/adapters/google-sheets.ts
|
|
1840
|
-
import { createHash as
|
|
3388
|
+
import { createHash as createHash4 } from "crypto";
|
|
1841
3389
|
var SCOPES2 = ["https://www.googleapis.com/auth/spreadsheets"];
|
|
1842
3390
|
var AUTH_URL2 = "https://accounts.google.com/o/oauth2/v2/auth";
|
|
1843
3391
|
var TOKEN_URL2 = "https://oauth2.googleapis.com/token";
|
|
@@ -2111,7 +3659,7 @@ async function fetchAllRows(accessToken, meta) {
|
|
|
2111
3659
|
function rowFingerprint(values) {
|
|
2112
3660
|
const keys = Object.keys(values).sort();
|
|
2113
3661
|
const blob = keys.map((k) => `${k}=${values[k]}`).join("");
|
|
2114
|
-
return
|
|
3662
|
+
return createHash4("sha256").update(blob).digest("hex").slice(0, 16);
|
|
2115
3663
|
}
|
|
2116
3664
|
function matchesPredicate(row, predicate) {
|
|
2117
3665
|
for (const [k, v] of Object.entries(predicate)) {
|
|
@@ -4150,7 +5698,7 @@ var repoParams = {
|
|
|
4150
5698
|
},
|
|
4151
5699
|
required: ["owner", "repo"]
|
|
4152
5700
|
};
|
|
4153
|
-
var
|
|
5701
|
+
var githubConnector2 = declarativeRestConnector({
|
|
4154
5702
|
kind: "github",
|
|
4155
5703
|
displayName: "GitHub",
|
|
4156
5704
|
description: "Search repositories/issues and create or update GitHub issues through a user-scoped token.",
|
|
@@ -4485,7 +6033,7 @@ var salesforceConnector = declarativeRestConnector({
|
|
|
4485
6033
|
});
|
|
4486
6034
|
|
|
4487
6035
|
// src/catalog.ts
|
|
4488
|
-
var
|
|
6036
|
+
var riskRank2 = {
|
|
4489
6037
|
read: 0,
|
|
4490
6038
|
write: 1,
|
|
4491
6039
|
destructive: 2
|
|
@@ -4508,7 +6056,7 @@ function buildIntegrationToolCatalog(connectors) {
|
|
|
4508
6056
|
const tools = [];
|
|
4509
6057
|
for (const connector of connectors) {
|
|
4510
6058
|
for (const action of connector.actions) {
|
|
4511
|
-
const tags =
|
|
6059
|
+
const tags = unique4([
|
|
4512
6060
|
connector.id,
|
|
4513
6061
|
connector.providerId,
|
|
4514
6062
|
connector.title,
|
|
@@ -4527,173 +6075,73 @@ function buildIntegrationToolCatalog(connectors) {
|
|
|
4527
6075
|
providerId: connector.providerId,
|
|
4528
6076
|
connectorId: connector.id,
|
|
4529
6077
|
connectorTitle: connector.title,
|
|
4530
|
-
category: connector.category,
|
|
4531
|
-
action,
|
|
4532
|
-
risk: action.risk,
|
|
4533
|
-
dataClass: action.dataClass,
|
|
4534
|
-
requiredScopes: action.requiredScopes,
|
|
4535
|
-
inputSchema: action.inputSchema,
|
|
4536
|
-
outputSchema: action.outputSchema,
|
|
4537
|
-
tags
|
|
4538
|
-
});
|
|
4539
|
-
}
|
|
4540
|
-
}
|
|
4541
|
-
return tools;
|
|
4542
|
-
}
|
|
4543
|
-
function searchIntegrationTools(catalog, query, filters = {}) {
|
|
4544
|
-
const terms = tokenize(query);
|
|
4545
|
-
const filtered = catalog.filter((tool) => {
|
|
4546
|
-
if (filters.providerId && tool.providerId !== filters.providerId) return false;
|
|
4547
|
-
if (filters.connectorId && tool.connectorId !== filters.connectorId) return false;
|
|
4548
|
-
if (filters.category && tool.category !== filters.category) return false;
|
|
4549
|
-
if (filters.dataClass && tool.dataClass !== filters.dataClass) return false;
|
|
4550
|
-
if (filters.maxRisk && riskRank[tool.risk] > riskRank[filters.maxRisk]) return false;
|
|
4551
|
-
return true;
|
|
4552
|
-
});
|
|
4553
|
-
const scored = filtered.map((tool) => scoreTool(tool, terms));
|
|
4554
|
-
return scored.filter((result) => terms.length === 0 || result.score > 0).sort((a, b) => b.score - a.score || a.tool.name.localeCompare(b.tool.name)).slice(0, filters.limit ?? 20);
|
|
4555
|
-
}
|
|
4556
|
-
function toMcpTools(tools) {
|
|
4557
|
-
return tools.map((tool) => ({
|
|
4558
|
-
name: tool.name,
|
|
4559
|
-
description: `${tool.title}. ${tool.description}`,
|
|
4560
|
-
inputSchema: tool.inputSchema ?? {
|
|
4561
|
-
type: "object",
|
|
4562
|
-
additionalProperties: true,
|
|
4563
|
-
properties: {}
|
|
4564
|
-
}
|
|
4565
|
-
}));
|
|
4566
|
-
}
|
|
4567
|
-
function scoreTool(tool, terms) {
|
|
4568
|
-
if (terms.length === 0) return { tool, score: 1, matched: [] };
|
|
4569
|
-
const haystack = new Set(tool.tags);
|
|
4570
|
-
const matched = [];
|
|
4571
|
-
let score = 0;
|
|
4572
|
-
for (const term of terms) {
|
|
4573
|
-
if (haystack.has(term)) {
|
|
4574
|
-
matched.push(term);
|
|
4575
|
-
score += 4;
|
|
4576
|
-
continue;
|
|
4577
|
-
}
|
|
4578
|
-
if (tool.tags.some((tag) => tag.includes(term))) {
|
|
4579
|
-
matched.push(term);
|
|
4580
|
-
score += 1;
|
|
4581
|
-
}
|
|
4582
|
-
}
|
|
4583
|
-
if (tool.risk === "read") score += 0.25;
|
|
4584
|
-
return { tool, score, matched: unique2(matched) };
|
|
4585
|
-
}
|
|
4586
|
-
function tokenize(value) {
|
|
4587
|
-
return value.toLowerCase().split(/[^a-z0-9]+/g).map((part) => part.trim()).filter(Boolean);
|
|
4588
|
-
}
|
|
4589
|
-
function encodeToolPart(value) {
|
|
4590
|
-
return Buffer.from(value, "utf8").toString("base64url").replace(/_/g, ".");
|
|
4591
|
-
}
|
|
4592
|
-
function decodeToolPart(value) {
|
|
4593
|
-
return Buffer.from(value.replace(/\./g, "_"), "base64url").toString("utf8");
|
|
4594
|
-
}
|
|
4595
|
-
function unique2(values) {
|
|
4596
|
-
return [...new Set(values)];
|
|
4597
|
-
}
|
|
4598
|
-
|
|
4599
|
-
// src/policy.ts
|
|
4600
|
-
import { randomUUID } from "crypto";
|
|
4601
|
-
var StaticIntegrationPolicyEngine = class {
|
|
4602
|
-
rules;
|
|
4603
|
-
defaultReadEffect;
|
|
4604
|
-
defaultWriteEffect;
|
|
4605
|
-
defaultDestructiveEffect;
|
|
4606
|
-
now;
|
|
4607
|
-
constructor(options = {}) {
|
|
4608
|
-
this.rules = options.rules ?? [];
|
|
4609
|
-
this.defaultReadEffect = options.defaultReadEffect ?? "allow";
|
|
4610
|
-
this.defaultWriteEffect = options.defaultWriteEffect ?? "require_approval";
|
|
4611
|
-
this.defaultDestructiveEffect = options.defaultDestructiveEffect ?? "deny";
|
|
4612
|
-
this.now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
4613
|
-
}
|
|
4614
|
-
decide(ctx) {
|
|
4615
|
-
const action = ctx.action;
|
|
4616
|
-
if (!action) return { decision: "deny", reason: "Integration action is missing from connector catalog." };
|
|
4617
|
-
const matched = this.rules.find((rule) => ruleMatches(rule, ctx));
|
|
4618
|
-
const effect = matched?.effect ?? this.defaultEffect(action.risk);
|
|
4619
|
-
const reason = matched?.reason ?? defaultReason(effect, action.risk);
|
|
4620
|
-
if (effect === "allow") return { decision: "allow", reason, metadata: matched ? { ruleId: matched.id } : void 0 };
|
|
4621
|
-
if (effect === "deny") return { decision: "deny", reason, metadata: matched ? { ruleId: matched.id } : void 0 };
|
|
4622
|
-
return {
|
|
4623
|
-
decision: "require_approval",
|
|
4624
|
-
reason,
|
|
4625
|
-
approval: buildApprovalRequest(ctx, reason, this.now()),
|
|
4626
|
-
metadata: matched ? { ruleId: matched.id } : void 0
|
|
4627
|
-
};
|
|
4628
|
-
}
|
|
4629
|
-
defaultEffect(risk) {
|
|
4630
|
-
if (risk === "read") return this.defaultReadEffect;
|
|
4631
|
-
if (risk === "write") return this.defaultWriteEffect;
|
|
4632
|
-
return this.defaultDestructiveEffect;
|
|
4633
|
-
}
|
|
4634
|
-
};
|
|
4635
|
-
function createDefaultIntegrationPolicyEngine(options = {}) {
|
|
4636
|
-
return new StaticIntegrationPolicyEngine(options);
|
|
4637
|
-
}
|
|
4638
|
-
function buildApprovalRequest(ctx, reason, requestedAt) {
|
|
4639
|
-
if (!ctx.action) {
|
|
4640
|
-
throw new Error("Cannot build approval request without an action descriptor.");
|
|
4641
|
-
}
|
|
4642
|
-
return {
|
|
4643
|
-
id: `approval_${randomUUID()}`,
|
|
4644
|
-
connectionId: ctx.connection.id,
|
|
4645
|
-
providerId: ctx.connection.providerId,
|
|
4646
|
-
connectorId: ctx.connection.connectorId,
|
|
4647
|
-
action: ctx.request.action,
|
|
4648
|
-
actor: { type: ctx.subject.type, id: ctx.subject.id },
|
|
4649
|
-
risk: ctx.action.risk,
|
|
4650
|
-
dataClass: ctx.action.dataClass,
|
|
4651
|
-
reason,
|
|
4652
|
-
requestedAt: requestedAt.toISOString(),
|
|
4653
|
-
inputPreview: previewInput(ctx.request.input)
|
|
4654
|
-
};
|
|
6078
|
+
category: connector.category,
|
|
6079
|
+
action,
|
|
6080
|
+
risk: action.risk,
|
|
6081
|
+
dataClass: action.dataClass,
|
|
6082
|
+
requiredScopes: action.requiredScopes,
|
|
6083
|
+
inputSchema: action.inputSchema,
|
|
6084
|
+
outputSchema: action.outputSchema,
|
|
6085
|
+
tags
|
|
6086
|
+
});
|
|
6087
|
+
}
|
|
6088
|
+
}
|
|
6089
|
+
return tools;
|
|
4655
6090
|
}
|
|
4656
|
-
function
|
|
4657
|
-
|
|
4658
|
-
|
|
4659
|
-
|
|
4660
|
-
|
|
6091
|
+
function searchIntegrationTools(catalog, query, filters = {}) {
|
|
6092
|
+
const terms = tokenize(query);
|
|
6093
|
+
const filtered = catalog.filter((tool) => {
|
|
6094
|
+
if (filters.providerId && tool.providerId !== filters.providerId) return false;
|
|
6095
|
+
if (filters.connectorId && tool.connectorId !== filters.connectorId) return false;
|
|
6096
|
+
if (filters.category && tool.category !== filters.category) return false;
|
|
6097
|
+
if (filters.dataClass && tool.dataClass !== filters.dataClass) return false;
|
|
6098
|
+
if (filters.maxRisk && riskRank2[tool.risk] > riskRank2[filters.maxRisk]) return false;
|
|
6099
|
+
return true;
|
|
6100
|
+
});
|
|
6101
|
+
const scored = filtered.map((tool) => scoreTool(tool, terms));
|
|
6102
|
+
return scored.filter((result) => terms.length === 0 || result.score > 0).sort((a, b) => b.score - a.score || a.tool.name.localeCompare(b.tool.name)).slice(0, filters.limit ?? 20);
|
|
4661
6103
|
}
|
|
4662
|
-
function
|
|
4663
|
-
|
|
4664
|
-
|
|
4665
|
-
|
|
4666
|
-
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
6104
|
+
function toMcpTools(tools) {
|
|
6105
|
+
return tools.map((tool) => ({
|
|
6106
|
+
name: tool.name,
|
|
6107
|
+
description: `${tool.title}. ${tool.description}`,
|
|
6108
|
+
inputSchema: tool.inputSchema ?? {
|
|
6109
|
+
type: "object",
|
|
6110
|
+
additionalProperties: true,
|
|
6111
|
+
properties: {}
|
|
6112
|
+
}
|
|
6113
|
+
}));
|
|
4671
6114
|
}
|
|
4672
|
-
function
|
|
4673
|
-
if (
|
|
4674
|
-
|
|
4675
|
-
|
|
6115
|
+
function scoreTool(tool, terms) {
|
|
6116
|
+
if (terms.length === 0) return { tool, score: 1, matched: [] };
|
|
6117
|
+
const haystack = new Set(tool.tags);
|
|
6118
|
+
const matched = [];
|
|
6119
|
+
let score = 0;
|
|
6120
|
+
for (const term of terms) {
|
|
6121
|
+
if (haystack.has(term)) {
|
|
6122
|
+
matched.push(term);
|
|
6123
|
+
score += 4;
|
|
6124
|
+
continue;
|
|
6125
|
+
}
|
|
6126
|
+
if (tool.tags.some((tag) => tag.includes(term))) {
|
|
6127
|
+
matched.push(term);
|
|
6128
|
+
score += 1;
|
|
6129
|
+
}
|
|
6130
|
+
}
|
|
6131
|
+
if (tool.risk === "read") score += 0.25;
|
|
6132
|
+
return { tool, score, matched: unique4(matched) };
|
|
4676
6133
|
}
|
|
4677
|
-
function
|
|
4678
|
-
|
|
4679
|
-
if (effect === "deny") return `${risk} integration action denied by default policy.`;
|
|
4680
|
-
return `${risk} integration action requires approval by default policy.`;
|
|
6134
|
+
function tokenize(value) {
|
|
6135
|
+
return value.toLowerCase().split(/[^a-z0-9]+/g).map((part) => part.trim()).filter(Boolean);
|
|
4681
6136
|
}
|
|
4682
|
-
function
|
|
4683
|
-
return
|
|
6137
|
+
function encodeToolPart(value) {
|
|
6138
|
+
return Buffer.from(value, "utf8").toString("base64url").replace(/_/g, ".");
|
|
4684
6139
|
}
|
|
4685
|
-
function
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
|
|
4689
|
-
|
|
4690
|
-
if (/token|secret|password|authorization|api[_-]?key|credential/i.test(key)) {
|
|
4691
|
-
out[key] = "[REDACTED]";
|
|
4692
|
-
} else {
|
|
4693
|
-
out[key] = redactUnknown(child);
|
|
4694
|
-
}
|
|
4695
|
-
}
|
|
4696
|
-
return out;
|
|
6140
|
+
function decodeToolPart(value) {
|
|
6141
|
+
return Buffer.from(value.replace(/\./g, "_"), "base64url").toString("utf8");
|
|
6142
|
+
}
|
|
6143
|
+
function unique4(values) {
|
|
6144
|
+
return [...new Set(values)];
|
|
4697
6145
|
}
|
|
4698
6146
|
|
|
4699
6147
|
// src/sandbox.ts
|
|
@@ -4754,13 +6202,13 @@ function redactInvocationEnvelope(envelope) {
|
|
|
4754
6202
|
return {
|
|
4755
6203
|
...envelope,
|
|
4756
6204
|
capabilityToken: "[REDACTED]",
|
|
4757
|
-
input:
|
|
6205
|
+
input: redactUnknown4(envelope.input)
|
|
4758
6206
|
};
|
|
4759
6207
|
}
|
|
4760
6208
|
function redactCapability(capability) {
|
|
4761
6209
|
return {
|
|
4762
6210
|
...capability,
|
|
4763
|
-
metadata:
|
|
6211
|
+
metadata: redactUnknown4(capability.metadata)
|
|
4764
6212
|
};
|
|
4765
6213
|
}
|
|
4766
6214
|
function normalizeIntegrationResult(result) {
|
|
@@ -4788,15 +6236,40 @@ function normalizeIntegrationResult(result) {
|
|
|
4788
6236
|
metadata: result.metadata
|
|
4789
6237
|
};
|
|
4790
6238
|
}
|
|
4791
|
-
function
|
|
4792
|
-
|
|
6239
|
+
async function dispatchIntegrationInvocation(envelope, options) {
|
|
6240
|
+
try {
|
|
6241
|
+
validateIntegrationInvocationEnvelope(envelope, options);
|
|
6242
|
+
const result = await options.hub.invokeWithCapability(
|
|
6243
|
+
envelope.capabilityToken,
|
|
6244
|
+
invocationRequestFromEnvelope(envelope)
|
|
6245
|
+
);
|
|
6246
|
+
return normalizeIntegrationResult(result);
|
|
6247
|
+
} catch (error) {
|
|
6248
|
+
return {
|
|
6249
|
+
status: "failed",
|
|
6250
|
+
action: typeof envelope?.action === "string" ? envelope.action : "unknown",
|
|
6251
|
+
error: error instanceof Error ? error.message : "Integration invocation failed."
|
|
6252
|
+
};
|
|
6253
|
+
}
|
|
6254
|
+
}
|
|
6255
|
+
var IntegrationSandboxHost = class {
|
|
6256
|
+
options;
|
|
6257
|
+
constructor(options) {
|
|
6258
|
+
this.options = options;
|
|
6259
|
+
}
|
|
6260
|
+
dispatch(envelope) {
|
|
6261
|
+
return dispatchIntegrationInvocation(envelope, this.options);
|
|
6262
|
+
}
|
|
6263
|
+
};
|
|
6264
|
+
function redactUnknown4(value) {
|
|
6265
|
+
if (Array.isArray(value)) return value.map(redactUnknown4);
|
|
4793
6266
|
if (!value || typeof value !== "object") return value;
|
|
4794
6267
|
const out = {};
|
|
4795
6268
|
for (const [key, child] of Object.entries(value)) {
|
|
4796
6269
|
if (/token|secret|password|authorization|api[_-]?key|credential/i.test(key)) {
|
|
4797
6270
|
out[key] = "[REDACTED]";
|
|
4798
6271
|
} else {
|
|
4799
|
-
out[key] =
|
|
6272
|
+
out[key] = redactUnknown4(child);
|
|
4800
6273
|
}
|
|
4801
6274
|
}
|
|
4802
6275
|
return out;
|
|
@@ -4808,152 +6281,6 @@ function isPlainRecord(value) {
|
|
|
4808
6281
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
4809
6282
|
}
|
|
4810
6283
|
|
|
4811
|
-
// src/adapter-provider.ts
|
|
4812
|
-
function createConnectorAdapterProvider(options) {
|
|
4813
|
-
const providerId = options.id ?? "first-party";
|
|
4814
|
-
const now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
4815
|
-
const adapters = /* @__PURE__ */ new Map();
|
|
4816
|
-
for (const adapter of options.adapters) {
|
|
4817
|
-
adapters.set(adapter.manifest.kind, adapter);
|
|
4818
|
-
}
|
|
4819
|
-
return {
|
|
4820
|
-
id: providerId,
|
|
4821
|
-
kind: options.kind ?? "first_party",
|
|
4822
|
-
listConnectors: () => [...adapters.values()].map((adapter) => manifestToConnector(providerId, adapter)),
|
|
4823
|
-
async invokeAction(connection, request) {
|
|
4824
|
-
const adapter = adapters.get(connection.connectorId);
|
|
4825
|
-
if (!adapter) {
|
|
4826
|
-
throw new IntegrationError(`Connector adapter ${connection.connectorId} not found.`, "connector_not_found");
|
|
4827
|
-
}
|
|
4828
|
-
const capability = adapter.manifest.capabilities.find((candidate) => candidate.name === request.action);
|
|
4829
|
-
if (!capability) {
|
|
4830
|
-
throw new IntegrationError(`Capability ${request.action} is not defined by ${connection.connectorId}.`, "action_not_found");
|
|
4831
|
-
}
|
|
4832
|
-
const source = await options.resolveDataSource(connection);
|
|
4833
|
-
const invocation = {
|
|
4834
|
-
source,
|
|
4835
|
-
capabilityName: request.action,
|
|
4836
|
-
args: toRecord(request.input),
|
|
4837
|
-
idempotencyKey: request.idempotencyKey ?? `idem_${connection.id}_${request.action}_${now().getTime()}`,
|
|
4838
|
-
expectedEtag: typeof request.metadata?.expectedEtag === "string" ? request.metadata.expectedEtag : void 0,
|
|
4839
|
-
callSessionId: typeof request.metadata?.callSessionId === "string" ? request.metadata.callSessionId : void 0
|
|
4840
|
-
};
|
|
4841
|
-
if (capability.class === "read") {
|
|
4842
|
-
if (!adapter.executeRead) {
|
|
4843
|
-
throw new IntegrationError(`Connector ${connection.connectorId} does not implement reads.`, "action_not_found");
|
|
4844
|
-
}
|
|
4845
|
-
const result = await adapter.executeRead(invocation);
|
|
4846
|
-
return readResultToAction(request, result);
|
|
4847
|
-
}
|
|
4848
|
-
if (capability.class === "mutation") {
|
|
4849
|
-
if (!adapter.executeMutation) {
|
|
4850
|
-
throw new IntegrationError(`Connector ${connection.connectorId} does not implement mutations.`, "action_not_found");
|
|
4851
|
-
}
|
|
4852
|
-
const result = await adapter.executeMutation(invocation);
|
|
4853
|
-
return mutationResultToAction(request, result);
|
|
4854
|
-
}
|
|
4855
|
-
throw new IntegrationError(`Capability ${request.action} is not invokable as an action.`, "action_not_found");
|
|
4856
|
-
}
|
|
4857
|
-
};
|
|
4858
|
-
}
|
|
4859
|
-
function manifestToConnector(providerId, adapter) {
|
|
4860
|
-
const manifest = adapter.manifest;
|
|
4861
|
-
return {
|
|
4862
|
-
id: manifest.kind,
|
|
4863
|
-
providerId,
|
|
4864
|
-
title: manifest.displayName,
|
|
4865
|
-
category: mapCategory(manifest.category),
|
|
4866
|
-
auth: mapAuth(manifest.auth.kind),
|
|
4867
|
-
scopes: manifest.auth.kind === "oauth2" ? manifest.auth.scopes : [],
|
|
4868
|
-
actions: manifest.capabilities.filter((capability) => capability.class === "read" || capability.class === "mutation").map((capability) => ({
|
|
4869
|
-
id: capability.name,
|
|
4870
|
-
title: titleFromName(capability.name),
|
|
4871
|
-
risk: capability.class === "read" ? "read" : capability.externalEffect ? "destructive" : "write",
|
|
4872
|
-
requiredScopes: capability.requiredScopes ?? [],
|
|
4873
|
-
dataClass: inferDataClass(manifest.category),
|
|
4874
|
-
description: capability.description,
|
|
4875
|
-
approvalRequired: capability.class === "mutation",
|
|
4876
|
-
inputSchema: capability.parameters
|
|
4877
|
-
})),
|
|
4878
|
-
metadata: {
|
|
4879
|
-
source: "first-party-adapter",
|
|
4880
|
-
supportTier: "firstPartyExecutable",
|
|
4881
|
-
executable: true
|
|
4882
|
-
}
|
|
4883
|
-
};
|
|
4884
|
-
}
|
|
4885
|
-
function readResultToAction(request, result) {
|
|
4886
|
-
return {
|
|
4887
|
-
ok: true,
|
|
4888
|
-
action: request.action,
|
|
4889
|
-
output: result.data,
|
|
4890
|
-
metadata: {
|
|
4891
|
-
etag: result.etag,
|
|
4892
|
-
fetchedAt: result.fetchedAt
|
|
4893
|
-
}
|
|
4894
|
-
};
|
|
4895
|
-
}
|
|
4896
|
-
function mutationResultToAction(request, result) {
|
|
4897
|
-
if (result.status === "committed") {
|
|
4898
|
-
return {
|
|
4899
|
-
ok: true,
|
|
4900
|
-
action: request.action,
|
|
4901
|
-
output: result.data,
|
|
4902
|
-
metadata: {
|
|
4903
|
-
etagAfter: result.etagAfter,
|
|
4904
|
-
committedAt: result.committedAt,
|
|
4905
|
-
idempotentReplay: result.idempotentReplay
|
|
4906
|
-
}
|
|
4907
|
-
};
|
|
4908
|
-
}
|
|
4909
|
-
if (result.status === "conflict") {
|
|
4910
|
-
return {
|
|
4911
|
-
ok: false,
|
|
4912
|
-
action: request.action,
|
|
4913
|
-
output: {
|
|
4914
|
-
conflict: true,
|
|
4915
|
-
message: result.message,
|
|
4916
|
-
alternatives: result.alternatives,
|
|
4917
|
-
currentState: result.currentState
|
|
4918
|
-
}
|
|
4919
|
-
};
|
|
4920
|
-
}
|
|
4921
|
-
return {
|
|
4922
|
-
ok: false,
|
|
4923
|
-
action: request.action,
|
|
4924
|
-
output: {
|
|
4925
|
-
rateLimited: true,
|
|
4926
|
-
retryAfterMs: result.retryAfterMs,
|
|
4927
|
-
message: result.message
|
|
4928
|
-
}
|
|
4929
|
-
};
|
|
4930
|
-
}
|
|
4931
|
-
function mapAuth(kind) {
|
|
4932
|
-
if (kind === "oauth2") return "oauth2";
|
|
4933
|
-
if (kind === "api-key") return "api_key";
|
|
4934
|
-
if (kind === "none") return "none";
|
|
4935
|
-
return "custom";
|
|
4936
|
-
}
|
|
4937
|
-
function mapCategory(category) {
|
|
4938
|
-
if (category === "comms") return "chat";
|
|
4939
|
-
if (category === "spreadsheet") return "database";
|
|
4940
|
-
if (category === "doc") return "docs";
|
|
4941
|
-
if (category === "commerce") return "workflow";
|
|
4942
|
-
return category === "other" ? "other" : category;
|
|
4943
|
-
}
|
|
4944
|
-
function inferDataClass(category) {
|
|
4945
|
-
if (category === "commerce") return "sensitive";
|
|
4946
|
-
if (category === "webhook") return "internal";
|
|
4947
|
-
return "private";
|
|
4948
|
-
}
|
|
4949
|
-
function titleFromName(name) {
|
|
4950
|
-
return name.split(/[._-]/g).filter(Boolean).map((part) => part.slice(0, 1).toUpperCase() + part.slice(1)).join(" ");
|
|
4951
|
-
}
|
|
4952
|
-
function toRecord(input) {
|
|
4953
|
-
if (input && typeof input === "object" && !Array.isArray(input)) return input;
|
|
4954
|
-
return {};
|
|
4955
|
-
}
|
|
4956
|
-
|
|
4957
6284
|
// src/importers.ts
|
|
4958
6285
|
var HTTP_METHODS = /* @__PURE__ */ new Set(["get", "post", "put", "patch", "delete"]);
|
|
4959
6286
|
function importOpenApiConnector(document, options) {
|
|
@@ -5008,7 +6335,7 @@ function importMcpConnector(catalog, options) {
|
|
|
5008
6335
|
}));
|
|
5009
6336
|
}
|
|
5010
6337
|
function connectorFromActions(options, actions) {
|
|
5011
|
-
const scopes =
|
|
6338
|
+
const scopes = unique5([
|
|
5012
6339
|
...options.scopes ?? [],
|
|
5013
6340
|
...actions.flatMap((action) => action.requiredScopes)
|
|
5014
6341
|
]);
|
|
@@ -5040,7 +6367,7 @@ function riskFromMcpTool(tool, fallback) {
|
|
|
5040
6367
|
}
|
|
5041
6368
|
function scopesFromOpenApiOperation(operation, fallback) {
|
|
5042
6369
|
const scopes = (operation.security ?? []).flatMap((entry) => Object.values(entry).flat());
|
|
5043
|
-
return
|
|
6370
|
+
return unique5(scopes.length > 0 ? scopes : fallback);
|
|
5044
6371
|
}
|
|
5045
6372
|
function openApiInputSchema(path, method, operation) {
|
|
5046
6373
|
return {
|
|
@@ -5060,7 +6387,7 @@ function titleFromId(id) {
|
|
|
5060
6387
|
function isObject(value) {
|
|
5061
6388
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
5062
6389
|
}
|
|
5063
|
-
function
|
|
6390
|
+
function unique5(values) {
|
|
5064
6391
|
return [...new Set(values)];
|
|
5065
6392
|
}
|
|
5066
6393
|
|
|
@@ -5111,7 +6438,7 @@ function normalizeGatewayCatalog(entries, options) {
|
|
|
5111
6438
|
title,
|
|
5112
6439
|
category: normalizeCategory(entry.category),
|
|
5113
6440
|
auth: normalizeAuth(entry.auth),
|
|
5114
|
-
scopes:
|
|
6441
|
+
scopes: unique6([
|
|
5115
6442
|
...entry.scopes ?? [],
|
|
5116
6443
|
...actions.flatMap((action) => action.requiredScopes)
|
|
5117
6444
|
]),
|
|
@@ -5141,7 +6468,7 @@ function normalizeActions(actions, fallbackScopes) {
|
|
|
5141
6468
|
id,
|
|
5142
6469
|
title: action.title ?? action.name ?? titleFromId2(id),
|
|
5143
6470
|
risk: normalizeRisk(action.risk),
|
|
5144
|
-
requiredScopes:
|
|
6471
|
+
requiredScopes: unique6([
|
|
5145
6472
|
...action.requiredScopes ?? [],
|
|
5146
6473
|
...action.scopes ?? [],
|
|
5147
6474
|
...action.requiredScopes?.length || action.scopes?.length ? [] : fallbackScopes
|
|
@@ -5155,21 +6482,21 @@ function normalizeActions(actions, fallbackScopes) {
|
|
|
5155
6482
|
}).filter((action) => action.id);
|
|
5156
6483
|
}
|
|
5157
6484
|
function normalizeTriggers(triggers, fallbackScopes) {
|
|
5158
|
-
const normalized = triggers.map((
|
|
5159
|
-
const id = slug2(
|
|
6485
|
+
const normalized = triggers.map((trigger2) => {
|
|
6486
|
+
const id = slug2(trigger2.id ?? trigger2.key ?? trigger2.name ?? trigger2.title ?? "");
|
|
5160
6487
|
return {
|
|
5161
6488
|
id,
|
|
5162
|
-
title:
|
|
5163
|
-
requiredScopes:
|
|
5164
|
-
...
|
|
5165
|
-
...
|
|
5166
|
-
...
|
|
6489
|
+
title: trigger2.title ?? trigger2.name ?? titleFromId2(id),
|
|
6490
|
+
requiredScopes: unique6([
|
|
6491
|
+
...trigger2.requiredScopes ?? [],
|
|
6492
|
+
...trigger2.scopes ?? [],
|
|
6493
|
+
...trigger2.requiredScopes?.length || trigger2.scopes?.length ? [] : fallbackScopes
|
|
5167
6494
|
]),
|
|
5168
|
-
dataClass: normalizeDataClass(
|
|
5169
|
-
description:
|
|
5170
|
-
payloadSchema:
|
|
6495
|
+
dataClass: normalizeDataClass(trigger2.dataClass),
|
|
6496
|
+
description: trigger2.description,
|
|
6497
|
+
payloadSchema: trigger2.payloadSchema
|
|
5171
6498
|
};
|
|
5172
|
-
}).filter((
|
|
6499
|
+
}).filter((trigger2) => trigger2.id);
|
|
5173
6500
|
return normalized.length > 0 ? normalized : void 0;
|
|
5174
6501
|
}
|
|
5175
6502
|
function defaultActionsFor(category, scopes) {
|
|
@@ -5249,7 +6576,7 @@ function slug2(value) {
|
|
|
5249
6576
|
function titleFromId2(id) {
|
|
5250
6577
|
return id.split("-").filter(Boolean).map((part) => part.slice(0, 1).toUpperCase() + part.slice(1)).join(" ");
|
|
5251
6578
|
}
|
|
5252
|
-
function
|
|
6579
|
+
function unique6(values) {
|
|
5253
6580
|
return [...new Set(values)];
|
|
5254
6581
|
}
|
|
5255
6582
|
|
|
@@ -5337,7 +6664,7 @@ var IntegrationRuntime = class {
|
|
|
5337
6664
|
const connector = {
|
|
5338
6665
|
...entry.connector,
|
|
5339
6666
|
actions: entry.connector.actions.filter((action) => grant.allowedActions.includes(action.id)),
|
|
5340
|
-
triggers: entry.connector.triggers?.filter((
|
|
6667
|
+
triggers: entry.connector.triggers?.filter((trigger2) => grant.allowedTriggers.includes(trigger2.id)),
|
|
5341
6668
|
scopes: entry.connector.scopes.filter((scope) => grant.scopes.includes(scope))
|
|
5342
6669
|
};
|
|
5343
6670
|
const capability = await this.hub.issueCapability({
|
|
@@ -5431,32 +6758,32 @@ function missing(requirement, status, message, connector, registryEntry2) {
|
|
|
5431
6758
|
}
|
|
5432
6759
|
function requiredActions(requirement, connector) {
|
|
5433
6760
|
if (requirement.mode === "trigger") return [];
|
|
5434
|
-
if (requirement.requiredActions?.length) return
|
|
6761
|
+
if (requirement.requiredActions?.length) return unique7(requirement.requiredActions);
|
|
5435
6762
|
const actions = connector.actions.filter((action) => {
|
|
5436
6763
|
if (requirement.mode === "read") return action.risk === "read";
|
|
5437
6764
|
if (requirement.mode === "write") return action.risk !== "read";
|
|
5438
6765
|
return false;
|
|
5439
6766
|
});
|
|
5440
|
-
return
|
|
6767
|
+
return unique7(actions.map((action) => action.id));
|
|
5441
6768
|
}
|
|
5442
6769
|
function requiredTriggers(requirement, connector) {
|
|
5443
|
-
if (requirement.requiredTriggers?.length) return
|
|
6770
|
+
if (requirement.requiredTriggers?.length) return unique7(requirement.requiredTriggers);
|
|
5444
6771
|
if (requirement.mode !== "trigger") return [];
|
|
5445
|
-
return
|
|
6772
|
+
return unique7((connector.triggers ?? []).map((trigger2) => trigger2.id));
|
|
5446
6773
|
}
|
|
5447
6774
|
function requiredScopes(requirement, connector) {
|
|
5448
|
-
if (requirement.requiredScopes?.length) return
|
|
6775
|
+
if (requirement.requiredScopes?.length) return unique7(requirement.requiredScopes);
|
|
5449
6776
|
const actionIds = new Set(requiredActions(requirement, connector));
|
|
5450
6777
|
const triggerIds = new Set(requiredTriggers(requirement, connector));
|
|
5451
|
-
return
|
|
6778
|
+
return unique7([
|
|
5452
6779
|
...connector.actions.filter((action) => actionIds.has(action.id)).flatMap((action) => action.requiredScopes),
|
|
5453
|
-
...(connector.triggers ?? []).filter((
|
|
6780
|
+
...(connector.triggers ?? []).filter((trigger2) => triggerIds.has(trigger2.id)).flatMap((trigger2) => trigger2.requiredScopes)
|
|
5454
6781
|
]);
|
|
5455
6782
|
}
|
|
5456
6783
|
function sameActor(a, b) {
|
|
5457
6784
|
return a.type === b.type && a.id === b.id;
|
|
5458
6785
|
}
|
|
5459
|
-
function
|
|
6786
|
+
function unique7(values) {
|
|
5460
6787
|
return [...new Set(values)];
|
|
5461
6788
|
}
|
|
5462
6789
|
|
|
@@ -5740,11 +7067,11 @@ var IntegrationHub = class {
|
|
|
5740
7067
|
assertScopes(connection, request.scopes);
|
|
5741
7068
|
const now = this.now();
|
|
5742
7069
|
const capability = {
|
|
5743
|
-
id: `cap_${
|
|
7070
|
+
id: `cap_${randomUUID3()}`,
|
|
5744
7071
|
subject: request.subject,
|
|
5745
7072
|
connectionId: request.connectionId,
|
|
5746
|
-
scopes:
|
|
5747
|
-
allowedActions:
|
|
7073
|
+
scopes: unique8(request.scopes),
|
|
7074
|
+
allowedActions: unique8(request.allowedActions),
|
|
5748
7075
|
issuedAt: now.toISOString(),
|
|
5749
7076
|
expiresAt: new Date(now.getTime() + request.ttlMs).toISOString(),
|
|
5750
7077
|
metadata: request.metadata
|
|
@@ -5797,18 +7124,18 @@ var IntegrationHub = class {
|
|
|
5797
7124
|
}
|
|
5798
7125
|
return proceed();
|
|
5799
7126
|
}
|
|
5800
|
-
async subscribeTrigger(connectionId,
|
|
7127
|
+
async subscribeTrigger(connectionId, trigger2, targetUrl) {
|
|
5801
7128
|
const connection = await this.requireConnection(connectionId);
|
|
5802
7129
|
this.assertConnectionActive(connection);
|
|
5803
7130
|
const provider = this.requireProvider(connection.providerId);
|
|
5804
7131
|
const connector = await this.requireConnector(provider, connection.connectorId);
|
|
5805
|
-
const spec = connector.triggers?.find((candidate) => candidate.id ===
|
|
5806
|
-
if (!spec) throw new IntegrationError(`Trigger ${
|
|
7132
|
+
const spec = connector.triggers?.find((candidate) => candidate.id === trigger2);
|
|
7133
|
+
if (!spec) throw new IntegrationError(`Trigger ${trigger2} is not defined by connector ${connector.id}.`, "action_not_found");
|
|
5807
7134
|
assertScopes(connection, spec.requiredScopes);
|
|
5808
7135
|
if (!provider.subscribeTrigger) {
|
|
5809
7136
|
throw new IntegrationError(`Provider ${provider.id} does not support triggers.`, "auth_not_supported");
|
|
5810
7137
|
}
|
|
5811
|
-
return provider.subscribeTrigger(connection,
|
|
7138
|
+
return provider.subscribeTrigger(connection, trigger2, targetUrl);
|
|
5812
7139
|
}
|
|
5813
7140
|
requireProvider(providerId) {
|
|
5814
7141
|
const provider = this.providers.get(providerId);
|
|
@@ -5893,10 +7220,10 @@ function createMockIntegrationProvider(options = {}) {
|
|
|
5893
7220
|
action: request.action,
|
|
5894
7221
|
output: { echo: request.input ?? null }
|
|
5895
7222
|
},
|
|
5896
|
-
subscribeTrigger: (connection,
|
|
5897
|
-
id: `sub_${connection.id}_${
|
|
7223
|
+
subscribeTrigger: (connection, trigger2, targetUrl) => ({
|
|
7224
|
+
id: `sub_${connection.id}_${trigger2}`,
|
|
5898
7225
|
connectionId: connection.id,
|
|
5899
|
-
trigger,
|
|
7226
|
+
trigger: trigger2,
|
|
5900
7227
|
targetUrl,
|
|
5901
7228
|
status: "active",
|
|
5902
7229
|
createdAt: (/* @__PURE__ */ new Date(0)).toISOString()
|
|
@@ -5924,10 +7251,10 @@ function createHttpIntegrationProvider(options) {
|
|
|
5924
7251
|
request
|
|
5925
7252
|
}, options.bearer);
|
|
5926
7253
|
},
|
|
5927
|
-
async subscribeTrigger(connection,
|
|
7254
|
+
async subscribeTrigger(connection, trigger2, targetUrl) {
|
|
5928
7255
|
return postJson(fetcher, `${baseUrl}/triggers/subscribe`, {
|
|
5929
7256
|
connection,
|
|
5930
|
-
trigger,
|
|
7257
|
+
trigger: trigger2,
|
|
5931
7258
|
targetUrl
|
|
5932
7259
|
}, options.bearer);
|
|
5933
7260
|
},
|
|
@@ -5990,61 +7317,95 @@ function base64UrlEncode(value) {
|
|
|
5990
7317
|
function base64UrlDecode(value) {
|
|
5991
7318
|
return Buffer.from(value, "base64url").toString("utf8");
|
|
5992
7319
|
}
|
|
5993
|
-
function
|
|
7320
|
+
function unique8(values) {
|
|
5994
7321
|
return [...new Set(values)];
|
|
5995
7322
|
}
|
|
5996
7323
|
export {
|
|
5997
7324
|
ACTIVEPIECES_OVERRIDES,
|
|
7325
|
+
ApprovalBackedPolicyEngine,
|
|
7326
|
+
CANONICAL_INTEGRATION_ACTIONS,
|
|
5998
7327
|
CredentialsExpired,
|
|
7328
|
+
DEFAULT_INTEGRATION_BRIDGE_ENV,
|
|
5999
7329
|
DEFAULT_SIGNATURE_TOLERANCE_SECONDS,
|
|
7330
|
+
DefaultIntegrationActionGuard,
|
|
6000
7331
|
INTEGRATION_FAMILIES,
|
|
6001
7332
|
InMemoryConnectionStore,
|
|
7333
|
+
InMemoryIntegrationApprovalStore,
|
|
7334
|
+
InMemoryIntegrationAuditStore,
|
|
7335
|
+
InMemoryIntegrationEventStore,
|
|
6002
7336
|
InMemoryIntegrationGrantStore,
|
|
7337
|
+
InMemoryIntegrationHealthcheckStore,
|
|
7338
|
+
InMemoryIntegrationIdempotencyStore,
|
|
7339
|
+
InMemoryIntegrationSecretStore,
|
|
6003
7340
|
InMemoryIntegrationWorkflowStore,
|
|
6004
7341
|
InMemoryOAuthFlowStore,
|
|
6005
7342
|
IntegrationError,
|
|
6006
7343
|
IntegrationHub,
|
|
6007
7344
|
IntegrationRuntime,
|
|
7345
|
+
IntegrationRuntimeError,
|
|
7346
|
+
IntegrationSandboxHost,
|
|
6008
7347
|
IntegrationWorkflowRuntime,
|
|
7348
|
+
PROVIDER_PASSTHROUGH_ACTION,
|
|
6009
7349
|
ResourceContention,
|
|
6010
7350
|
StaticIntegrationPolicyEngine,
|
|
7351
|
+
TangleIntegrationsClient,
|
|
6011
7352
|
_resetPendingFlowsForTests,
|
|
6012
7353
|
airtableConnector,
|
|
6013
7354
|
asanaConnector,
|
|
6014
7355
|
assertValidConnectorManifest,
|
|
7356
|
+
assertValidIntegrationManifest,
|
|
6015
7357
|
assertValidIntegrationSpec,
|
|
6016
7358
|
buildActivepiecesConnectors,
|
|
6017
7359
|
buildApprovalRequest,
|
|
7360
|
+
buildCanonicalLaunchConnectors,
|
|
6018
7361
|
buildDefaultIntegrationRegistry,
|
|
6019
7362
|
buildHealthcheckPlan,
|
|
7363
|
+
buildIntegrationBridgeEnvironment,
|
|
7364
|
+
buildIntegrationBridgePayload,
|
|
6020
7365
|
buildIntegrationCoverageConnectors,
|
|
6021
7366
|
buildIntegrationInvocationEnvelope,
|
|
6022
7367
|
buildIntegrationToolCatalog,
|
|
7368
|
+
calendarExercisePlannerManifest,
|
|
7369
|
+
canonicalActionConnectorId,
|
|
6023
7370
|
canonicalConnectorId,
|
|
6024
7371
|
composeIntegrationRegistry,
|
|
6025
7372
|
consoleStepsToText,
|
|
6026
7373
|
consumePendingFlow,
|
|
7374
|
+
createApprovalBackedPolicyEngine,
|
|
7375
|
+
createAuditingActionGuard,
|
|
7376
|
+
createConnectionCredentialResolver,
|
|
6027
7377
|
createConnectorAdapterProvider,
|
|
7378
|
+
createCredentialBackedAdapterProvider,
|
|
7379
|
+
createDefaultIntegrationActionGuard,
|
|
6028
7380
|
createDefaultIntegrationPolicyEngine,
|
|
6029
7381
|
createGatewayCatalogProvider,
|
|
6030
7382
|
createHttpIntegrationProvider,
|
|
7383
|
+
createIntegrationAuditEvent,
|
|
6031
7384
|
createIntegrationRuntime,
|
|
6032
7385
|
createIntegrationWorkflowRuntime,
|
|
6033
7386
|
createMockIntegrationProvider,
|
|
7387
|
+
createPlatformIntegrationPolicyPreset,
|
|
7388
|
+
createTangleIntegrationsClient,
|
|
6034
7389
|
declarativeRestConnector,
|
|
7390
|
+
decodeIntegrationBridgePayload,
|
|
7391
|
+
dispatchIntegrationInvocation,
|
|
7392
|
+
encodeIntegrationBridgePayload,
|
|
6035
7393
|
exchangeAuthorizationCode,
|
|
7394
|
+
explainMissingRequirements,
|
|
6036
7395
|
firstHeader,
|
|
6037
7396
|
getActivepiecesOverride,
|
|
6038
7397
|
getIntegrationFamily,
|
|
6039
7398
|
getIntegrationSpec,
|
|
6040
|
-
githubConnector,
|
|
7399
|
+
githubConnector2 as githubConnector,
|
|
6041
7400
|
gitlabConnector,
|
|
6042
7401
|
googleCalendar,
|
|
6043
7402
|
googleSheets,
|
|
7403
|
+
healthcheckRequest,
|
|
6044
7404
|
hubspot,
|
|
6045
7405
|
importGraphqlConnector,
|
|
6046
7406
|
importMcpConnector,
|
|
6047
7407
|
importOpenApiConnector,
|
|
7408
|
+
inferIntegrationManifestFromTools,
|
|
6048
7409
|
inferIntegrationSupportTier,
|
|
6049
7410
|
integrationCoverageChecklistMarkdown,
|
|
6050
7411
|
integrationSpecToConnector,
|
|
@@ -6057,18 +7418,30 @@ export {
|
|
|
6057
7418
|
manifestToConnector,
|
|
6058
7419
|
microsoftCalendar,
|
|
6059
7420
|
normalizeGatewayCatalog,
|
|
7421
|
+
normalizeIntegrationError,
|
|
6060
7422
|
normalizeIntegrationResult,
|
|
6061
7423
|
notionDatabase,
|
|
7424
|
+
parseIntegrationBridgeEnvironment,
|
|
6062
7425
|
parseIntegrationToolName,
|
|
6063
7426
|
parseStripeSignatureHeader,
|
|
7427
|
+
receiveIntegrationWebhook,
|
|
6064
7428
|
redactApprovalRequest,
|
|
6065
7429
|
redactCapability,
|
|
7430
|
+
redactIntegrationBridgePayload,
|
|
6066
7431
|
redactInvocationEnvelope,
|
|
6067
7432
|
refreshAccessToken,
|
|
6068
7433
|
renderAgentToolDescription,
|
|
7434
|
+
renderApprovalCopy,
|
|
7435
|
+
renderConsentSummary,
|
|
6069
7436
|
renderConsoleSteps,
|
|
6070
7437
|
renderRunbookMarkdown,
|
|
7438
|
+
resolveConnectionCredentials,
|
|
7439
|
+
resolveIntegrationApproval,
|
|
7440
|
+
revokeConnection,
|
|
7441
|
+
runIntegrationHealthcheck,
|
|
7442
|
+
runIntegrationHealthchecks,
|
|
6071
7443
|
salesforceConnector,
|
|
7444
|
+
sanitizeAuditConnection,
|
|
6072
7445
|
sanitizeConnection,
|
|
6073
7446
|
searchIntegrationTools,
|
|
6074
7447
|
signCapability,
|
|
@@ -6076,6 +7449,8 @@ export {
|
|
|
6076
7449
|
slackEventsConnector,
|
|
6077
7450
|
specAuthToConnectorAuth,
|
|
6078
7451
|
startOAuthFlow,
|
|
7452
|
+
statusForCode,
|
|
7453
|
+
storedEventToTriggerEvent,
|
|
6079
7454
|
stripePackConnector,
|
|
6080
7455
|
stripeWebhookReceiverConnector,
|
|
6081
7456
|
summarizeIntegrationRegistry,
|
|
@@ -6085,7 +7460,9 @@ export {
|
|
|
6085
7460
|
validateCredentialFormat,
|
|
6086
7461
|
validateCredentialSet,
|
|
6087
7462
|
validateIntegrationInvocationEnvelope,
|
|
7463
|
+
validateIntegrationManifest,
|
|
6088
7464
|
validateIntegrationSpec,
|
|
7465
|
+
validateProviderPassthroughRequest,
|
|
6089
7466
|
verifyCapabilityToken,
|
|
6090
7467
|
verifyHmacSignature,
|
|
6091
7468
|
verifySlackSignature,
|