Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/sdk-core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ See `examples/nextjs-realtime` or `examples/react-vite` for runnable demos.
- `realtime/connection-events.ts` - Handling connection state and errors
- `realtime/prompt-update.ts` - Updating prompt dynamically
- `realtime/custom-model.ts` - Using a custom model definition (e.g., preview/experimental models)
- `realtime/custom-base-url.ts` - Using a custom WebSocket base URL for realtime connections

## API Reference

Expand Down
47 changes: 47 additions & 0 deletions examples/sdk-core/realtime/custom-base-url.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* Browser-only example - requires WebRTC APIs
* Demonstrates using a custom realtimeBaseUrl for WebSocket connections
* See examples/nextjs-realtime or examples/react-vite for runnable demos
*/

import { createDecartClient, models } from "@decartai/sdk";

async function main() {
const model = models.realtime("mirage_v2");

// Get webcam stream with model-specific settings
const stream = await navigator.mediaDevices.getUserMedia({
audio: true,
video: {
frameRate: model.fps,
width: model.width,
height: model.height,
},
});

// Create a client with a custom realtime WebSocket base URL
// This overrides the default wss://api3.decart.ai endpoint
const client = createDecartClient({
apiKey: process.env.DECART_API_KEY!,
realtimeBaseUrl: "wss://custom-ws.example.com",
});

const realtimeClient = await client.realtime.connect(stream, {
model,
onRemoteStream: (transformedStream) => {
const video = document.getElementById("output") as HTMLVideoElement;
video.srcObject = transformedStream;
},
initialState: {
prompt: {
text: "Studio Ghibli animation style",
enhance: true,
},
},
});

console.log("Session ID:", realtimeClient.sessionId);
console.log("Connected:", realtimeClient.isConnected());
}

main();
9 changes: 9 additions & 0 deletions packages/sdk/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,10 @@ <h3>Configuration</h3>
<label for="api-key">API Key:</label>
<input type="password" autocomplete="off" id="api-key" placeholder="Enter your Decart API key">
</div>
<div class="control-group">
<label for="realtime-base-url">Realtime Base URL (optional):</label>
<input type="text" id="realtime-base-url" placeholder="wss://api3.decart.ai">
</div>
<div class="control-group">
<label for="model-select">Model:</label>
<select id="model-select">
Expand Down Expand Up @@ -369,6 +373,7 @@ <h3>Console Logs</h3>
const elements = {
apiKey: document.getElementById('api-key'),
modelSelect: document.getElementById('model-select'),
realtimeBaseUrl: document.getElementById('realtime-base-url'),
startCamera: document.getElementById('start-camera'),
connectBtn: document.getElementById('connect-btn'),
disconnectBtn: document.getElementById('disconnect-btn'),
Expand Down Expand Up @@ -492,8 +497,10 @@ <h3>Console Logs</h3>

addLog('Initializing Decart client...', 'info');

const realtimeBaseUrl = elements.realtimeBaseUrl.value.trim();
decartClient = createDecartClient({
apiKey,
...(realtimeBaseUrl && { realtimeBaseUrl }),
});

addLog('Connecting to Decart server...', 'info');
Expand Down Expand Up @@ -780,8 +787,10 @@ <h3>Console Logs</h3>

// Initialize client if not already done
if (!decartClient) {
const realtimeBaseUrl = elements.realtimeBaseUrl.value.trim();
decartClient = createDecartClient({
apiKey,
...(realtimeBaseUrl && { realtimeBaseUrl }),
});
addLog('Initialized Decart client for processing', 'info');
}
Expand Down
13 changes: 8 additions & 5 deletions packages/sdk/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ const decartClientOptionsSchema = z
baseUrl: z.url().optional(),
proxy: proxySchema.optional(),
integration: z.string().optional(),
realtimeBaseUrl: z.url().optional(),
})
.refine(
(data) => {
Expand All @@ -96,6 +97,7 @@ export type DecartClientOptions =
proxy: string;
apiKey?: never;
baseUrl?: string;
realtimeBaseUrl?: string;
integration?: string;
logger?: Logger;
telemetry?: boolean;
Expand All @@ -104,6 +106,7 @@ export type DecartClientOptions =
proxy?: never;
apiKey?: string;
baseUrl?: string;
realtimeBaseUrl?: string;
integration?: string;
logger?: Logger;
telemetry?: boolean;
Expand All @@ -116,6 +119,7 @@ export type DecartClientOptions =
* @param options.proxy - URL of the proxy server. When set, the client will use the proxy instead of direct API access and apiKey is not required.
* @param options.apiKey - API key for authentication.
* @param options.baseUrl - Override the default API base URL.
* @param options.realtimeBaseUrl - Override the default WebSocket base URL for realtime connections.
* @param options.integration - Optional integration identifier.
*
* @example
Expand All @@ -141,10 +145,9 @@ export const createDecartClient = (options: DecartClientOptions = {}) => {
throw createInvalidApiKeyError();
}

if (issue.path.includes("baseUrl")) {
throw createInvalidBaseUrlError(
issue.path.includes("baseUrl") ? (options as { baseUrl?: string }).baseUrl : undefined,
);
if (issue.path.includes("baseUrl") || issue.path.includes("realtimeBaseUrl")) {
const urlField = issue.path.includes("realtimeBaseUrl") ? "realtimeBaseUrl" : "baseUrl";
throw createInvalidBaseUrlError((options as Record<string, string | undefined>)[urlField]);
}

if (issue.path.includes("proxy")) {
Expand Down Expand Up @@ -181,7 +184,7 @@ export const createDecartClient = (options: DecartClientOptions = {}) => {
// Realtime (WebRTC) always requires direct API access with API key
// Proxy mode is only for HTTP endpoints (process, queue, tokens)
// Note: Realtime will fail at connection time if no API key is provided
const wsBaseUrl = "wss://api3.decart.ai";
const wsBaseUrl = parsedOptions.data.realtimeBaseUrl || "wss://api3.decart.ai";
const realtime = createRealTimeClient({
baseUrl: wsBaseUrl,
apiKey: apiKey || "",
Expand Down
12 changes: 12 additions & 0 deletions packages/sdk/tests/unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,18 @@ describe("Decart SDK", () => {
it("throws an error if invalid base url is provided", () => {
expect(() => createDecartClient({ apiKey: "test", baseUrl: "not-a-url" })).toThrow("Invalid base URL");
});

it("throws an error if invalid realtimeBaseUrl is provided", () => {
expect(() => createDecartClient({ apiKey: "test", realtimeBaseUrl: "not-a-url" })).toThrow("Invalid base URL");
});

it("creates a client with custom realtimeBaseUrl", () => {
const decart = createDecartClient({
apiKey: "test",
realtimeBaseUrl: "wss://custom-ws.example.com",
});
expect(decart).toBeDefined();
});
});

describe("Process API", () => {
Expand Down
Loading