@elevasis/core 0.22.0 → 0.24.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/dist/index.d.ts +3214 -2501
- package/dist/index.js +3112 -1222
- package/dist/knowledge/index.d.ts +1108 -1264
- package/dist/knowledge/index.js +112 -9
- package/dist/organization-model/index.d.ts +3214 -2501
- package/dist/organization-model/index.js +3112 -1222
- package/dist/test-utils/index.d.ts +985 -1103
- package/dist/test-utils/index.js +2464 -1165
- package/package.json +5 -5
- package/src/README.md +14 -14
- package/src/__tests__/publish.test.ts +24 -24
- package/src/__tests__/template-core-compatibility.test.ts +9 -80
- package/src/_gen/__tests__/__snapshots__/contracts.md.snap +2389 -2121
- package/src/_gen/__tests__/scaffold-contracts.test.ts +30 -30
- package/src/auth/multi-tenancy/credentials/__tests__/encryption.test.ts +217 -217
- package/src/auth/multi-tenancy/credentials/server/encryption.ts +69 -69
- package/src/auth/multi-tenancy/credentials/server/kek-loader.ts +37 -37
- package/src/auth/multi-tenancy/index.ts +26 -26
- package/src/auth/multi-tenancy/invitations/api-schemas.ts +104 -104
- package/src/auth/multi-tenancy/memberships/api-schemas.ts +143 -143
- package/src/auth/multi-tenancy/memberships/index.ts +26 -26
- package/src/auth/multi-tenancy/memberships/membership.ts +130 -130
- package/src/auth/multi-tenancy/organizations/__tests__/api-schemas.test.ts +194 -194
- package/src/auth/multi-tenancy/organizations/api-schemas.ts +136 -136
- package/src/auth/multi-tenancy/permissions.test.ts +42 -42
- package/src/auth/multi-tenancy/permissions.ts +123 -123
- package/src/auth/multi-tenancy/role-management/api-schemas.ts +78 -78
- package/src/auth/multi-tenancy/role-management/index.ts +16 -16
- package/src/auth/multi-tenancy/theme-presets.ts +45 -45
- package/src/auth/multi-tenancy/types.ts +57 -57
- package/src/auth/multi-tenancy/users/api-schemas.ts +165 -165
- package/src/business/README.md +2 -2
- package/src/business/acquisition/activity-events.test.ts +250 -250
- package/src/business/acquisition/activity-events.ts +93 -93
- package/src/business/acquisition/api-schemas.test.ts +1883 -1843
- package/src/business/acquisition/api-schemas.ts +1493 -1500
- package/src/business/acquisition/build-templates.test.ts +240 -240
- package/src/business/acquisition/build-templates.ts +83 -41
- package/src/business/acquisition/crm-next-action.test.ts +262 -262
- package/src/business/acquisition/crm-next-action.ts +220 -220
- package/src/business/acquisition/crm-priority.test.ts +216 -216
- package/src/business/acquisition/crm-priority.ts +349 -349
- package/src/business/acquisition/crm-state-actions.test.ts +153 -151
- package/src/business/acquisition/deal-ownership.test.ts +351 -351
- package/src/business/acquisition/deal-ownership.ts +120 -120
- package/src/business/acquisition/derive-actions.test.ts +129 -104
- package/src/business/acquisition/derive-actions.ts +74 -84
- package/src/business/acquisition/index.ts +171 -170
- package/src/business/acquisition/ontology-validation.ts +309 -0
- package/src/business/acquisition/stateful.ts +30 -30
- package/src/business/acquisition/types.ts +396 -392
- package/src/business/clients/api-schemas.test.ts +115 -115
- package/src/business/clients/api-schemas.ts +158 -158
- package/src/business/clients/index.ts +1 -1
- package/src/business/crm/api-schemas.ts +40 -40
- package/src/business/crm/index.ts +1 -1
- package/src/business/deals/api-schemas.ts +87 -87
- package/src/business/deals/index.ts +1 -1
- package/src/business/index.ts +5 -5
- package/src/business/projects/types.ts +144 -144
- package/src/commands/queue/types/task.ts +15 -15
- package/src/execution/core/runner-types.ts +61 -61
- package/src/execution/core/sse-executions.ts +7 -7
- package/src/execution/engine/__tests__/fixtures/test-agents.ts +10 -10
- package/src/execution/engine/agent/core/__tests__/agent.test.ts +16 -16
- package/src/execution/engine/agent/core/__tests__/error-passthrough.test.ts +4 -4
- package/src/execution/engine/agent/core/types.ts +25 -25
- package/src/execution/engine/agent/index.ts +6 -6
- package/src/execution/engine/agent/reasoning/__tests__/request-builder.test.ts +24 -24
- package/src/execution/engine/index.ts +443 -443
- package/src/execution/engine/tools/integration/server/adapters/apify/__tests__/apify-run-actor.integration.test.ts +298 -298
- package/src/execution/engine/tools/integration/server/adapters/apify/apify-adapter.test.ts +55 -55
- package/src/execution/engine/tools/integration/server/adapters/apify/apify-adapter.ts +107 -107
- package/src/execution/engine/tools/integration/server/adapters/apollo/apollo-adapter.test.ts +48 -48
- package/src/execution/engine/tools/integration/server/adapters/apollo/apollo-adapter.ts +99 -99
- package/src/execution/engine/tools/integration/server/adapters/apollo/index.ts +1 -1
- package/src/execution/engine/tools/integration/server/adapters/attio/__tests__/attio-crud.integration.test.ts +363 -363
- package/src/execution/engine/tools/integration/server/adapters/attio/fetch/get-record/index.test.ts +162 -162
- package/src/execution/engine/tools/integration/server/adapters/attio/fetch/list-records/index.test.ts +316 -316
- package/src/execution/engine/tools/integration/server/adapters/clickup/clickup-adapter.test.ts +18 -18
- package/src/execution/engine/tools/integration/server/adapters/clickup/clickup-adapter.ts +194 -194
- package/src/execution/engine/tools/integration/server/adapters/clickup/index.ts +7 -7
- package/src/execution/engine/tools/integration/server/adapters/gmail/gmail-adapter.ts +204 -204
- package/src/execution/engine/tools/integration/server/adapters/gmail/gmail-tools.ts +105 -105
- package/src/execution/engine/tools/integration/server/adapters/google-calendar/google-calendar-adapter.ts +428 -428
- package/src/execution/engine/tools/integration/server/adapters/google-calendar/index.ts +2 -2
- package/src/execution/engine/tools/integration/server/adapters/google-sheets/__tests__/google-sheets.integration.test.ts +261 -261
- package/src/execution/engine/tools/integration/server/adapters/instantly/instantly-tools.ts +1474 -1474
- package/src/execution/engine/tools/integration/server/adapters/millionverifier/millionverifier-tools.ts +103 -103
- package/src/execution/engine/tools/integration/server/adapters/resend/fetch/send-email/index.test.ts +88 -88
- package/src/execution/engine/tools/integration/server/adapters/resend/fetch/send-email/index.ts +141 -141
- package/src/execution/engine/tools/integration/server/adapters/resend/fetch/utils/types.ts +76 -76
- package/src/execution/engine/tools/integration/server/adapters/signature-api/signature-api-tools.ts +182 -182
- package/src/execution/engine/tools/integration/server/adapters/stripe/stripe-tools.ts +310 -310
- package/src/execution/engine/tools/integration/service.test.ts +239 -239
- package/src/execution/engine/tools/integration/service.ts +172 -172
- package/src/execution/engine/tools/integration/tool.ts +255 -255
- package/src/execution/engine/tools/lead-service-types.ts +1005 -1005
- package/src/execution/engine/tools/messages.ts +43 -43
- package/src/execution/engine/tools/platform/acquisition/company-tools.ts +7 -7
- package/src/execution/engine/tools/platform/acquisition/contact-tools.ts +6 -6
- package/src/execution/engine/tools/platform/acquisition/list-tools.ts +6 -6
- package/src/execution/engine/tools/platform/acquisition/types.ts +280 -280
- package/src/execution/engine/tools/platform/email/types.ts +97 -97
- package/src/execution/engine/tools/registry.ts +704 -704
- package/src/execution/engine/tools/tool-maps.ts +831 -831
- package/src/execution/engine/tools/types.ts +234 -234
- package/src/execution/engine/workflow/types.ts +202 -202
- package/src/execution/external/__tests__/api-schemas.test.ts +127 -127
- package/src/execution/external/api-schemas.ts +40 -40
- package/src/execution/external/index.ts +1 -1
- package/src/index.ts +18 -18
- package/src/integrations/credentials/__tests__/api-schemas.test.ts +420 -420
- package/src/integrations/credentials/api-schemas.ts +146 -146
- package/src/integrations/credentials/schemas.ts +200 -200
- package/src/integrations/oauth/__tests__/provider-registry.test.ts +7 -7
- package/src/integrations/oauth/provider-registry.ts +74 -74
- package/src/integrations/oauth/server/credentials.ts +43 -43
- package/src/integrations/webhook-endpoints/__tests__/api-schemas.test.ts +327 -327
- package/src/integrations/webhook-endpoints/api-schemas.ts +103 -103
- package/src/integrations/webhook-endpoints/types.ts +58 -58
- package/src/knowledge/README.md +33 -32
- package/src/knowledge/__tests__/queries.test.ts +633 -541
- package/src/knowledge/format.ts +100 -99
- package/src/knowledge/index.ts +5 -5
- package/src/knowledge/published.ts +5 -5
- package/src/knowledge/queries.ts +274 -222
- package/src/operations/activities/api-schemas.ts +80 -80
- package/src/operations/activities/types.ts +64 -64
- package/src/organization-model/README.md +149 -109
- package/src/organization-model/__tests__/content-kinds-registry.test.ts +210 -0
- package/src/organization-model/__tests__/defaults.test.ts +168 -194
- package/src/organization-model/__tests__/domains/actions.test.ts +78 -0
- package/src/organization-model/__tests__/domains/customers.test.ts +48 -44
- package/src/organization-model/__tests__/domains/entities.test.ts +56 -0
- package/src/organization-model/__tests__/domains/goals.test.ts +110 -96
- package/src/organization-model/__tests__/domains/identity.test.ts +4 -3
- package/src/organization-model/__tests__/domains/navigation.test.ts +222 -166
- package/src/organization-model/__tests__/domains/offerings.test.ts +83 -88
- package/src/organization-model/__tests__/domains/policies.test.ts +323 -0
- package/src/organization-model/__tests__/domains/resource-mappings.test.ts +30 -30
- package/src/organization-model/__tests__/domains/resources.test.ts +396 -175
- package/src/organization-model/__tests__/domains/roles.test.ts +463 -402
- package/src/organization-model/__tests__/domains/statuses.test.ts +13 -10
- package/src/organization-model/__tests__/domains/systems.test.ts +209 -193
- package/src/organization-model/__tests__/flatten-additive-merge.test.ts +362 -0
- package/src/organization-model/__tests__/foundation.test.ts +47 -75
- package/src/organization-model/__tests__/get-resources-for-system.test.ts +144 -0
- package/src/organization-model/__tests__/graph.test.ts +1336 -149
- package/src/organization-model/__tests__/icons.test.ts +10 -1
- package/src/organization-model/__tests__/knowledge.test.ts +418 -61
- package/src/organization-model/__tests__/lookup-helpers.test.ts +438 -0
- package/src/organization-model/__tests__/migration-helpers.test.ts +591 -0
- package/src/organization-model/__tests__/prospecting-ssot.test.ts +103 -94
- package/src/organization-model/__tests__/recursive-system-schema.test.ts +549 -0
- package/src/organization-model/__tests__/resolve.test.ts +303 -42
- package/src/organization-model/__tests__/schema.test.ts +863 -153
- package/src/organization-model/__tests__/surface-projection.test.ts +284 -174
- package/src/organization-model/catalogs/lead-gen.ts +144 -0
- package/src/organization-model/content-kinds/config.ts +36 -0
- package/src/organization-model/content-kinds/index.ts +78 -0
- package/src/organization-model/content-kinds/pipeline.ts +68 -0
- package/src/organization-model/content-kinds/registry.ts +44 -0
- package/src/organization-model/content-kinds/status.ts +71 -0
- package/src/organization-model/content-kinds/template.ts +83 -0
- package/src/organization-model/content-kinds/types.ts +117 -0
- package/src/organization-model/contracts.ts +27 -17
- package/src/organization-model/defaults.ts +489 -107
- package/src/organization-model/domains/actions.ts +333 -0
- package/src/organization-model/domains/customers.ts +10 -7
- package/src/organization-model/domains/entities.ts +144 -0
- package/src/organization-model/domains/goals.ts +9 -6
- package/src/organization-model/domains/knowledge.ts +128 -54
- package/src/organization-model/domains/navigation.ts +139 -416
- package/src/organization-model/domains/offerings.ts +15 -10
- package/src/organization-model/domains/policies.ts +102 -0
- package/src/organization-model/domains/projects.ts +6 -40
- package/src/organization-model/domains/prospecting.ts +395 -514
- package/src/organization-model/domains/resources.ts +173 -81
- package/src/organization-model/domains/roles.ts +96 -93
- package/src/organization-model/domains/sales.test.ts +218 -218
- package/src/organization-model/domains/sales.ts +380 -589
- package/src/organization-model/domains/shared.ts +8 -8
- package/src/organization-model/domains/statuses.ts +298 -89
- package/src/organization-model/domains/systems.ts +240 -38
- package/src/organization-model/foundation.ts +35 -48
- package/src/organization-model/graph/build.ts +1035 -279
- package/src/organization-model/graph/index.ts +4 -4
- package/src/organization-model/graph/link.ts +10 -10
- package/src/organization-model/graph/schema.ts +77 -56
- package/src/organization-model/graph/types.ts +75 -56
- package/src/organization-model/helpers.ts +312 -59
- package/src/organization-model/icons.ts +78 -66
- package/src/organization-model/index.ts +129 -16
- package/src/organization-model/migration-helpers.ts +252 -0
- package/src/organization-model/ontology.ts +661 -0
- package/src/organization-model/organization-graph.mdx +110 -89
- package/src/organization-model/organization-model.mdx +226 -171
- package/src/organization-model/published.ts +295 -139
- package/src/organization-model/resolve.ts +139 -21
- package/src/organization-model/schema.ts +841 -301
- package/src/organization-model/surface-projection.ts +212 -218
- package/src/organization-model/types.ts +181 -90
- package/src/platform/api/types.ts +38 -38
- package/src/platform/constants/versions.ts +3 -3
- package/src/platform/index.ts +23 -23
- package/src/platform/registry/__tests__/command-view.test.ts +5 -7
- package/src/platform/registry/__tests__/resource-link.test.ts +35 -30
- package/src/platform/registry/__tests__/resource-registry.integration.test.ts +17 -32
- package/src/platform/registry/__tests__/resource-registry.nested-systems.test.ts +245 -0
- package/src/platform/registry/__tests__/resource-registry.test.ts +2053 -2051
- package/src/platform/registry/__tests__/validation.test.ts +1347 -1343
- package/src/platform/registry/command-view.ts +10 -10
- package/src/platform/registry/index.ts +103 -103
- package/src/platform/registry/resource-link.ts +32 -32
- package/src/platform/registry/resource-registry.ts +890 -878
- package/src/platform/registry/serialization.ts +295 -295
- package/src/platform/registry/serialized-types.ts +166 -166
- package/src/platform/registry/stats-types.ts +68 -68
- package/src/platform/registry/types.ts +425 -425
- package/src/platform/registry/validation.ts +745 -743
- package/src/platform/utils/__tests__/validation.test.ts +1084 -1084
- package/src/platform/utils/validation.ts +425 -425
- package/src/projects/api-schemas.test.ts +39 -39
- package/src/projects/api-schemas.ts +291 -291
- package/src/reference/_generated/contracts.md +2389 -2121
- package/src/reference/glossary.md +76 -76
- package/src/scaffold-registry/__tests__/index.test.ts +206 -206
- package/src/scaffold-registry/__tests__/schema.test.ts +166 -166
- package/src/scaffold-registry/index.ts +392 -392
- package/src/scaffold-registry/schema.ts +243 -243
- package/src/server.ts +289 -289
- package/src/supabase/database.types.ts +3153 -3093
- package/src/test-utils/README.md +37 -37
- package/src/test-utils/entities.ts +108 -108
- package/src/test-utils/fixtures/memberships.ts +82 -82
- package/src/test-utils/index.ts +12 -12
- package/src/test-utils/organization-model.ts +65 -65
- package/src/test-utils/published.ts +6 -6
- package/src/test-utils/rls/RLSTestContext.ts +588 -588
- package/src/test-utils/test-utils.test.ts +44 -49
- package/src/organization-model/__tests__/domains/operations.test.ts +0 -203
- package/src/organization-model/domains/features.ts +0 -31
- package/src/organization-model/domains/operations.ts +0 -85
|
@@ -1,172 +1,172 @@
|
|
|
1
|
-
import type { ExecutionContext } from '../../base/types'
|
|
2
|
-
import type { BaseIntegrationAdapter } from './base-integration-adapter'
|
|
3
|
-
import type { RateLimiter, RetryPolicy } from '../../../../platform/resilience'
|
|
4
|
-
import { ToolingError } from '../types'
|
|
5
|
-
import { withRetry, InMemoryRateLimiter, DEFAULT_RETRY_POLICY } from '../../../../platform/resilience'
|
|
6
|
-
import {
|
|
7
|
-
INTEGRATION_RATE_LIMIT_CAPACITY,
|
|
8
|
-
INTEGRATION_RATE_LIMIT_WINDOW_MS
|
|
9
|
-
} from '../../../../platform/constants/resilience'
|
|
10
|
-
import { getToolServices } from '../registry'
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Integration Service
|
|
14
|
-
*
|
|
15
|
-
* Central service for managing integration adapters, rate limiting, and retry.
|
|
16
|
-
* Credentials accessed via global tool services registry.
|
|
17
|
-
*
|
|
18
|
-
* Pattern: Active Record (justified - owns state + behavior)
|
|
19
|
-
*
|
|
20
|
-
* @example
|
|
21
|
-
* const service = new IntegrationService(
|
|
22
|
-
* new Map([['gmail', gmailAdapter]])
|
|
23
|
-
* )
|
|
24
|
-
*
|
|
25
|
-
* const result = await service.call('gmail', 'sendEmail', params, 'gmail-cred', context)
|
|
26
|
-
*/
|
|
27
|
-
export class IntegrationService {
|
|
28
|
-
private readonly rateLimiter: RateLimiter
|
|
29
|
-
private readonly retryPolicy: RetryPolicy
|
|
30
|
-
|
|
31
|
-
constructor(public readonly adapters: Map<string, BaseIntegrationAdapter>) {
|
|
32
|
-
this.rateLimiter = new InMemoryRateLimiter(INTEGRATION_RATE_LIMIT_CAPACITY, INTEGRATION_RATE_LIMIT_WINDOW_MS)
|
|
33
|
-
this.retryPolicy = DEFAULT_RETRY_POLICY
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Call integration method with full orchestration
|
|
38
|
-
*
|
|
39
|
-
* Orchestrates the full integration call flow:
|
|
40
|
-
* 1. Validate execution context
|
|
41
|
-
* 2. Validate adapter exists
|
|
42
|
-
* 3. Check rate limit (per organization per integration)
|
|
43
|
-
* 4. Get credentials from registry
|
|
44
|
-
* 5. Validate credentials
|
|
45
|
-
* 6. Call adapter with retry
|
|
46
|
-
*
|
|
47
|
-
* @param integration - Integration adapter type (e.g., 'gmail', 'slack')
|
|
48
|
-
* @param method - Method name (e.g., 'sendEmail', 'postMessage')
|
|
49
|
-
* @param params - Method parameters
|
|
50
|
-
* @param credentialName - Credential name from credentials table
|
|
51
|
-
* @param context - Execution context (required for multi-tenant isolation)
|
|
52
|
-
* @returns Method result
|
|
53
|
-
* @throws ToolingError if call fails
|
|
54
|
-
*/
|
|
55
|
-
async call(
|
|
56
|
-
integration: string,
|
|
57
|
-
method: string,
|
|
58
|
-
params: unknown,
|
|
59
|
-
credentialName: string,
|
|
60
|
-
context: ExecutionContext | undefined
|
|
61
|
-
): Promise<unknown> {
|
|
62
|
-
// 1. Validate execution context (required for multi-tenant isolation)
|
|
63
|
-
if (!context) {
|
|
64
|
-
throw new ToolingError('validation_error', 'ExecutionContext required for integration calls', {
|
|
65
|
-
integration,
|
|
66
|
-
method,
|
|
67
|
-
credentialName
|
|
68
|
-
})
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// 2. Validate adapter exists
|
|
72
|
-
const adapter = this.adapters.get(integration)
|
|
73
|
-
if (!adapter) {
|
|
74
|
-
throw new ToolingError('adapter_not_found', `Integration adapter not found: ${integration}`, {
|
|
75
|
-
integration,
|
|
76
|
-
availableAdapters: Array.from(this.adapters.keys())
|
|
77
|
-
})
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// 3. Check rate limit (per organization per integration)
|
|
81
|
-
const rateLimitKey = `${context.organizationId}:${integration}`
|
|
82
|
-
const allowed = await this.rateLimiter.allow(rateLimitKey)
|
|
83
|
-
if (!allowed) {
|
|
84
|
-
const remaining = await this.rateLimiter.remaining(rateLimitKey)
|
|
85
|
-
throw new ToolingError('rate_limit_exceeded', 'Rate limit exceeded', {
|
|
86
|
-
integration,
|
|
87
|
-
organizationId: context.organizationId,
|
|
88
|
-
remaining
|
|
89
|
-
})
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// 4. Get credentials from registry (per-execution cache keyed by credential name).
|
|
93
|
-
// The store is an opportunistic perf cache — never a load-bearing contract.
|
|
94
|
-
// Optional chaining lets a missing/undefined store fall through to the fetch path
|
|
95
|
-
// instead of crashing with "Cannot read properties of undefined (reading 'get')".
|
|
96
|
-
const cacheKey = `__cred_cache__:${credentialName}`
|
|
97
|
-
let credentials = context.store?.get(cacheKey) as Record<string, unknown> | undefined
|
|
98
|
-
|
|
99
|
-
if (!credentials) {
|
|
100
|
-
const { credentialsService } = getToolServices()
|
|
101
|
-
if (!credentialsService) {
|
|
102
|
-
throw new ToolingError(
|
|
103
|
-
'service_unavailable',
|
|
104
|
-
'CredentialsService not initialized. Ensure toolServicesRegistry.initialize() was called in apps/api/src/main.ts',
|
|
105
|
-
{ integration, credentialName }
|
|
106
|
-
)
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const fetched = await credentialsService.getCredential(context.organizationId, credentialName)
|
|
110
|
-
if (!fetched) {
|
|
111
|
-
throw new ToolingError('credentials_missing', `No credentials found for credential name: ${credentialName}`, {
|
|
112
|
-
integration,
|
|
113
|
-
credentialName,
|
|
114
|
-
organizationId: context.organizationId
|
|
115
|
-
})
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
context.store?.set(cacheKey, fetched)
|
|
119
|
-
credentials = fetched
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Log credential retrieval for OAuth integrations
|
|
123
|
-
const isOAuth = !!credentials.provider
|
|
124
|
-
if (isOAuth) {
|
|
125
|
-
const creds = credentials
|
|
126
|
-
context.logger?.debug(
|
|
127
|
-
`OAuth credential retrieved: integration=${integration}, credentialName=${credentialName}, provider=${creds.provider}, hasRefreshToken=${!!creds.refreshToken}, hasExpiresAt=${!!creds.expiresAt}, expiresAt=${creds.expiresAt}`
|
|
128
|
-
)
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// 5. Validate credentials
|
|
132
|
-
const isValid = adapter.validateCredentials(credentials)
|
|
133
|
-
if (!isValid) {
|
|
134
|
-
throw new ToolingError('credentials_invalid', `Invalid credentials for integration: ${integration}`, {
|
|
135
|
-
integration,
|
|
136
|
-
organizationId: context.organizationId
|
|
137
|
-
})
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// 6. Call adapter with retry (pass credentialName in context for token refresh persistence)
|
|
141
|
-
const contextWithCredential: ExecutionContext = {
|
|
142
|
-
...context,
|
|
143
|
-
credentialName
|
|
144
|
-
}
|
|
145
|
-
return withRetry(() => adapter.call(method, params, credentials, contextWithCredential), this.retryPolicy)
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Get adapter by name
|
|
150
|
-
* @param integration - Integration name
|
|
151
|
-
* @returns Adapter or undefined
|
|
152
|
-
*/
|
|
153
|
-
getAdapter(integration: string): BaseIntegrationAdapter | undefined {
|
|
154
|
-
return this.adapters.get(integration)
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Register adapter
|
|
159
|
-
* @param adapter - Adapter to register
|
|
160
|
-
*/
|
|
161
|
-
registerAdapter(adapter: BaseIntegrationAdapter): void {
|
|
162
|
-
this.adapters.set(adapter.name, adapter)
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Unregister adapter
|
|
167
|
-
* @param integration - Integration name to unregister
|
|
168
|
-
*/
|
|
169
|
-
unregisterAdapter(integration: string): void {
|
|
170
|
-
this.adapters.delete(integration)
|
|
171
|
-
}
|
|
172
|
-
}
|
|
1
|
+
import type { ExecutionContext } from '../../base/types'
|
|
2
|
+
import type { BaseIntegrationAdapter } from './base-integration-adapter'
|
|
3
|
+
import type { RateLimiter, RetryPolicy } from '../../../../platform/resilience'
|
|
4
|
+
import { ToolingError } from '../types'
|
|
5
|
+
import { withRetry, InMemoryRateLimiter, DEFAULT_RETRY_POLICY } from '../../../../platform/resilience'
|
|
6
|
+
import {
|
|
7
|
+
INTEGRATION_RATE_LIMIT_CAPACITY,
|
|
8
|
+
INTEGRATION_RATE_LIMIT_WINDOW_MS
|
|
9
|
+
} from '../../../../platform/constants/resilience'
|
|
10
|
+
import { getToolServices } from '../registry'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Integration Service
|
|
14
|
+
*
|
|
15
|
+
* Central service for managing integration adapters, rate limiting, and retry.
|
|
16
|
+
* Credentials accessed via global tool services registry.
|
|
17
|
+
*
|
|
18
|
+
* Pattern: Active Record (justified - owns state + behavior)
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* const service = new IntegrationService(
|
|
22
|
+
* new Map([['gmail', gmailAdapter]])
|
|
23
|
+
* )
|
|
24
|
+
*
|
|
25
|
+
* const result = await service.call('gmail', 'sendEmail', params, 'gmail-cred', context)
|
|
26
|
+
*/
|
|
27
|
+
export class IntegrationService {
|
|
28
|
+
private readonly rateLimiter: RateLimiter
|
|
29
|
+
private readonly retryPolicy: RetryPolicy
|
|
30
|
+
|
|
31
|
+
constructor(public readonly adapters: Map<string, BaseIntegrationAdapter>) {
|
|
32
|
+
this.rateLimiter = new InMemoryRateLimiter(INTEGRATION_RATE_LIMIT_CAPACITY, INTEGRATION_RATE_LIMIT_WINDOW_MS)
|
|
33
|
+
this.retryPolicy = DEFAULT_RETRY_POLICY
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Call integration method with full orchestration
|
|
38
|
+
*
|
|
39
|
+
* Orchestrates the full integration call flow:
|
|
40
|
+
* 1. Validate execution context
|
|
41
|
+
* 2. Validate adapter exists
|
|
42
|
+
* 3. Check rate limit (per organization per integration)
|
|
43
|
+
* 4. Get credentials from registry
|
|
44
|
+
* 5. Validate credentials
|
|
45
|
+
* 6. Call adapter with retry
|
|
46
|
+
*
|
|
47
|
+
* @param integration - Integration adapter type (e.g., 'gmail', 'slack')
|
|
48
|
+
* @param method - Method name (e.g., 'sendEmail', 'postMessage')
|
|
49
|
+
* @param params - Method parameters
|
|
50
|
+
* @param credentialName - Credential name from credentials table
|
|
51
|
+
* @param context - Execution context (required for multi-tenant isolation)
|
|
52
|
+
* @returns Method result
|
|
53
|
+
* @throws ToolingError if call fails
|
|
54
|
+
*/
|
|
55
|
+
async call(
|
|
56
|
+
integration: string,
|
|
57
|
+
method: string,
|
|
58
|
+
params: unknown,
|
|
59
|
+
credentialName: string,
|
|
60
|
+
context: ExecutionContext | undefined
|
|
61
|
+
): Promise<unknown> {
|
|
62
|
+
// 1. Validate execution context (required for multi-tenant isolation)
|
|
63
|
+
if (!context) {
|
|
64
|
+
throw new ToolingError('validation_error', 'ExecutionContext required for integration calls', {
|
|
65
|
+
integration,
|
|
66
|
+
method,
|
|
67
|
+
credentialName
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 2. Validate adapter exists
|
|
72
|
+
const adapter = this.adapters.get(integration)
|
|
73
|
+
if (!adapter) {
|
|
74
|
+
throw new ToolingError('adapter_not_found', `Integration adapter not found: ${integration}`, {
|
|
75
|
+
integration,
|
|
76
|
+
availableAdapters: Array.from(this.adapters.keys())
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// 3. Check rate limit (per organization per integration)
|
|
81
|
+
const rateLimitKey = `${context.organizationId}:${integration}`
|
|
82
|
+
const allowed = await this.rateLimiter.allow(rateLimitKey)
|
|
83
|
+
if (!allowed) {
|
|
84
|
+
const remaining = await this.rateLimiter.remaining(rateLimitKey)
|
|
85
|
+
throw new ToolingError('rate_limit_exceeded', 'Rate limit exceeded', {
|
|
86
|
+
integration,
|
|
87
|
+
organizationId: context.organizationId,
|
|
88
|
+
remaining
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// 4. Get credentials from registry (per-execution cache keyed by credential name).
|
|
93
|
+
// The store is an opportunistic perf cache — never a load-bearing contract.
|
|
94
|
+
// Optional chaining lets a missing/undefined store fall through to the fetch path
|
|
95
|
+
// instead of crashing with "Cannot read properties of undefined (reading 'get')".
|
|
96
|
+
const cacheKey = `__cred_cache__:${credentialName}`
|
|
97
|
+
let credentials = context.store?.get(cacheKey) as Record<string, unknown> | undefined
|
|
98
|
+
|
|
99
|
+
if (!credentials) {
|
|
100
|
+
const { credentialsService } = getToolServices()
|
|
101
|
+
if (!credentialsService) {
|
|
102
|
+
throw new ToolingError(
|
|
103
|
+
'service_unavailable',
|
|
104
|
+
'CredentialsService not initialized. Ensure toolServicesRegistry.initialize() was called in apps/api/src/main.ts',
|
|
105
|
+
{ integration, credentialName }
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const fetched = await credentialsService.getCredential(context.organizationId, credentialName)
|
|
110
|
+
if (!fetched) {
|
|
111
|
+
throw new ToolingError('credentials_missing', `No credentials found for credential name: ${credentialName}`, {
|
|
112
|
+
integration,
|
|
113
|
+
credentialName,
|
|
114
|
+
organizationId: context.organizationId
|
|
115
|
+
})
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
context.store?.set(cacheKey, fetched)
|
|
119
|
+
credentials = fetched
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Log credential retrieval for OAuth integrations
|
|
123
|
+
const isOAuth = !!credentials.provider
|
|
124
|
+
if (isOAuth) {
|
|
125
|
+
const creds = credentials
|
|
126
|
+
context.logger?.debug(
|
|
127
|
+
`OAuth credential retrieved: integration=${integration}, credentialName=${credentialName}, provider=${creds.provider}, hasRefreshToken=${!!creds.refreshToken}, hasExpiresAt=${!!creds.expiresAt}, expiresAt=${creds.expiresAt}`
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// 5. Validate credentials
|
|
132
|
+
const isValid = adapter.validateCredentials(credentials)
|
|
133
|
+
if (!isValid) {
|
|
134
|
+
throw new ToolingError('credentials_invalid', `Invalid credentials for integration: ${integration}`, {
|
|
135
|
+
integration,
|
|
136
|
+
organizationId: context.organizationId
|
|
137
|
+
})
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// 6. Call adapter with retry (pass credentialName in context for token refresh persistence)
|
|
141
|
+
const contextWithCredential: ExecutionContext = {
|
|
142
|
+
...context,
|
|
143
|
+
credentialName
|
|
144
|
+
}
|
|
145
|
+
return withRetry(() => adapter.call(method, params, credentials, contextWithCredential), this.retryPolicy)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Get adapter by name
|
|
150
|
+
* @param integration - Integration name
|
|
151
|
+
* @returns Adapter or undefined
|
|
152
|
+
*/
|
|
153
|
+
getAdapter(integration: string): BaseIntegrationAdapter | undefined {
|
|
154
|
+
return this.adapters.get(integration)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Register adapter
|
|
159
|
+
* @param adapter - Adapter to register
|
|
160
|
+
*/
|
|
161
|
+
registerAdapter(adapter: BaseIntegrationAdapter): void {
|
|
162
|
+
this.adapters.set(adapter.name, adapter)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Unregister adapter
|
|
167
|
+
* @param integration - Integration name to unregister
|
|
168
|
+
*/
|
|
169
|
+
unregisterAdapter(integration: string): void {
|
|
170
|
+
this.adapters.delete(integration)
|
|
171
|
+
}
|
|
172
|
+
}
|