Skip to content

perf(sdk): precompile tool-schema TypeScript during indexing - #49

Merged
aryasaatvik merged 1 commit into
devfrom
perf/precompile-tool-schema-typescript
Jun 21, 2026
Merged

aryasaatvik merged 1 commit into
devfrom
perf/precompile-tool-schema-typescript

Conversation

@aryasaatvik

@aryasaatvik aryasaatvik commented Jun 21, 2026 •

Copy link
Copy Markdown
Owner

Why

Production traces showed the web UI's tool-detail path (GET /api/tools/schema, includeTypeScript: true) paying the json-schema → TypeScript compile on the first view of every tool. The reindex pipeline already visits every tool's schema, but it called tools.schema({ includeTypeScript: false }) specifically to skip that compile — so it warmed the wrong cache variant and the UI's variant stayed cold until first manual view.

What

  • executor.tools.schema always renders the TypeScript preview; the includeTypeScript option and the now-empty ToolSchemaOptions type are removed.
  • The reindex pipeline (collectDocForTool) calls tools.schema plainly, so every reindex pre-warms exactly the KV schema-view the UI reads → first tool-detail views become cache hits instead of a cold compile.
  • includeTypeScript is dropped from the schema-view cache key (content + manifest), collapsing the two variants into one.

Safety

  • Embedding/lexical text is unchanged: collectDocForTool only reads the raw schema facets (inputSchema / outputSchema / schemaDefinitions), never the rendered TypeScript — so no re-embedding / index churn.
  • The cache-key change supersedes existing entries; they re-warm on the next reindex or view (regenerable derived data).

Gate

tsgo (43/43), oxlint --deny-warnings, oxfmt, and vitest for @executor-js/sdk + @executor-js/plugin-semantic-search all green.

Follow-up: a reindex on the deployed instance warms the new unified variant.

Stack

  1. perf(sdk): precompile tool-schema TypeScript during indexing #49 👈 current
  2. perf(sdk): cache tools.list by catalog + policy revision #50

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).
@greptile-apps

greptile-apps Bot commented Jun 21, 2026 •

Copy link
Copy Markdown

Greptile Summary

This PR eliminates the two-tier includeTypeScript split in executor.tools.schema so the reindex pipeline and the web UI now share a single unified KV cache entry per tool. Previously, the reindex called schema({ includeTypeScript: false }) to skip the expensive JSON-schema → TypeScript codegen, inadvertently leaving the UI's cache variant cold and forcing a live compile on every first tool-detail view.

  • Removes ToolSchemaOptions (and the options parameter) from executor.tools.schema — the function now unconditionally renders the TypeScript preview, with results content-addressed in KV so the reindex pre-warms exactly what the UI reads.
  • Collapses the two cache-key variants into one by dropping includeTypeScript from both the content-hash and manifest-hash payloads; existing entries are superseded and re-warm on the next reindex or view.
  • Updates collectDocForTool in the semantic-search plugin to call schema(address) plainly, and adjusts tests to reflect the new single-counter expectation (codegen: 1).

Confidence Score: 4/5

The cache-warming logic and API simplification are correct; the main concern is the changeset classification of a public API removal.

The @executor-js/sdk package is publicly published at v1.5.10. Removing the exported ToolSchemaOptions type and the options parameter from executor.tools.schema() will cause TypeScript compile errors for any external consumer that imported the type or explicitly passed { includeTypeScript: ... }. The changeset marks this as minor, which would land as v1.6.0 rather than v2.0.0. The rest of the change — collapsing the two cache variants, updating the semantic-search pipeline, and removing dead codepaths — is clean and well-reasoned.

.changeset/tools-schema-precompile-typescript.md — bump level should be revisited before publish. packages/plugins/semantic-search/src/sdk/tool-search-index.test.ts — vestigial raw counter field in the type signature and assertions.

Important Files Changed

