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;