Skip to content

fix: align SDK clip + voice contracts with the live gateway - #67

Open
yakimoto wants to merge 4 commits into
mainfrom
fix/live-gateway-contract
Open

fix: align SDK clip + voice contracts with the live gateway#67
yakimoto wants to merge 4 commits into
mainfrom
fix/live-gateway-contract

Conversation

@yakimoto

@yakimoto yakimoto commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

The published SDK was out of sync with the live gateway. Verified today by probing api.wave.online:

  • clips.create() sent a rejected { source: { type, id, start_time, end_time } } object. The gateway accepts { source: "<recording-id>", in: "5s", out: "10s" }.
  • voice.synthesize() POSTed /v1/voice/synthesize and expected a JSON job object. The gateway serves POST /v1/voice returning raw audio/mpeg bytes directly (verified — got a real 51KB MP3).

Changed: CreateClipRequest (source string + in/out time strings), ClipSource, voice.synthesize() (returns ArrayBuffer), SynthesizeRequest.voice_id optional. CHANGELOG updated.

No auth/secret changes. Type-check clean for touched files.


Note

Medium Risk
Breaking public API changes: clip create payload and ClipSource types, plus voice.synthesize() return type and endpoint—callers expecting the old shapes or SynthesisResult need updates, though the change corrects behavior against the live gateway.

Overview
Aligns clips and voice client shapes with the live gateway at api.wave.online so published SDK calls succeed instead of being rejected or hitting the wrong route.

Clips: CreateClipRequest no longer nests a source object with type, start_time, and end_time. Create payloads now use a recording id string plus top-level in / out time strings (e.g. "5s", "2m"). ClipSource on clip resources is updated to id, in, and out instead of the old object fields.

Voice: synthesize() POSTs /v1/voice (not /v1/voice/synthesize) with { text, voice_id? }, requests audio/mpeg, and returns Promise<ArrayBuffer> instead of a JSON SynthesisResult. SynthesizeRequest.voice_id is optional. Job helpers like getSynthesis are unchanged in this diff.

CHANGELOG [Unreleased] documents the contract fix.

Reviewed by Cursor Bugbot for commit 92ebacd. Configure here.


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

Review in cubic

Note

Align SDK clip and voice contracts with the live gateway

  • clips.create() now expects source as a recording-id string with top-level in and out time strings, replacing the older discriminated ClipSource object shape.
  • voice.synthesize() now POSTs to /v1/voice (was /v1/voice/synthesize), sets Accept: audio/mpeg, and returns Promise<ArrayBuffer> of raw audio bytes instead of a JSON job object.
  • RequestOptions gains a responseType field ('json' | 'arraybuffer') wired through executeWithRetry() to support binary response parsing.
  • Risk: both changes are breaking — callers must update CreateClipRequest shapes and handle ArrayBuffer instead of a synthesis job object.

Macroscope summarized 00f8ab7.


Open in Devin Review

Verified against api.wave.online today: clips.create() sent a rejected
{ source: { type, id, start_time, end_time } } object; the gateway accepts
{ source: "<recording-id>", in: "5s", out: "10s" }. voice.synthesize() hit
/v1/voice/synthesize and expected a JSON job object; the gateway serves
POST /v1/voice returning audio/mpeg bytes directly.

- clips.ts: CreateClipRequest.source is now a recording-id string, plus
  in/out relative time-string fields.
- clips-types.ts: ClipSource updated to the verified string+in/out shape.
- voice.ts: synthesize() now POSTs /v1/voice and returns ArrayBuffer bytes.
- voice-types.ts: SynthesizeRequest.voice_id now optional.

No auth/secret changes. Type-check clean for touched files (pre-existing
telemetry.ts require error is unrelated).
@cursor

cursor Bot commented Aug 11, 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_bfc10709-cb64-4dc8-9c0a-ba48cb11bc50)

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Warning

Review limit reached

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

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

Next review available in: 2 minutes

Your organization has reached its usage spending cap. Adjust your spending cap in the billing tab.

How can I continue?

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

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

How do review limits work?

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

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

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 6048d9df-b282-4d10-8386-085b7eed39d8

📥 Commits

Reviewing files that changed from the base of the PR and between f905017 and 00f8ab7.

