Skip to content

fix: send edgeFunctionCode per the v3 custom-tools contract (422 on every run)#17

Merged
steveworley merged 1 commit into
mainfrom
fix/v3-contract
Jul 4, 2026
Merged

fix: send edgeFunctionCode per the v3 custom-tools contract (422 on every run)#17
steveworley merged 1 commit into
mainfrom
fix/v3-contract

Conversation

@steveworley

Copy link
Copy Markdown
Contributor

Summary

Every invocation of this action against the v3 API fails with a 422 Unprocessable Entity — for all consumers, including first use. This PR fixes the request contract plus two related bugs, and improves error output so future API failures are diagnosable from the workflow log.

Bug 1 — missing edgeFunctionCode (the 422 root cause)

The v3 endpoint POST /api/v3/organizations/{org}/ai/custom-tools requires edgeFunctionCode (the edge function source): the platform deploys the function itself, computes its URL, and registers the tool — upserting by name and reusing the existing tool's uuid on update. The action instead sent an edgeFunctionUrl built from tool.json's uuid + the preview_domain input, and never sent edgeFunctionCode, so server-side validation rejected every request.

The action now reads fn.js from each tool directory (sibling of tool.json — the existing convention in consuming repos) and sends it as edgeFunctionCode, along with toolName, description, inputSchema, and the optional accepted fields: category, responseMode, outputSchema, outputSchemaDescription, isAsync, timeoutSeconds. A missing fn.js fails with a clear error. timeout in tool.json is documented as seconds (API range 5–300); values above 300 are assumed to be milliseconds from older configs and converted with a warning.

Bug 2 — existing-tool detection always found 0 tools

The pre-flight list check read tool.name, but the list API returns toolName. Every tool was therefore reported as "Creating"/"Created" even when it already existed. The action now reads toolName ?? name and reports created vs updated accurately. (Correctness was never at risk — the endpoint upserts by name — but the reporting was wrong.)

Bug 3 — errors were swallowed

Failures read err.response.data.error, which Laravel validation responses don't populate (they return { errors: {...} }). The 422s surfaced as an unhelpful generic message. Failures now include the HTTP status and the full JSON response body.

Migration notes for existing workflows

  • Add fn.js to each tool directory — the edge function source the platform should deploy.
  • preview_domain and quant_project inputs are deprecated — still accepted (no workflow breakage) but ignored with a warning. You can remove them.
  • Separate edge-function deploy steps for these tools are now redundant — the registration call deploys the code itself.
  • uuid in tool.json is informational only and triggers a warning. The platform manages uuids, reusing the existing tool's uuid on update — so a uuid aligned with your functions.json stays stable only if the tool is already registered with that uuid in its edge function URL. Tools registered for the first time get a platform-assigned uuid.

Verification

  • npx tsc --noEmit clean
  • npm run build (ncc) — dist/ rebuilt and committed (actions ship dist), verified the bundle sends edgeFunctionCode
  • No test runner is configured in this repo (the test script references jest, but it isn't installed), so no unit tests were added

Related

The generated types in @quantcdn/quant-client have drifted from the API: ListCustomTools200ResponseToolsInner keys the tool name as name while the API returns toolName, and CreateCustomToolRequest still requires edgeFunctionUrl with no edgeFunctionCode field. Worth fixing in the OpenAPI spec at the source — this action works around both with a documented cast.

🤖 Generated with Claude Code

The v3 create-custom-tool endpoint requires edgeFunctionCode (it deploys
the edge function itself, computes the URL, and upserts the tool by
name). The action was instead sending an edgeFunctionUrl built from
tool.json uuid + preview_domain and never sending edgeFunctionCode, so
every invocation failed Laravel validation with a 422.

- Read fn.js from each tool directory (sibling of tool.json) and send
  it as edgeFunctionCode with toolName, description, inputSchema, and
  the optional accepted fields (category, responseMode, outputSchema,
  outputSchemaDescription, isAsync, timeoutSeconds)
- tool.json timeout is documented as seconds (5-300); values > 300 are
  assumed to be milliseconds and converted with a warning
- Fix existing-tool detection: the list API returns toolName, not name,
  so the pre-flight check always reported "Found 0 existing tool(s)"
- Surface HTTP status and the full JSON error body on failure instead
  of the (usually absent) .error field
- Deprecate preview_domain and quant_project inputs (accepted, ignored)
  and warn when tool.json carries a uuid (platform-managed now)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@steveworley steveworley merged commit 22d9fc9 into main Jul 4, 2026
4 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