From 760d219d8f00edfae4e3da5c3fc84dac96aa5198 Mon Sep 17 00:00:00 2001 From: Saatvik Arya Date: Sun, 21 Jun 2026 23:37:45 +0530 Subject: [PATCH] perf(sdk): precompile tool-schema TypeScript during indexing executor.tools.schema always renders the TypeScript preview now. The reindex pipeline -- the only caller that passed includeTypeScript:false to skip codegen -- calls it plainly, so each reindex warms the exact KV schema-view the web UI reads. First tool-detail views become cache hits instead of a cold compile. Embedding text is unchanged (collectDocForTool only reads raw schema facets, never the rendered TypeScript). Removes the includeTypeScript option + the now-empty ToolSchemaOptions type, and drops the flag from the schema-view cache key (old entries re-warm on next index/view). --- .../tools-schema-precompile-typescript.md | 15 +++++ packages/core/sdk/src/executor.test.ts | 2 +- packages/core/sdk/src/executor.ts | 57 +++++++------------ packages/core/sdk/src/index.ts | 1 - packages/core/sdk/src/promise.ts | 1 - packages/core/sdk/src/shared.ts | 1 - .../sdk/src/tool-schema-view-cache.test.ts | 3 - .../core/sdk/src/tool-schema-view-cache.ts | 4 -- packages/core/sdk/src/types.ts | 12 ---- .../semantic-search/src/sdk/documents.ts | 10 ++-- .../src/sdk/tool-search-index.test.ts | 12 ++-- 11 files changed, 47 insertions(+), 71 deletions(-) create mode 100644 .changeset/tools-schema-precompile-typescript.md diff --git a/.changeset/tools-schema-precompile-typescript.md b/.changeset/tools-schema-precompile-typescript.md new file mode 100644 index 0000000000..817d475411 --- /dev/null +++ b/.changeset/tools-schema-precompile-typescript.md @@ -0,0 +1,15 @@ +--- +"@executor-js/sdk": minor +"@executor-js/plugin-semantic-search": patch +--- + +Always render the TypeScript preview in `executor.tools.schema` and warm it during +indexing. The `includeTypeScript` option (and the now-empty `ToolSchemaOptions` type) is +removed — the only caller that passed `false` was the reindex pipeline, which did so to +skip the CPU-heavy JSON-schema → TypeScript codegen. It now calls `tools.schema` plainly, +so every reindex pre-warms exactly the schema view the web UI reads on first open (the +codegen is content-addressed in KV), turning first tool-detail views into cache hits +instead of a cold compile. The embedding text is unchanged — the indexer only ever read +the raw schema facets, never the rendered TypeScript. Because `includeTypeScript` is +dropped from the schema-view cache key, existing cached entries are superseded on the next +reindex or view (regenerable derived data). diff --git a/packages/core/sdk/src/executor.test.ts b/packages/core/sdk/src/executor.test.ts index 9030a08351..614f73bf56 100644 --- a/packages/core/sdk/src/executor.test.ts +++ b/packages/core/sdk/src/executor.test.ts @@ -492,7 +492,7 @@ describe("createExecutor", () => { expect(inspect?.definitionSetHash).toHaveLength(64); expect(inspect?.indexFingerprint).toHaveLength(64); - const schema = yield* executor.tools.schema(addr("inspect"), { includeTypeScript: false }); + const schema = yield* executor.tools.schema(addr("inspect")); expect(schema?.schemaDefinitions).not.toHaveProperty("Unused"); }), ); diff --git a/packages/core/sdk/src/executor.ts b/packages/core/sdk/src/executor.ts index a6b6a1f05d..0cac230f17 100644 --- a/packages/core/sdk/src/executor.ts +++ b/packages/core/sdk/src/executor.ts @@ -150,12 +150,7 @@ import { ORG_SUBJECT, type ExecutorOwnerPolicyContext, } from "./owner-policy"; -import { - ToolSchemaManifest, - ToolSchemaView, - type IntegrationDetectionResult, - type ToolSchemaOptions, -} from "./types"; +import { ToolSchemaManifest, ToolSchemaView, type IntegrationDetectionResult } from "./types"; import { type Tool, type ToolAnnotations, type ToolDef, type ToolListFilter } from "./tool"; import { buildToolTypeScriptPreview, type ToolTypeScriptPreview } from "./schema-types"; import { collectReferencedDefinitions } from "./schema-refs"; @@ -329,10 +324,7 @@ export type Executor = { readonly manifest: ( filter?: ToolListFilter, ) => Effect.Effect; - readonly schema: ( - address: ToolAddress, - options?: ToolSchemaOptions, - ) => Effect.Effect; + readonly schema: (address: ToolAddress) => Effect.Effect; }; readonly providers: { @@ -3216,14 +3208,12 @@ export const createExecutor = => Effect.gen(function* () { - // The JSON-schema → TypeScript codegen is the CPU-heavy part of this - // surface; callers that only need the schema's content (a reindex - // computing a content fingerprint) opt out and skip it. Raw schema roots - // + referenced `$defs` are returned either way. - const includeTypeScript = options?.includeTypeScript ?? true; + // Always renders the JSON-schema → TypeScript preview. The codegen is the + // CPU-heavy part of this surface, but the rendered view is content-addressed + // in KV, so the reindex pipeline warms exactly the view the web UI reads on + // first open. Raw schema roots + referenced `$defs` are returned alongside. const staticEntry = staticTools.get(String(address)); if (staticEntry) { const tool = staticToolToTool(staticEntry); @@ -3234,22 +3224,19 @@ export const createExecutor = Effect.succeed(Option.none()))); if (Option.isSome(cached)) return cached.value.view; - const preview = includeTypeScript - ? Option.getOrUndefined( - yield* buildCachedToolTypeScriptPreview({ - inputSchema: tool.inputSchema, - outputSchema: tool.outputSchema, - defs: new Map(), - }).pipe(Effect.option), - ) - : undefined; + const preview = Option.getOrUndefined( + yield* buildCachedToolTypeScriptPreview({ + inputSchema: tool.inputSchema, + outputSchema: tool.outputSchema, + defs: new Map(), + }).pipe(Effect.option), + ); const view = ToolSchemaView.make({ address, name: tool.name, @@ -3290,7 +3277,6 @@ export const createExecutor = (Object.entries(referenced)); - const preview = includeTypeScript - ? Option.getOrUndefined( - yield* buildCachedToolTypeScriptPreview({ - inputSchema: tool.inputSchema, - outputSchema: tool.outputSchema, - defs: referencedDefs, - }).pipe(Effect.option), - ) - : undefined; + const preview = Option.getOrUndefined( + yield* buildCachedToolTypeScriptPreview({ + inputSchema: tool.inputSchema, + outputSchema: tool.outputSchema, + defs: referencedDefs, + }).pipe(Effect.option), + ); const view = ToolSchemaView.make({ address, diff --git a/packages/core/sdk/src/index.ts b/packages/core/sdk/src/index.ts index 87bb80370f..f86e8a5f68 100644 --- a/packages/core/sdk/src/index.ts +++ b/packages/core/sdk/src/index.ts @@ -99,7 +99,6 @@ export type { CredentialProvider, ProviderEntry } from "./provider"; // Public projections / detection. export { ToolSchemaManifest, ToolSchemaView, IntegrationDetectionResult } from "./types"; -export type { ToolSchemaOptions } from "./types"; // Core schema. export { diff --git a/packages/core/sdk/src/promise.ts b/packages/core/sdk/src/promise.ts index 309e950f91..49c455292a 100644 --- a/packages/core/sdk/src/promise.ts +++ b/packages/core/sdk/src/promise.ts @@ -44,7 +44,6 @@ export type { UpdateToolPolicyInput, } from "./policies"; export { ToolSchemaManifest, ToolSchemaView, IntegrationDetectionResult } from "./types"; -export type { ToolSchemaOptions } from "./types"; export type { Tool, ToolDef, ToolListFilter, ToolAnnotations } from "./tool"; export type { AnyPlugin, PluginExtensions } from "./plugin"; export type { diff --git a/packages/core/sdk/src/shared.ts b/packages/core/sdk/src/shared.ts index ef1588f631..e4939ef60f 100644 --- a/packages/core/sdk/src/shared.ts +++ b/packages/core/sdk/src/shared.ts @@ -97,7 +97,6 @@ export type { ToolPolicyAction } from "./core-schema"; // Schema-side views + onboarding autodetect. export { ToolSchemaManifest, ToolSchemaView, IntegrationDetectionResult } from "./types"; -export type { ToolSchemaOptions } from "./types"; // OAuth wire contracts (data + tagged errors; the flow impl is server-only). export { diff --git a/packages/core/sdk/src/tool-schema-view-cache.test.ts b/packages/core/sdk/src/tool-schema-view-cache.test.ts index 425b987b1c..9dc1a05b95 100644 --- a/packages/core/sdk/src/tool-schema-view-cache.test.ts +++ b/packages/core/sdk/src/tool-schema-view-cache.test.ts @@ -69,9 +69,6 @@ describe("toolSchemaViewCacheKey", () => { inputSchema: { type: "object", properties: { pet: { type: "string" } } }, }), ).not.toBe(sameKey); - expect(yield* toolSchemaViewCacheKey({ ...base, includeTypeScript: false })).not.toBe( - sameKey, - ); }), ); }); diff --git a/packages/core/sdk/src/tool-schema-view-cache.ts b/packages/core/sdk/src/tool-schema-view-cache.ts index ca385781b3..ce99b289f6 100644 --- a/packages/core/sdk/src/tool-schema-view-cache.ts +++ b/packages/core/sdk/src/tool-schema-view-cache.ts @@ -25,14 +25,12 @@ interface ToolSchemaViewCacheKeyInput { readonly inputSchema?: unknown; readonly outputSchema?: unknown; readonly definitions: Readonly>; - readonly includeTypeScript?: boolean; } interface ToolSchemaViewManifestCacheKeyInput { readonly address: string; readonly indexFingerprint: string; readonly fingerprintVersion: string; - readonly includeTypeScript?: boolean; } const cachePayload = (input: ToolSchemaViewCacheKeyInput): string => @@ -46,7 +44,6 @@ const cachePayload = (input: ToolSchemaViewCacheKeyInput): string => inputSchema: input.inputSchema, outputSchema: input.outputSchema, definitions: input.definitions, - includeTypeScript: input.includeTypeScript ?? true, }); const manifestCachePayload = (input: ToolSchemaViewManifestCacheKeyInput): string => @@ -57,7 +54,6 @@ const manifestCachePayload = (input: ToolSchemaViewManifestCacheKeyInput): strin address: input.address, indexFingerprint: input.indexFingerprint, fingerprintVersion: input.fingerprintVersion, - includeTypeScript: input.includeTypeScript ?? true, }); export const toolSchemaViewCacheKey = (input: ToolSchemaViewCacheKeyInput): Effect.Effect => diff --git a/packages/core/sdk/src/types.ts b/packages/core/sdk/src/types.ts index 05a9f4b34a..3963046066 100644 --- a/packages/core/sdk/src/types.ts +++ b/packages/core/sdk/src/types.ts @@ -54,18 +54,6 @@ export const ToolSchemaManifest = Schema.Struct({ }); export type ToolSchemaManifest = typeof ToolSchemaManifest.Type; -/** Options for `executor.tools.schema`. */ -export interface ToolSchemaOptions { - /** Generate the TypeScript preview strings (`inputTypeScript` / - * `outputTypeScript` / `typeScriptDefinitions`). Defaults to `true`. Pass - * `false` to skip the CPU-heavy JSON-schema → TypeScript codegen and return - * only the raw JSON schema roots plus referenced `$defs` — the cheap tier for - * callers that need the schema's content but not its rendered types (e.g. a - * reindex computing a content fingerprint). Mirrors `tools.list`'s - * `includeAnnotations` opt-out of an expensive per-tool derivation. */ - readonly includeTypeScript?: boolean; -} - // --------------------------------------------------------------------------- // Integration detection — optional capability on `PluginSpec.detect`. When a // user pastes a URL in the onboarding UI, `executor.integrations.detect(url)` diff --git a/packages/plugins/semantic-search/src/sdk/documents.ts b/packages/plugins/semantic-search/src/sdk/documents.ts index 5250b10c84..20ea2beffe 100644 --- a/packages/plugins/semantic-search/src/sdk/documents.ts +++ b/packages/plugins/semantic-search/src/sdk/documents.ts @@ -145,7 +145,7 @@ export const listToolDescriptors = ( * * This is the manifest tier for indexing: it carries precomputed raw-schema * fingerprints from source refresh, so scan/diff can avoid per-tool - * `tools.schema(includeTypeScript: false)` reads entirely. */ + * `tools.schema` reads entirely. */ export const listToolManifests = ( executor: Executor, options?: ListToolDescriptorsOptions, @@ -219,8 +219,10 @@ const schemaDefinitionsFacetText = ( /** Collect `ToolDocumentInput` for a single tool descriptor. * - * Attempts to fetch raw schema via `tools.schema(address, { includeTypeScript: - * false })` to populate compact schema facets; on failure it degrades + * Fetches the tool's schema via `tools.schema(address)` to populate compact + * schema facets from the raw input/output schema + `$defs`. The TypeScript + * preview it also renders is unused here, but the call warms the shared + * schema-view cache the web UI reads on first open. On failure it degrades * gracefully to an identity-only document. */ export const collectDocForTool = ( @@ -234,7 +236,7 @@ export const collectDocForTool = ( integration: String(tool.integration), description: stripHtml(String(tool.description ?? "")), }; - return executor.tools.schema(tool.address as Tool["address"], { includeTypeScript: false }).pipe( + return executor.tools.schema(tool.address as Tool["address"]).pipe( Effect.map((view): ToolDocumentInput => { if (view === null) return { ...base, lexicalText: buildLexicalText(base) }; diff --git a/packages/plugins/semantic-search/src/sdk/tool-search-index.test.ts b/packages/plugins/semantic-search/src/sdk/tool-search-index.test.ts index 1232ef0d19..b09e0a2c77 100644 --- a/packages/plugins/semantic-search/src/sdk/tool-search-index.test.ts +++ b/packages/plugins/semantic-search/src/sdk/tool-search-index.test.ts @@ -68,14 +68,12 @@ const makeExecutor = ( tools: { list: () => Effect.succeed([tool]), manifest: () => Effect.succeed([manifestForTool(tool)]), - schema: (address, options) => { - const includeTypeScript = options?.includeTypeScript ?? true; - if (includeTypeScript) counters.codegen++; - else counters.raw++; + schema: (address) => { + counters.codegen++; return Effect.succeed({ address, inputSchema: { type: "object", properties: { owner: { type: "string" } } }, - inputTypeScript: includeTypeScript ? "{ owner: string }" : undefined, + inputTypeScript: "{ owner: string }", }); }, }, @@ -298,7 +296,7 @@ describe("ToolSearchIndex", () => { expect(chunked.processed).toBe(1); expect(chunked.chunks).toBeGreaterThan(0); expect(embedded).toMatchObject({ processed: chunked.chunks, chunks: chunked.chunks }); - expect(counters).toEqual({ raw: 1, codegen: 0 }); + expect(counters).toEqual({ raw: 0, codegen: 1 }); expect(upserted).toHaveLength(chunked.chunks); for (const vector of upserted) { expect( @@ -478,7 +476,7 @@ describe("ToolSearchIndex", () => { expect(first.chunkRefs.length).toBeGreaterThan(0); expect(retry.chunkRefs).toEqual(first.chunkRefs); expect(retry.commitPaths).toEqual([]); - expect(counters).toEqual({ raw: 1, codegen: 0 }); + expect(counters).toEqual({ raw: 0, codegen: 1 }); }), );