Skip to content

Commit 9d4e993

Browse files
authored
feat(realtime): add resolution option (#140)
## Summary Lets users of realtime models opt into 1080p output via a new `resolution` option on `realtime.connect`. Defaults to `720p` (no behavior change for existing callers). ## Usage ```ts const realtimeClient = await client.realtime.connect(stream, { model: models.realtime("lucy-2.1"), resolution: "1080p", // default: "720p" onRemoteStream, }); ``` ## Test plan - [x] `pnpm typecheck` - [x] `pnpm test` (4 new unit tests covering URL construction + Zod validation) - [x] `pnpm format:check` - [ ] Manual smoke against staging: verify `lucy-2.1` + `resolution: "1080p"` delivers a 1884×1080 remote track <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Low risk: adds an optional, Zod-validated `resolution` parameter that only affects realtime WebRTC URL query construction, with coverage to prevent invalid values. > > **Overview** > Adds a new optional `resolution` setting to `realtime.connect` (validated as `"720p"` or `"1080p"`) and forwards it to the realtime WebRTC endpoint via a `resolution=` query parameter when provided. > > Updates the SDK README with usage examples and adds unit tests verifying URL construction (param omitted vs appended) and input validation for invalid resolutions. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 56c8621. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent cc1029c commit 9d4e993

3 files changed

Lines changed: 81 additions & 2 deletions

File tree

packages/sdk/README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,16 @@ Options:
7979
- `"auto"` — mirror when the input track reports `facingMode: "user"` (mobile front cameras).
8080
- `true` — always mirror (e.g. desktop webcams).
8181

82+
#### Output resolution
83+
84+
```ts
85+
const realtimeClient = await client.realtime.connect(stream, {
86+
model,
87+
resolution: "1080p", // default: "720p"
88+
// ...
89+
});
90+
```
91+
8292
### Async Processing (Queue API)
8393

8494
For video generation jobs, use the queue API to submit jobs and poll for results:

packages/sdk/src/realtime/client.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ const realTimeClientConnectOptionsSchema = z.object({
100100
* - true: always mirror.
101101
*/
102102
mirror: z.union([z.literal("auto"), z.boolean()]).optional(),
103+
/** Output resolution. Defaults to "720p". */
104+
resolution: z.enum(["720p", "1080p"]).optional(),
103105
});
104106
export type RealTimeClientConnectOptions = Omit<z.infer<typeof realTimeClientConnectOptionsSchema>, "model"> & {
105107
model: ModelDefinition | CustomModelDefinition;
@@ -141,7 +143,7 @@ export const createRealTimeClient = (opts: RealTimeClientOptions) => {
141143
throw parsedOptions.error;
142144
}
143145

144-
const { onRemoteStream, initialState } = parsedOptions.data;
146+
const { onRemoteStream, initialState, resolution } = parsedOptions.data;
145147
const mirror = parsedOptions.data.mirror ?? false;
146148

147149
let inputStream: MediaStream = stream ?? new MediaStream();
@@ -189,9 +191,10 @@ export const createRealTimeClient = (opts: RealTimeClientOptions) => {
189191
: undefined;
190192

191193
const url = `${baseUrl}${options.model.urlPath}`;
194+
const resolutionQs = resolution ? `&resolution=${encodeURIComponent(resolution)}` : "";
192195

193196
webrtcManager = new WebRTCManager({
194-
webrtcUrl: `${url}?api_key=${encodeURIComponent(apiKey)}&model=${encodeURIComponent(options.model.name)}`,
197+
webrtcUrl: `${url}?api_key=${encodeURIComponent(apiKey)}&model=${encodeURIComponent(options.model.name)}${resolutionQs}`,
195198
integration,
196199
logger,
197200
observability,

packages/sdk/tests/unit.test.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1993,6 +1993,72 @@ describe("Subscribe Client", () => {
19931993
cleanupSpy.mockRestore();
19941994
}
19951995
});
1996+
1997+
describe("connect() resolution option", () => {
1998+
const connectAndCaptureUrl = async (resolution?: "720p" | "1080p"): Promise<string> => {
1999+
const { createRealTimeClient } = await import("../src/realtime/client.js");
2000+
const { WebRTCManager } = await import("../src/realtime/webrtc-manager.js");
2001+
2002+
let capturedUrl = "";
2003+
const connectSpy = vi.spyOn(WebRTCManager.prototype, "connect").mockImplementation(async function () {
2004+
const mgr = this as unknown as {
2005+
config: {
2006+
webrtcUrl: string;
2007+
onConnectionStateChange?: (state: import("../src/realtime/types").ConnectionState) => void;
2008+
};
2009+
managerState: import("../src/realtime/types").ConnectionState;
2010+
};
2011+
capturedUrl = mgr.config.webrtcUrl;
2012+
mgr.managerState = "connected";
2013+
mgr.config.onConnectionStateChange?.("connected");
2014+
return true;
2015+
});
2016+
const stateSpy = vi.spyOn(WebRTCManager.prototype, "getConnectionState").mockReturnValue("connected");
2017+
const cleanupSpy = vi.spyOn(WebRTCManager.prototype, "cleanup").mockImplementation(() => {});
2018+
2019+
try {
2020+
const realtime = createRealTimeClient({ baseUrl: "wss://api3.decart.ai", apiKey: "test-key" });
2021+
const client = await realtime.connect({} as MediaStream, {
2022+
model: models.realtime("lucy-2.1"),
2023+
onRemoteStream: vi.fn(),
2024+
...(resolution ? { resolution } : {}),
2025+
});
2026+
client.disconnect();
2027+
return capturedUrl;
2028+
} finally {
2029+
connectSpy.mockRestore();
2030+
stateSpy.mockRestore();
2031+
cleanupSpy.mockRestore();
2032+
}
2033+
};
2034+
2035+
it("omits the resolution query param when unset", async () => {
2036+
const url = await connectAndCaptureUrl();
2037+
expect(url).not.toContain("resolution=");
2038+
});
2039+
2040+
it("appends resolution=720p when set", async () => {
2041+
const url = await connectAndCaptureUrl("720p");
2042+
expect(url).toContain("&resolution=720p");
2043+
});
2044+
2045+
it("appends resolution=1080p when set", async () => {
2046+
const url = await connectAndCaptureUrl("1080p");
2047+
expect(url).toContain("&resolution=1080p");
2048+
});
2049+
2050+
it("rejects invalid resolution values", async () => {
2051+
const { createRealTimeClient } = await import("../src/realtime/client.js");
2052+
const realtime = createRealTimeClient({ baseUrl: "wss://api3.decart.ai", apiKey: "test-key" });
2053+
await expect(
2054+
realtime.connect({} as MediaStream, {
2055+
model: models.realtime("lucy-2.1"),
2056+
onRemoteStream: vi.fn(),
2057+
resolution: "480p" as unknown as "720p",
2058+
}),
2059+
).rejects.toThrow();
2060+
});
2061+
});
19962062
});
19972063

19982064
describe("Logger", () => {

0 commit comments

Comments
 (0)