From 5fc0c1205542fc40c525780c1921fa7f6422cd63 Mon Sep 17 00:00:00 2001 From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com> Date: Wed, 8 Apr 2026 15:18:32 -0700 Subject: [PATCH] derive SDK types from core and expose plugin store options Add PromisifyService utility type to derive promise-based SDK interfaces from core Effect types, reducing duplication across ToolRegistry, SourceRegistry, SecretStore, PolicyEngine, SourceManager, and SecretProvider. Use Pick for Executor sub-types. Expose operationStore/bindingStore options on openapi, mcp, graphql, and google-discovery plugin factories. --- .../plugins/google-discovery/src/promise.ts | 11 +- packages/plugins/graphql/src/promise.ts | 8 +- packages/plugins/mcp/src/promise.ts | 9 +- packages/plugins/openapi/src/promise.ts | 8 +- packages/published/sdk/src/executor.ts | 101 +++++++----------- 5 files changed, 72 insertions(+), 65 deletions(-) diff --git a/packages/plugins/google-discovery/src/promise.ts b/packages/plugins/google-discovery/src/promise.ts index de9dabfa46..1cc14f004e 100644 --- a/packages/plugins/google-discovery/src/promise.ts +++ b/packages/plugins/google-discovery/src/promise.ts @@ -9,5 +9,12 @@ export type { GoogleDiscoveryOAuthAuthResult, } from "./sdk/plugin"; -export const googleDiscoveryPlugin = (options?: {}) => - googleDiscoveryPluginEffect(options); +export type { GoogleDiscoveryBindingStore } from "./sdk/binding-store"; + +export interface GoogleDiscoveryPluginOptions { + readonly bindingStore?: import("./sdk/binding-store").GoogleDiscoveryBindingStore; +} + +export const googleDiscoveryPlugin = ( + options?: GoogleDiscoveryPluginOptions, +) => googleDiscoveryPluginEffect(options); diff --git a/packages/plugins/graphql/src/promise.ts b/packages/plugins/graphql/src/promise.ts index def153f1d1..86e95d2285 100644 --- a/packages/plugins/graphql/src/promise.ts +++ b/packages/plugins/graphql/src/promise.ts @@ -2,5 +2,11 @@ import { graphqlPlugin as graphqlPluginEffect } from "./sdk/plugin"; export type { GraphqlSourceConfig } from "./sdk/plugin"; export type { HeaderValue } from "./sdk/types"; +export type { GraphqlOperationStore } from "./sdk/operation-store"; -export const graphqlPlugin = (options?: {}) => graphqlPluginEffect(options); +export interface GraphqlPluginOptions { + readonly operationStore?: import("./sdk/operation-store").GraphqlOperationStore; +} + +export const graphqlPlugin = (options?: GraphqlPluginOptions) => + graphqlPluginEffect(options); diff --git a/packages/plugins/mcp/src/promise.ts b/packages/plugins/mcp/src/promise.ts index 9970f0a3d2..fa5f827de0 100644 --- a/packages/plugins/mcp/src/promise.ts +++ b/packages/plugins/mcp/src/promise.ts @@ -11,4 +11,11 @@ export type { McpOAuthCompleteResponse, } from "./sdk/plugin"; -export const mcpPlugin = (options?: {}) => mcpPluginEffect(options); +export type { McpBindingStore } from "./sdk/binding-store"; + +export interface McpPluginOptions { + readonly bindingStore?: import("./sdk/binding-store").McpBindingStore; +} + +export const mcpPlugin = (options?: McpPluginOptions) => + mcpPluginEffect(options); diff --git a/packages/plugins/openapi/src/promise.ts b/packages/plugins/openapi/src/promise.ts index 4e4da9e189..d8cf559a4d 100644 --- a/packages/plugins/openapi/src/promise.ts +++ b/packages/plugins/openapi/src/promise.ts @@ -1,5 +1,11 @@ import { openApiPlugin as openApiPluginEffect } from "./sdk/plugin"; export type { OpenApiSpecConfig } from "./sdk/plugin"; +export type { OpenApiOperationStore } from "./sdk/operation-store"; -export const openApiPlugin = (options?: {}) => openApiPluginEffect(options); +export interface OpenApiPluginOptions { + readonly operationStore?: import("./sdk/operation-store").OpenApiOperationStore; +} + +export const openApiPlugin = (options?: OpenApiPluginOptions) => + openApiPluginEffect(options); diff --git a/packages/published/sdk/src/executor.ts b/packages/published/sdk/src/executor.ts index 6e144e87e9..41e2f7b4b0 100644 --- a/packages/published/sdk/src/executor.ts +++ b/packages/published/sdk/src/executor.ts @@ -1,4 +1,4 @@ -import { Effect } from "effect"; +import { Context, Effect } from "effect"; import { createExecutor as createEffectExecutor, @@ -8,6 +8,10 @@ import { makeInMemoryPolicyEngine, makeInMemorySourceRegistry, ScopeId, + type ToolRegistry as CoreToolRegistry, + type SourceRegistry as CoreSourceRegistry, + type SecretStore as CoreSecretStore, + type PolicyEngine as CorePolicyEngine, type ExecutorConfig as EffectExecutorConfig, type ExecutorPlugin, type PluginContext as EffectPluginContext, @@ -15,8 +19,6 @@ import { type InvokeOptions as EffectInvokeOptions, type ToolInvocationResult, type ToolMetadata, - type ToolSchema, - type ToolRegistration, type ToolAnnotations, type ToolInvoker as EffectToolInvoker, type RuntimeToolHandler as EffectRuntimeToolHandler, @@ -44,6 +46,31 @@ const run = (effect: Effect.Effect): Promise => const fromPromise = (fn: () => Promise): Effect.Effect => Effect.tryPromise({ try: fn, catch: (e) => (e instanceof Error ? e : new Error(String(e))) }); +// --------------------------------------------------------------------------- +// Type derivation — derive Promise-based SDK types from core Effect types +// --------------------------------------------------------------------------- + +/** Replace branded IDs with plain strings in parameter types */ +type UnbrandParam = + T extends ToolId ? string : + T extends SecretId ? string : + T extends ScopeIdType ? string : + T extends PolicyId ? string : + T extends readonly (infer U)[] ? readonly UnbrandParam[] : + T; + +/** Convert an Effect service interface to Promise-based, unbranding ID params */ +type PromisifyService = { + readonly [K in keyof T]: NonNullable extends (...args: infer A) => Effect.Effect + ? (...args: { [I in keyof A]: UnbrandParam }) => Promise + : T[K]; +}; + +type CoreToolRegistryService = Context.Tag.Service; +type CoreSourceRegistryService = Context.Tag.Service; +type CoreSecretStoreService = Context.Tag.Service; +type CorePolicyEngineService = Context.Tag.Service; + // --------------------------------------------------------------------------- // Elicitation // --------------------------------------------------------------------------- @@ -98,22 +125,9 @@ export interface RuntimeToolHandler { readonly resolveAnnotations?: () => Promise; } -export interface SourceManager { - readonly kind: string; - readonly list: () => Promise; - readonly remove: (sourceId: string) => Promise; - readonly refresh?: (sourceId: string) => Promise; - readonly detect?: (url: string) => Promise; -} +export type SourceManager = PromisifyService; -export interface SecretProvider { - readonly key: string; - readonly writable: boolean; - readonly get: (key: string) => Promise; - readonly set?: (key: string, value: string) => Promise; - readonly delete?: (key: string) => Promise; - readonly list?: () => Promise; -} +export type SecretProvider = PromisifyService; // --- Adapters --- @@ -173,50 +187,27 @@ export interface PluginContext { readonly policies: PolicyEngine; } -export interface ToolRegistry { +export interface ToolRegistry extends Omit< + PromisifyService, + 'list' | 'invoke' | 'registerInvoker' | 'registerRuntimeHandler' +> { readonly list: (filter?: { sourceId?: string; query?: string }) => Promise; - readonly schema: (toolId: string) => Promise; readonly invoke: (toolId: string, args: unknown, options: InvokeOptions) => Promise; - readonly definitions: () => Promise>; - readonly registerDefinitions: (defs: Record) => Promise; - readonly registerRuntimeDefinitions: (defs: Record) => Promise; - readonly unregisterRuntimeDefinitions: (names: readonly string[]) => Promise; readonly registerInvoker: (pluginKey: string, invoker: ToolInvoker) => Promise; - readonly resolveAnnotations: (toolId: string) => Promise; - readonly register: (tools: readonly ToolRegistration[]) => Promise; - readonly registerRuntime: (tools: readonly ToolRegistration[]) => Promise; readonly registerRuntimeHandler: (toolId: string, handler: RuntimeToolHandler) => Promise; - readonly unregisterRuntime: (toolIds: readonly string[]) => Promise; - readonly unregister: (toolIds: readonly string[]) => Promise; - readonly unregisterBySource: (sourceId: string) => Promise; } -export interface SourceRegistry { +export interface SourceRegistry extends Omit, 'addManager'> { readonly addManager: (manager: SourceManager) => Promise; - readonly registerRuntime: (source: Source) => Promise; - readonly unregisterRuntime: (sourceId: string) => Promise; - readonly list: () => Promise; - readonly remove: (sourceId: string) => Promise; - readonly refresh: (sourceId: string) => Promise; - readonly detect: (url: string) => Promise; } -export interface SecretStore { - readonly list: (scopeId: string) => Promise; - readonly get: (secretId: string) => Promise; - readonly resolve: (secretId: string, scopeId: string) => Promise; - readonly status: (secretId: string, scopeId: string) => Promise<"resolved" | "missing">; +export interface SecretStore extends Omit, 'set' | 'addProvider'> { readonly set: (input: { readonly id: string; readonly scopeId: string; readonly name: string; readonly value: string; readonly provider?: string; readonly purpose?: string }) => Promise; - readonly remove: (secretId: string) => Promise; readonly addProvider: (provider: SecretProvider) => Promise; - readonly providers: () => Promise; } -export interface PolicyEngine { - readonly list: (scopeId: string) => Promise; +export interface PolicyEngine extends Omit, 'check'> { readonly check: (input: { scopeId: string; toolId: string }) => Promise; - readonly add: (policy: Omit) => Promise; - readonly remove: (policyId: string) => Promise; } const wrapPluginContext = (ctx: EffectPluginContext): PluginContext => ({ @@ -313,18 +304,8 @@ export type AnyPlugin = Plugin | ExecutorPlugin; export type Executor = { readonly scope: Scope; - readonly tools: { - readonly list: (filter?: { sourceId?: string; query?: string }) => Promise; - readonly schema: (toolId: string) => Promise; - readonly definitions: () => Promise>; - readonly invoke: (toolId: string, args: unknown, options: InvokeOptions) => Promise; - }; - readonly sources: { - readonly list: () => Promise; - readonly remove: (sourceId: string) => Promise; - readonly refresh: (sourceId: string) => Promise; - readonly detect: (url: string) => Promise; - }; + readonly tools: Pick; + readonly sources: Pick; readonly policies: { readonly list: () => Promise; readonly add: (policy: Omit) => Promise;