Filename Overview
.changeset/tools-schema-precompile-typescript.md Marks @executor-js/sdk as minor despite removing a publicly-exported type and a public API parameter — a breaking change for any consumer importing ToolSchemaOptions or passing { includeTypeScript: ... }.
packages/core/sdk/src/executor.ts Drops options?: ToolSchemaOptions from toolSchema and unconditionally calls buildCachedToolTypeScriptPreview; both code paths (static and dynamic) updated consistently.
packages/core/sdk/src/tool-schema-view-cache.ts Removes includeTypeScript from both cache-key payload structs, collapsing two cache variants into one; existing entries will be superseded (intentional).
packages/core/sdk/src/types.ts Removes the ToolSchemaOptions interface; clean deletion with no remaining references.
packages/core/sdk/src/index.ts Removes ToolSchemaOptions from the public barrel export — consistent with the type removal.
packages/core/sdk/src/promise.ts Removes ToolSchemaOptions re-export from the promise-based public API surface.
packages/core/sdk/src/shared.ts Removes ToolSchemaOptions re-export from the shared API surface.
packages/core/sdk/src/tool-schema-view-cache.test.ts Drops the includeTypeScript: false cache-key test case; remaining tests adequately cover the unified key.
packages/plugins/semantic-search/src/sdk/documents.ts Updates collectDocForTool to call executor.tools.schema(address) without options, so every reindex warms the shared cache the web UI reads.
packages/plugins/semantic-search/src/sdk/tool-search-index.test.ts Test mock simplified to always increment codegen; assertions updated to {raw: 0, codegen: 1} but the raw field is vestigial — initialized and asserted at 0 but never incremented in the new code.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant RI as Reindex Pipeline
    participant SDK as executor.tools.schema
    participant KV as KV Cache
    participant TS as TypeScript Codegen
    participant UI as Web UI

    note over RI,UI: Before this PR
    RI->>SDK: "schema(addr, { includeTypeScript: false })"
    SDK->>KV: get(key[false])
    KV-->>SDK: miss
    SDK-->>RI: raw schema (no TypeScript)
    UI->>SDK: schema(addr)
    SDK->>KV: get(key[true])
    KV-->>SDK: miss (different key variant)
    SDK->>TS: buildCachedToolTypeScriptPreview(...)
    TS-->>SDK: preview
    SDK->>KV: set(key[true], view)
    SDK-->>UI: view with TypeScript (cold compile on first view)

    note over RI,UI: After this PR
    RI->>SDK: schema(addr)
    SDK->>KV: get(unified_key)
    KV-->>SDK: miss (first reindex)
    SDK->>TS: buildCachedToolTypeScriptPreview(...)
    TS-->>SDK: preview
    SDK->>KV: set(unified_key, view)
    SDK-->>RI: view (TypeScript included, cache warmed)
    UI->>SDK: schema(addr)
    SDK->>KV: get(unified_key)
    KV-->>SDK: hit
    SDK-->>UI: view with TypeScript (cache hit)
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant RI as Reindex Pipeline
    participant SDK as executor.tools.schema
    participant KV as KV Cache
    participant TS as TypeScript Codegen
    participant UI as Web UI

    note over RI,UI: Before this PR
    RI->>SDK: "schema(addr, { includeTypeScript: false })"
    SDK->>KV: get(key[false])
    KV-->>SDK: miss
    SDK-->>RI: raw schema (no TypeScript)
    UI->>SDK: schema(addr)
    SDK->>KV: get(key[true])
    KV-->>SDK: miss (different key variant)
    SDK->>TS: buildCachedToolTypeScriptPreview(...)
    TS-->>SDK: preview
    SDK->>KV: set(key[true], view)
    SDK-->>UI: view with TypeScript (cold compile on first view)

    note over RI,UI: After this PR
    RI->>SDK: schema(addr)
    SDK->>KV: get(unified_key)
    KV-->>SDK: miss (first reindex)
    SDK->>TS: buildCachedToolTypeScriptPreview(...)
    TS-->>SDK: preview
    SDK->>KV: set(unified_key, view)
    SDK-->>RI: view (TypeScript included, cache warmed)
    UI->>SDK: schema(addr)
    SDK->>KV: get(unified_key)
    KV-->>SDK: hit
    SDK-->>UI: view with TypeScript (cache hit)
Loading

Comments Outside Diff (1)

  1. packages/plugins/semantic-search/src/sdk/tool-search-index.test.ts, line 54-57 (link)

    P2 The raw counter field is now vestigial — it is initialized to 0, asserted to stay at 0, but can never be incremented because the mock's schema function was simplified to only track codegen. The dead field adds noise without providing any safety signal; removing it makes the intent clearer.

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

    Fix in Codex Fix in Cursor Cloud Agents Fix in Claude Code Fix in Cursor

Fix All in Codex Fix All in Cursor Cloud Agents Fix All in Claude Code Fix All in Cursor

Reviews (1): Last reviewed commit: "perf(sdk): precompile tool-schema TypeSc..." | Re-trigger Greptile

@aryasaatvik
aryasaatvik merged commit 52d3876 into dev Jun 21, 2026
8 checks passed
@aryasaatvik
aryasaatvik deleted the perf/precompile-tool-schema-typescript branch June 21, 2026 21:52
aryasaatvik added a commit that referenced this pull request Jun 21, 2026
**Phase 1** of Tier-2 tool-catalog caching — stacked on #49.

Caches `executor.tools.list` (today a full `tool` scan + `tool_policy`
read on every call) behind a **content-derived revision key**, so the
list stays warm and only re-reads D1 when the catalog or policies
actually change. No TTL.

## Invalidation
- `produceConnectionTools` writes a per-connection
**`toolset_revision`** = `sha256` of the connection's manifest rows'
`(path, index_fingerprint)`, in **`plugin_storage`** (owner-scoped, **no
schema migration**), at all 5 catalog-mutation sites (produce, both
clear branches, connection-remove, integration-remove).
- `catalogGen` combines the in-scope connections' revisions; `policyGen`
combines the `tool_policy` rows. Key = `sha256(owner, integration,
connection, includeBlocked, catalogGen, policyGen)`.
- **Token rotation does not bust** (never calls
`produceConnectionTools`); add/remove/change a tool, or any policy edit,
does.

## Scope
- Cached value = the DB-derived, policy-filtered list. **Static tools
unioned live** (never cached); **`query` applied post-cache** so
distinct queries share one cached set.
- Hand-authored `ToolListItem` Schema (list projection only — no
input/output schema); branded ids stored as strings, rebuilt on read.

