Skip to content

sync: mirror open-core subset from private repo#2

Merged
yakimoto merged 1 commit into
masterfrom
auto/sync-public
May 28, 2026
Merged

sync: mirror open-core subset from private repo#2
yakimoto merged 1 commit into
masterfrom
auto/sync-public

Conversation

@yakimoto
Copy link
Copy Markdown
Contributor

@yakimoto yakimoto commented May 26, 2026

Automated by scripts/sync-public.sh. Mirrors README, SDK READMEs, threat-model, LICENSE, the wrangler example, and the JS/Python/Rust/Ruby thin clients from the private repo. worker.ts + sdk/go are curated separately (open-core boundary). Review + CI gate this before merge.


Summary by cubic

Mirrors the open‑core subset and ships a local‑first, OpenAI‑compatible proxy in the Python SDK (dispatch serve) that routes easy turns to your local models and escalates hard ones to your frontier. Adds public benchmarks plus savings/subscription APIs across SDKs, and tightens docs on auditability, zero prompt logging, pinned builds, batch routing, and vector‑only classification.

  • New Features
    • Python SDK: dispatch serve runs a local‑first proxy; serves from your local server (WAVE_ENDPOINT, default :11434) when confident, escalates to WAVE_OAI_UPSTREAM, always falls back on errors; binds 127.0.0.1:8090; installed as a dispatch console script via wave-dispatch.
    • SDKs (JS/Python/Ruby/Rust): added savings(), subscription(), and subscribe(plan) via the sidecar at https://dispatch-agents.wave.online; safer URL handling (non‑regex slash trim), explicit 401s, stronger error messages, and SSL verify in Ruby.
    • Docs: added BENCHMARKS.md (graders, routing, savings formula and 63–79% results); README clarifies the trust boundary and pinned builds, shows proxy usage, batch routing via {"prompts":[…]}, and keeping prompts local with {"vector":[…]}.

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

Review in cubic

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 26, 2026

⚠️ No Changeset found

Latest commit: d27c352

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

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 26, 2026

Review Change Stack

Warning

Review limit reached

@yakimoto, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 29 minutes and 30 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

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.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: a4e72bf7-af3b-4d99-bed9-8302cade3ec3

📥 Commits

Reviewing files that changed from the base of the PR and between dee21cf and d27c352.

📒 Files selected for processing (9)
  • BENCHMARKS.md
  • README.md
  • sdk/js/index.js
  • sdk/python/pyproject.toml
  • sdk/python/wave_dispatch/__init__.py
  • sdk/python/wave_dispatch/cli.py
  • sdk/python/wave_dispatch/proxy.py
  • sdk/ruby/lib/wave_dispatch.rb
  • sdk/rust/src/lib.rs

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds BENCHMARKS.md and README clarifications about routing, auditability, and savings; bumps Python package and adds a dispatch CLI; extends Python/JS/Ruby/Rust SDKs with an agents-sidecar and centralized HTTP helpers; and implements a stdlib local-first OpenAI-compatible proxy with classifier-driven routing, logging, and upstream fallbacks.

Changes

Documentation for Transparency and Auditability

Layer / File(s) Summary
Benchmarks methodology and savings model
BENCHMARKS.md
Introduces the benchmarks methodology, lists evaluation dimensions and graders, describes the embedding/classification routing pipeline and a margin-based frontier escalation threshold, reports headline routing outcomes, provides the savings calculation formula with a worked numeric example, and documents reproducibility details including pricing references.
Trust boundary and auditability clarification
README.md
Clarifies the edge worker as auditable, states it emits routing decisions and logs no prompt content, adds a reference architecture diagram and cross-references worker docs/threat model/benchmarks, clarifies what components touch prompts, and documents pinned/attestable builds plus a local-first proxy usage example (single and batch routing).

Python SDK and Local-First Proxy

Layer / File(s) Summary
Packaging and CLI
sdk/python/pyproject.toml, sdk/python/wave_dispatch/cli.py
Bumps wave-dispatch package version to 0.4.3, updates the project description, adds a dispatch console script entrypoint (wave_dispatch.cli:main), and provides a CLI that supports dispatch serve to start the local proxy and prints usage otherwise.
Dispatch client surface and agents endpoints
sdk/python/wave_dispatch/__init__.py
Bumps __version__, introduces DEFAULT_AGENTS_ENDPOINT, stores normalized endpoint and agents endpoints, extends Dispatch.__init__ to accept agents_endpoint, and adds public agents methods savings(), subscription(), and subscribe(plan) with _lic() enforcement.
Centralized HTTP helpers and error handling
sdk/python/wave_dispatch/__init__.py
Refactors HTTP plumbing: adds _headers() for JSON + optional Bearer, _send() centralizing urllib-based JSON decoding and HTTP error mapping (explicit 401/402), and exposes _post(url, body) and _get(url).
Proxy configuration and helpers
sdk/python/wave_dispatch/proxy.py
Adds environment-driven proxy configuration, JSONL logging helpers, last-user-message extraction, trivial-request heuristics, and local-route classification helpers.
Classifier-driven routing decision flow
sdk/python/wave_dispatch/proxy.py
Implements decision logic rejecting local handling for tool/function calls, using the hosted classifier when licensed (allowing local-only for local_*/direct routes with forward false), and falling back to trivial heuristics on classifier failure or when unlicensed.
HTTP handler, forwarding, fallbacks, and server
sdk/python/wave_dispatch/proxy.py
Adds an HTTP POST handler for OpenAI-compatible /v1/chat/completions that parses payloads, decides local vs upstream, rewrites payloads/headers for local Ollama calls, preserves client Authorization when forwarding upstream, streams responses, retries upstream on local failures, logs per-request JSONL, and exposes serve() to start a ThreadingHTTPServer on 127.0.0.1.

JavaScript SDK

Layer / File(s) Summary
JS Dispatch & agents API
sdk/js/index.js
Adds DEFAULT_AGENTS_ENDPOINT, normalizes endpoint/agentsEndpoint, generalizes request sending via _send(url, method, body) with conditional Bearer auth and JSON body handling, updates route/execute/routeVector to POST explicitly, and adds savings(), subscription(), and subscribe(plan) (license-enforced with _lic()) plus explicit 401/402 error mapping.

Ruby SDK

Layer / File(s) Summary
Ruby Client changes
sdk/ruby/lib/wave_dispatch.rb
Bumps WaveDispatch::VERSION, extends Client#initialize with agents_endpoint, replaces the previous POST-only helper with send_req(method, url, body=nil), adds savings, subscription, and subscribe(plan), enforces license presence via lic, and expands HTTP error handling (401/402 and non-2xx responses).

Rust SDK

Layer / File(s) Summary
Rust Dispatch agents & HTTP refactor
sdk/rust/src/lib.rs
Adds an agents field initialized from WAVE_AGENTS_ENDPOINT, refactors request helpers to URL-scoped get/post with auth() for conditional Bearer headers, rewires route/execute to use the post helper against self.endpoint, and adds license-required agents APIs savings, subscription, and subscribe with lic() enforcement.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Proxy
  participant Classifier
  participant LocalOllama
  participant Upstream
  Client->>Proxy: POST /v1/chat/completions (messages, auth)
  Proxy->>Proxy: extract last user message & trivial heuristic
  Proxy->>Classifier: POST prompt (if WAVE_LICENSE set)
  Classifier-->>Proxy: route decision (local_* / forward / direct) or error
  alt classifier indicates local and request allowed
    Proxy->>LocalOllama: forward rewritten request with Ollama token
    LocalOllama-->>Proxy: stream response
    Proxy-->>Client: stream response
  else classifier indicates forward or unlicensed or failure
    Proxy->>Upstream: forward original request with client Authorization
    Upstream-->>Proxy: response
    Proxy-->>Client: response
  end
  Proxy->>Proxy: append JSONL log with routing outcome
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hopped through docs and SDK code,

routing choices on the road.
A proxy listens, classifiers steer,
local paths first, fallbacks near.
🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 47.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'sync: mirror open-core subset from private repo' directly describes the main purpose of the changeset—an automated synchronization that mirrors code and documentation from a private repository.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The PR description clearly relates to the changeset: it documents the automated sync of open-core subset from private repo, specifically mentioning the new proxy, SDK additions, and documentation updates that align with the file changes.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch auto/sync-public
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch auto/sync-public

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

@yakimoto yakimoto force-pushed the auto/sync-public branch from ce6227e to faf7b05 Compare May 26, 2026 23:57
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

No issues found across 2 files

Re-trigger cubic

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
README.md (1)

