Add EnhanceAPI client for POST /v1/enhance (wave-gateway#799) - #66
Add EnhanceAPI client for POST /v1/enhance (wave-gateway#799)#66yakimoto wants to merge 1 commit into
Conversation
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>
Bugbot couldn't run - usage limit reachedBugbot 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) |
|
Important Review skippedAuto reviews are limited based on label configuration. 🚫 Review skipped — only excluded labels are configured. (1)
Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Comment |
ApprovabilityVerdict: 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. |
PR Summary by QodoAdd EnhanceAPI SDK client for POST /v1/enhance
AI Description
Diagram
High-Level Assessment
Files changed (6)
|
| 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; |
There was a problem hiding this comment.
🔴 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.
| if (this.organizationId) headers['x-wave-organization-id'] = this.organizationId; | |
| if (this.organizationId) headers['X-Organization-Id'] = this.organizationId; |
Was this helpful? React with 👍 or 👎 to provide feedback.
| 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); | ||
| } |
There was a problem hiding this comment.
🟡 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.
Was this helpful? React with 👍 or 👎 to provide feedback.
| // Enhance — AI video super-resolution (wave-gateway#799) | ||
| export { | ||
| EnhanceAPI, | ||
| createEnhanceAPI, | ||
| type EnhanceModel, | ||
| type EnhanceOptions, | ||
| type EnhanceReceipt, | ||
| type EnhanceResult, | ||
| } from "./enhance"; |
There was a problem hiding this comment.
🟡 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.
Was this helpful? React with 👍 or 👎 to provide feedback.
| "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", |
There was a problem hiding this comment.
🔍 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.
Was this helpful? React with 👍 or 👎 to provide feedback.
Code Review by Qodo
1. Org header mismatch
|
| 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; |
There was a problem hiding this comment.
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
| const body = options.sourceUrl ? undefined : (video ?? undefined); | ||
|
|
||
| const res = await fetch(url.toString(), { | ||
| method: 'POST', |
There was a problem hiding this comment.
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 FixerNo findings are within the configured fix scope. To change which findings are fixed, adjust the setting on your Qodo configuration page. |
There was a problem hiding this comment.
6 issues found across 6 files
Confidence score: 2/5
- In
src/enhance.ts, the non-2xx path reshapes gateway errors somessagecan become an object andcodestaysHTTP_<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: missingx-wave-metercurrently falls back towave_enhance_minutes, andrequireNumberHeaderaccepts blank values as0, 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 dropWaveClientConfig.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 — passcustomHeadersthrough the enhance transport layer. - In
src/index.tsandpackage.json, EnhanceAPI is added to the root barrel but not fully exposed via the./enhancesubpath exports, so consumers using the established@wave-av/sdk/enhanceimport pattern can hit module resolution/import failures — add matching./enhanceexport 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
| message = body.error_detail.message || message; | ||
| details = body.error_detail.details; | ||
| } else if (body?.error) { | ||
| message = body.error; |
There was a problem hiding this comment.
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>
| url.searchParams.set('model', model); | ||
| if (options.sourceUrl) url.searchParams.set('url', options.sourceUrl); | ||
|
|
||
| const headers: Record<string, string> = { Authorization: `Bearer ${this.apiKey}` }; |
There was a problem hiding this comment.
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>
| inputHeight, | ||
| outputWidth, | ||
| outputHeight, | ||
| meter: res.headers.get('x-wave-meter') ?? 'wave_enhance_minutes', |
There was a problem hiding this comment.
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>
| type EnhanceOptions, | ||
| type EnhanceReceipt, | ||
| type EnhanceResult, | ||
| } from "./enhance"; |
There was a problem hiding this comment.
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>
| "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", |
There was a problem hiding this comment.
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>
| /** 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); |
There was a problem hiding this comment.
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>
| const value = raw === null ? NaN : Number(raw); | |
| const value = raw === null || raw.trim() === '' ? NaN : Number(raw); |
| const info = client.getConnectionInfo(); | ||
| this.apiKey = info.apiKey; | ||
| this.organizationId = info.organizationId; | ||
| this.baseUrl = info.baseUrl.replace(/\/+$/, ''); |
Summary
Adds an
EnhanceAPISDK client forPOST /v1/enhance— an AI video super-resolution route thathas 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).
espcn(ESPCN super-resolution — a fixed exact 3x factor).enhance(video, options)posts raw video bytes (orenhanceFromUrl(url)for a server-side?url=fetch) and returns the enhanced video plus a typed per-job receipt (model, scalefactor, input/output dimensions, and the
wave_enhance_minutesbilling minutes) parsed off theresponse headers.
EnhanceAPIbypassesWaveClient.post()(which always JSON-encodes) and talks tofetch()directly viaclient.getConnectionInfo()— the same escape hatchRealtimeAPIalready uses in this repo forits own non-JSON (WebSocket) transport, so this follows an existing repo idiom rather than
inventing a new one.
WaveErroron non-2xx (a402carries the x402 payment-challenge detail,same as other x402-gated products); throws a clear
WaveErrorif the server omits a requiredreceipt header rather than returning a silently-incomplete result.
src/index.ts) exports and theWaveconvenience class, matching everysibling module's pattern.
src/__tests__/enhance.test.ts(constructor, POST shape/headers,?url=mode, the 402x402-challenge path, and the missing-receipt-header failure path), plus
sdk-exports.test.tscoverage 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, sonpm ci --include=devwas needed to pull in the lint/test/build devDependencies):dist/is checked into this repo; I deliberately did not commit my local rebuild of it (adifferent local tsup/toolchain version produced extra split-chunk
.mjsoutput not present in therepo's checked-in
dist/) — only thesrc/+package.jsondescription-count change are in thisPR, 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 --noEmitpasseseslint src/ --max-warnings 0passesnpm test— 97/97 pass (90 pre-existing + 7 new)npm run buildsucceedsNote
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
EnhanceAPIforPOST /v1/enhance(AI video super-resolution, v1 modelespcn/ fixed 3× upscale), closing the SDK gap for wave-gateway#799.Because bodies are raw video bytes (not JSON), the client uses
fetchviagetConnectionInfo()instead ofWaveClient.post(), matching theRealtimeAPInon-JSON pattern.enhance()posts bytes with optionalcontentType, orenhanceFromUrl()uses?url=with no body; successes return aBlobplus a typedreceiptfromx-enhance-*/x-wave-usage-minutesheaders. Non-2xx responses surfaceWaveError(including 402 x402 challenges); missing receipt headers throwENHANCE_BAD_RECEIPTinstead of partial results.EnhanceAPI,createEnhanceAPI, and types are exported from the barrel;Wave.enhanceis wired like sibling modules.package.jsonand export tests bump the module count 34 → 35;enhance.test.tscovers POST shape, URL mode, 402, and bad receipts.Reviewed by Cursor Bugbot for commit f536d75. Configure here.