@tangle-network/agent-integrations 0.2.1 → 0.3.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 +31 -1
- package/dist/index.d.ts +187 -3
- package/dist/index.js +470 -5
- package/dist/index.js.map +1 -1
- package/docs/execution-layer-launch-plan.md +220 -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, timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
2
|
+
import { createHmac as createHmac3, randomUUID as randomUUID2, timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
3
3
|
|
|
4
4
|
// src/connectors/types.ts
|
|
5
5
|
var ResourceContention = class extends Error {
|
|
@@ -2640,6 +2640,434 @@ var slackEventsConnector = {
|
|
|
2640
2640
|
}
|
|
2641
2641
|
};
|
|
2642
2642
|
|
|
2643
|
+
// src/catalog.ts
|
|
2644
|
+
var riskRank = {
|
|
2645
|
+
read: 0,
|
|
2646
|
+
write: 1,
|
|
2647
|
+
destructive: 2
|
|
2648
|
+
};
|
|
2649
|
+
function integrationToolName(providerId, connectorId, actionId) {
|
|
2650
|
+
return `int_${encodeToolPart(providerId)}_${encodeToolPart(connectorId)}_${encodeToolPart(actionId)}`;
|
|
2651
|
+
}
|
|
2652
|
+
function parseIntegrationToolName(name) {
|
|
2653
|
+
const parts = name.split("_");
|
|
2654
|
+
if (parts.length !== 4 || parts[0] !== "int") {
|
|
2655
|
+
throw new Error(`Invalid integration tool name: ${name}`);
|
|
2656
|
+
}
|
|
2657
|
+
return {
|
|
2658
|
+
providerId: decodeToolPart(parts[1]),
|
|
2659
|
+
connectorId: decodeToolPart(parts[2]),
|
|
2660
|
+
actionId: decodeToolPart(parts[3])
|
|
2661
|
+
};
|
|
2662
|
+
}
|
|
2663
|
+
function buildIntegrationToolCatalog(connectors) {
|
|
2664
|
+
const tools = [];
|
|
2665
|
+
for (const connector of connectors) {
|
|
2666
|
+
for (const action of connector.actions) {
|
|
2667
|
+
const tags = unique([
|
|
2668
|
+
connector.id,
|
|
2669
|
+
connector.providerId,
|
|
2670
|
+
connector.title,
|
|
2671
|
+
connector.category,
|
|
2672
|
+
action.id,
|
|
2673
|
+
action.title,
|
|
2674
|
+
action.risk,
|
|
2675
|
+
action.dataClass,
|
|
2676
|
+
...connector.scopes ?? [],
|
|
2677
|
+
...action.requiredScopes ?? []
|
|
2678
|
+
].flatMap(tokenize));
|
|
2679
|
+
tools.push({
|
|
2680
|
+
name: integrationToolName(connector.providerId, connector.id, action.id),
|
|
2681
|
+
title: `${connector.title}: ${action.title}`,
|
|
2682
|
+
description: action.description ?? `${action.risk} action ${action.id} on ${connector.title}`,
|
|
2683
|
+
providerId: connector.providerId,
|
|
2684
|
+
connectorId: connector.id,
|
|
2685
|
+
connectorTitle: connector.title,
|
|
2686
|
+
category: connector.category,
|
|
2687
|
+
action,
|
|
2688
|
+
risk: action.risk,
|
|
2689
|
+
dataClass: action.dataClass,
|
|
2690
|
+
requiredScopes: action.requiredScopes,
|
|
2691
|
+
inputSchema: action.inputSchema,
|
|
2692
|
+
outputSchema: action.outputSchema,
|
|
2693
|
+
tags
|
|
2694
|
+
});
|
|
2695
|
+
}
|
|
2696
|
+
}
|
|
2697
|
+
return tools;
|
|
2698
|
+
}
|
|
2699
|
+
function searchIntegrationTools(catalog, query, filters = {}) {
|
|
2700
|
+
const terms = tokenize(query);
|
|
2701
|
+
const filtered = catalog.filter((tool) => {
|
|
2702
|
+
if (filters.providerId && tool.providerId !== filters.providerId) return false;
|
|
2703
|
+
if (filters.connectorId && tool.connectorId !== filters.connectorId) return false;
|
|
2704
|
+
if (filters.category && tool.category !== filters.category) return false;
|
|
2705
|
+
if (filters.dataClass && tool.dataClass !== filters.dataClass) return false;
|
|
2706
|
+
if (filters.maxRisk && riskRank[tool.risk] > riskRank[filters.maxRisk]) return false;
|
|
2707
|
+
return true;
|
|
2708
|
+
});
|
|
2709
|
+
const scored = filtered.map((tool) => scoreTool(tool, terms));
|
|
2710
|
+
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);
|
|
2711
|
+
}
|
|
2712
|
+
function toMcpTools(tools) {
|
|
2713
|
+
return tools.map((tool) => ({
|
|
2714
|
+
name: tool.name,
|
|
2715
|
+
description: `${tool.title}. ${tool.description}`,
|
|
2716
|
+
inputSchema: tool.inputSchema ?? {
|
|
2717
|
+
type: "object",
|
|
2718
|
+
additionalProperties: true,
|
|
2719
|
+
properties: {}
|
|
2720
|
+
}
|
|
2721
|
+
}));
|
|
2722
|
+
}
|
|
2723
|
+
function scoreTool(tool, terms) {
|
|
2724
|
+
if (terms.length === 0) return { tool, score: 1, matched: [] };
|
|
2725
|
+
const haystack = new Set(tool.tags);
|
|
2726
|
+
const matched = [];
|
|
2727
|
+
let score = 0;
|
|
2728
|
+
for (const term of terms) {
|
|
2729
|
+
if (haystack.has(term)) {
|
|
2730
|
+
matched.push(term);
|
|
2731
|
+
score += 4;
|
|
2732
|
+
continue;
|
|
2733
|
+
}
|
|
2734
|
+
if (tool.tags.some((tag) => tag.includes(term))) {
|
|
2735
|
+
matched.push(term);
|
|
2736
|
+
score += 1;
|
|
2737
|
+
}
|
|
2738
|
+
}
|
|
2739
|
+
if (tool.risk === "read") score += 0.25;
|
|
2740
|
+
return { tool, score, matched: unique(matched) };
|
|
2741
|
+
}
|
|
2742
|
+
function tokenize(value) {
|
|
2743
|
+
return value.toLowerCase().split(/[^a-z0-9]+/g).map((part) => part.trim()).filter(Boolean);
|
|
2744
|
+
}
|
|
2745
|
+
function encodeToolPart(value) {
|
|
2746
|
+
return Buffer.from(value, "utf8").toString("base64url");
|
|
2747
|
+
}
|
|
2748
|
+
function decodeToolPart(value) {
|
|
2749
|
+
return Buffer.from(value, "base64url").toString("utf8");
|
|
2750
|
+
}
|
|
2751
|
+
function unique(values) {
|
|
2752
|
+
return [...new Set(values)];
|
|
2753
|
+
}
|
|
2754
|
+
|
|
2755
|
+
// src/policy.ts
|
|
2756
|
+
import { randomUUID } from "crypto";
|
|
2757
|
+
var StaticIntegrationPolicyEngine = class {
|
|
2758
|
+
rules;
|
|
2759
|
+
defaultReadEffect;
|
|
2760
|
+
defaultWriteEffect;
|
|
2761
|
+
defaultDestructiveEffect;
|
|
2762
|
+
now;
|
|
2763
|
+
constructor(options = {}) {
|
|
2764
|
+
this.rules = options.rules ?? [];
|
|
2765
|
+
this.defaultReadEffect = options.defaultReadEffect ?? "allow";
|
|
2766
|
+
this.defaultWriteEffect = options.defaultWriteEffect ?? "require_approval";
|
|
2767
|
+
this.defaultDestructiveEffect = options.defaultDestructiveEffect ?? "deny";
|
|
2768
|
+
this.now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
2769
|
+
}
|
|
2770
|
+
decide(ctx) {
|
|
2771
|
+
const action = ctx.action;
|
|
2772
|
+
if (!action) return { decision: "deny", reason: "Integration action is missing from connector catalog." };
|
|
2773
|
+
const matched = this.rules.find((rule) => ruleMatches(rule, ctx));
|
|
2774
|
+
const effect = matched?.effect ?? this.defaultEffect(action.risk);
|
|
2775
|
+
const reason = matched?.reason ?? defaultReason(effect, action.risk);
|
|
2776
|
+
if (effect === "allow") return { decision: "allow", reason, metadata: matched ? { ruleId: matched.id } : void 0 };
|
|
2777
|
+
if (effect === "deny") return { decision: "deny", reason, metadata: matched ? { ruleId: matched.id } : void 0 };
|
|
2778
|
+
return {
|
|
2779
|
+
decision: "require_approval",
|
|
2780
|
+
reason,
|
|
2781
|
+
approval: buildApprovalRequest(ctx, reason, this.now()),
|
|
2782
|
+
metadata: matched ? { ruleId: matched.id } : void 0
|
|
2783
|
+
};
|
|
2784
|
+
}
|
|
2785
|
+
defaultEffect(risk) {
|
|
2786
|
+
if (risk === "read") return this.defaultReadEffect;
|
|
2787
|
+
if (risk === "write") return this.defaultWriteEffect;
|
|
2788
|
+
return this.defaultDestructiveEffect;
|
|
2789
|
+
}
|
|
2790
|
+
};
|
|
2791
|
+
function createDefaultIntegrationPolicyEngine(options = {}) {
|
|
2792
|
+
return new StaticIntegrationPolicyEngine(options);
|
|
2793
|
+
}
|
|
2794
|
+
function buildApprovalRequest(ctx, reason, requestedAt) {
|
|
2795
|
+
if (!ctx.action) {
|
|
2796
|
+
throw new Error("Cannot build approval request without an action descriptor.");
|
|
2797
|
+
}
|
|
2798
|
+
return {
|
|
2799
|
+
id: `approval_${randomUUID()}`,
|
|
2800
|
+
connectionId: ctx.connection.id,
|
|
2801
|
+
providerId: ctx.connection.providerId,
|
|
2802
|
+
connectorId: ctx.connection.connectorId,
|
|
2803
|
+
action: ctx.request.action,
|
|
2804
|
+
actor: { type: ctx.subject.type, id: ctx.subject.id },
|
|
2805
|
+
risk: ctx.action.risk,
|
|
2806
|
+
dataClass: ctx.action.dataClass,
|
|
2807
|
+
reason,
|
|
2808
|
+
requestedAt: requestedAt.toISOString(),
|
|
2809
|
+
inputPreview: previewInput(ctx.request.input)
|
|
2810
|
+
};
|
|
2811
|
+
}
|
|
2812
|
+
function redactApprovalRequest(request) {
|
|
2813
|
+
return {
|
|
2814
|
+
...request,
|
|
2815
|
+
inputPreview: redactUnknown(request.inputPreview)
|
|
2816
|
+
};
|
|
2817
|
+
}
|
|
2818
|
+
function ruleMatches(rule, ctx) {
|
|
2819
|
+
if (!ctx.action) return false;
|
|
2820
|
+
if (rule.providerId && rule.providerId !== ctx.connection.providerId) return false;
|
|
2821
|
+
if (rule.connectorId && rule.connectorId !== ctx.connection.connectorId) return false;
|
|
2822
|
+
if (rule.action && rule.action !== ctx.request.action) return false;
|
|
2823
|
+
if (rule.risk && rule.risk !== ctx.action.risk) return false;
|
|
2824
|
+
if (rule.maxRisk && riskRank2(ctx.action.risk) > riskRank2(rule.maxRisk)) return false;
|
|
2825
|
+
if (rule.dataClass && rule.dataClass !== ctx.action.dataClass) return false;
|
|
2826
|
+
return true;
|
|
2827
|
+
}
|
|
2828
|
+
function riskRank2(risk) {
|
|
2829
|
+
if (risk === "read") return 0;
|
|
2830
|
+
if (risk === "write") return 1;
|
|
2831
|
+
return 2;
|
|
2832
|
+
}
|
|
2833
|
+
function defaultReason(effect, risk) {
|
|
2834
|
+
if (effect === "allow") return `${risk} integration action allowed by default policy.`;
|
|
2835
|
+
if (effect === "deny") return `${risk} integration action denied by default policy.`;
|
|
2836
|
+
return `${risk} integration action requires approval by default policy.`;
|
|
2837
|
+
}
|
|
2838
|
+
function previewInput(input) {
|
|
2839
|
+
return redactUnknown(input);
|
|
2840
|
+
}
|
|
2841
|
+
function redactUnknown(value) {
|
|
2842
|
+
if (Array.isArray(value)) return value.map(redactUnknown);
|
|
2843
|
+
if (!value || typeof value !== "object") return value;
|
|
2844
|
+
const out = {};
|
|
2845
|
+
for (const [key, child] of Object.entries(value)) {
|
|
2846
|
+
if (/token|secret|password|authorization|api[_-]?key|credential/i.test(key)) {
|
|
2847
|
+
out[key] = "[REDACTED]";
|
|
2848
|
+
} else {
|
|
2849
|
+
out[key] = redactUnknown(child);
|
|
2850
|
+
}
|
|
2851
|
+
}
|
|
2852
|
+
return out;
|
|
2853
|
+
}
|
|
2854
|
+
|
|
2855
|
+
// src/sandbox.ts
|
|
2856
|
+
function buildIntegrationInvocationEnvelope(input) {
|
|
2857
|
+
const parsed = parseIntegrationToolName(input.toolName);
|
|
2858
|
+
return {
|
|
2859
|
+
kind: "integration.invocation",
|
|
2860
|
+
capabilityToken: input.capabilityToken,
|
|
2861
|
+
toolName: input.toolName,
|
|
2862
|
+
action: parsed.actionId,
|
|
2863
|
+
input: input.args,
|
|
2864
|
+
idempotencyKey: input.idempotencyKey,
|
|
2865
|
+
dryRun: input.dryRun,
|
|
2866
|
+
metadata: input.metadata
|
|
2867
|
+
};
|
|
2868
|
+
}
|
|
2869
|
+
function invocationRequestFromEnvelope(envelope) {
|
|
2870
|
+
return {
|
|
2871
|
+
action: envelope.action,
|
|
2872
|
+
input: envelope.input,
|
|
2873
|
+
idempotencyKey: envelope.idempotencyKey,
|
|
2874
|
+
dryRun: envelope.dryRun,
|
|
2875
|
+
metadata: envelope.metadata
|
|
2876
|
+
};
|
|
2877
|
+
}
|
|
2878
|
+
function redactInvocationEnvelope(envelope) {
|
|
2879
|
+
return {
|
|
2880
|
+
...envelope,
|
|
2881
|
+
capabilityToken: "[REDACTED]",
|
|
2882
|
+
input: redactUnknown2(envelope.input)
|
|
2883
|
+
};
|
|
2884
|
+
}
|
|
2885
|
+
function redactCapability(capability) {
|
|
2886
|
+
return {
|
|
2887
|
+
...capability,
|
|
2888
|
+
metadata: redactUnknown2(capability.metadata)
|
|
2889
|
+
};
|
|
2890
|
+
}
|
|
2891
|
+
function normalizeIntegrationResult(result) {
|
|
2892
|
+
const output = result.output;
|
|
2893
|
+
if (!result.ok && output?.approvalRequired === true && output.approval) {
|
|
2894
|
+
return {
|
|
2895
|
+
status: "approval_required",
|
|
2896
|
+
action: result.action,
|
|
2897
|
+
approval: output.approval,
|
|
2898
|
+
metadata: result.metadata
|
|
2899
|
+
};
|
|
2900
|
+
}
|
|
2901
|
+
if (!result.ok) {
|
|
2902
|
+
return {
|
|
2903
|
+
status: "failed",
|
|
2904
|
+
action: result.action,
|
|
2905
|
+
error: String(result.output ?? result.warnings?.[0] ?? "integration action failed"),
|
|
2906
|
+
metadata: result.metadata
|
|
2907
|
+
};
|
|
2908
|
+
}
|
|
2909
|
+
return {
|
|
2910
|
+
status: "ok",
|
|
2911
|
+
action: result.action,
|
|
2912
|
+
output: result.output,
|
|
2913
|
+
metadata: result.metadata
|
|
2914
|
+
};
|
|
2915
|
+
}
|
|
2916
|
+
function redactUnknown2(value) {
|
|
2917
|
+
if (Array.isArray(value)) return value.map(redactUnknown2);
|
|
2918
|
+
if (!value || typeof value !== "object") return value;
|
|
2919
|
+
const out = {};
|
|
2920
|
+
for (const [key, child] of Object.entries(value)) {
|
|
2921
|
+
if (/token|secret|password|authorization|api[_-]?key|credential/i.test(key)) {
|
|
2922
|
+
out[key] = "[REDACTED]";
|
|
2923
|
+
} else {
|
|
2924
|
+
out[key] = redactUnknown2(child);
|
|
2925
|
+
}
|
|
2926
|
+
}
|
|
2927
|
+
return out;
|
|
2928
|
+
}
|
|
2929
|
+
|
|
2930
|
+
// src/adapter-provider.ts
|
|
2931
|
+
function createConnectorAdapterProvider(options) {
|
|
2932
|
+
const providerId = options.id ?? "first-party";
|
|
2933
|
+
const now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
2934
|
+
const adapters = /* @__PURE__ */ new Map();
|
|
2935
|
+
for (const adapter of options.adapters) {
|
|
2936
|
+
adapters.set(adapter.manifest.kind, adapter);
|
|
2937
|
+
}
|
|
2938
|
+
return {
|
|
2939
|
+
id: providerId,
|
|
2940
|
+
kind: options.kind ?? "first_party",
|
|
2941
|
+
listConnectors: () => [...adapters.values()].map((adapter) => manifestToConnector(providerId, adapter)),
|
|
2942
|
+
async invokeAction(connection, request) {
|
|
2943
|
+
const adapter = adapters.get(connection.connectorId);
|
|
2944
|
+
if (!adapter) {
|
|
2945
|
+
throw new IntegrationError(`Connector adapter ${connection.connectorId} not found.`, "connector_not_found");
|
|
2946
|
+
}
|
|
2947
|
+
const capability = adapter.manifest.capabilities.find((candidate) => candidate.name === request.action);
|
|
2948
|
+
if (!capability) {
|
|
2949
|
+
throw new IntegrationError(`Capability ${request.action} is not defined by ${connection.connectorId}.`, "action_not_found");
|
|
2950
|
+
}
|
|
2951
|
+
const source = await options.resolveDataSource(connection);
|
|
2952
|
+
const invocation = {
|
|
2953
|
+
source,
|
|
2954
|
+
capabilityName: request.action,
|
|
2955
|
+
args: toRecord(request.input),
|
|
2956
|
+
idempotencyKey: request.idempotencyKey ?? `idem_${connection.id}_${request.action}_${now().getTime()}`,
|
|
2957
|
+
expectedEtag: typeof request.metadata?.expectedEtag === "string" ? request.metadata.expectedEtag : void 0,
|
|
2958
|
+
callSessionId: typeof request.metadata?.callSessionId === "string" ? request.metadata.callSessionId : void 0
|
|
2959
|
+
};
|
|
2960
|
+
if (capability.class === "read") {
|
|
2961
|
+
if (!adapter.executeRead) {
|
|
2962
|
+
throw new IntegrationError(`Connector ${connection.connectorId} does not implement reads.`, "action_not_found");
|
|
2963
|
+
}
|
|
2964
|
+
const result = await adapter.executeRead(invocation);
|
|
2965
|
+
return readResultToAction(request, result);
|
|
2966
|
+
}
|
|
2967
|
+
if (capability.class === "mutation") {
|
|
2968
|
+
if (!adapter.executeMutation) {
|
|
2969
|
+
throw new IntegrationError(`Connector ${connection.connectorId} does not implement mutations.`, "action_not_found");
|
|
2970
|
+
}
|
|
2971
|
+
const result = await adapter.executeMutation(invocation);
|
|
2972
|
+
return mutationResultToAction(request, result);
|
|
2973
|
+
}
|
|
2974
|
+
throw new IntegrationError(`Capability ${request.action} is not invokable as an action.`, "action_not_found");
|
|
2975
|
+
}
|
|
2976
|
+
};
|
|
2977
|
+
}
|
|
2978
|
+
function manifestToConnector(providerId, adapter) {
|
|
2979
|
+
const manifest = adapter.manifest;
|
|
2980
|
+
return {
|
|
2981
|
+
id: manifest.kind,
|
|
2982
|
+
providerId,
|
|
2983
|
+
title: manifest.displayName,
|
|
2984
|
+
category: mapCategory(manifest.category),
|
|
2985
|
+
auth: mapAuth(manifest.auth.kind),
|
|
2986
|
+
scopes: manifest.auth.kind === "oauth2" ? manifest.auth.scopes : [],
|
|
2987
|
+
actions: manifest.capabilities.filter((capability) => capability.class === "read" || capability.class === "mutation").map((capability) => ({
|
|
2988
|
+
id: capability.name,
|
|
2989
|
+
title: titleFromName(capability.name),
|
|
2990
|
+
risk: capability.class === "read" ? "read" : capability.externalEffect ? "destructive" : "write",
|
|
2991
|
+
requiredScopes: capability.requiredScopes ?? [],
|
|
2992
|
+
dataClass: inferDataClass(manifest.category),
|
|
2993
|
+
description: capability.description,
|
|
2994
|
+
approvalRequired: capability.class === "mutation",
|
|
2995
|
+
inputSchema: capability.parameters
|
|
2996
|
+
}))
|
|
2997
|
+
};
|
|
2998
|
+
}
|
|
2999
|
+
function readResultToAction(request, result) {
|
|
3000
|
+
return {
|
|
3001
|
+
ok: true,
|
|
3002
|
+
action: request.action,
|
|
3003
|
+
output: result.data,
|
|
3004
|
+
metadata: {
|
|
3005
|
+
etag: result.etag,
|
|
3006
|
+
fetchedAt: result.fetchedAt
|
|
3007
|
+
}
|
|
3008
|
+
};
|
|
3009
|
+
}
|
|
3010
|
+
function mutationResultToAction(request, result) {
|
|
3011
|
+
if (result.status === "committed") {
|
|
3012
|
+
return {
|
|
3013
|
+
ok: true,
|
|
3014
|
+
action: request.action,
|
|
3015
|
+
output: result.data,
|
|
3016
|
+
metadata: {
|
|
3017
|
+
etagAfter: result.etagAfter,
|
|
3018
|
+
committedAt: result.committedAt,
|
|
3019
|
+
idempotentReplay: result.idempotentReplay
|
|
3020
|
+
}
|
|
3021
|
+
};
|
|
3022
|
+
}
|
|
3023
|
+
if (result.status === "conflict") {
|
|
3024
|
+
return {
|
|
3025
|
+
ok: false,
|
|
3026
|
+
action: request.action,
|
|
3027
|
+
output: {
|
|
3028
|
+
conflict: true,
|
|
3029
|
+
message: result.message,
|
|
3030
|
+
alternatives: result.alternatives,
|
|
3031
|
+
currentState: result.currentState
|
|
3032
|
+
}
|
|
3033
|
+
};
|
|
3034
|
+
}
|
|
3035
|
+
return {
|
|
3036
|
+
ok: false,
|
|
3037
|
+
action: request.action,
|
|
3038
|
+
output: {
|
|
3039
|
+
rateLimited: true,
|
|
3040
|
+
retryAfterMs: result.retryAfterMs,
|
|
3041
|
+
message: result.message
|
|
3042
|
+
}
|
|
3043
|
+
};
|
|
3044
|
+
}
|
|
3045
|
+
function mapAuth(kind) {
|
|
3046
|
+
if (kind === "oauth2") return "oauth2";
|
|
3047
|
+
if (kind === "api-key") return "api_key";
|
|
3048
|
+
if (kind === "none") return "none";
|
|
3049
|
+
return "custom";
|
|
3050
|
+
}
|
|
3051
|
+
function mapCategory(category) {
|
|
3052
|
+
if (category === "comms") return "chat";
|
|
3053
|
+
if (category === "spreadsheet") return "database";
|
|
3054
|
+
if (category === "doc") return "docs";
|
|
3055
|
+
if (category === "commerce") return "workflow";
|
|
3056
|
+
return category === "other" ? "other" : category;
|
|
3057
|
+
}
|
|
3058
|
+
function inferDataClass(category) {
|
|
3059
|
+
if (category === "commerce") return "sensitive";
|
|
3060
|
+
if (category === "webhook") return "internal";
|
|
3061
|
+
return "private";
|
|
3062
|
+
}
|
|
3063
|
+
function titleFromName(name) {
|
|
3064
|
+
return name.split(/[._-]/g).filter(Boolean).map((part) => part.slice(0, 1).toUpperCase() + part.slice(1)).join(" ");
|
|
3065
|
+
}
|
|
3066
|
+
function toRecord(input) {
|
|
3067
|
+
if (input && typeof input === "object" && !Array.isArray(input)) return input;
|
|
3068
|
+
return {};
|
|
3069
|
+
}
|
|
3070
|
+
|
|
2643
3071
|
// src/index.ts
|
|
2644
3072
|
var IntegrationError = class extends Error {
|
|
2645
3073
|
constructor(message, code) {
|
|
@@ -2671,6 +3099,7 @@ var IntegrationHub = class {
|
|
|
2671
3099
|
store;
|
|
2672
3100
|
capabilitySecret;
|
|
2673
3101
|
guard;
|
|
3102
|
+
policy;
|
|
2674
3103
|
now;
|
|
2675
3104
|
constructor(options) {
|
|
2676
3105
|
if (!options.capabilitySecret) {
|
|
@@ -2680,6 +3109,7 @@ var IntegrationHub = class {
|
|
|
2680
3109
|
this.store = options.store;
|
|
2681
3110
|
this.capabilitySecret = options.capabilitySecret;
|
|
2682
3111
|
this.guard = options.guard;
|
|
3112
|
+
this.policy = options.policy;
|
|
2683
3113
|
this.now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
2684
3114
|
}
|
|
2685
3115
|
async listConnectors() {
|
|
@@ -2709,11 +3139,11 @@ var IntegrationHub = class {
|
|
|
2709
3139
|
assertScopes(connection, request.scopes);
|
|
2710
3140
|
const now = this.now();
|
|
2711
3141
|
const capability = {
|
|
2712
|
-
id: `cap_${
|
|
3142
|
+
id: `cap_${randomUUID2()}`,
|
|
2713
3143
|
subject: request.subject,
|
|
2714
3144
|
connectionId: request.connectionId,
|
|
2715
|
-
scopes:
|
|
2716
|
-
allowedActions:
|
|
3145
|
+
scopes: unique2(request.scopes),
|
|
3146
|
+
allowedActions: unique2(request.allowedActions),
|
|
2717
3147
|
issuedAt: now.toISOString(),
|
|
2718
3148
|
expiresAt: new Date(now.getTime() + request.ttlMs).toISOString(),
|
|
2719
3149
|
metadata: request.metadata
|
|
@@ -2741,6 +3171,25 @@ var IntegrationHub = class {
|
|
|
2741
3171
|
assertScopes(connection, action.requiredScopes);
|
|
2742
3172
|
assertScopes({ ...connection, grantedScopes: capability.scopes }, action.requiredScopes);
|
|
2743
3173
|
const fullRequest = { ...request, connectionId: connection.id };
|
|
3174
|
+
if (this.policy) {
|
|
3175
|
+
const decision = await this.policy.decide({
|
|
3176
|
+
connection,
|
|
3177
|
+
request: fullRequest,
|
|
3178
|
+
action,
|
|
3179
|
+
subject: capability.subject
|
|
3180
|
+
});
|
|
3181
|
+
if (decision.decision === "deny") {
|
|
3182
|
+
throw new IntegrationError(decision.reason, "policy_denied");
|
|
3183
|
+
}
|
|
3184
|
+
if (decision.decision === "require_approval") {
|
|
3185
|
+
return {
|
|
3186
|
+
ok: false,
|
|
3187
|
+
action: request.action,
|
|
3188
|
+
output: { approvalRequired: true, approval: decision.approval },
|
|
3189
|
+
metadata: { policyDecision: decision.decision, reason: decision.reason, ...decision.metadata }
|
|
3190
|
+
};
|
|
3191
|
+
}
|
|
3192
|
+
}
|
|
2744
3193
|
const proceed = () => Promise.resolve(provider.invokeAction(connection, fullRequest));
|
|
2745
3194
|
if (this.guard) {
|
|
2746
3195
|
return this.guard.invokeAction({ connection, request: fullRequest, action }, proceed);
|
|
@@ -2940,7 +3389,7 @@ function base64UrlEncode(value) {
|
|
|
2940
3389
|
function base64UrlDecode(value) {
|
|
2941
3390
|
return Buffer.from(value, "base64url").toString("utf8");
|
|
2942
3391
|
}
|
|
2943
|
-
function
|
|
3392
|
+
function unique2(values) {
|
|
2944
3393
|
return [...new Set(values)];
|
|
2945
3394
|
}
|
|
2946
3395
|
export {
|
|
@@ -2951,9 +3400,15 @@ export {
|
|
|
2951
3400
|
IntegrationError,
|
|
2952
3401
|
IntegrationHub,
|
|
2953
3402
|
ResourceContention,
|
|
3403
|
+
StaticIntegrationPolicyEngine,
|
|
2954
3404
|
_resetPendingFlowsForTests,
|
|
2955
3405
|
assertValidConnectorManifest,
|
|
3406
|
+
buildApprovalRequest,
|
|
3407
|
+
buildIntegrationInvocationEnvelope,
|
|
3408
|
+
buildIntegrationToolCatalog,
|
|
2956
3409
|
consumePendingFlow,
|
|
3410
|
+
createConnectorAdapterProvider,
|
|
3411
|
+
createDefaultIntegrationPolicyEngine,
|
|
2957
3412
|
createHttpIntegrationProvider,
|
|
2958
3413
|
createMockIntegrationProvider,
|
|
2959
3414
|
exchangeAuthorizationCode,
|
|
@@ -2961,17 +3416,27 @@ export {
|
|
|
2961
3416
|
googleCalendar,
|
|
2962
3417
|
googleSheets,
|
|
2963
3418
|
hubspot,
|
|
3419
|
+
integrationToolName,
|
|
3420
|
+
invocationRequestFromEnvelope,
|
|
3421
|
+
manifestToConnector,
|
|
2964
3422
|
microsoftCalendar,
|
|
3423
|
+
normalizeIntegrationResult,
|
|
2965
3424
|
notionDatabase,
|
|
3425
|
+
parseIntegrationToolName,
|
|
2966
3426
|
parseStripeSignatureHeader,
|
|
3427
|
+
redactApprovalRequest,
|
|
3428
|
+
redactCapability,
|
|
3429
|
+
redactInvocationEnvelope,
|
|
2967
3430
|
refreshAccessToken,
|
|
2968
3431
|
sanitizeConnection,
|
|
3432
|
+
searchIntegrationTools,
|
|
2969
3433
|
signCapability,
|
|
2970
3434
|
slack,
|
|
2971
3435
|
slackEventsConnector,
|
|
2972
3436
|
startOAuthFlow,
|
|
2973
3437
|
stripePackConnector,
|
|
2974
3438
|
stripeWebhookReceiverConnector,
|
|
3439
|
+
toMcpTools,
|
|
2975
3440
|
twilioSmsConnector,
|
|
2976
3441
|
validateConnectorManifest,
|
|
2977
3442
|
verifyCapabilityToken,
|