Tool argument descriptions lost in schema conversion #2882

Open
opened 2026-02-16 17:37:40 -05:00 by yindo · 8 comments
Owner

Originally created by @arsham on GitHub (Nov 15, 2025).

Originally assigned to: @rekram1-node on GitHub.

Description

I noticed that when I create custom tools in the ./tool directory and use .describe() on the arguments, those descriptions aren't showing up in the prompts sent to the LLM. The agent doesn't seem to know what the parameters are for, which leads to incorrect usage. At the moment I am adding them to the description of the tool itself, which is not ideal.

After digging through the source code, I found the issue. There are two different methods being used to convert Zod schemas to JSON Schema:

  1. In server.ts (line 307) - uses zodToJsonSchema() from the zod-to-json-schema package
  2. In prompt.ts (line 581) - uses z.toJSONSchema() from Zod's built-in method

The problem is that z.toJSONSchema() doesn't properly extract the .describe() metadata from schema properties, so all those descriptions get dropped before reaching the LLM.

(Possible) Fix:
Change line 581 in session/prompt.ts from:

const schema = ProviderTransform.schema(input.providerID, input.modelID, z.toJSONSchema(item.parameters))

To:

const schema = ProviderTransform.schema(input.providerID, input.modelID, zodToJsonSchema(item.parameters))

And add the import at the top:

import { zodToJsonSchema } from "zod-to-json-schema"

This makes the schema conversion consistent with what's already being done in server.ts and ensures all tool descriptions are properly passed to the LLM.

OpenCode version

v1.0.65

Steps to reproduce

For example, I have a tool like this:

export default tool({
    description: "Return the length of a string",
    args: {
        text: z.string().describe("The text string to measure"),
    },
    // ...
});

I expected the LLM to see the description "The text string to measure" for the text parameter, but it's not getting that information.

Screenshot and/or share link

No response

Operating System

Arch Linux

Terminal

foot

Originally created by @arsham on GitHub (Nov 15, 2025). Originally assigned to: @rekram1-node on GitHub. ### Description I noticed that when I create custom tools in the `./tool` directory and use `.describe()` on the arguments, those descriptions aren't showing up in the prompts sent to the LLM. The agent doesn't seem to know what the parameters are for, which leads to incorrect usage. At the moment I am adding them to the description of the tool itself, which is not ideal. After digging through the source code, I found the issue. There are two different methods being used to convert Zod schemas to JSON Schema: 1. In `server.ts` (line 307) - uses `zodToJsonSchema()` from the `zod-to-json-schema` package ✅ 2. In `prompt.ts` (line 581) - uses `z.toJSONSchema()` from Zod's built-in method ❌ The problem is that `z.toJSONSchema()` doesn't properly extract the `.describe()` metadata from schema properties, so all those descriptions get dropped before reaching the LLM. **(Possible) Fix:** Change line 581 in `session/prompt.ts` from: ```typescript const schema = ProviderTransform.schema(input.providerID, input.modelID, z.toJSONSchema(item.parameters)) ``` To: ```typescript const schema = ProviderTransform.schema(input.providerID, input.modelID, zodToJsonSchema(item.parameters)) ``` And add the import at the top: ```typescript import { zodToJsonSchema } from "zod-to-json-schema" ``` This makes the schema conversion consistent with what's already being done in `server.ts` and ensures all tool descriptions are properly passed to the LLM. ### OpenCode version v1.0.65 ### Steps to reproduce For example, I have a tool like this: ```typescript export default tool({ description: "Return the length of a string", args: { text: z.string().describe("The text string to measure"), }, // ... }); ``` I expected the LLM to see the description "The text string to measure" for the `text` parameter, but it's not getting that information. ### Screenshot and/or share link _No response_ ### Operating System Arch Linux ### Terminal foot
yindo added the help-wantedbug labels 2026-02-16 17:37:40 -05:00
Author
Owner

@rekram1-node commented on GitHub (Nov 17, 2025):

@arsham I can't replicate this with any tool.

Are you sure it is being sent without a description?

This is how I tested:

{
  "$schema": "https://opencode.ai/config.json",
  "plugin": ["opencode-openai-codex-auth"],
  "provider": {
    "opencode": {
      "api": "https://webhook.site/3bd417e1-b682-44c5-b743-2e4be1508605"
    }
  }
}

U can do this with any provider ofc but it just gives the exact request sent to the provider

@rekram1-node commented on GitHub (Nov 17, 2025): @arsham I can't replicate this with any tool. Are you sure it is being sent without a description? This is how I tested: ``` { "$schema": "https://opencode.ai/config.json", "plugin": ["opencode-openai-codex-auth"], "provider": { "opencode": { "api": "https://webhook.site/3bd417e1-b682-44c5-b743-2e4be1508605" } } } ``` U can do this with any provider ofc but it just gives the exact request sent to the provider
Author
Owner

