@charivo/realtime-client-remote 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +66 -0
- package/dist/index.d.ts +43 -0
- package/dist/index.js +121 -0
- package/dist/index.js.map +1 -0
- package/package.json +51 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Zeikar
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# @charivo/realtime-client-remote
|
|
2
|
+
|
|
3
|
+
Browser-side realtime client for server API routes.
|
|
4
|
+
|
|
5
|
+
This is the default production path for realtime sessions: the browser talks to
|
|
6
|
+
your own `/api/realtime` route, the route returns an adapter-aware bootstrap,
|
|
7
|
+
and the client resolves a browser transport adapter from its registry.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pnpm add @charivo/realtime-client-remote
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import {
|
|
19
|
+
createRemoteRealtimeClient,
|
|
20
|
+
DEFAULT_REMOTE_REALTIME_ADAPTERS,
|
|
21
|
+
} from "@charivo/realtime-client-remote";
|
|
22
|
+
|
|
23
|
+
const client = createRemoteRealtimeClient({ apiEndpoint: "/api/realtime" });
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
You can also extend the adapter registry:
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
const client = createRemoteRealtimeClient({
|
|
30
|
+
apiEndpoint: "/api/realtime",
|
|
31
|
+
adapters: {
|
|
32
|
+
...DEFAULT_REMOTE_REALTIME_ADAPTERS,
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Request Contract
|
|
38
|
+
|
|
39
|
+
The current client posts JSON shaped like:
|
|
40
|
+
|
|
41
|
+
```json
|
|
42
|
+
{
|
|
43
|
+
"transport": "webrtc",
|
|
44
|
+
"session": {
|
|
45
|
+
"provider": "openai",
|
|
46
|
+
"model": "gpt-realtime-mini",
|
|
47
|
+
"voice": "marin"
|
|
48
|
+
},
|
|
49
|
+
"sdpOffer": "..."
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The server should respond with a `RealtimeSessionBootstrap` JSON object:
|
|
54
|
+
|
|
55
|
+
```json
|
|
56
|
+
{
|
|
57
|
+
"adapter": "openai-webrtc",
|
|
58
|
+
"transport": "webrtc",
|
|
59
|
+
"answerSdp": "..."
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Current defaults:
|
|
64
|
+
|
|
65
|
+
- the built-in resolver maps `provider: "openai"` + `transport: "webrtc"` to `openai-webrtc`
|
|
66
|
+
- the built-in registry only ships the OpenAI WebRTC adapter
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { RealtimeSessionRequest, RealtimeSessionBootstrap, RealtimeSessionConfig } from '@charivo/core';
|
|
2
|
+
import { RealtimeTransportClient, RealtimeTransportEvent } from '@charivo/realtime-core';
|
|
3
|
+
|
|
4
|
+
interface RemoteRealtimeAdapterFactoryOptions {
|
|
5
|
+
debug?: boolean;
|
|
6
|
+
requestBootstrap: (request: RealtimeSessionRequest) => Promise<RealtimeSessionBootstrap>;
|
|
7
|
+
}
|
|
8
|
+
type RemoteRealtimeAdapterFactory = (options: RemoteRealtimeAdapterFactoryOptions) => RealtimeTransportClient;
|
|
9
|
+
declare const DEFAULT_REMOTE_REALTIME_ADAPTERS: {
|
|
10
|
+
"openai-webrtc": (options: RemoteRealtimeAdapterFactoryOptions) => RealtimeTransportClient;
|
|
11
|
+
};
|
|
12
|
+
interface RemoteRealtimeClientConfig {
|
|
13
|
+
apiEndpoint?: string;
|
|
14
|
+
debug?: boolean;
|
|
15
|
+
adapters?: Record<string, RemoteRealtimeAdapterFactory>;
|
|
16
|
+
resolveAdapterId?: (config?: RealtimeSessionConfig) => string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Production browser client for server-mediated realtime sessions.
|
|
20
|
+
*
|
|
21
|
+
* It resolves a browser-side transport adapter, forwards bootstrap requests to
|
|
22
|
+
* your server route, and validates that the returned bootstrap matches the
|
|
23
|
+
* selected adapter.
|
|
24
|
+
*/
|
|
25
|
+
declare class RemoteRealtimeClient implements RealtimeTransportClient {
|
|
26
|
+
private config;
|
|
27
|
+
private transportClient;
|
|
28
|
+
private readonly adapters;
|
|
29
|
+
private readonly eventCallbacks;
|
|
30
|
+
constructor(config?: RemoteRealtimeClientConfig);
|
|
31
|
+
connect(config?: RealtimeSessionConfig): Promise<void>;
|
|
32
|
+
disconnect(): Promise<void>;
|
|
33
|
+
sendText(text: string): Promise<void>;
|
|
34
|
+
sendAudio(audio: ArrayBuffer): Promise<void>;
|
|
35
|
+
interrupt(): Promise<void>;
|
|
36
|
+
onEvent(callback: (event: RealtimeTransportEvent) => void): void;
|
|
37
|
+
private getActiveTransportClient;
|
|
38
|
+
private resolveAdapterId;
|
|
39
|
+
private requestBootstrap;
|
|
40
|
+
}
|
|
41
|
+
declare function createRemoteRealtimeClient(config?: RemoteRealtimeClientConfig): RealtimeTransportClient;
|
|
42
|
+
|
|
43
|
+
export { DEFAULT_REMOTE_REALTIME_ADAPTERS, type RemoteRealtimeAdapterFactory, type RemoteRealtimeAdapterFactoryOptions, RemoteRealtimeClient, type RemoteRealtimeClientConfig, createRemoteRealtimeClient };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { OPENAI_REALTIME_ADAPTER } from '@charivo/core';
|
|
2
|
+
import { createOpenAIRealtimeClient } from '@charivo/realtime-client-openai';
|
|
3
|
+
import { fetchWithTimeout, DEFAULT_REQUEST_TIMEOUT_MS, isRealtimeSessionBootstrap } from '@charivo/shared';
|
|
4
|
+
|
|
5
|
+
// src/index.ts
|
|
6
|
+
var DEFAULT_REMOTE_REALTIME_ADAPTERS = {
|
|
7
|
+
[OPENAI_REALTIME_ADAPTER]: (options) => createOpenAIRealtimeClient({
|
|
8
|
+
debug: options.debug,
|
|
9
|
+
sessionBootstrap: options.requestBootstrap
|
|
10
|
+
})
|
|
11
|
+
};
|
|
12
|
+
var RemoteRealtimeClient = class {
|
|
13
|
+
constructor(config = {}) {
|
|
14
|
+
this.config = config;
|
|
15
|
+
this.adapters = new Map(
|
|
16
|
+
Object.entries({
|
|
17
|
+
...DEFAULT_REMOTE_REALTIME_ADAPTERS,
|
|
18
|
+
...config.adapters
|
|
19
|
+
})
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
transportClient = null;
|
|
23
|
+
adapters;
|
|
24
|
+
eventCallbacks = /* @__PURE__ */ new Set();
|
|
25
|
+
async connect(config) {
|
|
26
|
+
const adapterId = this.resolveAdapterId(config);
|
|
27
|
+
const factory = this.adapters.get(adapterId);
|
|
28
|
+
if (!factory) {
|
|
29
|
+
throw new Error(
|
|
30
|
+
`No realtime adapter registered for "${adapterId}". Registered adapters: ${Array.from(this.adapters.keys()).join(", ") || "(none)"}`
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
const transportClient = factory({
|
|
34
|
+
debug: this.config.debug,
|
|
35
|
+
requestBootstrap: (request) => this.requestBootstrap(request, { expectedAdapterId: adapterId })
|
|
36
|
+
});
|
|
37
|
+
for (const callback of this.eventCallbacks) {
|
|
38
|
+
transportClient.onEvent(callback);
|
|
39
|
+
}
|
|
40
|
+
this.transportClient = transportClient;
|
|
41
|
+
try {
|
|
42
|
+
await transportClient.connect(config);
|
|
43
|
+
} catch (error) {
|
|
44
|
+
this.transportClient = null;
|
|
45
|
+
throw error;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
async disconnect() {
|
|
49
|
+
if (!this.transportClient) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const transportClient = this.transportClient;
|
|
53
|
+
this.transportClient = null;
|
|
54
|
+
await transportClient.disconnect();
|
|
55
|
+
}
|
|
56
|
+
async sendText(text) {
|
|
57
|
+
await this.getActiveTransportClient().sendText(text);
|
|
58
|
+
}
|
|
59
|
+
async sendAudio(audio) {
|
|
60
|
+
await this.getActiveTransportClient().sendAudio(audio);
|
|
61
|
+
}
|
|
62
|
+
async interrupt() {
|
|
63
|
+
await this.getActiveTransportClient().interrupt();
|
|
64
|
+
}
|
|
65
|
+
onEvent(callback) {
|
|
66
|
+
this.eventCallbacks.add(callback);
|
|
67
|
+
this.transportClient?.onEvent(callback);
|
|
68
|
+
}
|
|
69
|
+
getActiveTransportClient() {
|
|
70
|
+
if (!this.transportClient) {
|
|
71
|
+
throw new Error("Realtime session not active");
|
|
72
|
+
}
|
|
73
|
+
return this.transportClient;
|
|
74
|
+
}
|
|
75
|
+
resolveAdapterId(config) {
|
|
76
|
+
if (this.config.resolveAdapterId) {
|
|
77
|
+
return this.config.resolveAdapterId(config);
|
|
78
|
+
}
|
|
79
|
+
const transport = config?.transport ?? "webrtc";
|
|
80
|
+
if (config?.provider === "openai" && transport === "webrtc") {
|
|
81
|
+
return OPENAI_REALTIME_ADAPTER;
|
|
82
|
+
}
|
|
83
|
+
throw new Error(
|
|
84
|
+
`No remote realtime adapter resolver for provider "${config?.provider ?? "(unspecified)"}" and transport "${transport}"`
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
async requestBootstrap(request, options) {
|
|
88
|
+
const response = await fetchWithTimeout(
|
|
89
|
+
this.config.apiEndpoint || "/api/realtime",
|
|
90
|
+
{
|
|
91
|
+
method: "POST",
|
|
92
|
+
headers: {
|
|
93
|
+
"Content-Type": "application/json"
|
|
94
|
+
},
|
|
95
|
+
body: JSON.stringify(request)
|
|
96
|
+
},
|
|
97
|
+
`Realtime session request timed out after ${DEFAULT_REQUEST_TIMEOUT_MS}ms`
|
|
98
|
+
);
|
|
99
|
+
if (!response.ok) {
|
|
100
|
+
const errorText = await response.text();
|
|
101
|
+
throw new Error(`Failed to create Realtime session: ${errorText}`);
|
|
102
|
+
}
|
|
103
|
+
const bootstrap = await response.json();
|
|
104
|
+
if (!isRealtimeSessionBootstrap(bootstrap)) {
|
|
105
|
+
throw new Error("Invalid realtime session bootstrap response");
|
|
106
|
+
}
|
|
107
|
+
if (bootstrap.adapter !== options.expectedAdapterId) {
|
|
108
|
+
throw new Error(
|
|
109
|
+
`Realtime session bootstrap adapter mismatch: expected ${options.expectedAdapterId}, received ${bootstrap.adapter}`
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
return bootstrap;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
function createRemoteRealtimeClient(config) {
|
|
116
|
+
return new RemoteRealtimeClient(config);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export { DEFAULT_REMOTE_REALTIME_ADAPTERS, RemoteRealtimeClient, createRemoteRealtimeClient };
|
|
120
|
+
//# sourceMappingURL=index.js.map
|
|
121
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type {\n RealtimeSessionBootstrap,\n RealtimeSessionConfig,\n RealtimeSessionRequest,\n} from \"@charivo/core\";\nimport { OPENAI_REALTIME_ADAPTER } from \"@charivo/core\";\nimport { createOpenAIRealtimeClient } from \"@charivo/realtime-client-openai\";\nimport type {\n RealtimeTransportClient,\n RealtimeTransportEvent,\n} from \"@charivo/realtime-core\";\nimport {\n DEFAULT_REQUEST_TIMEOUT_MS,\n fetchWithTimeout,\n isRealtimeSessionBootstrap,\n} from \"@charivo/shared\";\n\nexport interface RemoteRealtimeAdapterFactoryOptions {\n debug?: boolean;\n requestBootstrap: (\n request: RealtimeSessionRequest,\n ) => Promise<RealtimeSessionBootstrap>;\n}\n\nexport type RemoteRealtimeAdapterFactory = (\n options: RemoteRealtimeAdapterFactoryOptions,\n) => RealtimeTransportClient;\n\nexport const DEFAULT_REMOTE_REALTIME_ADAPTERS = {\n [OPENAI_REALTIME_ADAPTER]: (options) =>\n createOpenAIRealtimeClient({\n debug: options.debug,\n sessionBootstrap: options.requestBootstrap,\n }),\n} satisfies Record<string, RemoteRealtimeAdapterFactory>;\n\nexport interface RemoteRealtimeClientConfig {\n apiEndpoint?: string;\n debug?: boolean;\n adapters?: Record<string, RemoteRealtimeAdapterFactory>;\n resolveAdapterId?: (config?: RealtimeSessionConfig) => string;\n}\n\n/**\n * Production browser client for server-mediated realtime sessions.\n *\n * It resolves a browser-side transport adapter, forwards bootstrap requests to\n * your server route, and validates that the returned bootstrap matches the\n * selected adapter.\n */\nexport class RemoteRealtimeClient implements RealtimeTransportClient {\n private transportClient: RealtimeTransportClient | null = null;\n private readonly adapters: Map<string, RemoteRealtimeAdapterFactory>;\n private readonly eventCallbacks = new Set<\n (event: RealtimeTransportEvent) => void\n >();\n\n constructor(private config: RemoteRealtimeClientConfig = {}) {\n this.adapters = new Map(\n Object.entries({\n ...DEFAULT_REMOTE_REALTIME_ADAPTERS,\n ...config.adapters,\n }),\n );\n }\n\n async connect(config?: RealtimeSessionConfig): Promise<void> {\n const adapterId = this.resolveAdapterId(config);\n const factory = this.adapters.get(adapterId);\n\n if (!factory) {\n throw new Error(\n `No realtime adapter registered for \"${adapterId}\". Registered adapters: ${Array.from(this.adapters.keys()).join(\", \") || \"(none)\"}`,\n );\n }\n\n const transportClient = factory({\n debug: this.config.debug,\n requestBootstrap: (request) =>\n this.requestBootstrap(request, { expectedAdapterId: adapterId }),\n });\n\n for (const callback of this.eventCallbacks) {\n transportClient.onEvent(callback);\n }\n\n this.transportClient = transportClient;\n\n try {\n await transportClient.connect(config);\n } catch (error) {\n this.transportClient = null;\n throw error;\n }\n }\n\n async disconnect(): Promise<void> {\n if (!this.transportClient) {\n return;\n }\n\n const transportClient = this.transportClient;\n this.transportClient = null;\n await transportClient.disconnect();\n }\n\n async sendText(text: string): Promise<void> {\n await this.getActiveTransportClient().sendText(text);\n }\n\n async sendAudio(audio: ArrayBuffer): Promise<void> {\n await this.getActiveTransportClient().sendAudio(audio);\n }\n\n async interrupt(): Promise<void> {\n await this.getActiveTransportClient().interrupt();\n }\n\n onEvent(callback: (event: RealtimeTransportEvent) => void): void {\n this.eventCallbacks.add(callback);\n this.transportClient?.onEvent(callback);\n }\n\n private getActiveTransportClient(): RealtimeTransportClient {\n if (!this.transportClient) {\n throw new Error(\"Realtime session not active\");\n }\n\n return this.transportClient;\n }\n\n private resolveAdapterId(config?: RealtimeSessionConfig): string {\n if (this.config.resolveAdapterId) {\n return this.config.resolveAdapterId(config);\n }\n\n const transport = config?.transport ?? \"webrtc\";\n if (config?.provider === \"openai\" && transport === \"webrtc\") {\n return OPENAI_REALTIME_ADAPTER;\n }\n\n throw new Error(\n `No remote realtime adapter resolver for provider \"${config?.provider ?? \"(unspecified)\"}\" and transport \"${transport}\"`,\n );\n }\n\n private async requestBootstrap(\n request: RealtimeSessionRequest,\n options: { expectedAdapterId: string },\n ): Promise<RealtimeSessionBootstrap> {\n const response = await fetchWithTimeout(\n this.config.apiEndpoint || \"/api/realtime\",\n {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(request),\n },\n `Realtime session request timed out after ${DEFAULT_REQUEST_TIMEOUT_MS}ms`,\n );\n\n if (!response.ok) {\n const errorText = await response.text();\n throw new Error(`Failed to create Realtime session: ${errorText}`);\n }\n\n const bootstrap = (await response.json()) as unknown;\n if (!isRealtimeSessionBootstrap(bootstrap)) {\n throw new Error(\"Invalid realtime session bootstrap response\");\n }\n\n if (bootstrap.adapter !== options.expectedAdapterId) {\n throw new Error(\n `Realtime session bootstrap adapter mismatch: expected ${options.expectedAdapterId}, received ${bootstrap.adapter}`,\n );\n }\n\n return bootstrap as RealtimeSessionBootstrap;\n }\n}\n\nexport function createRemoteRealtimeClient(\n config?: RemoteRealtimeClientConfig,\n): RealtimeTransportClient {\n return new RemoteRealtimeClient(config);\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@charivo/realtime-client-remote",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Remote realtime client for Charivo (calls server API)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"module": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@charivo/core": "0.3.0",
|
|
21
|
+
"@charivo/realtime-client-openai": "0.1.0",
|
|
22
|
+
"@charivo/shared": "0.1.0",
|
|
23
|
+
"@charivo/realtime-core": "0.2.0"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"tsup": "^8.5.0",
|
|
27
|
+
"typescript": "^5.9.2"
|
|
28
|
+
},
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
},
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "https://github.com/zeikar/charivo.git",
|
|
36
|
+
"directory": "packages/realtime-client-remote"
|
|
37
|
+
},
|
|
38
|
+
"author": {
|
|
39
|
+
"name": "Zeikar",
|
|
40
|
+
"url": "https://github.com/zeikar"
|
|
41
|
+
},
|
|
42
|
+
"homepage": "https://github.com/zeikar/charivo#readme",
|
|
43
|
+
"bugs": {
|
|
44
|
+
"url": "https://github.com/zeikar/charivo/issues"
|
|
45
|
+
},
|
|
46
|
+
"scripts": {
|
|
47
|
+
"build": "tsup",
|
|
48
|
+
"dev": "tsup --watch",
|
|
49
|
+
"typecheck": "tsc --noEmit"
|
|
50
|
+
}
|
|
51
|
+
}
|