import * as Effect from "effect/Effect"; import * as Option from "effect/Option"; import type { SourceCatalogSyncResult } from "@executor/source-core"; import type { SourceCatalogKind, } from "@executor/source-core"; import type { ExecutorEffect } from "./executor-effect"; import type { ExecutorScopeContext } from "./scope"; import { type ExecutorScopeConfigSource, type ExecutorScopeConfig, type Source as ExecutorSource, type SecretMaterial, type SecretMaterialPurpose, type SecretStore, } from "./schema"; import type { SourceInvokeInput, SourceInvokeResult, } from "@executor/source-core"; import type * as Schema from "effect/Schema"; import { runtimeEffectError } from "./runtime/effect-errors"; import { ExecutorStateStore, } from "./runtime/executor-state-store"; import { LocalToolRuntimeLoaderService, } from "./runtime/local-tool-runtime"; import { SecretMaterialDeleterService, SecretMaterialResolverService, SecretMaterialStorerService, SecretMaterialUpdaterService, } from "./runtime/scope/secret-material-providers"; import { ScopeConfigStore } from "./runtime/scope/storage"; export type PluginCleanup = { close: () => void | Promise; }; export type ExecutorSdkPluginHost = ExecutorSourcePluginInternalHost; export type ExecutorSdkPluginContext = { executor: ExecutorEffect & Record; scope: ExecutorScopeContext; host: ExecutorSdkPluginHost; }; export type ExecutorSdkPluginStartContext< TExtension extends object = {}, > = ExecutorSdkPluginContext & { extension: TExtension; }; type ExecutorSdkPluginInternals = { sources?: readonly ExecutorSourceContribution[]; secretStores?: readonly ExecutorSecretStoreContribution[]; managementTools?: readonly ExecutorManagementToolContribution[]; }; const executorSdkPluginInternalsSymbol = Symbol.for( "@executor/platform-sdk/plugins/internals", ); type ExecutorSdkPluginInternalCarrier = { [executorSdkPluginInternalsSymbol]?: ExecutorSdkPluginInternals; }; type ExecutorSourcePluginRuntimeServices = | LocalToolRuntimeLoaderService | SecretMaterialResolverService | SecretMaterialStorerService | SecretMaterialDeleterService | SecretMaterialUpdaterService; type ExecutorSourceContributionRuntimeServices = | ScopeConfigStore | ExecutorSourcePluginRuntimeServices; type ExecutorSecretStorePluginRuntimeServices = | SecretMaterialResolverService; type ExecutorSecretStoreContributionRuntimeServices = | ExecutorStateStore | ExecutorSecretStorePluginRuntimeServices; export type ExecutorSdkPlugin< TKey extends string = string, TExtension extends object = {}, > = { key: TKey; extendExecutor?: (input: ExecutorSdkPluginContext) => TExtension; start?: ( input: ExecutorSdkPluginStartContext, ) => Effect.Effect; } & ExecutorSdkPluginInternalCarrier; export const defineExecutorSdkPlugin = < const TPlugin extends ExecutorSdkPlugin, >( plugin: TPlugin, ): TPlugin => plugin; type ExecutorSourcePluginInternalHost = { sources: { create: (input: { source: Omit< ExecutorSource, "id" | "scopeId" | "createdAt" | "updatedAt" >; }) => Effect.Effect; get: (sourceId: ExecutorSource["id"]) => Effect.Effect; save: (source: ExecutorSource) => Effect.Effect; refreshCatalog: ( sourceId: ExecutorSource["id"], ) => Effect.Effect; remove: (sourceId: ExecutorSource["id"]) => Effect.Effect; }; }; type ExecutorSecretStorePluginInternalHost = { secretStores: { create: (input: { store: Omit< SecretStore, "id" | "scopeId" | "createdAt" | "updatedAt" >; }) => Effect.Effect; get: (storeId: SecretStore["id"]) => Effect.Effect; save: (store: SecretStore) => Effect.Effect; remove: (storeId: SecretStore["id"]) => Effect.Effect; }; }; export type ExecutorSourcePluginStorage = { get: (input: { scopeId: ExecutorSource["scopeId"]; sourceId: ExecutorSource["id"]; }) => Effect.Effect; put: (input: { scopeId: ExecutorSource["scopeId"]; sourceId: ExecutorSource["id"]; value: TStored; }) => Effect.Effect; remove?: (input: { scopeId: ExecutorSource["scopeId"]; sourceId: ExecutorSource["id"]; }) => Effect.Effect; }; export type ExecutorSecretStorePluginStorage = { get: (input: { scopeId: SecretStore["scopeId"]; storeId: SecretStore["id"]; }) => Effect.Effect; put: (input: { scopeId: SecretStore["scopeId"]; storeId: SecretStore["id"]; value: TStored; }) => Effect.Effect; remove?: (input: { scopeId: SecretStore["scopeId"]; storeId: SecretStore["id"]; }) => Effect.Effect; }; export type ExecutorSourcePluginApi< TConnectInput, TSourceConfig, TUpdateInput extends { sourceId: string; config: TSourceConfig; }, > = { getSource: ( sourceId: ExecutorSource["id"], ) => Effect.Effect; getSourceConfig: ( sourceId: ExecutorSource["id"], ) => Effect.Effect; createSource: ( input: TConnectInput, ) => Effect.Effect; updateSource: ( input: TUpdateInput, ) => Effect.Effect; refreshSource: ( sourceId: ExecutorSource["id"], ) => Effect.Effect; removeSource: ( sourceId: ExecutorSource["id"], ) => Effect.Effect; }; export type ExecutorSecretStorePluginApi< TConnectInput, TStoreConfig, TUpdateInput extends { storeId: string; config: TStoreConfig; }, > = { getStore: ( storeId: SecretStore["id"], ) => Effect.Effect; getStoreConfig: ( storeId: SecretStore["id"], ) => Effect.Effect; createStore: ( input: TConnectInput, ) => Effect.Effect; updateStore: ( input: TUpdateInput, ) => Effect.Effect; removeStore: ( storeId: SecretStore["id"], ) => Effect.Effect; }; export type ExecutorManagementToolContribution< TInput = unknown, TOutput = unknown, > = { path: `executor.${string}`; description: string; inputSchema: Schema.Schema; outputSchema: Schema.Schema; execute: (input: { args: TInput; host: ExecutorSdkPluginHost; }) => Effect.Effect; }; export type ExecutorSourcePluginManagementToolDefinition< TName extends string = string, TInput = unknown, TOutput = unknown, TConnectInput = unknown, TSourceConfig = unknown, TUpdateInput extends { sourceId: string; config: TSourceConfig; } = { sourceId: string; config: TSourceConfig; }, > = { name: TName; description: string; inputSchema: Schema.Schema; outputSchema: Schema.Schema; execute: (input: { args: TInput; source: ExecutorSourcePluginApi; host: ExecutorSourcePluginInternalHost; }) => Effect.Effect; }; export type ExecutorSourcePluginDefinition< TAddInput, TConnectInput, TSourceConfig, TStored, _TUpdateInput extends { sourceId: string; config: TSourceConfig; }, > = { kind: string; displayName: string; add: { inputSchema: Schema.Schema; inputSignatureWidth?: number; helpText?: readonly string[]; toConnectInput: (input: TAddInput) => TConnectInput; }; storage: ExecutorSourcePluginStorage; source: { create: (input: TConnectInput) => { source: Omit< ExecutorSource, "id" | "scopeId" | "createdAt" | "updatedAt" >; stored: TStored; }; update: (input: { source: ExecutorSource; config: TSourceConfig; }) => { source: ExecutorSource; stored: TStored; }; toConfig: (input: { source: ExecutorSource; stored: TStored; }) => TSourceConfig; remove?: (input: { source: ExecutorSource; stored: TStored | null; }) => Effect.Effect; }; scopeConfig?: { toConfigSource: (input: { source: ExecutorSource; stored: TStored; }) => ExecutorScopeConfigSource; recoverStored: (input: { source: ExecutorSource; config: ExecutorScopeConfigSource; loadedConfig: ExecutorScopeConfig | null; }) => Effect.Effect | TStored; }; catalog: { kind: SourceCatalogKind; identity?: (input: { source: ExecutorSource; }) => Record; sync: (input: { source: ExecutorSource; stored: TStored | null; }) => Effect.Effect< SourceCatalogSyncResult, Error, ExecutorSourcePluginRuntimeServices >; invoke: ( input: SourceInvokeInput & { source: ExecutorSource; stored: TStored | null; }, ) => Effect.Effect< SourceInvokeResult, Error, ExecutorSourcePluginRuntimeServices >; }; }; export type ExecutorSecretStorePluginCapabilities = { canCreateSecrets: boolean; canUpdateSecrets: boolean; canDeleteSecrets: boolean; canBrowseSecrets: boolean; canImportSecrets: boolean; }; export type ExecutorSecretStoreBrowseEntry = { key: string; label: string; description: string | null; kind: "group" | "secret"; }; export type ExecutorSecretStoreBuiltinDefinition = { storeId: string; defaultPriority?: number; enabled?: () => boolean; createStore: () => Omit< SecretStore, "id" | "scopeId" | "createdAt" | "updatedAt" >; }; export type ExecutorSecretStorePluginDefinition< TAddInput, TConnectInput, TStoreConfig, TStored, TSecretStored, _TUpdateInput extends { storeId: string; config: TStoreConfig; }, > = { kind: string; displayName: string; builtin?: ExecutorSecretStoreBuiltinDefinition; add?: { inputSchema: Schema.Schema; inputSignatureWidth?: number; helpText?: readonly string[]; toConnectInput: (input: TAddInput) => TConnectInput; }; storage: ExecutorSecretStorePluginStorage; store: { create: (input: TConnectInput) => { store: Omit< SecretStore, "id" | "scopeId" | "createdAt" | "updatedAt" >; stored: TStored; }; update: (input: { store: SecretStore; config: TStoreConfig; }) => { store: SecretStore; stored: TStored; }; toConfig: (input: { store: SecretStore; stored: TStored; }) => TStoreConfig; remove?: (input: { store: SecretStore; stored: TStored | null; }) => Effect.Effect; resolveSecret: (input: { secret: SecretMaterial; secretStored: TSecretStored; store: SecretStore; stored: TStored | null; context?: { params?: Readonly>; }; }) => Effect.Effect< string, Error, ExecutorSecretStorePluginRuntimeServices >; createSecret?: (input: { store: SecretStore; stored: TStored | null; purpose: SecretMaterialPurpose; value: string; name?: string | null; }) => Effect.Effect<{ name: string | null; secretStored: TSecretStored; }, Error, ExecutorSecretStorePluginRuntimeServices>; updateSecret?: (input: { secret: SecretMaterial; secretStored: TSecretStored; store: SecretStore; stored: TStored | null; name?: string | null; value?: string; }) => Effect.Effect<{ name: string | null; secretStored?: TSecretStored; }, Error, ExecutorSecretStorePluginRuntimeServices>; deleteSecret?: (input: { secret: SecretMaterial; secretStored: TSecretStored; store: SecretStore; stored: TStored | null; }) => Effect.Effect< boolean, Error, ExecutorSecretStorePluginRuntimeServices >; browseSecrets?: (input: { store: SecretStore; stored: TStored | null; parentKey?: string | null; query?: string | null; }) => Effect.Effect<{ entries: ReadonlyArray; }, Error, ExecutorSecretStorePluginRuntimeServices>; importSecret?: (input: { store: SecretStore; stored: TStored | null; selectionKey: string; purpose: SecretMaterialPurpose; name?: string | null; }) => Effect.Effect<{ name: string | null; secretStored: TSecretStored; }, Error, ExecutorSecretStorePluginRuntimeServices>; capabilities?: (input: { store: SecretStore; stored: TStored | null; }) => ExecutorSecretStorePluginCapabilities; }; }; export type ExecutorSourcePluginInput< TKey extends string = string, TAddInput = unknown, TConnectInput = unknown, TSourceConfig = unknown, TStored = unknown, TUpdateInput extends { sourceId: string; config: TSourceConfig; } = { sourceId: string; config: TSourceConfig; }, TExtension extends object = {}, > = { key: TKey; source: ExecutorSourcePluginDefinition< TAddInput, TConnectInput, TSourceConfig, TStored, TUpdateInput >; tools?: readonly ExecutorSourcePluginManagementToolDefinition< string, any, any, TConnectInput, TSourceConfig, TUpdateInput >[]; extendExecutor?: (input: ExecutorSdkPluginContext & { source: ExecutorSourcePluginApi; }) => TExtension; start?: ( input: ExecutorSdkPluginStartContext & { source: ExecutorSourcePluginApi< TConnectInput, TSourceConfig, TUpdateInput >; }, ) => Effect.Effect; }; export type ExecutorSecretStorePluginInput< TKey extends string = string, TAddInput = unknown, TConnectInput = unknown, TStoreConfig = unknown, TStored = unknown, TSecretStored = unknown, TUpdateInput extends { storeId: string; config: TStoreConfig; } = { storeId: string; config: TStoreConfig; }, TExtension extends object = {}, > = { key: TKey; secretStore: ExecutorSecretStorePluginDefinition< TAddInput, TConnectInput, TStoreConfig, TStored, TSecretStored, TUpdateInput >; extendExecutor?: (input: ExecutorSdkPluginContext & { secretStore: ExecutorSecretStorePluginApi; }) => TExtension; start?: ( input: ExecutorSdkPluginStartContext & { secretStore: ExecutorSecretStorePluginApi< TConnectInput, TStoreConfig, TUpdateInput >; }, ) => Effect.Effect; }; const loadSourceOfKind = ( sourceId: ExecutorSource["id"], input: { definition: ExecutorSourcePluginDefinition; host: ExecutorSourcePluginInternalHost; }, ): Effect.Effect => Effect.gen(function* () { const source = yield* input.host.sources.get(sourceId); if (source.kind !== input.definition.kind) { return yield* runtimeEffectError( "plugins", `Source ${sourceId} is not a ${input.definition.displayName} source.`, ); } return source; }); const cloneJson = (value: T): T => JSON.parse(JSON.stringify(value)) as T; const fromMaybeEffect = ( value: Effect.Effect | A, ): Effect.Effect => Effect.isEffect(value) ? value : Effect.succeed(value); const persistSourceScopeConfig = < TAddInput, TConnectInput, TSourceConfig, TStored, TUpdateInput extends { sourceId: string; config: TSourceConfig; }, >( definition: ExecutorSourcePluginDefinition< TAddInput, TConnectInput, TSourceConfig, TStored, TUpdateInput >, input: { source: ExecutorSource; stored: TStored; }, ): Effect.Effect => definition.scopeConfig ? Effect.gen(function* () { const scopeConfig = definition.scopeConfig!; const scopeConfigStore = yield* ScopeConfigStore; const loadedConfig = yield* scopeConfigStore.load(); const projectConfig = cloneJson(loadedConfig.projectConfig ?? {}); const sources = { ...projectConfig.sources, [input.source.id]: scopeConfig.toConfigSource({ source: input.source, stored: input.stored, }), }; yield* scopeConfigStore.writeProject({ config: { ...projectConfig, sources, }, }); }) : Effect.void; const loadStoredOrRecover = < TAddInput, TConnectInput, TSourceConfig, TStored, TUpdateInput extends { sourceId: string; config: TSourceConfig; }, >( definition: ExecutorSourcePluginDefinition< TAddInput, TConnectInput, TSourceConfig, TStored, TUpdateInput >, source: ExecutorSource, ): Effect.Effect => Effect.gen(function* () { const scopeConfig = definition.scopeConfig; const stored = yield* definition.storage.get({ scopeId: source.scopeId, sourceId: source.id, }); if (stored !== null || scopeConfig === undefined) { return stored; } const scopeConfigStore = yield* ScopeConfigStore; const loadedConfig = yield* scopeConfigStore.load(); const config = loadedConfig.config?.sources?.[source.id] ?? null; if (config === null) { return null; } if (config.kind !== definition.kind) { return yield* runtimeEffectError( "plugins", `Source ${source.id} config kind ${config.kind} does not match ${definition.kind}.`, ); } const recovered = yield* fromMaybeEffect( scopeConfig.recoverStored({ source, config, loadedConfig: loadedConfig.config, }), ); yield* definition.storage.put({ scopeId: source.scopeId, sourceId: source.id, value: recovered, }); return recovered; }); const createExecutorSourcePluginApi = < TAddInput, TConnectInput, TSourceConfig, TStored, _TUpdateInput extends { sourceId: string; config: TSourceConfig; }, >( definition: ExecutorSourcePluginDefinition< TAddInput, TConnectInput, TSourceConfig, TStored, _TUpdateInput >, host: ExecutorSourcePluginInternalHost, ): ExecutorSourcePluginApi => ({ getSource: (sourceId) => loadSourceOfKind(sourceId, { definition, host, }), getSourceConfig: (sourceId) => Effect.gen(function* () { const source = yield* loadSourceOfKind(sourceId, { definition, host, }); const stored = yield* loadStoredOrRecover(definition, source); if (stored === null) { return yield* runtimeEffectError( "plugins", `${definition.displayName} source storage missing for ${source.id}`, ); } return definition.source.toConfig({ source, stored, }); }), createSource: (input) => Effect.gen(function* () { const created = definition.source.create(input); const source = yield* host.sources.create({ source: created.source, }); yield* definition.storage.put({ scopeId: source.scopeId, sourceId: source.id, value: created.stored, }); yield* persistSourceScopeConfig(definition, { source, stored: created.stored, }); return yield* host.sources.refreshCatalog(source.id); }), updateSource: (input) => Effect.gen(function* () { const source = yield* loadSourceOfKind(input.sourceId as ExecutorSource["id"], { definition, host, }); const updated = definition.source.update({ source, config: input.config, }); const saved = yield* host.sources.save(updated.source); yield* definition.storage.put({ scopeId: saved.scopeId, sourceId: saved.id, value: updated.stored, }); yield* persistSourceScopeConfig(definition, { source: saved, stored: updated.stored, }); return yield* host.sources.refreshCatalog(saved.id); }), refreshSource: (sourceId) => Effect.gen(function* () { const source = yield* loadSourceOfKind(sourceId, { definition, host, }); return yield* host.sources.refreshCatalog(source.id); }), removeSource: (sourceId) => Effect.gen(function* () { const source = yield* loadSourceOfKind(sourceId, { definition, host, }); const stored = yield* definition.storage.get({ scopeId: source.scopeId, sourceId: source.id, }); if (definition.source.remove) { yield* definition.source.remove({ source, stored, }); } if (definition.storage.remove) { yield* definition.storage.remove({ scopeId: source.scopeId, sourceId: source.id, }); } return yield* host.sources.remove(source.id); }), }); const loadStoreOfKind = ( storeId: SecretStore["id"], input: { definition: ExecutorSecretStorePluginDefinition; host: ExecutorSecretStorePluginInternalHost; }, ): Effect.Effect => Effect.gen(function* () { const store = yield* input.host.secretStores.get(storeId); if (store.kind !== input.definition.kind) { return yield* runtimeEffectError( "plugins", `Secret store ${storeId} is not a ${input.definition.displayName} store.`, ); } return store; }); const loadSecretMaterialStoredData = ( secretId: SecretMaterial["id"], ): Effect.Effect => Effect.gen(function* () { const executorState = yield* ExecutorStateStore; const stored = yield* executorState.secretMaterialStoredData.getBySecretId(secretId); if (Option.isNone(stored)) { return yield* runtimeEffectError( "plugins", `Secret storage missing for ${secretId}.`, ); } return stored.value.data as TSecretStored; }); const createExecutorSecretStorePluginApi = < TAddInput, TConnectInput, TStoreConfig, TStored, TSecretStored, _TUpdateInput extends { storeId: string; config: TStoreConfig; }, >( definition: ExecutorSecretStorePluginDefinition< TAddInput, TConnectInput, TStoreConfig, TStored, TSecretStored, _TUpdateInput >, host: ExecutorSecretStorePluginInternalHost, ): ExecutorSecretStorePluginApi => ({ getStore: (storeId) => loadStoreOfKind(storeId, { definition, host, }), getStoreConfig: (storeId) => Effect.gen(function* () { const store = yield* loadStoreOfKind(storeId, { definition, host, }); const stored = yield* definition.storage.get({ scopeId: store.scopeId, storeId: store.id, }); if (stored === null) { return yield* runtimeEffectError( "plugins", `${definition.displayName} store storage missing for ${store.id}`, ); } return definition.store.toConfig({ store, stored, }); }), createStore: (input) => Effect.gen(function* () { const created = definition.store.create(input); const store = yield* host.secretStores.create({ store: created.store, }); yield* definition.storage.put({ scopeId: store.scopeId, storeId: store.id, value: created.stored, }); return store; }), updateStore: (input) => Effect.gen(function* () { const store = yield* loadStoreOfKind(input.storeId as SecretStore["id"], { definition, host, }); const updated = definition.store.update({ store, config: input.config, }); const saved = yield* host.secretStores.save(updated.store); yield* definition.storage.put({ scopeId: saved.scopeId, storeId: saved.id, value: updated.stored, }); return saved; }), removeStore: (storeId) => Effect.gen(function* () { const store = yield* loadStoreOfKind(storeId, { definition, host, }); const stored = yield* definition.storage.get({ scopeId: store.scopeId, storeId: store.id, }); if (definition.store.remove) { yield* definition.store.remove({ store, stored, }); } if (definition.storage.remove) { yield* definition.storage.remove({ scopeId: store.scopeId, storeId: store.id, }); } return yield* host.secretStores.remove(store.id); }), }); type ExecutorSourceContribution = { pluginKey: string; kind: string; displayName: string; inputSchema: Schema.Schema; inputSignatureWidth?: number; helpText?: readonly string[]; catalogKind: SourceCatalogKind; catalogIdentity?: (input: { source: ExecutorSource; }) => Record; createSource: (input: { args: TInput; host: ExecutorSourcePluginInternalHost; }) => Effect.Effect; syncCatalog: (input: { source: ExecutorSource; }) => Effect.Effect< SourceCatalogSyncResult, Error, ExecutorSourceContributionRuntimeServices >; invoke: ( input: SourceInvokeInput & { source: ExecutorSource; }, ) => Effect.Effect< SourceInvokeResult, Error, ExecutorSourceContributionRuntimeServices >; }; type ExecutorSecretStoreContribution = { kind: string; displayName: string; builtin?: ExecutorSecretStoreBuiltinDefinition; canCreate: boolean; inputSchema?: Schema.Schema; inputSignatureWidth?: number; helpText?: readonly string[]; createStore?: (input: { args: TInput; host: ExecutorSecretStorePluginInternalHost; }) => Effect.Effect; getStoreConfig: (input: { store: SecretStore; }) => Effect.Effect; updateStore: (input: { store: SecretStore; config: unknown; host: ExecutorSecretStorePluginInternalHost; }) => Effect.Effect; removeStore: (input: { store: SecretStore; host: ExecutorSecretStorePluginInternalHost; }) => Effect.Effect; getCapabilities: (input: { store: SecretStore; }) => Effect.Effect; resolveSecret: (input: { secret: SecretMaterial; store: SecretStore; context?: { params?: Readonly>; }; }) => Effect.Effect< string, Error, ExecutorSecretStoreContributionRuntimeServices >; createSecret: (input: { store: SecretStore; purpose: SecretMaterialPurpose; value: string; name?: string | null; }) => Effect.Effect<{ name: string | null; secretStored: unknown; }, Error, ExecutorSecretStoreContributionRuntimeServices>; updateSecret: (input: { secret: SecretMaterial; store: SecretStore; name?: string | null; value?: string; }) => Effect.Effect<{ name: string | null; secretStored?: unknown; }, Error, ExecutorSecretStoreContributionRuntimeServices>; deleteSecret: (input: { secret: SecretMaterial; store: SecretStore; }) => Effect.Effect< boolean, Error, ExecutorSecretStoreContributionRuntimeServices >; browseSecrets: (input: { store: SecretStore; parentKey?: string | null; query?: string | null; }) => Effect.Effect<{ entries: ReadonlyArray; }, Error, ExecutorSecretStoreContributionRuntimeServices>; importSecret: (input: { store: SecretStore; selectionKey: string; purpose: SecretMaterialPurpose; name?: string | null; }) => Effect.Effect<{ name: string | null; secretStored: unknown; }, Error, ExecutorSecretStoreContributionRuntimeServices>; }; export type ExecutorSdkPluginRegistry = { plugins: readonly ExecutorSdkPlugin[]; sources: readonly ExecutorSourceContribution[]; secretStores: readonly ExecutorSecretStoreContribution[]; managementTools: readonly ExecutorManagementToolContribution[]; getSourceContribution: (kind: string) => ExecutorSourceContribution; getSourceContributionForSource: ( source: Pick, ) => ExecutorSourceContribution; getSecretStoreContribution: (kind: string) => ExecutorSecretStoreContribution; getSecretStoreContributionForStore: ( store: Pick, ) => ExecutorSecretStoreContribution; getManagementTool: ( path: string, ) => ExecutorManagementToolContribution; }; const createExecutorSourceContribution = < TKey extends string, TAddInput, TConnectInput, TSourceConfig, TStored, TUpdateInput extends { sourceId: string; config: TSourceConfig; }, >( input: { key: TKey; source: ExecutorSourcePluginDefinition< TAddInput, TConnectInput, TSourceConfig, TStored, TUpdateInput >; }, ): ExecutorSourceContribution => ({ pluginKey: input.key, kind: input.source.kind, displayName: input.source.displayName, inputSchema: input.source.add.inputSchema, inputSignatureWidth: input.source.add.inputSignatureWidth, helpText: input.source.add.helpText, catalogKind: input.source.catalog.kind, catalogIdentity: input.source.catalog.identity, createSource: ({ args, host }) => createExecutorSourcePluginApi(input.source, host).createSource( input.source.add.toConnectInput(args), ), syncCatalog: ({ source }) => Effect.flatMap( loadStoredOrRecover(input.source, source), (stored) => input.source.catalog.sync({ source, stored, }), ), invoke: (invokeInput) => Effect.flatMap( loadStoredOrRecover(input.source, invokeInput.source), (stored) => input.source.catalog.invoke({ ...invokeInput, stored, }), ), }); const createExecutorSecretStoreContribution = < TAddInput, TConnectInput, TStoreConfig, TStored, TSecretStored, TUpdateInput extends { storeId: string; config: TStoreConfig; }, >( definition: ExecutorSecretStorePluginDefinition< TAddInput, TConnectInput, TStoreConfig, TStored, TSecretStored, TUpdateInput >, ): ExecutorSecretStoreContribution => ({ kind: definition.kind, displayName: definition.displayName, builtin: definition.builtin, canCreate: definition.add !== undefined, inputSchema: definition.add?.inputSchema, inputSignatureWidth: definition.add?.inputSignatureWidth, helpText: definition.add?.helpText, createStore: definition.add ? ({ args, host }) => createExecutorSecretStorePluginApi(definition, host).createStore( definition.add!.toConnectInput(args), ) : undefined, getStoreConfig: ({ store }) => Effect.flatMap( definition.storage.get({ scopeId: store.scopeId, storeId: store.id, }), (stored) => { if (stored === null) { return runtimeEffectError( "plugins", `${definition.displayName} store storage missing for ${store.id}`, ); } return Effect.succeed( definition.store.toConfig({ store, stored, }), ); }, ), updateStore: ({ store, config, host }) => createExecutorSecretStorePluginApi(definition, host).updateStore({ storeId: store.id, config: config as TStoreConfig, } as unknown as TUpdateInput), removeStore: ({ store, host }) => createExecutorSecretStorePluginApi(definition, host).removeStore(store.id), getCapabilities: ({ store }) => Effect.flatMap( definition.storage.get({ scopeId: store.scopeId, storeId: store.id, }), (stored) => Effect.succeed( definition.store.capabilities?.({ store, stored, }) ?? { canCreateSecrets: definition.store.createSecret !== undefined, canUpdateSecrets: definition.store.updateSecret !== undefined, canDeleteSecrets: definition.store.deleteSecret !== undefined, canBrowseSecrets: definition.store.browseSecrets !== undefined, canImportSecrets: definition.store.importSecret !== undefined, }, ), ), resolveSecret: ({ secret, store, context }) => Effect.flatMap( Effect.all({ stored: definition.storage.get({ scopeId: store.scopeId, storeId: store.id, }), secretStored: loadSecretMaterialStoredData(secret.id), }), ({ stored, secretStored }) => definition.store.resolveSecret({ secret, secretStored, store, stored, context, }), ), createSecret: ({ store, purpose, value, name }) => definition.store.createSecret ? Effect.flatMap( definition.storage.get({ scopeId: store.scopeId, storeId: store.id, }), (stored) => definition.store.createSecret!({ store, stored, purpose, value, name, }), ) : runtimeEffectError( "plugins", `Secret store ${store.id} does not support creating secrets.`, ), updateSecret: ({ secret, store, name, value }) => definition.store.updateSecret ? Effect.flatMap( Effect.all({ stored: definition.storage.get({ scopeId: store.scopeId, storeId: store.id, }), secretStored: loadSecretMaterialStoredData(secret.id), }), ({ stored, secretStored }) => definition.store.updateSecret!({ secret, secretStored, store, stored, name, value, }), ) : runtimeEffectError( "plugins", `Secret store ${store.id} does not support updating secrets.`, ), deleteSecret: ({ secret, store }) => definition.store.deleteSecret ? Effect.flatMap( Effect.all({ stored: definition.storage.get({ scopeId: store.scopeId, storeId: store.id, }), secretStored: loadSecretMaterialStoredData(secret.id), }), ({ stored, secretStored }) => definition.store.deleteSecret!({ secret, secretStored, store, stored, }), ) : runtimeEffectError( "plugins", `Secret store ${store.id} does not support deleting secrets.`, ), browseSecrets: ({ store, parentKey, query }) => definition.store.browseSecrets ? Effect.flatMap( definition.storage.get({ scopeId: store.scopeId, storeId: store.id, }), (stored) => definition.store.browseSecrets!({ store, stored, parentKey, query, }), ) : runtimeEffectError( "plugins", `Secret store ${store.id} does not support browsing secrets.`, ), importSecret: ({ store, selectionKey, purpose, name }) => definition.store.importSecret ? Effect.flatMap( definition.storage.get({ scopeId: store.scopeId, storeId: store.id, }), (stored) => definition.store.importSecret!({ store, stored, selectionKey, purpose, name, }), ) : runtimeEffectError( "plugins", `Secret store ${store.id} does not support importing secrets.`, ), }); const createExecutorSourceManagementToolContribution = < TKey extends string, TName extends string, TInput, TOutput, TConnectInput, TSourceConfig, TStored, TUpdateInput extends { sourceId: string; config: TSourceConfig; }, >( input: { key: TKey; source: ExecutorSourcePluginDefinition< any, TConnectInput, TSourceConfig, TStored, TUpdateInput >; tool: ExecutorSourcePluginManagementToolDefinition< TName, TInput, TOutput, TConnectInput, TSourceConfig, TUpdateInput >; }, ): ExecutorManagementToolContribution => ({ path: `executor.${input.key}.${input.tool.name}`, description: input.tool.description, inputSchema: input.tool.inputSchema, outputSchema: input.tool.outputSchema, execute: ({ args, host }) => input.tool.execute({ args, host: host as ExecutorSourcePluginInternalHost, source: createExecutorSourcePluginApi( input.source, host as ExecutorSourcePluginInternalHost, ), }), }); export const defineExecutorSourcePlugin = < const TKey extends string, TAddInput, TConnectInput, TSourceConfig, TStored, TUpdateInput extends { sourceId: string; config: TSourceConfig; }, TExtension extends object = {}, >( input: ExecutorSourcePluginInput< TKey, TAddInput, TConnectInput, TSourceConfig, TStored, TUpdateInput, TExtension >, ): ExecutorSdkPlugin => ((extendExecutor, start) => defineExecutorSdkPlugin({ key: input.key, extendExecutor: extendExecutor ? (pluginInput) => extendExecutor({ ...pluginInput, source: createExecutorSourcePluginApi( input.source, pluginInput.host as ExecutorSourcePluginInternalHost, ), }) : undefined, start: start ? (pluginInput) => start({ ...pluginInput, source: createExecutorSourcePluginApi( input.source, pluginInput.host as ExecutorSourcePluginInternalHost, ), }) : undefined, [executorSdkPluginInternalsSymbol]: { sources: [createExecutorSourceContribution({ key: input.key, source: input.source, })], managementTools: (input.tools ?? []).map((tool) => createExecutorSourceManagementToolContribution({ key: input.key, source: input.source, tool, }) ), }, }))(input.extendExecutor, input.start); export const defineExecutorSecretStorePlugin = < const TKey extends string, TAddInput, TConnectInput, TStoreConfig, TStored, TSecretStored, TUpdateInput extends { storeId: string; config: TStoreConfig; }, TExtension extends object = {}, >( input: ExecutorSecretStorePluginInput< TKey, TAddInput, TConnectInput, TStoreConfig, TStored, TSecretStored, TUpdateInput, TExtension >, ): ExecutorSdkPlugin => ((extendExecutor, start) => defineExecutorSdkPlugin({ key: input.key, extendExecutor: extendExecutor ? (pluginInput) => extendExecutor({ ...pluginInput, secretStore: createExecutorSecretStorePluginApi( input.secretStore, pluginInput.host as unknown as ExecutorSecretStorePluginInternalHost, ), }) : undefined, start: start ? (pluginInput) => start({ ...pluginInput, secretStore: createExecutorSecretStorePluginApi( input.secretStore, pluginInput.host as unknown as ExecutorSecretStorePluginInternalHost, ), }) : undefined, [executorSdkPluginInternalsSymbol]: { secretStores: [createExecutorSecretStoreContribution(input.secretStore)], }, }))(input.extendExecutor, input.start); export type ExecutorSdkPluginExtensions< TPlugins extends readonly ExecutorSdkPlugin[], > = { [TPlugin in TPlugins[number] as TPlugin["key"]]: TPlugin extends ExecutorSdkPlugin ? TExtension : never; }; export const registerExecutorSdkPlugins = ( plugins: readonly ExecutorSdkPlugin[], ) => { const pluginKeys = new Set(); const sources = new Map>(); const secretStores = new Map>(); const managementTools = new Map< string, ExecutorManagementToolContribution >(); for (const plugin of plugins) { if (pluginKeys.has(plugin.key)) { throw new Error(`Duplicate executor SDK plugin registration: ${plugin.key}`); } pluginKeys.add(plugin.key); const internals = plugin[executorSdkPluginInternalsSymbol]; for (const source of internals?.sources ?? []) { if (sources.has(source.kind)) { throw new Error( `Duplicate source registration: ${source.kind}`, ); } sources.set(source.kind, source); } for (const secretStore of internals?.secretStores ?? []) { if (secretStores.has(secretStore.kind)) { throw new Error( `Duplicate secret store registration: ${secretStore.kind}`, ); } secretStores.set(secretStore.kind, secretStore); } for (const tool of internals?.managementTools ?? []) { if (managementTools.has(tool.path)) { throw new Error(`Duplicate executor management tool: ${tool.path}`); } managementTools.set(tool.path, tool); } } const getSourceContribution = (kind: string) => { const definition = sources.get(kind); if (!definition) { throw new Error(`Unsupported source kind: ${kind}`); } return definition; }; const getSourceContributionForSource = (source: Pick) => getSourceContribution(source.kind); const getSecretStoreContribution = (kind: string) => { const definition = secretStores.get(kind); if (!definition) { throw new Error(`Unsupported secret store kind: ${kind}`); } return definition; }; const getSecretStoreContributionForStore = ( store: Pick, ) => getSecretStoreContribution(store.kind); const getManagementTool = (path: string) => { const tool = managementTools.get(path); if (!tool) { throw new Error(`Unsupported executor management tool: ${path}`); } return tool; }; return { plugins, sources: [...sources.values()], secretStores: [...secretStores.values()], managementTools: [...managementTools.values()], getSourceContribution, getSourceContributionForSource, getSecretStoreContribution, getSecretStoreContributionForStore, getManagementTool, } satisfies ExecutorSdkPluginRegistry; };