@arsham commented on GitHub (Nov 17, 2025):

@rekram1-node thank you for getting back to me. Here is an example tool you could try:

import { tool } from "@opencode-ai/plugin";
import { z } from "zod";

export default tool({
    description: "Return what Arsham wants",
    args: {
        text: z.string().describe("You can heabang to this Death Metal tune!"),
    },
    async execute(args: { text: string }): Promise<string> {
        return "Done!";
    },
});

If you ask the agent to give you how the tool is represented verbatim in its tool set, you will get something like this:

Here is the verbatim representation of the arsham tool as it appears in my toolbox:

```json
{
  "description": "Return what Arsham wants",
  "name": "arsham",
  "parameters": {
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "additionalProperties": false,
    "properties": {
      "text": {
        "type": "string"
      }
    },
    "required": ["text"],
    "type": "object"
  }
}
```


**Key elements**:
- **name**: `arsham`
- **description**: "Return what Arsham wants"
- **parameters**: JSON Schema object with:
  - `text` (type: string, required)
  - No additional properties allowed
  - Uses JSON Schema draft 2020-12

That's the complete, verbatim definition provided to me in the system prompt.

I might be doing something stupid here though.

@arsham commented on GitHub (Nov 17, 2025): @rekram1-node thank you for getting back to me. Here is an example tool you could try: ```ts import { tool } from "@opencode-ai/plugin"; import { z } from "zod"; export default tool({ description: "Return what Arsham wants", args: { text: z.string().describe("You can heabang to this Death Metal tune!"), }, async execute(args: { text: string }): Promise<string> { return "Done!"; }, }); ``` If you ask the agent to give you how the tool is represented verbatim in its tool set, you will get something like this: ````markdown Here is the verbatim representation of the arsham tool as it appears in my toolbox: ```json { "description": "Return what Arsham wants", "name": "arsham", "parameters": { "$schema": "https://json-schema.org/draft/2020-12/schema", "additionalProperties": false, "properties": { "text": { "type": "string" } }, "required": ["text"], "type": "object" } } ``` **Key elements**: - **name**: `arsham` - **description**: "Return what Arsham wants" - **parameters**: JSON Schema object with: - `text` (type: string, required) - No additional properties allowed - Uses JSON Schema draft 2020-12 That's the complete, verbatim definition provided to me in the system prompt. ```` I might be doing something stupid here though.
Author
Owner

@rekram1-node commented on GitHub (Nov 18, 2025):

I can replicate now, fixing shortly

@rekram1-node commented on GitHub (Nov 18, 2025): I can replicate now, fixing shortly
Author
Owner

@rekram1-node commented on GitHub (Nov 18, 2025):

some weird zod bug going on...

@rekram1-node commented on GitHub (Nov 18, 2025): some weird zod bug going on...
Author
Owner

@rekram1-node commented on GitHub (Nov 18, 2025):

If you import a file dynamically, vs define the tool as we do, the zod parameters change wtf

@rekram1-node commented on GitHub (Nov 18, 2025): If you import a file dynamically, vs define the tool as we do, the zod parameters change wtf
Author
Owner

@arsham commented on GitHub (Nov 18, 2025):

wtf indeed!

@arsham commented on GitHub (Nov 18, 2025): wtf indeed!
Author
Owner

@rekram1-node commented on GitHub (Nov 19, 2025):

This one is pretty obscure but there is something weird going on here:
https://github.com/sst/opencode/blob/48e4f2f45d54364a8123d603dbbca7bfe8fcc763/packages/opencode/src/tool/registry.ts#L59

I will try to attack this when I get a chance to, have a few higher priority ones but this is ofc a big issue for custom tools since descriptions are prompts.

I guess work around for now:

Overload the tool top level description to describe the tool parameters too

@rekram1-node commented on GitHub (Nov 19, 2025): This one is pretty obscure but there is something weird going on here: https://github.com/sst/opencode/blob/48e4f2f45d54364a8123d603dbbca7bfe8fcc763/packages/opencode/src/tool/registry.ts#L59 I will try to attack this when I get a chance to, have a few higher priority ones but this is ofc a big issue for custom tools since descriptions are prompts. I guess work around for now: Overload the tool top level description to describe the tool parameters too
Author
Owner

@arsham commented on GitHub (Nov 19, 2025):

Thank you. I would this this issue is super low priority.

@arsham commented on GitHub (Nov 19, 2025): Thank you. I would this this issue is super low priority.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: anomalyco/opencode#2882