import { describe, expect, it } from "@effect/vitest";import { Data, Effect, Exit, Predicate, Result, Schema } from "effect";import { FetchHttpClient } from "effect/unstable/http"; import { makeMemoryAdapter } from "@executor-js/storage-core/testing/memory";import type { DBAdapter, Where } from "@executor-js/storage-core"; import { makeInMemoryBlobStore } from "./blob";import { CreateConnectionInput, TokenMaterial } from "./connections";import { collectSchemas, createExecutor } from "./executor";import { ElicitationResponse, FormElicitation, UrlElicitation } from "./elicitation";import { defineSchema, definePlugin, tool } from "./plugin";import { RemoveSecretInput, SetSecretInput } from "./secrets";import { makeTestConfig } from "./testing";import type { SecretProvider } from "./secrets";import { ConnectionId, ScopeId, SecretId } from "./ids";import { Scope } from "./scope";import { SourceDetectionResult } from "./types"; type FindManyCall = { readonly model: string; readonly where?: readonly Where[];}; class TestPluginError extends Data.TaggedError("TestPluginError")<{ readonly message: string;}> {} const recordFindMany = (adapter: DBAdapter, calls: FindManyCall[]): DBAdapter => ({ ...adapter, findMany: (data) => { calls.push({ model: data.model, where: data.where }); return adapter.findMany(data); }, transaction: (callback) => adapter.transaction((trx) => callback({ ...trx, findMany: (data) => { calls.push({ model: data.model, where: data.where }); return trx.findMany(data); }, }), ),}); // ---------------------------------------------------------------------------// Tiny test plugin — declares a static source with two built-in tools, a// plugin schema for a per-row key/value table, and a dynamic invokeTool// handler. Exercises everything createExecutor has to wire up.// --------------------------------------------------------------------------- // Plugin-declared schema. `defineSchema` preserves literal types via// `const` inference — no `as const satisfies DBSchema` ceremony.const testSchema = defineSchema({ test_thing: { fields: { id: { type: "string", required: true }, value: { type: "string", required: true }, }, },}); let testAnnotationResolveCount = 0; const testPlugin = definePlugin(() => ({ id: "test" as const, schema: testSchema, // `adapter` is typed against testSchema automatically — no imports of // DBAdapter, no typedAdapter wrapping. `model: "test_thing"` is // narrowed to the schema's model names, and row data shape comes // from the schema's field definitions. storage: ({ adapter }) => ({ writeThing: (id: string, value: string) => adapter .create({ model: "test_thing", data: { id, value }, forceAllowId: true, }) .pipe(Effect.asVoid), readThing: (id: string) => adapter .findOne({ model: "test_thing", where: [{ field: "id", value: id }], }) .pipe(Effect.map((row) => row?.value ?? null)), }), extension: (ctx) => ({ echo: (text: string) => Effect.succeed(`echo:${text}`), addThing: (id: string, value: string) => ctx.transaction( Effect.gen(function* () { yield* ctx.storage.writeThing(id, value); yield* ctx.core.sources.register({ id, scope: ctx.scopes[0]!.id, kind: "test", name: id, canRemove: true, tools: [ { name: "read", description: "read the thing" }, { name: "write", description: "overwrite the thing" }, ], }); }), ), }), staticSources: (self) => [ { id: "test.control", kind: "control", name: "Test Control", tools: [ { name: "echo", description: "static echo tool", handler: ({ args }) => self.echo((args as { text: string }).text), }, ], }, ], invokeTool: ({ ctx, toolRow, args }) => Effect.gen(function* () { // toolRow.source_id = the thing id (we registered the source with // that id). toolRow.name = "read" | "write". No string splitting. const thingId = toolRow.source_id; if (toolRow.name === "read") { return yield* ctx.storage.readThing(thingId); } if (toolRow.name === "write") { const { value } = args as { value: string }; yield* ctx.storage.writeThing(thingId, value); return { ok: true }; } return yield* new TestPluginError({ message: `unknown tool ${toolRow.id}`, }); }), // Derived annotations: `write` gates on approval, `read` doesn't. // Purely computed from the tool's name — no data persisted on the row. resolveAnnotations: ({ toolRows }) => Effect.sync(() => { testAnnotationResolveCount++; const out: Record<string, { requiresApproval: boolean; approvalDescription?: string }> = {}; for (const row of toolRows) { if (row.name === "write") { out[row.id] = { requiresApproval: true, approvalDescription: `Overwrite ${row.source_id}`, }; } else { out[row.id] = { requiresApproval: false }; } } return out; }),})); // ---------------------------------------------------------------------------// Test plugin that contributes an in-memory writable secret provider so// the secrets surface has something to talk to.// --------------------------------------------------------------------------- const memoryProvider: SecretProvider = (() => { const store = new Map<string, string>(); const key = (scope: string, id: string) => `${scope}\u0000${id}`; return { key: "memory", writable: true, get: (id, scope) => Effect.sync(() => store.get(key(scope, id)) ?? null), set: (id, value, scope) => Effect.sync(() => { store.set(key(scope, id), value); }), delete: (id, scope) => Effect.sync(() => store.delete(key(scope, id))), list: () => Effect.sync(() => Array.from(store.keys()).map((k) => { const name = k.split("\u0000", 2)[1] ?? k; return { id: name, name }; }), ), };})(); const memorySecretsPlugin = definePlugin(() => ({ id: "memory-secrets" as const, storage: () => ({}), secretProviders: [memoryProvider],})); const memoryConnectionPlugin = definePlugin(() => ({ id: "memory-connection" as const, storage: () => ({}), connectionProviders: [{ key: "memory-connection" }],})); const staleRouteProvider: SecretProvider = { key: "stale-route", writable: true, get: () => Effect.succeed(null), has: () => Effect.succeed(false), set: () => Effect.void, delete: () => Effect.succeed(false), list: () => Effect.succeed([]),}; const staleRouteSecretsPlugin = definePlugin(() => ({ id: "stale-route-secrets" as const, storage: () => ({}), secretProviders: [staleRouteProvider],})); const opaqueRouteProvider: SecretProvider = { key: "opaque-route", writable: true, get: () => Effect.succeed(null), set: () => Effect.void, delete: () => Effect.succeed(false), list: () => Effect.succeed([]),}; const opaqueRouteSecretsPlugin = definePlugin(() => ({ id: "opaque-route-secrets" as const, storage: () => ({}), secretProviders: [opaqueRouteProvider],})); const providerOnlySecretPlugin = definePlugin(() => ({ id: "provider-only-secret" as const, storage: () => ({}), secretProviders: [ { key: "provider-only", writable: false, get: (id: string) => Effect.succeed(id === "provider-token" ? "provider-value" : null), list: () => Effect.succeed([{ id: "provider-token", name: "Provider token" }]), } satisfies SecretProvider, ],})); const fallbackOrderingPlugin = (laterCalls: { count: number }) => definePlugin(() => ({ id: "fallback-ordering" as const, storage: () => ({}), secretProviders: [ { key: "first-fallback", writable: false, get: (id: string) => Effect.succeed(id === "token" ? "first-value" : null), list: () => Effect.succeed([{ id: "token", name: "token" }]), }, { key: "later-fallback", writable: false, get: () => Effect.sync(() => { laterCalls.count += 1; return "later-value"; }), list: () => Effect.succeed([{ id: "token", name: "token" }]), }, ] satisfies readonly SecretProvider[], })); const fallbackDisabledPlugin = definePlugin(() => ({ id: "fallback-disabled" as const, storage: () => ({}), secretProviders: [ { key: "fallback-disabled-provider", writable: false, allowFallback: false, get: (id: string) => Effect.succeed(id === "token" ? "disabled-value" : null), list: () => Effect.succeed([{ id: "token", name: "token" }]), }, ] satisfies readonly SecretProvider[],})); const duplicateToolsPlugin = definePlugin(() => ({ id: "duplicateTools" as const, storage: () => ({}), extension: (ctx) => ({ register: () => ctx.core.sources.register({ id: "dup-source", scope: ctx.scopes[0]!.id, kind: "test", name: "Duplicate source", tools: [ { name: "same", description: "first" }, { name: "same", description: "last" }, ], }), }),})); // ---------------------------------------------------------------------------// Tests// --------------------------------------------------------------------------- describe("createExecutor", () => { it.effect("invokes a static tool via the in-memory pool", () => Effect.gen(function* () { const executor = yield* createExecutor(makeTestConfig({ plugins: [testPlugin()] as const })); const result = yield* executor.tools.invoke( "test.control.echo", { text: "hi" }, { onElicitation: "accept-all" }, ); expect(result).toBe("echo:hi"); }), ); it.effect("lists static tools alongside dynamic ones", () => Effect.gen(function* () { const executor = yield* createExecutor(makeTestConfig({ plugins: [testPlugin()] as const })); yield* executor.test.addThing("thing1", "hello"); const tools = yield* executor.tools.list(); const ids = tools.map((t) => t.id); expect(ids).toContain("test.control.echo"); expect(ids).toContain("thing1.read"); expect(ids).toContain("thing1.write"); }), ); it.effect("filters tools by query", () => Effect.gen(function* () { const executor = yield* createExecutor(makeTestConfig({ plugins: [testPlugin()] as const })); yield* executor.test.addThing("thing1", "hello"); const tools = yield* executor.tools.list({ query: "echo" }); expect(tools.map((t) => t.id)).toEqual(["test.control.echo"]); }), ); it.effect("pushes sourceId tool list filters into storage", () => Effect.gen(function* () { const config = makeTestConfig({ plugins: [testPlugin()] as const }); const findManyCalls: FindManyCall[] = []; const executor = yield* createExecutor({ ...config, adapter: recordFindMany(config.adapter, findManyCalls), onElicitation: "accept-all", }); yield* executor.test.addThing("thing1", "hello"); yield* executor.test.addThing("thing2", "goodbye"); findManyCalls.length = 0; const tools = yield* executor.tools.list({ sourceId: "thing1" }); expect(tools.map((t) => t.id).sort()).toEqual(["thing1.read", "thing1.write"]); const toolRead = findManyCalls.find((call) => call.model === "tool"); expect(toolRead?.where).toEqual( expect.arrayContaining([expect.objectContaining({ field: "source_id", value: "thing1" })]), ); }), ); it.effect("can list tools without resolving dynamic annotations", () => Effect.gen(function* () { const executor = yield* createExecutor(makeTestConfig({ plugins: [testPlugin()] as const })); yield* executor.test.addThing("thing1", "hello"); testAnnotationResolveCount = 0; const tools = yield* executor.tools.list({ sourceId: "thing1", includeAnnotations: false, }); expect(testAnnotationResolveCount).toBe(0); expect(tools.map((t) => t.id).sort()).toEqual(["thing1.read", "thing1.write"]); expect(tools.every((tool) => tool.annotations === undefined)).toBe(true); }), ); it.effect("bounds annotation resolution groups", () => Effect.gen(function* () { const executor = yield* createExecutor(makeTestConfig({ plugins: [testPlugin()] as const })); for (let index = 0; index < 70; index++) { yield* executor.test.addThing(`thing${index}`, "hello"); } testAnnotationResolveCount = 0; yield* executor.tools.list(); expect(testAnnotationResolveCount).toBe(64); }), ); it.effect("invokes a dynamic tool through plugin.invokeTool", () => Effect.gen(function* () { const executor = yield* createExecutor(makeTestConfig({ plugins: [testPlugin()] as const })); yield* executor.test.addThing("thing1", "hello"); const result = yield* executor.tools.invoke( "thing1.read", {}, { onElicitation: "accept-all" }, ); expect(result).toBe("hello"); }), ); it.effect("enforces tool annotations before invoking", () => Effect.gen(function* () { const executor = yield* createExecutor(makeTestConfig({ plugins: [testPlugin()] as const })); yield* executor.test.addThing("thing1", "hello"); let approvalMessage = ""; // requiresApproval: true → declined → ElicitationDeclinedError const declined = yield* executor.tools .invoke( "thing1.write", { value: "updated" }, { onElicitation: (ctx) => Effect.sync(() => { approvalMessage = ctx.request.message; return ElicitationResponse.make({ action: "decline" }); }), }, ) .pipe(Effect.flip); expect(Predicate.isTagged(declined, "ElicitationDeclinedError")).toBe(true); expect(approvalMessage).toContain('"value": "updated"'); // auto-accept → succeeds const accepted = yield* executor.tools.invoke( "thing1.write", { value: "updated" }, { onElicitation: "accept-all" }, ); expect(accepted).toEqual({ ok: true }); }), ); it.effect("sources.list unions static runtime sources and dynamic ones", () => Effect.gen(function* () { const executor = yield* createExecutor(makeTestConfig({ plugins: [testPlugin()] as const })); yield* executor.test.addThing("thing1", "hello"); const sources = yield* executor.sources.list(); const control = sources.find((s) => s.id === "test.control"); expect(control).toBeDefined(); expect(control!.runtime).toBe(true); expect(control!.canRemove).toBe(false); const dynamic = sources.find((s) => s.id === "thing1"); expect(dynamic).toBeDefined(); expect(dynamic!.runtime).toBe(false); expect(dynamic!.canRemove).toBe(true); }), ); it.effect("orders source detection results by confidence", () => Effect.gen(function* () { const lowConfidencePlugin = definePlugin(() => ({ id: "low-detect" as const, storage: () => ({}), detect: () => Effect.succeed( SourceDetectionResult.make({ kind: "mcp", confidence: "low", endpoint: "https://example.com/source", name: "Weak OAuth match", namespace: "weak_oauth_match", }), ), })); const highConfidencePlugin = definePlugin(() => ({ id: "high-detect" as const, storage: () => ({}), detect: () => Effect.succeed( SourceDetectionResult.make({ kind: "graphql", confidence: "high", endpoint: "https://example.com/source", name: "GraphQL API", namespace: "graphql_api", }), ), })); const executor = yield* createExecutor( makeTestConfig({ plugins: [lowConfidencePlugin(), highConfidencePlugin()] as const, }), ); const results = yield* executor.sources.detect("https://example.com/source"); Lines 1–500 of 1981. Load more
import { describe, expect, it } from "@effect/vitest"; import { Data, Effect, Exit, Predicate, Result, Schema } from "effect"; import { FetchHttpClient } from "effect/unstable/http"; import { makeMemoryAdapter } from "@executor-js/storage-core/testing/memory"; import type { DBAdapter, Where } from "@executor-js/storage-core"; import { makeInMemoryBlobStore } from "./blob"; import { CreateConnectionInput, TokenMaterial } from "./connections"; import { collectSchemas, createExecutor } from "./executor"; import { ElicitationResponse, FormElicitation, UrlElicitation } from "./elicitation"; import { defineSchema, definePlugin, tool } from "./plugin"; import { RemoveSecretInput, SetSecretInput } from "./secrets"; import { makeTestConfig } from "./testing"; import type { SecretProvider } from "./secrets"; import { ConnectionId, ScopeId, SecretId } from "./ids"; import { Scope } from "./scope"; import { SourceDetectionResult } from "./types"; type FindManyCall = { readonly model: string; readonly where?: readonly Where[]; }; class TestPluginError extends Data.TaggedError("TestPluginError")<{ readonly message: string; }> {} const recordFindMany = (adapter: DBAdapter, calls: FindManyCall[]): DBAdapter => ({ ...adapter, findMany: (data) => { calls.push({ model: data.model, where: data.where }); return adapter.findMany(data); }, transaction: (callback) => adapter.transaction((trx) => callback({ ...trx, findMany: (data) => { calls.push({ model: data.model, where: data.where }); return trx.findMany(data); }, }), ), }); // --------------------------------------------------------------------------- // Tiny test plugin — declares a static source with two built-in tools, a // plugin schema for a per-row key/value table, and a dynamic invokeTool // handler. Exercises everything createExecutor has to wire up. // --------------------------------------------------------------------------- // Plugin-declared schema. `defineSchema` preserves literal types via // `const` inference — no `as const satisfies DBSchema` ceremony. const testSchema = defineSchema({ test_thing: { fields: { id: { type: "string", required: true }, value: { type: "string", required: true }, }, }, }); let testAnnotationResolveCount = 0; const testPlugin = definePlugin(() => ({ id: "test" as const, schema: testSchema, // `adapter` is typed against testSchema automatically — no imports of // DBAdapter, no typedAdapter wrapping. `model: "test_thing"` is // narrowed to the schema's model names, and row data shape comes // from the schema's field definitions. storage: ({ adapter }) => ({ writeThing: (id: string, value: string) => adapter .create({ model: "test_thing", data: { id, value }, forceAllowId: true, }) .pipe(Effect.asVoid), readThing: (id: string) => adapter .findOne({ model: "test_thing", where: [{ field: "id", value: id }], }) .pipe(Effect.map((row) => row?.value ?? null)), }), extension: (ctx) => ({ echo: (text: string) => Effect.succeed(`echo:${text}`), addThing: (id: string, value: string) => ctx.transaction( Effect.gen(function* () { yield* ctx.storage.writeThing(id, value); yield* ctx.core.sources.register({ id, scope: ctx.scopes[0]!.id, kind: "test", name: id, canRemove: true, tools: [ { name: "read", description: "read the thing" }, { name: "write", description: "overwrite the thing" }, ], }); }), ), }), staticSources: (self) => [ { id: "test.control", kind: "control", name: "Test Control", tools: [ { name: "echo", description: "static echo tool", handler: ({ args }) => self.echo((args as { text: string }).text), }, ], }, ], invokeTool: ({ ctx, toolRow, args }) => Effect.gen(function* () { // toolRow.source_id = the thing id (we registered the source with // that id). toolRow.name = "read" | "write". No string splitting. const thingId = toolRow.source_id; if (toolRow.name === "read") { return yield* ctx.storage.readThing(thingId); } if (toolRow.name === "write") { const { value } = args as { value: string }; yield* ctx.storage.writeThing(thingId, value); return { ok: true }; } return yield* new TestPluginError({ message: `unknown tool ${toolRow.id}`, }); }), // Derived annotations: `write` gates on approval, `read` doesn't. // Purely computed from the tool's name — no data persisted on the row. resolveAnnotations: ({ toolRows }) => Effect.sync(() => { testAnnotationResolveCount++; const out: Record<string, { requiresApproval: boolean; approvalDescription?: string }> = {}; for (const row of toolRows) { if (row.name === "write") { out[row.id] = { requiresApproval: true, approvalDescription: `Overwrite ${row.source_id}`, }; } else { out[row.id] = { requiresApproval: false }; } } return out; }), })); // --------------------------------------------------------------------------- // Test plugin that contributes an in-memory writable secret provider so // the secrets surface has something to talk to. // --------------------------------------------------------------------------- const memoryProvider: SecretProvider = (() => { const store = new Map<string, string>(); const key = (scope: string, id: string) => `${scope}\u0000${id}`; return { key: "memory", writable: true, get: (id, scope) => Effect.sync(() => store.get(key(scope, id)) ?? null), set: (id, value, scope) => Effect.sync(() => { store.set(key(scope, id), value); }), delete: (id, scope) => Effect.sync(() => store.delete(key(scope, id))), list: () => Effect.sync(() => Array.from(store.keys()).map((k) => { const name = k.split("\u0000", 2)[1] ?? k; return { id: name, name }; }), ), }; })(); const memorySecretsPlugin = definePlugin(() => ({ id: "memory-secrets" as const, storage: () => ({}), secretProviders: [memoryProvider], })); const memoryConnectionPlugin = definePlugin(() => ({ id: "memory-connection" as const, storage: () => ({}), connectionProviders: [{ key: "memory-connection" }], })); const staleRouteProvider: SecretProvider = { key: "stale-route", writable: true, get: () => Effect.succeed(null), has: () => Effect.succeed(false), set: () => Effect.void, delete: () => Effect.succeed(false), list: () => Effect.succeed([]), }; const staleRouteSecretsPlugin = definePlugin(() => ({ id: "stale-route-secrets" as const, storage: () => ({}), secretProviders: [staleRouteProvider], })); const opaqueRouteProvider: SecretProvider = { key: "opaque-route", writable: true, get: () => Effect.succeed(null), set: () => Effect.void, delete: () => Effect.succeed(false), list: () => Effect.succeed([]), }; const opaqueRouteSecretsPlugin = definePlugin(() => ({ id: "opaque-route-secrets" as const, storage: () => ({}), secretProviders: [opaqueRouteProvider], })); const providerOnlySecretPlugin = definePlugin(() => ({ id: "provider-only-secret" as const, storage: () => ({}), secretProviders: [ { key: "provider-only", writable: false, get: (id: string) => Effect.succeed(id === "provider-token" ? "provider-value" : null), list: () => Effect.succeed([{ id: "provider-token", name: "Provider token" }]), } satisfies SecretProvider, ], })); const fallbackOrderingPlugin = (laterCalls: { count: number }) => definePlugin(() => ({ id: "fallback-ordering" as const, storage: () => ({}), secretProviders: [ { key: "first-fallback", writable: false, get: (id: string) => Effect.succeed(id === "token" ? "first-value" : null), list: () => Effect.succeed([{ id: "token", name: "token" }]), }, { key: "later-fallback", writable: false, get: () => Effect.sync(() => { laterCalls.count += 1; return "later-value"; }), list: () => Effect.succeed([{ id: "token", name: "token" }]), }, ] satisfies readonly SecretProvider[], })); const fallbackDisabledPlugin = definePlugin(() => ({ id: "fallback-disabled" as const, storage: () => ({}), secretProviders: [ { key: "fallback-disabled-provider", writable: false, allowFallback: false, get: (id: string) => Effect.succeed(id === "token" ? "disabled-value" : null), list: () => Effect.succeed([{ id: "token", name: "token" }]), }, ] satisfies readonly SecretProvider[], })); const duplicateToolsPlugin = definePlugin(() => ({ id: "duplicateTools" as const, storage: () => ({}), extension: (ctx) => ({ register: () => ctx.core.sources.register({ id: "dup-source", scope: ctx.scopes[0]!.id, kind: "test", name: "Duplicate source", tools: [ { name: "same", description: "first" }, { name: "same", description: "last" }, ], }), }), })); // --------------------------------------------------------------------------- // Tests // --------------------------------------------------------------------------- describe("createExecutor", () => { it.effect("invokes a static tool via the in-memory pool", () => Effect.gen(function* () { const executor = yield* createExecutor(makeTestConfig({ plugins: [testPlugin()] as const })); const result = yield* executor.tools.invoke( "test.control.echo", { text: "hi" }, { onElicitation: "accept-all" }, ); expect(result).toBe("echo:hi"); }), ); it.effect("lists static tools alongside dynamic ones", () => Effect.gen(function* () { const executor = yield* createExecutor(makeTestConfig({ plugins: [testPlugin()] as const })); yield* executor.test.addThing("thing1", "hello"); const tools = yield* executor.tools.list(); const ids = tools.map((t) => t.id); expect(ids).toContain("test.control.echo"); expect(ids).toContain("thing1.read"); expect(ids).toContain("thing1.write"); }), ); it.effect("filters tools by query", () => Effect.gen(function* () { const executor = yield* createExecutor(makeTestConfig({ plugins: [testPlugin()] as const })); yield* executor.test.addThing("thing1", "hello"); const tools = yield* executor.tools.list({ query: "echo" }); expect(tools.map((t) => t.id)).toEqual(["test.control.echo"]); }), ); it.effect("pushes sourceId tool list filters into storage", () => Effect.gen(function* () { const config = makeTestConfig({ plugins: [testPlugin()] as const }); const findManyCalls: FindManyCall[] = []; const executor = yield* createExecutor({ ...config, adapter: recordFindMany(config.adapter, findManyCalls), onElicitation: "accept-all", }); yield* executor.test.addThing("thing1", "hello"); yield* executor.test.addThing("thing2", "goodbye"); findManyCalls.length = 0; const tools = yield* executor.tools.list({ sourceId: "thing1" }); expect(tools.map((t) => t.id).sort()).toEqual(["thing1.read", "thing1.write"]); const toolRead = findManyCalls.find((call) => call.model === "tool"); expect(toolRead?.where).toEqual( expect.arrayContaining([expect.objectContaining({ field: "source_id", value: "thing1" })]), ); }), ); it.effect("can list tools without resolving dynamic annotations", () => Effect.gen(function* () { const executor = yield* createExecutor(makeTestConfig({ plugins: [testPlugin()] as const })); yield* executor.test.addThing("thing1", "hello"); testAnnotationResolveCount = 0; const tools = yield* executor.tools.list({ sourceId: "thing1", includeAnnotations: false, }); expect(testAnnotationResolveCount).toBe(0); expect(tools.map((t) => t.id).sort()).toEqual(["thing1.read", "thing1.write"]); expect(tools.every((tool) => tool.annotations === undefined)).toBe(true); }), ); it.effect("bounds annotation resolution groups", () => Effect.gen(function* () { const executor = yield* createExecutor(makeTestConfig({ plugins: [testPlugin()] as const })); for (let index = 0; index < 70; index++) { yield* executor.test.addThing(`thing${index}`, "hello"); } testAnnotationResolveCount = 0; yield* executor.tools.list(); expect(testAnnotationResolveCount).toBe(64); }), ); it.effect("invokes a dynamic tool through plugin.invokeTool", () => Effect.gen(function* () { const executor = yield* createExecutor(makeTestConfig({ plugins: [testPlugin()] as const })); yield* executor.test.addThing("thing1", "hello"); const result = yield* executor.tools.invoke( "thing1.read", {}, { onElicitation: "accept-all" }, ); expect(result).toBe("hello"); }), ); it.effect("enforces tool annotations before invoking", () => Effect.gen(function* () { const executor = yield* createExecutor(makeTestConfig({ plugins: [testPlugin()] as const })); yield* executor.test.addThing("thing1", "hello"); let approvalMessage = ""; // requiresApproval: true → declined → ElicitationDeclinedError const declined = yield* executor.tools .invoke( "thing1.write", { value: "updated" }, { onElicitation: (ctx) => Effect.sync(() => { approvalMessage = ctx.request.message; return ElicitationResponse.make({ action: "decline" }); }), }, ) .pipe(Effect.flip); expect(Predicate.isTagged(declined, "ElicitationDeclinedError")).toBe(true); expect(approvalMessage).toContain('"value": "updated"'); // auto-accept → succeeds const accepted = yield* executor.tools.invoke( "thing1.write", { value: "updated" }, { onElicitation: "accept-all" }, ); expect(accepted).toEqual({ ok: true }); }), ); it.effect("sources.list unions static runtime sources and dynamic ones", () => Effect.gen(function* () { const executor = yield* createExecutor(makeTestConfig({ plugins: [testPlugin()] as const })); yield* executor.test.addThing("thing1", "hello"); const sources = yield* executor.sources.list(); const control = sources.find((s) => s.id === "test.control"); expect(control).toBeDefined(); expect(control!.runtime).toBe(true); expect(control!.canRemove).toBe(false); const dynamic = sources.find((s) => s.id === "thing1"); expect(dynamic).toBeDefined(); expect(dynamic!.runtime).toBe(false); expect(dynamic!.canRemove).toBe(true); }), ); it.effect("orders source detection results by confidence", () => Effect.gen(function* () { const lowConfidencePlugin = definePlugin(() => ({ id: "low-detect" as const, storage: () => ({}), detect: () => Effect.succeed( SourceDetectionResult.make({ kind: "mcp", confidence: "low", endpoint: "https://example.com/source", name: "Weak OAuth match", namespace: "weak_oauth_match", }), ), })); const highConfidencePlugin = definePlugin(() => ({ id: "high-detect" as const, storage: () => ({}), detect: () => Effect.succeed( SourceDetectionResult.make({ kind: "graphql", confidence: "high", endpoint: "https://example.com/source", name: "GraphQL API", namespace: "graphql_api", }), ), })); const executor = yield* createExecutor( makeTestConfig({ plugins: [lowConfidencePlugin(), highConfidencePlugin()] as const, }), ); const results = yield* executor.sources.detect("https://example.com/source");