sync: mirror open-core subset from private repo#2
Conversation
|
|
Warning Review limit reached
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 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 configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (9)
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds BENCHMARKS.md and README clarifications about routing, auditability, and savings; bumps Python package and adds a ChangesDocumentation for Transparency and Auditability
Python SDK and Local-First Proxy
JavaScript SDK
Ruby SDK
Rust SDK
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
Estimated Code Review Effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
✨ Simplify code
Comment |
ce6227e to
faf7b05
Compare
There was a problem hiding this comment.
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 winFix README “Pinned, attestable builds” wording to match
/statusimplementation
/status?format=jsonis publicly reachable and returns JSON containing aversionfield (README.mdsuggests using it).- The implementation sets
versiontoenv.CF_VERSION_METADATA?.idand falls back to"live"whenCF_VERSION_METADATAisn’t present (edge-router/worker.ts, aroundconst ver = ... || "live"andversion: 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
📒 Files selected for processing (2)
BENCHMARKS.mdREADME.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
- Verify the sync script integrity: Confirm that
scripts/sync-public.shcorrectly filters files and preserves intended exclusions (worker.ts, sdk/go) - Audit documentation accuracy: Validate that BENCHMARKS.md technical details and README architecture diagrams are current and accurate
- Check for sensitive data: Ensure no private credentials, internal IPs, or proprietary details leaked into documentation
- Verify link integrity: Confirm that references to
/pricing.jsonand other external artifacts are valid and accessible - 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 winEnsure
https://dispatch.wave.online/pricing.jsonis publicly accessible and returns valid JSON.
BENCHMARKS.md(Reproducibility, lines 58-64) relies on/pricing.jsonfor 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 winReferenced docs files exist in repo (README.md lines 46-49):
threat-model.md,edge-router/worker.ts, andedge-router/wrangler.example.tomlare present.
| ``` | ||
| 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 | ||
| ``` |
There was a problem hiding this comment.
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 -->
There was a problem hiding this comment.
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 winUpdate README pinned-build verification claim
https://dispatch.wave.online/status?format=jsondoes not return any commit/hash/sha field—current JSON includes onlyoperational,checks.edge_embed/classifier/licensing_kv,colo,version(e.g.,"live"), andcheckedtimestamp—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 winAdd 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.mdaround 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.5reads "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 theqwen2.5line 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 -->
| ## 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). |
There was a problem hiding this comment.
🛠️ 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.
| - `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.
763489a to
fc01d6a
Compare
There was a problem hiding this comment.
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
📒 Files selected for processing (6)
BENCHMARKS.mdREADME.mdsdk/python/pyproject.tomlsdk/python/wave_dispatch/__init__.pysdk/python/wave_dispatch/cli.pysdk/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-dispatchv0.2.0" but the code shows version0.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
fc01d6a to
25ff294
Compare
There was a problem hiding this comment.
Actionable comments posted: 10
♻️ Duplicate comments (1)
sdk/python/wave_dispatch/__init__.py (1)
18-23:⚠️ Potential issue | 🟠 Major | ⚡ Quick winRestrict configured endpoints to absolute HTTP(S) URLs before issuing requests.
Dispatch.__init__(lines 21-22) storesendpoint/agents_endpointfrom args/env without scheme checks, and those values are later used to buildurllib.request.Requestobjects and sent viaurllib.request.urlopen(e.g.,_sendlines 60-63,_postline 71-72,_getline 74-75). Validate that both are absolutehttp/httpsURLs before use.
- Also:
subscribe()forwardsself.licenseandplanwithout validation.- Also:
_send()convertsHTTPErrortoRuntimeErrorwithout 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
📒 Files selected for processing (9)
BENCHMARKS.mdREADME.mdsdk/js/index.jssdk/python/pyproject.tomlsdk/python/wave_dispatch/__init__.pysdk/python/wave_dispatch/cli.pysdk/python/wave_dispatch/proxy.pysdk/ruby/lib/wave_dispatch.rbsdk/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:
- Consistent signature across languages: Python, JavaScript, Rust, and Ruby all expose the same public methods with matching semantics
- License requirement enforcement: All three methods enforce that
licenseis configured via a_lic()helper that raises if missing - 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
httportext.
8-9: LGTM!
41-41: LGTM!
46-49: LGTM!
52-56: LGTM!
58-58: LGTM!
60-62: LGTM!
99-104: ⚡ Quick winProxy default port matches README —
sdk/python/wave_dispatch/proxy.pysetsPORT = int(os.environ.get("WAVE_OAI_PORT", "8090")), so the default:8090is correct (overridable viaWAVE_OAI_PORT).
110-110: ⚡ Quick winVerify 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 orprompts.lengthvalidation; matches were limited to unrelated build artifacts containing “32” (e.g.,espidf_time32,musl32_time64). Inspect the/embedhandler’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.licensedirectly instead of using the same local validation path assavings()/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
| 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 }); } |
There was a problem hiding this comment.
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.
| 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 |
There was a problem hiding this comment.
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.
| 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 |
There was a problem hiding this comment.
🧩 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:
- 1: https://docs.python.org/3/howto/urllib2.html
- 2: https://rollbar.com/blog/python-httperror/
- 3: https://docs.python.org/3/library/urllib.request.html
- 4: https://docs.python.org/3/library/urllib.request.html?highlight=requests
- 5: https://docs.python.org/3/library/urllib.error.html?highlight=httperror
- 6: https://stackoverflow.com/questions/6010010/how-do-i-access-response-headers-on-an-error-using-urlib2
- 7: https://realpython.com/urllib-request/
- 8: https://docs.python.org/3/library/urllib.error.html
- 9: https://stackoverflow.com/questions/7032890/why-does-pythons-urllib2-urlopen-raise-an-httperror-for-successful-status-cod
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.
| 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 }) |
There was a problem hiding this comment.
🧩 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"
fiRepository: 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+; howeversdk/ruby/wave_dispatch.gemspecsetss.required_ruby_version = ">= 2.7", causing a Ruby 2.7 load-time syntax error.send_req(sdk/ruby/lib/wave_dispatch.rblines 40-49) only raises on 401/402 and then unconditionallyJSON.parse(res.body)for all other non-2xx responses.licenseis duplicated across query string (savings,subscription), request body (subscribe), andAuthorizationheader (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.
| 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 }) |
There was a problem hiding this comment.
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.
| 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 })) |
There was a problem hiding this comment.
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.
| 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()) | ||
| } |
There was a problem hiding this comment.
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}.
b3b1dde to
ce0e7c7
Compare
|
@coderabbitai review — the ReDoS finding is fixed in upstream 0.4.3; please re-review against the latest sync. |
|
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 performedFull review triggered. |
There was a problem hiding this comment.
Actionable comments posted: 4
♻️ Duplicate comments (6)
BENCHMARKS.md (2)
40-45: 🧹 Nitpick | 🔵 TrivialAdd 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 | 🔵 TrivialClarify "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 | 🔵 TrivialAdd 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
httportext.🤖 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 winEnforce license precondition in
subscribe()before sending the request.
subscribe()should fail locally likesavings()andsubscription()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 winRuby 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
📒 Files selected for processing (9)
BENCHMARKS.mdREADME.mdsdk/js/index.jssdk/python/pyproject.tomlsdk/python/wave_dispatch/__init__.pysdk/python/wave_dispatch/cli.pysdk/python/wave_dispatch/proxy.pysdk/ruby/lib/wave_dispatch.rbsdk/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):
- scripts/check-npm-publish-readiness.sh orchestrator
- check-npm-no-secrets.sh (gitleaks on tarball)
- check-npm-license.sh (SPDX, no GPL-3+ in runtime)
- check-npm-bundle-size.sh (per-package budget)
- check-npm-types-coverage.sh (.d.ts, public API type quality)
- and other composable sub-checks for publish readiness
- URL: https://linear.app/wave-inc/issue/WAVE-24376/phase-4-pre-publish-quality-gates-secretslicensebundletypesfiles
-
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
dispatchconsole 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).
- Verify the new CLI/script/entrypoint (python
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 code0instead 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 to502.This is still the previously flagged proxying bug: the blanket
except ExceptioncatchesurllibHTTP failures and turns them intosend_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
| 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 |
There was a problem hiding this comment.
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.
| 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 |
There was a problem hiding this comment.
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).
| 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()), | ||
| } |
There was a problem hiding this comment.
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.
| 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).
ce0e7c7 to
d27c352
Compare
|
@coderabbitai review — wave-dispatch 0.4.4 just synced (commit on auto/sync-public). The flagged CR findings are addressed:
|
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).
🧩 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.pyLength of output: 21821
✅ Confirmed resolved
🟡 Minor cross-SDK inconsistency (new, non-blocking)Ruby and Rust
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 ✅ Actions performedFull review triggered. |
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/goare 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.dispatch serveruns a local‑first proxy; serves from your local server (WAVE_ENDPOINT, default:11434) when confident, escalates toWAVE_OAI_UPSTREAM, always falls back on errors; binds127.0.0.1:8090; installed as adispatchconsole script viawave-dispatch.savings(),subscription(), andsubscribe(plan)via the sidecar athttps://dispatch-agents.wave.online; safer URL handling (non‑regex slash trim), explicit 401s, stronger error messages, and SSL verify in Ruby.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