@ai-sdk/cartesia 0.0.1 → 3.0.2
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/CHANGELOG.md +23 -0
- package/LICENSE +13 -0
- package/README.md +47 -0
- package/dist/index.d.ts +185 -0
- package/dist/index.js +724 -0
- package/dist/index.js.map +1 -0
- package/docs/95-cartesia.mdx +262 -0
- package/package.json +69 -7
- package/src/cartesia-api-types.ts +17 -0
- package/src/cartesia-config.ts +9 -0
- package/src/cartesia-error.ts +17 -0
- package/src/cartesia-provider.ts +190 -0
- package/src/cartesia-realtime-model-options.ts +3 -0
- package/src/cartesia-realtime-model.ts +326 -0
- package/src/cartesia-speech-api-types.ts +64 -0
- package/src/cartesia-speech-model-options.ts +40 -0
- package/src/cartesia-speech-model.ts +285 -0
- package/src/cartesia-speech-options.ts +9 -0
- package/src/cartesia-transcription-model-options.ts +13 -0
- package/src/cartesia-transcription-model.ts +157 -0
- package/src/cartesia-transcription-options.ts +1 -0
- package/src/index.ts +20 -0
- package/src/transcript-test.mp3 +0 -0
- package/src/version.ts +6 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type Experimental_RealtimeFactoryV4 as RealtimeFactoryV4,
|
|
3
|
+
type Experimental_RealtimeFactoryV4GetTokenOptions as RealtimeFactoryV4GetTokenOptions,
|
|
4
|
+
NoSuchModelError,
|
|
5
|
+
type TranscriptionModelV4,
|
|
6
|
+
type SpeechModelV4,
|
|
7
|
+
type ProviderV4,
|
|
8
|
+
} from '@ai-sdk/provider';
|
|
9
|
+
import {
|
|
10
|
+
loadApiKey,
|
|
11
|
+
withUserAgentSuffix,
|
|
12
|
+
type FetchFunction,
|
|
13
|
+
} from '@ai-sdk/provider-utils';
|
|
14
|
+
import { CartesiaTranscriptionModel } from './cartesia-transcription-model';
|
|
15
|
+
import type { CartesiaTranscriptionModelId } from './cartesia-transcription-options';
|
|
16
|
+
import { CartesiaSpeechModel } from './cartesia-speech-model';
|
|
17
|
+
import type { CartesiaSpeechModelId } from './cartesia-speech-options';
|
|
18
|
+
import { CartesiaRealtimeModel } from './cartesia-realtime-model';
|
|
19
|
+
import type { CartesiaRealtimeModelId } from './cartesia-realtime-model-options';
|
|
20
|
+
import { VERSION } from './version';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The Cartesia API version sent with every request via the `Cartesia-Version`
|
|
24
|
+
* header. See https://docs.cartesia.ai/api-reference/api-versioning
|
|
25
|
+
*/
|
|
26
|
+
const CARTESIA_API_VERSION = '2026-03-01';
|
|
27
|
+
|
|
28
|
+
export interface CartesiaProvider extends ProviderV4 {
|
|
29
|
+
(
|
|
30
|
+
modelId: CartesiaSpeechModelId,
|
|
31
|
+
settings?: {},
|
|
32
|
+
): {
|
|
33
|
+
speech: CartesiaSpeechModel;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Creates a model for transcription.
|
|
38
|
+
*/
|
|
39
|
+
transcription(modelId: CartesiaTranscriptionModelId): TranscriptionModelV4;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Creates a model for speech generation.
|
|
43
|
+
*/
|
|
44
|
+
speech(modelId: CartesiaSpeechModelId): SpeechModelV4;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Creates a realtime Ink speech-to-text model.
|
|
48
|
+
*/
|
|
49
|
+
experimental_realtime: RealtimeFactoryV4;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @deprecated Use `embeddingModel` instead.
|
|
53
|
+
*/
|
|
54
|
+
textEmbeddingModel(modelId: string): never;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface CartesiaProviderSettings {
|
|
58
|
+
/**
|
|
59
|
+
* API key for authenticating requests.
|
|
60
|
+
*/
|
|
61
|
+
apiKey?: string;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* The Cartesia API version to use (sent via the `Cartesia-Version` header).
|
|
65
|
+
*/
|
|
66
|
+
version?: string;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Custom headers to include in the requests.
|
|
70
|
+
*/
|
|
71
|
+
headers?: Record<string, string>;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Custom fetch implementation. You can use it as a middleware to intercept requests,
|
|
75
|
+
* or to provide a custom fetch implementation for e.g. testing.
|
|
76
|
+
*/
|
|
77
|
+
fetch?: FetchFunction;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Create a Cartesia provider instance.
|
|
82
|
+
*/
|
|
83
|
+
export function createCartesia(
|
|
84
|
+
options: CartesiaProviderSettings = {},
|
|
85
|
+
): CartesiaProvider {
|
|
86
|
+
const getHeaders = () =>
|
|
87
|
+
withUserAgentSuffix(
|
|
88
|
+
{
|
|
89
|
+
Authorization: `Bearer ${loadApiKey({
|
|
90
|
+
apiKey: options.apiKey,
|
|
91
|
+
environmentVariableName: 'CARTESIA_API_KEY',
|
|
92
|
+
description: 'Cartesia',
|
|
93
|
+
})}`,
|
|
94
|
+
'Cartesia-Version': options.version ?? CARTESIA_API_VERSION,
|
|
95
|
+
...options.headers,
|
|
96
|
+
},
|
|
97
|
+
`ai-sdk/cartesia/${VERSION}`,
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
const createTranscriptionModel = (modelId: CartesiaTranscriptionModelId) =>
|
|
101
|
+
new CartesiaTranscriptionModel(modelId, {
|
|
102
|
+
provider: `cartesia.transcription`,
|
|
103
|
+
url: ({ path }) => `https://api.cartesia.ai${path}`,
|
|
104
|
+
headers: getHeaders,
|
|
105
|
+
fetch: options.fetch,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const createSpeechModel = (modelId: CartesiaSpeechModelId) =>
|
|
109
|
+
new CartesiaSpeechModel(modelId, {
|
|
110
|
+
provider: `cartesia.speech`,
|
|
111
|
+
url: ({ path }) => `https://api.cartesia.ai${path}`,
|
|
112
|
+
headers: getHeaders,
|
|
113
|
+
fetch: options.fetch,
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const createRealtimeModel = (modelId: CartesiaRealtimeModelId) =>
|
|
117
|
+
new CartesiaRealtimeModel(modelId, {
|
|
118
|
+
provider: `cartesia.realtime`,
|
|
119
|
+
baseURL: 'https://api.cartesia.ai',
|
|
120
|
+
version: options.version ?? CARTESIA_API_VERSION,
|
|
121
|
+
headers: getHeaders,
|
|
122
|
+
fetch: options.fetch,
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const experimentalRealtimeFactory = Object.assign(
|
|
126
|
+
(modelId: CartesiaRealtimeModelId) => createRealtimeModel(modelId),
|
|
127
|
+
{
|
|
128
|
+
getToken: async (tokenOptions: RealtimeFactoryV4GetTokenOptions) => {
|
|
129
|
+
const secret = await createRealtimeModel(
|
|
130
|
+
tokenOptions.model as CartesiaRealtimeModelId,
|
|
131
|
+
).doCreateClientSecret({
|
|
132
|
+
sessionConfig: tokenOptions.sessionConfig,
|
|
133
|
+
expiresAfterSeconds: tokenOptions.expiresAfterSeconds,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
token: secret.token,
|
|
138
|
+
url: secret.url,
|
|
139
|
+
expiresAt: secret.expiresAt,
|
|
140
|
+
};
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
) as RealtimeFactoryV4;
|
|
144
|
+
|
|
145
|
+
const provider = function (modelId: CartesiaSpeechModelId) {
|
|
146
|
+
return {
|
|
147
|
+
speech: createSpeechModel(modelId),
|
|
148
|
+
};
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
provider.specificationVersion = 'v4' as const;
|
|
152
|
+
provider.transcription = createTranscriptionModel;
|
|
153
|
+
provider.transcriptionModel = createTranscriptionModel;
|
|
154
|
+
provider.speech = createSpeechModel;
|
|
155
|
+
provider.speechModel = createSpeechModel;
|
|
156
|
+
provider.experimental_realtime = experimentalRealtimeFactory;
|
|
157
|
+
|
|
158
|
+
// Required ProviderV4 methods that are not supported
|
|
159
|
+
provider.languageModel = (modelId: string) => {
|
|
160
|
+
throw new NoSuchModelError({
|
|
161
|
+
modelId,
|
|
162
|
+
modelType: 'languageModel',
|
|
163
|
+
message: 'Cartesia does not provide language models',
|
|
164
|
+
});
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
provider.embeddingModel = (modelId: string) => {
|
|
168
|
+
throw new NoSuchModelError({
|
|
169
|
+
modelId,
|
|
170
|
+
modelType: 'embeddingModel',
|
|
171
|
+
message: 'Cartesia does not provide embedding models',
|
|
172
|
+
});
|
|
173
|
+
};
|
|
174
|
+
provider.textEmbeddingModel = provider.embeddingModel;
|
|
175
|
+
|
|
176
|
+
provider.imageModel = (modelId: string) => {
|
|
177
|
+
throw new NoSuchModelError({
|
|
178
|
+
modelId,
|
|
179
|
+
modelType: 'imageModel',
|
|
180
|
+
message: 'Cartesia does not provide image models',
|
|
181
|
+
});
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
return provider as CartesiaProvider;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Default Cartesia provider instance.
|
|
189
|
+
*/
|
|
190
|
+
export const cartesia = createCartesia();
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Experimental_RealtimeModelV4 as RealtimeModelV4,
|
|
3
|
+
Experimental_RealtimeModelV4ClientEvent as RealtimeModelV4ClientEvent,
|
|
4
|
+
Experimental_RealtimeModelV4ClientSecretOptions as RealtimeModelV4ClientSecretOptions,
|
|
5
|
+
Experimental_RealtimeModelV4ClientSecretResult as RealtimeModelV4ClientSecretResult,
|
|
6
|
+
Experimental_RealtimeModelV4ServerEvent as RealtimeModelV4ServerEvent,
|
|
7
|
+
Experimental_RealtimeModelV4SessionConfig as RealtimeModelV4SessionConfig,
|
|
8
|
+
} from '@ai-sdk/provider';
|
|
9
|
+
import {
|
|
10
|
+
convertBase64ToUint8Array,
|
|
11
|
+
type FetchFunction,
|
|
12
|
+
} from '@ai-sdk/provider-utils';
|
|
13
|
+
import type { CartesiaRealtimeModelId } from './cartesia-realtime-model-options';
|
|
14
|
+
|
|
15
|
+
export type CartesiaRealtimeModelConfig = {
|
|
16
|
+
provider: string;
|
|
17
|
+
baseURL: string;
|
|
18
|
+
version: string;
|
|
19
|
+
headers: () => Record<string, string | undefined>;
|
|
20
|
+
fetch?: FetchFunction;
|
|
21
|
+
_internal?: {
|
|
22
|
+
currentDate?: () => Date;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
type CartesiaRealtimeWireEvent = {
|
|
27
|
+
type: string;
|
|
28
|
+
request_id?: string;
|
|
29
|
+
transcript?: string;
|
|
30
|
+
text?: string;
|
|
31
|
+
is_final?: boolean;
|
|
32
|
+
duration?: number;
|
|
33
|
+
message?: string;
|
|
34
|
+
error_code?: string;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
type ManualTranscriptState = {
|
|
38
|
+
duration: number;
|
|
39
|
+
events: CartesiaRealtimeWireEvent[];
|
|
40
|
+
text: string;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Realtime Ink model using Cartesia's STT WebSocket APIs.
|
|
45
|
+
*/
|
|
46
|
+
export class CartesiaRealtimeModel implements RealtimeModelV4 {
|
|
47
|
+
readonly specificationVersion = 'v4' as const;
|
|
48
|
+
readonly provider: string;
|
|
49
|
+
readonly modelId: CartesiaRealtimeModelId;
|
|
50
|
+
|
|
51
|
+
private readonly config: CartesiaRealtimeModelConfig;
|
|
52
|
+
private manualTranscript: ManualTranscriptState = {
|
|
53
|
+
duration: 0,
|
|
54
|
+
events: [],
|
|
55
|
+
text: '',
|
|
56
|
+
};
|
|
57
|
+
private sessionCreated = false;
|
|
58
|
+
private useTurnDetection = true;
|
|
59
|
+
|
|
60
|
+
constructor(
|
|
61
|
+
modelId: CartesiaRealtimeModelId,
|
|
62
|
+
config: CartesiaRealtimeModelConfig,
|
|
63
|
+
) {
|
|
64
|
+
this.modelId = modelId;
|
|
65
|
+
this.provider = config.provider;
|
|
66
|
+
this.config = config;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async doCreateClientSecret(
|
|
70
|
+
options: RealtimeModelV4ClientSecretOptions,
|
|
71
|
+
): Promise<RealtimeModelV4ClientSecretResult> {
|
|
72
|
+
const expiresIn = options.expiresAfterSeconds;
|
|
73
|
+
if (
|
|
74
|
+
expiresIn != null &&
|
|
75
|
+
(!Number.isInteger(expiresIn) || expiresIn <= 0 || expiresIn > 3600)
|
|
76
|
+
) {
|
|
77
|
+
throw new Error(
|
|
78
|
+
'Cartesia realtime client secrets must expire between 1 and 3600 seconds.',
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const sessionConfig = options.sessionConfig;
|
|
83
|
+
const language = sessionConfig?.inputAudioTranscription?.language;
|
|
84
|
+
if (language != null && language !== 'en') {
|
|
85
|
+
throw new Error('Cartesia Ink 2 currently supports English only.');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const fetchFn = this.config.fetch ?? fetch;
|
|
89
|
+
const response = await fetchFn(`${this.config.baseURL}/access-token`, {
|
|
90
|
+
method: 'POST',
|
|
91
|
+
headers: {
|
|
92
|
+
...this.config.headers(),
|
|
93
|
+
'Content-Type': 'application/json',
|
|
94
|
+
},
|
|
95
|
+
body: JSON.stringify({
|
|
96
|
+
grants: { stt: true },
|
|
97
|
+
...(expiresIn != null ? { expires_in: expiresIn } : {}),
|
|
98
|
+
}),
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
if (!response.ok) {
|
|
102
|
+
const text = await response.text();
|
|
103
|
+
throw new Error(
|
|
104
|
+
`Cartesia realtime client secret request failed: ${response.status} ${text}`,
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const data = (await response.json()) as { token?: unknown };
|
|
109
|
+
if (typeof data.token !== 'string' || data.token.length === 0) {
|
|
110
|
+
throw new Error(
|
|
111
|
+
'Cartesia realtime client secret response did not include a token.',
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const turnDetection = sessionConfig?.turnDetection;
|
|
116
|
+
const useTurnDetection =
|
|
117
|
+
turnDetection === undefined ||
|
|
118
|
+
(turnDetection !== null && turnDetection.type !== 'disabled');
|
|
119
|
+
const inputAudioFormat = sessionConfig?.inputAudioFormat;
|
|
120
|
+
const url = new URL(
|
|
121
|
+
useTurnDetection
|
|
122
|
+
? `${this.config.baseURL}/stt/turns/websocket`
|
|
123
|
+
: `${this.config.baseURL}/stt/websocket`,
|
|
124
|
+
);
|
|
125
|
+
url.protocol = url.protocol === 'http:' ? 'ws:' : 'wss:';
|
|
126
|
+
url.searchParams.set('model', this.modelId);
|
|
127
|
+
url.searchParams.set(
|
|
128
|
+
'encoding',
|
|
129
|
+
resolveCartesiaEncoding(inputAudioFormat?.type),
|
|
130
|
+
);
|
|
131
|
+
url.searchParams.set(
|
|
132
|
+
'sample_rate',
|
|
133
|
+
String(inputAudioFormat?.rate ?? 24000),
|
|
134
|
+
);
|
|
135
|
+
url.searchParams.set('cartesia_version', this.config.version);
|
|
136
|
+
if (!useTurnDetection && language != null) {
|
|
137
|
+
url.searchParams.set('language', language);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
token: data.token,
|
|
142
|
+
url: url.toString(),
|
|
143
|
+
...(expiresIn != null
|
|
144
|
+
? {
|
|
145
|
+
expiresAt:
|
|
146
|
+
Math.floor(
|
|
147
|
+
(
|
|
148
|
+
this.config._internal?.currentDate?.() ?? new Date()
|
|
149
|
+
).getTime() / 1000,
|
|
150
|
+
) + expiresIn,
|
|
151
|
+
}
|
|
152
|
+
: {}),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
getWebSocketConfig(options: { token: string; url: string }): {
|
|
157
|
+
url: string;
|
|
158
|
+
} {
|
|
159
|
+
const url = new URL(options.url);
|
|
160
|
+
this.useTurnDetection = url.pathname.includes('/stt/turns/websocket');
|
|
161
|
+
url.searchParams.set('access_token', options.token);
|
|
162
|
+
return { url: url.toString() };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
parseServerEvent(
|
|
166
|
+
raw: unknown,
|
|
167
|
+
): RealtimeModelV4ServerEvent | RealtimeModelV4ServerEvent[] {
|
|
168
|
+
const event = raw as CartesiaRealtimeWireEvent;
|
|
169
|
+
const requestId = event.request_id ?? 'cartesia-realtime';
|
|
170
|
+
let mapped: RealtimeModelV4ServerEvent | RealtimeModelV4ServerEvent[];
|
|
171
|
+
|
|
172
|
+
switch (event.type) {
|
|
173
|
+
case 'connected':
|
|
174
|
+
this.sessionCreated = true;
|
|
175
|
+
return {
|
|
176
|
+
type: 'session-created',
|
|
177
|
+
sessionId: event.request_id,
|
|
178
|
+
raw,
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
case 'turn.start':
|
|
182
|
+
mapped = { type: 'speech-started', itemId: requestId, raw };
|
|
183
|
+
break;
|
|
184
|
+
|
|
185
|
+
case 'turn.resume':
|
|
186
|
+
mapped = [
|
|
187
|
+
{ type: 'speech-started', itemId: requestId, raw },
|
|
188
|
+
{ type: 'custom', rawType: event.type, raw },
|
|
189
|
+
];
|
|
190
|
+
break;
|
|
191
|
+
|
|
192
|
+
case 'turn.end':
|
|
193
|
+
mapped = [
|
|
194
|
+
{ type: 'speech-stopped', itemId: requestId, raw },
|
|
195
|
+
{
|
|
196
|
+
type: 'input-transcription-completed',
|
|
197
|
+
itemId: requestId,
|
|
198
|
+
transcript: event.transcript ?? '',
|
|
199
|
+
raw,
|
|
200
|
+
},
|
|
201
|
+
];
|
|
202
|
+
break;
|
|
203
|
+
|
|
204
|
+
case 'transcript':
|
|
205
|
+
if (event.is_final === true) {
|
|
206
|
+
this.manualTranscript.text += event.text ?? '';
|
|
207
|
+
this.manualTranscript.duration += event.duration ?? 0;
|
|
208
|
+
this.manualTranscript.events.push(event);
|
|
209
|
+
}
|
|
210
|
+
mapped = { type: 'custom', rawType: event.type, raw };
|
|
211
|
+
break;
|
|
212
|
+
|
|
213
|
+
case 'flush_done':
|
|
214
|
+
mapped = this.finishManualTranscript(event, raw);
|
|
215
|
+
break;
|
|
216
|
+
|
|
217
|
+
case 'done': {
|
|
218
|
+
if (this.manualTranscript.text.length > 0) {
|
|
219
|
+
const completed = this.finishManualTranscript(event, raw);
|
|
220
|
+
mapped = Array.isArray(completed)
|
|
221
|
+
? [...completed, { type: 'custom', rawType: event.type, raw }]
|
|
222
|
+
: [completed, { type: 'custom', rawType: event.type, raw }];
|
|
223
|
+
} else {
|
|
224
|
+
mapped = { type: 'custom', rawType: event.type, raw };
|
|
225
|
+
}
|
|
226
|
+
break;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
case 'error':
|
|
230
|
+
mapped = {
|
|
231
|
+
type: 'error',
|
|
232
|
+
message: event.message ?? 'Unknown Cartesia realtime error',
|
|
233
|
+
code: event.error_code,
|
|
234
|
+
raw,
|
|
235
|
+
};
|
|
236
|
+
break;
|
|
237
|
+
|
|
238
|
+
default:
|
|
239
|
+
mapped = { type: 'custom', rawType: event.type, raw };
|
|
240
|
+
break;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return this.withSessionCreated(mapped, requestId, raw);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
serializeClientEvent(event: RealtimeModelV4ClientEvent): unknown {
|
|
247
|
+
switch (event.type) {
|
|
248
|
+
case 'session-update':
|
|
249
|
+
case 'input-audio-clear':
|
|
250
|
+
case 'response-create':
|
|
251
|
+
return null;
|
|
252
|
+
case 'input-audio-append':
|
|
253
|
+
return convertBase64ToUint8Array(event.audio);
|
|
254
|
+
case 'input-audio-commit':
|
|
255
|
+
return this.useTurnDetection ? null : 'finalize';
|
|
256
|
+
default:
|
|
257
|
+
throw new Error(
|
|
258
|
+
`Cartesia Ink 2 does not support realtime client event "${event.type}".`,
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
buildSessionConfig(_config: RealtimeModelV4SessionConfig): null {
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
private finishManualTranscript(
|
|
268
|
+
event: CartesiaRealtimeWireEvent,
|
|
269
|
+
raw: unknown,
|
|
270
|
+
): RealtimeModelV4ServerEvent | RealtimeModelV4ServerEvent[] {
|
|
271
|
+
const state = this.manualTranscript;
|
|
272
|
+
this.manualTranscript = { duration: 0, events: [], text: '' };
|
|
273
|
+
if (state.text.length === 0) {
|
|
274
|
+
return { type: 'custom', rawType: event.type, raw };
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return [
|
|
278
|
+
{
|
|
279
|
+
type: 'audio-committed',
|
|
280
|
+
itemId: event.request_id,
|
|
281
|
+
raw,
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
type: 'input-transcription-completed',
|
|
285
|
+
itemId: event.request_id ?? 'cartesia-realtime',
|
|
286
|
+
transcript: state.text,
|
|
287
|
+
raw: {
|
|
288
|
+
event,
|
|
289
|
+
transcriptEvents: state.events,
|
|
290
|
+
duration: state.duration,
|
|
291
|
+
},
|
|
292
|
+
},
|
|
293
|
+
];
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
private withSessionCreated(
|
|
297
|
+
event: RealtimeModelV4ServerEvent | RealtimeModelV4ServerEvent[],
|
|
298
|
+
sessionId: string,
|
|
299
|
+
raw: unknown,
|
|
300
|
+
): RealtimeModelV4ServerEvent | RealtimeModelV4ServerEvent[] {
|
|
301
|
+
if (this.sessionCreated) {
|
|
302
|
+
return event;
|
|
303
|
+
}
|
|
304
|
+
this.sessionCreated = true;
|
|
305
|
+
const created: RealtimeModelV4ServerEvent = {
|
|
306
|
+
type: 'session-created',
|
|
307
|
+
sessionId,
|
|
308
|
+
raw,
|
|
309
|
+
};
|
|
310
|
+
return Array.isArray(event) ? [created, ...event] : [created, event];
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function resolveCartesiaEncoding(type: string | undefined): string {
|
|
315
|
+
switch (type) {
|
|
316
|
+
case undefined:
|
|
317
|
+
case 'audio/pcm':
|
|
318
|
+
return 'pcm_s16le';
|
|
319
|
+
case 'audio/pcmu':
|
|
320
|
+
return 'pcm_mulaw';
|
|
321
|
+
case 'audio/pcma':
|
|
322
|
+
return 'pcm_alaw';
|
|
323
|
+
default:
|
|
324
|
+
throw new Error(`Unsupported Cartesia realtime audio format: ${type}`);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export type CartesiaSpeechOutputFormat =
|
|
2
|
+
| {
|
|
3
|
+
container: 'mp3';
|
|
4
|
+
sample_rate: CartesiaSpeechSampleRate;
|
|
5
|
+
bit_rate: CartesiaSpeechBitRate;
|
|
6
|
+
}
|
|
7
|
+
| {
|
|
8
|
+
container: 'raw' | 'wav';
|
|
9
|
+
encoding: CartesiaSpeechEncoding;
|
|
10
|
+
sample_rate: CartesiaSpeechSampleRate;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type CartesiaSpeechEncoding =
|
|
14
|
+
| 'pcm_f32le'
|
|
15
|
+
| 'pcm_s16le'
|
|
16
|
+
| 'pcm_mulaw'
|
|
17
|
+
| 'pcm_alaw';
|
|
18
|
+
|
|
19
|
+
export type CartesiaSpeechSampleRate =
|
|
20
|
+
| 8000
|
|
21
|
+
| 16000
|
|
22
|
+
| 22050
|
|
23
|
+
| 24000
|
|
24
|
+
| 44100
|
|
25
|
+
| 48000;
|
|
26
|
+
|
|
27
|
+
export type CartesiaSpeechBitRate = 32000 | 64000 | 96000 | 128000 | 192000;
|
|
28
|
+
|
|
29
|
+
export type CartesiaSpeechAPITypes = {
|
|
30
|
+
/**
|
|
31
|
+
* The ID of the model to use for the generation.
|
|
32
|
+
*/
|
|
33
|
+
model_id: string;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The transcript to generate speech for.
|
|
37
|
+
*/
|
|
38
|
+
transcript: string;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The voice specifier. Cartesia uses id mode with a voice id.
|
|
42
|
+
*/
|
|
43
|
+
voice: {
|
|
44
|
+
mode: 'id';
|
|
45
|
+
id: string;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The language to generate speech in (ISO 639-1 code).
|
|
50
|
+
*/
|
|
51
|
+
language?: string;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The output audio format.
|
|
55
|
+
*/
|
|
56
|
+
output_format: CartesiaSpeechOutputFormat;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Controls supported generation attributes.
|
|
60
|
+
*/
|
|
61
|
+
generation_config?: {
|
|
62
|
+
speed?: number;
|
|
63
|
+
};
|
|
64
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { z } from 'zod/v4';
|
|
2
|
+
|
|
3
|
+
// https://docs.cartesia.ai/api-reference/tts/bytes
|
|
4
|
+
export const cartesiaSpeechModelOptionsSchema = z.object({
|
|
5
|
+
/** Container format for the output audio (raw, wav, mp3). */
|
|
6
|
+
container: z.enum(['raw', 'wav', 'mp3']).nullish(),
|
|
7
|
+
/** Encoding type for the audio output (pcm_f32le, pcm_s16le, pcm_mulaw, pcm_alaw). */
|
|
8
|
+
encoding: z
|
|
9
|
+
.enum(['pcm_f32le', 'pcm_s16le', 'pcm_mulaw', 'pcm_alaw'])
|
|
10
|
+
.nullish(),
|
|
11
|
+
/** Sample rate for the output audio in Hz (e.g. 8000, 16000, 22050, 24000, 44100, 48000). */
|
|
12
|
+
sampleRate: z
|
|
13
|
+
.union([
|
|
14
|
+
z.literal(8000),
|
|
15
|
+
z.literal(16000),
|
|
16
|
+
z.literal(22050),
|
|
17
|
+
z.literal(24000),
|
|
18
|
+
z.literal(44100),
|
|
19
|
+
z.literal(48000),
|
|
20
|
+
])
|
|
21
|
+
.nullish(),
|
|
22
|
+
/** Bitrate for mp3 output in bits per second (e.g. 32000, 64000, 128000, 192000). */
|
|
23
|
+
bitRate: z
|
|
24
|
+
.union([
|
|
25
|
+
z.literal(32000),
|
|
26
|
+
z.literal(64000),
|
|
27
|
+
z.literal(96000),
|
|
28
|
+
z.literal(128000),
|
|
29
|
+
z.literal(192000),
|
|
30
|
+
])
|
|
31
|
+
.nullish(),
|
|
32
|
+
/** Controls the speed of the generated speech. */
|
|
33
|
+
speed: z.number().min(0.6).max(1.5).nullish(),
|
|
34
|
+
/** The language to generate speech in (ISO 639-1 code). */
|
|
35
|
+
language: z.string().nullish(),
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
export type CartesiaSpeechModelOptions = z.infer<
|
|
39
|
+
typeof cartesiaSpeechModelOptionsSchema
|
|
40
|
+
>;
|