60-66: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fix README “Pinned, attestable builds” wording to match /status implementation

  • /status?format=json is publicly reachable and returns JSON containing a version field (README.md suggests using it).
  • The implementation sets version to env.CF_VERSION_METADATA?.id and falls back to "live" when CF_VERSION_METADATA isn’t present (edge-router/worker.ts, around const ver = ... || "live" and version: ver).
  • Update the README claim from “commit hash” to the actual source/behavior of version (Cloudflare version metadata id when available; otherwise "live").
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 60 - 66, Update the "Pinned, attestable builds"
README blurb to accurately reflect the /status implementation: replace the claim
that it returns a "commit hash" with a statement that the JSON `version` field
is set to the Cloudflare version metadata id when available
(env.CF_VERSION_METADATA?.id as assigned to `ver` in edge-router/worker.ts) and
otherwise falls back to the string "live" (`const ver = ... || "live"` and
`version: ver`), and keep the example curl to show `.version` from
`/status?format=json`.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@BENCHMARKS.md`:
- Around line 40-45: The fenced code block containing the formulas (lines
starting with "frontier_only", "with_dispatch", and "savings") lacks a language
identifier; update the opening fence from ``` to include a language token (e.g.,
```text) so the block becomes ```text ... ``` to enable proper syntax
highlighting and accessibility while leaving the formulas unchanged.

---

Outside diff comments:
In `@README.md`:
- Around line 60-66: Update the "Pinned, attestable builds" README blurb to
accurately reflect the /status implementation: replace the claim that it returns
a "commit hash" with a statement that the JSON `version` field is set to the
Cloudflare version metadata id when available (env.CF_VERSION_METADATA?.id as
assigned to `ver` in edge-router/worker.ts) and otherwise falls back to the
string "live" (`const ver = ... || "live"` and `version: ver`), and keep the
example curl to show `.version` from `/status?format=json`.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: f608ec62-2b9c-4818-994e-401ef9102702

📥 Commits

Reviewing files that changed from the base of the PR and between dee21cf and ce6227e.

📒 Files selected for processing (2)
  • BENCHMARKS.md
  • README.md
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: cubic · AI code reviewer
  • GitHub Check: Analyze (rust)
  • GitHub Check: Analyze (go)
🧰 Additional context used
🪛 markdownlint-cli2 (0.22.1)
BENCHMARKS.md

[warning] 40-40: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🔍 Remote MCP Linear

The web search did not find specific information about the wave-av/dispatch-edge project. The Linear search results appear to be for a different organization (WAVE platform). Let me try one more targeted approach to find context about the actual GitHub repository and open-core practices relevant to this PR.## Summary of Findings

Based on my search across available tools, here is the relevant context for reviewing this PR:

Key Context Found

PR Overview:
The PR automates the mirroring of an open-core subset from a private repository into the public dispatch-edge repository. The automation is handled by scripts/sync-public.sh and runs on the auto/sync-public branch.

Repository Synchronization Approach:
According to best practices for repository mirroring, synchronization should be automated with cron jobs, and mirror integrity should be verified after initial setup. The PR follows this pattern with automated scripted synchronization.

What's Included in the Sync:

  • README (expanded with trust boundary reference architecture and security clarifications)
  • SDK READMEs (for multiple languages)
  • Threat model documentation
  • LICENSE
  • Wrangler example configuration
  • Thin clients for JavaScript, Python, Rust, and Ruby
  • BENCHMARKS.md (detailed documentation on routing evaluation and reproducibility)

Open-Core Boundary:
The PR explicitly notes that worker.ts and sdk/go are excluded from the automated mirror and are being "curated separately as the open-core boundary." This indicates a deliberate policy where:

  • These files represent proprietary or differentiating core functionality
  • They are managed through separate channels (not automated sync)
  • The remaining sync'd content forms the public-facing open-core subset

Documentation Quality:
Both changed files are documentation-only with Low estimated code review effort:

  • BENCHMARKS.md: 63 lines added (documents benchmarking methodology, savings model with worked examples, reproducibility details)
  • README.md: +15 lines/-7 lines (security clarifications, architecture diagrams, component responsibility matrix)

CI/Merge Requirements:
The PR description explicitly states: "review and CI must gate the merge" — indicating that both manual review and automated testing are required before merging.

Recommendations for Review

  1. Verify the sync script integrity: Confirm that scripts/sync-public.sh correctly filters files and preserves intended exclusions (worker.ts, sdk/go)
  2. Audit documentation accuracy: Validate that BENCHMARKS.md technical details and README architecture diagrams are current and accurate
  3. Check for sensitive data: Ensure no private credentials, internal IPs, or proprietary details leaked into documentation
  4. Verify link integrity: Confirm that references to /pricing.json and other external artifacts are valid and accessible
  5. Security posture: Validate that README's trust boundary documentation accurately reflects current security practices, [::web_search::] — Note: Specific Linear issues directly related to this dispatch-edge sync could not be located in the workspace, suggesting this PR originates from a separate organizational context.
🔇 Additional comments (11)
BENCHMARKS.md (7)

1-5: LGTM!


6-19: LGTM!


20-22: LGTM!


23-28: LGTM!


29-34: LGTM!


47-54: LGTM!


58-64: ⚡ Quick win

Ensure https://dispatch.wave.online/pricing.json is publicly accessible and returns valid JSON.

BENCHMARKS.md (Reproducibility, lines 58-64) relies on /pricing.json for machine-readable pricing; the docs should guarantee it’s reachable publicly (e.g., stable URL + expected 200 response) and that the payload is valid JSON.

README.md (4)

8-9: LGTM!


51-56: LGTM!


57-58: LGTM!


46-49: ⚡ Quick win

Referenced docs files exist in repo (README.md lines 46-49): threat-model.md, edge-router/worker.ts, and edge-router/wrangler.example.toml are present.

Comment thread BENCHMARKS.md
Comment on lines +40 to +45
```
frontier_only = N · baseline
with_dispatch = N · fee (the routing decision, every call)
+ N · (1 − localShare) · baseline (the escalated remainder still pays your frontier)
savings = frontier_only − with_dispatch
```
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add language identifier to the code block.

The fenced code block should specify a language for proper syntax highlighting and accessibility.

📝 Proposed fix
-```
+```text
 frontier_only  = N · baseline
 with_dispatch  = N · fee            (the routing decision, every call)
                + N · (1 − localShare) · baseline   (the escalated remainder still pays your frontier)
 savings        = frontier_only − with_dispatch
</details>

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 markdownlint-cli2 (0.22.1)</summary>

[warning] 40-40: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

</details>

</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @BENCHMARKS.md around lines 40 - 45, The fenced code block containing the
formulas (lines starting with "frontier_only", "with_dispatch", and "savings")
lacks a language identifier; update the opening fence from to include a language token (e.g.,text) so the block becomes text ... to enable
proper syntax highlighting and accessibility while leaving the formulas
unchanged.


</details>

<!-- fingerprinting:phantom:triton:puma -->

<!-- This is an auto-generated comment by CodeRabbit -->

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
README.md (1)

60-67: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Update README pinned-build verification claim
https://dispatch.wave.online/status?format=json does not return any commit/hash/sha field—current JSON includes only operational, checks.edge_embed/classifier/licensing_kv, colo, version (e.g., "live"), and checked timestamp—so the README’s claim that the commit hash can be verified via this endpoint isn’t supported.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 60 - 67, The README's "Pinned, attestable builds"
claim that curl https://dispatch.wave.online/status?format=json returns a commit
hash is incorrect; update that section to remove or correct the assertion and
example: state that the /status endpoint returns fields like "operational",
"checks.edge_embed/classifier/licensing_kv", "colo", "version" (e.g., "live")
and "checked" timestamp (not a commit/sha), and either point readers to the
correct internal endpoint or CI artifact that exposes the commit hash or explain
how to retrieve the pinned build from deployment metadata instead. Ensure you
update the example curl/ jq snippet to reflect the actual "version" field (or
replace it with the correct endpoint) under the "Pinned, attestable builds"
heading.
♻️ Duplicate comments (1)
BENCHMARKS.md (1)

40-45: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add language identifier to the code block.

The fenced code block should specify a language for proper syntax highlighting and accessibility.

📝 Proposed fix
-```
+```text
 frontier_only  = N · baseline
 with_dispatch  = N · fee            (the routing decision, every call)
                + N · (1 − localShare) · baseline   (the escalated remainder still pays your frontier)
 savings        = frontier_only − with_dispatch
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @BENCHMARKS.md around lines 40 - 45, The fenced code block containing the
formulas (lines starting with "frontier_only", "with_dispatch", "savings") needs
a language identifier for proper syntax highlighting and accessibility; update
the triple-backtick fence so it becomes "text" (or another appropriate language like "none") immediately after the opening backticks, leaving the
block content unchanged; ensure the closing triple-backticks remain and commit
the change so the block renders with the specified language.


</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>🤖 Prompt for all review comments with AI agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @BENCHMARKS.md:

  • Line 32: Update the ambiguous phrase in BENCHMARKS.md where the line about
    qwen2.5 reads "tool-calling 2/2" to explicitly state what that fraction means
    (e.g., "tool-calling: 2/2 benchmarks passed (100% success)" or "tool-calling: 2
    of 2 tool-calling tasks"), so change the qwen2.5 line to include a short
    parenthetical clarifying whether it denotes number of tool-calling tests passed,
    percentage accuracy, or something else.

In @README.md:

  • Around line 8-9: The README asserts "logs zero prompt content" but
    threat-model.md does not. Either (A) update threat-model.md (in the "Edge worker
    public attack surface" section) with an explicit "No-prompt-content logging"
    policy that states the edge returns only routing decisions, no raw prompt text
    is persisted or emitted to telemetry, and list enforcement controls (e.g., no
    prompt fields in logs, prompt-capping/scrubbing behavior, and where decisions
    are recorded), or (B) edit README.md to reference the actual policy location if
    it lives elsewhere; use the exact phrases "logs zero prompt content", "Edge
    worker public attack surface", README.md and threat-model.md in your change so
    reviewers can verify the claim against the documented source of truth.

Outside diff comments:
In @README.md:

  • Around line 60-67: The README's "Pinned, attestable builds" claim that curl
    https://dispatch.wave.online/status?format=json returns a commit hash is
    incorrect; update that section to remove or correct the assertion and example:
    state that the /status endpoint returns fields like "operational",
    "checks.edge_embed/classifier/licensing_kv", "colo", "version" (e.g., "live")
    and "checked" timestamp (not a commit/sha), and either point readers to the
    correct internal endpoint or CI artifact that exposes the commit hash or explain
    how to retrieve the pinned build from deployment metadata instead. Ensure you
    update the example curl/ jq snippet to reflect the actual "version" field (or
    replace it with the correct endpoint) under the "Pinned, attestable builds"
    heading.

Duplicate comments:
In @BENCHMARKS.md:

  • Around line 40-45: The fenced code block containing the formulas (lines
    starting with "frontier_only", "with_dispatch", "savings") needs a language
    identifier for proper syntax highlighting and accessibility; update the
    triple-backtick fence so it becomes "text" (or another appropriate language like "none") immediately after the opening backticks, leaving the block
    content unchanged; ensure the closing triple-backticks remain and commit the
    change so the block renders with the specified language.

</details>

<details>
<summary>🪄 Autofix (Beta)</summary>

Fix all unresolved CodeRabbit comments on this PR:

- [ ] <!-- {"checkboxId": "4b0d0e0a-96d7-4f10-b296-3a18ea78f0b9"} --> Push a commit to this branch (recommended)
- [ ] <!-- {"checkboxId": "ff5b1114-7d8c-49e6-8ac1-43f82af23a33"} --> Create a new PR with the fixes

</details>

---

<details>
<summary>ℹ️ Review info</summary>

<details>
<summary>⚙️ Run configuration</summary>

**Configuration used**: Organization UI

**Review profile**: ASSERTIVE

**Plan**: Pro

**Run ID**: `95fc7fac-1440-4746-98c3-bc6113647cae`

</details>

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between ce6227e33c23a39fcbf50fb69c8ab995085b2cf5 and faf7b05fb987dec97161d666b7df5db96f5054dd.

</details>

<details>
<summary>📒 Files selected for processing (2)</summary>

* `BENCHMARKS.md`
* `README.md`

</details>

</details>

<details>
<summary>📜 Review details</summary>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🪛 markdownlint-cli2 (0.22.1)</summary>

<details>
<summary>BENCHMARKS.md</summary>

[warning] 40-40: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

</details>

</details><details>
<summary>🔍 Remote MCP Linear</summary>

## Relevant context found (concise)

- Linear contains an issue explicitly tracking verification of synced data: "Verify synced data" (WAVE-3193). This aligns with the PR's claim that the sync is automated and should be validated before merge.  

- There are active Linear issues about Edge function integration and embedding routing that are directly relevant to public sync decisions that exclude worker.ts:
  - "generate-embeddings Edge Function bypasses unified embedding router" (WAVE-24410) — shows prior findings where edge code calling provider APIs directly created integration gaps. Relevant when auditing what to exclude from the public mirror (worker.ts).
  - Multiple other integration/dispatch issues (e.g., WAVE-24224, WAVE-23901, WAVE-23913) show ongoing platform work around dispatch, migration, and public SDKs — useful context when reviewing which SDKs and examples are mirrored vs. kept private.

- A documents search returned no matching internal documentation for the repo (empty results), so no additional doc artifacts were found in Linear to validate BENCHMARKS.md or README links.

Sources used:
- Linear — list_issues (multiple results)
- Linear — list_documents (empty)

</details>

</details>

<details>
<summary>🔇 Additional comments (10)</summary><blockquote>

<details>
<summary>BENCHMARKS.md (6)</summary><blockquote>

`1-5`: LGTM!

---

`6-19`: LGTM!

---

`23-28`: LGTM!

---

`47-54`: LGTM!

---

`55-56`: LGTM!

---

`58-63`: _⚡ Quick win_

**Confirm `/pricing.json` endpoint is present and returns machine-readable pricing JSON.**

`https://dispatch.wave.online/pricing.json` is reachable and responds with valid JSON containing fields like `currency`, `free_tier`, `card_required`, `trial_days`, `plans` (starter/pro/scale/enterprise), and `pay_per_use` (including `per_decision_usd`).

</blockquote></details>
<details>
<summary>README.md (4)</summary><blockquote>

`41-44`: LGTM!

---

`51-56`: LGTM!

---

`57-58`: LGTM!

---

`46-48`: _⚡ Quick win_

**Resolve: trust boundary file references in README.md (lines 46-48)**
All referenced files exist in the repository: `edge-router/worker.ts`, `edge-router/wrangler.example.toml`, and `threat-model.md`.

</blockquote></details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

Comment thread BENCHMARKS.md
## Headline results

- Local handles **~80%** of decisions on mixed agent/dev workloads.
- `qwen2.5` (local, $0) wins **code · reason · summarize · tool-calling** routes outright (tool-calling 2/2).
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Clarify "tool-calling 2/2" notation.

The phrase "tool-calling 2/2" is ambiguous. Does this mean 2 out of 2 tool-calling benchmarks, 100% accuracy, or something else? Consider expanding for clarity.

📝 Suggested clarification
-- `qwen2.5` (local, $0) wins **code · reason · summarize · tool-calling** routes outright (tool-calling 2/2).
+- `qwen2.5` (local, $0) wins **code · reason · summarize · tool-calling** routes outright (passes 2/2 tool-calling tests).

Or if it means something else, adjust accordingly.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- `qwen2.5` (local, $0) wins **code · reason · summarize · tool-calling** routes outright (tool-calling 2/2).
- `qwen2.5` (local, $0) wins **code · reason · summarize · tool-calling** routes outright (passes 2/2 tool-calling tests).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@BENCHMARKS.md` at line 32, Update the ambiguous phrase in BENCHMARKS.md where
the line about `qwen2.5` reads "tool-calling 2/2" to explicitly state what that
fraction means (e.g., "tool-calling: 2/2 benchmarks passed (100% success)" or
"tool-calling: 2 of 2 tool-calling tasks"), so change the `qwen2.5` line to
include a short parenthetical clarifying whether it denotes number of
tool-calling tests passed, percentage accuracy, or something else.

Comment thread README.md
@yakimoto yakimoto force-pushed the auto/sync-public branch 4 times, most recently from 763489a to fc01d6a Compare May 27, 2026 19:19
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@README.md`:
- Line 106: The README's fenced code block lacks a language identifier, which
prevents proper syntax highlighting; update the code block surrounding the POST
example by adding an appropriate language tag (e.g., use "http" or "text")
immediately after the opening triple backticks so the block reads like ```http
and then the POST request line.

In `@sdk/python/wave_dispatch/__init__.py`:
- Around line 64-69: In the HTTPError handler inside
sdk/python/wave_dispatch/__init__.py (the except urllib.error.HTTPError as e:
block), preserve exception chaining by appending "from e" to each raised
RuntimeError (the three raises for 402, 401, and the generic raise using
e.read()). Update the raise statements so they re-raise the RuntimeError with
"from e" to retain the original traceback and error context.
- Around line 60-69: The _send method currently wraps urllib.error.HTTPError but
lets urllib.error.URLError bubble up; update _send in wave_dispatch.__init__.py
to catch urllib.error.URLError after the HTTPError block (or alongside it) and
re-raise a RuntimeError with a clear message including the URLError reason
(e.g., "dispatch: network error" plus e.reason or str(e)) so callers get a
consistent RuntimeError interface from _send; reference the _send method and the
urllib.error.URLError/HTTPError exceptions when making this change.
- Around line 18-19: The constructor parameter name `license` in the
WaveDispatch class shadows Python's built-in license() function—rename the
parameter (for example to `license_key` or `license_token`) in the `def
__init__(self, license: str | None = None, endpoint: str = DEFAULT_ENDPOINT,
agents_endpoint: str | None = None)` signature and update all references inside
the class (and any references when constructing this class) to the new name
(`license_key`); ensure type hints and default value remain the same and adjust
any docstrings or public API docs to reflect the new parameter name to avoid
breaking callers.
- Around line 45-47: The subscribe method uses self.license directly instead of
the validated helper used by savings() and subscription(), leading to
inconsistent error behavior; change subscribe(self, plan: str) to call
self._lic() to obtain the license (or a shared validation helper) and pass that
value into the body sent to self._post(self.agents + "/subscription/create",
{"license": <validated_license>, "plan": plan}); if the API expects the raw key
rather than the URL-encoded value returned by _lic(), adjust _lic() or add a
small validator that raises the same clear error when missing but returns the
raw license for subscribe.

In `@sdk/python/wave_dispatch/proxy.py`:
- Around line 95-97: The current code mutates the original upstream payload by
assigning raw = json.dumps(out).encode() (using out/body/LOCAL_MODEL and
target=OLLAMA), which then gets retried upstream on local failure; instead,
preserve the original upstream payload by keeping a separate variable for the
local request (e.g., local_raw or local_payload built from out with LOCAL_MODEL)
and leave raw (the original json.dumps(body).encode()) unchanged; use the
local-only variable when sending to the local target (OLLAMA) and only fall back
to sending the preserved original raw to the upstream endpoint on failure.
- Around line 88-93: json.loads(raw) may return non-dict types causing
_route_local(body) to fail when it expects a mapping; validate that the parsed
body is a dict before passing it to _route_local. Update the try/except block
around json.loads to set body to {} on error or if parsed value is not an
instance of dict, then call _route_local only when isinstance(body, dict);
reference the variables/functions json.loads, body, and _route_local to locate
the change.
- Around line 104-114: The current _log call before attempting the request
records "served": "local"/"upstream" based on the planned target, which is wrong
if the local attempt fails and you fall back to upstream; change the logic in
proxy.py (referencing _log, self.path, local, target, UPSTREAM, resp) so you
only log after a successful request (or update the log inside the exception
fallback) and record the actual served target (e.g., set served="local" when the
initial urllib.request.urlopen to target succeeds, otherwise set
served="upstream" when the fallback to UPSTREAM + self.path succeeds); ensure
the Authorization/header logic remains the same and that any logging on final
failure includes the actual attempted target.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3075d618-51b2-4b96-95fc-a48656fb231e

📥 Commits

Reviewing files that changed from the base of the PR and between faf7b05 and fc01d6a.

📒 Files selected for processing (6)
  • BENCHMARKS.md
  • README.md
  • sdk/python/pyproject.toml
  • sdk/python/wave_dispatch/__init__.py
  • sdk/python/wave_dispatch/cli.py
  • sdk/python/wave_dispatch/proxy.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (rust)
🧰 Additional context used
🪛 markdownlint-cli2 (0.22.1)
BENCHMARKS.md

[warning] 40-40: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

README.md

[warning] 106-106: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🪛 Ruff (0.15.14)
sdk/python/wave_dispatch/proxy.py

[warning] 31-31: Missing return type annotation for private function _log

Add return type annotation: None

(ANN202)


[error] 35-36: try-except-pass detected, consider logging the exception

(S110)


[warning] 35-35: Do not catch blind exception: Exception

(BLE001)


[warning] 39-39: Missing return type annotation for private function _prompt_of

(ANN202)


[warning] 47-47: Missing return type annotation for private function _trivial

(ANN202)


[warning] 56-56: Missing return type annotation for private function _is_local_route

(ANN202)


[warning] 60-60: Missing return type annotation for private function _route_local

(ANN202)


[error] 70-73: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.

(S310)


[error] 74-74: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.

(S310)


[warning] 76-76: Do not catch blind exception: Exception

(BLE001)


[warning] 82-82: Missing return type annotation for private function log_message

Add return type annotation: None

(ANN202)


[warning] 82-82: Missing type annotation for *a

(ANN002)


[warning] 85-85: Missing return type annotation for private function do_POST

Add return type annotation: None

(ANN202)


[warning] 90-90: Do not catch blind exception: Exception

(BLE001)


[error] 95-95: Multiple statements on one line (semicolon)

(E702)


[error] 106-106: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.

(S310)


[error] 106-106: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.

(S310)


[warning] 107-107: Do not catch blind exception: Exception

(BLE001)


[error] 113-113: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.

(S310)


[error] 113-113: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.

(S310)


[warning] 114-114: Do not catch blind exception: Exception

(BLE001)


[error] 115-115: Multiple statements on one line (semicolon)

(E702)


[error] 117-117: Multiple statements on one line (semicolon)

(E702)


[warning] 127-127: Do not catch blind exception: Exception

(BLE001)

sdk/python/wave_dispatch/__init__.py

[warning] 18-18: Missing return type annotation for special method __init__

Add return type annotation: None

(ANN204)


[error] 18-18: Function argument license is shadowing a Python builtin

(A002)


[warning] 51-51: Avoid specifying long messages outside the exception class

(TRY003)


[error] 62-62: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.

(S310)


[warning] 66-66: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


[warning] 66-66: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 68-68: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


[warning] 68-68: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 69-69: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


[warning] 69-69: Avoid specifying long messages outside the exception class

(TRY003)


[error] 72-72: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.

(S310)


[error] 75-75: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.

(S310)

🔇 Additional comments (22)
BENCHMARKS.md (6)

32-32: Clarify "tool-calling 2/2" notation.

The phrase is ambiguous - does it mean 2 out of 2 tool-calling benchmarks passed, 100% accuracy, or something else?


40-45: Add language identifier to the code block.

The fenced code block should specify a language (e.g., text) for proper syntax highlighting and accessibility.


1-19: LGTM!


20-27: LGTM!


47-56: LGTM!


58-63: LGTM!

README.md (6)

8-9: LGTM!


41-49: LGTM!


51-58: LGTM!


60-66: LGTM!


99-104: LGTM!


107-113: LGTM!

sdk/python/pyproject.toml (2)

7-7: Version mismatch with PR objectives.

The PR objectives state "shipped as wave-dispatch v0.2.0" but the code shows version 0.3.0.


20-21: LGTM!

sdk/python/wave_dispatch/cli.py (2)

5-10: LGTM!


11-24: LGTM!

sdk/python/wave_dispatch/__init__.py (5)

6-6: LGTM!

Also applies to: 10-10, 12-12


24-34: LGTM!


49-52: LGTM!


54-58: LGTM!


71-75: LGTM!

sdk/python/wave_dispatch/proxy.py (1)

66-67: LGTM!

Also applies to: 75-78

Comment thread README.md
Comment thread sdk/python/wave_dispatch/__init__.py
Comment thread sdk/python/wave_dispatch/__init__.py Outdated
Comment thread sdk/python/wave_dispatch/__init__.py Outdated
Comment thread sdk/python/wave_dispatch/proxy.py
Comment thread sdk/python/wave_dispatch/proxy.py
Comment thread sdk/python/wave_dispatch/proxy.py
@yakimoto yakimoto force-pushed the auto/sync-public branch from fc01d6a to 25ff294 Compare May 27, 2026 21:19
Comment thread sdk/js/index.js Fixed
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 10

♻️ Duplicate comments (1)
sdk/python/wave_dispatch/__init__.py (1)

18-23: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Restrict configured endpoints to absolute HTTP(S) URLs before issuing requests.

Dispatch.__init__ (lines 21-22) stores endpoint/agents_endpoint from args/env without scheme checks, and those values are later used to build urllib.request.Request objects and sent via urllib.request.urlopen (e.g., _send lines 60-63, _post line 71-72, _get line 74-75). Validate that both are absolute http/https URLs before use.

  • Also: subscribe() forwards self.license and plan without validation.
  • Also: _send() converts HTTPError to RuntimeError without chaining the original exception (raise ... from e), losing error context.
Suggested patch
 class Dispatch:
@@
     def __init__(self, license: str | None = None, endpoint: str = DEFAULT_ENDPOINT,
                  agents_endpoint: str | None = None):
         self.license = license or os.environ.get("WAVE_LICENSE")
-        self.endpoint = endpoint.rstrip("/")
-        self.agents = (agents_endpoint or os.environ.get("WAVE_AGENTS_ENDPOINT") or DEFAULT_AGENTS_ENDPOINT).rstrip("/")
+        self.endpoint = self._normalize_http_url(endpoint, "endpoint")
+        self.agents = self._normalize_http_url(
+            agents_endpoint or os.environ.get("WAVE_AGENTS_ENDPOINT") or DEFAULT_AGENTS_ENDPOINT,
+            "agents_endpoint",
+        )
+
+    `@staticmethod`
+    def _normalize_http_url(url: str, field: str) -> str:
+        p = urllib.parse.urlparse(url)
+        if p.scheme not in {"http", "https"} or not p.netloc:
+            raise ValueError(f"dispatch: {field} must be an absolute http(s) URL")
+        return url.rstrip("/")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdk/python/wave_dispatch/__init__.py` around lines 18 - 23,
Dispatch.__init__: validate that `endpoint` and `agents_endpoint` (used by
`_send`, `_post`, `_get`) are absolute URLs with http or https schemes before
assigning (normalize trailing slashes after validation) and raise a ValueError
if not; `subscribe()` should validate `self.license` (and the provided `plan`)
are non-empty and well-formed before sending; and `_send()` should re-raise
Http-related errors using exception chaining (e.g., `raise RuntimeError(...)
from e`) so the original `HTTPError`/exception is preserved for debugging.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@sdk/js/index.js`:
- Around line 27-31: The subscribe method currently sends an undefined license
and waits for a network error; change subscribe to validate and fail fast by
calling the existing _lic() guard (or checking this.license and throwing the
same error) before calling _send. Update the subscribe function (and references
to agents + "/subscription/create") to obtain/encode the license via _lic() so
the method throws immediately when no license is configured.
- Around line 23-27: Remove inclusion of the license in URLs and payloads for
savings(), subscription(), and subscribe(): stop calling this._lic() to append
?license= to the GET URLs and stop including { license: this.license } in
subscribe()'s POST body; rely on _send()'s Authorization header for auth
instead. Update savings() to call this._send(this.agents + "/ledger/summary",
"GET"), subscription() to call this._send(this.agents + "/subscription/status",
"GET"), and subscribe(plan) to call this._send(this.agents +
"/subscription/create", "POST", { plan }), ensuring no other code paths still
expose this._lic() or this.license in URLs or request bodies.

In `@sdk/python/wave_dispatch/cli.py`:
- Around line 5-24: The CLI currently prints the help message for unknown
commands but exits zero; update main() so that when args exist and args[0] is
not "serve" it prints the help and then returns a non-zero exit code (e.g., call
sys.exit(2)) to signal invalid usage, while preserving the existing behavior
that when args[0] == "serve" it imports proxy and calls proxy.serve() normally.

In `@sdk/python/wave_dispatch/proxy.py`:
- Around line 69-77: The current call to urllib.request.urlopen uses a hardcoded
15s timeout which blocks licensed requests; replace this with an
operator-configurable timeout (e.g., EDGE_TIMEOUT_SECONDS) with a low sensible
default (e.g., 1s) and use that variable in the urlopen(req,
timeout=EDGE_TIMEOUT_SECONDS) call inside the try block; keep the existing
behavior of building the Request with EDGE, LICENSE, _prompt_of(body) and
falling back to _trivial(body) on any Exception, but ensure the config is read
once (env var or module-level constant) and validated (fallback to default if
missing/invalid) so slow/unreachable EDGE triggers the fallback quickly.
- Around line 105-117: The current blanket except hides upstream HTTP errors;
update the try/except around urllib.request.urlopen (both the initial request
and the local->upstream fallback that uses UPSTREAM + self.path) to explicitly
catch urllib.error.HTTPError first, and in that handler forward the original
status, headers and body to the client (use the error's .code for status,
.headers.get("Content-Type", "application/json") for content-type and .read()
for the body) instead of mapping to 502; keep a separate except Exception block
to handle non-HTTPError failures and still return 502.

In `@sdk/ruby/lib/wave_dispatch.rb`:
- Around line 25-31: The methods savings, subscription, and subscribe are
including the license in URLs/bodies which leaks the bearer; update them to stop
passing license in query strings or JSON and instead rely on send_req's existing
Authorization handling: change savings to call send_req(:get,
"#{`@agents`}/ledger/summary") and subscription to call send_req(:get,
"#{`@agents`}/subscription/status"), and change subscribe(plan) to call
send_req(:post, "#{`@agents`}/subscription/create", { plan: plan }) (i.e., remove
`@license/license` from the body); also ensure no other code in these methods logs
the license before send_req is invoked.
- Around line 40-48: In send_req, don't parse every response body as success:
check the HTTP status using res.is_a?(Net::HTTPSuccess) (or Net::HTTP::Success)
before calling JSON.parse; if not successful, raise a descriptive error that
includes res.code and res.body (or parsed error when JSON) so callers receive
the HTTP status and message instead of a JSON::ParserError; update send_req to
perform this status check on the response object (res) and only parse on
success, otherwise raise with the status and body.
- Around line 19-31: The file uses Ruby 3+ endless method syntax in route,
execute, savings, subscription and subscribe which breaks under the gemspec's
Ruby >= 2.7 constraint; convert those endless defs to regular multi-line method
definitions (def ...; return ...; end) or update the gemspec to require Ruby >=
3.0. Update send_req to only JSON.parse(res.body) for successful 2xx responses
and to raise a useful error (including status and body) for other non-2xx
responses instead of unconditionally parsing. Consolidate license handling by
removing duplicated license parameters from savings/subscription query strings
and subscribe request body and instead rely on the existing Authorization header
usage in send_req (or, if an explicit license value is required by API, pass it
consistently via a single method parameter and never duplicate into both header
and query/body); adjust savings, subscription, and subscribe to use the chosen
consistent approach.

In `@sdk/rust/src/lib.rs`:
- Around line 33-45: The savings(), subscription(), and subscribe() methods are
leaking the bearer license by including it in query params and request bodies;
remove the license interpolation from the URL in savings() and subscription()
(stop appending ?license=...), and remove "license" from the JSON body in
subscribe(), relying on the existing auth() / Authorization header already set
by the client instead; update calls to self.get(...) and self.post(...) in those
functions to omit the license parameter and make sure auth() is invoked where
requests are prepared so the token is sent only via the Authorization header.
- Around line 43-50: subscribe currently sends the raw self.license (which can
be None) instead of enforcing the local license precondition; change subscribe
to call the existing lic() helper (e.g., let license = self.lic()?) and use that
returned String in the JSON payload so subscribe fails locally like
savings()/subscription() and propagates the error rather than sending
{"license": null}.

---

Duplicate comments:
In `@sdk/python/wave_dispatch/__init__.py`:
- Around line 18-23: Dispatch.__init__: validate that `endpoint` and
`agents_endpoint` (used by `_send`, `_post`, `_get`) are absolute URLs with http
or https schemes before assigning (normalize trailing slashes after validation)
and raise a ValueError if not; `subscribe()` should validate `self.license` (and
the provided `plan`) are non-empty and well-formed before sending; and `_send()`
should re-raise Http-related errors using exception chaining (e.g., `raise
RuntimeError(...) from e`) so the original `HTTPError`/exception is preserved
for debugging.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 47d2dd3b-3cd6-41e0-94b4-aa363acd587c

📥 Commits

Reviewing files that changed from the base of the PR and between fc01d6a and 25ff294.

📒 Files selected for processing (9)
  • BENCHMARKS.md
  • README.md
  • sdk/js/index.js
  • sdk/python/pyproject.toml
  • sdk/python/wave_dispatch/__init__.py
  • sdk/python/wave_dispatch/cli.py
  • sdk/python/wave_dispatch/proxy.py
  • sdk/ruby/lib/wave_dispatch.rb
  • sdk/rust/src/lib.rs
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Socket Security: Pull Request Alerts
  • GitHub Check: Analyze (ruby)
  • GitHub Check: Analyze (python)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (go)
  • GitHub Check: Analyze (rust)
🧰 Additional context used
🪛 markdownlint-cli2 (0.22.1)
BENCHMARKS.md

[warning] 40-40: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

README.md

[warning] 106-106: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🪛 RuboCop (1.86.2)
sdk/ruby/lib/wave_dispatch.rb

[fatal] 19-19: unexpected token tEQL
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)

(Lint/Syntax)


[fatal] 22-22: unexpected token tEQL
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)

(Lint/Syntax)


[fatal] 25-25: unexpected token tEQL
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)

(Lint/Syntax)


[fatal] 28-28: unexpected token tEQL
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)

(Lint/Syntax)


[fatal] 31-31: unexpected token tEQL
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)

(Lint/Syntax)

🪛 Ruff (0.15.14)
sdk/python/wave_dispatch/__init__.py

[warning] 18-18: Missing return type annotation for special method __init__

Add return type annotation: None

(ANN204)


[error] 18-18: Function argument license is shadowing a Python builtin

(A002)


[warning] 51-51: Avoid specifying long messages outside the exception class

(TRY003)


[error] 62-62: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.

(S310)


[warning] 66-66: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


[warning] 66-66: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 68-68: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


[warning] 68-68: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 69-69: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


[warning] 69-69: Avoid specifying long messages outside the exception class

(TRY003)


[error] 72-72: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.

(S310)


[error] 75-75: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.

(S310)

sdk/python/wave_dispatch/proxy.py

[warning] 31-31: Missing return type annotation for private function _log

Add return type annotation: None

(ANN202)


[error] 35-36: try-except-pass detected, consider logging the exception

(S110)


[warning] 35-35: Do not catch blind exception: Exception

(BLE001)


[warning] 39-39: Missing return type annotation for private function _prompt_of

(ANN202)


[warning] 47-47: Missing return type annotation for private function _trivial

(ANN202)


[warning] 56-56: Missing return type annotation for private function _is_local_route

(ANN202)


[warning] 60-60: Missing return type annotation for private function _route_local

(ANN202)


[error] 70-73: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.

(S310)


[error] 74-74: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.

(S310)


[warning] 76-76: Do not catch blind exception: Exception

(BLE001)


[warning] 82-82: Missing return type annotation for private function log_message

Add return type annotation: None

(ANN202)


[warning] 82-82: Missing type annotation for *a

(ANN002)


[warning] 85-85: Missing return type annotation for private function do_POST

Add return type annotation: None

(ANN202)


[warning] 90-90: Do not catch blind exception: Exception

(BLE001)


[error] 95-95: Multiple statements on one line (semicolon)

(E702)


[error] 106-106: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.

(S310)


[error] 106-106: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.

(S310)


[warning] 107-107: Do not catch blind exception: Exception

(BLE001)


[error] 113-113: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.

(S310)


[error] 113-113: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.

(S310)


[warning] 114-114: Do not catch blind exception: Exception

(BLE001)


[error] 115-115: Multiple statements on one line (semicolon)

(E702)


[error] 117-117: Multiple statements on one line (semicolon)

(E702)


[warning] 127-127: Do not catch blind exception: Exception

(BLE001)

🔍 Remote MCP Linear

Based on my searches, I found limited specific context about this particular PR in the Linear issues. Let me provide a summary of the relevant context found:

Additional Context Found

SDK Ecosystem and Public Release Planning

The PR aligns with broader WAVE public SDK strategy documented in Linear:

  • WAVE-23913: Tracks "@wave-av/sdk-typescript — public SDK ship" (R4 status requiring "full deprecation discipline per careful-operation-discipline.md")
  • WAVE-23912: Umbrella issue for "WAVE Ecosystem Expansion — partners + SDK + cross-vertical" with plans for @wave-av/* packages
  • WAVE-24383: Urgent followup identifying a need to fix "GA-overclaim" language in SDK package.json description (the repository claims "Official" status while the product is pre-GA), which is relevant for reviewing version bump accuracy and maturity language

Quality Gates for Public Release

  • WAVE-24376: "Phase 4: Pre-Publish Quality Gates" documents composable pre-publication checks for npm packages, including secrets scanning (gitleaks), SPDX license validation (no GPL-3+ in runtime deps), bundle size budgets, TypeScript coverage, and dev-dependency isolation

License and Dependency Considerations

The "open-core boundary" discussed in this PR's objectives explicitly excludes worker.ts and sdk/go. This separation aligns with careful API surface management. The PR introduces new public methods (savings(), subscription(), subscribe()) across all SDKs—this means reviewers should verify:

  1. Consistent signature across languages: Python, JavaScript, Rust, and Ruby all expose the same public methods with matching semantics
  2. License requirement enforcement: All three methods enforce that license is configured via a _lic() helper that raises if missing
  3. HTTP error handling: Explicit handling for 402 (payment required) and 401 (unauthorized) errors is now standardized

Missing Specific PR Tracking Issue

No Linear issue specifically tracks this sync PR (#2) or the dispatch-agents feature rollout. This may indicate the sync is infrastructure-driven (via scripts/sync-public.sh) rather than feature-tracked as a discrete Linear issue.

🔇 Additional comments (20)
BENCHMARKS.md (7)

32-32: Clarify "tool-calling 2/2" notation.

The phrase "tool-calling 2/2" is ambiguous. Does this mean 2 out of 2 tool-calling benchmarks passed, 100% accuracy, or something else? Consider expanding for clarity.


40-45: Add language identifier to the code block.

The fenced code block should specify a language for proper syntax highlighting and accessibility.


1-5: LGTM!


6-22: LGTM!


23-28: LGTM!


47-57: LGTM!


58-64: LGTM!

README.md (9)

106-106: Add language identifier to the code block.

The fenced code block should specify a language for proper rendering. Since this documents HTTP request format, consider http or text.


8-9: LGTM!


41-41: LGTM!


46-49: LGTM!


52-56: LGTM!


58-58: LGTM!


60-62: LGTM!


99-104: ⚡ Quick win

Proxy default port matches READMEsdk/python/wave_dispatch/proxy.py sets PORT = int(os.environ.get("WAVE_OAI_PORT", "8090")), so the default :8090 is correct (overridable via WAVE_OAI_PORT).


110-110: ⚡ Quick win

Verify the “up to 32 prompts” batch limit is enforced in code.
README.md claims batches of “up to 32 prompts”, but a repo-wide search for prompt/batch limit patterns in edge-router/worker.ts/sdk didn’t surface any explicit max prompts/batch-size constants or prompts.length validation; matches were limited to unrelated build artifacts containing “32” (e.g., espidf_time32, musl32_time64). Inspect the /embed handler’s batching logic to confirm it rejects/truncates at 32 (or update the README to the real limit).

sdk/python/wave_dispatch/__init__.py (2)

45-47: subscribe() still bypasses local license validation.

Line 47 still sends self.license directly instead of using the same local validation path as savings()/subscription().


64-70: HTTP exception handling consistency issue remains.

Lines 66/68/69 still raise without chaining, and network-layer failures are not normalized to the same error interface.

sdk/python/pyproject.toml (1)

7-8: LGTM!

Also applies to: 20-21

sdk/python/wave_dispatch/proxy.py (1)

22-28: LGTM!

Also applies to: 39-58, 131-140

Comment thread sdk/js/index.js Outdated
Comment on lines +23 to +27
savings() { return this._send(this.agents + "/ledger/summary?license=" + this._lic(), "GET"); }
/** This license's agent-subscription status (plan, quota, used, remaining, renews_at). */
subscription() { return this._send(this.agents + "/subscription/status?license=" + this._lic(), "GET"); }
/** Start/replace a programmatic subscription. plan: agent_starter | agent_pro | agent_scale. */
subscribe(plan) { return this._send(this.agents + "/subscription/create", "POST", { license: this.license, plan }); }
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Keep the license out of URLs and payloads.

_send() already authenticates with Authorization, so adding the same bearer to ...?license= and subscribe()'s JSON body widens where it can be logged or cached. That is especially risky for the JS client, which may run in browsers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdk/js/index.js` around lines 23 - 27, Remove inclusion of the license in
URLs and payloads for savings(), subscription(), and subscribe(): stop calling
this._lic() to append ?license= to the GET URLs and stop including { license:
this.license } in subscribe()'s POST body; rely on _send()'s Authorization
header for auth instead. Update savings() to call this._send(this.agents +
"/ledger/summary", "GET"), subscription() to call this._send(this.agents +
"/subscription/status", "GET"), and subscribe(plan) to call
this._send(this.agents + "/subscription/create", "POST", { plan }), ensuring no
other code paths still expose this._lic() or this.license in URLs or request
bodies.

Comment thread sdk/js/index.js Outdated
Comment thread sdk/python/wave_dispatch/cli.py
Comment on lines +69 to +77
try:
req = urllib.request.Request(
EDGE + "/", data=json.dumps({"prompt": _prompt_of(body)}).encode(),
headers={"content-type": "application/json", "authorization": "Bearer " + LICENSE},
method="POST")
d = json.loads(urllib.request.urlopen(req, timeout=15).read())
return _is_local_route(d.get("route", "")) and not bool(d.get("forward"))
except Exception:
return _trivial(body) # edge unreachable -> safe heuristic
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid a 15s classifier penalty on every fallback.

When EDGE is slow or unavailable, each licensed request blocks here for the full 15 seconds before _trivial(body) is even attempted. That makes a control-plane outage look like an application outage to interactive clients. Make this timeout much shorter and operator-configurable at minimum.

Suggested fix
+CLASSIFIER_TIMEOUT = float(os.environ.get("WAVE_PROXY_CLASSIFIER_TIMEOUT", "2"))
+
 def _route_local(body):
     """True -> serve from local Ollama. Hosted classifier when licensed, else a safe heuristic.
@@
-            d = json.loads(urllib.request.urlopen(req, timeout=15).read())
+            d = json.loads(urllib.request.urlopen(req, timeout=CLASSIFIER_TIMEOUT).read())
🧰 Tools
🪛 Ruff (0.15.14)

[error] 70-73: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.

(S310)


[error] 74-74: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.

(S310)


[warning] 76-76: Do not catch blind exception: Exception

(BLE001)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdk/python/wave_dispatch/proxy.py` around lines 69 - 77, The current call to
urllib.request.urlopen uses a hardcoded 15s timeout which blocks licensed
requests; replace this with an operator-configurable timeout (e.g.,
EDGE_TIMEOUT_SECONDS) with a low sensible default (e.g., 1s) and use that
variable in the urlopen(req, timeout=EDGE_TIMEOUT_SECONDS) call inside the try
block; keep the existing behavior of building the Request with EDGE, LICENSE,
_prompt_of(body) and falling back to _trivial(body) on any Exception, but ensure
the config is read once (env var or module-level constant) and validated
(fallback to default if missing/invalid) so slow/unreachable EDGE triggers the
fallback quickly.

Comment on lines +105 to +117
try:
resp = urllib.request.urlopen(urllib.request.Request(target, data=raw, headers=hdrs, method="POST"), timeout=600)
except Exception as e:
if local: # local failed -> fall through to upstream, never break the caller
try:
up_hdrs = {"Content-Type": "application/json"}
if auth:
up_hdrs["Authorization"] = auth
resp = urllib.request.urlopen(urllib.request.Request(UPSTREAM + self.path, data=raw, headers=up_hdrs, method="POST"), timeout=600)
except Exception as e2:
self.send_error(502, str(e2)); return
else:
self.send_error(502, str(e)); return
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

In Python's standard library, does urllib.request.urlopen(...) raise urllib.error.HTTPError for HTTP 4xx/5xx responses, and does that HTTPError object expose the original status code, headers, and response body so a proxy can pass them through unchanged?

💡 Result:

Yes, urllib.request.urlopen raises a urllib.error.HTTPError for HTTP 4xx and 5xx responses [1][2]. By default, the library treats these status codes as exceptional cases and triggers the error handling mechanism [3][4]. The resulting HTTPError object does expose the original response data, allowing a proxy or client to inspect and pass through the details [1][5]. Specifically, it provides the following attributes and methods: - Status Code: Accessible via the.code attribute [1][2]. - Headers: Accessible via the.headers attribute (which is an instance of email.message.Message or a similar HTTP message object, often also accessible via.hdrs) [5][6][7]. - Response Body: Accessible via the.read method [1][5][6]. Because HTTPError is a subclass of urllib.error.URLError and also acts as a file-like object, you can treat it similarly to the response object returned for successful requests when handling the exception [5][8]. To prevent this behavior for specific status codes (or altogether), you can modify the default handlers by removing the HTTPErrorProcessor from your opener instance [9].

Citations:


Pass through upstream HTTPError instead of mapping upstream 4xx/5xx to 502 (sdk/python/wave_dispatch/proxy.py lines 105-117)

urllib.request.urlopen(...) raises urllib.error.HTTPError for HTTP 4xx/5xx, and the exception provides the original status via .code, headers via .headers, and response payload via .read(). The current except Exception converts those upstream errors into self.send_error(502, ...), losing the real status and error body/headers. Catch urllib.error.HTTPError for both the primary request and the local→upstream fallback, forward e.code, e.headers.get("Content-Type", "application/json"), and e.read() to the client, and keep except Exception for non-HTTPError failures (still returning 502).

🧰 Tools
🪛 Ruff (0.15.14)

[error] 106-106: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.

(S310)


[error] 106-106: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.

(S310)


[warning] 107-107: Do not catch blind exception: Exception

(BLE001)


[error] 113-113: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.

(S310)


[error] 113-113: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.

(S310)


[warning] 114-114: Do not catch blind exception: Exception

(BLE001)


[error] 115-115: Multiple statements on one line (semicolon)

(E702)


[error] 117-117: Multiple statements on one line (semicolon)

(E702)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdk/python/wave_dispatch/proxy.py` around lines 105 - 117, The current
blanket except hides upstream HTTP errors; update the try/except around
urllib.request.urlopen (both the initial request and the local->upstream
fallback that uses UPSTREAM + self.path) to explicitly catch
urllib.error.HTTPError first, and in that handler forward the original status,
headers and body to the client (use the error's .code for status,
.headers.get("Content-Type", "application/json") for content-type and .read()
for the body) instead of mapping to 502; keep a separate except Exception block
to handle non-HTTPError failures and still return 502.

Comment on lines +19 to +31
def route(prompt) = send_req(:post, @endpoint + "/", { prompt: prompt })

# Classify and run on the edge if your plan allows it.
def execute(prompt) = post({ prompt: prompt, execute: true })
def execute(prompt) = send_req(:post, @endpoint + "/", { prompt: prompt, execute: true })

# This license's savings ledger (decisions, saved_usd, saved_pct, ...). Requires a license.
def savings = send_req(:get, "#{@agents}/ledger/summary?license=#{lic}")

# This license's agent-subscription status.
def subscription = send_req(:get, "#{@agents}/subscription/status?license=#{lic}")

# Start/replace a programmatic subscription (plan: agent_starter | agent_pro | agent_scale).
def subscribe(plan) = send_req(:post, "#{@agents}/subscription/create", { license: @license, plan: plan })
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '## Declared Ruby support\n'
rg -n 'required_ruby_version|TargetRubyVersion|^\s*ruby\s+["'"'"'][0-9.]+' -S

printf '\n## Gemspec files\n'
fd -HI '.*\.gemspec$' -x sh -c 'echo "### $1"; sed -n "1,220p" "$1"' sh {}

printf '\n## Gemfile\n'
fd -HI '^Gemfile$' -x sh -c 'echo "### $1"; sed -n "1,220p" "$1"' sh {}

printf '\n## RuboCop config\n'
fd -HI '^\.rubocop.*\.yml$' -x sh -c 'echo "### $1"; sed -n "1,220p" "$1"' sh {}

Repository: wave-av/dispatch-edge

Length of output: 4722


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="sdk/ruby/lib/wave_dispatch.rb"

echo "== File: $FILE =="
ls -la "$FILE" || true
echo

echo "== Endless method defs around lines 1-80 (to show route/execute/savings/subscription/subscribe) =="
nl -ba "$FILE" | sed -n '1,120p'

echo
echo "== Locate send_req and inspect its full implementation =="
rg -n "def\s+send_req\b|send_req\(" "$FILE"
# Print a reasonable window around send_req definition
SEND_LINE=$(rg -n "def\s+send_req\b" "$FILE" | head -n1 | cut -d: -f1)
if [[ -n "${SEND_LINE:-}" ]]; then
  START=$((SEND_LINE-20))
  END=$((SEND_LINE+120))
  nl -ba "$FILE" | sed -n "${START},${END}p"
fi

echo
echo "== Check where license is inserted (query string vs body) =="
rg -n "license=|\\{[^}]*license:|`@license`" "$FILE"

Repository: wave-av/dispatch-edge

Length of output: 348


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="sdk/ruby/lib/wave_dispatch.rb"

echo "== File: $FILE =="
ls -la "$FILE" || true
echo

echo "== Lines 1-120 (to show route/execute/savings/subscription/subscribe) =="
awk '{printf "%5d  %s\n", NR, $0}' "$FILE" | sed -n '1,120p'

echo
echo "== Locate send_req and inspect its full implementation =="
rg -n "def\s+send_req\b" "$FILE" || true
SEND_LINE=$(rg -n "def\s+send_req\b" "$FILE" | head -n1 | cut -d: -f1 || true)
if [[ -n "${SEND_LINE:-}" ]]; then
  START=$((SEND_LINE-20))
  END=$((SEND_LINE+200))
  awk '{printf "%5d  %s\n", NR, $0}' "$FILE" | sed -n "${START},${END}p"
fi

echo
echo "== Where license is inserted (query string vs body vs headers) =="
rg -n "license=|`@license`|\{[^}]*license:|Authorization" "$FILE" || true

echo
echo "== Check JSON.parse and non-2xx handling in send_req =="
# show any JSON.parse occurrences plus a small window around them
rg -n "JSON\.parse" "$FILE" || true
PARSE_LINE=$(rg -n "JSON\.parse" "$FILE" | head -n1 | cut -d: -f1 || true)
if [[ -n "${PARSE_LINE:-}" ]]; then
  START=$((PARSE_LINE-20))
  END=$((PARSE_LINE+60))
  awk '{printf "%5d  %s\n", NR, $0}' "$FILE" | sed -n "${START},${END}p"
fi

Repository: wave-av/dispatch-edge

Length of output: 6418


Declare Ruby 3+ or refactor out endless method syntax

  • sdk/ruby/lib/wave_dispatch.rb (lines 19-31) uses endless defs (def ... = ...), which require Ruby 3.0+; however sdk/ruby/wave_dispatch.gemspec sets s.required_ruby_version = ">= 2.7", causing a Ruby 2.7 load-time syntax error.
  • send_req (sdk/ruby/lib/wave_dispatch.rb lines 40-49) only raises on 401/402 and then unconditionally JSON.parse(res.body) for all other non-2xx responses.
  • license is duplicated across query string (savings, subscription), request body (subscribe), and Authorization header (send_req), increasing exposure/log surface and making credential handling inconsistent.
🧰 Tools
🪛 RuboCop (1.86.2)

[fatal] 19-19: unexpected token tEQL
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)

(Lint/Syntax)


[fatal] 22-22: unexpected token tEQL
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)

(Lint/Syntax)


[fatal] 25-25: unexpected token tEQL
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)

(Lint/Syntax)


[fatal] 28-28: unexpected token tEQL
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)

(Lint/Syntax)


[fatal] 31-31: unexpected token tEQL
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)

(Lint/Syntax)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdk/ruby/lib/wave_dispatch.rb` around lines 19 - 31, The file uses Ruby 3+
endless method syntax in route, execute, savings, subscription and subscribe
which breaks under the gemspec's Ruby >= 2.7 constraint; convert those endless
defs to regular multi-line method definitions (def ...; return ...; end) or
update the gemspec to require Ruby >= 3.0. Update send_req to only
JSON.parse(res.body) for successful 2xx responses and to raise a useful error
(including status and body) for other non-2xx responses instead of
unconditionally parsing. Consolidate license handling by removing duplicated
license parameters from savings/subscription query strings and subscribe request
body and instead rely on the existing Authorization header usage in send_req
(or, if an explicit license value is required by API, pass it consistently via a
single method parameter and never duplicate into both header and query/body);
adjust savings, subscription, and subscribe to use the chosen consistent
approach.

Comment on lines +25 to +31
def savings = send_req(:get, "#{@agents}/ledger/summary?license=#{lic}")

# This license's agent-subscription status.
def subscription = send_req(:get, "#{@agents}/subscription/status?license=#{lic}")

# Start/replace a programmatic subscription (plan: agent_starter | agent_pro | agent_scale).
def subscribe(plan) = send_req(:post, "#{@agents}/subscription/create", { license: @license, plan: plan })
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Keep the license out of query strings and request bodies.

send_req already sends the bearer in Authorization, so savings/subscription leaking it into the URL and subscribe repeating it in JSON just widens the places that secret can be logged.

🧰 Tools
🪛 RuboCop (1.86.2)

[fatal] 25-25: unexpected token tEQL
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)

(Lint/Syntax)


[fatal] 28-28: unexpected token tEQL
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)

(Lint/Syntax)


[fatal] 31-31: unexpected token tEQL
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)

(Lint/Syntax)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdk/ruby/lib/wave_dispatch.rb` around lines 25 - 31, The methods savings,
subscription, and subscribe are including the license in URLs/bodies which leaks
the bearer; update them to stop passing license in query strings or JSON and
instead rely on send_req's existing Authorization handling: change savings to
call send_req(:get, "#{`@agents`}/ledger/summary") and subscription to call
send_req(:get, "#{`@agents`}/subscription/status"), and change subscribe(plan) to
call send_req(:post, "#{`@agents`}/subscription/create", { plan: plan }) (i.e.,
remove `@license/license` from the body); also ensure no other code in these
methods logs the license before send_req is invoked.

Comment thread sdk/ruby/lib/wave_dispatch.rb
Comment thread sdk/rust/src/lib.rs
Comment on lines +33 to +45
pub fn savings(&self) -> Result<Value, Box<dyn Error>> {
self.get(&format!("{}/ledger/summary?license={}", self.agents, self.lic()?))
}

/// This license's agent-subscription status.
pub fn subscription(&self) -> Result<Value, Box<dyn Error>> {
self.get(&format!("{}/subscription/status?license={}", self.agents, self.lic()?))
}

/// Start/replace a programmatic subscription (plan: agent_starter|agent_pro|agent_scale).
pub fn subscribe(&self, plan: &str) -> Result<Value, Box<dyn Error>> {
self.post(&format!("{}/subscription/create", self.agents),
json!({ "license": self.license, "plan": plan }))
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Don't put the bearer license in the URL.

auth() already sets Authorization, but savings()/subscription() also append the token to the query string and subscribe() repeats it in the JSON body. That leaks the secret into more logs and telemetry, and Rust currently interpolates the query value raw.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdk/rust/src/lib.rs` around lines 33 - 45, The savings(), subscription(), and
subscribe() methods are leaking the bearer license by including it in query
params and request bodies; remove the license interpolation from the URL in
savings() and subscription() (stop appending ?license=...), and remove "license"
from the JSON body in subscribe(), relying on the existing auth() /
Authorization header already set by the client instead; update calls to
self.get(...) and self.post(...) in those functions to omit the license
parameter and make sure auth() is invoked where requests are prepared so the
token is sent only via the Authorization header.

Comment thread sdk/rust/src/lib.rs
Comment on lines +43 to +50
pub fn subscribe(&self, plan: &str) -> Result<Value, Box<dyn Error>> {
self.post(&format!("{}/subscription/create", self.agents),
json!({ "license": self.license, "plan": plan }))
}

fn lic(&self) -> Result<String, Box<dyn Error>> {
self.license.clone().ok_or_else(|| "dispatch: a license is required for savings()/subscription()".into())
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Make subscribe() enforce the same local license precondition as the other sidecar methods.

savings() and subscription() fail locally through lic(), but subscribe() sends {"license":null} when the client is unlicensed and only discovers the problem after the request is sent.

Suggested fix
     pub fn subscribe(&self, plan: &str) -> Result<Value, Box<dyn Error>> {
+        self.lic()?;
         self.post(&format!("{}/subscription/create", self.agents),
                   json!({ "license": self.license, "plan": plan }))
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdk/rust/src/lib.rs` around lines 43 - 50, subscribe currently sends the raw
self.license (which can be None) instead of enforcing the local license
precondition; change subscribe to call the existing lic() helper (e.g., let
license = self.lic()?) and use that returned String in the JSON payload so
subscribe fails locally like savings()/subscription() and propagates the error
rather than sending {"license": null}.

@yakimoto yakimoto force-pushed the auto/sync-public branch 6 times, most recently from b3b1dde to ce0e7c7 Compare May 27, 2026 23:55
@yakimoto
Copy link
Copy Markdown
Contributor Author

@coderabbitai review — the ReDoS finding is fixed in upstream 0.4.3; please re-review against the latest sync.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 28, 2026

@yakimoto — noted, thanks for the heads-up. Kicking off a fresh review with the upstream 0.4.3 ReDoS fix in mind. I'll re-evaluate all changed files accordingly.

Re-reviewing with the note that the ReDoS vulnerability previously flagged has been resolved in upstream 0.4.3; please assess all changed files (Python proxy, JS/Ruby/Rust SDKs, BENCHMARKS.md, README) against the latest sync state.

✅ Actions performed

Full review triggered.

coderabbitai[bot]
coderabbitai Bot previously requested changes May 28, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

♻️ Duplicate comments (6)
BENCHMARKS.md (2)

40-45: 🧹 Nitpick | 🔵 Trivial

Add language identifier to the code block.

The fenced code block containing the savings formulas should specify a language identifier for proper rendering and accessibility. Since these are plain-text formulas, use text.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@BENCHMARKS.md` around lines 40 - 45, The fenced code block containing the
formulas (lines with "frontier_only", "with_dispatch", and "savings") needs a
language identifier for proper rendering; change the opening fence from ``` to
```text so the block reads ```text ... ``` to mark it as plain-text and improve
accessibility and rendering.

32-32: 🧹 Nitpick | 🔵 Trivial

Clarify "tool-calling 2/2" notation.

The phrase "tool-calling 2/2" remains ambiguous. Does this mean 2 out of 2 tool-calling benchmarks passed, 100% accuracy on tool-calling tasks, or something else? Consider expanding for clarity.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@BENCHMARKS.md` at line 32, Replace the ambiguous "tool-calling 2/2" notation
in the `qwen2.5` benchmark line with an explicit phrase that states what it
means (for example "tool-calling: 2 of 2 tasks successful (100%)" or
"tool-calling: 2/2 benchmarks passed"), so update the `qwen2.5` line to include
the clarified wording and optionally a parenthetical explaining the denominator
(tasks vs. benchmarks) to remove ambiguity.
README.md (1)

106-106: 🧹 Nitpick | 🔵 Trivial

Add language identifier to the code block.

The fenced code block should specify a language identifier for proper rendering. Since this documents HTTP request format, use http or text.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` at line 106, The fenced code block in README.md lacks a language
identifier, so update the triple-backtick fence at the reported snippet to
include a language tag (for example use "http" or "text") so the HTTP request
example renders correctly; locate the relevant code block in README.md and
change the opening ``` to ```http (or ```text) accordingly.
sdk/js/index.js (1)

34-38: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Enforce license precondition in subscribe() before sending the request.

subscribe() should fail locally like savings() and subscription() when no license is configured.

Suggested fix
-  subscribe(plan) { return this._send(this.agents + "/subscription/create", "POST", { license: this.license, plan }); }
+  subscribe(plan) {
+    this._lic(); // fail fast if missing
+    return this._send(this.agents + "/subscription/create", "POST", { license: this.license, plan });
+  }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdk/js/index.js` around lines 34 - 38, subscribe() does not enforce the
license precondition like savings()/subscription(); call the existing _lic()
helper (or perform the same check) at the start of subscribe() to throw the same
Error("dispatch: a license is required for savings()/subscription()") when
this.license is missing, before calling this._send(this.agents +
"/subscription/create", ...), so the method fails locally rather than sending
the request without a license.
sdk/rust/src/lib.rs (1)

43-46: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

subscribe() should enforce license locally before request dispatch.

It currently allows an unlicensed client to send a request and fail remotely instead of failing immediately.

Suggested fix
     pub fn subscribe(&self, plan: &str) -> Result<Value, Box<dyn Error>> {
+        self.lic()?;
         self.post(&format!("{}/subscription/create", self.agents),
                   json!({ "license": self.license, "plan": plan }))
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdk/rust/src/lib.rs` around lines 43 - 46, The subscribe method should
validate the client license locally before making the network call: inside pub
fn subscribe(&self, plan: &str) -> Result<Value, Box<dyn Error>> check that
self.license is present/non-empty (or otherwise valid) and return an immediate
Err boxed error if not, instead of calling self.post; only call
self.post(&format!("{}/subscription/create", self.agents), json!({ "license":
self.license, "plan": plan })) when the license check passes. Use the same
Result<Value, Box<dyn Error>> error type (e.g., return a boxed error via .into()
or Box::from) so the signature remains unchanged.
sdk/ruby/lib/wave_dispatch.rb (1)

20-32: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Ruby 3 endless defs conflict with Ruby 2.7 compatibility expectations.

These method definitions are not parseable on Ruby 2.7 (as shown by the static-analysis parser failures). Either raise the supported Ruby floor to 3.0+ or convert to standard def ... end.

#!/bin/bash
set -euo pipefail

echo "== Endless defs in client =="
nl -ba sdk/ruby/lib/wave_dispatch.rb | sed -n '18,34p'

echo
echo "== Declared Ruby version support (if present) =="
fd -HI 'wave_dispatch.gemspec' -x sh -c 'echo "### $1"; rg -n "required_ruby_version" "$1" || true' sh {}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdk/ruby/lib/wave_dispatch.rb` around lines 20 - 32, The file uses Ruby 3
"endless method" syntax for methods route, execute, savings, subscription, and
subscribe which breaks Ruby 2.7 parsing; either update the gemspec to require
Ruby >= 3.0 or (preferred for compatibility) rewrite those methods to classic
multi-line defs with explicit end blocks (e.g., change "def route(prompt) = ..."
to "def route(prompt)\n  send_req(...)\nend" for route, execute, savings,
subscription, and subscribe), ensuring you reference the same instance variables
(`@endpoint`, `@agents`, `@license`) and method send_req exactly as in the diff.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@sdk/python/wave_dispatch/__init__.py`:
- Around line 18-19: The constructor signature in __init__ of the WaveDispatch
class (function __init__) uses PEP 604 union syntax (`str | None`) which breaks
on Python 3.8/3.9; change the type hints to use typing.Optional (e.g. `license:
Optional[str]` and `agents_endpoint: Optional[str]`), add `from typing import
Optional` at the top of the module if not already imported, and update any other
occurrences in this file using `| None` to the Optional[...] form to maintain
compatibility with requires-python=">=3.8".

In `@sdk/ruby/lib/wave_dispatch.rb`:
- Around line 12-17: The initialize method may produce URLs with double slashes
when callers pass endpoint or agents_endpoint with trailing slashes; update
initialize (the constructor) to normalize inputs by removing any trailing slash
from the provided endpoint and agents_endpoint before assigning to `@endpoint` and
`@agents` (e.g., use a trailing-slash trim like rstrip or chomp on the endpoint
and agents_endpoint parameters) so subsequent path concatenation never yields
'//' while preserving the defaults.
- Around line 32-39: The subscribe method currently calls send_req with license
potentially nil; change it to fail fast by validating/encoding the license
before the network call—call the existing lic helper (or otherwise check
`@license` and URI.encode_www_form_component(`@license`)) inside subscribe so it
raises the existing error when missing and include the encoded license value in
the payload sent by send_req (reference methods: subscribe and lic).

In `@sdk/rust/src/lib.rs`:
- Around line 17-19: The endpoint and agents base URLs are taken directly from
DISPATCH_ENDPOINT and WAVE_AGENTS_ENDPOINT and may include trailing slashes,
which can cause double-slash request URLs; update the initialization of the
endpoint and agents fields in lib.rs to normalize values by trimming any
trailing '/' (use trim_end_matches('/') on the env var string before converting
into the field) so both fields always store a slash-less base URL.

---

Duplicate comments:
In `@BENCHMARKS.md`:
- Around line 40-45: The fenced code block containing the formulas (lines with
"frontier_only", "with_dispatch", and "savings") needs a language identifier for
proper rendering; change the opening fence from ``` to ```text so the block
reads ```text ... ``` to mark it as plain-text and improve accessibility and
rendering.
- Line 32: Replace the ambiguous "tool-calling 2/2" notation in the `qwen2.5`
benchmark line with an explicit phrase that states what it means (for example
"tool-calling: 2 of 2 tasks successful (100%)" or "tool-calling: 2/2 benchmarks
passed"), so update the `qwen2.5` line to include the clarified wording and
optionally a parenthetical explaining the denominator (tasks vs. benchmarks) to
remove ambiguity.

In `@README.md`:
- Line 106: The fenced code block in README.md lacks a language identifier, so
update the triple-backtick fence at the reported snippet to include a language
tag (for example use "http" or "text") so the HTTP request example renders
correctly; locate the relevant code block in README.md and change the opening
``` to ```http (or ```text) accordingly.

In `@sdk/js/index.js`:
- Around line 34-38: subscribe() does not enforce the license precondition like
savings()/subscription(); call the existing _lic() helper (or perform the same
check) at the start of subscribe() to throw the same Error("dispatch: a license
is required for savings()/subscription()") when this.license is missing, before
calling this._send(this.agents + "/subscription/create", ...), so the method
fails locally rather than sending the request without a license.

In `@sdk/ruby/lib/wave_dispatch.rb`:
- Around line 20-32: The file uses Ruby 3 "endless method" syntax for methods
route, execute, savings, subscription, and subscribe which breaks Ruby 2.7
parsing; either update the gemspec to require Ruby >= 3.0 or (preferred for
compatibility) rewrite those methods to classic multi-line defs with explicit
end blocks (e.g., change "def route(prompt) = ..." to "def route(prompt)\n 
send_req(...)\nend" for route, execute, savings, subscription, and subscribe),
ensuring you reference the same instance variables (`@endpoint`, `@agents`,
`@license`) and method send_req exactly as in the diff.

In `@sdk/rust/src/lib.rs`:
- Around line 43-46: The subscribe method should validate the client license
locally before making the network call: inside pub fn subscribe(&self, plan:
&str) -> Result<Value, Box<dyn Error>> check that self.license is
present/non-empty (or otherwise valid) and return an immediate Err boxed error
if not, instead of calling self.post; only call
self.post(&format!("{}/subscription/create", self.agents), json!({ "license":
self.license, "plan": plan })) when the license check passes. Use the same
Result<Value, Box<dyn Error>> error type (e.g., return a boxed error via .into()
or Box::from) so the signature remains unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: fbaa55de-3b5b-43fc-99e7-401aafaa5e6c

📥 Commits

Reviewing files that changed from the base of the PR and between dee21cf and ce0e7c7.

📒 Files selected for processing (9)
  • BENCHMARKS.md
  • README.md
  • sdk/js/index.js
  • sdk/python/pyproject.toml
  • sdk/python/wave_dispatch/__init__.py
  • sdk/python/wave_dispatch/cli.py
  • sdk/python/wave_dispatch/proxy.py
  • sdk/ruby/lib/wave_dispatch.rb
  • sdk/rust/src/lib.rs
📜 Review details
🧰 Additional context used
🪛 markdownlint-cli2 (0.22.1)
BENCHMARKS.md

[warning] 40-40: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

README.md

[warning] 106-106: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🪛 RuboCop (1.86.2)
sdk/ruby/lib/wave_dispatch.rb

[fatal] 20-20: unexpected token tEQL
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)

(Lint/Syntax)


[fatal] 23-23: unexpected token tEQL
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)

(Lint/Syntax)


[fatal] 26-26: unexpected token tEQL
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)

(Lint/Syntax)


[fatal] 29-29: unexpected token tEQL
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)

(Lint/Syntax)


[fatal] 32-32: unexpected token tEQL
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)

(Lint/Syntax)

🪛 Ruff (0.15.14)
sdk/python/wave_dispatch/__init__.py

[warning] 18-18: Missing return type annotation for special method __init__

Add return type annotation: None

(ANN204)


[error] 18-18: Function argument license is shadowing a Python builtin

(A002)


[warning] 48-48: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 53-53: Avoid specifying long messages outside the exception class

(TRY003)


[error] 64-64: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.

(S310)


[warning] 68-68: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 70-70: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 71-71: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 73-73: Avoid specifying long messages outside the exception class

(TRY003)


[error] 76-76: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.

(S310)


[error] 79-79: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.

(S310)

sdk/python/wave_dispatch/proxy.py

[warning] 31-31: Missing return type annotation for private function _log

Add return type annotation: None

(ANN202)


[error] 35-36: try-except-pass detected, consider logging the exception

(S110)


[warning] 35-35: Do not catch blind exception: Exception

(BLE001)


[warning] 39-39: Missing return type annotation for private function _prompt_of

(ANN202)


[warning] 47-47: Missing return type annotation for private function _trivial

(ANN202)


[warning] 56-56: Missing return type annotation for private function _is_local_route

(ANN202)


[warning] 60-60: Missing return type annotation for private function _route_local

(ANN202)


[error] 70-73: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.

(S310)


[error] 74-74: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.

(S310)


[warning] 76-76: Do not catch blind exception: Exception

(BLE001)


[warning] 82-82: Missing return type annotation for private function log_message

Add return type annotation: None

(ANN202)


[warning] 82-82: Missing type annotation for *a

(ANN002)


[warning] 85-85: Too many branches (13 > 12)

(PLR0912)


[warning] 85-85: Missing return type annotation for private function do_POST

Add return type annotation: None

(ANN202)


[warning] 92-92: Do not catch blind exception: Exception

(BLE001)


[error] 97-97: Multiple statements on one line (semicolon)

(E702)


[error] 109-109: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.

(S310)


[error] 109-109: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.

(S310)


[warning] 110-110: Do not catch blind exception: Exception

(BLE001)


[error] 116-116: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.

(S310)


[error] 116-116: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.

(S310)


[warning] 117-117: Do not catch blind exception: Exception

(BLE001)


[error] 118-118: Multiple statements on one line (semicolon)

(E702)


[error] 120-120: Multiple statements on one line (semicolon)

(E702)


[warning] 130-130: Do not catch blind exception: Exception

(BLE001)

🔍 Remote MCP Linear

Additional relevant facts for reviewing PR #2 (sync: mirror open-core subset)

  • This PR intersects the organization’s SDK public‑release work tracked under WAVE-23912 (umbrella for WAVE Ecosystem Expansion).

  • There is an active issue specifically for public TypeScript SDK publishing discipline (relevant to package metadata and publish process): WAVE-23913 (public SDK ship; R4/npm publish — requires deprecation/publish discipline).

  • Pre-publish quality‑gate automation exists and lists concrete checks that should be applied to packages introduced/updated by this PR (relevant to version bumps, new CLI entrypoints, and published SDKs): WAVE-24376 — “Phase 4: Pre-Publish Quality Gates”. Key items (from the issue summary):

  • An urgent followup exists to avoid “GA” overclaim language in SDK package metadata (relevant to the pyproject/package description version bump and README language): WAVE-24383 — Fix GA‑overclaim in @wave‑av/sdk package.json description. Reviewers should ensure package descriptions and README language do not claim “Official”/GA where pre‑GA status applies.

  • Practical review implications drawn from the above Linear items (actionable checklist for reviewers):

    • Verify the new CLI/script/entrypoint (python dispatch console script) and bumped package versions meet the Phase‑4 pre-publish checks (secrets scan, SPDX licensing, bundle size/type coverage).
    • Confirm package metadata & README wording do not assert GA/“Official” status (address WAVE-24383) before any public publish.
    • Validate API/signature consistency for the newly added agents-sidecar methods (savings(), subscription(), subscribe(plan)) across SDKs (Python/JS/Ruby/Rust) as the sync introduces the same surface expected by the ecosystem plan (WAVE-23913 / WAVE-23912).

Sources/tools used:

  • Linear issue lookups: WAVE-23912, WAVE-23913, WAVE-24376, WAVE-24383 — via Linear_list_issues.
🔇 Additional comments (16)
BENCHMARKS.md (4)

1-5: LGTM!


6-21: LGTM!


23-27: LGTM!


58-63: LGTM!

README.md (5)

8-9: LGTM!


40-59: LGTM!


60-66: LGTM!


99-104: LGTM!


110-110: LGTM!

sdk/js/index.js (1)

4-33: LGTM!

Also applies to: 40-49

sdk/ruby/lib/wave_dispatch.rb (1)

6-10: LGTM!

Also applies to: 41-52

sdk/rust/src/lib.rs (1)

9-10: LGTM!

Also applies to: 24-40, 48-67

sdk/python/wave_dispatch/cli.py (1)

5-24: Invalid CLI usage still exits successfully.

This is still the same issue raised earlier: unknown subcommands fall through to the help banner and return None, so the generated console script exits with code 0 instead of signaling misuse.

sdk/python/wave_dispatch/proxy.py (2)

68-77: The classifier timeout is still hardcoded to 15 seconds.

This remains the same hot-path latency issue noted earlier: every licensed request can block here for up to 15 seconds before _trivial(body) is allowed to take over when the control plane is slow or unavailable.


108-123: Upstream 4xx/5xx are still being flattened to 502.

This is still the previously flagged proxying bug: the blanket except Exception catches urllib HTTP failures and turns them into send_error(502, ...), so callers lose the real upstream status, headers, and response body.

In Python's standard library, does `urllib.request.urlopen(...)` raise `urllib.error.HTTPError` for HTTP 4xx/5xx responses, and does that exception expose the original status code, headers, and response body?
sdk/python/pyproject.toml (1)

7-8: LGTM!

Also applies to: 20-21

Comment thread sdk/python/wave_dispatch/__init__.py Outdated
Comment on lines +12 to 17
def initialize(license = ENV["WAVE_LICENSE"], endpoint: "https://dispatch.wave.online",
agents_endpoint: ENV["WAVE_AGENTS_ENDPOINT"] || "https://dispatch-agents.wave.online")
@license = license
@endpoint = endpoint
@agents = agents_endpoint
end
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Normalize endpoint inputs to avoid accidental // request URLs.

If callers pass endpoint or agents_endpoint with a trailing slash, current concatenation can generate double-slash paths.

Suggested fix
-      `@endpoint` = endpoint
-      `@agents` = agents_endpoint
+      `@endpoint` = endpoint.sub(%r{/+\z}, "")
+      `@agents` = agents_endpoint.sub(%r{/+\z}, "")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdk/ruby/lib/wave_dispatch.rb` around lines 12 - 17, The initialize method
may produce URLs with double slashes when callers pass endpoint or
agents_endpoint with trailing slashes; update initialize (the constructor) to
normalize inputs by removing any trailing slash from the provided endpoint and
agents_endpoint before assigning to `@endpoint` and `@agents` (e.g., use a
trailing-slash trim like rstrip or chomp on the endpoint and agents_endpoint
parameters) so subsequent path concatenation never yields '//' while preserving
the defaults.

Comment on lines +32 to +39
def subscribe(plan) = send_req(:post, "#{@agents}/subscription/create", { license: @license, plan: plan })

private

def post(body)
uri = URI(@endpoint + "/")
req = Net::HTTP::Post.new(uri, "content-type" => "application/json")
def lic
raise "dispatch: a license is required for savings/subscription" unless @license
URI.encode_www_form_component(@license)
end
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

subscribe should fail fast when license is missing.

Right now it can send license: nil and fail only after a network call.

Suggested fix
-    def subscribe(plan) = send_req(:post, "#{`@agents`}/subscription/create", { license: `@license`, plan: plan })
+    def subscribe(plan)
+      lic
+      send_req(:post, "#{`@agents`}/subscription/create", { license: `@license`, plan: plan })
+    end
🧰 Tools
🪛 RuboCop (1.86.2)

[fatal] 32-32: unexpected token tEQL
(Using Ruby 2.7 parser; configure using TargetRubyVersion parameter, under AllCops)

(Lint/Syntax)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdk/ruby/lib/wave_dispatch.rb` around lines 32 - 39, The subscribe method
currently calls send_req with license potentially nil; change it to fail fast by
validating/encoding the license before the network call—call the existing lic
helper (or otherwise check `@license` and URI.encode_www_form_component(`@license`))
inside subscribe so it raises the existing error when missing and include the
encoded license value in the payload sent by send_req (reference methods:
subscribe and lic).

Comment thread sdk/rust/src/lib.rs
Comment on lines 17 to 19
endpoint: std::env::var("DISPATCH_ENDPOINT").unwrap_or_else(|_| "https://dispatch.wave.online".into()),
agents: std::env::var("WAVE_AGENTS_ENDPOINT").unwrap_or_else(|_| "https://dispatch-agents.wave.online".into()),
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Trim trailing slashes on configured base URLs.

Without normalization, endpoint environment values ending with / can produce double-slash request URLs.

Suggested fix
-            endpoint: std::env::var("DISPATCH_ENDPOINT").unwrap_or_else(|_| "https://dispatch.wave.online".into()),
-            agents: std::env::var("WAVE_AGENTS_ENDPOINT").unwrap_or_else(|_| "https://dispatch-agents.wave.online".into()),
+            endpoint: std::env::var("DISPATCH_ENDPOINT")
+                .unwrap_or_else(|_| "https://dispatch.wave.online".into())
+                .trim_end_matches('/')
+                .to_string(),
+            agents: std::env::var("WAVE_AGENTS_ENDPOINT")
+                .unwrap_or_else(|_| "https://dispatch-agents.wave.online".into())
+                .trim_end_matches('/')
+                .to_string(),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
endpoint: std::env::var("DISPATCH_ENDPOINT").unwrap_or_else(|_| "https://dispatch.wave.online".into()),
agents: std::env::var("WAVE_AGENTS_ENDPOINT").unwrap_or_else(|_| "https://dispatch-agents.wave.online".into()),
}
endpoint: std::env::var("DISPATCH_ENDPOINT")
.unwrap_or_else(|_| "https://dispatch.wave.online".into())
.trim_end_matches('/')
.to_string(),
agents: std::env::var("WAVE_AGENTS_ENDPOINT")
.unwrap_or_else(|_| "https://dispatch-agents.wave.online".into())
.trim_end_matches('/')
.to_string(),
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdk/rust/src/lib.rs` around lines 17 - 19, The endpoint and agents base URLs
are taken directly from DISPATCH_ENDPOINT and WAVE_AGENTS_ENDPOINT and may
include trailing slashes, which can cause double-slash request URLs; update the
initialization of the endpoint and agents fields in lib.rs to normalize values
by trimming any trailing '/' (use trim_end_matches('/') on the env var string
before converting into the field) so both fields always store a slash-less base
URL.

Automated by scripts/sync-public.sh — README, SDK READMEs, threat-model, LICENSE, wrangler example,
and the JS/Python/Rust/Ruby thin clients. worker.ts + sdk/go are curated separately (boundary).
@yakimoto yakimoto force-pushed the auto/sync-public branch from ce0e7c7 to d27c352 Compare May 28, 2026 13:34
@yakimoto
Copy link
Copy Markdown
Contributor Author

@coderabbitai review — wave-dispatch 0.4.4 just synced (commit on auto/sync-public). The flagged CR findings are addressed:

  • sdk/python/init.py: Optional[str] type hints (py3.8 compat for requires-python >=3.8)
  • sdk/python/cli.py: unknown command exits 2 (POSIX misuse) instead of silent 0
  • sdk/js/index.js: subscribe() fail-fast when no license configured
  • URLError + exception chaining + license validation (previously addressed in 0.4.3)
    Please re-review and clear if the findings are resolved. The remaining (license-in-URL on /ledger and /subscription) requires a coordinated WAVE-sidecar change and is tracked separately.

@yakimoto yakimoto dismissed coderabbitai[bot]’s stale review May 28, 2026 13:36

Addressed in wave-dispatch 0.4.4 (just synced to auto/sync-public): Optional[str] type hints for py3.8 compat, cli.py POSIX exit code, JS subscribe() fail-fast, URLError+chaining (0.4.3). Remaining items (license-in-URL on /ledger and /subscription) require a coordinated WAVE-sidecar contract change tracked separately. All 5 SDK registries verified at 0.4.4 (npm/PyPI/crates/RubyGems/Go).

@yakimoto yakimoto merged commit a158518 into master May 28, 2026
9 checks passed
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 28, 2026

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== sdk/python/wave_dispatch/__init__.py ==="
cat sdk/python/wave_dispatch/__init__.py

echo ""
echo "=== sdk/python/wave_dispatch/cli.py ==="
cat sdk/python/wave_dispatch/cli.py

echo ""
echo "=== sdk/js/index.js ==="
cat sdk/js/index.js

echo ""
echo "=== sdk/python/pyproject.toml ==="
cat sdk/python/pyproject.toml

echo ""
echo "=== sdk/ruby/lib/wave_dispatch.rb ==="
cat sdk/ruby/lib/wave_dispatch.rb

echo ""
echo "=== sdk/rust/src/lib.rs ==="
cat sdk/rust/src/lib.rs

echo ""
echo "=== sdk/python/wave_dispatch/proxy.py ==="
cat sdk/python/wave_dispatch/proxy.py

Length of output: 21821



@yakimoto — all four claimed fixes verified against the 0.4.4 files. Here's the outcome:


✅ Confirmed resolved

Finding File Status
Optional[str] type hints (py3.8 compat) sdk/python/wave_dispatch/__init__.py from typing import Optional present; both Optional[str] params in __init__
Unknown command exits 2 (POSIX misuse) sdk/python/wave_dispatch/cli.py if args: sys.exit(2) at end of main()
subscribe() fail-fast when no license sdk/js/index.js if (!this.license) throw new Error(...) before the POST
URLError handling + exception chaining + license validation sdk/python/wave_dispatch/__init__.py urllib.error.URLError caught with from e, _lic() guards, _send() chains correctly
ReDoS (stripTrailingSlashes) sdk/js/index.js ✅ Linear charCodeAt loop, no regex
License-in-URL (/ledger, /subscription) All SDKs 🕐 Deferred — acknowledged

🟡 Minor cross-SDK inconsistency (new, non-blocking)

Ruby and Rust subscribe() don't fail-fast on a missing license, unlike Python and JS which explicitly guard it:

  • Rubysubscribe(plan) calls send_req directly without going through lic; with no license set, it POSTs {license: nil, plan: ...} to the server (server returns 401, which is then raised — so not silent, but not a local error message either).
  • Rustsubscribe() serializes self.license (Option<String>) directly into the JSON body; None becomes JSON null, and lic() is never called.

This is consistent in outcome (server-side 401 is caught and raised) but inconsistent in developer experience. Worth a follow-up ticket alongside the license-in-URL work, or handled in the next sync. Not blocking this PR.


All flagged CR findings from 0.4.3 / 0.4.4 are cleared. The deferred license-in-URL item and the minor Ruby/Rust subscribe() inconsistency can travel together in the coordinated sidecar change you mentioned.

✅ Actions performed

Full review triggered.

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.

2 participants