## Notes (intentional, single-tenant self-host)
- `catalogGen`/`policyGen` reads are tenant-wide → per-tenant-coarse
invalidation (a catalog change re-warms all owners' list caches).
Correct, just coarse.

## Gate
tsgo (sdk) · `oxlint --deny-warnings` · oxfmt · vitest **sdk 350/350**
(new cache suite: add/remove/content invalidation, policy bust, query
post-cache, static-union-on-hit).

Phase 2 (stacked) moves the UI list onto the compact manifest tier.

<!-- stack:links:start -->
### [Stack](https://github.com/kitlangton/stack)

1. #49
2. **#50** 👈 current
<!-- stack:links:end -->
aryasaatvik added a commit that referenced this pull request Jun 23, 2026
## Why

Production traces showed the web UI's tool-detail path (`GET
/api/tools/schema`, `includeTypeScript: true`) paying the json-schema →
TypeScript compile on the first view of every tool. The reindex pipeline
already visits every tool's schema, but it called `tools.schema({
includeTypeScript: false })` specifically to *skip* that compile — so it
warmed the wrong cache variant and the UI's variant stayed cold until
first manual view.

## What

- `executor.tools.schema` always renders the TypeScript preview; the
`includeTypeScript` option and the now-empty `ToolSchemaOptions` type
are removed.
- The reindex pipeline (`collectDocForTool`) calls `tools.schema`
plainly, so every reindex pre-warms exactly the KV schema-view the UI
reads → first tool-detail views become cache hits instead of a cold
compile.
- `includeTypeScript` is dropped from the schema-view cache key (content
+ manifest), collapsing the two variants into one.

## Safety

- Embedding/lexical text is unchanged: `collectDocForTool` only reads
the raw schema facets (`inputSchema` / `outputSchema` /
`schemaDefinitions`), never the rendered TypeScript — so no re-embedding
/ index churn.
- The cache-key change supersedes existing entries; they re-warm on the
next reindex or view (regenerable derived data).

## Gate

tsgo (43/43), `oxlint --deny-warnings`, oxfmt, and vitest for
`@executor-js/sdk` + `@executor-js/plugin-semantic-search` all green.

Follow-up: a reindex on the deployed instance warms the new unified
variant.

<!-- stack:links:start -->
### [Stack](https://github.com/kitlangton/stack)

1. **#49** 👈 current
2. #50
<!-- stack:links:end -->
aryasaatvik added a commit that referenced this pull request Jun 23, 2026
**Phase 1** of Tier-2 tool-catalog caching — stacked on #49.

Caches `executor.tools.list` (today a full `tool` scan + `tool_policy`
read on every call) behind a **content-derived revision key**, so the
list stays warm and only re-reads D1 when the catalog or policies
actually change. No TTL.

- `produceConnectionTools` writes a per-connection
**`toolset_revision`** = `sha256` of the connection's manifest rows'
`(path, index_fingerprint)`, in **`plugin_storage`** (owner-scoped, **no
schema migration**), at all 5 catalog-mutation sites (produce, both
clear branches, connection-remove, integration-remove).
- `catalogGen` combines the in-scope connections' revisions; `policyGen`
combines the `tool_policy` rows. Key = `sha256(owner, integration,
connection, includeBlocked, catalogGen, policyGen)`.
- **Token rotation does not bust** (never calls
`produceConnectionTools`); add/remove/change a tool, or any policy edit,
does.

- Cached value = the DB-derived, policy-filtered list. **Static tools
unioned live** (never cached); **`query` applied post-cache** so
distinct queries share one cached set.
- Hand-authored `ToolListItem` Schema (list projection only — no
input/output schema); branded ids stored as strings, rebuilt on read.

- `catalogGen`/`policyGen` reads are tenant-wide → per-tenant-coarse
invalidation (a catalog change re-warms all owners' list caches).
Correct, just coarse.

tsgo (sdk) · `oxlint --deny-warnings` · oxfmt · vitest **sdk 350/350**
(new cache suite: add/remove/content invalidation, policy bust, query
post-cache, static-union-on-hit).

Phase 2 (stacked) moves the UI list onto the compact manifest tier.

<!-- stack:links:start -->

1. #49
2. **#50** 👈 current
<!-- stack:links:end -->
aryasaatvik added a commit that referenced this pull request Jun 23, 2026
## Why

Production traces showed the web UI's tool-detail path (`GET
/api/tools/schema`, `includeTypeScript: true`) paying the json-schema →
TypeScript compile on the first view of every tool. The reindex pipeline
already visits every tool's schema, but it called `tools.schema({
includeTypeScript: false })` specifically to *skip* that compile — so it
warmed the wrong cache variant and the UI's variant stayed cold until
first manual view.

## What

- `executor.tools.schema` always renders the TypeScript preview; the
`includeTypeScript` option and the now-empty `ToolSchemaOptions` type
are removed.
- The reindex pipeline (`collectDocForTool`) calls `tools.schema`
plainly, so every reindex pre-warms exactly the KV schema-view the UI
reads → first tool-detail views become cache hits instead of a cold
compile.
- `includeTypeScript` is dropped from the schema-view cache key (content
+ manifest), collapsing the two variants into one.

## Safety

- Embedding/lexical text is unchanged: `collectDocForTool` only reads
the raw schema facets (`inputSchema` / `outputSchema` /
`schemaDefinitions`), never the rendered TypeScript — so no re-embedding
/ index churn.
- The cache-key change supersedes existing entries; they re-warm on the
next reindex or view (regenerable derived data).

## Gate

tsgo (43/43), `oxlint --deny-warnings`, oxfmt, and vitest for
`@executor-js/sdk` + `@executor-js/plugin-semantic-search` all green.

Follow-up: a reindex on the deployed instance warms the new unified
variant.

<!-- stack:links:start -->
### [Stack](https://github.com/kitlangton/stack)

1. **#49** 👈 current
2. #50
<!-- stack:links:end -->
aryasaatvik added a commit that referenced this pull request Jun 23, 2026
**Phase 1** of Tier-2 tool-catalog caching — stacked on #49.

Caches `executor.tools.list` (today a full `tool` scan + `tool_policy`
read on every call) behind a **content-derived revision key**, so the
list stays warm and only re-reads D1 when the catalog or policies
actually change. No TTL.

- `produceConnectionTools` writes a per-connection
**`toolset_revision`** = `sha256` of the connection's manifest rows'
`(path, index_fingerprint)`, in **`plugin_storage`** (owner-scoped, **no
schema migration**), at all 5 catalog-mutation sites (produce, both
clear branches, connection-remove, integration-remove).
- `catalogGen` combines the in-scope connections' revisions; `policyGen`
combines the `tool_policy` rows. Key = `sha256(owner, integration,
connection, includeBlocked, catalogGen, policyGen)`.
- **Token rotation does not bust** (never calls
`produceConnectionTools`); add/remove/change a tool, or any policy edit,
does.

- Cached value = the DB-derived, policy-filtered list. **Static tools
unioned live** (never cached); **`query` applied post-cache** so
distinct queries share one cached set.
- Hand-authored `ToolListItem` Schema (list projection only — no
input/output schema); branded ids stored as strings, rebuilt on read.

- `catalogGen`/`policyGen` reads are tenant-wide → per-tenant-coarse
invalidation (a catalog change re-warms all owners' list caches).
Correct, just coarse.

tsgo (sdk) · `oxlint --deny-warnings` · oxfmt · vitest **sdk 350/350**
(new cache suite: add/remove/content invalidation, policy bust, query
post-cache, static-union-on-hit).

Phase 2 (stacked) moves the UI list onto the compact manifest tier.

<!-- stack:links:start -->

1. #49
2. **#50** 👈 current
<!-- stack:links:end -->
aryasaatvik added a commit that referenced this pull request Aug 18, 2026
Production traces showed the web UI's tool-detail path (`GET
/api/tools/schema`, `includeTypeScript: true`) paying the json-schema →
TypeScript compile on the first view of every tool. The reindex pipeline
already visits every tool's schema, but it called `tools.schema({
includeTypeScript: false })` specifically to *skip* that compile — so it
warmed the wrong cache variant and the UI's variant stayed cold until
first manual view.

- `executor.tools.schema` always renders the TypeScript preview; the
`includeTypeScript` option and the now-empty `ToolSchemaOptions` type
are removed.
- The reindex pipeline (`collectDocForTool`) calls `tools.schema`
plainly, so every reindex pre-warms exactly the KV schema-view the UI
reads → first tool-detail views become cache hits instead of a cold
compile.
- `includeTypeScript` is dropped from the schema-view cache key (content
+ manifest), collapsing the two variants into one.

- Embedding/lexical text is unchanged: `collectDocForTool` only reads
the raw schema facets (`inputSchema` / `outputSchema` /
`schemaDefinitions`), never the rendered TypeScript — so no re-embedding
/ index churn.
- The cache-key change supersedes existing entries; they re-warm on the
next reindex or view (regenerable derived data).

tsgo (43/43), `oxlint --deny-warnings`, oxfmt, and vitest for
`@executor-js/sdk` + `@executor-js/plugin-semantic-search` all green.

Follow-up: a reindex on the deployed instance warms the new unified
variant.

<!-- stack:links:start -->

1. **#49** 👈 current
2. #50
<!-- stack:links:end -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant