@gentbajko/slopify 0.7.0 → 0.8.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/adapters/llm/codex.js +3 -0
- package/dist/adapters/llm/gemini-workspace.js +24 -1
- package/dist/adapters/llm/gemini.js +1 -1
- package/dist/adapters/llm/openrouter.js +1 -0
- package/dist/assets/models.yaml +470 -0
- package/dist/catalog/registry.js +147 -0
- package/dist/catalog/schema.js +91 -0
- package/dist/catalog/store.js +90 -0
- package/dist/catalog/validate.js +43 -0
- package/dist/edge/http/actions.js +1 -1
- package/dist/edge/http/app.js +2 -0
- package/dist/edge/http/planning.js +101 -0
- package/dist/edge/http/projects.js +4 -2
- package/dist/edge/http/providers.js +28 -0
- package/dist/kernel/db/migrations/0003-batch-queue.sql +11 -0
- package/dist/kernel/ports/llm.js +1 -0
- package/dist/kernel/ports/text.js +26 -0
- package/dist/kernel/runner/providers.js +8 -6
- package/dist/kernel/runner/queue.js +65 -0
- package/dist/main.js +41 -6
- package/dist/slices/admission/repo.js +6 -1
- package/dist/slices/admission/start.js +9 -8
- package/dist/slices/article/continuation.js +8 -1
- package/dist/slices/article/segments.js +1 -0
- package/dist/slices/batch/index.js +82 -0
- package/dist/slices/control/index.js +5 -1
- package/dist/slices/control/providers.js +14 -1
- package/dist/slices/estimate/index.js +81 -0
- package/dist/slices/narration/plan.js +58 -0
- package/dist/slices/narration/run.js +6 -27
- package/dist/slices/research/run.js +4 -0
- package/dist/slices/storage/staging.js +2 -1
- package/dist/slices/thumbnail/run.js +3 -0
- package/dist/web/assets/index-6zz8telY.css +1 -0
- package/dist/web/assets/index-CbYEcBOa.js +130 -0
- package/dist/web/index.html +2 -2
- package/package.json +2 -1
- package/dist/web/assets/index-6QIQ-TzO.css +0 -1
- package/dist/web/assets/index-CNgWL-ct.js +0 -130
|
@@ -25,6 +25,9 @@ export function codexArgs(req) {
|
|
|
25
25
|
"--skip-git-repo-check",
|
|
26
26
|
"-c",
|
|
27
27
|
`web_search="${req.webSearch === true ? "live" : "disabled"}"`,
|
|
28
|
+
...(req.thinkingConfig?.effort
|
|
29
|
+
? ["-c", `model_reasoning_effort="${req.thinkingConfig.effort}"`]
|
|
30
|
+
: []),
|
|
28
31
|
...(req.model === "" ? [] : ["-m", req.model]),
|
|
29
32
|
// The prompt is one argv element after `--`, so a leading dash is text, not a flag.
|
|
30
33
|
"--",
|
|
@@ -4,7 +4,7 @@ import { join } from "node:path";
|
|
|
4
4
|
// Gemini 0.16 supports stream-json and tools.core, but headless default mode still
|
|
5
5
|
// exposes file-reading tools. Isolate each writing call while retaining ~/.gemini
|
|
6
6
|
// authentication. Nothing is written into the user's settings or project.
|
|
7
|
-
export function geminiWorkspace(webSearch) {
|
|
7
|
+
export function geminiWorkspace(webSearch, request) {
|
|
8
8
|
const directory = mkdtempSync(join(tmpdir(), "slopify-gemini-"));
|
|
9
9
|
try {
|
|
10
10
|
const settings = join(directory, "settings.json");
|
|
@@ -12,6 +12,29 @@ export function geminiWorkspace(webSearch) {
|
|
|
12
12
|
const trust = join(directory, "trusted-folders.json");
|
|
13
13
|
const mcpAllowlist = `slopify-disabled-${directory.split(/[\\/]/).at(-1)}`;
|
|
14
14
|
writeFileSync(settings, JSON.stringify({
|
|
15
|
+
...(request?.thinkingConfig
|
|
16
|
+
? {
|
|
17
|
+
modelConfigs: {
|
|
18
|
+
customOverrides: [
|
|
19
|
+
{
|
|
20
|
+
match: { model: request.model },
|
|
21
|
+
modelConfig: {
|
|
22
|
+
generateContentConfig: {
|
|
23
|
+
thinkingConfig: {
|
|
24
|
+
...(request.thinkingConfig.level === undefined
|
|
25
|
+
? {}
|
|
26
|
+
: { thinkingLevel: request.thinkingConfig.level }),
|
|
27
|
+
...(request.thinkingConfig.budget === undefined
|
|
28
|
+
? {}
|
|
29
|
+
: { thinkingBudget: request.thinkingConfig.budget }),
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
},
|
|
36
|
+
}
|
|
37
|
+
: {}),
|
|
15
38
|
tools: {
|
|
16
39
|
core: webSearch ? ["google_web_search"] : [],
|
|
17
40
|
allowed: webSearch ? ["google_web_search"] : [],
|
|
@@ -43,7 +43,7 @@ export function geminiLlm(deps) {
|
|
|
43
43
|
const binary = deps.binary ?? geminiBinary;
|
|
44
44
|
async function* complete(req) {
|
|
45
45
|
req.signal.throwIfAborted();
|
|
46
|
-
const workspace = geminiWorkspace(req.webSearch === true);
|
|
46
|
+
const workspace = geminiWorkspace(req.webSearch === true, req);
|
|
47
47
|
let run;
|
|
48
48
|
try {
|
|
49
49
|
run = deps.run(binary, geminiArgs(req, workspace.mcpAllowlist), req.signal, workspace.options);
|
|
@@ -47,6 +47,7 @@ export function openRouterLlm(deps) {
|
|
|
47
47
|
role: message.role,
|
|
48
48
|
content: message.content,
|
|
49
49
|
})),
|
|
50
|
+
...(req.thinkingConfig?.effort ? { reasoning: { effort: req.thinkingConfig.effort } } : {}),
|
|
50
51
|
stream: true,
|
|
51
52
|
// The usage-accounting flag: without it the final chunk carries no token counts
|
|
52
53
|
// and the Usage page would have nothing to count.
|
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
# Curated adapter-compatible models. Edit this file to change the published defaults.
|
|
2
|
+
# A private copy in the Slopify data directory reloads automatically.
|
|
3
|
+
# Omitted pricing means unknown, never free. Provider concurrency is capped at five.
|
|
4
|
+
schemaVersion: 1
|
|
5
|
+
updatedAt: 2026-09-10
|
|
6
|
+
providers:
|
|
7
|
+
openrouter:
|
|
8
|
+
maxConcurrent: 3
|
|
9
|
+
claude-code:
|
|
10
|
+
maxConcurrent: 3
|
|
11
|
+
codex:
|
|
12
|
+
maxConcurrent: 3
|
|
13
|
+
gemini:
|
|
14
|
+
maxConcurrent: 3
|
|
15
|
+
elevenlabs:
|
|
16
|
+
maxConcurrent: 3
|
|
17
|
+
openai-tts:
|
|
18
|
+
maxConcurrent: 3
|
|
19
|
+
cartesia:
|
|
20
|
+
maxConcurrent: 3
|
|
21
|
+
inworld:
|
|
22
|
+
maxConcurrent: 5
|
|
23
|
+
fal:
|
|
24
|
+
maxConcurrent: 3
|
|
25
|
+
replicate:
|
|
26
|
+
maxConcurrent: 3
|
|
27
|
+
openai-image:
|
|
28
|
+
maxConcurrent: 3
|
|
29
|
+
google-image:
|
|
30
|
+
maxConcurrent: 3
|
|
31
|
+
llm:
|
|
32
|
+
- provider: claude-code
|
|
33
|
+
id: opus
|
|
34
|
+
name: Claude Opus (latest)
|
|
35
|
+
source: https://code.claude.com/docs/en/model-config
|
|
36
|
+
keywords:
|
|
37
|
+
- llm
|
|
38
|
+
pricing: &a1
|
|
39
|
+
note: Uses your CLI login and account plan. No per-run price is available; paid
|
|
40
|
+
overage may apply.
|
|
41
|
+
llm:
|
|
42
|
+
webSearch: true
|
|
43
|
+
- provider: claude-code
|
|
44
|
+
id: sonnet
|
|
45
|
+
name: Claude Sonnet (latest)
|
|
46
|
+
source: https://code.claude.com/docs/en/model-config
|
|
47
|
+
keywords:
|
|
48
|
+
- llm
|
|
49
|
+
pricing: *a1
|
|
50
|
+
llm:
|
|
51
|
+
webSearch: true
|
|
52
|
+
- provider: claude-code
|
|
53
|
+
id: haiku
|
|
54
|
+
name: Claude Haiku (latest)
|
|
55
|
+
source: https://code.claude.com/docs/en/model-config
|
|
56
|
+
keywords:
|
|
57
|
+
- llm
|
|
58
|
+
pricing: *a1
|
|
59
|
+
llm:
|
|
60
|
+
webSearch: true
|
|
61
|
+
- provider: codex
|
|
62
|
+
id: gpt-6-astra
|
|
63
|
+
name: GPT-6-Astra
|
|
64
|
+
source: https://developers.openai.com/codex/models
|
|
65
|
+
keywords:
|
|
66
|
+
- llm
|
|
67
|
+
pricing: *a1
|
|
68
|
+
llm:
|
|
69
|
+
webSearch: true
|
|
70
|
+
thinking: &a2
|
|
71
|
+
low:
|
|
72
|
+
effort: low
|
|
73
|
+
medium:
|
|
74
|
+
effort: medium
|
|
75
|
+
high:
|
|
76
|
+
effort: high
|
|
77
|
+
- provider: codex
|
|
78
|
+
id: gpt-5.6-sol
|
|
79
|
+
name: GPT-5.6-Sol
|
|
80
|
+
source: https://developers.openai.com/codex/models
|
|
81
|
+
keywords:
|
|
82
|
+
- llm
|
|
83
|
+
pricing: *a1
|
|
84
|
+
llm:
|
|
85
|
+
webSearch: true
|
|
86
|
+
thinking: *a2
|
|
87
|
+
- provider: codex
|
|
88
|
+
id: gpt-5.6-terra
|
|
89
|
+
name: GPT-5.6-Terra
|
|
90
|
+
source: https://developers.openai.com/codex/models
|
|
91
|
+
keywords:
|
|
92
|
+
- llm
|
|
93
|
+
pricing: *a1
|
|
94
|
+
llm:
|
|
95
|
+
webSearch: true
|
|
96
|
+
thinking: *a2
|
|
97
|
+
- provider: codex
|
|
98
|
+
id: gpt-5.6-luna
|
|
99
|
+
name: GPT-5.6-Luna
|
|
100
|
+
source: https://developers.openai.com/codex/models
|
|
101
|
+
keywords:
|
|
102
|
+
- llm
|
|
103
|
+
pricing: *a1
|
|
104
|
+
llm:
|
|
105
|
+
webSearch: true
|
|
106
|
+
thinking: *a2
|
|
107
|
+
- provider: codex
|
|
108
|
+
id: gpt-5.5
|
|
109
|
+
name: GPT-5.5
|
|
110
|
+
source: https://developers.openai.com/codex/models
|
|
111
|
+
keywords:
|
|
112
|
+
- llm
|
|
113
|
+
pricing: *a1
|
|
114
|
+
llm:
|
|
115
|
+
webSearch: true
|
|
116
|
+
thinking: *a2
|
|
117
|
+
- provider: codex
|
|
118
|
+
id: gpt-5.3-codex-spark
|
|
119
|
+
name: GPT-5.3-Codex-Spark
|
|
120
|
+
source: https://developers.openai.com/codex/models
|
|
121
|
+
keywords:
|
|
122
|
+
- llm
|
|
123
|
+
pricing: *a1
|
|
124
|
+
llm:
|
|
125
|
+
webSearch: true
|
|
126
|
+
thinking: *a2
|
|
127
|
+
- provider: gemini
|
|
128
|
+
id: gemini-3.8-flash
|
|
129
|
+
name: Gemini 3.8 Flash
|
|
130
|
+
source: https://ai.google.dev/gemini-api/docs/models
|
|
131
|
+
keywords:
|
|
132
|
+
- llm
|
|
133
|
+
pricing: *a1
|
|
134
|
+
llm:
|
|
135
|
+
webSearch: true
|
|
136
|
+
thinking: &a3
|
|
137
|
+
low:
|
|
138
|
+
level: low
|
|
139
|
+
medium:
|
|
140
|
+
level: medium
|
|
141
|
+
high:
|
|
142
|
+
level: high
|
|
143
|
+
- provider: gemini
|
|
144
|
+
id: gemini-3.1-pro-preview
|
|
145
|
+
name: Gemini 3.1 Pro (preview)
|
|
146
|
+
source: https://ai.google.dev/gemini-api/docs/models
|
|
147
|
+
keywords:
|
|
148
|
+
- llm
|
|
149
|
+
pricing: *a1
|
|
150
|
+
llm:
|
|
151
|
+
webSearch: true
|
|
152
|
+
thinking: *a3
|
|
153
|
+
- provider: openrouter
|
|
154
|
+
id: google/gemini-3.8-flash
|
|
155
|
+
name: "Google: Gemini 3.8 Flash"
|
|
156
|
+
source: https://openrouter.ai/api/v1/models
|
|
157
|
+
keywords:
|
|
158
|
+
- llm
|
|
159
|
+
pricing:
|
|
160
|
+
inputPerMillionTokens: 0.75
|
|
161
|
+
outputPerMillionTokens: 3.75
|
|
162
|
+
note: Public OpenRouter rates verified 2026-09-10; routing, long context,
|
|
163
|
+
reasoning and web search may add charges.
|
|
164
|
+
llm:
|
|
165
|
+
webSearch: true
|
|
166
|
+
contextTokens: 1048576
|
|
167
|
+
thinking: *a2
|
|
168
|
+
- provider: openrouter
|
|
169
|
+
id: google/gemini-3.1-pro-preview
|
|
170
|
+
name: "Google: Gemini 3.1 Pro Preview"
|
|
171
|
+
source: https://openrouter.ai/api/v1/models
|
|
172
|
+
keywords:
|
|
173
|
+
- llm
|
|
174
|
+
pricing:
|
|
175
|
+
inputPerMillionTokens: 2
|
|
176
|
+
outputPerMillionTokens: 12
|
|
177
|
+
note: Public OpenRouter rates verified 2026-09-10; routing, long context,
|
|
178
|
+
reasoning and web search may add charges.
|
|
179
|
+
llm:
|
|
180
|
+
webSearch: true
|
|
181
|
+
contextTokens: 1048576
|
|
182
|
+
thinking: *a2
|
|
183
|
+
- provider: openrouter
|
|
184
|
+
id: openai/gpt-5.6-sol
|
|
185
|
+
name: "OpenAI: GPT-5.6 Sol"
|
|
186
|
+
source: https://openrouter.ai/api/v1/models
|
|
187
|
+
keywords:
|
|
188
|
+
- llm
|
|
189
|
+
pricing:
|
|
190
|
+
inputPerMillionTokens: 2
|
|
191
|
+
outputPerMillionTokens: 10
|
|
192
|
+
note: Public OpenRouter rates verified 2026-09-10; routing, long context,
|
|
193
|
+
reasoning and web search may add charges.
|
|
194
|
+
llm:
|
|
195
|
+
webSearch: true
|
|
196
|
+
contextTokens: 1050000
|
|
197
|
+
thinking: *a2
|
|
198
|
+
- provider: openrouter
|
|
199
|
+
id: openai/gpt-5.6-terra
|
|
200
|
+
name: "OpenAI: GPT-5.6 Terra"
|
|
201
|
+
source: https://openrouter.ai/api/v1/models
|
|
202
|
+
keywords:
|
|
203
|
+
- llm
|
|
204
|
+
pricing:
|
|
205
|
+
inputPerMillionTokens: 2
|
|
206
|
+
outputPerMillionTokens: 12
|
|
207
|
+
note: Public OpenRouter rates verified 2026-09-10; routing, long context,
|
|
208
|
+
reasoning and web search may add charges.
|
|
209
|
+
llm:
|
|
210
|
+
webSearch: true
|
|
211
|
+
contextTokens: 1050000
|
|
212
|
+
thinking: *a2
|
|
213
|
+
- provider: openrouter
|
|
214
|
+
id: openai/gpt-5.6-luna
|
|
215
|
+
name: "OpenAI: GPT-5.6 Luna"
|
|
216
|
+
source: https://openrouter.ai/api/v1/models
|
|
217
|
+
keywords:
|
|
218
|
+
- llm
|
|
219
|
+
pricing:
|
|
220
|
+
inputPerMillionTokens: 0.19999999999999998
|
|
221
|
+
outputPerMillionTokens: 1.2
|
|
222
|
+
note: Public OpenRouter rates verified 2026-09-10; routing, long context,
|
|
223
|
+
reasoning and web search may add charges.
|
|
224
|
+
llm:
|
|
225
|
+
webSearch: true
|
|
226
|
+
contextTokens: 1050000
|
|
227
|
+
thinking: *a2
|
|
228
|
+
- provider: openrouter
|
|
229
|
+
id: anthropic/claude-sonnet-4.6
|
|
230
|
+
name: "Anthropic: Claude Sonnet 4.6"
|
|
231
|
+
source: https://openrouter.ai/api/v1/models
|
|
232
|
+
keywords:
|
|
233
|
+
- llm
|
|
234
|
+
pricing:
|
|
235
|
+
inputPerMillionTokens: 3
|
|
236
|
+
outputPerMillionTokens: 15
|
|
237
|
+
note: Public OpenRouter rates verified 2026-09-10; routing, long context,
|
|
238
|
+
reasoning and web search may add charges.
|
|
239
|
+
llm:
|
|
240
|
+
webSearch: true
|
|
241
|
+
contextTokens: 1000000
|
|
242
|
+
thinking: *a2
|
|
243
|
+
- provider: openrouter
|
|
244
|
+
id: anthropic/claude-opus-4.7
|
|
245
|
+
name: "Anthropic: Claude Opus 4.7"
|
|
246
|
+
source: https://openrouter.ai/api/v1/models
|
|
247
|
+
keywords:
|
|
248
|
+
- llm
|
|
249
|
+
pricing:
|
|
250
|
+
inputPerMillionTokens: 5
|
|
251
|
+
outputPerMillionTokens: 25
|
|
252
|
+
note: Public OpenRouter rates verified 2026-09-10; routing, long context,
|
|
253
|
+
reasoning and web search may add charges.
|
|
254
|
+
llm:
|
|
255
|
+
webSearch: true
|
|
256
|
+
contextTokens: 1000000
|
|
257
|
+
thinking: *a2
|
|
258
|
+
image:
|
|
259
|
+
- provider: openai-image
|
|
260
|
+
id: gpt-image-2.5-sunburst
|
|
261
|
+
name: GPT Image 2.5 Sunburst
|
|
262
|
+
source: https://developers.openai.com/api/docs/guides/image-generation
|
|
263
|
+
keywords:
|
|
264
|
+
- image
|
|
265
|
+
pricing:
|
|
266
|
+
note: Automatic quality and generated image tokens determine the charge; a fixed
|
|
267
|
+
image price is unavailable.
|
|
268
|
+
image: &a4
|
|
269
|
+
aspectRatios: &a5
|
|
270
|
+
- 16:9
|
|
271
|
+
- 9:16
|
|
272
|
+
- provider: openai-image
|
|
273
|
+
id: gpt-image-2.5-flare
|
|
274
|
+
name: GPT Image 2.5 Flare
|
|
275
|
+
source: https://developers.openai.com/api/docs/guides/image-generation
|
|
276
|
+
keywords:
|
|
277
|
+
- image
|
|
278
|
+
pricing:
|
|
279
|
+
note: Automatic quality and generated image tokens determine the charge; a fixed
|
|
280
|
+
image price is unavailable.
|
|
281
|
+
image: *a4
|
|
282
|
+
- provider: openai-image
|
|
283
|
+
id: gpt-image-2
|
|
284
|
+
name: GPT Image 2
|
|
285
|
+
source: https://developers.openai.com/api/docs/guides/image-generation
|
|
286
|
+
keywords:
|
|
287
|
+
- image
|
|
288
|
+
pricing:
|
|
289
|
+
note: Automatic quality and generated image tokens determine the charge; a fixed
|
|
290
|
+
image price is unavailable.
|
|
291
|
+
image: *a4
|
|
292
|
+
- provider: google-image
|
|
293
|
+
id: gemini-3.1-flash-image
|
|
294
|
+
name: Nano Banana 2
|
|
295
|
+
source: https://ai.google.dev/gemini-api/docs/pricing
|
|
296
|
+
keywords:
|
|
297
|
+
- image
|
|
298
|
+
pricing:
|
|
299
|
+
perImage: 0.101
|
|
300
|
+
note: 2K image output price; prompt, thinking and other input tokens are
|
|
301
|
+
additional.
|
|
302
|
+
image:
|
|
303
|
+
aspectRatios: *a5
|
|
304
|
+
resolution: 2K
|
|
305
|
+
- provider: google-image
|
|
306
|
+
id: gemini-3-pro-image
|
|
307
|
+
name: Nano Banana Pro
|
|
308
|
+
source: https://ai.google.dev/gemini-api/docs/models
|
|
309
|
+
keywords:
|
|
310
|
+
- image
|
|
311
|
+
pricing:
|
|
312
|
+
note: Price depends on your account or generated output; estimate unavailable.
|
|
313
|
+
image:
|
|
314
|
+
aspectRatios: *a5
|
|
315
|
+
resolution: 2K
|
|
316
|
+
- provider: fal
|
|
317
|
+
id: fal-ai/flux-2
|
|
318
|
+
name: FLUX.2
|
|
319
|
+
source: https://fal.ai/models/fal-ai/flux-2
|
|
320
|
+
keywords:
|
|
321
|
+
- image
|
|
322
|
+
pricing:
|
|
323
|
+
note: Price depends on your account or generated output; estimate unavailable.
|
|
324
|
+
image: *a4
|
|
325
|
+
- provider: fal
|
|
326
|
+
id: fal-ai/nano-banana-2
|
|
327
|
+
name: Nano Banana 2
|
|
328
|
+
source: https://fal.ai/models/fal-ai/nano-banana-2
|
|
329
|
+
keywords:
|
|
330
|
+
- image
|
|
331
|
+
pricing:
|
|
332
|
+
note: Price depends on your account or generated output; estimate unavailable.
|
|
333
|
+
image: *a4
|
|
334
|
+
- provider: replicate
|
|
335
|
+
id: black-forest-labs/flux-1.1-pro
|
|
336
|
+
name: FLUX 1.1 Pro
|
|
337
|
+
source: https://replicate.com/black-forest-labs/flux-1.1-pro
|
|
338
|
+
keywords:
|
|
339
|
+
- image
|
|
340
|
+
pricing:
|
|
341
|
+
note: Price depends on your account or generated output; estimate unavailable.
|
|
342
|
+
image: *a4
|
|
343
|
+
- provider: replicate
|
|
344
|
+
id: black-forest-labs/flux-dev
|
|
345
|
+
name: FLUX.1 Dev
|
|
346
|
+
source: https://replicate.com/black-forest-labs/flux-dev
|
|
347
|
+
keywords:
|
|
348
|
+
- image
|
|
349
|
+
pricing:
|
|
350
|
+
note: Price depends on your account or generated output; estimate unavailable.
|
|
351
|
+
image: *a4
|
|
352
|
+
- provider: replicate
|
|
353
|
+
id: black-forest-labs/flux-schnell
|
|
354
|
+
name: FLUX.1 Schnell
|
|
355
|
+
source: https://replicate.com/black-forest-labs/flux-schnell
|
|
356
|
+
keywords:
|
|
357
|
+
- image
|
|
358
|
+
pricing:
|
|
359
|
+
note: Price depends on your account or generated output; estimate unavailable.
|
|
360
|
+
image: *a4
|
|
361
|
+
tts:
|
|
362
|
+
- provider: elevenlabs
|
|
363
|
+
id: eleven_v3
|
|
364
|
+
name: Eleven v3
|
|
365
|
+
source: https://elevenlabs.io/docs/overview/models
|
|
366
|
+
keywords:
|
|
367
|
+
- tts
|
|
368
|
+
pricing:
|
|
369
|
+
perMillionCharacters: 100
|
|
370
|
+
note: Advertised starting API rate; your plan, voice and account credits affect
|
|
371
|
+
actual cost.
|
|
372
|
+
tts:
|
|
373
|
+
maxCharacters: 5000
|
|
374
|
+
streaming: true
|
|
375
|
+
- provider: elevenlabs
|
|
376
|
+
id: eleven_multilingual_v2
|
|
377
|
+
name: Eleven Multilingual v2
|
|
378
|
+
source: https://elevenlabs.io/docs/overview/models
|
|
379
|
+
keywords:
|
|
380
|
+
- tts
|
|
381
|
+
pricing:
|
|
382
|
+
perMillionCharacters: 100
|
|
383
|
+
note: Advertised starting API rate; your plan, voice and account credits affect
|
|
384
|
+
actual cost.
|
|
385
|
+
tts:
|
|
386
|
+
maxCharacters: 10000
|
|
387
|
+
streaming: true
|
|
388
|
+
- provider: elevenlabs
|
|
389
|
+
id: eleven_flash_v2_5
|
|
390
|
+
name: Eleven Flash v2.5
|
|
391
|
+
source: https://elevenlabs.io/docs/overview/models
|
|
392
|
+
keywords:
|
|
393
|
+
- tts
|
|
394
|
+
pricing:
|
|
395
|
+
perMillionCharacters: 50
|
|
396
|
+
note: Advertised starting API rate; your plan, voice and account credits affect
|
|
397
|
+
actual cost.
|
|
398
|
+
tts:
|
|
399
|
+
maxCharacters: 40000
|
|
400
|
+
streaming: true
|
|
401
|
+
- provider: inworld
|
|
402
|
+
id: inworld-tts-2
|
|
403
|
+
name: Inworld TTS-2
|
|
404
|
+
source: https://inworld.ai/pricing
|
|
405
|
+
keywords:
|
|
406
|
+
- tts
|
|
407
|
+
pricing:
|
|
408
|
+
perMillionCharacters: 25
|
|
409
|
+
note: On-Demand rate before discounts or credits. Requests are split at 10,000
|
|
410
|
+
characters for account compatibility.
|
|
411
|
+
tts:
|
|
412
|
+
maxCharacters: 10000
|
|
413
|
+
asyncMaxCharacters: 100000
|
|
414
|
+
streaming: true
|
|
415
|
+
- provider: inworld
|
|
416
|
+
id: inworld-tts-2-flash
|
|
417
|
+
name: Inworld TTS-2 Flash
|
|
418
|
+
source: https://inworld.ai/pricing
|
|
419
|
+
keywords:
|
|
420
|
+
- tts
|
|
421
|
+
pricing:
|
|
422
|
+
perMillionCharacters: 15
|
|
423
|
+
note: On-Demand rate before plan discounts or credits.
|
|
424
|
+
tts:
|
|
425
|
+
maxCharacters: 4000
|
|
426
|
+
streaming: true
|
|
427
|
+
- provider: cartesia
|
|
428
|
+
id: sonic-3.6
|
|
429
|
+
name: Sonic 3.6
|
|
430
|
+
source: https://docs.cartesia.ai/build-with-cartesia/tts-models/latest
|
|
431
|
+
keywords:
|
|
432
|
+
- tts
|
|
433
|
+
pricing:
|
|
434
|
+
note: Price depends on your account or generated output; estimate unavailable.
|
|
435
|
+
tts:
|
|
436
|
+
maxCharacters: 4000
|
|
437
|
+
streaming: true
|
|
438
|
+
- provider: openai-tts
|
|
439
|
+
id: gpt-4o-mini-tts
|
|
440
|
+
name: GPT-4o mini TTS
|
|
441
|
+
source: https://developers.openai.com/api/docs/guides/text-to-speech
|
|
442
|
+
keywords:
|
|
443
|
+
- tts
|
|
444
|
+
pricing:
|
|
445
|
+
note: Price depends on your account or generated output; estimate unavailable.
|
|
446
|
+
tts:
|
|
447
|
+
maxCharacters: 4096
|
|
448
|
+
streaming: true
|
|
449
|
+
- provider: openai-tts
|
|
450
|
+
id: tts-1
|
|
451
|
+
name: TTS-1
|
|
452
|
+
source: https://developers.openai.com/api/docs/guides/text-to-speech
|
|
453
|
+
keywords:
|
|
454
|
+
- tts
|
|
455
|
+
pricing:
|
|
456
|
+
note: Price depends on your account or generated output; estimate unavailable.
|
|
457
|
+
tts:
|
|
458
|
+
maxCharacters: 4096
|
|
459
|
+
streaming: true
|
|
460
|
+
- provider: openai-tts
|
|
461
|
+
id: tts-1-hd
|
|
462
|
+
name: TTS-1 HD
|
|
463
|
+
source: https://developers.openai.com/api/docs/guides/text-to-speech
|
|
464
|
+
keywords:
|
|
465
|
+
- tts
|
|
466
|
+
pricing:
|
|
467
|
+
note: Price depends on your account or generated output; estimate unavailable.
|
|
468
|
+
tts:
|
|
469
|
+
maxCharacters: 4096
|
|
470
|
+
streaming: true
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { providerError } from "../kernel/ports/model.js";
|
|
2
|
+
import { splitText } from "../kernel/ports/text.js";
|
|
3
|
+
export function curateRegistry(registry, catalogue) {
|
|
4
|
+
const requireModel = (provider, family, id) => {
|
|
5
|
+
const models = catalogue.models(provider, family);
|
|
6
|
+
const model = id === undefined ? models[0] : models.find((m) => m.id === id);
|
|
7
|
+
if (!model)
|
|
8
|
+
throw providerError({
|
|
9
|
+
kind: "unsupported",
|
|
10
|
+
message: `${provider}: this model is not enabled in the current catalogue. Choose a supported model in Run settings.`,
|
|
11
|
+
});
|
|
12
|
+
return model;
|
|
13
|
+
};
|
|
14
|
+
return {
|
|
15
|
+
list: registry.list,
|
|
16
|
+
llm: (id) => {
|
|
17
|
+
const port = registry.llm(id);
|
|
18
|
+
return {
|
|
19
|
+
...port,
|
|
20
|
+
models: async () => catalogue.models(id, "llm").map((m) => ({
|
|
21
|
+
...m,
|
|
22
|
+
...("llm" in m && m.llm.thinking
|
|
23
|
+
? {
|
|
24
|
+
thinkingModes: Object.keys(m.llm.thinking).filter((mode) => ["off", "low", "medium", "high", "xhigh"].includes(mode)),
|
|
25
|
+
}
|
|
26
|
+
: {}),
|
|
27
|
+
})),
|
|
28
|
+
complete: async function* (request) {
|
|
29
|
+
const model = requireModel(id, "llm", request.model);
|
|
30
|
+
if (!("llm" in model))
|
|
31
|
+
throw new Error("Invalid LLM catalogue entry");
|
|
32
|
+
if (request.webSearch && !model.llm.webSearch)
|
|
33
|
+
throw providerError({
|
|
34
|
+
kind: "unsupported",
|
|
35
|
+
message: "This model does not support research web search.",
|
|
36
|
+
});
|
|
37
|
+
const thinkingConfig = request.thinking === undefined ? undefined : model.llm.thinking?.[request.thinking];
|
|
38
|
+
if (request.thinking !== undefined && !thinkingConfig)
|
|
39
|
+
throw providerError({
|
|
40
|
+
kind: "unsupported",
|
|
41
|
+
message: "This model does not support the selected thinking setting. Choose an available setting.",
|
|
42
|
+
});
|
|
43
|
+
yield* port.complete({ ...request, ...(thinkingConfig ? { thinkingConfig } : {}) });
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
},
|
|
47
|
+
image: (id) => {
|
|
48
|
+
const port = registry.image(id);
|
|
49
|
+
return {
|
|
50
|
+
...port,
|
|
51
|
+
models: async () => catalogue.models(id, "image"),
|
|
52
|
+
generate: async (request) => {
|
|
53
|
+
const model = requireModel(id, "image", request.model);
|
|
54
|
+
if (!("image" in model) || !model.image.aspectRatios.includes(request.aspect))
|
|
55
|
+
throw providerError({
|
|
56
|
+
kind: "unsupported",
|
|
57
|
+
message: "This model does not support the selected video shape.",
|
|
58
|
+
});
|
|
59
|
+
return port.generate(request);
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
},
|
|
63
|
+
tts: (id) => {
|
|
64
|
+
const port = registry.tts(id);
|
|
65
|
+
return {
|
|
66
|
+
...port,
|
|
67
|
+
models: async () => catalogue.models(id, "tts"),
|
|
68
|
+
synthesize: async (request) => {
|
|
69
|
+
const model = requireModel(id, "tts", request.model);
|
|
70
|
+
if (!("tts" in model))
|
|
71
|
+
throw new Error("Invalid TTS catalogue entry");
|
|
72
|
+
const parts = splitText(request.text, model.tts.maxCharacters);
|
|
73
|
+
if (parts.length <= 1)
|
|
74
|
+
return port.synthesize({ ...request, model: model.id });
|
|
75
|
+
const abort = new AbortController();
|
|
76
|
+
const signal = AbortSignal.any([request.signal, abort.signal]);
|
|
77
|
+
const iterator = audioParts(parts, { ...request, model: model.id, signal }, port.synthesize);
|
|
78
|
+
return {
|
|
79
|
+
container: "mp3",
|
|
80
|
+
audio: new ReadableStream({
|
|
81
|
+
async pull(controller) {
|
|
82
|
+
try {
|
|
83
|
+
const next = await iterator.next();
|
|
84
|
+
if (next.done)
|
|
85
|
+
controller.close();
|
|
86
|
+
else
|
|
87
|
+
controller.enqueue(next.value);
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
controller.error(error);
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
async cancel() {
|
|
94
|
+
abort.abort();
|
|
95
|
+
await iterator.return();
|
|
96
|
+
},
|
|
97
|
+
}, { highWaterMark: 0 }),
|
|
98
|
+
};
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
async function* audioParts(parts, request, synthesize) {
|
|
105
|
+
let tokens = {};
|
|
106
|
+
const saved = request.continuation?.read();
|
|
107
|
+
if (saved) {
|
|
108
|
+
try {
|
|
109
|
+
const parsed = JSON.parse(saved);
|
|
110
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed))
|
|
111
|
+
tokens = Object.fromEntries(Object.entries(parsed).filter((entry) => typeof entry[1] === "string"));
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
/* A legacy single-job token cannot identify several split jobs. */
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
for (const [index, text] of parts.entries()) {
|
|
118
|
+
request.signal.throwIfAborted();
|
|
119
|
+
// Body chunks are persisted individually by narration. Long intro/outro text
|
|
120
|
+
// is bounded here as well; each split has its own remote continuation.
|
|
121
|
+
const response = await synthesize({
|
|
122
|
+
...request,
|
|
123
|
+
text,
|
|
124
|
+
continuation: {
|
|
125
|
+
read: () => tokens[String(index)],
|
|
126
|
+
write: (value) => {
|
|
127
|
+
tokens[String(index)] = value;
|
|
128
|
+
request.continuation?.write(JSON.stringify(tokens));
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
const reader = response.audio.getReader();
|
|
133
|
+
try {
|
|
134
|
+
for (;;) {
|
|
135
|
+
const next = await reader.read();
|
|
136
|
+
request.signal.throwIfAborted();
|
|
137
|
+
if (next.done)
|
|
138
|
+
break;
|
|
139
|
+
yield next.value;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
finally {
|
|
143
|
+
await reader.cancel().catch(() => { });
|
|
144
|
+
reader.releaseLock();
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|