📒 Files selected for processing (8)
  • CHANGELOG.md
  • README.md
  • src/client-types.ts
  • src/client.ts
  • src/clips-types.ts
  • src/clips.ts
  • src/voice-types.ts
  • src/voice.ts

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

@macroscopeapp

macroscopeapp Bot commented Aug 11, 2026

Copy link
Copy Markdown

Approvability

Verdict: Needs human review

Breaking API changes to CreateClipRequest and voice.synthesize() modify public SDK contracts and runtime behavior. A Medium-severity finding about response type mismatch requires author verification against the live gateway.

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

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Align clips + voice SDK contracts with live gateway responses

🐞 Bug fix 📝 Documentation 🕐 40+ Minutes

Grey Divider

AI Description

• Fix clips.create() payload to match live gateway: recording id + in/out time strings.
• Fix voice.synthesize() to POST /v1/voice and return raw audio/mpeg bytes.
• Update exported SDK types and document the verified live contract in CHANGELOG.
Diagram

graph TD
  U["SDK Consumer"] --> C["Clips API"] --> G{{"Live Gateway"}}
  U --> V["Voice API"] --> G
  C --> CT["clips-types"]
  V --> VT["voice-types"]
  subgraph Legend
    direction LR
    _usr["Client code"] ~~~ _svc["SDK module"] ~~~ _ext{{"External service"}} ~~~ _mod["Type defs"]
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Extend SDK HTTP client to support binary responses
  • ➕ Keeps transport/auth/error-handling behavior consistent across SDK APIs
  • ➕ Avoids reaching into client[&#x27;config&#x27;] and bypassing existing abstractions
  • ➕ Easier to add retries/telemetry uniformly
  • ➖ Requires changing the client abstraction (may ripple across other endpoints)
  • ➖ Potentially larger PR if the client is shared widely
2. Offer dual methods (e.g., synthesizeBytes vs synthesizeJob)
  • ➕ Softens breaking change for existing consumers expecting JSON job results
  • ➕ Allows staged migration and clearer API intent
  • ➖ Maintains extra surface area and documentation burden
  • ➖ Still requires a decision on which method is canonical long-term
3. Content-negotiation via Accept header with typed overloads
  • ➕ Single endpoint wrapper can return bytes or JSON based on Accept
  • ➕ Can preserve compatibility if gateway supports multiple representations
  • ➖ Only viable if the live gateway actually supports JSON job responses
  • ➖ More complex typing/overloads for consumers

Recommendation: The PR’s approach (match the verified live gateway contract) is the correct direction. If this SDK has a standard HTTP client abstraction, consider a follow-up to add binary response support there so voice.synthesize() doesn’t bypass the client and access client[&#x27;config&#x27;] directly; that will improve consistency and reduce future maintenance risk.

Files changed (5) +60 / -11

Bug fix (4) +56 / -11
clips-types.tsUpdate ClipSource to recording-id + in/out time strings +13/-3

Update ClipSource to recording-id + in/out time strings

• Replaces the previous object-based source shape (type/start_time/end_time) with a contract aligned to the live gateway. Adds documentation clarifying that the older shape is rejected on create.

src/clips-types.ts

clips.tsChange CreateClipRequest to source string + in/out offsets +11/-1

Change CreateClipRequest to source string + in/out offsets

• Updates the 'CreateClipRequest' interface so 'source' is a recording id string and introduces required 'in'/'out' time-string fields. Adds inline docs explaining the verified live gateway expectations.

src/clips.ts

voice-types.tsMake SynthesizeRequest.voice_id optional +2/-2

Make SynthesizeRequest.voice_id optional

• Changes 'voice_id' to optional to reflect gateway behavior when omitted. Updates documentation to indicate the gateway will select a default voice.

src/voice-types.ts

voice.tsSwitch synthesize() to POST /v1/voice and return audio ArrayBuffer +30/-5

Switch synthesize() to POST /v1/voice and return audio ArrayBuffer

• Updates 'VoiceAPI.synthesize()' to call the live gateway endpoint ('POST /v1/voice') with '{ text, voice_id? }'. Changes the return type to 'Promise<ArrayBuffer>' and implements a fetch-based call that requests 'audio/mpeg' and throws on non-OK responses.

src/voice.ts

Documentation (1) +4 / -0
CHANGELOG.mdDocument live-gateway contract alignment for clips + voice +4/-0

Document live-gateway contract alignment for clips + voice

• Adds an Unreleased/Fixed entry describing the verified gateway payload/endpoint differences. Captures the new 'clips.create()' shape and the new 'voice.synthesize()' behavior returning raw MP3 bytes.

CHANGELOG.md

Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>

@cursor cursor 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.

Risk: medium. Left a non-blocking comment — Cursor Bugbot and Cursor Security Agent did not complete successfully (skipped / usage limit), so approval is deferred for human review. No reviewers assigned because no non-author assignable reviewers were available.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Router and Approver

@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 5 potential issues.

Open in Devin Review

Comment thread src/clips.ts
Comment thread src/voice.ts

@devin-ai-integration devin-ai-integration Bot Aug 11, 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.

🔍 Synthesis job helpers become unreachable now that synthesize returns raw bytes

synthesize() no longer returns a SynthesisResult with an id, so getSynthesis() (src/voice.ts:173), listSyntheses() (src/voice.ts:184) and waitForSynthesis() (src/voice.ts:233) have no in-SDK way to obtain a synthesis id, and they still target /v1/voice/synthesize/... — the very path the PR says the gateway does not implement for synthesis. synthesizeStream() (src/voice.ts:199) likewise still POSTs to /v1/voice/synthesize/stream with a bare fetch (no retries/WaveError). Worth confirming whether these endpoints exist on the live gateway; if not, they should be removed or re-pointed in the same contract-alignment pass.

Open in Devin Review

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Removing or deprecating the synthesis-job surface (getSynthesis, listSyntheses, waitForSynthesis, synthesizeStream) is a breaking scope expansion beyond this contract fix, and whether those endpoints exist on the live gateway cannot be verified without API credentials. Best handled as a follow-up by the author.

Comment thread src/voice.ts Outdated
Comment thread src/voice.ts Outdated
Comment thread src/clips-types.ts
Comment on lines 30 to 37
export interface ClipSource {
type: 'stream' | 'recording' | 'upload';
/** Recording id the clip is cut from */
id: string;
start_time: number;
end_time: number;
/** Start offset as a time string, e.g. `"5s"` or `"2m"` */
in: string;
/** End offset as a time string, e.g. `"10s"` or `"1m30s"` */
out: string;
}

@devin-ai-integration devin-ai-integration Bot Aug 11, 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.

🔍 Response type reuses the request-side clip source shape

ClipSource was redefined as { id, in, out }, but it is only used on the response object Clip.source (src/clips-types.ts:43) — CreateClipRequest now uses a plain string plus top-level in/out. The PR verified the request shape against the live gateway; there is no evidence in the diff that the gateway returns { id, in, out } for clip.source. If the response still returns numeric start_time/end_time, consumers reading clip.source.in will get undefined at runtime while the types claim otherwise.

Open in Devin Review

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The response-side shape of clip.source cannot be verified from this environment (no API credentials to probe the live gateway). The type follows the PR author's verified contract; if the gateway returns a different shape for clip.source, that needs the author's live verification to resolve.

@qodo-code-review

qodo-code-review Bot commented Aug 11, 2026

Copy link
Copy Markdown

Code Review by Qodo

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

Grey Divider


Action required

1. Bypasses client HTTP layer ✓ Resolved 🐞 Bug ☼ Reliability
Description
VoiceAPI.synthesize() now calls fetch() directly and reaches into this.client['config'],
bypassing WaveClient’s timeout, retry/backoff, request events, and centralized headers
(User-Agent/customHeaders). This can cause synthesize calls to hang (no timeout) and behave
inconsistently vs the rest of the SDK.
Code

src/voice.ts[R159-162]

+    const response = await fetch(
+      `${this.client['config'].baseUrl}${this.basePath}`,
+      {
+        method: 'POST',
Relevance

●●● Strong

Team has precedent fixing bypassed client retry/error plumbing; likely want synthesize to use
WaveClient stack.

PR-#21

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
WaveClient centrally applies timeout (AbortController), retry/backoff, and standardized
headers/events; the new synthesize implementation bypasses all of that and also uses unsupported
access to a protected config field instead of the intended accessor.

src/voice.ts[158-176]
src/client.ts[250-280]
src/client.ts[286-396]
src/client.ts[401-432]
src/client.ts[184-190]
src/realtime.ts[123-128]
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
`VoiceAPI.synthesize()` bypasses the shared `WaveClient` request pipeline (timeout, retries/backoff, events, and header building) by using raw `fetch()` and by accessing `this.client['config']` via bracket notation.

## Issue Context
The SDK already has a centralized HTTP client (`WaveClient.request()`/`executeWithRetry()`/`fetchWithTimeout()` + `buildHeaders()`). The new voice endpoint returns `audio/mpeg`, so we need a shared way to request and return binary data while preserving the same request policy.

## Fix Focus Areas
- src/voice.ts[148-183]
- src/client.ts[247-345]
- src/client.ts[379-432]
- src/client.ts[184-190]

### Suggested implementation direction
1. Add a new public method on `WaveClient` (e.g. `postArrayBuffer(path: string, body: unknown, options?: RequestOptions): Promise<ArrayBuffer>` or a more general `requestRaw()`/`requestBinary()` with `responseType: 'arrayBuffer'`).
2. Implement it by reusing the existing retry/timeout/error parsing logic:
  - Use the same `buildHeaders()` and `fetchWithTimeout()`.
  - Preserve the same retry + 429 handling behavior as `executeWithRetry()`.
  - On success, call `response.arrayBuffer()`.
3. Update `VoiceAPI.synthesize()` to call the new `WaveClient` method instead of raw `fetch()`.
4. Avoid `this.client['config']` access; if needed, use `getConnectionInfo()` or the new client method so `VoiceAPI` doesn’t depend on internal fields.

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



Remediation recommended

2. Throws non-standard errors ✓ Resolved 🐞 Bug ≡ Correctness
Description
On non-2xx, VoiceAPI.synthesize() throws a generic Error instead of the SDK’s
WaveError/RateLimitError, dropping code, requestId, and retryable metadata. This breaks
consumers that rely on the SDK’s standardized error types and makes failures harder to debug.
Code

src/voice.ts[R178-180]

+    if (!response.ok) {
+      throw new Error(`Synthesis failed: ${response.status} ${response.statusText}`);
+    }
Relevance

●●● Strong

Consistent SDK error types/metadata emphasized previously; throwing plain Error would be seen as
regression.

PR-#21

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
WaveClient’s request stack converts non-OK responses into WaveError (and RateLimitError on 429). The
new synthesize path instead throws a plain Error and discards structured error information.

src/voice.ts[178-183]
src/client.ts[303-333]
src/client.ts[442-464]

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

## Issue description
`VoiceAPI.synthesize()` throws a plain `Error` on HTTP errors, losing the SDK’s structured `WaveError` information (code, requestId, retryable) and rate-limit semantics.

## Issue Context
`WaveClient` already parses error envelopes into `WaveError` and throws `RateLimitError` on 429. The new synthesize path should keep the same behavior.

## Fix Focus Areas
- src/voice.ts[178-183]
- src/client.ts[303-333]
- src/client.ts[442-464]

### Suggested implementation direction
- Preferably: implement/consume a `WaveClient` binary request helper so error parsing stays centralized.
- If keeping a local fetch: read `x-request-id`, parse JSON error envelope when present, and throw `new WaveError(...)` (and `RateLimitError` for 429) to match the rest of the SDK.

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


3. Clips example outdated ✓ Resolved 🐞 Bug ⚙ Maintainability
Description
The ClipsAPI JSDoc example still shows the old nested source object shape, but
CreateClipRequest was changed to require source: string plus top-level in/out time strings.
Users copying the example will now produce invalid code/requests.
Code

src/clips.ts[R55-58]

+  /** Recording id to clip from (string, e.g. `"rec_abc123"`) */
+  source: string;
+  /** Start offset as a time string, e.g. `"5s"` or `"2m"` */
+  in: string;
Relevance

●●● Strong

Doc/example drift after contract change; updating JSDoc prevents copy-paste invalid requests.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The CreateClipRequest interface was updated to source: string and required in/out, but the
example directly below still uses the old nested object form with type/start_time/end_time.

src/clips.ts[52-69]
src/clips.ts[151-168]

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

## Issue description
The public `ClipsAPI` example uses the old `source: { type, id, start_time, end_time }` payload, but `CreateClipRequest` now requires `source` to be a recording-id string and requires `in`/`out` time strings.

## Issue Context
This mismatch was introduced when changing the request contract and will mislead users.

## Fix Focus Areas
- src/clips.ts[52-69]
- src/clips.ts[151-168]

### Suggested implementation direction
Update the example to something like:
```ts
await clips.create({
 title: 'Best Moment',
 source: 'rec_abc123',
 in: '5s',
 out: '10s',
});
```
(and adjust the surrounding comment text to say “clip from a recording” if `source` is no longer typed for streams/uploads).

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


4. Synthesize type/impl mismatch ✓ Resolved 🐞 Bug ≡ Correctness
Description
SynthesizeRequest still exposes format, sample_rate, speed, pitch, etc., but
VoiceAPI.synthesize() now sends only { text, voice_id? }, so these fields are silently ignored.
Callers can set options per the exported type but will not get the behavior they requested.
Code

src/voice.ts[R171-174]

+        body: JSON.stringify({
+          text: request.text,
+          ...(request.voice_id ? { voice_id: request.voice_id } : {}),
+        }),
Relevance

●●● Strong

Exported request type advertises options that implementation drops; likely treated as correctness
bug to fix.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The type definition advertises many optional synthesis parameters, but the updated synthesize
implementation constructs a new body with only text and voice_id, so those parameters never
reach the server.

src/voice-types.ts[25-50]
src/voice.ts[171-174]

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

## Issue description
The public `SynthesizeRequest` type still includes many synthesis tuning options, but `VoiceAPI.synthesize()` drops them from the request body.

## Issue Context
This PR’s documentation states the live gateway accepts only `{ text, voice_id? }`. The SDK should not advertise options it does not send.

## Fix Focus Areas
- src/voice-types.ts[25-50]
- src/voice.ts[158-175]

### Suggested implementation direction
Choose one:
1. **If live contract is only `{ text, voice_id? }`**: remove unsupported fields from `SynthesizeRequest` (or split into `SynthesizeRequest` vs `LegacySynthesizeRequest`) so the type matches behavior.
2. **If gateway accepts extra options**: include the additional fields in the JSON body (and consider `Accept`/format negotiation) instead of silently dropping them.

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


Grey Divider

Context
✅ Compliance rules (platform): 7 rules
✅ REVIEW.md
Review mode: ⚖️ Balanced: This changes public SDK/API contracts and introduces a new raw-audio HTTP path with authentication and error handling; it has meaningful compatibility and behavior risk, but the logic is concentrated enough for one careful review.

Grey Divider

Tip of the day
💡 Did you know, you can group findings by type and pick your Finding display, from Minimal to Full

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

qodo-code-review[bot]

This comment was marked as resolved.

- Add responseType: 'arraybuffer' support to WaveClient request path so
  binary endpoints keep retries, rate-limit handling, timeouts, custom
  headers, and WaveError-typed failures
- voice.synthesize() now forwards the full SynthesizeRequest (audio
  options were silently dropped) via client.post instead of a bare fetch
- Update ClipsAPI JSDoc example and README quick-start to the new
  clip/voice contracts

Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>
@qodo-code-review

Copy link
Copy Markdown

Qodo Fixer

🍒 Ready to be cherry-picked — ✅ Merged (0) · ☑ Fixed (1)

Grey Divider

🔗 Fix PR: #68

This fix PR was closed automatically. Its branch is preserved so you can cherry pick the changes into the original PR.

Prompt for coding agent

This is an automated fix prepared on a separate branch (#68). It is NOT applied to this PR.
To use it: review Fix PR #68 (https://github.com/wave-av/sdk/pull/68), evaluate each change critically against your local context, and cherry-pick the changes that are correct into this branch. Do not accept them blindly.
Process — 1 fixed
  • ☑ Fixed: Bypasses client HTTP layer

@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 2 new potential issues.

Open in Devin Review

Comment thread src/clips-types.ts
Comment on lines +22 to 37
/**
* Clip source reference.
*
* Live contract (verified against api.wave.online): `source` is the recording
* id as a string, with `in`/`out` relative time strings (`"5s"`, `"2m"`).
* The older `{ type, id, start_time, end_time }` object shape is rejected by
* the gateway on create.
*/
export interface ClipSource {
type: 'stream' | 'recording' | 'upload';
/** Recording id the clip is cut from */
id: string;
start_time: number;
end_time: number;
/** Start offset as a time string, e.g. `"5s"` or `"2m"` */
in: string;
/** End offset as a time string, e.g. `"10s"` or `"1m30s"` */
out: string;
}

@devin-ai-integration devin-ai-integration Bot Aug 11, 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.

🟡 Clip results describe their source with fields the service does not return

The description of where a clip came from was rewritten to hold two text offsets (in/out at src/clips-types.ts:30-37) even though this description is only ever used for clip data returned by the service, so code reading it can get fields that never arrive.
Impact: Users inspecting a returned clip's source get misleading typing and may read fields that are absent at runtime.

Mechanism: request-shape comment applied to a response-only type

ClipSource is only referenced by Clip.source (src/clips-types.ts:43), i.e. the response object. The create request no longer uses it at all — CreateClipRequest now carries source: string plus top-level in/out (src/clips.ts:55-60). The new JSDoc on ClipSource explicitly documents the create contract ("The older { type, id, start_time, end_time } object shape is rejected by the gateway on create"), which does not describe what the gateway returns for a clip. Additionally ListClipsParams.source_type (src/clips.ts:86) still filters by 'stream' | 'recording' | 'upload', a discriminator that no longer exists anywhere on the source type. Either the response shape should be verified and typed separately from the create shape, or Clip.source should be aligned with what the live gateway actually returns.

Open in Devin Review

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

What the gateway returns for clip.source cannot be verified from this environment (no API credentials), so retyping the response or removing the ListClipsParams.source_type filter would be a speculative breaking change; this needs the author's live verification, as noted on the earlier identical finding.

Comment thread src/clips.ts
Co-authored-by: Codesmith <codesmith-bot@users.noreply.github.com>

@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 2 new potential issues.

Open in Devin Review

Comment thread src/voice.ts
Comment on lines +161 to +165
async synthesize(request: SynthesizeRequest): Promise<ArrayBuffer> {
return this.client.post<ArrayBuffer>(this.basePath, request, {
headers: { Accept: 'audio/mpeg' },
responseType: 'arraybuffer',
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Agent-authored pull request changes library source code although the repo only permits docs/config-only agent changes

This pull request modifies SDK source code (src/voice.ts:161-165, plus src/client.ts, src/clips.ts, src/clips-types.ts, src/voice-types.ts), while the repository rules limit agent-authored pull requests to documentation and configuration changes only.
Impact: The change falls outside the scope the repository owner allows for agent-authored contributions, so it should not be merged as-is.

Repository rule in CONTRIBUTING.md restricting agent PR scope

CONTRIBUTING.md states under "Agent-authored pull requests": "This repo accepts PRs opened by an AI coding agent (e.g. a Cursor Cloud Agent session), scoped today to docs/config-only changes." The commits in this PR (fix: route voice.synthesize through WaveClient and refresh stale docs, fix: remove unused ClipSource import in clips.ts) change runtime behavior of VoiceAPI.synthesize, the WaveClient request pipeline, and the clips request/type contract, which exceeds docs/config scope.

Open in Devin Review

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This is a repo-governance concern, not a code defect: the PR was opened by the repo owner (Jake), who is also the required human reviewer for agent PRs per CONTRIBUTING.md, and the only way to "fix" it would be closing the PR, which is the owner's decision.

Comment thread src/clips-types.ts
Comment on lines +22 to +29
/**
* Clip source reference.
*
* Live contract (verified against api.wave.online): `source` is the recording
* id as a string, with `in`/`out` relative time strings (`"5s"`, `"2m"`).
* The older `{ type, id, start_time, end_time }` object shape is rejected by
* the gateway on create.
*/

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔍 Committed dist/ artifacts still declare the old synthesize/clip contracts

dist/ is tracked in git (dist/voice.d.ts:213 still declares synthesize(...): Promise<SynthesisResult>; dist/clips.d.ts:25-39 still has the old ClipSource). Consumers reading the checked-in build output — or any tooling that resolves the package's types entry without a rebuild — will see the pre-change contract. Confirm the release pipeline rebuilds dist/ before publish, or refresh the committed artifacts.

Open in Devin Review

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Verified release.yml rebuilds dist/ (npm run build) and packs the fresh output before publishing, so published artifacts never carry the stale contracts; refreshing committed dist/ would add large generated churn and require a full local build outside this PR's scope.

@yakimoto
yakimoto enabled auto-merge August 15, 2026 05:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant