@vibesdotdev/integrations 0.0.0-stage → 1.0.0-rc.3
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/LICENSE +18 -0
- package/README.md +100 -2
- package/dist/ai/ai.cloud.plugin.d.ts +5 -0
- package/dist/ai/ai.cloud.plugin.d.ts.map +1 -0
- package/dist/ai/ai.cloud.plugin.js +67 -0
- package/dist/ai/ai.cloud.plugin.js.map +1 -0
- package/dist/ai/ai.plugin.d.ts +5 -0
- package/dist/ai/ai.plugin.d.ts.map +1 -0
- package/dist/ai/ai.plugin.js +97 -0
- package/dist/ai/ai.plugin.js.map +1 -0
- package/dist/base/base.plugin.d.ts +10 -0
- package/dist/base/base.plugin.d.ts.map +1 -0
- package/dist/base/base.plugin.js +23 -0
- package/dist/base/base.plugin.js.map +1 -0
- package/dist/base/index.d.ts +13 -0
- package/dist/base/index.d.ts.map +1 -0
- package/dist/base/index.js +15 -0
- package/dist/base/index.js.map +1 -0
- package/dist/base/resolver.d.ts +13 -0
- package/dist/base/resolver.d.ts.map +1 -0
- package/dist/base/resolver.js +30 -0
- package/dist/base/resolver.js.map +1 -0
- package/dist/base/schemas.d.ts +167 -0
- package/dist/base/schemas.d.ts.map +1 -0
- package/dist/base/schemas.js +48 -0
- package/dist/base/schemas.js.map +1 -0
- package/dist/catalog/index.d.ts +6 -0
- package/dist/catalog/index.d.ts.map +1 -0
- package/dist/catalog/index.js +6 -0
- package/dist/catalog/index.js.map +1 -0
- package/dist/catalog/integration-catalog.capability.d.ts +14 -0
- package/dist/catalog/integration-catalog.capability.d.ts.map +1 -0
- package/dist/catalog/integration-catalog.capability.js +23 -0
- package/dist/catalog/integration-catalog.capability.js.map +1 -0
- package/dist/catalog/integration-catalog.descriptor.d.ts +6 -0
- package/dist/catalog/integration-catalog.descriptor.d.ts.map +1 -0
- package/dist/catalog/integration-catalog.descriptor.js +11 -0
- package/dist/catalog/integration-catalog.descriptor.js.map +1 -0
- package/dist/catalog/integration-catalog.exposure.d.ts +62 -0
- package/dist/catalog/integration-catalog.exposure.d.ts.map +1 -0
- package/dist/catalog/integration-catalog.exposure.js +101 -0
- package/dist/catalog/integration-catalog.exposure.js.map +1 -0
- package/dist/catalog/integration-catalog.http.d.ts +9 -0
- package/dist/catalog/integration-catalog.http.d.ts.map +1 -0
- package/dist/catalog/integration-catalog.http.js +58 -0
- package/dist/catalog/integration-catalog.http.js.map +1 -0
- package/dist/catalog/integration-catalog.kind.d.ts +36 -0
- package/dist/catalog/integration-catalog.kind.d.ts.map +1 -0
- package/dist/catalog/integration-catalog.kind.js +14 -0
- package/dist/catalog/integration-catalog.kind.js.map +1 -0
- package/dist/catalog/queries.d.ts +6 -0
- package/dist/catalog/queries.d.ts.map +1 -0
- package/dist/catalog/queries.js +29 -0
- package/dist/catalog/queries.js.map +1 -0
- package/dist/client/helper.d.ts +5 -0
- package/dist/client/helper.d.ts.map +1 -0
- package/dist/client/helper.js +6 -0
- package/dist/client/helper.js.map +1 -0
- package/dist/client/index.d.ts +4 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +3 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/integrations.runtime-client.browser.d.ts +10 -0
- package/dist/client/integrations.runtime-client.browser.d.ts.map +1 -0
- package/dist/client/integrations.runtime-client.browser.js +13 -0
- package/dist/client/integrations.runtime-client.browser.js.map +1 -0
- package/dist/client/integrations.runtime-client.d.ts +25 -0
- package/dist/client/integrations.runtime-client.d.ts.map +1 -0
- package/dist/client/integrations.runtime-client.descriptor.d.ts +4 -0
- package/dist/client/integrations.runtime-client.descriptor.d.ts.map +1 -0
- package/dist/client/integrations.runtime-client.descriptor.js +13 -0
- package/dist/client/integrations.runtime-client.descriptor.js.map +1 -0
- package/dist/client/integrations.runtime-client.js +41 -0
- package/dist/client/integrations.runtime-client.js.map +1 -0
- package/dist/docs/integrations.docs.descriptor.d.ts +4 -0
- package/dist/docs/integrations.docs.descriptor.d.ts.map +1 -0
- package/dist/docs/integrations.docs.descriptor.js +43 -0
- package/dist/docs/integrations.docs.descriptor.js.map +1 -0
- package/dist/docs/integrations.providers.docs.descriptor.d.ts +4 -0
- package/dist/docs/integrations.providers.docs.descriptor.d.ts.map +1 -0
- package/dist/docs/integrations.providers.docs.descriptor.js +32 -0
- package/dist/docs/integrations.providers.docs.descriptor.js.map +1 -0
- package/dist/http/fetch-with-retry.d.ts +27 -0
- package/dist/http/fetch-with-retry.d.ts.map +1 -0
- package/dist/http/fetch-with-retry.js +76 -0
- package/dist/http/fetch-with-retry.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/integrations.browser.plugin.d.ts +11 -0
- package/dist/integrations.browser.plugin.d.ts.map +1 -0
- package/dist/integrations.browser.plugin.js +26 -0
- package/dist/integrations.browser.plugin.js.map +1 -0
- package/dist/integrations.consumer.plugin.d.ts +3 -0
- package/dist/integrations.consumer.plugin.d.ts.map +1 -0
- package/dist/integrations.consumer.plugin.js +12 -0
- package/dist/integrations.consumer.plugin.js.map +1 -0
- package/dist/integrations.plugin.d.ts +10 -0
- package/dist/integrations.plugin.d.ts.map +1 -0
- package/dist/integrations.plugin.js +67 -0
- package/dist/integrations.plugin.js.map +1 -0
- package/dist/kinds/integration-store.context.descriptor.d.ts +31 -0
- package/dist/kinds/integration-store.context.descriptor.d.ts.map +1 -0
- package/dist/kinds/integration-store.context.descriptor.js +8 -0
- package/dist/kinds/integration-store.context.descriptor.js.map +1 -0
- package/dist/kinds/integration.kind.d.ts +74 -0
- package/dist/kinds/integration.kind.d.ts.map +1 -0
- package/dist/kinds/integration.kind.js +542 -0
- package/dist/kinds/integration.kind.js.map +1 -0
- package/dist/kinds/integration.store.d.ts +30 -0
- package/dist/kinds/integration.store.d.ts.map +1 -0
- package/dist/kinds/integration.store.js +295 -0
- package/dist/kinds/integration.store.js.map +1 -0
- package/dist/schemas/boundary.d.ts +7 -0
- package/dist/schemas/boundary.d.ts.map +1 -0
- package/dist/schemas/boundary.js +16 -0
- package/dist/schemas/boundary.js.map +1 -0
- package/dist/schemas/catalog.d.ts +135 -0
- package/dist/schemas/catalog.d.ts.map +1 -0
- package/dist/schemas/catalog.js +13 -0
- package/dist/schemas/catalog.js.map +1 -0
- package/dist/schemas/credential-resolution.d.ts +35 -0
- package/dist/schemas/credential-resolution.d.ts.map +1 -0
- package/dist/schemas/credential-resolution.js +14 -0
- package/dist/schemas/credential-resolution.js.map +1 -0
- package/dist/schemas/http-retry.d.ts +9 -0
- package/dist/schemas/http-retry.d.ts.map +1 -0
- package/dist/schemas/http-retry.js +8 -0
- package/dist/schemas/http-retry.js.map +1 -0
- package/dist/schemas/integration-connection.d.ts +79 -0
- package/dist/schemas/integration-connection.d.ts.map +1 -0
- package/dist/schemas/integration-connection.js +55 -0
- package/dist/schemas/integration-connection.js.map +1 -0
- package/dist/schemas/integration-descriptor.d.ts +358 -0
- package/dist/schemas/integration-descriptor.d.ts.map +1 -0
- package/dist/schemas/integration-descriptor.js +153 -0
- package/dist/schemas/integration-descriptor.js.map +1 -0
- package/dist/schemas/oauth-state.d.ts +30 -0
- package/dist/schemas/oauth-state.d.ts.map +1 -0
- package/dist/schemas/oauth-state.js +26 -0
- package/dist/schemas/oauth-state.js.map +1 -0
- package/dist/schemas/settings.d.ts +9 -0
- package/dist/schemas/settings.d.ts.map +1 -0
- package/dist/schemas/settings.js +8 -0
- package/dist/schemas/settings.js.map +1 -0
- package/dist/schemas/store.d.ts +68 -0
- package/dist/schemas/store.d.ts.map +1 -0
- package/dist/schemas/store.js +31 -0
- package/dist/schemas/store.js.map +1 -0
- package/dist/server/defaults/system-defaults.kind.d.ts +8 -0
- package/dist/server/defaults/system-defaults.kind.d.ts.map +1 -0
- package/dist/server/defaults/system-defaults.kind.js +13 -0
- package/dist/server/defaults/system-defaults.kind.js.map +1 -0
- package/dist/server/integration-oauth.d.ts +44 -0
- package/dist/server/integration-oauth.d.ts.map +1 -0
- package/dist/server/integration-oauth.js +56 -0
- package/dist/server/integration-oauth.js.map +1 -0
- package/dist/server/integration-secrets.d.ts +11 -0
- package/dist/server/integration-secrets.d.ts.map +1 -0
- package/dist/server/integration-secrets.js +98 -0
- package/dist/server/integration-secrets.js.map +1 -0
- package/dist/server/oauth-state.d.ts +74 -0
- package/dist/server/oauth-state.d.ts.map +1 -0
- package/dist/server/oauth-state.js +208 -0
- package/dist/server/oauth-state.js.map +1 -0
- package/dist/server/schemas/integration-oauth.d.ts +32 -0
- package/dist/server/schemas/integration-oauth.d.ts.map +1 -0
- package/dist/server/schemas/integration-oauth.js +20 -0
- package/dist/server/schemas/integration-oauth.js.map +1 -0
- package/dist/server/schemas/integration-secrets.d.ts +165 -0
- package/dist/server/schemas/integration-secrets.d.ts.map +1 -0
- package/dist/server/schemas/integration-secrets.js +72 -0
- package/dist/server/schemas/integration-secrets.js.map +1 -0
- package/dist/server/schemas/system-defaults.d.ts +124 -0
- package/dist/server/schemas/system-defaults.d.ts.map +1 -0
- package/dist/server/schemas/system-defaults.js +48 -0
- package/dist/server/schemas/system-defaults.js.map +1 -0
- package/dist/server/system-defaults.d.ts +20 -0
- package/dist/server/system-defaults.d.ts.map +1 -0
- package/dist/server/system-defaults.js +58 -0
- package/dist/server/system-defaults.js.map +1 -0
- package/dist/settings/context.d.ts +11 -0
- package/dist/settings/context.d.ts.map +1 -0
- package/dist/settings/context.js +19 -0
- package/dist/settings/context.js.map +1 -0
- package/dist/settings/system-settings-reader.context.descriptor.d.ts +43 -0
- package/dist/settings/system-settings-reader.context.descriptor.d.ts.map +1 -0
- package/dist/settings/system-settings-reader.context.descriptor.js +23 -0
- package/dist/settings/system-settings-reader.context.descriptor.js.map +1 -0
- package/dist/status/integration-connect.availability.d.ts +16 -0
- package/dist/status/integration-connect.availability.d.ts.map +1 -0
- package/dist/status/integration-connect.availability.js +14 -0
- package/dist/status/integration-connect.availability.js.map +1 -0
- package/dist/status/integration-connection-status.d.ts +18 -0
- package/dist/status/integration-connection-status.d.ts.map +1 -0
- package/dist/status/integration-connection-status.js +17 -0
- package/dist/status/integration-connection-status.js.map +1 -0
- package/dist/status/integration-connection.capability.d.ts +19 -0
- package/dist/status/integration-connection.capability.d.ts.map +1 -0
- package/dist/status/integration-connection.capability.js +15 -0
- package/dist/status/integration-connection.capability.js.map +1 -0
- package/dist/status/integration-connection.kind.d.ts +55 -0
- package/dist/status/integration-connection.kind.d.ts.map +1 -0
- package/dist/status/integration-connection.kind.js +31 -0
- package/dist/status/integration-connection.kind.js.map +1 -0
- package/dist/status/integration-connection.queries.d.ts +25 -0
- package/dist/status/integration-connection.queries.d.ts.map +1 -0
- package/dist/status/integration-connection.queries.js +65 -0
- package/dist/status/integration-connection.queries.js.map +1 -0
- package/dist/svelte/components/IntegrationsCatalog.svelte +201 -0
- package/dist/svelte/components/IntegrationsCatalog.svelte.d.ts +5 -0
- package/dist/svelte/components/IntegrationsCatalog.svelte.d.ts.map +1 -0
- package/dist/svelte/components/IntegrationsLoader.svelte +46 -0
- package/dist/svelte/components/IntegrationsLoader.svelte.d.ts +12 -0
- package/dist/svelte/components/IntegrationsLoader.svelte.d.ts.map +1 -0
- package/dist/svelte/connect-action.d.ts +20 -0
- package/dist/svelte/connect-action.d.ts.map +1 -0
- package/dist/svelte/connect-action.js +40 -0
- package/dist/svelte/docs/integrations-svelte.catalog.docs.descriptor.d.ts +4 -0
- package/dist/svelte/docs/integrations-svelte.catalog.docs.descriptor.d.ts.map +1 -0
- package/dist/svelte/docs/integrations-svelte.catalog.docs.descriptor.js +30 -0
- package/dist/svelte/index.d.ts +12 -0
- package/dist/svelte/index.d.ts.map +1 -0
- package/dist/svelte/index.js +7 -0
- package/dist/svelte/integrations-svelte.plugin.d.ts +10 -0
- package/dist/svelte/integrations-svelte.plugin.d.ts.map +1 -0
- package/dist/svelte/integrations-svelte.plugin.js +16 -0
- package/dist/svelte/loaders.d.ts +17 -0
- package/dist/svelte/loaders.d.ts.map +1 -0
- package/dist/svelte/loaders.js +17 -0
- package/dist/svelte/runtime.d.ts +12 -0
- package/dist/svelte/runtime.d.ts.map +1 -0
- package/dist/svelte/runtime.js +11 -0
- package/dist/svelte/state.svelte.d.ts +30 -0
- package/dist/svelte/state.svelte.d.ts.map +1 -0
- package/dist/svelte/state.svelte.js +133 -0
- package/dist/svelte/styles.css +2 -0
- package/dist/svelte/types.d.ts +13 -0
- package/dist/svelte/types.d.ts.map +1 -0
- package/dist/svelte/types.js +1 -0
- package/package.json +223 -4
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { KindContext, RuntimeKindDescriptor } from '@vibesdotdev/kernel/schemas/kind';
|
|
2
|
+
import type { RuntimeDescriptor } from '@vibesdotdev/kernel/schemas/descriptor';
|
|
3
|
+
import type { CredentialCandidates } from '@vibesdotdev/secrets/schemas';
|
|
4
|
+
import { type IntegrationConfig, type IntegrationConnectionSelector, type IntegrationCredentialSource, type IntegrationCredentialWriteMetadata, type IntegrationConfigValue, type IntegrationOAuthAuthorization, type IntegrationOAuthCompletion, type IntegrationOAuthStart, type IntegrationOAuthMetadata, type IntegrationOperationResult, type IntegrationProviderDescriptor, type IntegrationProviderImplementation, type IntegrationProviderResolvedConfiguration } from '../schemas/integration-descriptor.ts';
|
|
5
|
+
/**
|
|
6
|
+
* Base implementation of the integration/provider kind. Handles credential
|
|
7
|
+
* resolution, persistent config, OAuth lifecycle for descriptors that
|
|
8
|
+
* declare an `oauth` block, and operation dispatch.
|
|
9
|
+
*
|
|
10
|
+
* Provider packs (e.g. `@vibesdotdev/integrations-quickbooks`) extend this
|
|
11
|
+
* class to implement declared operations like `'sync'`. The runtime calls
|
|
12
|
+
* extended classes through plugin loaders registered on the
|
|
13
|
+
* `integration/provider` kind.
|
|
14
|
+
*/
|
|
15
|
+
export declare class BaseIntegrationProvider implements IntegrationProviderImplementation {
|
|
16
|
+
readonly descriptor: IntegrationProviderDescriptor;
|
|
17
|
+
private readonly settingsReader;
|
|
18
|
+
private readonly store;
|
|
19
|
+
protected readonly context: KindContext;
|
|
20
|
+
constructor(descriptor: RuntimeDescriptor, context: KindContext);
|
|
21
|
+
/** Throw if the descriptor doesn't declare the named operation. */
|
|
22
|
+
protected requireDeclaredOperation(opId: string): void;
|
|
23
|
+
/** Provider policy applies to every read before Core resolves metadata or values. */
|
|
24
|
+
protected credentialSources(): Promise<IntegrationCredentialSource[]>;
|
|
25
|
+
getConfig(): Promise<IntegrationConfig>;
|
|
26
|
+
getCredentials(): Promise<Record<string, string>>;
|
|
27
|
+
getCredential(key: string): Promise<string | undefined>;
|
|
28
|
+
/** Return metadata references without resolving credential values in provider code. */
|
|
29
|
+
getCredentialCandidates(key: string): Promise<CredentialCandidates>;
|
|
30
|
+
private credentialCandidates;
|
|
31
|
+
/** Provider request configuration contains references; only Secrets reads their values. */
|
|
32
|
+
getCredentialUseConfiguration(selector?: Readonly<IntegrationConnectionSelector>): Promise<IntegrationProviderResolvedConfiguration>;
|
|
33
|
+
private loadSelectedConnection;
|
|
34
|
+
getResolvedConfiguration(selector?: Readonly<IntegrationConnectionSelector>): Promise<IntegrationProviderResolvedConfiguration>;
|
|
35
|
+
setCredential(key: string, value: string, metadata?: IntegrationCredentialWriteMetadata): Promise<void>;
|
|
36
|
+
unsetCredential(key: string): Promise<void>;
|
|
37
|
+
startAuthorization(input: IntegrationOAuthStart): Promise<IntegrationOAuthAuthorization>;
|
|
38
|
+
completeAuthorization(input: IntegrationOAuthCompletion): Promise<void>;
|
|
39
|
+
private missingStoreError;
|
|
40
|
+
invokeOperation<T = unknown>(opId: string, args?: IntegrationConfigValue): Promise<IntegrationOperationResult<T>>;
|
|
41
|
+
protected requireOAuthMetadata(): IntegrationOAuthMetadata;
|
|
42
|
+
private requireWriteableSource;
|
|
43
|
+
private scopedSource;
|
|
44
|
+
private resolveCredentialSource;
|
|
45
|
+
private resolveCredentialSet;
|
|
46
|
+
private readSelectedConnection;
|
|
47
|
+
private hostCredentialSources;
|
|
48
|
+
private mergeCredentialReferences;
|
|
49
|
+
private resolveSelectedConnection;
|
|
50
|
+
private protectHostEndpointConfiguration;
|
|
51
|
+
private resolveCredentialReference;
|
|
52
|
+
/**
|
|
53
|
+
* Build org-scoped scopeId for credential lookup.
|
|
54
|
+
* Combines the live request orgId with the descriptor-level scopeId.
|
|
55
|
+
* Format: {orgId}:{scopeId} when both present, {orgId} when only orgId.
|
|
56
|
+
*/
|
|
57
|
+
private resolveScopeId;
|
|
58
|
+
/**
|
|
59
|
+
* Resolve the CALLING request's org id at call time. The integration
|
|
60
|
+
* provider instance is value-cached by the runtime for the isolate
|
|
61
|
+
* lifetime, so a constructor-snapshotted `this.context.orgId` would
|
|
62
|
+
* freeze to whichever org FIRST resolved the provider — every later
|
|
63
|
+
* request (any org) would then read and write that first org's
|
|
64
|
+
* credentials and config (cross-tenant bleed; the Microsoft pack fixed
|
|
65
|
+
* the same bug in ac19f2688). Read the live request context instead
|
|
66
|
+
* (`runtime.context('orgId')`, set by hosts via runWithRequestContext),
|
|
67
|
+
* falling back to the constructor-injected context orgId, then
|
|
68
|
+
* undefined (system/platform scope).
|
|
69
|
+
*/
|
|
70
|
+
protected getCurrentOrgId(): Promise<string | undefined>;
|
|
71
|
+
private getSettingValue;
|
|
72
|
+
}
|
|
73
|
+
export declare const integrationProviderKind: RuntimeKindDescriptor<IntegrationProviderDescriptor, IntegrationProviderImplementation, KindContext>;
|
|
74
|
+
//# sourceMappingURL=integration.kind.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"integration.kind.d.ts","sourceRoot":"","sources":["../../src/kinds/integration.kind.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,542 @@
|
|
|
1
|
+
import { parseIntegrationBoundary, IntegrationContextOrgIdSchema } from "../schemas/boundary.js";
|
|
2
|
+
import { IntegrationCredentialSourceSchema } from "../schemas/integration-descriptor.js";
|
|
3
|
+
import { CapabilityError } from '@vibesdotdev/kernel';
|
|
4
|
+
import { CredentialReferenceSchema } from '@vibesdotdev/kernel/credentials';
|
|
5
|
+
import { applyIntegrationClientId, exchangeIntegrationAuthorizationCode } from "../server/integration-oauth.js";
|
|
6
|
+
import { IntegrationConfigValueSchema, IntegrationConfigSchema, IntegrationOperationInputSchema, IntegrationConnectionSelectorSchema, IntegrationProviderResolvedConfigurationSchema, IntegrationOAuthStartSchema, IntegrationOAuthCompletionSchema, IntegrationCredentialKeySchema, IntegrationCredentialWriteRequestSchema, IntegrationProviderDescriptorSchema } from "../schemas/integration-descriptor.js";
|
|
7
|
+
import { isIntegrationStoreAdapter } from "./integration.store.js";
|
|
8
|
+
import { integrationStoreContextId } from "./integration-store.context.descriptor.js";
|
|
9
|
+
import { createUnavailableSystemSettingsReader, isSystemSettingsReader, systemSettingsReaderContextId } from "../settings/context.js";
|
|
10
|
+
/**
|
|
11
|
+
* Base implementation of the integration/provider kind. Handles credential
|
|
12
|
+
* resolution, persistent config, OAuth lifecycle for descriptors that
|
|
13
|
+
* declare an `oauth` block, and operation dispatch.
|
|
14
|
+
*
|
|
15
|
+
* Provider packs (e.g. `@vibesdotdev/integrations-quickbooks`) extend this
|
|
16
|
+
* class to implement declared operations like `'sync'`. The runtime calls
|
|
17
|
+
* extended classes through plugin loaders registered on the
|
|
18
|
+
* `integration/provider` kind.
|
|
19
|
+
*/
|
|
20
|
+
export class BaseIntegrationProvider {
|
|
21
|
+
descriptor;
|
|
22
|
+
settingsReader;
|
|
23
|
+
store;
|
|
24
|
+
context;
|
|
25
|
+
constructor(descriptor, context) {
|
|
26
|
+
this.descriptor = parseIntegrationBoundary(IntegrationProviderDescriptorSchema, descriptor);
|
|
27
|
+
this.context = context;
|
|
28
|
+
// Use a structural guard, not a nullish check: the runtime fills an
|
|
29
|
+
// unwired context slot with an inert placeholder that is truthy but
|
|
30
|
+
// has no `.get()`, which would defeat `?? createUnavailable...` and
|
|
31
|
+
// throw "settingsReader.get is not a function" on the first getConfig()
|
|
32
|
+
// of any descriptor with a configSettingKey. isSystemSettingsReader
|
|
33
|
+
// only accepts a real injected reader.
|
|
34
|
+
const injectedSettingsReader = context[systemSettingsReaderContextId];
|
|
35
|
+
this.settingsReader = isSystemSettingsReader(injectedSettingsReader)
|
|
36
|
+
? injectedSettingsReader
|
|
37
|
+
: createUnavailableSystemSettingsReader(context.runtime);
|
|
38
|
+
const injectedStore = context[integrationStoreContextId];
|
|
39
|
+
this.store = isIntegrationStoreAdapter(injectedStore) ? injectedStore : undefined;
|
|
40
|
+
}
|
|
41
|
+
/** Throw if the descriptor doesn't declare the named operation. */
|
|
42
|
+
requireDeclaredOperation(opId) {
|
|
43
|
+
const declared = (this.descriptor.operations ?? []).find((op) => op.id === opId);
|
|
44
|
+
if (!declared) {
|
|
45
|
+
throw new CapabilityError('The integration operation is not declared.', 'INVALID_INPUT', { reason: 'integration-operation-undeclared', retryable: false });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/** Provider policy applies to every read before Core resolves metadata or values. */
|
|
49
|
+
async credentialSources() {
|
|
50
|
+
return this.descriptor.credentials;
|
|
51
|
+
}
|
|
52
|
+
async getConfig() {
|
|
53
|
+
if (this.descriptor.configurationSource === 'host')
|
|
54
|
+
return { ...this.descriptor.config };
|
|
55
|
+
let config = { ...this.descriptor.config };
|
|
56
|
+
const settingKey = this.descriptor.configSettingKey;
|
|
57
|
+
if (settingKey) {
|
|
58
|
+
const settingValue = await this.getSettingValue(settingKey);
|
|
59
|
+
if (settingValue !== undefined) {
|
|
60
|
+
config = { ...config, ...parseIntegrationBoundary(IntegrationConfigSchema, settingValue) };
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// Scope the persisted-config read to this actor's org using the SAME
|
|
64
|
+
// selector the credential reads use (scope: 'org' + composite
|
|
65
|
+
// scopeId derived from the live request orgId). Without this,
|
|
66
|
+
// getConfig() returns whichever org's `core` row was updated most
|
|
67
|
+
// recently — a cross-tenant config bleed (org A reads org B's
|
|
68
|
+
// realmId etc.).
|
|
69
|
+
const orgId = await this.getCurrentOrgId();
|
|
70
|
+
const serviceConfig = await this.store?.loadConfig(this.descriptor.id, {
|
|
71
|
+
scope: 'org',
|
|
72
|
+
scopeId: this.resolveScopeId(undefined, orgId)
|
|
73
|
+
});
|
|
74
|
+
if (serviceConfig) {
|
|
75
|
+
config = { ...config, ...parseIntegrationBoundary(IntegrationConfigSchema, serviceConfig) };
|
|
76
|
+
}
|
|
77
|
+
return config;
|
|
78
|
+
}
|
|
79
|
+
async getCredentials() {
|
|
80
|
+
return (await this.resolveCredentialSet()).credentials;
|
|
81
|
+
}
|
|
82
|
+
async getCredential(key) {
|
|
83
|
+
const credentials = await this.getCredentials();
|
|
84
|
+
return credentials[key];
|
|
85
|
+
}
|
|
86
|
+
/** Return metadata references without resolving credential values in provider code. */
|
|
87
|
+
async getCredentialCandidates(key) {
|
|
88
|
+
key = parseIntegrationBoundary(IntegrationCredentialKeySchema, key);
|
|
89
|
+
const sources = (await this.credentialSources()).filter(source => source.key === key);
|
|
90
|
+
return this.credentialCandidates(sources);
|
|
91
|
+
}
|
|
92
|
+
async credentialCandidates(sources) {
|
|
93
|
+
const candidates = [];
|
|
94
|
+
for (const source of sources) {
|
|
95
|
+
if (source.source === 'setting') {
|
|
96
|
+
const value = await this.getSettingValue(source.settingKey);
|
|
97
|
+
if (value !== undefined)
|
|
98
|
+
candidates.push(parseIntegrationBoundary(CredentialReferenceSchema, value));
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (source.reference !== undefined && source.scope === undefined) {
|
|
102
|
+
candidates.push(source.reference);
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (this.store) {
|
|
106
|
+
const orgId = source.scope === undefined || source.scope === 'org' ? await this.getCurrentOrgId() : undefined;
|
|
107
|
+
const selected = await this.store.loadCredential(this.descriptor.id, this.scopedSource(source, orgId));
|
|
108
|
+
if (selected)
|
|
109
|
+
candidates.push(parseIntegrationBoundary(CredentialReferenceSchema, selected));
|
|
110
|
+
}
|
|
111
|
+
if (source.reference)
|
|
112
|
+
candidates.push(source.reference);
|
|
113
|
+
}
|
|
114
|
+
return candidates;
|
|
115
|
+
}
|
|
116
|
+
/** Provider request configuration contains references; only Secrets reads their values. */
|
|
117
|
+
async getCredentialUseConfiguration(selector) {
|
|
118
|
+
if (selector && this.descriptor.configurationSource === 'host') {
|
|
119
|
+
throw new CapabilityError('Host integration configuration cannot select a saved account.', 'CONFLICT', {
|
|
120
|
+
reason: 'integration-host-credential-selection-conflict', retryable: false
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
let selected;
|
|
124
|
+
if (selector) {
|
|
125
|
+
const load = this.store?.loadConnectionReferences;
|
|
126
|
+
if (!load)
|
|
127
|
+
throw new CapabilityError('Saved connection reference delivery is unavailable.', 'UNAVAILABLE', {
|
|
128
|
+
reason: 'integration-connection-references-unsupported', retryable: false
|
|
129
|
+
});
|
|
130
|
+
selected = await this.loadSelectedConnection(selector, load);
|
|
131
|
+
}
|
|
132
|
+
const declaredSources = await this.credentialSources();
|
|
133
|
+
const sources = selected ? this.hostCredentialSources(declaredSources) : declaredSources;
|
|
134
|
+
const entries = [];
|
|
135
|
+
for (const key of new Set(sources.map(source => source.key))) {
|
|
136
|
+
const candidates = await this.credentialCandidates(sources.filter(source => source.key === key));
|
|
137
|
+
if (candidates.length > 0)
|
|
138
|
+
entries.push([key, candidates]);
|
|
139
|
+
}
|
|
140
|
+
const references = Object.fromEntries(entries);
|
|
141
|
+
const hostKeys = new Set(sources
|
|
142
|
+
.filter(source => source.source === 'setting' || source.reference !== undefined)
|
|
143
|
+
.map(source => source.key));
|
|
144
|
+
return parseIntegrationBoundary(IntegrationProviderResolvedConfigurationSchema, {
|
|
145
|
+
config: this.protectHostEndpointConfiguration(selected?.config ?? await this.getConfig(), hostKeys),
|
|
146
|
+
credentials: {},
|
|
147
|
+
credentialReferences: this.mergeCredentialReferences(references, selected?.credentialReferences ?? {})
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
async loadSelectedConnection(selector, load) {
|
|
151
|
+
selector = parseIntegrationBoundary(IntegrationConnectionSelectorSchema, selector);
|
|
152
|
+
const runtime = this.context.runtime;
|
|
153
|
+
if (!runtime) {
|
|
154
|
+
throw new CapabilityError('Selected integration delivery requires its authenticated runtime request.', 'UNAVAILABLE', { reason: 'integration-runtime-required', retryable: false });
|
|
155
|
+
}
|
|
156
|
+
const principal = runtime.requestPrincipal();
|
|
157
|
+
if (principal.kind === 'service') {
|
|
158
|
+
const account = principal.delegation?.account;
|
|
159
|
+
if (!account || principal.onBehalfOf !== account.ownerId || account.integrationId !== this.descriptor.id || account.connectionId !== selector.connectionId) {
|
|
160
|
+
throw new CapabilityError('Selected integration delivery requires verified authority for this account.', 'PERMISSION_DENIED', { reason: 'integration-account-delegation-mismatch', retryable: false });
|
|
161
|
+
}
|
|
162
|
+
const actor = { userId: account.ownerId };
|
|
163
|
+
if (account.orgId !== undefined)
|
|
164
|
+
actor.organizationId = account.orgId;
|
|
165
|
+
return this.readSelectedConnection(load, selector, actor);
|
|
166
|
+
}
|
|
167
|
+
if (principal.kind !== 'user')
|
|
168
|
+
throw new CapabilityError('Selected integration delivery requires an authenticated user.', 'PERMISSION_DENIED', { reason: 'integration-account-principal-required', retryable: false });
|
|
169
|
+
const actor = { userId: principal.userId };
|
|
170
|
+
if (principal.orgId !== undefined)
|
|
171
|
+
actor.organizationId = principal.orgId;
|
|
172
|
+
return this.readSelectedConnection(load, selector, actor);
|
|
173
|
+
}
|
|
174
|
+
async getResolvedConfiguration(selector) {
|
|
175
|
+
if (selector && this.descriptor.configurationSource === 'host') {
|
|
176
|
+
throw new CapabilityError('Host integration configuration cannot select a saved account.', 'CONFLICT', {
|
|
177
|
+
reason: 'integration-host-credential-selection-conflict', retryable: false
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
if (selector) {
|
|
181
|
+
const load = this.store?.loadConnection;
|
|
182
|
+
if (!load)
|
|
183
|
+
throw new CapabilityError('Saved connection selection is unavailable.', 'UNAVAILABLE', { reason: 'integration-connection-selection-unsupported' });
|
|
184
|
+
return this.resolveSelectedConnection(await this.loadSelectedConnection(selector, load));
|
|
185
|
+
}
|
|
186
|
+
const [config, resolved] = await Promise.all([
|
|
187
|
+
this.getConfig(),
|
|
188
|
+
this.resolveCredentialSet()
|
|
189
|
+
]);
|
|
190
|
+
return {
|
|
191
|
+
config: this.protectHostEndpointConfiguration(config, resolved.hostCredentialKeys),
|
|
192
|
+
credentials: resolved.credentials,
|
|
193
|
+
credentialReferences: resolved.credentialReferences
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
async setCredential(key, value, metadata) {
|
|
197
|
+
const request = parseIntegrationBoundary(IntegrationCredentialWriteRequestSchema, {
|
|
198
|
+
key,
|
|
199
|
+
value,
|
|
200
|
+
metadata
|
|
201
|
+
});
|
|
202
|
+
const source = this.requireWriteableSource(request.key);
|
|
203
|
+
const orgId = source.scope === undefined || source.scope === 'org'
|
|
204
|
+
? await this.getCurrentOrgId()
|
|
205
|
+
: undefined;
|
|
206
|
+
const scopedSource = this.scopedSource(source, orgId);
|
|
207
|
+
if (!this.store)
|
|
208
|
+
throw this.missingStoreError();
|
|
209
|
+
await this.store.writeCredential(this.descriptor.id, scopedSource, request.value, request.metadata);
|
|
210
|
+
}
|
|
211
|
+
async unsetCredential(key) {
|
|
212
|
+
key = parseIntegrationBoundary(IntegrationCredentialKeySchema, key);
|
|
213
|
+
const source = this.requireWriteableSource(key);
|
|
214
|
+
const orgId = source.scope === undefined || source.scope === 'org'
|
|
215
|
+
? await this.getCurrentOrgId()
|
|
216
|
+
: undefined;
|
|
217
|
+
const scopedSource = this.scopedSource(source, orgId);
|
|
218
|
+
if (!this.store)
|
|
219
|
+
throw this.missingStoreError();
|
|
220
|
+
await this.store.deleteCredential(this.descriptor.id, scopedSource);
|
|
221
|
+
}
|
|
222
|
+
async startAuthorization(input) {
|
|
223
|
+
input = parseIntegrationBoundary(IntegrationOAuthStartSchema, input);
|
|
224
|
+
const oauth = this.requireOAuthMetadata();
|
|
225
|
+
const clientId = await this.getCredentialCandidates('clientId');
|
|
226
|
+
if (clientId.length === 0) {
|
|
227
|
+
throw new CapabilityError('Integration OAuth requires a client identifier.', 'UNAVAILABLE', { reason: 'integration-oauth-client-id-required', retryable: false });
|
|
228
|
+
}
|
|
229
|
+
const params = new URLSearchParams({
|
|
230
|
+
client_id: '{clientId}',
|
|
231
|
+
redirect_uri: input.redirectUri,
|
|
232
|
+
response_type: 'code',
|
|
233
|
+
state: input.state
|
|
234
|
+
});
|
|
235
|
+
const scopes = input.scopes ?? oauth.scopes;
|
|
236
|
+
if (scopes.length > 0) {
|
|
237
|
+
params.set('scope', scopes.join(' '));
|
|
238
|
+
}
|
|
239
|
+
for (const [key, value] of Object.entries(oauth.authorizationParams ?? {})) {
|
|
240
|
+
params.set(key, value);
|
|
241
|
+
}
|
|
242
|
+
try {
|
|
243
|
+
return { authorizationUrl: await applyIntegrationClientId(this.context.runtime, {
|
|
244
|
+
authorizationUrl: `${oauth.authorizationEndpoint}?${params.toString()}`,
|
|
245
|
+
clientId
|
|
246
|
+
}) };
|
|
247
|
+
}
|
|
248
|
+
catch (error) {
|
|
249
|
+
if (error instanceof CapabilityError && error.reason === 'oauth-client-credential-unset') {
|
|
250
|
+
throw new CapabilityError('Integration OAuth requires a client identifier.', 'UNAVAILABLE', { reason: 'integration-oauth-client-id-required', retryable: false });
|
|
251
|
+
}
|
|
252
|
+
throw error;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
async completeAuthorization(input) {
|
|
256
|
+
input = parseIntegrationBoundary(IntegrationOAuthCompletionSchema, input);
|
|
257
|
+
const oauth = this.requireOAuthMetadata();
|
|
258
|
+
const tokens = await exchangeIntegrationAuthorizationCode(this.context.runtime, {
|
|
259
|
+
tokenEndpoint: oauth.tokenEndpoint,
|
|
260
|
+
clientId: await this.getCredentialCandidates('clientId'),
|
|
261
|
+
clientSecret: await this.getCredentialCandidates('clientSecret'),
|
|
262
|
+
clientAuthentication: oauth.clientAuthentication,
|
|
263
|
+
bodyEncoding: oauth.bodyEncoding,
|
|
264
|
+
headers: oauth.headers,
|
|
265
|
+
code: input.code,
|
|
266
|
+
redirectUri: input.redirectUri
|
|
267
|
+
});
|
|
268
|
+
// Persist callback-derived config first (e.g. QuickBooks realmId,
|
|
269
|
+
// Microsoft tenant) so subsequent getConfig() reads see them and
|
|
270
|
+
// the integration_services row is up-to-date before the secrets
|
|
271
|
+
// land via setCredential.
|
|
272
|
+
const configPatch = {};
|
|
273
|
+
for (const param of oauth.configFromCallback) {
|
|
274
|
+
const value = input.params?.[param];
|
|
275
|
+
if (value !== undefined)
|
|
276
|
+
configPatch[param] = value;
|
|
277
|
+
}
|
|
278
|
+
if (Object.keys(configPatch).length > 0) {
|
|
279
|
+
const orgId = await this.getCurrentOrgId();
|
|
280
|
+
if (!this.store)
|
|
281
|
+
throw this.missingStoreError();
|
|
282
|
+
await this.store.writeConfig(this.descriptor.id, { scope: 'org', scopeId: this.resolveScopeId(undefined, orgId) }, configPatch);
|
|
283
|
+
}
|
|
284
|
+
const accessExpiresAt = tokens.expires_in
|
|
285
|
+
? new Date(Date.now() + tokens.expires_in * 1000).toISOString()
|
|
286
|
+
: undefined;
|
|
287
|
+
const refreshExpiry = tokens.x_refresh_token_expires_in ?? tokens.refresh_token_expires_in;
|
|
288
|
+
const refreshExpiresAt = refreshExpiry
|
|
289
|
+
? new Date(Date.now() + refreshExpiry * 1000).toISOString()
|
|
290
|
+
: undefined;
|
|
291
|
+
await this.setCredential('accessToken', tokens.access_token, {
|
|
292
|
+
expiresAt: accessExpiresAt,
|
|
293
|
+
tags: { tokenType: 'access' }
|
|
294
|
+
});
|
|
295
|
+
if (tokens.refresh_token) {
|
|
296
|
+
await this.setCredential('refreshToken', tokens.refresh_token, {
|
|
297
|
+
expiresAt: refreshExpiresAt,
|
|
298
|
+
tags: { tokenType: 'refresh' }
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
missingStoreError() {
|
|
303
|
+
return new CapabilityError('The integration store is not mounted.', 'UNAVAILABLE', { reason: 'integration-store-unavailable', retryable: false });
|
|
304
|
+
}
|
|
305
|
+
async invokeOperation(opId, args) {
|
|
306
|
+
parseIntegrationBoundary(IntegrationOperationInputSchema, args);
|
|
307
|
+
const declared = (this.descriptor.operations ?? []).find((op) => op.id === opId);
|
|
308
|
+
if (!declared) {
|
|
309
|
+
throw new CapabilityError('The integration operation is not declared.', 'INVALID_INPUT', { reason: 'integration-operation-undeclared', retryable: false });
|
|
310
|
+
}
|
|
311
|
+
throw new CapabilityError('The integration operation has no implementation.', 'UNAVAILABLE', { reason: 'integration-operation-unimplemented', retryable: false });
|
|
312
|
+
}
|
|
313
|
+
requireOAuthMetadata() {
|
|
314
|
+
const oauth = this.descriptor.oauth;
|
|
315
|
+
if (!oauth) {
|
|
316
|
+
throw new CapabilityError('Integration OAuth metadata is not declared.', 'UNAVAILABLE', { reason: 'integration-oauth-metadata-required', retryable: false });
|
|
317
|
+
}
|
|
318
|
+
return oauth;
|
|
319
|
+
}
|
|
320
|
+
requireWriteableSource(key) {
|
|
321
|
+
key = parseIntegrationBoundary(IntegrationCredentialKeySchema, key);
|
|
322
|
+
const declared = (this.descriptor.credentials ?? []).filter((source) => source.key === key);
|
|
323
|
+
const source = declared.find((candidate) => candidate.source === 'secret'
|
|
324
|
+
&& (candidate.scope !== undefined || candidate.reference === undefined));
|
|
325
|
+
if (!source) {
|
|
326
|
+
if (declared.some((candidate) => candidate.reference !== undefined && candidate.scope === undefined)) {
|
|
327
|
+
throw new CapabilityError('Host credential references are read-only.', 'INVALID_INPUT', { reason: 'integration-host-credential-readonly', retryable: false });
|
|
328
|
+
}
|
|
329
|
+
throw new CapabilityError('The integration credential is not declared as a managed secret.', 'INVALID_INPUT', { reason: 'integration-credential-source-readonly', retryable: false });
|
|
330
|
+
}
|
|
331
|
+
return source;
|
|
332
|
+
}
|
|
333
|
+
scopedSource(source, orgId) {
|
|
334
|
+
const scope = source.scope ?? 'org';
|
|
335
|
+
let scopeId;
|
|
336
|
+
if (scope === 'org') {
|
|
337
|
+
scopeId = this.resolveScopeId(source.scopeId, orgId);
|
|
338
|
+
}
|
|
339
|
+
else if (source.scopeId !== undefined) {
|
|
340
|
+
scopeId = source.scopeId;
|
|
341
|
+
}
|
|
342
|
+
else {
|
|
343
|
+
throw new CapabilityError('An integration credential requires a complete managed scope.', 'INVALID_INPUT', { reason: 'integration-credential-scope-incomplete', retryable: false });
|
|
344
|
+
}
|
|
345
|
+
return parseIntegrationBoundary(IntegrationCredentialSourceSchema, {
|
|
346
|
+
...source,
|
|
347
|
+
scope,
|
|
348
|
+
scopeId,
|
|
349
|
+
providerType: source.providerType ?? this.descriptor.provider
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
async resolveCredentialSource(source, orgId) {
|
|
353
|
+
if (source.source === 'setting') {
|
|
354
|
+
const settingKey = source.settingKey;
|
|
355
|
+
if (!settingKey) {
|
|
356
|
+
throw new CapabilityError('The credential setting key is not declared.', 'INVALID_INPUT', {
|
|
357
|
+
reason: 'integration-credential-setting-key-required', retryable: false
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
const value = await this.getSettingValue(settingKey);
|
|
361
|
+
if (value === undefined)
|
|
362
|
+
return { value: undefined, hostReference: false };
|
|
363
|
+
const reference = parseIntegrationBoundary(CredentialReferenceSchema, value);
|
|
364
|
+
const resolved = await this.resolveCredentialReference(reference, source.required);
|
|
365
|
+
return { value: resolved, hostReference: resolved !== undefined, reference };
|
|
366
|
+
}
|
|
367
|
+
if (source.reference !== undefined && source.scope === undefined) {
|
|
368
|
+
const resolved = await this.resolveCredentialReference(source.reference, source.required);
|
|
369
|
+
return { value: resolved, hostReference: resolved !== undefined, reference: source.reference };
|
|
370
|
+
}
|
|
371
|
+
if (!this.store) {
|
|
372
|
+
if (source.required)
|
|
373
|
+
throw this.missingStoreError();
|
|
374
|
+
return { value: undefined, hostReference: false };
|
|
375
|
+
}
|
|
376
|
+
const scopedSource = this.scopedSource(source, orgId);
|
|
377
|
+
const reference = await this.store.loadCredential(this.descriptor.id, scopedSource);
|
|
378
|
+
if (!reference) {
|
|
379
|
+
const resolved = await this.resolveCredentialReference(source.reference, source.required);
|
|
380
|
+
return { value: resolved, hostReference: resolved !== undefined, reference: source.reference };
|
|
381
|
+
}
|
|
382
|
+
// Metadata proved the tenant secret exists. A value lookup failure now
|
|
383
|
+
// propagates instead of being confused with an absent saved credential.
|
|
384
|
+
return {
|
|
385
|
+
value: await this.resolveCredentialReference(reference, true),
|
|
386
|
+
hostReference: false,
|
|
387
|
+
reference
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
async resolveCredentialSet(sources) {
|
|
391
|
+
sources ??= await this.credentialSources();
|
|
392
|
+
const credentials = {};
|
|
393
|
+
const credentialReferences = {};
|
|
394
|
+
const hostCredentialKeys = new Set();
|
|
395
|
+
const needsOrg = sources.some((source) => source.source === 'secret'
|
|
396
|
+
&& !(source.reference !== undefined && source.scope === undefined)
|
|
397
|
+
&& (source.scope === undefined || source.scope === 'org'));
|
|
398
|
+
const orgId = needsOrg ? await this.getCurrentOrgId() : undefined;
|
|
399
|
+
for (const source of sources) {
|
|
400
|
+
if (Object.hasOwn(credentials, source.key))
|
|
401
|
+
continue;
|
|
402
|
+
const resolved = await this.resolveCredentialSource(source, orgId);
|
|
403
|
+
if (resolved.value !== undefined) {
|
|
404
|
+
credentials[source.key] = resolved.value;
|
|
405
|
+
if (resolved.reference)
|
|
406
|
+
credentialReferences[source.key] = [resolved.reference];
|
|
407
|
+
if (resolved.hostReference)
|
|
408
|
+
hostCredentialKeys.add(source.key);
|
|
409
|
+
continue;
|
|
410
|
+
}
|
|
411
|
+
if (source.required) {
|
|
412
|
+
throw new CapabilityError('A required integration credential is absent.', 'UNAVAILABLE', { reason: 'integration-credential-required', retryable: false });
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
return { credentials, credentialReferences, hostCredentialKeys };
|
|
416
|
+
}
|
|
417
|
+
async readSelectedConnection(load, selector, actor) {
|
|
418
|
+
const store = this.store;
|
|
419
|
+
if (!store) {
|
|
420
|
+
throw new CapabilityError('Saved connection selection is unavailable.', 'UNAVAILABLE', {
|
|
421
|
+
reason: 'integration-connection-selection-unsupported', retryable: false
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
return parseIntegrationBoundary(IntegrationProviderResolvedConfigurationSchema, await load.call(store, this.descriptor, selector, actor));
|
|
425
|
+
}
|
|
426
|
+
hostCredentialSources(sources) {
|
|
427
|
+
return sources.filter((source) => source.source === 'setting'
|
|
428
|
+
|| (source.reference !== undefined && source.scope === undefined));
|
|
429
|
+
}
|
|
430
|
+
mergeCredentialReferences(host, selected) {
|
|
431
|
+
return {
|
|
432
|
+
...selected,
|
|
433
|
+
...Object.fromEntries(Object.entries(host).map(([key, candidates]) => {
|
|
434
|
+
const accountCandidates = Object.hasOwn(selected, key) ? selected[key] : [];
|
|
435
|
+
return [key, [...candidates, ...accountCandidates]];
|
|
436
|
+
}))
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
async resolveSelectedConnection(selected) {
|
|
440
|
+
const hostSources = this.hostCredentialSources(await this.credentialSources());
|
|
441
|
+
const host = await this.resolveCredentialSet(hostSources);
|
|
442
|
+
// A resolved host value wins; a confirmed-absent optional host source
|
|
443
|
+
// advances to the selected account, as reference delivery does.
|
|
444
|
+
return parseIntegrationBoundary(IntegrationProviderResolvedConfigurationSchema, {
|
|
445
|
+
config: this.protectHostEndpointConfiguration(selected.config, host.hostCredentialKeys),
|
|
446
|
+
credentials: { ...selected.credentials, ...host.credentials },
|
|
447
|
+
credentialReferences: this.mergeCredentialReferences(host.credentialReferences, selected.credentialReferences)
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
protectHostEndpointConfiguration(config, hostCredentialKeys) {
|
|
451
|
+
if (hostCredentialKeys.size === 0)
|
|
452
|
+
return config;
|
|
453
|
+
const protectedConfig = { ...config };
|
|
454
|
+
for (const key of new Set(['baseUrl', ...this.descriptor.protectedConfigKeys])) {
|
|
455
|
+
const declared = key === 'baseUrl' ? this.descriptor.baseUrl ?? this.descriptor.config[key] : this.descriptor.config[key];
|
|
456
|
+
if (declared === undefined)
|
|
457
|
+
delete protectedConfig[key];
|
|
458
|
+
else
|
|
459
|
+
protectedConfig[key] = declared;
|
|
460
|
+
}
|
|
461
|
+
return protectedConfig;
|
|
462
|
+
}
|
|
463
|
+
async resolveCredentialReference(reference, required) {
|
|
464
|
+
const runtime = this.context.runtime;
|
|
465
|
+
if (!runtime) {
|
|
466
|
+
throw new CapabilityError('Integration credentials require the caller runtime.', 'UNAVAILABLE', { reason: 'integration-runtime-required', retryable: false });
|
|
467
|
+
}
|
|
468
|
+
if (!reference)
|
|
469
|
+
return undefined;
|
|
470
|
+
const result = await runtime.resolveCredential(reference);
|
|
471
|
+
if (result.ok)
|
|
472
|
+
return result.value;
|
|
473
|
+
if (!required && result.error.code === 'NOT_FOUND' && result.error.reason === 'credential-not-set') {
|
|
474
|
+
return undefined;
|
|
475
|
+
}
|
|
476
|
+
throw result.error;
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Build org-scoped scopeId for credential lookup.
|
|
480
|
+
* Combines the live request orgId with the descriptor-level scopeId.
|
|
481
|
+
* Format: {orgId}:{scopeId} when both present, {orgId} when only orgId.
|
|
482
|
+
*/
|
|
483
|
+
resolveScopeId(descriptorScopeId, orgId) {
|
|
484
|
+
if (!orgId) {
|
|
485
|
+
throw new CapabilityError('Organization identity is required for integration access.', 'PERMISSION_DENIED', {
|
|
486
|
+
reason: 'integration-organization-required', retryable: false
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
if (!descriptorScopeId)
|
|
490
|
+
return orgId;
|
|
491
|
+
return `${orgId}:${descriptorScopeId}`;
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Resolve the CALLING request's org id at call time. The integration
|
|
495
|
+
* provider instance is value-cached by the runtime for the isolate
|
|
496
|
+
* lifetime, so a constructor-snapshotted `this.context.orgId` would
|
|
497
|
+
* freeze to whichever org FIRST resolved the provider — every later
|
|
498
|
+
* request (any org) would then read and write that first org's
|
|
499
|
+
* credentials and config (cross-tenant bleed; the Microsoft pack fixed
|
|
500
|
+
* the same bug in ac19f2688). Read the live request context instead
|
|
501
|
+
* (`runtime.context('orgId')`, set by hosts via runWithRequestContext),
|
|
502
|
+
* falling back to the constructor-injected context orgId, then
|
|
503
|
+
* undefined (system/platform scope).
|
|
504
|
+
*/
|
|
505
|
+
async getCurrentOrgId() {
|
|
506
|
+
const runtime = this.context.runtime;
|
|
507
|
+
if (runtime?.hasContext('orgId')) {
|
|
508
|
+
const value = parseIntegrationBoundary(IntegrationContextOrgIdSchema, await runtime.context('orgId'));
|
|
509
|
+
if (value !== undefined)
|
|
510
|
+
return value;
|
|
511
|
+
}
|
|
512
|
+
return parseIntegrationBoundary(IntegrationContextOrgIdSchema, this.context.orgId);
|
|
513
|
+
}
|
|
514
|
+
async getSettingValue(settingKey) {
|
|
515
|
+
const settingValue = await this.settingsReader.get(settingKey);
|
|
516
|
+
if (settingValue === undefined)
|
|
517
|
+
return undefined;
|
|
518
|
+
return parseIntegrationBoundary(IntegrationConfigValueSchema, settingValue);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
function resolveIntegrationProvider(candidates, scope) {
|
|
522
|
+
const typedCandidates = candidates.map((candidate) => parseIntegrationBoundary(IntegrationProviderDescriptorSchema, candidate));
|
|
523
|
+
if (typedCandidates.length === 0)
|
|
524
|
+
return undefined;
|
|
525
|
+
const enabled = typedCandidates.filter((descriptor) => descriptor.enabled !== false);
|
|
526
|
+
const matching = enabled.filter((descriptor) => {
|
|
527
|
+
if (!descriptor.hardware || descriptor.hardware.length === 0)
|
|
528
|
+
return true;
|
|
529
|
+
return scope.hardware !== undefined && descriptor.hardware.includes(scope.hardware);
|
|
530
|
+
});
|
|
531
|
+
if (matching.length === 0)
|
|
532
|
+
return enabled[0] ?? typedCandidates[0];
|
|
533
|
+
return [...matching].sort((left, right) => (right.priority ?? 0) - (left.priority ?? 0))[0];
|
|
534
|
+
}
|
|
535
|
+
export const integrationProviderKind = {
|
|
536
|
+
id: 'integration/provider',
|
|
537
|
+
descriptorSchema: IntegrationProviderDescriptorSchema,
|
|
538
|
+
defaultImplementation: BaseIntegrationProvider,
|
|
539
|
+
resolve: resolveIntegrationProvider,
|
|
540
|
+
contexts: [integrationStoreContextId]
|
|
541
|
+
};
|
|
542
|
+
//# sourceMappingURL=integration.kind.js.map
|