@tangle-network/agent-integrations 0.7.0 → 0.8.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 +151 -161
- package/dist/index.d.ts +184 -1
- package/dist/index.js +591 -0
- package/dist/index.js.map +1 -1
- package/docs/integration-coverage-checklist.md +2 -1
- package/docs/provider-decision-matrix.md +1 -3
- package/examples/basic-hub.ts +47 -0
- package/examples/declarative-rest.ts +27 -0
- package/examples/first-party-adapter.ts +32 -0
- package/package.json +2 -1
- package/docs/execution-layer-launch-plan.md +0 -222
package/dist/index.js
CHANGED
|
@@ -4081,6 +4081,581 @@ function objectSchema() {
|
|
|
4081
4081
|
return { type: "object", additionalProperties: true, properties: {} };
|
|
4082
4082
|
}
|
|
4083
4083
|
|
|
4084
|
+
// src/specs/types.ts
|
|
4085
|
+
function specAuthToConnectorAuth(auth) {
|
|
4086
|
+
if (auth.mode === "api_key") return "api_key";
|
|
4087
|
+
if (auth.mode === "oauth2") return "oauth2";
|
|
4088
|
+
if (auth.mode === "none") return "none";
|
|
4089
|
+
return "custom";
|
|
4090
|
+
}
|
|
4091
|
+
|
|
4092
|
+
// src/specs/families.ts
|
|
4093
|
+
var INTEGRATION_FAMILIES = {
|
|
4094
|
+
google: {
|
|
4095
|
+
id: "google",
|
|
4096
|
+
title: "Google OAuth",
|
|
4097
|
+
authMode: "oauth2",
|
|
4098
|
+
consoleUrl: "https://console.cloud.google.com/apis/credentials",
|
|
4099
|
+
authorizationUrl: "https://accounts.google.com/o/oauth2/v2/auth",
|
|
4100
|
+
tokenUrl: "https://oauth2.googleapis.com/token",
|
|
4101
|
+
redirectUriTemplate: "https://{host}/api/integrations/oauth/google/callback",
|
|
4102
|
+
credentialFields: [
|
|
4103
|
+
{ label: "Client ID", env: "GOOGLE_OAUTH_CLIENT_ID", description: "Google OAuth client ID.", example: "1234567890-abc.apps.googleusercontent.com", regex: "^[0-9]+-[a-zA-Z0-9_-]+\\.apps\\.googleusercontent\\.com$", secret: false },
|
|
4104
|
+
{ label: "Client Secret", env: "GOOGLE_OAUTH_CLIENT_SECRET", description: "Google OAuth client secret.", example: "GOCSPX-...", secret: true }
|
|
4105
|
+
],
|
|
4106
|
+
consoleSteps: [
|
|
4107
|
+
{ id: "project", title: "Select project", detail: "Open Google Cloud Console and select the project that owns the OAuth client." },
|
|
4108
|
+
{ id: "consent", title: "Configure consent screen", detail: "Configure OAuth consent, app name, support email, and publishing status appropriate for the deployment." },
|
|
4109
|
+
{ id: "client", title: "Create web client", detail: "Create an OAuth client of type Web application." },
|
|
4110
|
+
{ id: "redirect", title: "Add redirect URI", detail: "Add {redirectUri} as an authorized redirect URI.", copyValue: "{redirectUri}" },
|
|
4111
|
+
{ id: "scopes", title: "Add scopes", detail: "Add the provider scopes listed in this spec." }
|
|
4112
|
+
],
|
|
4113
|
+
knownQuirks: [
|
|
4114
|
+
{ id: "offline-access", severity: "warning", message: "Use access_type=offline and prompt=consent when refresh tokens are required." },
|
|
4115
|
+
{ id: "verification", severity: "warning", message: "Sensitive or restricted scopes may require Google verification before broad external use." }
|
|
4116
|
+
],
|
|
4117
|
+
lifecycle: { supportsRefresh: true, supportsRevoke: true, supportsIncrementalAuth: true, recommendedHealthcheckIntervalHours: 24 }
|
|
4118
|
+
},
|
|
4119
|
+
"microsoft-graph": {
|
|
4120
|
+
id: "microsoft-graph",
|
|
4121
|
+
title: "Microsoft Graph OAuth",
|
|
4122
|
+
authMode: "oauth2",
|
|
4123
|
+
consoleUrl: "https://entra.microsoft.com/#view/Microsoft_AAD_RegisteredApps/ApplicationsListBlade",
|
|
4124
|
+
authorizationUrl: "https://login.microsoftonline.com/common/oauth2/v2.0/authorize",
|
|
4125
|
+
tokenUrl: "https://login.microsoftonline.com/common/oauth2/v2.0/token",
|
|
4126
|
+
redirectUriTemplate: "https://{host}/api/integrations/oauth/microsoft/callback",
|
|
4127
|
+
credentialFields: [
|
|
4128
|
+
{ label: "Client ID", env: "MS_OAUTH_CLIENT_ID", description: "Microsoft Entra application client ID.", example: "00000000-0000-0000-0000-000000000000", regex: "^[0-9a-fA-F-]{36}$", secret: false },
|
|
4129
|
+
{ label: "Client Secret", env: "MS_OAUTH_CLIENT_SECRET", description: "Microsoft Entra client secret value.", secret: true }
|
|
4130
|
+
],
|
|
4131
|
+
consoleSteps: [
|
|
4132
|
+
{ id: "app", title: "Register app", detail: "Create or open an app registration in Microsoft Entra." },
|
|
4133
|
+
{ id: "redirect", title: "Add redirect URI", detail: "Add {redirectUri} as a Web redirect URI.", copyValue: "{redirectUri}" },
|
|
4134
|
+
{ id: "secret", title: "Create secret", detail: "Create a client secret and store the secret value, not the secret ID." },
|
|
4135
|
+
{ id: "permissions", title: "Add Graph permissions", detail: "Add the delegated Graph scopes listed in this spec and grant admin consent where required." }
|
|
4136
|
+
],
|
|
4137
|
+
knownQuirks: [
|
|
4138
|
+
{ id: "tenant-common", severity: "info", message: "The common tenant supports multi-tenant OAuth; single-tenant deployments should override the tenant segment." },
|
|
4139
|
+
{ id: "admin-consent", severity: "warning", message: "Some Graph scopes require tenant admin consent." }
|
|
4140
|
+
],
|
|
4141
|
+
lifecycle: { supportsRefresh: true, supportsRevoke: true, supportsIncrementalAuth: true, recommendedHealthcheckIntervalHours: 24 }
|
|
4142
|
+
},
|
|
4143
|
+
atlassian: {
|
|
4144
|
+
id: "atlassian",
|
|
4145
|
+
title: "Atlassian OAuth",
|
|
4146
|
+
authMode: "oauth2",
|
|
4147
|
+
consoleUrl: "https://developer.atlassian.com/console/myapps/",
|
|
4148
|
+
authorizationUrl: "https://auth.atlassian.com/authorize",
|
|
4149
|
+
tokenUrl: "https://auth.atlassian.com/oauth/token",
|
|
4150
|
+
redirectUriTemplate: "https://{host}/api/integrations/oauth/atlassian/callback",
|
|
4151
|
+
credentialFields: [
|
|
4152
|
+
{ label: "Client ID", description: "Atlassian OAuth client ID.", secret: false },
|
|
4153
|
+
{ label: "Client Secret", description: "Atlassian OAuth client secret.", secret: true }
|
|
4154
|
+
],
|
|
4155
|
+
consoleSteps: [
|
|
4156
|
+
{ id: "app", title: "Create OAuth app", detail: "Create an OAuth 2.0 app in the Atlassian developer console." },
|
|
4157
|
+
{ id: "redirect", title: "Add callback URL", detail: "Add {redirectUri} as the callback URL.", copyValue: "{redirectUri}" },
|
|
4158
|
+
{ id: "apis", title: "Enable APIs", detail: "Enable the Jira or Confluence APIs required by this connector." }
|
|
4159
|
+
],
|
|
4160
|
+
lifecycle: { supportsRefresh: true, supportsRevoke: false, supportsIncrementalAuth: false, recommendedHealthcheckIntervalHours: 24 }
|
|
4161
|
+
},
|
|
4162
|
+
salesforce: {
|
|
4163
|
+
id: "salesforce",
|
|
4164
|
+
title: "Salesforce OAuth",
|
|
4165
|
+
authMode: "oauth2",
|
|
4166
|
+
consoleUrl: "https://login.salesforce.com",
|
|
4167
|
+
authorizationUrl: "https://login.salesforce.com/services/oauth2/authorize",
|
|
4168
|
+
tokenUrl: "https://login.salesforce.com/services/oauth2/token",
|
|
4169
|
+
redirectUriTemplate: "https://{host}/api/integrations/oauth/salesforce/callback",
|
|
4170
|
+
credentialFields: [
|
|
4171
|
+
{ label: "Client ID", env: "SALESFORCE_OAUTH_CLIENT_ID", description: "Salesforce connected app consumer key.", secret: false },
|
|
4172
|
+
{ label: "Client Secret", env: "SALESFORCE_OAUTH_CLIENT_SECRET", description: "Salesforce connected app consumer secret.", secret: true }
|
|
4173
|
+
],
|
|
4174
|
+
consoleSteps: [
|
|
4175
|
+
{ id: "connected-app", title: "Create connected app", detail: "Create a Salesforce connected app with OAuth enabled." },
|
|
4176
|
+
{ id: "callback", title: "Add callback URL", detail: "Add {redirectUri} as the callback URL.", copyValue: "{redirectUri}" },
|
|
4177
|
+
{ id: "scopes", title: "Select scopes", detail: "Add api and refresh_token/offline_access, plus any connector-specific scopes." }
|
|
4178
|
+
],
|
|
4179
|
+
knownQuirks: [
|
|
4180
|
+
{ id: "instance-url", severity: "critical", message: "Runtime calls must use the instance_url returned by the token response." }
|
|
4181
|
+
],
|
|
4182
|
+
lifecycle: { supportsRefresh: true, supportsRevoke: true, supportsIncrementalAuth: false, recommendedHealthcheckIntervalHours: 24 }
|
|
4183
|
+
},
|
|
4184
|
+
hubspot: {
|
|
4185
|
+
id: "hubspot",
|
|
4186
|
+
title: "HubSpot OAuth",
|
|
4187
|
+
authMode: "oauth2",
|
|
4188
|
+
consoleUrl: "https://developers.hubspot.com/",
|
|
4189
|
+
authorizationUrl: "https://app.hubspot.com/oauth/authorize",
|
|
4190
|
+
tokenUrl: "https://api.hubapi.com/oauth/v1/token",
|
|
4191
|
+
redirectUriTemplate: "https://{host}/api/integrations/oauth/hubspot/callback",
|
|
4192
|
+
credentialFields: [
|
|
4193
|
+
{ label: "Client ID", env: "HUBSPOT_OAUTH_CLIENT_ID", description: "HubSpot app client ID.", secret: false },
|
|
4194
|
+
{ label: "Client Secret", env: "HUBSPOT_OAUTH_CLIENT_SECRET", description: "HubSpot app client secret.", secret: true }
|
|
4195
|
+
],
|
|
4196
|
+
consoleSteps: [
|
|
4197
|
+
{ id: "app", title: "Create private/public app", detail: "Create a HubSpot app and configure OAuth." },
|
|
4198
|
+
{ id: "redirect", title: "Add redirect URL", detail: "Add {redirectUri} to the app redirect URLs.", copyValue: "{redirectUri}" },
|
|
4199
|
+
{ id: "scopes", title: "Add CRM scopes", detail: "Add the CRM object scopes listed in this spec." }
|
|
4200
|
+
],
|
|
4201
|
+
lifecycle: { supportsRefresh: true, supportsRevoke: true, supportsIncrementalAuth: false, recommendedHealthcheckIntervalHours: 24 }
|
|
4202
|
+
},
|
|
4203
|
+
slack: {
|
|
4204
|
+
id: "slack",
|
|
4205
|
+
title: "Slack OAuth",
|
|
4206
|
+
authMode: "oauth2",
|
|
4207
|
+
consoleUrl: "https://api.slack.com/apps",
|
|
4208
|
+
authorizationUrl: "https://slack.com/oauth/v2/authorize",
|
|
4209
|
+
tokenUrl: "https://slack.com/api/oauth.v2.access",
|
|
4210
|
+
redirectUriTemplate: "https://{host}/api/integrations/oauth/slack/callback",
|
|
4211
|
+
credentialFields: [
|
|
4212
|
+
{ label: "Client ID", env: "SLACK_OAUTH_CLIENT_ID", description: "Slack app client ID.", secret: false },
|
|
4213
|
+
{ label: "Client Secret", env: "SLACK_OAUTH_CLIENT_SECRET", description: "Slack app client secret.", secret: true }
|
|
4214
|
+
],
|
|
4215
|
+
consoleSteps: [
|
|
4216
|
+
{ id: "app", title: "Create Slack app", detail: "Create or open a Slack app." },
|
|
4217
|
+
{ id: "redirect", title: "Add redirect URL", detail: "Add {redirectUri} under OAuth & Permissions.", copyValue: "{redirectUri}" },
|
|
4218
|
+
{ id: "scopes", title: "Add bot scopes", detail: "Add the bot token scopes listed in this spec and reinstall the app." }
|
|
4219
|
+
],
|
|
4220
|
+
knownQuirks: [
|
|
4221
|
+
{ id: "bot-token", severity: "info", message: "Slack usually returns a bot access token; refresh tokens require token rotation." }
|
|
4222
|
+
],
|
|
4223
|
+
lifecycle: { supportsRefresh: false, supportsRevoke: true, supportsIncrementalAuth: false, recommendedHealthcheckIntervalHours: 24 }
|
|
4224
|
+
},
|
|
4225
|
+
notion: {
|
|
4226
|
+
id: "notion",
|
|
4227
|
+
title: "Notion OAuth",
|
|
4228
|
+
authMode: "oauth2",
|
|
4229
|
+
consoleUrl: "https://www.notion.so/my-integrations",
|
|
4230
|
+
authorizationUrl: "https://api.notion.com/v1/oauth/authorize",
|
|
4231
|
+
tokenUrl: "https://api.notion.com/v1/oauth/token",
|
|
4232
|
+
redirectUriTemplate: "https://{host}/api/integrations/oauth/notion/callback",
|
|
4233
|
+
credentialFields: [
|
|
4234
|
+
{ label: "Client ID", env: "NOTION_OAUTH_CLIENT_ID", description: "Notion integration OAuth client ID.", secret: false },
|
|
4235
|
+
{ label: "Client Secret", env: "NOTION_OAUTH_CLIENT_SECRET", description: "Notion integration OAuth client secret.", secret: true }
|
|
4236
|
+
],
|
|
4237
|
+
consoleSteps: [
|
|
4238
|
+
{ id: "integration", title: "Create integration", detail: "Create a Notion public integration." },
|
|
4239
|
+
{ id: "redirect", title: "Add redirect URI", detail: "Add {redirectUri} as the redirect URI.", copyValue: "{redirectUri}" },
|
|
4240
|
+
{ id: "capabilities", title: "Select capabilities", detail: "Enable read/update/insert capabilities matching this connector." }
|
|
4241
|
+
],
|
|
4242
|
+
lifecycle: { supportsRefresh: true, supportsRevoke: true, supportsIncrementalAuth: false, recommendedHealthcheckIntervalHours: 24 }
|
|
4243
|
+
},
|
|
4244
|
+
"standard-oauth2": {
|
|
4245
|
+
id: "standard-oauth2",
|
|
4246
|
+
title: "Standard OAuth 2.0",
|
|
4247
|
+
authMode: "oauth2",
|
|
4248
|
+
redirectUriTemplate: "https://{host}/api/integrations/oauth/{kind}/callback",
|
|
4249
|
+
credentialFields: [
|
|
4250
|
+
{ label: "Client ID", description: "OAuth client ID.", secret: false },
|
|
4251
|
+
{ label: "Client Secret", description: "OAuth client secret.", secret: true }
|
|
4252
|
+
],
|
|
4253
|
+
consoleSteps: [
|
|
4254
|
+
{ id: "app", title: "Create OAuth app", detail: "Create an OAuth app in the provider console." },
|
|
4255
|
+
{ id: "redirect", title: "Add redirect URI", detail: "Add {redirectUri} as an allowed redirect URI.", copyValue: "{redirectUri}" },
|
|
4256
|
+
{ id: "scopes", title: "Add scopes", detail: "Add the scopes listed in this spec." }
|
|
4257
|
+
],
|
|
4258
|
+
lifecycle: { supportsRefresh: true, supportsRevoke: false, supportsIncrementalAuth: false, recommendedHealthcheckIntervalHours: 24 }
|
|
4259
|
+
},
|
|
4260
|
+
"api-key": {
|
|
4261
|
+
id: "api-key",
|
|
4262
|
+
title: "API key",
|
|
4263
|
+
authMode: "api_key",
|
|
4264
|
+
credentialFields: [
|
|
4265
|
+
{ label: "API Key", description: "Provider API key or token.", example: "sk_...", secret: true }
|
|
4266
|
+
],
|
|
4267
|
+
consoleSteps: [
|
|
4268
|
+
{ id: "token", title: "Create token", detail: "Create an API key/token in the provider console with the minimum required permissions." }
|
|
4269
|
+
],
|
|
4270
|
+
lifecycle: { supportsRefresh: false, supportsRevoke: true, supportsIncrementalAuth: false, recommendedHealthcheckIntervalHours: 24 }
|
|
4271
|
+
},
|
|
4272
|
+
hmac: {
|
|
4273
|
+
id: "hmac",
|
|
4274
|
+
title: "HMAC secret",
|
|
4275
|
+
authMode: "hmac",
|
|
4276
|
+
credentialFields: [
|
|
4277
|
+
{ label: "Signing Secret", description: "Webhook signing secret.", secret: true }
|
|
4278
|
+
],
|
|
4279
|
+
consoleSteps: [
|
|
4280
|
+
{ id: "secret", title: "Configure signing secret", detail: "Configure the shared signing secret in the sender and receiver." }
|
|
4281
|
+
],
|
|
4282
|
+
lifecycle: { supportsRefresh: false, supportsRevoke: true, supportsIncrementalAuth: false, recommendedHealthcheckIntervalHours: 24 }
|
|
4283
|
+
},
|
|
4284
|
+
none: {
|
|
4285
|
+
id: "none",
|
|
4286
|
+
title: "No authentication",
|
|
4287
|
+
authMode: "none",
|
|
4288
|
+
credentialFields: [],
|
|
4289
|
+
consoleSteps: [
|
|
4290
|
+
{ id: "configure", title: "Configure endpoint", detail: "No provider credentials are required." }
|
|
4291
|
+
],
|
|
4292
|
+
lifecycle: { supportsRefresh: false, supportsRevoke: false, supportsIncrementalAuth: false }
|
|
4293
|
+
}
|
|
4294
|
+
};
|
|
4295
|
+
function getIntegrationFamily(id) {
|
|
4296
|
+
return INTEGRATION_FAMILIES[id];
|
|
4297
|
+
}
|
|
4298
|
+
|
|
4299
|
+
// src/specs/registry.ts
|
|
4300
|
+
var EXECUTABLE_KINDS = /* @__PURE__ */ new Set([
|
|
4301
|
+
"google-calendar",
|
|
4302
|
+
"google-sheets",
|
|
4303
|
+
"outlook-calendar",
|
|
4304
|
+
"microsoft-calendar",
|
|
4305
|
+
"slack",
|
|
4306
|
+
"hubspot",
|
|
4307
|
+
"notion",
|
|
4308
|
+
"notion-database",
|
|
4309
|
+
"salesforce",
|
|
4310
|
+
"github",
|
|
4311
|
+
"gitlab",
|
|
4312
|
+
"airtable",
|
|
4313
|
+
"asana",
|
|
4314
|
+
"stripe",
|
|
4315
|
+
"stripe-pack",
|
|
4316
|
+
"twilio",
|
|
4317
|
+
"twilio-sms",
|
|
4318
|
+
"webhook"
|
|
4319
|
+
]);
|
|
4320
|
+
var KIND_ALIASES = {
|
|
4321
|
+
"outlook-calendar": "microsoft-calendar",
|
|
4322
|
+
notion: "notion-database",
|
|
4323
|
+
stripe: "stripe-pack",
|
|
4324
|
+
twilio: "twilio-sms"
|
|
4325
|
+
};
|
|
4326
|
+
function listIntegrationSpecs() {
|
|
4327
|
+
const connectors = new Map(buildIntegrationCoverageConnectors({ providerId: "spec" }).map((c) => [c.id, c]));
|
|
4328
|
+
return listIntegrationCoverageSpecs().map((coverage) => {
|
|
4329
|
+
const connector = connectors.get(coverage.id);
|
|
4330
|
+
if (!connector) throw new Error(`missing coverage connector for ${coverage.id}`);
|
|
4331
|
+
return specFromCoverage(coverage, connector);
|
|
4332
|
+
});
|
|
4333
|
+
}
|
|
4334
|
+
function getIntegrationSpec(kind) {
|
|
4335
|
+
const canonical = KIND_ALIASES[kind] ?? kind;
|
|
4336
|
+
return listIntegrationSpecs().find((spec) => spec.kind === canonical || KIND_ALIASES[spec.kind] === canonical);
|
|
4337
|
+
}
|
|
4338
|
+
function listExecutableIntegrationSpecs() {
|
|
4339
|
+
return listIntegrationSpecs().filter((spec) => spec.status === "executable");
|
|
4340
|
+
}
|
|
4341
|
+
function integrationSpecToConnector(spec, providerId = "spec") {
|
|
4342
|
+
return {
|
|
4343
|
+
id: spec.kind,
|
|
4344
|
+
providerId,
|
|
4345
|
+
title: spec.title,
|
|
4346
|
+
category: spec.category,
|
|
4347
|
+
auth: spec.auth.mode === "api_key" ? "api_key" : spec.auth.mode === "oauth2" ? "oauth2" : spec.auth.mode === "none" ? "none" : "custom",
|
|
4348
|
+
scopes: spec.permissions.flatMap((permission) => permission.providerScopes),
|
|
4349
|
+
actions: spec.actions,
|
|
4350
|
+
triggers: spec.triggers,
|
|
4351
|
+
metadata: {
|
|
4352
|
+
...spec.metadata ?? {},
|
|
4353
|
+
source: "integration-spec",
|
|
4354
|
+
status: spec.status,
|
|
4355
|
+
family: spec.family,
|
|
4356
|
+
plannerHints: spec.plannerHints
|
|
4357
|
+
}
|
|
4358
|
+
};
|
|
4359
|
+
}
|
|
4360
|
+
function specFromCoverage(coverage, connector) {
|
|
4361
|
+
const kind = KIND_ALIASES[coverage.id] ?? coverage.id;
|
|
4362
|
+
const family = familyFor(coverage);
|
|
4363
|
+
const familySpec = getIntegrationFamily(family);
|
|
4364
|
+
const permissions = permissionsFor(coverage, connector.actions);
|
|
4365
|
+
const auth = authFor(coverage, family, permissions);
|
|
4366
|
+
const status = statusFor(kind);
|
|
4367
|
+
return {
|
|
4368
|
+
kind,
|
|
4369
|
+
title: connector.title,
|
|
4370
|
+
category: connector.category,
|
|
4371
|
+
status,
|
|
4372
|
+
family,
|
|
4373
|
+
auth,
|
|
4374
|
+
permissions,
|
|
4375
|
+
actions: connector.actions,
|
|
4376
|
+
triggers: connector.triggers,
|
|
4377
|
+
setup: {
|
|
4378
|
+
consoleUrl: familySpec.consoleUrl,
|
|
4379
|
+
consoleSteps: familySpec.consoleSteps,
|
|
4380
|
+
credentialFields: credentialFieldsFor(auth),
|
|
4381
|
+
redirectUriTemplate: auth.mode === "oauth2" ? auth.redirectUriTemplate : familySpec.redirectUriTemplate,
|
|
4382
|
+
knownQuirks: familySpec.knownQuirks,
|
|
4383
|
+
healthcheck: healthcheckFor(kind, status, auth)
|
|
4384
|
+
},
|
|
4385
|
+
lifecycle: familySpec.lifecycle,
|
|
4386
|
+
plannerHints: plannerHintsFor(coverage, connector.actions),
|
|
4387
|
+
metadata: { priority: coverage.priority, domains: coverage.domains }
|
|
4388
|
+
};
|
|
4389
|
+
}
|
|
4390
|
+
function familyFor(spec) {
|
|
4391
|
+
if (hmacKinds.has(spec.id)) return "hmac";
|
|
4392
|
+
if (spec.auth === "none") return "none";
|
|
4393
|
+
if (spec.id.startsWith("google-") || spec.domains.includes("google")) return "google";
|
|
4394
|
+
if (spec.id.startsWith("microsoft-") || ["outlook-mail", "outlook-calendar", "onedrive", "sharepoint"].includes(spec.id)) return "microsoft-graph";
|
|
4395
|
+
if (["jira", "confluence", "trello", "bitbucket"].includes(spec.id)) return "atlassian";
|
|
4396
|
+
if (spec.id === "salesforce") return "salesforce";
|
|
4397
|
+
if (spec.id === "hubspot") return "hubspot";
|
|
4398
|
+
if (spec.id === "slack") return "slack";
|
|
4399
|
+
if (spec.id === "notion") return "notion";
|
|
4400
|
+
if (apiKeyKinds.has(spec.id)) return "api-key";
|
|
4401
|
+
return "standard-oauth2";
|
|
4402
|
+
}
|
|
4403
|
+
var apiKeyKinds = /* @__PURE__ */ new Set(["github", "gitlab", "airtable", "asana", "stripe", "twilio", "sendgrid", "postmark"]);
|
|
4404
|
+
var hmacKinds = /* @__PURE__ */ new Set(["webhook"]);
|
|
4405
|
+
function authFor(spec, family, permissions) {
|
|
4406
|
+
const f = INTEGRATION_FAMILIES[family];
|
|
4407
|
+
if (family === "none") return { mode: "none" };
|
|
4408
|
+
if (family === "hmac") {
|
|
4409
|
+
return { mode: "hmac", credential: f.credentialFields[0], signatureHeader: `${spec.id}-signature` };
|
|
4410
|
+
}
|
|
4411
|
+
if (family === "api-key") {
|
|
4412
|
+
return { mode: "api_key", credential: apiKeyFieldFor(spec.id), placement: apiKeyPlacementFor(spec.id) };
|
|
4413
|
+
}
|
|
4414
|
+
const scopes = permissions.flatMap(
|
|
4415
|
+
(permission) => permission.providerScopes.map((providerScope) => ({
|
|
4416
|
+
normalized: permission.normalized,
|
|
4417
|
+
providerScope,
|
|
4418
|
+
title: permission.title,
|
|
4419
|
+
reason: permission.reason,
|
|
4420
|
+
risk: permission.risk,
|
|
4421
|
+
dataClass: permission.dataClass
|
|
4422
|
+
}))
|
|
4423
|
+
);
|
|
4424
|
+
return {
|
|
4425
|
+
mode: "oauth2",
|
|
4426
|
+
authorizationUrl: f.authorizationUrl ?? `https://example.invalid/${spec.id}/authorize`,
|
|
4427
|
+
tokenUrl: f.tokenUrl ?? `https://example.invalid/${spec.id}/token`,
|
|
4428
|
+
clientIdEnv: f.credentialFields.find((field) => !field.secret)?.env,
|
|
4429
|
+
clientSecretEnv: f.credentialFields.find((field) => field.secret)?.env,
|
|
4430
|
+
scopes,
|
|
4431
|
+
extraAuthParams: extraAuthParamsFor(family),
|
|
4432
|
+
redirectUriTemplate: (f.redirectUriTemplate ?? "https://{host}/api/integrations/oauth/{kind}/callback").replace("{kind}", spec.id),
|
|
4433
|
+
pkce: family === "google" || family === "microsoft-graph" ? "supported" : "unsupported"
|
|
4434
|
+
};
|
|
4435
|
+
}
|
|
4436
|
+
function credentialFieldsFor(auth) {
|
|
4437
|
+
if (auth.mode === "api_key" || auth.mode === "hmac") return [auth.credential];
|
|
4438
|
+
if (auth.mode === "oauth2") {
|
|
4439
|
+
return [
|
|
4440
|
+
{ label: "Client ID", env: auth.clientIdEnv, description: "OAuth client ID.", secret: false },
|
|
4441
|
+
{ label: "Client Secret", env: auth.clientSecretEnv, description: "OAuth client secret.", secret: true }
|
|
4442
|
+
];
|
|
4443
|
+
}
|
|
4444
|
+
return [];
|
|
4445
|
+
}
|
|
4446
|
+
function permissionsFor(spec, actions) {
|
|
4447
|
+
const dataClass = dataClassFor2(actions);
|
|
4448
|
+
const readScope = providerScopeFor(spec, "read");
|
|
4449
|
+
const writeScope = providerScopeFor(spec, "write");
|
|
4450
|
+
const permissions = [
|
|
4451
|
+
{
|
|
4452
|
+
normalized: `${spec.actionPack}.read`,
|
|
4453
|
+
providerScopes: readScope ? [readScope] : [],
|
|
4454
|
+
title: `${spec.title} read`,
|
|
4455
|
+
risk: "read",
|
|
4456
|
+
dataClass,
|
|
4457
|
+
reason: `Read ${spec.title} data for user-authorized agent workflows.`
|
|
4458
|
+
}
|
|
4459
|
+
];
|
|
4460
|
+
if (actions.some((a) => a.risk !== "read")) {
|
|
4461
|
+
permissions.push({
|
|
4462
|
+
normalized: `${spec.actionPack}.write`,
|
|
4463
|
+
providerScopes: writeScope ? [writeScope] : [],
|
|
4464
|
+
title: `${spec.title} write`,
|
|
4465
|
+
risk: "write",
|
|
4466
|
+
dataClass,
|
|
4467
|
+
reason: `Create or update ${spec.title} resources after policy approval.`
|
|
4468
|
+
});
|
|
4469
|
+
}
|
|
4470
|
+
return permissions;
|
|
4471
|
+
}
|
|
4472
|
+
function providerScopeFor(spec, mode) {
|
|
4473
|
+
const explicit = explicitScopes[spec.id]?.[mode];
|
|
4474
|
+
if (explicit) return explicit;
|
|
4475
|
+
if (spec.auth === "none") return "";
|
|
4476
|
+
return `${spec.id}.${mode}`;
|
|
4477
|
+
}
|
|
4478
|
+
var explicitScopes = {
|
|
4479
|
+
gmail: { read: "https://www.googleapis.com/auth/gmail.readonly", write: "https://www.googleapis.com/auth/gmail.modify" },
|
|
4480
|
+
"google-calendar": { read: "https://www.googleapis.com/auth/calendar.readonly", write: "https://www.googleapis.com/auth/calendar" },
|
|
4481
|
+
"google-sheets": { read: "https://www.googleapis.com/auth/spreadsheets.readonly", write: "https://www.googleapis.com/auth/spreadsheets" },
|
|
4482
|
+
"google-drive": { read: "https://www.googleapis.com/auth/drive.readonly", write: "https://www.googleapis.com/auth/drive.file" },
|
|
4483
|
+
"google-docs": { read: "https://www.googleapis.com/auth/documents.readonly", write: "https://www.googleapis.com/auth/documents" },
|
|
4484
|
+
"outlook-mail": { read: "Mail.Read", write: "Mail.Send" },
|
|
4485
|
+
"outlook-calendar": { read: "Calendars.Read", write: "Calendars.ReadWrite" },
|
|
4486
|
+
"microsoft-teams": { read: "ChannelMessage.Read.All", write: "ChannelMessage.Send" },
|
|
4487
|
+
onedrive: { read: "Files.Read", write: "Files.ReadWrite" },
|
|
4488
|
+
sharepoint: { read: "Sites.Read.All", write: "Sites.ReadWrite.All" },
|
|
4489
|
+
slack: { read: "channels:read", write: "chat:write" },
|
|
4490
|
+
hubspot: { read: "crm.objects.contacts.read", write: "crm.objects.contacts.write" },
|
|
4491
|
+
salesforce: { read: "api", write: "api" },
|
|
4492
|
+
notion: { read: "", write: "" },
|
|
4493
|
+
github: { read: "repo:read", write: "repo" },
|
|
4494
|
+
gitlab: { read: "read_api", write: "api" },
|
|
4495
|
+
airtable: { read: "data.records:read", write: "data.records:write" },
|
|
4496
|
+
asana: { read: "default", write: "default" },
|
|
4497
|
+
stripe: { read: "read_only", write: "standard" },
|
|
4498
|
+
twilio: { read: "api_key", write: "api_key" }
|
|
4499
|
+
};
|
|
4500
|
+
function plannerHintsFor(spec, actions) {
|
|
4501
|
+
return {
|
|
4502
|
+
useFor: spec.domains.map((domain) => domain.replace(/-/g, " ")),
|
|
4503
|
+
dataFreshness: ["calendar", "chat", "commerce", "finance", "support"].includes(spec.actionPack) ? "near_realtime" : "eventual",
|
|
4504
|
+
writeRisk: actions.some((a) => a.risk === "destructive") ? "high" : actions.some((a) => a.risk === "write") ? "medium" : "low"
|
|
4505
|
+
};
|
|
4506
|
+
}
|
|
4507
|
+
function healthcheckFor(kind, status, auth) {
|
|
4508
|
+
if (status !== "executable") {
|
|
4509
|
+
return { id: `${kind}.static`, level: "static", description: "Catalog-only integration; no executable connector healthcheck is available yet." };
|
|
4510
|
+
}
|
|
4511
|
+
if (auth.mode === "oauth2") {
|
|
4512
|
+
return { id: `${kind}.connection`, level: "connection", description: "Validate a user connection by calling the connector test endpoint." };
|
|
4513
|
+
}
|
|
4514
|
+
if (auth.mode === "api_key") {
|
|
4515
|
+
return { id: `${kind}.connection`, level: "connection", description: "Validate API credentials by calling the connector test endpoint." };
|
|
4516
|
+
}
|
|
4517
|
+
if (auth.mode === "hmac") {
|
|
4518
|
+
return { id: `${kind}.webhook`, level: "webhook", description: "Validate webhook signing configuration with a signed test payload." };
|
|
4519
|
+
}
|
|
4520
|
+
return { id: `${kind}.static`, level: "static", description: "No credentials are required." };
|
|
4521
|
+
}
|
|
4522
|
+
function statusFor(kind) {
|
|
4523
|
+
return EXECUTABLE_KINDS.has(kind) ? "executable" : "catalog";
|
|
4524
|
+
}
|
|
4525
|
+
function dataClassFor2(actions) {
|
|
4526
|
+
if (actions.some((a) => a.dataClass === "secret")) return "secret";
|
|
4527
|
+
if (actions.some((a) => a.dataClass === "sensitive")) return "sensitive";
|
|
4528
|
+
if (actions.some((a) => a.dataClass === "private")) return "private";
|
|
4529
|
+
if (actions.some((a) => a.dataClass === "internal")) return "internal";
|
|
4530
|
+
return "public";
|
|
4531
|
+
}
|
|
4532
|
+
function apiKeyFieldFor(kind) {
|
|
4533
|
+
return {
|
|
4534
|
+
label: `${kind} API key`,
|
|
4535
|
+
description: `API key or token for ${kind}.`,
|
|
4536
|
+
example: kind === "stripe" ? "sk_live_..." : void 0,
|
|
4537
|
+
secret: true
|
|
4538
|
+
};
|
|
4539
|
+
}
|
|
4540
|
+
function apiKeyPlacementFor(kind) {
|
|
4541
|
+
if (kind === "gitlab") return "header";
|
|
4542
|
+
return "bearer";
|
|
4543
|
+
}
|
|
4544
|
+
function extraAuthParamsFor(family) {
|
|
4545
|
+
if (family === "google") return { access_type: "offline", prompt: "consent", include_granted_scopes: "true" };
|
|
4546
|
+
if (family === "notion") return { owner: "user" };
|
|
4547
|
+
return void 0;
|
|
4548
|
+
}
|
|
4549
|
+
|
|
4550
|
+
// src/specs/renderers.ts
|
|
4551
|
+
function renderConsoleSteps(spec, options) {
|
|
4552
|
+
const redirectUri = renderRedirectUri(spec, options);
|
|
4553
|
+
return spec.setup.consoleSteps.map((step) => ({
|
|
4554
|
+
...step,
|
|
4555
|
+
detail: renderTemplate(step.detail, spec, options, redirectUri),
|
|
4556
|
+
copyValue: step.copyValue ? renderTemplate(step.copyValue, spec, options, redirectUri) : void 0
|
|
4557
|
+
}));
|
|
4558
|
+
}
|
|
4559
|
+
function renderRunbookMarkdown(spec, options) {
|
|
4560
|
+
const steps = renderConsoleSteps(spec, options);
|
|
4561
|
+
const lines = [
|
|
4562
|
+
`# ${spec.title} Integration Setup`,
|
|
4563
|
+
"",
|
|
4564
|
+
`- Kind: \`${spec.kind}\``,
|
|
4565
|
+
`- Status: \`${spec.status}\``,
|
|
4566
|
+
`- Auth: \`${spec.auth.mode}\``,
|
|
4567
|
+
`- Family: \`${spec.family}\``
|
|
4568
|
+
];
|
|
4569
|
+
if (spec.setup.consoleUrl) lines.push(`- Console: ${spec.setup.consoleUrl}`);
|
|
4570
|
+
if (spec.setup.redirectUriTemplate) lines.push(`- Redirect URI: \`${renderRedirectUri(spec, options)}\``);
|
|
4571
|
+
lines.push("", "## Credentials", "");
|
|
4572
|
+
for (const field of spec.setup.credentialFields) {
|
|
4573
|
+
lines.push(`- ${field.secret ? "[secret] " : ""}${field.label}${field.env ? ` (\`${field.env}\`)` : ""}: ${field.description}`);
|
|
4574
|
+
}
|
|
4575
|
+
lines.push("", "## Permissions", "");
|
|
4576
|
+
for (const permission of spec.permissions) {
|
|
4577
|
+
lines.push(`- \`${permission.normalized}\`: ${permission.providerScopes.length ? permission.providerScopes.map((scope) => `\`${scope}\``).join(", ") : "no provider scope"} - ${permission.reason}`);
|
|
4578
|
+
}
|
|
4579
|
+
lines.push("", "## Console Steps", "");
|
|
4580
|
+
for (const [i, step] of steps.entries()) {
|
|
4581
|
+
lines.push(`${i + 1}. ${step.title}: ${step.detail}`);
|
|
4582
|
+
}
|
|
4583
|
+
if (spec.setup.knownQuirks?.length) {
|
|
4584
|
+
lines.push("", "## Known Quirks", "");
|
|
4585
|
+
for (const quirk of spec.setup.knownQuirks) lines.push(`- ${quirk.severity}: ${quirk.message}`);
|
|
4586
|
+
}
|
|
4587
|
+
return `${lines.join("\n")}
|
|
4588
|
+
`;
|
|
4589
|
+
}
|
|
4590
|
+
function renderAgentToolDescription(spec) {
|
|
4591
|
+
const hints = spec.plannerHints;
|
|
4592
|
+
const useFor = hints?.useFor?.length ? `Use for ${hints.useFor.join(", ")}.` : `Use for ${spec.title} workflows.`;
|
|
4593
|
+
const risk = hints ? `Freshness: ${hints.dataFreshness}. Write risk: ${hints.writeRisk}.` : "";
|
|
4594
|
+
return `${spec.title} (${spec.kind}). ${useFor} ${risk}`.trim();
|
|
4595
|
+
}
|
|
4596
|
+
function buildHealthcheckPlan(spec) {
|
|
4597
|
+
const healthcheck = spec.setup.healthcheck ?? { id: `${spec.kind}.static`, level: "static", description: "No healthcheck defined." };
|
|
4598
|
+
const requires = [];
|
|
4599
|
+
if (healthcheck.level === "connection") requires.push("connection_credentials");
|
|
4600
|
+
if (healthcheck.level === "client_config" && spec.auth.mode === "oauth2") requires.push("client_id", "client_secret");
|
|
4601
|
+
if (spec.auth.mode === "api_key") requires.push("api_key");
|
|
4602
|
+
if (spec.auth.mode === "hmac") requires.push("hmac_secret");
|
|
4603
|
+
return {
|
|
4604
|
+
kind: spec.kind,
|
|
4605
|
+
healthcheck,
|
|
4606
|
+
requires,
|
|
4607
|
+
message: healthcheck.description
|
|
4608
|
+
};
|
|
4609
|
+
}
|
|
4610
|
+
function renderTemplate(template, spec, options, redirectUri) {
|
|
4611
|
+
return template.replaceAll("{host}", options.host).replaceAll("{kind}", spec.kind).replaceAll("{redirectUri}", redirectUri ?? renderRedirectUri(spec, options));
|
|
4612
|
+
}
|
|
4613
|
+
function renderRedirectUri(spec, options) {
|
|
4614
|
+
return (options.callbackPath ?? spec.setup.redirectUriTemplate ?? "").replaceAll("{host}", options.host).replaceAll("{kind}", spec.kind);
|
|
4615
|
+
}
|
|
4616
|
+
function consoleStepsToText(steps) {
|
|
4617
|
+
return steps.map((step, index) => `${index + 1}. ${step.title}: ${step.detail}`).join("\n");
|
|
4618
|
+
}
|
|
4619
|
+
|
|
4620
|
+
// src/specs/validation.ts
|
|
4621
|
+
function validateIntegrationSpec(spec) {
|
|
4622
|
+
const issues = [];
|
|
4623
|
+
if (!spec.kind.trim()) issues.push({ path: "kind", message: "kind is required" });
|
|
4624
|
+
if (!spec.title.trim()) issues.push({ path: "title", message: "title is required" });
|
|
4625
|
+
if (!spec.actions.length) issues.push({ path: "actions", message: "at least one action is required" });
|
|
4626
|
+
if (!spec.permissions.length) issues.push({ path: "permissions", message: "at least one permission is required" });
|
|
4627
|
+
if (spec.auth.mode === "oauth2") {
|
|
4628
|
+
if (!spec.auth.authorizationUrl) issues.push({ path: "auth.authorizationUrl", message: "authorizationUrl is required" });
|
|
4629
|
+
if (!spec.auth.tokenUrl) issues.push({ path: "auth.tokenUrl", message: "tokenUrl is required" });
|
|
4630
|
+
if (!spec.auth.redirectUriTemplate) issues.push({ path: "auth.redirectUriTemplate", message: "redirectUriTemplate is required" });
|
|
4631
|
+
}
|
|
4632
|
+
const actionIds = /* @__PURE__ */ new Set();
|
|
4633
|
+
for (const [index, action] of spec.actions.entries()) {
|
|
4634
|
+
if (actionIds.has(action.id)) issues.push({ path: `actions[${index}].id`, message: `duplicate action id ${action.id}` });
|
|
4635
|
+
actionIds.add(action.id);
|
|
4636
|
+
}
|
|
4637
|
+
return { ok: issues.length === 0, issues };
|
|
4638
|
+
}
|
|
4639
|
+
function assertValidIntegrationSpec(spec) {
|
|
4640
|
+
const result = validateIntegrationSpec(spec);
|
|
4641
|
+
if (!result.ok) {
|
|
4642
|
+
throw new Error(`Invalid integration spec ${spec.kind}: ${result.issues.map((i) => `${i.path}: ${i.message}`).join("; ")}`);
|
|
4643
|
+
}
|
|
4644
|
+
}
|
|
4645
|
+
function validateCredentialFormat(field, value) {
|
|
4646
|
+
if (!value.trim()) return { ok: false, field: field.label, message: `${field.label} is required` };
|
|
4647
|
+
if (field.regex && !new RegExp(field.regex).test(value)) {
|
|
4648
|
+
return { ok: false, field: field.label, message: `${field.label} does not match expected format` };
|
|
4649
|
+
}
|
|
4650
|
+
return { ok: true, field: field.label };
|
|
4651
|
+
}
|
|
4652
|
+
function validateCredentialSet(spec, values) {
|
|
4653
|
+
return spec.setup.credentialFields.map((field) => {
|
|
4654
|
+
const key = field.env ?? field.label;
|
|
4655
|
+
return validateCredentialFormat(field, values[key] ?? "");
|
|
4656
|
+
});
|
|
4657
|
+
}
|
|
4658
|
+
|
|
4084
4659
|
// src/index.ts
|
|
4085
4660
|
var IntegrationError = class extends Error {
|
|
4086
4661
|
constructor(message, code) {
|
|
@@ -4408,6 +4983,7 @@ function unique3(values) {
|
|
|
4408
4983
|
export {
|
|
4409
4984
|
CredentialsExpired,
|
|
4410
4985
|
DEFAULT_SIGNATURE_TOLERANCE_SECONDS,
|
|
4986
|
+
INTEGRATION_FAMILIES,
|
|
4411
4987
|
InMemoryConnectionStore,
|
|
4412
4988
|
InMemoryOAuthFlowStore,
|
|
4413
4989
|
IntegrationError,
|
|
@@ -4418,10 +4994,13 @@ export {
|
|
|
4418
4994
|
airtableConnector,
|
|
4419
4995
|
asanaConnector,
|
|
4420
4996
|
assertValidConnectorManifest,
|
|
4997
|
+
assertValidIntegrationSpec,
|
|
4421
4998
|
buildApprovalRequest,
|
|
4999
|
+
buildHealthcheckPlan,
|
|
4422
5000
|
buildIntegrationCoverageConnectors,
|
|
4423
5001
|
buildIntegrationInvocationEnvelope,
|
|
4424
5002
|
buildIntegrationToolCatalog,
|
|
5003
|
+
consoleStepsToText,
|
|
4425
5004
|
consumePendingFlow,
|
|
4426
5005
|
createConnectorAdapterProvider,
|
|
4427
5006
|
createDefaultIntegrationPolicyEngine,
|
|
@@ -4430,6 +5009,8 @@ export {
|
|
|
4430
5009
|
declarativeRestConnector,
|
|
4431
5010
|
exchangeAuthorizationCode,
|
|
4432
5011
|
firstHeader,
|
|
5012
|
+
getIntegrationFamily,
|
|
5013
|
+
getIntegrationSpec,
|
|
4433
5014
|
githubConnector,
|
|
4434
5015
|
gitlabConnector,
|
|
4435
5016
|
googleCalendar,
|
|
@@ -4439,9 +5020,12 @@ export {
|
|
|
4439
5020
|
importMcpConnector,
|
|
4440
5021
|
importOpenApiConnector,
|
|
4441
5022
|
integrationCoverageChecklistMarkdown,
|
|
5023
|
+
integrationSpecToConnector,
|
|
4442
5024
|
integrationToolName,
|
|
4443
5025
|
invocationRequestFromEnvelope,
|
|
5026
|
+
listExecutableIntegrationSpecs,
|
|
4444
5027
|
listIntegrationCoverageSpecs,
|
|
5028
|
+
listIntegrationSpecs,
|
|
4445
5029
|
manifestToConnector,
|
|
4446
5030
|
microsoftCalendar,
|
|
4447
5031
|
normalizeIntegrationResult,
|
|
@@ -4452,19 +5036,26 @@ export {
|
|
|
4452
5036
|
redactCapability,
|
|
4453
5037
|
redactInvocationEnvelope,
|
|
4454
5038
|
refreshAccessToken,
|
|
5039
|
+
renderAgentToolDescription,
|
|
5040
|
+
renderConsoleSteps,
|
|
5041
|
+
renderRunbookMarkdown,
|
|
4455
5042
|
salesforceConnector,
|
|
4456
5043
|
sanitizeConnection,
|
|
4457
5044
|
searchIntegrationTools,
|
|
4458
5045
|
signCapability,
|
|
4459
5046
|
slack,
|
|
4460
5047
|
slackEventsConnector,
|
|
5048
|
+
specAuthToConnectorAuth,
|
|
4461
5049
|
startOAuthFlow,
|
|
4462
5050
|
stripePackConnector,
|
|
4463
5051
|
stripeWebhookReceiverConnector,
|
|
4464
5052
|
toMcpTools,
|
|
4465
5053
|
twilioSmsConnector,
|
|
4466
5054
|
validateConnectorManifest,
|
|
5055
|
+
validateCredentialFormat,
|
|
5056
|
+
validateCredentialSet,
|
|
4467
5057
|
validateIntegrationInvocationEnvelope,
|
|
5058
|
+
validateIntegrationSpec,
|
|
4468
5059
|
verifyCapabilityToken,
|
|
4469
5060
|
verifyHmacSignature,
|
|
4470
5061
|
verifySlackSignature,
|