import { toTool, type ToolMap } from "@executor/codemode-core"; import { type ScopeId, SourceIdSchema, SourceSchema, type Source, } from "#schema"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; import * as Schema from "effect/Schema"; import type { ExecutorSdkPluginRegistry, } from "../../plugins"; import { getSource, listSources, refreshManagedSourceCatalog, removeSource, saveManagedSourceRecord, createManagedSourceRecord, } from "../../sources/operations"; import { deriveSchemaTypeSignature, } from "../catalog/schema-type-signature"; import { RuntimeSourceCatalogSyncService, } from "../catalog/source/sync"; import { ExecutorStateStore, type ExecutorStateStoreShape, } from "../executor-state-store"; import { provideOptionalRuntimeLocalScope, type RuntimeLocalScopeState, } from "../scope/runtime-context"; import { SecretMaterialDeleterService, SecretMaterialResolverService, SecretMaterialStorerService, SecretMaterialUpdaterService, type DeleteSecretMaterial, type ResolveSecretMaterial, type StoreSecretMaterial, type UpdateSecretMaterial, } from "../scope/secret-material-providers"; import { InstallationStore, type InstallationStoreShape, makeLocalStorageLayer, ScopeConfigStore, type ScopeConfigStoreShape, ScopeStateStore, type ScopeStateStoreShape, SourceArtifactStore, type SourceArtifactStoreShape, } from "../scope/storage"; import { registeredManagementToolContributions, registeredSourceContributions, } from "./source-plugins"; import { type RuntimeSourceStore, RuntimeSourceStoreService, } from "./source-store"; const SourceArraySchema = Schema.Array(SourceSchema); const SourceIdInputSchema = Schema.Struct({ sourceId: SourceIdSchema, }); const SourceRemoveResultSchema = Schema.Struct({ removed: Schema.Boolean, }); type RegisteredSourceContribution = ExecutorSdkPluginRegistry["sources"][number]; type RegisteredManagementToolContribution = ExecutorSdkPluginRegistry["managementTools"][number]; const coreSourceManagementHelpLines = (): readonly string[] => [ "- executor.sources.list", `- executor.sources.get: ${deriveSchemaTypeSignature(SourceIdInputSchema, 180)}`, `- executor.sources.refresh: ${deriveSchemaTypeSignature(SourceIdInputSchema, 180)}`, `- executor.sources.remove: ${deriveSchemaTypeSignature(SourceIdInputSchema, 180)}`, ]; export const getExecutorInternalToolHelpLines = ( pluginRegistry: ExecutorSdkPluginRegistry, ): readonly string[] => { const managementTools = registeredManagementToolContributions(pluginRegistry); return [ "Core source management tools:", ...coreSourceManagementHelpLines(), ...(managementTools.length === 0 ? ["No plugin management tools are registered in this build."] : [ "Plugin management tools:", ...managementTools.map((tool) => `- ${tool.path}: ${deriveSchemaTypeSignature(tool.inputSchema, 260)}` ), ]), ]; }; const createSourceConnectorHost = (input: { scopeId: ScopeId; actorScopeId: ScopeId; }) => ({ sources: { create: ({ source, }: { source: Omit< Source, "id" | "scopeId" | "createdAt" | "updatedAt" >; }) => createManagedSourceRecord({ scopeId: input.scopeId, actorScopeId: input.actorScopeId, source, }), get: (sourceId: Source["id"]) => getSource({ scopeId: input.scopeId, sourceId, actorScopeId: input.actorScopeId, }), save: (source: Source) => saveManagedSourceRecord({ actorScopeId: input.actorScopeId, source, }), refreshCatalog: (sourceId: Source["id"]) => refreshManagedSourceCatalog({ scopeId: input.scopeId, sourceId, actorScopeId: input.actorScopeId, }), remove: (sourceId: Source["id"]) => removeSource({ scopeId: input.scopeId, sourceId, }).pipe(Effect.map((result) => result.removed)), }, }); type ExecutorToolRuntimeServices = | InstallationStore | ScopeConfigStore | ScopeStateStore | SourceArtifactStore | ExecutorStateStore | RuntimeSourceStoreService | RuntimeSourceCatalogSyncService | SecretMaterialResolverService | SecretMaterialStorerService | SecretMaterialDeleterService | SecretMaterialUpdaterService; const runExecutorToolEffect = async ( effect: Effect.Effect, input: { executorStateStore: ExecutorStateStoreShape; sourceStore: RuntimeSourceStore; sourceCatalogSyncService: Effect.Effect.Success< typeof RuntimeSourceCatalogSyncService >; installationStore: InstallationStoreShape; scopeConfigStore: ScopeConfigStoreShape; scopeStateStore: ScopeStateStoreShape; sourceArtifactStore: SourceArtifactStoreShape; runtimeLocalScope: RuntimeLocalScopeState | null; secretMaterialServices: { resolve: ResolveSecretMaterial; store: StoreSecretMaterial; delete: DeleteSecretMaterial; update: UpdateSecretMaterial; }; }, ): Promise => { const servicesLayer = Layer.mergeAll( makeLocalStorageLayer({ installationStore: input.installationStore, scopeConfigStore: input.scopeConfigStore, scopeStateStore: input.scopeStateStore, sourceArtifactStore: input.sourceArtifactStore, }), Layer.succeed(ExecutorStateStore, input.executorStateStore), Layer.succeed(RuntimeSourceStoreService, input.sourceStore), Layer.succeed( RuntimeSourceCatalogSyncService, input.sourceCatalogSyncService, ), Layer.succeed( SecretMaterialResolverService, input.secretMaterialServices.resolve, ), Layer.succeed( SecretMaterialStorerService, input.secretMaterialServices.store, ), Layer.succeed( SecretMaterialDeleterService, input.secretMaterialServices.delete, ), Layer.succeed( SecretMaterialUpdaterService, input.secretMaterialServices.update, ), ); return Effect.runPromise( provideOptionalRuntimeLocalScope( effect.pipe(Effect.provide(servicesLayer)), input.runtimeLocalScope, ), ); }; const toSerializableValue = (value: A): A => JSON.parse(JSON.stringify(value)) as A; const pluginRemoveToolPath = ( source: RegisteredSourceContribution, ): `executor.${string}` => `executor.${source.pluginKey}.removeSource`; const pluginRefreshToolPath = ( source: RegisteredSourceContribution, ): `executor.${string}` => `executor.${source.pluginKey}.refreshSource`; const findManagementTool = ( managementTools: ReadonlyArray, path: string, ): RegisteredManagementToolContribution | null => managementTools.find((tool) => tool.path === path) ?? null; const createPluginManagementToolMap = (input: { managementTools: ReadonlyArray; host: ReturnType; runtime: Parameters[1]; }): ToolMap => Object.fromEntries( input.managementTools.map((tool) => [ tool.path, toTool({ tool: { description: tool.description, inputSchema: Schema.standardSchemaV1(tool.inputSchema), outputSchema: Schema.standardSchemaV1(tool.outputSchema), execute: async (args: unknown) => toSerializableValue( await runExecutorToolEffect( tool.execute({ args: args as never, host: input.host, }), input.runtime, ), ), }, }), ]), ); export const createExecutorToolMap = (input: { pluginRegistry: ExecutorSdkPluginRegistry; scopeId: ScopeId; actorScopeId: ScopeId; executorStateStore: ExecutorStateStoreShape; sourceStore: RuntimeSourceStore; sourceCatalogSyncService: Effect.Effect.Success< typeof RuntimeSourceCatalogSyncService >; installationStore: InstallationStoreShape; scopeConfigStore: ScopeConfigStoreShape; scopeStateStore: ScopeStateStoreShape; sourceArtifactStore: SourceArtifactStoreShape; runtimeLocalScope: RuntimeLocalScopeState | null; secretMaterialServices: { resolve: ResolveSecretMaterial; store: StoreSecretMaterial; delete: DeleteSecretMaterial; update: UpdateSecretMaterial; }; }): ToolMap => { const sources = registeredSourceContributions(input.pluginRegistry); const managementTools = registeredManagementToolContributions( input.pluginRegistry, ); const host = createSourceConnectorHost({ scopeId: input.scopeId, actorScopeId: input.actorScopeId, }); const runtime = { executorStateStore: input.executorStateStore, sourceStore: input.sourceStore, sourceCatalogSyncService: input.sourceCatalogSyncService, installationStore: input.installationStore, scopeConfigStore: input.scopeConfigStore, scopeStateStore: input.scopeStateStore, sourceArtifactStore: input.sourceArtifactStore, runtimeLocalScope: input.runtimeLocalScope, secretMaterialServices: input.secretMaterialServices, }; const pluginToolMap = createPluginManagementToolMap({ managementTools, host, runtime, }); const coreTools: ToolMap = { "executor.sources.list": toTool({ tool: { description: "List sources in the current executor scope.", inputSchema: Schema.standardSchemaV1(Schema.Struct({})), outputSchema: Schema.standardSchemaV1(SourceArraySchema), execute: async () => toSerializableValue( await runExecutorToolEffect( listSources({ scopeId: input.scopeId, actorScopeId: input.actorScopeId, }), runtime, ), ), }, }), "executor.sources.get": toTool({ tool: { description: "Get a source by id.", inputSchema: Schema.standardSchemaV1(SourceIdInputSchema), outputSchema: Schema.standardSchemaV1(SourceSchema), execute: async (args: typeof SourceIdInputSchema.Type) => toSerializableValue( await runExecutorToolEffect( getSource({ scopeId: input.scopeId, sourceId: args.sourceId, actorScopeId: input.actorScopeId, }), runtime, ), ), }, }), "executor.sources.refresh": toTool({ tool: { description: "Refresh catalog artifacts for a source by id.", inputSchema: Schema.standardSchemaV1(SourceIdInputSchema), outputSchema: Schema.standardSchemaV1(SourceSchema), execute: async (args: typeof SourceIdInputSchema.Type) => { const source = await runExecutorToolEffect( getSource({ scopeId: input.scopeId, sourceId: args.sourceId, actorScopeId: input.actorScopeId, }), runtime, ); const contribution = sources.find( (candidate) => candidate.kind === source.kind, ); const pluginRefreshTool = contribution ? findManagementTool( managementTools, pluginRefreshToolPath(contribution), ) : undefined; if (pluginRefreshTool) { return toSerializableValue( await runExecutorToolEffect( pluginRefreshTool.execute({ args, host, }), runtime, ), ); } return toSerializableValue( await runExecutorToolEffect( refreshManagedSourceCatalog({ scopeId: input.scopeId, sourceId: args.sourceId, actorScopeId: input.actorScopeId, }), runtime, ), ); }, }, }), "executor.sources.remove": toTool({ tool: { description: "Remove a source by id. Delegates to plugin-owned cleanup when available.", inputSchema: Schema.standardSchemaV1(SourceIdInputSchema), outputSchema: Schema.standardSchemaV1(SourceRemoveResultSchema), execute: async (args: typeof SourceIdInputSchema.Type) => { const source = await runExecutorToolEffect( getSource({ scopeId: input.scopeId, sourceId: args.sourceId, actorScopeId: input.actorScopeId, }), runtime, ); const contribution = sources.find( (candidate) => candidate.kind === source.kind, ); const pluginRemoveTool = contribution ? findManagementTool( managementTools, pluginRemoveToolPath(contribution), ) : undefined; if (pluginRemoveTool) { const removed = await runExecutorToolEffect( pluginRemoveTool.execute({ args, host, }), runtime, ); return toSerializableValue({ removed: Boolean(removed), }); } return toSerializableValue( await runExecutorToolEffect( removeSource({ scopeId: input.scopeId, sourceId: args.sourceId, }), runtime, ), ); }, }, }), }; return { ...coreTools, ...pluginToolMap, }; };