Skip to content

Add EnhanceAPI client for POST /v1/enhance (wave-gateway#799) - #66

Open
yakimoto wants to merge 1 commit into
mainfrom
enhance/sdk-799
Open

Add EnhanceAPI client for POST /v1/enhance (wave-gateway#799)#66
yakimoto wants to merge 1 commit into
mainfrom
enhance/sdk-799

Conversation

@yakimoto

@yakimoto yakimoto commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds an EnhanceAPI SDK client for POST /v1/enhance — an AI video super-resolution route that
has been live-routed with an OpenAPI entry going up alongside this PR but no SDK method (closes
the contract-drift gap flagged against wave-gateway#799).

  • v1 ships exactly one model, espcn (ESPCN super-resolution — a fixed exact 3x factor).
  • enhance(video, options) posts raw video bytes (or enhanceFromUrl(url) for a server-side
    ?url= fetch) and returns the enhanced video plus a typed per-job receipt (model, scale
    factor, input/output dimensions, and the wave_enhance_minutes billing minutes) parsed off the
    response headers.
  • The request/response bodies here are raw video bytes, not JSON, so EnhanceAPI bypasses
    WaveClient.post() (which always JSON-encodes) and talks to fetch() directly via
    client.getConnectionInfo() — the same escape hatch RealtimeAPI already uses in this repo for
    its own non-JSON (WebSocket) transport, so this follows an existing repo idiom rather than
    inventing a new one.
  • Throws the existing WaveError on non-2xx (a 402 carries the x402 payment-challenge detail,
    same as other x402-gated products); throws a clear WaveError if the server omits a required
    receipt header rather than returning a silently-incomplete result.
  • Wired into the barrel (src/index.ts) exports and the Wave convenience class, matching every
    sibling module's pattern.
  • Tests: src/__tests__/enhance.test.ts (constructor, POST shape/headers, ?url= mode, the 402
    x402-challenge path, and the missing-receipt-header failure path), plus
    sdk-exports.test.ts coverage bumped for the new module (34 → 35 API classes/factories).

Validation

Ran the repo's own toolchain locally (note: this sandbox's npm defaults to omit=dev, so
npm ci --include=dev was needed to pull in the lint/test/build devDependencies):

$ npx tsc --noEmit -p tsconfig.json
(no output — clean)

$ npx eslint src/ --max-warnings 0
(no output — clean)

$ npm test
 Test Files  3 passed (3)
      Tests  97 passed (97)

$ npm run build
(tsup cjs+esm+dts build succeeds for all modules incl. enhance.ts/enhance-types.ts)

dist/ is checked into this repo; I deliberately did not commit my local rebuild of it (a
different local tsup/toolchain version produced extra split-chunk .mjs output not present in the
repo's checked-in dist/) — only the src/ + package.json description-count change are in this
PR, consistent with how this repo's own release process presumably regenerates dist/.

CI on this PR may show as failed/cancelled due to the ongoing GitHub Actions "Service
Unavailable" incident — that is unrelated to this change; see the local runs above for the real
result.

Test plan

  • tsc --noEmit passes
  • eslint src/ --max-warnings 0 passes
  • npm test — 97/97 pass (90 pre-existing + 7 new)
  • npm run build succeeds
  • Operator merge (this PR is not merged by the agent that opened it — public repo, human-only merge)

Note

Low Risk
New additive SDK module with tests; no changes to shared client auth or existing API behavior beyond exports and documentation counts.

Overview
Adds EnhanceAPI for POST /v1/enhance (AI video super-resolution, v1 model espcn / fixed 3× upscale), closing the SDK gap for wave-gateway#799.

Because bodies are raw video bytes (not JSON), the client uses fetch via getConnectionInfo() instead of WaveClient.post(), matching the RealtimeAPI non-JSON pattern. enhance() posts bytes with optional contentType, or enhanceFromUrl() uses ?url= with no body; successes return a Blob plus a typed receipt from x-enhance-* / x-wave-usage-minutes headers. Non-2xx responses surface WaveError (including 402 x402 challenges); missing receipt headers throw ENHANCE_BAD_RECEIPT instead of partial results.

EnhanceAPI, createEnhanceAPI, and types are exported from the barrel; Wave.enhance is wired like sibling modules. package.json and export tests bump the module count 34 → 35; enhance.test.ts covers POST shape, URL mode, 402, and bad receipts.

Reviewed by Cursor Bugbot for commit f536d75. Configure here.

Review in cubic

AI video super-resolution (ESPCN, v1) had a live gateway route with no SDK
method. EnhanceAPI mirrors RealtimeAPI's pattern for non-JSON transport
(getConnectionInfo() + a direct fetch() call, since the request/response
bodies here are raw video bytes, not JSON): enhance()/enhanceFromUrl() post
the source video (or a ?url= reference), and parse the per-job receipt +
wave_enhance_minutes billing metadata off the x-enhance-*/x-wave-* response
headers. Wired into the barrel exports and the Wave convenience class;
adds enhance.test.ts plus sdk-exports.test.ts coverage (module count bumped
34 -> 35).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@yakimoto yakimoto added the rr:skip-coderabbit RF.P1 reviewer routing (#1039) label Aug 6, 2026
@cursor

cursor Bot commented Aug 6, 2026

Copy link
Copy Markdown

Bugbot couldn't run - usage limit reached

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

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

(requestId: serverGenReqId_a697692c-957b-45fb-ab55-f196bce3c706)

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are limited based on label configuration.

🚫 Review skipped — only excluded labels are configured. (1)
  • rr:skip-coderabbit

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 2a0fa9c8-c77d-4dda-8754-e9ac84794288

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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

@macroscopeapp

macroscopeapp Bot commented Aug 6, 2026

Copy link
Copy Markdown

Approvability

Verdict: Needs human review

Unable to check for correctness in f536d75. This PR introduces a new EnhanceAPI client for AI video super-resolution, adding a new user-facing SDK surface with billing/metering integration. While the implementation follows established patterns and the author owns all files, new feature additions warrant human review to validate the API design and integration approach.

You can customize Macroscope's approvability policy. Learn more.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Add EnhanceAPI SDK client for POST /v1/enhance

✨ Enhancement 🧪 Tests ⚙️ Configuration changes 🕐 20-40 Minutes

Grey Divider

AI Description

• Add EnhanceAPI for raw-bytes POST /v1/enhance (video super-resolution, model=espcn).
• Parse typed per-job receipt and billing minutes from x-enhance-* / x-wave-* headers.
• Wire EnhanceAPI into SDK exports/Wave client and add unit + export coverage.
Diagram

graph TD
  App["Client app"] --> Wave["Wave client"] --> Enhance["EnhanceAPI"] --> Fetch["fetch() POST bytes"] --> GW{{"POST /v1/enhance"}} --> Result["Blob + receipt headers"]
  GW -. "non-2xx / 402" .-> Err["WaveError"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Add binary/raw support to WaveClient.post()
  • ➕ Centralizes auth/org headers, retries, error parsing, and telemetry in one place
  • ➕ Avoids each future binary endpoint re-implementing fetch/error parsing
  • ➖ Expands the scope and risk of a core transport abstraction
  • ➖ May complicate existing JSON-only assumptions and typing across the SDK
2. Introduce a generic low-level request method (e.g., WaveClient.requestRaw)
  • ➕ Keeps WaveClient.post() JSON-focused while providing an explicit escape hatch
  • ➕ Encourages consistent error parsing and header injection across non-JSON APIs
  • ➖ Adds new public surface area to maintain
  • ➖ Still requires some per-endpoint response parsing logic

Recommendation: The PR’s approach (direct fetch() using getConnectionInfo(), matching the existing RealtimeAPI escape hatch) is a reasonable, low-risk way to add a single binary endpoint without refactoring core transport. If more non-JSON endpoints are expected soon, consider adding a small WaveClient.requestRaw helper later to deduplicate auth/header/error handling while keeping WaveClient.post() JSON-specific.

Files changed (6) +355 / -6

Enhancement (3) +215 / -0
enhance-types.tsDefine Enhance API types (options, receipt, result) +53/-0

Define Enhance API types (options, receipt, result)

• Introduces typed surface area for /v1/enhance including supported model, request options (bytes vs sourceUrl), receipt header schema, and returned result shape.

src/enhance-types.ts

enhance.tsImplement EnhanceAPI using direct fetch() for raw video bytes +145/-0

Implement EnhanceAPI using direct fetch() for raw video bytes

• Implements enhance()/enhanceFromUrl() against POST /v1/enhance with binary request/response bodies, parses required receipt/billing headers, and throws WaveError on non-2xx or malformed receipts.

src/enhance.ts

index.tsExport EnhanceAPI and wire it into the Wave convenience client +17/-0

Export EnhanceAPI and wire it into the Wave convenience client

• Adds barrel exports for EnhanceAPI and its types, imports EnhanceAPI, and attaches wave.enhance in the Wave class constructor to match existing module wiring patterns.

src/index.ts

Tests (2) +139 / -5
enhance.test.tsAdd EnhanceAPI unit tests for binary POST, URL mode, and error paths +128/-0

Add EnhanceAPI unit tests for binary POST, URL mode, and error paths

• Adds tests that mock global fetch() to validate POST shape/headers, enhanceFromUrl() query behavior, 402/x402 WaveError handling, and failure on missing receipt headers.

src/tests/enhance.test.ts

sdk-exports.test.tsExtend export verification to include EnhanceAPI +11/-5

Extend export verification to include EnhanceAPI

• Updates the expected module count and verifies EnhanceAPI is exported, factory is exported, and Wave.enhance is wired as an instance of EnhanceAPI.

src/tests/sdk-exports.test.ts

Other (1) +1 / -1
package.jsonBump SDK description module count (34 → 35) +1/-1

Bump SDK description module count (34 → 35)

• Updates the package description string to reflect the addition of a new API module (EnhanceAPI). No runtime/package behavior changes beyond metadata.

package.json

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Devin Review found 4 potential issues.

Open in Devin Review

Comment thread src/enhance.ts
if (options.sourceUrl) url.searchParams.set('url', options.sourceUrl);

const headers: Record<string, string> = { Authorization: `Bearer ${this.apiKey}` };
if (this.organizationId) headers['x-wave-organization-id'] = this.organizationId;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 Enhance requests ignore the caller's organization, sending it under a name the API doesn't recognize

The organization the caller configured is attached under a different label than every other request in the SDK uses (headers['x-wave-organization-id'] at src/enhance.ts:80), so the server never sees which organization the enhance job belongs to.
Impact: For customers who belong to multiple organizations, video-enhance jobs can be authorized and billed against the wrong organization (or rejected), unlike every other SDK call.

Header-name mismatch with WaveClient.buildHeaders

WaveClient sends the organization as X-Organization-Id (src/client.ts:412-413) for all normal requests. EnhanceAPI bypasses WaveClient.request() and constructs its own headers, but uses x-wave-organization-id, a name that appears nowhere else in the repo. The value read from getConnectionInfo() (src/client.ts:184-190) is the same, only the header key differs.

Suggested change
if (this.organizationId) headers['x-wave-organization-id'] = this.organizationId;
if (this.organizationId) headers['X-Organization-Id'] = this.organizationId;
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread src/enhance.ts
Comment on lines +85 to +94
const res = await fetch(url.toString(), {
method: 'POST',
headers,
body: body as BodyInit | undefined,
signal: options.signal,
});

if (!res.ok) {
throw await this.parseError(res);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Video enhance calls never retry and lose rate-limit information

The new video-enhance calls talk to the network directly (fetch(...) at src/enhance.ts:85-90) instead of going through the shared request path, so throttled or temporarily failing requests are neither retried nor reported with the wait time the server asked for.
Impact: Users of the enhance feature see hard failures on transient server errors and get no retry-after guidance when they are throttled, unlike every other part of the SDK.

Divergence from WaveClient retry / RateLimitError contract

WaveClient.executeWithRetry() (src/client.ts:281-320) retries retryable statuses with backoff, honours the configured timeout, and converts HTTP 429 into a RateLimitError carrying retryAfter. EnhanceAPI.parseError() (src/enhance.ts:121-140) always returns a plain WaveError, so err instanceof RateLimitError checks and err.retryAfter no longer work for enhance calls, and the configured maxRetries/timeout are ignored entirely. CONTRIBUTING.md code standards require that "All external API calls must support retry and rate limiting".

Prompt for agents
EnhanceAPI issues raw fetch() calls and therefore skips the retry/backoff, timeout, and rate-limit handling that WaveClient.executeWithRetry provides, and it never produces a RateLimitError for HTTP 429 (so callers lose retryAfter and instanceof RateLimitError checks). CONTRIBUTING.md requires all external API calls to support retry and rate limiting. Consider either adding a binary-capable request path to WaveClient (so enhance can reuse retries/timeouts/error typing), or replicating the retry/backoff loop, timeout, and RateLimitError construction (parsing Retry-After) inside src/enhance.ts.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread src/index.ts
Comment on lines +328 to +336
// Enhance — AI video super-resolution (wave-gateway#799)
export {
EnhanceAPI,
createEnhanceAPI,
type EnhanceModel,
type EnhanceOptions,
type EnhanceReceipt,
type EnhanceResult,
} from "./enhance";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changelog not updated for the new user-facing API module

A new public API surface is added to the package (export { EnhanceAPI, ... } at src/index.ts:328-336) without any entry being added to the changelog's Unreleased section, so users upgrading have no record of the new capability.
Impact: Consumers of the SDK will not learn about the new video-enhance feature from the release notes.

Repo rule

AGENTS.md requires: "Conventional Commit titles; update CHANGELOG.md (Unreleased) for user-facing changes." The ## [Unreleased] section in CHANGELOG.md is still empty on this branch, while this PR adds src/enhance.ts, src/enhance-types.ts, a new Wave.enhance property, and new root exports.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread package.json
"name": "@wave-av/sdk",
"version": "2.1.0-next.0",
"description": "Official WAVE SDK - 34 API modules covering streaming, production, device management, analytics, content, monetization, and more",
"description": "Official WAVE SDK - 35 API modules covering streaming, production, device management, analytics, content, monetization, and more",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔍 Missing "./enhance" subpath export in package.json

package.json exports maps a subpath for nearly every module (./realtime, ./drm, ...) but no ./enhance entry is added by this PR, so import { EnhanceAPI } from '@wave-av/sdk/enhance' will fail even though npm run build (which globs src/*.ts) emits dist/enhance.js/.mjs. Note the same gap already exists for ./perception, so this may be an accepted pattern for newer modules — root-barrel import still works. Worth confirming which convention is intended.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (1) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. Org header mismatch 🐞 Bug ≡ Correctness
Description
EnhanceAPI sends the org identifier as x-wave-organization-id, but WaveClient (and thus other SDK
modules) uses X-Organization-Id; if the gateway expects the established header, Enhance requests
won’t be scoped to the intended organization.
Code

src/enhance.ts[R79-81]

+    const headers: Record<string, string> = { Authorization: `Bearer ${this.apiKey}` };
+    if (this.organizationId) headers['x-wave-organization-id'] = this.organizationId;
+    if (!options.sourceUrl && options.contentType) headers['content-type'] = options.contentType;
Relevance

●●● Strong

Likely straightforward bugfix: align org scoping header with rest of SDK to avoid unscoped requests.

PR-#21

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
EnhanceAPI uses a unique header name that is not used anywhere else in the SDK; the base client
uses a different header key for organization scoping.

src/enhance.ts[79-82]
src/client.ts[401-415]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`EnhanceAPI` uses a different organization header name (`x-wave-organization-id`) than the rest of the SDK (`X-Organization-Id`). If the enhance endpoint follows the same contract as other endpoints, organization scoping may be ignored.

### Issue Context
The base `WaveClient` builds headers consistently for all JSON endpoints; Enhance bypasses it and recreates headers manually.

### Fix Focus Areas
- src/enhance.ts[79-82]
- src/client.ts[401-415]

### Proposed fix
- Change `headers['x-wave-organization-id']` to `headers['X-Organization-Id']` to match `WaveClient.buildHeaders()`.
- If you must support both contracts, send both headers (same value) or confirm the gateway’s expected header name and document the difference explicitly.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Missing input validation 🐞 Bug ☼ Reliability
Description
EnhanceAPI.enhance() documents that callers must provide video bytes or options.sourceUrl, but it
can be called with neither and will still POST with an undefined body, yielding a confusing
server-side failure instead of a clear local WaveError.
Code

src/enhance.ts[R83-86]

+    const body = options.sourceUrl ? undefined : (video ?? undefined);
+
+    const res = await fetch(url.toString(), {
+      method: 'POST',
Relevance

●● Moderate

Local argument validation is reasonable, but repo has rejected some extra defensive guards in
fetch-based APIs.

PR-#15

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The method’s docstring requires exactly one input, but the code computes body as undefined when
both are missing and still calls fetch().

src/enhance.ts[61-90]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`EnhanceAPI.enhance()` allows `video === null` with no `options.sourceUrl`, resulting in a POST with no body. This violates the method’s own contract (“set exactly one”) and produces avoidable network calls and unclear errors.

### Issue Context
The method signature allows `null` (needed for `enhanceFromUrl`), but the implementation should reject the case where both inputs are absent.

### Fix Focus Areas
- src/enhance.ts[60-90]

### Proposed fix
- Add an early guard:
 - If `!options.sourceUrl` and `video == null`, throw `new WaveError('enhance requires either video bytes or sourceUrl', 'INVALID_ARGUMENT', 400)`.
- Optionally, decide whether providing both should be allowed (current behavior ignores `video` when `sourceUrl` is set); if not, throw when both are provided.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

3. Loose receipt number parsing 🐞 Bug ≡ Correctness
Description
requireNumberHeader() uses Number(header) and only checks isFinite, so malformed/non-canonical
numeric strings (e.g., empty string → 0, whitespace, decimals, exponential notation) can be accepted
for receipt fields that represent numeric quantities.
Code

src/enhance.ts[R29-34]

+function requireNumberHeader(headers: Headers, name: string): number {
+  const raw = headers.get(name);
+  const value = raw === null ? NaN : Number(raw);
+  if (!Number.isFinite(value)) {
+    throw new WaveError(`enhance response omitted or sent an invalid ${name} header`, 'ENHANCE_BAD_RECEIPT', 502);
+  }
Relevance

●●● Strong

Team previously accepted tightening Number()-based header parsing to strict integers (Retry-After).

PR-#21

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Enhance’s numeric header parsing is permissive, while the base client contains a precedent for
strict integer header parsing to prevent Number() from accepting invalid formats.

src/enhance.ts[28-36]
src/client.ts[484-495]
PR-#21

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`requireNumberHeader()` accepts many non-canonical numeric encodings because it relies on `Number()` + `isFinite()`. This can allow malformed receipt metadata to slip through without error.

### Issue Context
The base client previously tightened integer parsing for `Retry-After` to avoid `Number()` accepting exponential/decimal formats.

### Fix Focus Areas
- src/enhance.ts[28-36]
- src/client.ts[484-495]

### Proposed fix
- Update `requireNumberHeader` to validate a strict wire format before parsing, e.g.:
 - `const trimmed = raw?.trim(); if (!trimmed || !/^\d+$/.test(trimmed)) throw ...;`
 - `const value = Number(trimmed);`
- Optionally enforce non-negative range and (if required) integer-only semantics for `x-enhance-scale-factor` and `x-wave-usage-minutes`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. Missing Enhance changelog entry 📘 Rule violation § Compliance
Description
This PR adds a new user-facing SDK module (EnhanceAPI) but does not add an entry under
CHANGELOG.mdUnreleased. Users may miss the new API surface and release notes will be
incomplete.
Code

src/index.ts[R328-331]

+// Enhance — AI video super-resolution (wave-gateway#799)
+export {
+  EnhanceAPI,
+  createEnhanceAPI,
Relevance

●● Moderate

Changelog requirements aren’t evidenced in past reviews here; could be deferred to release process.

PR-#16

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The change exports a new public SDK module (EnhanceAPI) from the package root, which is a
user-facing addition. However, CHANGELOG.md contains only the Unreleased header with no entry
describing this new feature.

Rule 2497929: Document user-facing changes in CHANGELOG.md Unreleased section
src/index.ts[328-336]
CHANGELOG.md[1-7]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A new user-facing SDK API (`EnhanceAPI`) was added, but `CHANGELOG.md` has no corresponding entry under the `Unreleased` section.

## Issue Context
The compliance checklist requires documenting user-facing changes in `CHANGELOG.md` under `Unreleased`.

## Fix Focus Areas
- CHANGELOG.md[7-12]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context used
✅ Compliance rules (platform): 7 rules
✅ REVIEW.md
Review mode: ⚖️ Balanced: Downgraded extended -> standard: change is below the extended eligibility bar (hunks 13/18, lines 361/200; both must reach the floor). Router rationale: This adds a new raw-byte API client with independent request modes, receipt parsing, error handling, and SDK integration across multiple files, creating a dense set of easy-to-miss behavioral and contract risks.
ⓘ  2 issues published inline · 4 in summary

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

Comment thread src/enhance.ts
Comment on lines +79 to +81
const headers: Record<string, string> = { Authorization: `Bearer ${this.apiKey}` };
if (this.organizationId) headers['x-wave-organization-id'] = this.organizationId;
if (!options.sourceUrl && options.contentType) headers['content-type'] = options.contentType;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

2. Org header mismatch 🐞 Bug ≡ Correctness

EnhanceAPI sends the org identifier as x-wave-organization-id, but WaveClient (and thus other SDK
modules) uses X-Organization-Id; if the gateway expects the established header, Enhance requests
won’t be scoped to the intended organization.
Agent Prompt
### Issue description
`EnhanceAPI` uses a different organization header name (`x-wave-organization-id`) than the rest of the SDK (`X-Organization-Id`). If the enhance endpoint follows the same contract as other endpoints, organization scoping may be ignored.

### Issue Context
The base `WaveClient` builds headers consistently for all JSON endpoints; Enhance bypasses it and recreates headers manually.

### Fix Focus Areas
- src/enhance.ts[79-82]
- src/client.ts[401-415]

### Proposed fix
- Change `headers['x-wave-organization-id']` to `headers['X-Organization-Id']` to match `WaveClient.buildHeaders()`.
- If you must support both contracts, send both headers (same value) or confirm the gateway’s expected header name and document the difference explicitly.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread src/enhance.ts
Comment on lines +83 to +86
const body = options.sourceUrl ? undefined : (video ?? undefined);

const res = await fetch(url.toString(), {
method: 'POST',

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

3. Missing input validation 🐞 Bug ☼ Reliability

EnhanceAPI.enhance() documents that callers must provide video bytes or options.sourceUrl, but it
can be called with neither and will still POST with an undefined body, yielding a confusing
server-side failure instead of a clear local WaveError.
Agent Prompt
### Issue description
`EnhanceAPI.enhance()` allows `video === null` with no `options.sourceUrl`, resulting in a POST with no body. This violates the method’s own contract (“set exactly one”) and produces avoidable network calls and unclear errors.

### Issue Context
The method signature allows `null` (needed for `enhanceFromUrl`), but the implementation should reject the case where both inputs are absent.

### Fix Focus Areas
- src/enhance.ts[60-90]

### Proposed fix
- Add an early guard:
  - If `!options.sourceUrl` and `video == null`, throw `new WaveError('enhance requires either video bytes or sourceUrl', 'INVALID_ARGUMENT', 400)`.
- Optionally, decide whether providing both should be allowed (current behavior ignores `video` when `sourceUrl` is set); if not, throw when both are provided.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@qodo-code-review

Copy link
Copy Markdown

Qodo Fixer

No findings are within the configured fix scope. To change which findings are fixed, adjust the setting on your Qodo configuration page.

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

6 issues found across 6 files

Confidence score: 2/5

  • In src/enhance.ts, the non-2xx path reshapes gateway errors so message can become an object and code stays HTTP_<status>, which breaks the stable error contract callers rely on (notably for 402 handling) and can cause downstream parsing/regression issues — preserve the gateway { code, message, details } shape on error responses.
  • In src/enhance.ts, receipt/header validation can misreport usage: missing x-wave-meter currently falls back to wave_enhance_minutes, and requireNumberHeader accepts blank values as 0, so successful responses may carry false billing data — require the expected header and reject empty/whitespace numeric headers before coercion.
  • In src/enhance.ts, enhance requests drop WaveClientConfig.customHeaders, so integrations that depend on tenant, tracing, or policy headers may silently fail despite the SDK type contract promising they apply to all requests — pass customHeaders through the enhance transport layer.
  • In src/index.ts and package.json, EnhanceAPI is added to the root barrel but not fully exposed via the ./enhance subpath exports, so consumers using the established @wave-av/sdk/enhance import pattern can hit module resolution/import failures — add matching ./enhance export mappings (types/import/require).
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/enhance.ts">

<violation number="1" location="src/enhance.ts:31">
P2: The `requireNumberHeader` guard treats a present-but-blank header as valid. `Number('') === 0` and `Number('   ') === 0`, both of which pass `Number.isFinite`. So if the server ever emits an empty `x-wave-usage-minutes` (or `x-enhance-scale-factor`) header, the SDK silently reports `usageMinutes: 0` / `scaleFactor: 0` instead of hitting the documented ENHANCE_BAD_RECEIPT fail-loud path. For a billing receipt this is a fail-open edge that could mask 0-minute billing or a malformed server response. Consider treating blank strings as invalid too so a blank receipt header still throws.</violation>

<violation number="2" location="src/enhance.ts:79">
P2: Enhance requests silently drop `WaveClientConfig.customHeaders`: the raw transport sends only Authorization, organization, and optional content type, despite `src/client-types.ts:16` promising custom headers on all requests. Callers relying on tracing, routing, or gateway integration headers therefore get different behavior for this endpoint; the raw transport should merge the configured headers.</violation>

<violation number="3" location="src/enhance.ts:105">
P2: A successful enhance call can return a false billing receipt when `x-wave-meter` is missing because this line substitutes `wave_enhance_minutes` instead of raising `ENHANCE_BAD_RECEIPT`. Requiring the header would surface a gateway/proxy contract regression instead of hiding a potentially unreconciled usage record.</violation>

<violation number="4" location="src/enhance.ts:133">
P1: Non-2xx callers lose the gateway’s stable error contract: this branch assigns an `{ code, message, details }` object to `message` and leaves `code` as `HTTP_<status>`. For 402, `wave-av/wave-gateway/src/x402.ts:187-193` puts the payment challenge in top-level `accepts`/`next_action`, so `error.details` is empty despite this method’s documented contract; parsing both envelopes should preserve the stable code, details, and challenge.</violation>
</file>

<file name="src/index.ts">

<violation number="1" location="src/index.ts:336">
P2: Consumers cannot import the new API through the SDK's established `@wave-av/sdk/enhance` subpath because this module is only added to the root barrel. Adding the corresponding `./enhance` types/import/require mapping would keep the public module surface consistent.</violation>
</file>

<file name="package.json">

<violation number="1" location="package.json:4">
P2: The new EnhanceAPI is counted as the 35th module in this description but is not exposed as a package.json subpath export, unlike every other module (all 42, including the analogous ./realtime). With an "exports" map in place, `import { EnhanceAPI } from "@wave-av/sdk/enhance"` will fail to resolve for consumers, breaking the per-module import pattern the README documents ("./enhance" artifacts are emitted to dist but never reachable). Add a `"./enhance"` entry mirroring the others.</violation>
</file>

Tip: instead of fixing issues one by one fix them all with cubic

Re-trigger cubic

Comment thread src/enhance.ts
message = body.error_detail.message || message;
details = body.error_detail.details;
} else if (body?.error) {
message = body.error;

@cubic-dev-ai cubic-dev-ai Bot Aug 6, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1: Non-2xx callers lose the gateway’s stable error contract: this branch assigns an { code, message, details } object to message and leaves code as HTTP_<status>. For 402, wave-av/wave-gateway/src/x402.ts:187-193 puts the payment challenge in top-level accepts/next_action, so error.details is empty despite this method’s documented contract; parsing both envelopes should preserve the stable code, details, and challenge.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/enhance.ts, line 133:

<comment>Non-2xx callers lose the gateway’s stable error contract: this branch assigns an `{ code, message, details }` object to `message` and leaves `code` as `HTTP_<status>`. For 402, `wave-av/wave-gateway/src/x402.ts:187-193` puts the payment challenge in top-level `accepts`/`next_action`, so `error.details` is empty despite this method’s documented contract; parsing both envelopes should preserve the stable code, details, and challenge.</comment>

<file context>
@@ -0,0 +1,145 @@
+        message = body.error_detail.message || message;
+        details = body.error_detail.details;
+      } else if (body?.error) {
+        message = body.error;
+        details = body.detail ? { detail: body.detail } : undefined;
+      }
</file context>
Fix with cubic

Comment thread src/enhance.ts
url.searchParams.set('model', model);
if (options.sourceUrl) url.searchParams.set('url', options.sourceUrl);

const headers: Record<string, string> = { Authorization: `Bearer ${this.apiKey}` };

@cubic-dev-ai cubic-dev-ai Bot Aug 6, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: Enhance requests silently drop WaveClientConfig.customHeaders: the raw transport sends only Authorization, organization, and optional content type, despite src/client-types.ts:16 promising custom headers on all requests. Callers relying on tracing, routing, or gateway integration headers therefore get different behavior for this endpoint; the raw transport should merge the configured headers.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/enhance.ts, line 79:

<comment>Enhance requests silently drop `WaveClientConfig.customHeaders`: the raw transport sends only Authorization, organization, and optional content type, despite `src/client-types.ts:16` promising custom headers on all requests. Callers relying on tracing, routing, or gateway integration headers therefore get different behavior for this endpoint; the raw transport should merge the configured headers.</comment>

<file context>
@@ -0,0 +1,145 @@
+    url.searchParams.set('model', model);
+    if (options.sourceUrl) url.searchParams.set('url', options.sourceUrl);
+
+    const headers: Record<string, string> = { Authorization: `Bearer ${this.apiKey}` };
+    if (this.organizationId) headers['x-wave-organization-id'] = this.organizationId;
+    if (!options.sourceUrl && options.contentType) headers['content-type'] = options.contentType;
</file context>
Fix with cubic

Comment thread src/enhance.ts
inputHeight,
outputWidth,
outputHeight,
meter: res.headers.get('x-wave-meter') ?? 'wave_enhance_minutes',

@cubic-dev-ai cubic-dev-ai Bot Aug 6, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: A successful enhance call can return a false billing receipt when x-wave-meter is missing because this line substitutes wave_enhance_minutes instead of raising ENHANCE_BAD_RECEIPT. Requiring the header would surface a gateway/proxy contract regression instead of hiding a potentially unreconciled usage record.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/enhance.ts, line 105:

<comment>A successful enhance call can return a false billing receipt when `x-wave-meter` is missing because this line substitutes `wave_enhance_minutes` instead of raising `ENHANCE_BAD_RECEIPT`. Requiring the header would surface a gateway/proxy contract regression instead of hiding a potentially unreconciled usage record.</comment>

<file context>
@@ -0,0 +1,145 @@
+      inputHeight,
+      outputWidth,
+      outputHeight,
+      meter: res.headers.get('x-wave-meter') ?? 'wave_enhance_minutes',
+      usageMinutes: requireNumberHeader(res.headers, 'x-wave-usage-minutes'),
+    };
</file context>
Fix with cubic

Comment thread src/index.ts
type EnhanceOptions,
type EnhanceReceipt,
type EnhanceResult,
} from "./enhance";

@cubic-dev-ai cubic-dev-ai Bot Aug 6, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: Consumers cannot import the new API through the SDK's established @wave-av/sdk/enhance subpath because this module is only added to the root barrel. Adding the corresponding ./enhance types/import/require mapping would keep the public module surface consistent.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/index.ts, line 336:

<comment>Consumers cannot import the new API through the SDK's established `@wave-av/sdk/enhance` subpath because this module is only added to the root barrel. Adding the corresponding `./enhance` types/import/require mapping would keep the public module surface consistent.</comment>

<file context>
@@ -325,6 +325,16 @@ export { DrmAPI, createDrmAPI } from "./drm";
+  type EnhanceOptions,
+  type EnhanceReceipt,
+  type EnhanceResult,
+} from "./enhance";
+
 // Perception — agentic live-media subscribe() control plane (#85)
</file context>
Fix with cubic

Comment thread package.json
"name": "@wave-av/sdk",
"version": "2.1.0-next.0",
"description": "Official WAVE SDK - 34 API modules covering streaming, production, device management, analytics, content, monetization, and more",
"description": "Official WAVE SDK - 35 API modules covering streaming, production, device management, analytics, content, monetization, and more",

@cubic-dev-ai cubic-dev-ai Bot Aug 6, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: The new EnhanceAPI is counted as the 35th module in this description but is not exposed as a package.json subpath export, unlike every other module (all 42, including the analogous ./realtime). With an "exports" map in place, import { EnhanceAPI } from "@wave-av/sdk/enhance" will fail to resolve for consumers, breaking the per-module import pattern the README documents ("./enhance" artifacts are emitted to dist but never reachable). Add a "./enhance" entry mirroring the others.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At package.json, line 4:

<comment>The new EnhanceAPI is counted as the 35th module in this description but is not exposed as a package.json subpath export, unlike every other module (all 42, including the analogous ./realtime). With an "exports" map in place, `import { EnhanceAPI } from "@wave-av/sdk/enhance"` will fail to resolve for consumers, breaking the per-module import pattern the README documents ("./enhance" artifacts are emitted to dist but never reachable). Add a `"./enhance"` entry mirroring the others.</comment>

<file context>
@@ -1,7 +1,7 @@
   "name": "@wave-av/sdk",
   "version": "2.1.0-next.0",
-  "description": "Official WAVE SDK - 34 API modules covering streaming, production, device management, analytics, content, monetization, and more",
+  "description": "Official WAVE SDK - 35 API modules covering streaming, production, device management, analytics, content, monetization, and more",
   "main": "./dist/index.js",
   "module": "./dist/index.mjs",
</file context>
Fix with cubic

Comment thread src/enhance.ts
/** Read a required numeric response header, throwing a clear error if the server omitted it. */
function requireNumberHeader(headers: Headers, name: string): number {
const raw = headers.get(name);
const value = raw === null ? NaN : Number(raw);

@cubic-dev-ai cubic-dev-ai Bot Aug 6, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: The requireNumberHeader guard treats a present-but-blank header as valid. Number('') === 0 and Number(' ') === 0, both of which pass Number.isFinite. So if the server ever emits an empty x-wave-usage-minutes (or x-enhance-scale-factor) header, the SDK silently reports usageMinutes: 0 / scaleFactor: 0 instead of hitting the documented ENHANCE_BAD_RECEIPT fail-loud path. For a billing receipt this is a fail-open edge that could mask 0-minute billing or a malformed server response. Consider treating blank strings as invalid too so a blank receipt header still throws.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/enhance.ts, line 31:

<comment>The `requireNumberHeader` guard treats a present-but-blank header as valid. `Number('') === 0` and `Number('   ') === 0`, both of which pass `Number.isFinite`. So if the server ever emits an empty `x-wave-usage-minutes` (or `x-enhance-scale-factor`) header, the SDK silently reports `usageMinutes: 0` / `scaleFactor: 0` instead of hitting the documented ENHANCE_BAD_RECEIPT fail-loud path. For a billing receipt this is a fail-open edge that could mask 0-minute billing or a malformed server response. Consider treating blank strings as invalid too so a blank receipt header still throws.</comment>

<file context>
@@ -0,0 +1,145 @@
+/** Read a required numeric response header, throwing a clear error if the server omitted it. */
+function requireNumberHeader(headers: Headers, name: string): number {
+  const raw = headers.get(name);
+  const value = raw === null ? NaN : Number(raw);
+  if (!Number.isFinite(value)) {
+    throw new WaveError(`enhance response omitted or sent an invalid ${name} header`, 'ENHANCE_BAD_RECEIPT', 502);
</file context>
Suggested change
const value = raw === null ? NaN : Number(raw);
const value = raw === null || raw.trim() === '' ? NaN : Number(raw);
Fix with cubic

Comment thread src/enhance.ts
const info = client.getConnectionInfo();
this.apiKey = info.apiKey;
this.organizationId = info.organizationId;
this.baseUrl = info.baseUrl.replace(/\/+$/, '');
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

rr:skip-coderabbit RF.P1 reviewer routing (#1039)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants