import * as Effect from "effect/Effect"; import * as Schema from "effect/Schema"; import { createDiscoveryPrimitivesFromToolCatalog } from "./discovery"; import { toTool } from "./tool-map"; import { ToolDescriptorSchema, ToolNamespaceSchema, type ToolCatalog, type ToolMap, type ToolPath, } from "./types"; const asToolPath = (value: string): ToolPath => value as ToolPath; const catalogNamespacesInputSchema = Schema.standardSchemaV1( Schema.Struct({ limit: Schema.optional(Schema.Number), }), ); const catalogNamespacesOutputSchema = Schema.standardSchemaV1( Schema.Struct({ namespaces: Schema.Array(ToolNamespaceSchema), }), ); const catalogToolsInputSchema = Schema.standardSchemaV1( Schema.Struct({ namespace: Schema.optional(Schema.String), query: Schema.optional(Schema.String), limit: Schema.optional(Schema.Number), includeSchemas: Schema.optional(Schema.Boolean), }), ); const catalogToolsOutputSchema = Schema.standardSchemaV1( Schema.Struct({ results: Schema.Array(ToolDescriptorSchema), }), ); const describeToolInputSchema = Schema.standardSchemaV1( Schema.Struct({ path: Schema.String, includeSchemas: Schema.optional(Schema.Boolean), }), ); const describeToolOutputSchema = Schema.standardSchemaV1( Schema.NullOr(ToolDescriptorSchema), ); const discoverInputSchema = Schema.standardSchemaV1( Schema.Struct({ query: Schema.String, limit: Schema.optional(Schema.Number), includeSchemas: Schema.optional(Schema.Boolean), }), ); const discoverResultItemSchema = Schema.extend( ToolDescriptorSchema, Schema.Struct({ score: Schema.Number, }), ); const discoverOutputSchema = Schema.standardSchemaV1( Schema.Struct({ bestPath: Schema.NullOr(Schema.String), results: Schema.Array(discoverResultItemSchema), total: Schema.Number, }), ); export type CreateSystemToolMapInput = { catalog?: ToolCatalog; getCatalog?: () => ToolCatalog; sourceKey?: string; }; export const createSystemToolMap = ( input: CreateSystemToolMapInput, ): ToolMap => { const sourceKey = input.sourceKey ?? "system"; const getCatalog = (): ToolCatalog => { if (input.catalog) { return input.catalog; } if (input.getCatalog) { return input.getCatalog(); } throw new Error("createSystemToolMap requires a catalog or getCatalog"); }; const getPrimitives = () => createDiscoveryPrimitivesFromToolCatalog({ catalog: getCatalog(), }) as Required>; const tools: ToolMap = {}; tools["catalog.namespaces"] = toTool({ tool: { description: "List available namespaces with display names and tool counts", inputSchema: catalogNamespacesInputSchema, outputSchema: catalogNamespacesOutputSchema, execute: ({ limit }: { limit?: number }) => Effect.runPromise( getPrimitives().catalog.namespaces((limit !== undefined ? { limit } : {})), ), }, metadata: { sourceKey, interaction: "auto", }, }); tools["catalog.tools"] = toTool({ tool: { description: "List tools with optional namespace and query filters", inputSchema: catalogToolsInputSchema, outputSchema: catalogToolsOutputSchema, execute: ( toolInput: { namespace?: string; query?: string; limit?: number; includeSchemas?: boolean; }, ) => Effect.runPromise( getPrimitives().catalog.tools({ ...(toolInput.namespace !== undefined ? { namespace: toolInput.namespace } : {}), ...(toolInput.query !== undefined ? { query: toolInput.query } : {}), ...(toolInput.limit !== undefined ? { limit: toolInput.limit } : {}), ...(toolInput.includeSchemas !== undefined ? { includeSchemas: toolInput.includeSchemas } : {}), }), ), }, metadata: { sourceKey, interaction: "auto", }, }); tools["describe.tool"] = toTool({ tool: { description: "Get metadata and optional schemas for a tool path", inputSchema: describeToolInputSchema, outputSchema: describeToolOutputSchema, execute: ({ path, includeSchemas }: { path: string; includeSchemas?: boolean }) => Effect.runPromise( getPrimitives().describe.tool({ path: asToolPath(path), ...(includeSchemas !== undefined ? { includeSchemas } : {}), }), ), }, metadata: { sourceKey, interaction: "auto", }, }); tools.discover = toTool({ tool: { description: "Search tools by intent and return ranked matches", inputSchema: discoverInputSchema, outputSchema: discoverOutputSchema, execute: ( toolInput: { query: string; limit?: number; includeSchemas?: boolean; }, ) => Effect.runPromise( getPrimitives().discover({ query: toolInput.query, ...(toolInput.limit !== undefined ? { limit: toolInput.limit } : {}), ...(toolInput.includeSchemas !== undefined ? { includeSchemas: toolInput.includeSchemas } : {}), }), ), }, metadata: { sourceKey, interaction: "auto", }, }); return tools; }; export type MergeToolMapsOptions = { conflictMode?: "throw" | "override"; }; export const mergeToolMaps = ( maps: ReadonlyArray, options: MergeToolMapsOptions = {}, ): ToolMap => { const conflictMode = options.conflictMode ?? "throw"; const merged: ToolMap = {}; for (const map of maps) { for (const [path, tool] of Object.entries(map)) { if (conflictMode === "throw" && path in merged) { throw new Error(`Tool path conflict: ${path}`); } merged[path] = tool; } } return merged; };