@read-frog/api-contract 0.19.0 → 0.21.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/dist/index.d.ts +2749 -28
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +160 -6
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/contracts/card-template.ts +16 -4
- package/src/contracts/mastery-goal.ts +22 -0
- package/src/contracts/speech.ts +45 -0
- package/src/index.ts +6 -0
- package/src/public-errors.ts +49 -0
- package/src/schemas/card.ts +4 -0
- package/src/schemas/mastery-goal.ts +23 -0
- package/src/schemas/speech.ts +113 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@read-frog/api-contract",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"@orpc/contract": "^1.15.2",
|
|
28
|
-
"@read-frog/definitions": "0.
|
|
28
|
+
"@read-frog/definitions": "0.10.0",
|
|
29
29
|
"zod": "^4.6.5"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
@@ -42,10 +42,16 @@ export const cardTemplateContract = {
|
|
|
42
42
|
path: "/card-templates",
|
|
43
43
|
summary: "Create card template",
|
|
44
44
|
description:
|
|
45
|
-
|
|
45
|
+
'Create a template in an owned notebase; it generates a card for every existing note. Each face (front, back) is either { columns: [column IDs], style }, those columns in a built-in style that stays editable in the website\'s simple mode, or { layout: "<HTML + Liquid>" } for a custom design that references a column as {{ ["<column id>"] }}. Prefer columns faces. Get column IDs from the notebase schema.',
|
|
46
46
|
tags: ["Card Templates"],
|
|
47
47
|
})
|
|
48
|
-
.errors(
|
|
48
|
+
.errors(
|
|
49
|
+
pickPublicErrorMap(
|
|
50
|
+
"NOTEBASE_NOT_FOUND",
|
|
51
|
+
"CARD_TEMPLATE_INVALID_NOTEBASE_COLUMNS",
|
|
52
|
+
"CARD_TEMPLATE_INVALID_LAYOUT",
|
|
53
|
+
),
|
|
54
|
+
)
|
|
49
55
|
.errors(pickPublicErrorMap("RESOURCE_ALREADY_EXISTS"))
|
|
50
56
|
.input(CardTemplateCreateInputSchema)
|
|
51
57
|
.output(CardTemplateCreateOutputSchema),
|
|
@@ -55,10 +61,16 @@ export const cardTemplateContract = {
|
|
|
55
61
|
path: "/card-templates/{id}",
|
|
56
62
|
summary: "Update card template",
|
|
57
63
|
description:
|
|
58
|
-
"Change an owned template name or
|
|
64
|
+
"Change an owned template name or faces (see creating a template for the face shapes). Rendered cards reflect the saved template. expectedUpdatedAt guards against editing stale data.",
|
|
59
65
|
tags: ["Card Templates"],
|
|
60
66
|
})
|
|
61
|
-
.errors(
|
|
67
|
+
.errors(
|
|
68
|
+
pickPublicErrorMap(
|
|
69
|
+
"CARD_TEMPLATE_NOT_FOUND",
|
|
70
|
+
"CARD_TEMPLATE_INVALID_NOTEBASE_COLUMNS",
|
|
71
|
+
"CARD_TEMPLATE_INVALID_LAYOUT",
|
|
72
|
+
),
|
|
73
|
+
)
|
|
62
74
|
.errors(pickPublicErrorMap("RESOURCE_UPDATE_CONFLICT"))
|
|
63
75
|
.input(CardTemplateUpdateInputSchema)
|
|
64
76
|
.output(CardTemplateUpdateOutputSchema),
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { oc } from "@orpc/contract";
|
|
2
|
+
import { masteryGoalOutputSchema, masteryGoalUpdateInputSchema } from "#/schemas/mastery-goal";
|
|
3
|
+
|
|
4
|
+
export const masteryGoalContract = {
|
|
5
|
+
get: oc
|
|
6
|
+
.route({
|
|
7
|
+
method: "GET",
|
|
8
|
+
path: "/mastery-goal",
|
|
9
|
+
summary: "Get the authenticated user's mastered-cards goal",
|
|
10
|
+
tags: ["Goals"],
|
|
11
|
+
})
|
|
12
|
+
.output(masteryGoalOutputSchema),
|
|
13
|
+
update: oc
|
|
14
|
+
.route({
|
|
15
|
+
method: "PUT",
|
|
16
|
+
path: "/mastery-goal",
|
|
17
|
+
summary: "Set or clear the authenticated user's mastered-cards goal",
|
|
18
|
+
tags: ["Goals"],
|
|
19
|
+
})
|
|
20
|
+
.input(masteryGoalUpdateInputSchema)
|
|
21
|
+
.output(masteryGoalOutputSchema),
|
|
22
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { oc } from "@orpc/contract";
|
|
2
|
+
import {
|
|
3
|
+
speechPreferencesSchema,
|
|
4
|
+
speechPreferencesUpdateInputSchema,
|
|
5
|
+
speechSynthesizeInputSchema,
|
|
6
|
+
speechSynthesizeOutputSchema,
|
|
7
|
+
speechVoicesOutputSchema,
|
|
8
|
+
} from "#/schemas/speech";
|
|
9
|
+
|
|
10
|
+
export const speechContract = {
|
|
11
|
+
getPreferences: oc
|
|
12
|
+
.route({
|
|
13
|
+
method: "GET",
|
|
14
|
+
path: "/speech/preferences",
|
|
15
|
+
summary: "Get how the authenticated user's text is read aloud",
|
|
16
|
+
tags: ["Speech"],
|
|
17
|
+
})
|
|
18
|
+
.output(speechPreferencesSchema),
|
|
19
|
+
updatePreferences: oc
|
|
20
|
+
.route({
|
|
21
|
+
method: "PATCH",
|
|
22
|
+
path: "/speech/preferences",
|
|
23
|
+
summary: "Change how the authenticated user's text is read aloud",
|
|
24
|
+
tags: ["Speech"],
|
|
25
|
+
})
|
|
26
|
+
.input(speechPreferencesUpdateInputSchema)
|
|
27
|
+
.output(speechPreferencesSchema),
|
|
28
|
+
listVoices: oc
|
|
29
|
+
.route({
|
|
30
|
+
method: "GET",
|
|
31
|
+
path: "/speech/voices",
|
|
32
|
+
summary: "List the voices text can be read aloud in, and each language's default",
|
|
33
|
+
tags: ["Speech"],
|
|
34
|
+
})
|
|
35
|
+
.output(speechVoicesOutputSchema),
|
|
36
|
+
synthesize: oc
|
|
37
|
+
.route({
|
|
38
|
+
method: "POST",
|
|
39
|
+
path: "/speech/synthesize",
|
|
40
|
+
summary: "Read a text aloud, as MP3 audio",
|
|
41
|
+
tags: ["Speech"],
|
|
42
|
+
})
|
|
43
|
+
.input(speechSynthesizeInputSchema)
|
|
44
|
+
.output(speechSynthesizeOutputSchema),
|
|
45
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -5,10 +5,12 @@ import { cardContract } from "./contracts/card";
|
|
|
5
5
|
import { cardTemplateContract } from "./contracts/card-template";
|
|
6
6
|
import { ebookContract } from "./contracts/ebook";
|
|
7
7
|
import { hostedAiContract } from "./contracts/hosted-ai";
|
|
8
|
+
import { masteryGoalContract } from "./contracts/mastery-goal";
|
|
8
9
|
import { mcpConnectionContract } from "./contracts/mcp-connection";
|
|
9
10
|
import { notebaseContract } from "./contracts/notebase";
|
|
10
11
|
import { notebaseColumnContract } from "./contracts/notebase-column";
|
|
11
12
|
import { notebaseRowContract } from "./contracts/notebase-row";
|
|
13
|
+
import { speechContract } from "./contracts/speech";
|
|
12
14
|
import { srsContract } from "./contracts/srs";
|
|
13
15
|
import { statsContract } from "./contracts/stats";
|
|
14
16
|
import { userContract } from "./contracts/user";
|
|
@@ -21,10 +23,12 @@ export const contract = {
|
|
|
21
23
|
cardTemplate: cardTemplateContract,
|
|
22
24
|
ebook: ebookContract,
|
|
23
25
|
hostedAi: hostedAiContract,
|
|
26
|
+
masteryGoal: masteryGoalContract,
|
|
24
27
|
mcpConnection: mcpConnectionContract,
|
|
25
28
|
notebase: notebaseContract,
|
|
26
29
|
notebaseColumn: notebaseColumnContract,
|
|
27
30
|
notebaseRow: notebaseRowContract,
|
|
31
|
+
speech: speechContract,
|
|
28
32
|
srs: srsContract,
|
|
29
33
|
stats: statsContract,
|
|
30
34
|
user: userContract,
|
|
@@ -40,12 +44,14 @@ export * from "./schemas/billing";
|
|
|
40
44
|
export * from "./schemas/card";
|
|
41
45
|
export * from "./schemas/ebook";
|
|
42
46
|
export * from "./schemas/hosted-ai";
|
|
47
|
+
export * from "./schemas/mastery-goal";
|
|
43
48
|
export * from "./schemas/mcp-connection";
|
|
44
49
|
export * from "./schemas/notebase";
|
|
45
50
|
export * from "./schemas/notebase-column";
|
|
46
51
|
export * from "./schemas/notebase-row";
|
|
47
52
|
export * from "./schemas/notebase-stats";
|
|
48
53
|
export * from "./schemas/notebase-view";
|
|
54
|
+
export * from "./schemas/speech";
|
|
49
55
|
export * from "./schemas/srs";
|
|
50
56
|
export * from "./schemas/srs-stats";
|
|
51
57
|
export * from "./schemas/stats";
|
package/src/public-errors.ts
CHANGED
|
@@ -45,6 +45,33 @@ export const CardTemplateInvalidColumnsDataSchema = z
|
|
|
45
45
|
.strict();
|
|
46
46
|
export type CardTemplateInvalidColumnsData = z.infer<typeof CardTemplateInvalidColumnsDataSchema>;
|
|
47
47
|
|
|
48
|
+
export const CardTemplateInvalidLayoutDataSchema = z
|
|
49
|
+
.object({
|
|
50
|
+
errors: z.array(
|
|
51
|
+
z
|
|
52
|
+
.object({
|
|
53
|
+
side: z.enum(["front", "back"]),
|
|
54
|
+
message: z.string(),
|
|
55
|
+
// UTF-16 offsets into that face's layout, when the parser knows them.
|
|
56
|
+
from: z.number().int().min(0).optional(),
|
|
57
|
+
to: z.number().int().min(0).optional(),
|
|
58
|
+
})
|
|
59
|
+
.strict(),
|
|
60
|
+
),
|
|
61
|
+
})
|
|
62
|
+
.strict();
|
|
63
|
+
export type CardTemplateInvalidLayoutData = z.infer<typeof CardTemplateInvalidLayoutDataSchema>;
|
|
64
|
+
|
|
65
|
+
// The templates that use the column, which keep it from being deleted.
|
|
66
|
+
export const CardTemplateNotebaseColumnInUseDataSchema = z
|
|
67
|
+
.object({
|
|
68
|
+
templates: z.array(z.object({ id: z.uuid(), name: z.string() }).strict()),
|
|
69
|
+
})
|
|
70
|
+
.strict();
|
|
71
|
+
export type CardTemplateNotebaseColumnInUseData = z.infer<
|
|
72
|
+
typeof CardTemplateNotebaseColumnInUseDataSchema
|
|
73
|
+
>;
|
|
74
|
+
|
|
48
75
|
export const HostedAiTierRestrictedDataSchema = z.strictObject({
|
|
49
76
|
modelTier: HostedAiModelTierSchema,
|
|
50
77
|
});
|
|
@@ -60,6 +87,11 @@ export const HostedAiQuotaExhaustedDataSchema = z.strictObject({
|
|
|
60
87
|
resetAt: z.iso.datetime().nullable(),
|
|
61
88
|
});
|
|
62
89
|
|
|
90
|
+
export const SpeechDailyLimitReachedDataSchema = z.strictObject({
|
|
91
|
+
limit: z.number().int().min(0),
|
|
92
|
+
resetAt: z.iso.datetime(),
|
|
93
|
+
});
|
|
94
|
+
|
|
63
95
|
export const PUBLIC_APP_ERROR_DEFS = {
|
|
64
96
|
RESOURCE_UPDATE_CONFLICT: {
|
|
65
97
|
message: "The resource changed. Read it again before updating.",
|
|
@@ -127,6 +159,17 @@ export const PUBLIC_APP_ERROR_DEFS = {
|
|
|
127
159
|
message: "This video is longer than AI subtitles support",
|
|
128
160
|
status: 422,
|
|
129
161
|
},
|
|
162
|
+
// The day's read-aloud requests are used up (`SPEECH_DAILY_REQUEST_LIMITS`).
|
|
163
|
+
SPEECH_DAILY_LIMIT_REACHED: {
|
|
164
|
+
data: SpeechDailyLimitReachedDataSchema,
|
|
165
|
+
message: "Today's read-aloud limit is reached",
|
|
166
|
+
status: 429,
|
|
167
|
+
},
|
|
168
|
+
// No voice service to read with right now: not configured, or failing.
|
|
169
|
+
SPEECH_UNAVAILABLE: {
|
|
170
|
+
message: "Reading aloud is unavailable right now",
|
|
171
|
+
status: 503,
|
|
172
|
+
},
|
|
130
173
|
NOTEBASE_COLUMN_NOT_FOUND: {
|
|
131
174
|
message: "Notebase column not found",
|
|
132
175
|
status: 404,
|
|
@@ -148,7 +191,13 @@ export const PUBLIC_APP_ERROR_DEFS = {
|
|
|
148
191
|
message: "Card template references unknown notebase columns",
|
|
149
192
|
status: 422,
|
|
150
193
|
},
|
|
194
|
+
CARD_TEMPLATE_INVALID_LAYOUT: {
|
|
195
|
+
data: CardTemplateInvalidLayoutDataSchema,
|
|
196
|
+
message: "Card template layout does not parse",
|
|
197
|
+
status: 422,
|
|
198
|
+
},
|
|
151
199
|
CARD_TEMPLATE_NOTEBASE_COLUMN_IN_USE: {
|
|
200
|
+
data: CardTemplateNotebaseColumnInUseDataSchema,
|
|
152
201
|
message: "Notebase column is used by a card template",
|
|
153
202
|
status: 409,
|
|
154
203
|
},
|
package/src/schemas/card.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
cardMemoryStateShape,
|
|
6
6
|
cardStateSchema,
|
|
7
7
|
cardTemplateConfigSchema,
|
|
8
|
+
cardTemplateCreatedBySchema,
|
|
8
9
|
scheduleStatusSchema,
|
|
9
10
|
} from "@read-frog/definitions";
|
|
10
11
|
import { z } from "zod";
|
|
@@ -17,6 +18,9 @@ export const cardTemplateSchema = z.object({
|
|
|
17
18
|
notebaseId: z.uuid(),
|
|
18
19
|
name: z.string(),
|
|
19
20
|
config: cardTemplateConfigSchema,
|
|
21
|
+
createdBy: cardTemplateCreatedBySchema.describe(
|
|
22
|
+
'Who made the template: "user", or "system" for the default template Read Frog gives a notebase whose fields fit one.',
|
|
23
|
+
),
|
|
20
24
|
createdAt: z.coerce.date(),
|
|
21
25
|
updatedAt: z.coerce.date(),
|
|
22
26
|
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const MASTERY_GOAL_TARGET_MAX = 1_000_000;
|
|
4
|
+
|
|
5
|
+
/** How many mastered cards to aim for: the cards `stats.srsMemoryGrowth` counts. */
|
|
6
|
+
export const masteryGoalTargetSchema = z.number().int().min(1).max(MASTERY_GOAL_TARGET_MAX);
|
|
7
|
+
export type MasteryGoalTarget = z.infer<typeof masteryGoalTargetSchema>;
|
|
8
|
+
|
|
9
|
+
/** `null` clears the goal. */
|
|
10
|
+
export const masteryGoalUpdateInputSchema = z
|
|
11
|
+
.object({
|
|
12
|
+
target: masteryGoalTargetSchema.nullable(),
|
|
13
|
+
})
|
|
14
|
+
.strict();
|
|
15
|
+
export type MasteryGoalUpdateInput = z.infer<typeof masteryGoalUpdateInputSchema>;
|
|
16
|
+
|
|
17
|
+
/** The goal's target, or `null` while none is set. */
|
|
18
|
+
export const masteryGoalOutputSchema = z
|
|
19
|
+
.object({
|
|
20
|
+
target: masteryGoalTargetSchema.nullable(),
|
|
21
|
+
})
|
|
22
|
+
.strict();
|
|
23
|
+
export type MasteryGoalOutput = z.infer<typeof masteryGoalOutputSchema>;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type { BillingPlan } from "./billing";
|
|
2
|
+
import { langCodeISO6393Schema } from "@read-frog/definitions";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
|
|
5
|
+
// Reading text aloud in the account's voices: the reader's settings, the
|
|
6
|
+
// voices to choose from, and the request that turns a text into audio.
|
|
7
|
+
|
|
8
|
+
/** The longest text one request reads aloud. */
|
|
9
|
+
export const SPEECH_TEXT_MAX_LENGTH = 500;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* How many texts each plan may have read aloud in a day (24 hours from the
|
|
13
|
+
* first one). Generous enough that reviewing never runs into it; the cap is
|
|
14
|
+
* there to keep the voices from being used as a free speech service.
|
|
15
|
+
*/
|
|
16
|
+
export const SPEECH_DAILY_REQUEST_LIMITS = {
|
|
17
|
+
free: 200,
|
|
18
|
+
pro: 5000,
|
|
19
|
+
ultra: 5000,
|
|
20
|
+
} as const satisfies Record<BillingPlan, number>;
|
|
21
|
+
|
|
22
|
+
/** A voice by its short name: its locale, then the voice (`en-US-AndrewMultilingualNeural`). */
|
|
23
|
+
export const speechVoiceNameSchema = z
|
|
24
|
+
.string()
|
|
25
|
+
.max(100)
|
|
26
|
+
.regex(/^[a-z]{2,3}(?:-[A-Za-z0-9]+)+(?::[A-Za-z0-9]+)?$/);
|
|
27
|
+
export type SpeechVoiceName = z.infer<typeof speechVoiceNameSchema>;
|
|
28
|
+
|
|
29
|
+
export const SPEECH_RATE_MIN = -50;
|
|
30
|
+
export const SPEECH_RATE_MAX = 100;
|
|
31
|
+
|
|
32
|
+
/** Reading speed: percent faster (+) or slower (−) than normal. */
|
|
33
|
+
export const speechRateSchema = z.number().int().min(SPEECH_RATE_MIN).max(SPEECH_RATE_MAX);
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* How the account reads aloud. A language left out of `languageVoices`, and a
|
|
37
|
+
* `null` `multilingualVoice`, read in the default voice (`speech.listVoices`
|
|
38
|
+
* names them).
|
|
39
|
+
*/
|
|
40
|
+
export const speechPreferencesSchema = z
|
|
41
|
+
.object({
|
|
42
|
+
languageVoices: z.partialRecord(langCodeISO6393Schema, speechVoiceNameSchema),
|
|
43
|
+
// Reads text whose language is not known, working the language out itself.
|
|
44
|
+
multilingualVoice: speechVoiceNameSchema.nullable(),
|
|
45
|
+
rate: speechRateSchema,
|
|
46
|
+
})
|
|
47
|
+
.strict();
|
|
48
|
+
export type SpeechPreferences = z.infer<typeof speechPreferencesSchema>;
|
|
49
|
+
|
|
50
|
+
export const DEFAULT_SPEECH_PREFERENCES: SpeechPreferences = {
|
|
51
|
+
languageVoices: {},
|
|
52
|
+
multilingualVoice: null,
|
|
53
|
+
rate: 0,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Changes some settings and keeps the rest. `languageVoices` changes only the
|
|
58
|
+
* languages it names; `null` puts a language back on its default voice.
|
|
59
|
+
*/
|
|
60
|
+
export const speechPreferencesUpdateInputSchema = z
|
|
61
|
+
.object({
|
|
62
|
+
languageVoices: z
|
|
63
|
+
.partialRecord(langCodeISO6393Schema, speechVoiceNameSchema.nullable())
|
|
64
|
+
.optional(),
|
|
65
|
+
multilingualVoice: speechVoiceNameSchema.nullable().optional(),
|
|
66
|
+
rate: speechRateSchema.optional(),
|
|
67
|
+
})
|
|
68
|
+
.strict();
|
|
69
|
+
export type SpeechPreferencesUpdateInput = z.infer<typeof speechPreferencesUpdateInputSchema>;
|
|
70
|
+
|
|
71
|
+
export const speechVoiceGenderSchema = z.enum(["female", "male", "neutral"]);
|
|
72
|
+
export type SpeechVoiceGender = z.infer<typeof speechVoiceGenderSchema>;
|
|
73
|
+
|
|
74
|
+
export const speechVoiceSchema = z
|
|
75
|
+
.object({
|
|
76
|
+
name: speechVoiceNameSchema,
|
|
77
|
+
// The voice's name for people, such as "Andrew Multilingual".
|
|
78
|
+
displayName: z.string(),
|
|
79
|
+
locale: z.string(),
|
|
80
|
+
// The language the voice is for; null for one this list has no code for.
|
|
81
|
+
language: langCodeISO6393Schema.nullable(),
|
|
82
|
+
gender: speechVoiceGenderSchema,
|
|
83
|
+
// Reads many languages, working out the text's language itself.
|
|
84
|
+
multilingual: z.boolean(),
|
|
85
|
+
})
|
|
86
|
+
.strict();
|
|
87
|
+
export type SpeechVoice = z.infer<typeof speechVoiceSchema>;
|
|
88
|
+
|
|
89
|
+
export const speechVoicesOutputSchema = z
|
|
90
|
+
.object({
|
|
91
|
+
voices: z.array(speechVoiceSchema),
|
|
92
|
+
// The voice each language reads in until the reader picks one.
|
|
93
|
+
defaultVoices: z.partialRecord(langCodeISO6393Schema, speechVoiceNameSchema),
|
|
94
|
+
defaultMultilingualVoice: speechVoiceNameSchema,
|
|
95
|
+
})
|
|
96
|
+
.strict();
|
|
97
|
+
export type SpeechVoicesOutput = z.infer<typeof speechVoicesOutputSchema>;
|
|
98
|
+
|
|
99
|
+
export const speechSynthesizeInputSchema = z
|
|
100
|
+
.object({
|
|
101
|
+
text: z.string().trim().min(1).max(SPEECH_TEXT_MAX_LENGTH),
|
|
102
|
+
// The text's language, when the caller knows it; without one, a
|
|
103
|
+
// multilingual voice works it out.
|
|
104
|
+
language: langCodeISO6393Schema.optional(),
|
|
105
|
+
// Reads in this voice instead of the account's, such as one the reader is
|
|
106
|
+
// trying out in the settings.
|
|
107
|
+
voice: speechVoiceNameSchema.optional(),
|
|
108
|
+
})
|
|
109
|
+
.strict();
|
|
110
|
+
export type SpeechSynthesizeInput = z.infer<typeof speechSynthesizeInputSchema>;
|
|
111
|
+
|
|
112
|
+
/** The audio, as an MP3 file. */
|
|
113
|
+
export const speechSynthesizeOutputSchema = z.file();
|