Skip to content

feat(tools): fleet, dispatch and payment toolkits (#72) - #58

Open
yakimoto wants to merge 1 commit into
mainfrom
feat/fleet-tools-72
Open

feat(tools): fleet, dispatch and payment toolkits (#72)#58
yakimoto wants to merge 1 commit into
mainfrom
feat/fleet-tools-72

Conversation

@yakimoto

@yakimoto yakimoto commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Part of wave-av/wave-context#48. Delivers the adk half of wave-av/wave-context#72 (the mcp-server half is wave-av/mcp-server#63).

The ADK shipped 10 tools covering streams and live production — and nothing for the fleet products that are actually in production. This adds three toolkits, taking the registry to 17 tools.

Toolkit Tools Calls
FleetToolkit wave_speak, wave_transcribe, wave_caption POST /v1/{voice,transcribe,captions} via the gateway
DispatchToolkit wave_route, wave_list_routing_profiles Dispatch POST /, GET /profilesits own host
PaymentsToolkit wave_find_paid_services, wave_payment_schemes GET /v1/mpp/services, GET /v1/{x402,mpp}/facilitator/supportedpublic

Every path is grounded in the spoke's own router, not the API spec

wave-av/api-spec declares /voice/generate, /voice/voices, /voice/clone, /transcribe/{id}, /captions/{jobId}/download, /phone/*, /podcast/*none of which exist. Each spoke owns its whole /v1 namespace with an exact-match router and 404s anything else; the gateway forwards /v1/<product> verbatim with no rewriting. Building from the spec would have shipped tools that 404. Filed as wave-av/api-spec#33; the paths here come from git show origin/main:src/api.ts in each spoke.

Worth stating plainly: phone and podcast are not buildable. wave-phone-edge and wave-podcast-edge have no api.ts at all — their src/ is favicon.ts, landing.ts, tokens.css.ts, worker.ts. They are landing shims, and neither prefix appears in the gateway routing table. No tool is claimed for either.

Three decisions worth your attention

1. wave_speak gives code the bytes and a serialiser a receipt. POST /v1/voice returns audio/mpeg. Returning the bytes is right for an SDK — discarding a response body is lossy and the caller can't recover it. But agent frameworks routinely JSON.stringify a tool result straight into a model's context, where a megabyte of serialised byte array is useless to something that cannot listen. So VoiceResult holds the real Uint8Array on .audio, and its toJSON() emits { contentType, byteLength, usage } instead. No caller has to choose.

This is the concrete answer to the open question I raised on mcp-server#63 — in MCP the tool result is text so a receipt is all that's possible, but in a TypeScript SDK we can have both.

2. PaymentsToolkit takes no API key — not an optional one, none at all. Those gateway routes are public by design, because an agent has to discover what it can buy before it holds a WAVE key. The surest way to never send a credential to an endpoint that doesn't authenticate it is to have nothing to send. Only the read half is exposed; the facilitator's verify and settle are the money-moving side and are deliberately not wrapped.

3. A separate base URL and a separate fetch path, twice. api.wave.online fronts the product spokes and the payment rails; dispatch.wave.online fronts Dispatch, which is not behind the gateway. Reusing AgentToolkit.call() would also have forced Content-Type: application/json onto content-type-sensitive spokes and — see below — swallowed every error.

Two things this fixes on the way past

AgentTool and the MCP-shape mapper move to src/tools/shared.ts so all four toolkits emit identical MCP definitions. AgentToolkit re-exports AgentTool from its original path, so import { type AgentTool } from '@wave-av/adk' resolves exactly as before — the public API is unchanged, and the smoke check asserts it.

The new toolkits throw a typed WaveToolError carrying the status and the response body verbatim. AgentToolkit.call() never checks response.ok, so it returns error bodies as though they were successful results — that's pre-existing and out of scope here, but it's why wave_find_paid_services surfacing a real 503 (below) is the intended behaviour rather than a swallowed empty list. WaveToolError is also the export the README has been advertising from @wave-av/adk/tools without it existing.

Verification

npm run type-check   # exit 0
npm run lint         # exit 0 (eslint --max-warnings 0)
npm run build        # ESM + CJS build success

Smoke check against the built output (dist/index.js, so it proves what a consumer gets) — 33/33:

=== registry ===
  ✓ AgentToolkit (pre-existing): 10 tools    ✓ FleetToolkit: 3 tools
  ✓ DispatchToolkit: 2 tools                 ✓ PaymentsToolkit: 2 tools
  total tools: 17
=== MCP shape is identical across all four toolkits ===
  ✓ AgentToolkit MCP defs unchanged in shape — wave_create_stream
=== no credential can reach the public payment rails ===
  ✓ PaymentsToolkit holds no apiKey — {"baseUrl":"https://api.wave.online"}
=== VoiceResult returns bytes to code, a receipt to a serialiser ===
  ✓ result.audio holds the real bytes        ✓ JSON.stringify omits the bytes
=== usage headers: absent must be null, never 0 ===
  ✓ usageMinutes null when absent (not 0)    ✓ a genuine 0 remaining survives as 0
=== zod validation actually fires ===
  ✓ wave_transcribe rejects a malformed url before any network call
  ✓ wave_payment_schemes rejects an out-of-enum rail (no path injection)
SMOKE PASS

Live proof — the two public tools need no key, so they ran against production:

=== wave_payment_schemes rail=x402 ===
{"supportedNetworks":[{"networkId":"eip155:8453","version":"x402@1","asset":"0x833589…2913"},
                      {"networkId":"eip155:84532","version":"x402@1","asset":"0x036cbd…cf7e"}],
 "schemes":["exact"],"facilitatorUrl":"https://gateway.wave.online/v1/x402/facilitator"}

=== wave_find_paid_services q='video transcription' ===
WaveToolError 503: {"error":"discovery_unavailable","reason":"vectorize_unbound"}

That 503 is a real production gap, not a defect in this PR — MPP service discovery is dark because its Vectorize index isn't bound (wave-av/wave-gateway#730). The toolkit raising it as a typed error rather than swallowing it is the point.

Caveats — read these

  • The five authenticated tools are not proven end-to-end. That needs a live WAVE_API_KEY, and every product call is metered, so proving them bills real money. It should be done deliberately against a test org, not incidentally in a PR.
  • This repo's tests do not run. src/__tests__/*.test.ts import vitest, but vitest is absent from package-lock.json, there is no test script, and tsconfig.json excludes the test directory. So the gate here is type-check + lint + build + the out-of-repo smoke run above. I'm filing that separately rather than smuggling a test framework into this PR — and it is why I removed the words "and are covered by tests" from the Status prose in .wave/repo.json, which was not true.
  • dist/ is tracked but intentionally not updated here. It is committed at release time (prepublishOnly rebuilds it), not per feature.
  • The framework adapters still wrap only AgentToolkit, so the new tools aren't reachable from Mastra/LangGraph/LiveKit yet. Filing that as a follow-up.
  • CI is hard-down org-wide on Actions billing (wave-av/wave-rig#174), so checks cannot run on this PR.

.wave/repo.json

Updated so the generated README covers the new surface: three new capabilities, six new resolver-verified claims, a corrected tool count (10 → 17), and a "Working with audio" section. The README prose changes ride in this PR deliberately — merging it is the review.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.


Note

Medium Risk
Adds live production API clients (voice, payments discovery, Dispatch routing) with new error and auth semantics; framework adapters still only wrap AgentToolkit, so new tools aren’t reachable via Mastra/LangGraph/LiveKit yet.

Overview
Expands the ADK from 10 stream/production tools to 17 by adding FleetToolkit, DispatchToolkit, and PaymentsToolkit, with shared plumbing in src/tools/shared.ts and root exports from src/index.ts.

FleetToolkit adds wave_speak, wave_transcribe, and wave_caption against gateway paths grounded in spoke routers (POST /v1/voice, /v1/transcribe, /v1/captions). VoiceResult keeps real audio on .audio while toJSON() emits a usage receipt so serializers don’t dump megabytes into model context. STT tools pass a public url query param; calls use assertOk and WaveToolError instead of AgentToolkit.call()’s unchecked JSON parsing.

DispatchToolkit targets dispatch.wave.online (not api.wave.online) for wave_route and wave_list_routing_profiles. PaymentsToolkit exposes read-only x402/MPP discovery with no API key and no Authorization header; verify/settle are intentionally omitted.

AgentToolkit now delegates AgentTool, validated, and toMCPToolDefs to shared without changing the public AgentTool import path. .wave/repo.json documents the new capabilities, claims, tool count, and “Working with audio” guidance.

Reviewed by Cursor Bugbot for commit f31bbad. Configure here.


Summary by cubic

Add FleetToolkit, DispatchToolkit, and PaymentsToolkit to expose voice, transcription, captions, model routing, and payment discovery; the tool registry grows from 10 to 17 with paths grounded in live spokes and Dispatch (not the API spec).

  • New Features

    • FleetToolkit (gateway): wave_speak (POST /v1/voice), wave_transcribe (POST /v1/transcribe), wave_caption (POST /v1/captions). VoiceResult returns real audio bytes; toJSON() emits a small receipt. Usage/rate-limit headers surfaced via readUsage.
    • DispatchToolkit (own host): wave_route (POST /), wave_list_routing_profiles (GET /profiles). Defaults to https://dispatch.wave.online.
    • PaymentsToolkit (public, no key): wave_find_paid_services (GET /v1/mpp/services), wave_payment_schemes (GET /v1/{x402,mpp}/facilitator/supported). Sends no Authorization header by design.
    • All three toolkits throw WaveToolError on non-2xx (status/body preserved, isRateLimited for 429).
  • Refactors

    • Shared plumbing in src/tools/shared.ts: AgentTool, MCP mapper (toMCPToolDefs), WaveToolError, readUsage, validated. AgentToolkit re-exports types; MCP tool defs are identical across toolkits.
    • Public exports added in @wave-av/adk: new toolkits, types, and helpers.
    • Docs updated in .wave/repo.json to cover 17 tools and audio handling.

Written for commit f31bbad. Summary will update on new commits.

Review in cubic

Adds the shipped WAVE surface the ADK was missing: the voice/transcribe/captions product spokes (FleetToolkit), Dispatch model routing (DispatchToolkit), and the read half of the x402/MPP agent-payment rails (PaymentsToolkit). Registry goes 10 -> 17 tools.

Every path comes from each spoke router at origin/main, not api-spec/openapi.yaml, which over-declares endpoints that 404 in production (wave-av/api-spec#33).

AgentTool and the MCP-shape mapper move to src/tools/shared.ts so all four toolkits emit identical MCP definitions; AgentToolkit re-exports AgentTool from its original path, so the public API is unchanged.
@changeset-bot

changeset-bot Bot commented Jul 26, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: f31bbad

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@cursor

cursor Bot commented Jul 26, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_8f5f1c1f-9cbb-40fd-a286-7717a40ff437)

@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 44 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 458117d5-0eac-457d-984a-efaca08d4918

📥 Commits

Reviewing files that changed from the base of the PR and between eb79119 and f31bbad.

📒 Files selected for processing (8)
  • .wave/repo.json
  • src/index.ts
  • src/tools/AgentToolkit.ts
  • src/tools/DispatchToolkit.ts
  • src/tools/FleetToolkit.ts
  • src/tools/PaymentsToolkit.ts
  • src/tools/index.ts
  • src/tools/shared.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/fleet-tools-72
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch feat/fleet-tools-72

Comment @coderabbitai help to get the list of available commands.

@yakimoto

Copy link
Copy Markdown
Contributor Author

Flagging a problem with this PR's base that I found after opening it.

This repo is not the one that publishes @wave-av/adk. It sits at 1.0.2; npm serves 1.0.14, built from wave-av/sdkssdk-typescript/packages/adk. Full evidence and the decision needed are in wave-av/sdks#42.

Two consequences for this PR:

  1. Merging it would not ship these toolkits to anyone. The work is sound — 33/33 smoke checks, live-proved payment tools, type-check/lint/build clean — but it lands in a fork.
  2. One of its stated caveats is wrong because the fork is wrong. I wrote that dist/ here contains only index.js/index.cjs and that the subpath exports are unbuilt. That is true of this repo and false of the shipped package, which builds all six subpath entries. That claim comes from this repo's .wave/repo.json, which describes the fork rather than the product.

If sdks becomes the home (the likelier call), this needs re-basing onto sdk-typescript/packages/adk — mechanically straightforward, since the four new files are self-contained and the only edits to existing files are the AgentTool/toMCPTools extraction in AgentToolkit.ts and the two barrel exports. The .wave/repo.json half would need reworking against whatever SSOT sdks uses.

Not re-basing it unilaterally — that decision belongs with wave-av/sdks#42. Holding here.

@wave-bugbot

wave-bugbot Bot commented Aug 11, 2026

Copy link
Copy Markdown

🌊 WAVE BugBot — 39 finding(s)

🔴 28 · 🟠 11

  • 🔴 P0 .wave/repo.json:29 CWE-862Missing role guard for SECURITY DEFINER RPCs
    The FleetToolkit, DispatchToolkit, and PaymentsToolkit are exposed as SECURITY DEFINER functions, but there is no role check to ensure the caller has the
  • 🔴 P0 src/tools/DispatchToolkit.ts:34 CWE-862Missing role guard for SECURITY DEFINER RPC
    The call method does not check the caller's role before making requests. This could allow unauthorized access if the toolkit is used by an unauthenticated use
  • 🔴 P0 src/tools/FleetToolkit.ts:34 CWE-862Missing role guard for SECURITY DEFINER RPC
    The post method does not check the caller's role before making requests. This could allow unauthorized access if the toolkit is used by an unauthenticated use
  • 🔴 P0 src/tools/PaymentsToolkit.ts:34 CWE-862Missing role guard for SECURITY DEFINER RPC
    The publicGet method does not check the caller's role before making requests. This could allow unauthorized access if the toolkit is used by an unauthenticate
  • 🔴 P0 src/tools/DispatchToolkit.ts:32 CWE-840Potential money-path issue due to missing idempotency key
    The route and listProfiles methods in the DispatchToolkit class do not include an idempotency key, which could lead to unintended side effects if a reques
  • 🔴 P0 src/tools/FleetToolkit.ts:32 CWE-840Potential money-path issue due to missing idempotency key
    The speak, transcribe, and caption methods in the FleetToolkit class do not include an idempotency key, which could lead to unintended side effects if a
  • 🔴 P0 src/tools/PaymentsToolkit.ts:32 CWE-840Potential money-path issue due to missing idempotency key
    The findPaidServices and paymentSchemes methods in the PaymentsToolkit class do not include an idempotency key, which could lead to unintended side effect
  • 🔴 P0 src/tools/DispatchToolkit.ts:34 CWE-862Missing authentication check for public endpoints
    The DispatchToolkit does not perform any authentication checks on its methods, which are exposed over the network. This could allow unauthorized access to sensi
  • 🔴 P0 src/tools/FleetToolkit.ts:34 CWE-862Missing authentication check for public endpoints
    The FleetToolkit does not perform any authentication checks on its methods, which are exposed over the network. This could allow unauthorized access to sensitiv
  • 🔴 P0 src/tools/PaymentsToolkit.ts:34 CWE-862Missing authentication check for public endpoints
    The PaymentsToolkit does not perform any authentication checks on its methods, which are exposed over the network. This could allow unauthorized access to sensi
  • 🔴 P0 .wave/repo.json:105 CWE-840Potential money-path issue in PaymentsToolkit
    The PaymentsToolkit does not perform any authorization checks, which could lead to unauthorized access to the payment rails.
  • 🔴 P0 .wave/repo.json:76 CWE-840Potential money path without authorization checks
    The documentation mentions 'Agent-payment rails (2 tools) — public, so no key is taken at all', but there is no indication of role guards or authorization check
  • 🔴 P0 .wave/repo.json:105 CWE-269Missing role guard for admin RPCs
    The documentation mentions 'four MCP-compatible toolkits exposing 17 tools across streams/production', but there is no indication of role guards or authorizatio
  • 🔴 P0 .wave/repo.json:149 CWE-840Potential money path without authorization checks
    The documentation mentions 'Agent-payment rails (2 tools) — public, so no key is taken at all', but there is no indication of role guards or authorization check
  • 🔴 P0 src/tools/AgentToolkit.ts:153 CWE-476Potential money-path issue due to missing idempotency key
    The toMCPTools method does not include an idempotency key in the tool definitions, which could lead to replay attacks if the same request is made multiple tim
  • 🔴 P0 src/tools/FleetToolkit.ts:10 CWE-476Potential money-path issue due to missing idempotency key
    The FleetToolkit class does not include an idempotency key in the tool definitions, which could lead to replay attacks if the same request is made multiple ti
  • 🔴 P0 src/tools/DispatchToolkit.ts:10 CWE-476Potential money-path issue due to missing idempotency key
    The DispatchToolkit class does not include an idempotency key in the tool definitions, which could lead to replay attacks if the same request is made multiple
  • 🔴 P0 src/tools/PaymentsToolkit.ts:10 CWE-476Potential money-path issue due to missing idempotency key
    The PaymentsToolkit class does not include an idempotency key in the tool definitions, which could lead to replay attacks if the same request is made multiple
  • 🔴 P0 src/tools/DispatchToolkit.ts:57 CWE-89Potential SQL injection vulnerability
    The call method constructs a URL using string interpolation, which could be used to inject malicious SQL if the input is not properly sanitized.
  • 🔴 P0 src/tools/FleetToolkit.ts:57 CWE-89Potential SQL injection vulnerability
    The post method constructs a URL using string interpolation, which could be used to inject malicious SQL if the input is not properly sanitized.
  • 🔴 P0 src/tools/PaymentsToolkit.ts:57 CWE-89Potential SQL injection vulnerability
    The publicGet method constructs a URL using string interpolation, which could be used to inject malicious SQL if the input is not properly sanitized.
  • 🔴 P0 src/tools/DispatchToolkit.ts:70 CWE-918Potential SSRF vulnerability
    The call method accepts a URL as input, which could be used to perform an SSRF attack if the input is not properly validated.
  • 🔴 P0 src/tools/FleetToolkit.ts:70 CWE-918Potential SSRF vulnerability
    The post method accepts a URL as input, which could be used to perform an SSRF attack if the input is not properly validated.
  • 🔴 P0 src/tools/PaymentsToolkit.ts:70 CWE-918Potential SSRF vulnerability
    The publicGet method accepts a URL as input, which could be used to perform an SSRF attack if the input is not properly validated.
  • 🔴 P0 src/tools/DispatchToolkit.ts:31 CWE-264Missing authentication for public endpoint
    The listProfiles method does not require any authentication, but it should be protected to prevent unauthorized access.
  • 🔴 P0 src/tools/FleetToolkit.ts:31 CWE-264Missing authentication for public endpoint
    The transcribe method does not require any authentication, but it should be protected to prevent unauthorized access.
  • 🔴 P0 src/tools/PaymentsToolkit.ts:31 CWE-264Missing authentication for public endpoint
    The findPaidServices method does not require any authentication, but it should be protected to prevent unauthorized access.
  • 🔴 P0 .wave/repo.json:105 CWE-264Potential for public access to sensitive operations in PaymentsToolkit
    The PaymentsToolkit exposes methods that do not require authentication, which could be used by unauthorized users.
  • 🟠 P1 .wave/repo.json:76 CWE-426Unpinned search_path on SECURITY DEFINER function
    The FleetToolkit and DispatchToolkit functions are SECURITY DEFINER, but the search_path is not pinned with pg_temp LAST.
  • 🟠 P1 .wave/repo.json:76 CWE-457Potential for missing tools in the toMCPTools method
    The toMCPTools method is called twice, once directly and once through a delegate. Ensure that both calls return the same set of tools to avoid discrepancies.
  • 🟠 P1 src/tools/DispatchToolkit.ts:34 CWE-426Unpinned search_path (must end pg_temp)
    The call method does not pin the search_path, which could allow an attacker to shadow objects in earlier schemas.
  • 🟠 P1 src/tools/FleetToolkit.ts:34 CWE-426Unpinned search_path (must end pg_temp)
    The post method does not pin the search_path, which could allow an attacker to shadow objects in earlier schemas.
  • 🟠 P1 src/tools/PaymentsToolkit.ts:34 CWE-426Unpinned search_path (must end pg_temp)
    The publicGet method does not pin the search_path, which could allow an attacker to shadow objects in earlier schemas.
  • 🟠 P1 .wave/repo.json:149 CWE-476Potential crash due to unguarded NULL in FleetToolkit
    The VoiceResult.toJSON() method returns a usage receipt, but it does not handle the case where audio is NULL.
  • 🟠 P1 src/tools/DispatchToolkit.ts:70 CWE-476Potential NULL pointer dereference
    The call method does not check if the response body is null before parsing it as JSON, which could lead to a NULL pointer dereference.
  • 🟠 P1 src/tools/FleetToolkit.ts:70 CWE-476Potential NULL pointer dereference
    The post method does not check if the response body is null before parsing it as JSON, which could lead to a NULL pointer dereference.
  • 🟠 P1 src/tools/PaymentsToolkit.ts:70 CWE-476Potential NULL pointer dereference
    The publicGet method does not check if the response body is null before parsing it as JSON, which could lead to a NULL pointer dereference.
  • 🟠 P1 .wave/repo.json:149 CWE-476Potential for unhandled errors in toMCPTools method
    The toMCPTools method does not handle potential errors that could occur during the invocation of this.getTools(). This could lead to data corruption or cras
  • 🟠 P1 .wave/repo.json:149 CWE-476Potential for unhandled errors in call method
    The call method does not handle potential errors that could occur during the HTTP request. This could lead to data corruption or crashes.

severity: critical · major · minor · info — local review · $0 inference · wave-dispatch · react 👍/👎 to tune

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