Skip to content

fix(plugin): make tool values structural#37202

Merged
kitlangton merged 3 commits into
v2from
structural-plugin-tools
Jul 16, 2026
Merged

fix(plugin): make tool values structural#37202
kitlangton merged 3 commits into
v2from
structural-plugin-tools

Conversation

@kitlangton

Copy link
Copy Markdown
Contributor

What

Make V2 Effect tool values transparent structural declarations so tools created by an external plugin remain valid when the plugin and OpenCode resolve separate physical copies of @opencode-ai/plugin.

Typed tools now accept either Effect codecs or schemas implementing both Standard Schema V1 and Standard JSON Schema V1. The existing raw jsonSchema overload remains available for MCP, manifests, and process bridges.

Before / After

Before: Tool.make returned a frozen empty object and stored its schemas and executor in a module-local WeakMap. When an external Effect plugin registered a tool from its package copy, OpenCode looked up that object in the host package copy's different WeakMap and threw Invalid Tool value.

After: the tool value directly carries its declaration. The host derives definitions and settles calls from those fields, so package-copy identity is irrelevant.

const tool = Tool.make({
  description: "Lookup",
  input: Schema.Struct({ query: Schema.String }),
  output: Schema.String,
  execute: ({ query }) => Effect.succeed(query),
})

How

  • Replaces the opaque WeakMap carrier with structural typed and raw-JSON declaration variants.
  • Keeps Effect codecs on their native decode-input and encode-output paths.
  • Supports portable schemas through Standard Schema validation and Standard JSON Schema definition generation.
  • Derives Promise tool declarations and events from the Effect declaration model; Promise execution is still lifted at the host boundary.
  • Removes the unconsumed model-definition referential-identity test and does not add a replacement cache.
  • Updates the V2 tool architecture documentation to describe the structural contract.
flowchart LR
  P[External plugin package] -->|structural Tool value| H[OpenCode plugin host]
  H --> R[Tool registry]
  R --> D[Model definition]
  R --> E[Effect executor]
Loading

Scope

  • Keeps the existing Tool.make overload for raw JSON Schema tools; no separate Tool.dynamic API is added.
  • Does not cache generated model definitions. Referential identity has no consumer or external semantics; caching can be added at a measured materialization boundary if profiling justifies it.
  • Replaces feat(plugin): support dynamic Effect tools #37192, whose addDynamic method worked around the package-identity issue without fixing the tool representation.

Testing

  • bun run test in packages/core: 1,301 passed, 6 skipped
  • bun run test in packages/plugin: 6 passed
  • bun run build in packages/plugin
  • Changed-file lint: 0 warnings, 0 errors
  • Full monorepo bun run typecheck: 32 tasks passed
  • Regression test loads a second module instance, creates a typed tool there, and defines and settles it through the host module instance
  • Portable-schema tests cover input transformation, output transformation, JSON Schema direction, and failure mapping

@kitlangton
kitlangton merged commit 0a5530d into v2 Jul 16, 2026
7 checks passed
@kitlangton
kitlangton deleted the structural-plugin-tools branch July 16, 2026 02:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant