@jchaffin/voicekit 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.
@@ -0,0 +1,80 @@
1
+ export { S as ServerAdapter, g as ServerSessionConfig } from './types-DY31oVB1.mjs';
2
+
3
+ /**
4
+ * Server-side utilities for VoiceKit
5
+ * Use these in your API routes (Next.js, Express, etc.)
6
+ *
7
+ * This module provides the legacy OpenAI-specific helpers.
8
+ * For provider-agnostic server adapters, import from the adapter entrypoints:
9
+ * import { openaiServer } from '@jchaffin/voicekit/openai'
10
+ * import { livekitServer } from '@jchaffin/voicekit/livekit'
11
+ */
12
+
13
+ interface SessionConfig {
14
+ /** OpenAI API key (defaults to OPENAI_API_KEY env var) */
15
+ apiKey?: string;
16
+ /** Model to use */
17
+ model?: string;
18
+ /** Voice ID */
19
+ voice?: string;
20
+ /** Instructions for the agent */
21
+ instructions?: string;
22
+ /** Expiration time in seconds (default: 600 = 10 minutes) */
23
+ expiresIn?: number;
24
+ }
25
+ /**
26
+ * Create an ephemeral session key for OpenAI Realtime API
27
+ *
28
+ * @example Next.js App Router
29
+ * ```ts
30
+ * // app/api/session/route.ts
31
+ * import { createSessionHandler } from '@jchaffin/voicekit/server';
32
+ * export const POST = createSessionHandler();
33
+ * ```
34
+ *
35
+ * @example Next.js with config
36
+ * ```ts
37
+ * export const POST = createSessionHandler({
38
+ * model: 'gpt-realtime',
39
+ * voice: 'alloy'
40
+ * });
41
+ * ```
42
+ */
43
+ declare function createSessionHandler(config?: SessionConfig): (request?: Request) => Promise<Response>;
44
+ /**
45
+ * Get an ephemeral key directly (for custom server implementations)
46
+ *
47
+ * @example Express
48
+ * ```ts
49
+ * import { getEphemeralKey } from '@jchaffin/voicekit/server';
50
+ *
51
+ * app.post('/api/session', async (req, res) => {
52
+ * const result = await getEphemeralKey();
53
+ * if (result.error) {
54
+ * return res.status(500).json({ error: result.error });
55
+ * }
56
+ * res.json({ ephemeralKey: result.ephemeralKey });
57
+ * });
58
+ * ```
59
+ */
60
+ declare function getEphemeralKey(config?: SessionConfig): Promise<{
61
+ ephemeralKey: string;
62
+ error?: never;
63
+ } | {
64
+ ephemeralKey?: never;
65
+ error: string;
66
+ }>;
67
+ /**
68
+ * Create CORS headers for API routes
69
+ */
70
+ declare function corsHeaders(origin?: string): {
71
+ 'Access-Control-Allow-Origin': string;
72
+ 'Access-Control-Allow-Methods': string;
73
+ 'Access-Control-Allow-Headers': string;
74
+ };
75
+ /**
76
+ * Handle OPTIONS preflight request
77
+ */
78
+ declare function handleOptions(origin?: string): Response;
79
+
80
+ export { type SessionConfig, corsHeaders, createSessionHandler, getEphemeralKey, handleOptions };
@@ -0,0 +1,80 @@
1
+ export { S as ServerAdapter, g as ServerSessionConfig } from './types-DY31oVB1.js';
2
+
3
+ /**
4
+ * Server-side utilities for VoiceKit
5
+ * Use these in your API routes (Next.js, Express, etc.)
6
+ *
7
+ * This module provides the legacy OpenAI-specific helpers.
8
+ * For provider-agnostic server adapters, import from the adapter entrypoints:
9
+ * import { openaiServer } from '@jchaffin/voicekit/openai'
10
+ * import { livekitServer } from '@jchaffin/voicekit/livekit'
11
+ */
12
+
13
+ interface SessionConfig {
14
+ /** OpenAI API key (defaults to OPENAI_API_KEY env var) */
15
+ apiKey?: string;
16
+ /** Model to use */
17
+ model?: string;
18
+ /** Voice ID */
19
+ voice?: string;
20
+ /** Instructions for the agent */
21
+ instructions?: string;
22
+ /** Expiration time in seconds (default: 600 = 10 minutes) */
23
+ expiresIn?: number;
24
+ }
25
+ /**
26
+ * Create an ephemeral session key for OpenAI Realtime API
27
+ *
28
+ * @example Next.js App Router
29
+ * ```ts
30
+ * // app/api/session/route.ts
31
+ * import { createSessionHandler } from '@jchaffin/voicekit/server';
32
+ * export const POST = createSessionHandler();
33
+ * ```
34
+ *
35
+ * @example Next.js with config
36
+ * ```ts
37
+ * export const POST = createSessionHandler({
38
+ * model: 'gpt-realtime',
39
+ * voice: 'alloy'
40
+ * });
41
+ * ```
42
+ */
43
+ declare function createSessionHandler(config?: SessionConfig): (request?: Request) => Promise<Response>;
44
+ /**
45
+ * Get an ephemeral key directly (for custom server implementations)
46
+ *
47
+ * @example Express
48
+ * ```ts
49
+ * import { getEphemeralKey } from '@jchaffin/voicekit/server';
50
+ *
51
+ * app.post('/api/session', async (req, res) => {
52
+ * const result = await getEphemeralKey();
53
+ * if (result.error) {
54
+ * return res.status(500).json({ error: result.error });
55
+ * }
56
+ * res.json({ ephemeralKey: result.ephemeralKey });
57
+ * });
58
+ * ```
59
+ */
60
+ declare function getEphemeralKey(config?: SessionConfig): Promise<{
61
+ ephemeralKey: string;
62
+ error?: never;
63
+ } | {
64
+ ephemeralKey?: never;
65
+ error: string;
66
+ }>;
67
+ /**
68
+ * Create CORS headers for API routes
69
+ */
70
+ declare function corsHeaders(origin?: string): {
71
+ 'Access-Control-Allow-Origin': string;
72
+ 'Access-Control-Allow-Methods': string;
73
+ 'Access-Control-Allow-Headers': string;
74
+ };
75
+ /**
76
+ * Handle OPTIONS preflight request
77
+ */
78
+ declare function handleOptions(origin?: string): Response;
79
+
80
+ export { type SessionConfig, corsHeaders, createSessionHandler, getEphemeralKey, handleOptions };
package/dist/server.js ADDED
@@ -0,0 +1,147 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/server.ts
21
+ var server_exports = {};
22
+ __export(server_exports, {
23
+ corsHeaders: () => corsHeaders,
24
+ createSessionHandler: () => createSessionHandler,
25
+ getEphemeralKey: () => getEphemeralKey,
26
+ handleOptions: () => handleOptions
27
+ });
28
+ module.exports = __toCommonJS(server_exports);
29
+ function createSessionHandler(config = {}) {
30
+ return async function handler(request) {
31
+ try {
32
+ const apiKey = config.apiKey || process.env.OPENAI_API_KEY;
33
+ if (!apiKey) {
34
+ return Response.json(
35
+ { error: "OpenAI API key not configured" },
36
+ { status: 500 }
37
+ );
38
+ }
39
+ const response = await fetch("https://api.openai.com/v1/realtime/client_secrets", {
40
+ method: "POST",
41
+ headers: {
42
+ "Authorization": `Bearer ${apiKey}`,
43
+ "Content-Type": "application/json"
44
+ },
45
+ body: JSON.stringify({
46
+ expires_after: {
47
+ anchor: "created_at",
48
+ seconds: config.expiresIn || 600
49
+ },
50
+ session: {
51
+ type: "realtime",
52
+ model: config.model || "gpt-realtime",
53
+ ...config.voice && {
54
+ audio: {
55
+ output: { voice: config.voice }
56
+ }
57
+ },
58
+ ...config.instructions && { instructions: config.instructions }
59
+ }
60
+ })
61
+ });
62
+ if (!response.ok) {
63
+ const error = await response.text();
64
+ console.error("OpenAI client_secrets error:", error);
65
+ return Response.json(
66
+ { error: `OpenAI API error: ${response.status}` },
67
+ { status: 500 }
68
+ );
69
+ }
70
+ const data = await response.json();
71
+ if (!data.value) {
72
+ return Response.json(
73
+ { error: "Invalid response from OpenAI" },
74
+ { status: 500 }
75
+ );
76
+ }
77
+ return Response.json({ ephemeralKey: data.value });
78
+ } catch (error) {
79
+ console.error("Session handler error:", error);
80
+ return Response.json(
81
+ { error: "Internal server error" },
82
+ { status: 500 }
83
+ );
84
+ }
85
+ };
86
+ }
87
+ async function getEphemeralKey(config = {}) {
88
+ try {
89
+ const apiKey = config.apiKey || process.env.OPENAI_API_KEY;
90
+ if (!apiKey) {
91
+ return { error: "OpenAI API key not configured" };
92
+ }
93
+ const response = await fetch("https://api.openai.com/v1/realtime/client_secrets", {
94
+ method: "POST",
95
+ headers: {
96
+ "Authorization": `Bearer ${apiKey}`,
97
+ "Content-Type": "application/json"
98
+ },
99
+ body: JSON.stringify({
100
+ expires_after: {
101
+ anchor: "created_at",
102
+ seconds: config.expiresIn || 600
103
+ },
104
+ session: {
105
+ type: "realtime",
106
+ model: config.model || "gpt-realtime",
107
+ ...config.voice && {
108
+ audio: {
109
+ output: { voice: config.voice }
110
+ }
111
+ },
112
+ ...config.instructions && { instructions: config.instructions }
113
+ }
114
+ })
115
+ });
116
+ if (!response.ok) {
117
+ return { error: `OpenAI API error: ${response.status}` };
118
+ }
119
+ const data = await response.json();
120
+ if (!data.value) {
121
+ return { error: "Invalid response from OpenAI" };
122
+ }
123
+ return { ephemeralKey: data.value };
124
+ } catch (error) {
125
+ return { error: String(error) };
126
+ }
127
+ }
128
+ function corsHeaders(origin = "*") {
129
+ return {
130
+ "Access-Control-Allow-Origin": origin,
131
+ "Access-Control-Allow-Methods": "POST, OPTIONS",
132
+ "Access-Control-Allow-Headers": "Content-Type"
133
+ };
134
+ }
135
+ function handleOptions(origin = "*") {
136
+ return new Response(null, {
137
+ status: 200,
138
+ headers: corsHeaders(origin)
139
+ });
140
+ }
141
+ // Annotate the CommonJS export names for ESM import in node:
142
+ 0 && (module.exports = {
143
+ corsHeaders,
144
+ createSessionHandler,
145
+ getEphemeralKey,
146
+ handleOptions
147
+ });
@@ -0,0 +1,119 @@
1
+ // src/server.ts
2
+ function createSessionHandler(config = {}) {
3
+ return async function handler(request) {
4
+ try {
5
+ const apiKey = config.apiKey || process.env.OPENAI_API_KEY;
6
+ if (!apiKey) {
7
+ return Response.json(
8
+ { error: "OpenAI API key not configured" },
9
+ { status: 500 }
10
+ );
11
+ }
12
+ const response = await fetch("https://api.openai.com/v1/realtime/client_secrets", {
13
+ method: "POST",
14
+ headers: {
15
+ "Authorization": `Bearer ${apiKey}`,
16
+ "Content-Type": "application/json"
17
+ },
18
+ body: JSON.stringify({
19
+ expires_after: {
20
+ anchor: "created_at",
21
+ seconds: config.expiresIn || 600
22
+ },
23
+ session: {
24
+ type: "realtime",
25
+ model: config.model || "gpt-realtime",
26
+ ...config.voice && {
27
+ audio: {
28
+ output: { voice: config.voice }
29
+ }
30
+ },
31
+ ...config.instructions && { instructions: config.instructions }
32
+ }
33
+ })
34
+ });
35
+ if (!response.ok) {
36
+ const error = await response.text();
37
+ console.error("OpenAI client_secrets error:", error);
38
+ return Response.json(
39
+ { error: `OpenAI API error: ${response.status}` },
40
+ { status: 500 }
41
+ );
42
+ }
43
+ const data = await response.json();
44
+ if (!data.value) {
45
+ return Response.json(
46
+ { error: "Invalid response from OpenAI" },
47
+ { status: 500 }
48
+ );
49
+ }
50
+ return Response.json({ ephemeralKey: data.value });
51
+ } catch (error) {
52
+ console.error("Session handler error:", error);
53
+ return Response.json(
54
+ { error: "Internal server error" },
55
+ { status: 500 }
56
+ );
57
+ }
58
+ };
59
+ }
60
+ async function getEphemeralKey(config = {}) {
61
+ try {
62
+ const apiKey = config.apiKey || process.env.OPENAI_API_KEY;
63
+ if (!apiKey) {
64
+ return { error: "OpenAI API key not configured" };
65
+ }
66
+ const response = await fetch("https://api.openai.com/v1/realtime/client_secrets", {
67
+ method: "POST",
68
+ headers: {
69
+ "Authorization": `Bearer ${apiKey}`,
70
+ "Content-Type": "application/json"
71
+ },
72
+ body: JSON.stringify({
73
+ expires_after: {
74
+ anchor: "created_at",
75
+ seconds: config.expiresIn || 600
76
+ },
77
+ session: {
78
+ type: "realtime",
79
+ model: config.model || "gpt-realtime",
80
+ ...config.voice && {
81
+ audio: {
82
+ output: { voice: config.voice }
83
+ }
84
+ },
85
+ ...config.instructions && { instructions: config.instructions }
86
+ }
87
+ })
88
+ });
89
+ if (!response.ok) {
90
+ return { error: `OpenAI API error: ${response.status}` };
91
+ }
92
+ const data = await response.json();
93
+ if (!data.value) {
94
+ return { error: "Invalid response from OpenAI" };
95
+ }
96
+ return { ephemeralKey: data.value };
97
+ } catch (error) {
98
+ return { error: String(error) };
99
+ }
100
+ }
101
+ function corsHeaders(origin = "*") {
102
+ return {
103
+ "Access-Control-Allow-Origin": origin,
104
+ "Access-Control-Allow-Methods": "POST, OPTIONS",
105
+ "Access-Control-Allow-Headers": "Content-Type"
106
+ };
107
+ }
108
+ function handleOptions(origin = "*") {
109
+ return new Response(null, {
110
+ status: 200,
111
+ headers: corsHeaders(origin)
112
+ });
113
+ }
114
+ export {
115
+ corsHeaders,
116
+ createSessionHandler,
117
+ getEphemeralKey,
118
+ handleOptions
119
+ };
@@ -0,0 +1,150 @@
1
+ type VoiceStatus$1 = 'DISCONNECTED' | 'CONNECTING' | 'CONNECTED';
2
+ interface TranscriptMessage {
3
+ id: string;
4
+ role: 'user' | 'assistant';
5
+ text: string;
6
+ timestamp: Date;
7
+ status: 'pending' | 'complete';
8
+ }
9
+ type ToolParamType = 'string' | 'number' | 'boolean' | 'array' | 'object';
10
+ interface ToolParamDefinition {
11
+ type: ToolParamType;
12
+ description?: string;
13
+ enum?: string[];
14
+ default?: unknown;
15
+ }
16
+ interface ToolDefinition<TParams = Record<string, unknown>, TResult = unknown> {
17
+ name: string;
18
+ description: string;
19
+ parameters: {
20
+ type: 'object';
21
+ properties: Record<string, ToolParamDefinition>;
22
+ required?: string[];
23
+ };
24
+ execute: (params: TParams) => Promise<TResult> | TResult;
25
+ }
26
+ interface AgentConfig {
27
+ name: string;
28
+ instructions: string;
29
+ tools?: ToolDefinition[];
30
+ voice?: string;
31
+ }
32
+ interface VoiceConfig {
33
+ /** API endpoint that returns session token */
34
+ sessionEndpoint?: string;
35
+ /** Model identifier (provider-specific) */
36
+ model?: string;
37
+ /** Audio codec preference */
38
+ codec?: 'opus' | 'pcmu' | 'pcma';
39
+ /** Language for transcription */
40
+ language?: string;
41
+ onStatusChange?: (status: VoiceStatus$1) => void;
42
+ onTranscriptUpdate?: (messages: TranscriptMessage[]) => void;
43
+ onToolCall?: (toolName: string, params: unknown, result: unknown) => void;
44
+ onError?: (error: Error) => void;
45
+ }
46
+ interface VoiceProviderProps extends VoiceConfig {
47
+ /** Provider adapter (e.g. openai(), livekit(), deepgram()) */
48
+ adapter: VoiceAdapter;
49
+ /** Agent configuration */
50
+ agent: VoiceAgentConfig;
51
+ children: React.ReactNode;
52
+ }
53
+ interface VoiceContextValue {
54
+ status: VoiceStatus$1;
55
+ connect: () => Promise<void>;
56
+ disconnect: () => Promise<void>;
57
+ transcript: TranscriptMessage[];
58
+ clearTranscript: () => void;
59
+ sendMessage: (text: string) => void;
60
+ interrupt: () => void;
61
+ mute: (muted: boolean) => void;
62
+ isMuted: boolean;
63
+ agent: VoiceAgentConfig;
64
+ }
65
+
66
+ type VoiceStatus = 'DISCONNECTED' | 'CONNECTING' | 'CONNECTED';
67
+ interface VoiceAgentConfig {
68
+ name: string;
69
+ instructions: string;
70
+ tools?: ToolDefinition[];
71
+ voice?: string;
72
+ handoffs?: VoiceAgentConfig[];
73
+ }
74
+ interface SessionEvents {
75
+ [event: string]: (...args: any[]) => void;
76
+ status_change: (status: VoiceStatus) => void;
77
+ user_speech_started: () => void;
78
+ user_transcript: (data: TranscriptData) => void;
79
+ assistant_transcript: (data: TranscriptData) => void;
80
+ tool_call_start: (name: string, input: unknown) => void;
81
+ tool_call_end: (name: string, input: unknown, output: unknown) => void;
82
+ agent_handoff: (from: string, to: string) => void;
83
+ guardrail_tripped: (info: unknown) => void;
84
+ audio_delta: (itemId: string, delta: string) => void;
85
+ error: (error: Error) => void;
86
+ /** Escape hatch for provider-specific events not covered by normalized types */
87
+ raw_event: (event: unknown) => void;
88
+ }
89
+ interface TranscriptData {
90
+ itemId: string;
91
+ delta?: string;
92
+ text?: string;
93
+ isFinal: boolean;
94
+ }
95
+ interface VoiceSession {
96
+ connect(config: ConnectConfig): Promise<void>;
97
+ disconnect(): Promise<void>;
98
+ sendMessage(text: string): void | Promise<void>;
99
+ interrupt(): void;
100
+ mute(muted: boolean): void;
101
+ sendRawEvent?(event: Record<string, unknown>): void;
102
+ on<E extends string & keyof SessionEvents>(event: E, handler: SessionEvents[E]): void;
103
+ off<E extends string & keyof SessionEvents>(event: E, handler: SessionEvents[E]): void;
104
+ }
105
+ interface ConnectConfig {
106
+ /** Authentication token/key obtained from server handler */
107
+ authToken: string;
108
+ /** HTML audio element for playback */
109
+ audioElement?: HTMLAudioElement;
110
+ /** Extra context passed to the session */
111
+ context?: Record<string, unknown>;
112
+ /** Output guardrails */
113
+ outputGuardrails?: unknown[];
114
+ }
115
+ interface SessionOptions {
116
+ model?: string;
117
+ language?: string;
118
+ codec?: string;
119
+ voice?: string;
120
+ /** Provider-specific options */
121
+ [key: string]: unknown;
122
+ }
123
+ interface VoiceAdapter {
124
+ /** Unique name for this adapter (e.g. 'openai', 'livekit') */
125
+ readonly name: string;
126
+ /** Create a session for the given agent */
127
+ createSession(agent: VoiceAgentConfig, options?: SessionOptions): VoiceSession;
128
+ }
129
+ interface ServerSessionConfig {
130
+ /** Provider-specific API key */
131
+ apiKey?: string;
132
+ model?: string;
133
+ voice?: string;
134
+ instructions?: string;
135
+ expiresIn?: number;
136
+ /** Provider-specific options */
137
+ [key: string]: unknown;
138
+ }
139
+ interface ServerAdapter {
140
+ createSessionHandler(config?: ServerSessionConfig): (request?: Request) => Promise<Response>;
141
+ getSessionToken(config?: ServerSessionConfig): Promise<{
142
+ token: string;
143
+ error?: string;
144
+ } | {
145
+ token?: string;
146
+ error: string;
147
+ }>;
148
+ }
149
+
150
+ export type { AgentConfig as A, ConnectConfig as C, ServerAdapter as S, TranscriptMessage as T, VoiceProviderProps as V, VoiceContextValue as a, VoiceAgentConfig as b, ToolDefinition as c, ToolParamDefinition as d, VoiceAdapter as e, VoiceStatus$1 as f, ServerSessionConfig as g, SessionEvents as h, SessionOptions as i, TranscriptData as j, VoiceConfig as k, VoiceSession as l };