Skip to content

fix(openapi): put the file-emit contract in file-returning tools' descriptions#1081

Merged
RhysSullivan merged 1 commit into
mainfrom
claude/bold-proskuriakova-6d5481
Jun 22, 2026
Merged

fix(openapi): put the file-emit contract in file-returning tools' descriptions#1081
RhysSullivan merged 1 commit into
mainfrom
claude/bold-proskuriakova-6d5481

Conversation

@RhysSullivan

Copy link
Copy Markdown
Collaborator

Problem

When an agent loads a file through a file-returning tool (e.g. a Gmail attachment, a PDF invoice), the correct move is to pass the result's data straight to emit(result.data). The tool's output schema already declares the ToolFile envelope, so the contract was technically discoverable.

But the output schema is dropped from the hot tool-list projection for performance, so it never reaches the sandbox search step. search is the step a model always walks before its first call. So the model couldn't see the contract until it had already guessed wrong: a typical first attempt hand-rebuilds the envelope (reading a non-existent size field, double-converting base64), which silently renders nothing even though the emit "succeeded". The contract only showed up in describe.tool, which an agent often skips.

Fix

Append a short, copyable emit instruction to the stored description of any OpenAPI operation whose response is a file. Stored descriptions ride both search (always walked) and describe.tool, so the contract is in front of the agent before the first call, with no new columns and no list-path cost. This is prevention (right on the first attempt), not recovery.

openApiToolDefsFromCompiled is the single producer of a file-returning tool's description, so the hint is computed once from the same responseBody.fileHint that already selects the ToolFile output schema. Non-file tools are untouched.

The appended text:

Returns a ToolFile: the file bytes already decoded into { _tag: "ToolFile", mimeType, encoding, data, byteLength }. To display or forward it, pass the result's data straight to emit(result.data). Do not rebuild the envelope or read upstream fields like size.

Coverage

Extends the tool-descriptions e2e scenario with a PDF-returning operation and asserts the hint appears in both the tools.list catalog entry and the tools.schema view, while a non-file tool's description stays clean. The scenario's descriptions.md artifact shows the hint riding the catalog description.

Verified on the selfhost target; openapi unit-level file-hint tests still green; format/lint/typecheck clean.

…criptions

A tool whose output is a ToolFile declared the envelope in its output
schema, but the output schema is dropped from the hot tool-list
projection, so it never reaches the sandbox search step that an agent
always walks before its first call. Agents only discovered
emit(result.data) after a failed first attempt, or by reading
describe.tool.

Now any OpenAPI operation whose response is a file gets a short,
copyable emit instruction appended to its stored description. Stored
descriptions ride both search and describe.tool, so the contract is in
front of the agent before it calls the tool, with no new columns and no
list-path cost. Non-file tools are untouched.

Covered by the tool-descriptions e2e scenario: a PDF-returning operation
asserts the hint appears in both the tools.list catalog entry and the
tools.schema view.
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
executor-cloud 6311c7e Jun 22 2026, 11:33 PM

@greptile-apps

greptile-apps Bot commented Jun 22, 2026

Copy link
Copy Markdown

Greptile Summary

This PR appends a short emit-contract hint to the stored description of any OpenAPI tool whose response is a ToolFile, ensuring the emit(result.data) pattern is visible to a model during the search step before its first call. It also refactors openApiToolDefsFromCompiled to compute the returnsFile flag once and use it for both the description and outputSchema fields, replacing a duplicated nested Option.match.

  • backing.ts: introduces FILE_OUTPUT_HINT and withFileEmitHint; the openApiToolDefsFromCompiled refactor is logically equivalent to the original Option.match pattern.
  • tool-descriptions.test.ts: adds a PDF-returning fixture operation and asserts the hint appears in both catalog (listDescription) and schema (schemaDescription) views while non-file tools stay clean.

Confidence Score: 4/5

Safe to merge. The core logic change is a straightforward refactor of an existing boolean check, and the new hint is a static string appended only to file-returning tools' descriptions.

The description hint in FILE_OUTPUT_HINT omits the optional name field that is present in the actual ToolFile schema, leaving models without a documented way to surface an attachment filename. This is the only finding and it affects the accuracy of the agent-visible hint rather than runtime behaviour.

packages/plugins/openapi/src/sdk/backing.ts — the FILE_OUTPUT_HINT constant omits the name? field from the ToolFile shape description.

Important Files Changed

Filename Overview
packages/plugins/openapi/src/sdk/backing.ts Adds FILE_OUTPUT_HINT constant and withFileEmitHint helper; refactors openApiToolDefsFromCompiled to compute returnsFile once and use it for both description and outputSchema. Logic is equivalent to the original; the ToolFile envelope description in the hint omits the optional name field.
e2e/scenarios/tool-descriptions.test.ts Adds a PDF-returning operation to the fixture spec, bumps toolCount to 3, and asserts the emit hint appears in both listDescription and schemaDescription for the new file tool while staying absent from non-file tools.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[openApiToolDefsFromCompiled] --> B{returnsFile?}
    B -- yes --> C[withFileEmitHint\nappends FILE_OUTPUT_HINT\nto description]
    B -- no --> D[descriptionFor unchanged]
    B -- yes --> E[outputSchema = ToolFileJsonSchema]
    B -- no --> F[outputSchema = normalizeOpenApiRefs\noutputSchema]
    C --> G[ToolDef with hint\nin stored description]
    D --> H[ToolDef with plain\ndescription]
    G --> I[tools.search / tools.list\nalways walks stored description]
    G --> J[describe.tool\nalso carries hint]
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"}}}%%
flowchart TD
    A[openApiToolDefsFromCompiled] --> B{returnsFile?}
    B -- yes --> C[withFileEmitHint\nappends FILE_OUTPUT_HINT\nto description]
    B -- no --> D[descriptionFor unchanged]
    B -- yes --> E[outputSchema = ToolFileJsonSchema]
    B -- no --> F[outputSchema = normalizeOpenApiRefs\noutputSchema]
    C --> G[ToolDef with hint\nin stored description]
    D --> H[ToolDef with plain\ndescription]
    G --> I[tools.search / tools.list\nalways walks stored description]
    G --> J[describe.tool\nalso carries hint]
Loading

Reviews (1): Last reviewed commit: "fix(openapi): put the file-emit contract..." | Re-trigger Greptile

Comment on lines +193 to +196
const FILE_OUTPUT_HINT =
'Returns a ToolFile: the file bytes already decoded into { _tag: "ToolFile", mimeType, encoding, data, byteLength }. ' +
"To display or forward it, pass the result's data straight to emit(result.data). " +
"Do not rebuild the envelope or read upstream fields like size.";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 The ToolFile schema (defined in tool-result.ts) includes an optional name field for the original filename — name: Schema.optional(Schema.String) — but FILE_OUTPUT_HINT lists only { _tag, mimeType, encoding, data, byteLength }. A model that wants to display or forward the filename alongside the bytes would not know this field exists, and might try to read a non-existent filename field instead. Adding name? to the inline shape description keeps the hint self-consistent with the actual schema.

Suggested change
const FILE_OUTPUT_HINT =
'Returns a ToolFile: the file bytes already decoded into { _tag: "ToolFile", mimeType, encoding, data, byteLength }. ' +
"To display or forward it, pass the result's data straight to emit(result.data). " +
"Do not rebuild the envelope or read upstream fields like size.";
const FILE_OUTPUT_HINT =
'Returns a ToolFile: the file bytes already decoded into { _tag: "ToolFile", name?, mimeType, encoding, data, byteLength }. ' +
"To display or forward it, pass the result's data straight to emit(result.data). " +
"Do not rebuild the envelope or read upstream fields like size.";

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!

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
executor-marketing 6311c7e Commit Preview URL

Branch Preview URL
Jun 22 2026, 11:33 PM

@github-actions

github-actions Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Cloudflare preview

Torn down — the PR is closed.

@pkg-pr-new

pkg-pr-new Bot commented Jun 22, 2026

Copy link
Copy Markdown

Open in StackBlitz

@executor-js/cli

npm i https://pkg.pr.new/@executor-js/cli@1081

@executor-js/config

npm i https://pkg.pr.new/@executor-js/config@1081

@executor-js/execution

npm i https://pkg.pr.new/@executor-js/execution@1081

@executor-js/sdk

npm i https://pkg.pr.new/@executor-js/sdk@1081

@executor-js/codemode-core

npm i https://pkg.pr.new/@executor-js/codemode-core@1081

@executor-js/runtime-quickjs

npm i https://pkg.pr.new/@executor-js/runtime-quickjs@1081

@executor-js/plugin-file-secrets

npm i https://pkg.pr.new/@executor-js/plugin-file-secrets@1081

@executor-js/plugin-graphql

npm i https://pkg.pr.new/@executor-js/plugin-graphql@1081

@executor-js/plugin-keychain

npm i https://pkg.pr.new/@executor-js/plugin-keychain@1081

@executor-js/plugin-mcp

npm i https://pkg.pr.new/@executor-js/plugin-mcp@1081

@executor-js/plugin-onepassword

npm i https://pkg.pr.new/@executor-js/plugin-onepassword@1081

@executor-js/plugin-openapi

npm i https://pkg.pr.new/@executor-js/plugin-openapi@1081

executor

npm i https://pkg.pr.new/executor@1081

commit: 6311c7e

@RhysSullivan RhysSullivan merged commit b74c365 into main Jun 22, 2026
15 checks passed
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