@qwen-code/arena-agent 0.0.9-dev.23 → 0.0.9-dev.24

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.
Files changed (40) hide show
  1. package/.env.example +9 -10
  2. package/README.md +12 -9
  3. package/dist/cli.js +0 -0
  4. package/dist/code-cli.js +0 -0
  5. package/dist/code-tools.js +14 -160
  6. package/dist/code-tools.js.map +1 -1
  7. package/dist/model-router-media.d.ts +21 -0
  8. package/dist/model-router-media.js +298 -0
  9. package/dist/model-router-media.js.map +1 -0
  10. package/dist/tools.js +66 -193
  11. package/dist/tools.js.map +1 -1
  12. package/dist/web/app.d.ts +1 -0
  13. package/package.json +1 -1
  14. package/dist/audit.d.ts +0 -14
  15. package/dist/audit.js +0 -31
  16. package/dist/audit.js.map +0 -1
  17. package/dist/dashscope.d.ts +0 -5
  18. package/dist/dashscope.js +0 -55
  19. package/dist/dashscope.js.map +0 -1
  20. package/dist/fetch-page/aparat.d.ts +0 -30
  21. package/dist/fetch-page/aparat.js +0 -209
  22. package/dist/fetch-page/aparat.js.map +0 -1
  23. package/dist/fetch-page/json-path.d.ts +0 -12
  24. package/dist/fetch-page/json-path.js +0 -111
  25. package/dist/fetch-page/json-path.js.map +0 -1
  26. package/dist/fetch-page/sites.d.ts +0 -26
  27. package/dist/fetch-page/sites.js +0 -39
  28. package/dist/fetch-page/sites.js.map +0 -1
  29. package/dist/fetch-page/text.d.ts +0 -5
  30. package/dist/fetch-page/text.js +0 -38
  31. package/dist/fetch-page/text.js.map +0 -1
  32. package/dist/fetch-page/types.d.ts +0 -17
  33. package/dist/fetch-page/types.js +0 -3
  34. package/dist/fetch-page/types.js.map +0 -1
  35. package/dist/fetch-page/youtube.d.ts +0 -35
  36. package/dist/fetch-page/youtube.js +0 -282
  37. package/dist/fetch-page/youtube.js.map +0 -1
  38. package/dist/web-search.d.ts +0 -5
  39. package/dist/web-search.js +0 -465
  40. package/dist/web-search.js.map +0 -1
@@ -0,0 +1,298 @@
1
+ import { spawn } from "node:child_process";
2
+ import { setTimeout as delay } from "node:timers/promises";
3
+ const defaultVertexBaseUrl = "https://routify-pub.alibaba-inc.com/protocol/vertex/v1beta";
4
+ const maximumGeneratedFileBytes = 25 * 1024 * 1024;
5
+ const maximumJsonResponseBytes = 35 * 1024 * 1024;
6
+ function record(value) {
7
+ return value !== null && typeof value === "object" && !Array.isArray(value)
8
+ ? value
9
+ : {};
10
+ }
11
+ function stringValue(value) {
12
+ return typeof value === "string" ? value : "";
13
+ }
14
+ function modelRouterApiKey() {
15
+ const apiKey = process.env.ROUTIFY_API_KEY?.trim();
16
+ if (!apiKey) {
17
+ throw new Error("ModelRouter media tools require ROUTIFY_API_KEY");
18
+ }
19
+ return apiKey.replace(/^Bearer\s+/i, "");
20
+ }
21
+ function vertexModelUrl(model) {
22
+ const baseUrl = (process.env.ROUTIFY_VERTEX_BASE_URL || defaultVertexBaseUrl).replace(/\/+$/, "");
23
+ return `${baseUrl}/models/${encodeURIComponent(model)}:generateContent`;
24
+ }
25
+ function retryableStatus(status) {
26
+ return status === 408 || status === 409 || status === 429 || status >= 500;
27
+ }
28
+ function retryDelayMilliseconds(response, attempt) {
29
+ const retryAfter = Number(response.headers.get("retry-after"));
30
+ if (Number.isFinite(retryAfter) && retryAfter >= 0) {
31
+ return Math.min(60_000, retryAfter * 1000);
32
+ }
33
+ return Math.min(30_000, 2_000 * 2 ** attempt);
34
+ }
35
+ function responseDetail(payload, rawBody) {
36
+ return (stringValue(record(payload.routify_response).error_message) ||
37
+ stringValue(record(payload.error).message) ||
38
+ stringValue(payload.message) ||
39
+ stringValue(payload.detailMessage) ||
40
+ rawBody ||
41
+ "unknown provider error");
42
+ }
43
+ async function modelRouterFetch(url, init, label) {
44
+ const headers = new Headers(init.headers);
45
+ headers.set("x-goog-api-key", `Bearer ${modelRouterApiKey()}`);
46
+ const configuredRetries = Number(process.env.ROUTIFY_MEDIA_MAX_RETRIES ||
47
+ process.env.CODE_HARNESS_TOOL_MAX_RETRIES ||
48
+ 5);
49
+ const maximumRetries = Number.isSafeInteger(configuredRetries) && configuredRetries >= 0
50
+ ? configuredRetries
51
+ : 5;
52
+ for (let attempt = 0;; attempt += 1) {
53
+ const response = await fetch(url, { ...init, headers });
54
+ if (response.ok)
55
+ return response;
56
+ const rawBody = await response.text();
57
+ let payload = {};
58
+ try {
59
+ payload = record(JSON.parse(rawBody));
60
+ }
61
+ catch {
62
+ // Keep the raw response body as the diagnostic.
63
+ }
64
+ const retryable = retryableStatus(response.status);
65
+ if (!retryable || attempt >= maximumRetries) {
66
+ throw new Error(`${label} failed (${response.status} ${response.statusText}): ${responseDetail(payload, rawBody).slice(0, 2000)}`);
67
+ }
68
+ await delay(retryDelayMilliseconds(response, attempt), undefined, {
69
+ signal: init.signal || undefined,
70
+ });
71
+ }
72
+ }
73
+ async function responseBytes(response, limit = maximumGeneratedFileBytes) {
74
+ const bytes = Buffer.from(await response.arrayBuffer());
75
+ if (bytes.length > limit) {
76
+ throw new Error(`generated file exceeds ${limit} bytes`);
77
+ }
78
+ return bytes;
79
+ }
80
+ function parseJsonResponse(bytes, label) {
81
+ try {
82
+ return record(JSON.parse(bytes.toString("utf8")));
83
+ }
84
+ catch {
85
+ throw new Error(`${label} returned malformed JSON`);
86
+ }
87
+ }
88
+ function inlineDataFromResponse(payload, label, expectedMimePrefix) {
89
+ const candidates = Array.isArray(payload.candidates)
90
+ ? payload.candidates
91
+ : [];
92
+ for (const candidate of candidates) {
93
+ const parts = Array.isArray(record(record(candidate).content).parts)
94
+ ? record(record(candidate).content).parts
95
+ : [];
96
+ for (const part of parts) {
97
+ const inlineData = record(record(part).inlineData);
98
+ const mimeType = stringValue(inlineData.mimeType).toLowerCase();
99
+ const data = stringValue(inlineData.data);
100
+ if (!data || !mimeType.startsWith(expectedMimePrefix))
101
+ continue;
102
+ const bytes = Buffer.from(data, "base64");
103
+ if (bytes.length > maximumGeneratedFileBytes) {
104
+ throw new Error(`generated file exceeds ${maximumGeneratedFileBytes} bytes`);
105
+ }
106
+ return { bytes, mimeType };
107
+ }
108
+ }
109
+ throw new Error(`${label} response did not include ${expectedMimePrefix} data`);
110
+ }
111
+ const imageAspectRatios = [
112
+ ["1:1", 1],
113
+ ["2:3", 2 / 3],
114
+ ["3:2", 3 / 2],
115
+ ["3:4", 3 / 4],
116
+ ["4:3", 4 / 3],
117
+ ["4:5", 4 / 5],
118
+ ["5:4", 5 / 4],
119
+ ["9:16", 9 / 16],
120
+ ["16:9", 16 / 9],
121
+ ["21:9", 21 / 9],
122
+ ];
123
+ function geminiImageConfig(size) {
124
+ const match = size.replace("*", "x").match(/^(\d+)x(\d+)$/i);
125
+ const width = Number(match?.[1] || 1024);
126
+ const height = Number(match?.[2] || 1024);
127
+ const ratio = width > 0 && height > 0 ? width / height : 1;
128
+ const [aspectRatio] = imageAspectRatios.reduce((best, candidate) => Math.abs(Math.log(candidate[1] / ratio)) <
129
+ Math.abs(Math.log(best[1] / ratio))
130
+ ? candidate
131
+ : best);
132
+ const maximumDimension = Math.max(width, height);
133
+ const imageSize = maximumDimension > 2048
134
+ ? "4K"
135
+ : maximumDimension > 1024
136
+ ? "2K"
137
+ : "1K";
138
+ return { aspectRatio, imageSize };
139
+ }
140
+ export async function generateImageWithModelRouter(options) {
141
+ const parts = [{ text: options.prompt }];
142
+ for (const image of options.images || []) {
143
+ parts.push({
144
+ inlineData: {
145
+ mimeType: image.mimeType,
146
+ data: image.bytes.toString("base64"),
147
+ },
148
+ });
149
+ }
150
+ const response = await modelRouterFetch(vertexModelUrl(options.model), {
151
+ method: "POST",
152
+ headers: { "content-type": "application/json" },
153
+ body: JSON.stringify({
154
+ contents: [{ role: "user", parts }],
155
+ generationConfig: {
156
+ responseModalities: ["IMAGE"],
157
+ imageConfig: geminiImageConfig(options.size),
158
+ },
159
+ }),
160
+ signal: options.signal,
161
+ }, "generate_image");
162
+ const payload = parseJsonResponse(await responseBytes(response, maximumJsonResponseBytes), "generate_image");
163
+ return inlineDataFromResponse(payload, "generate_image", "image/").bytes;
164
+ }
165
+ function waveFromPcm(pcm, sampleRate, channels) {
166
+ const header = Buffer.alloc(44);
167
+ const bitsPerSample = 16;
168
+ const blockAlign = (channels * bitsPerSample) / 8;
169
+ header.write("RIFF", 0, "ascii");
170
+ header.writeUInt32LE(36 + pcm.length, 4);
171
+ header.write("WAVE", 8, "ascii");
172
+ header.write("fmt ", 12, "ascii");
173
+ header.writeUInt32LE(16, 16);
174
+ header.writeUInt16LE(1, 20);
175
+ header.writeUInt16LE(channels, 22);
176
+ header.writeUInt32LE(sampleRate, 24);
177
+ header.writeUInt32LE(sampleRate * blockAlign, 28);
178
+ header.writeUInt16LE(blockAlign, 32);
179
+ header.writeUInt16LE(bitsPerSample, 34);
180
+ header.write("data", 36, "ascii");
181
+ header.writeUInt32LE(pcm.length, 40);
182
+ return Buffer.concat([header, pcm]);
183
+ }
184
+ function pcmFormatFromMimeType(mimeType) {
185
+ const sampleRate = Number(mimeType.match(/rate=(\d+)/i)?.[1] || 24000);
186
+ const channels = Number(mimeType.match(/channels=(\d+)/i)?.[1] || 1);
187
+ return {
188
+ sampleRate: Number.isSafeInteger(sampleRate) && sampleRate > 0
189
+ ? sampleRate
190
+ : 24000,
191
+ channels: Number.isSafeInteger(channels) && channels > 0 ? channels : 1,
192
+ };
193
+ }
194
+ async function runEncoder(command, arguments_, input, signal) {
195
+ return new Promise((resolve, reject) => {
196
+ const child = spawn(command, arguments_, {
197
+ signal,
198
+ stdio: ["pipe", "pipe", "pipe"],
199
+ });
200
+ const stdout = [];
201
+ const stderr = [];
202
+ let outputBytes = 0;
203
+ let settled = false;
204
+ const finish = (error, output) => {
205
+ if (settled)
206
+ return;
207
+ settled = true;
208
+ if (error)
209
+ reject(error);
210
+ else
211
+ resolve(output);
212
+ };
213
+ child.stdout.on("data", (chunk) => {
214
+ outputBytes += chunk.length;
215
+ if (outputBytes > maximumGeneratedFileBytes) {
216
+ child.kill("SIGKILL");
217
+ finish(new Error(`generated file exceeds ${maximumGeneratedFileBytes} bytes`));
218
+ return;
219
+ }
220
+ stdout.push(chunk);
221
+ });
222
+ child.stderr.on("data", (chunk) => {
223
+ if (Buffer.concat(stderr).length < 4096)
224
+ stderr.push(chunk);
225
+ });
226
+ child.stdin.on("error", () => undefined);
227
+ child.on("error", (error) => {
228
+ if (error.code === "ENOENT")
229
+ finish(undefined, undefined);
230
+ else
231
+ finish(error);
232
+ });
233
+ child.on("close", (code) => {
234
+ if (settled)
235
+ return;
236
+ if (code === 0)
237
+ finish(undefined, Buffer.concat(stdout));
238
+ else {
239
+ finish(new Error(`${command} audio conversion failed (${code}): ${Buffer.concat(stderr)
240
+ .toString("utf8")
241
+ .slice(0, 1000)}`));
242
+ }
243
+ });
244
+ child.stdin.end(input);
245
+ });
246
+ }
247
+ async function encodeWave(wave, format, signal) {
248
+ if (format === "wav")
249
+ return wave;
250
+ const ffmpegArguments = [
251
+ "-hide_banner",
252
+ "-loglevel",
253
+ "error",
254
+ "-i",
255
+ "pipe:0",
256
+ ...(format === "opus" ? ["-c:a", "libopus"] : []),
257
+ "-f",
258
+ format,
259
+ "pipe:1",
260
+ ];
261
+ const ffmpeg = await runEncoder("ffmpeg", ffmpegArguments, wave, signal);
262
+ if (ffmpeg)
263
+ return ffmpeg;
264
+ if (format === "mp3") {
265
+ const lame = await runEncoder("lame", ["--silent", "-", "-"], wave, signal);
266
+ if (lame)
267
+ return lame;
268
+ }
269
+ throw new Error(`Gemini TTS ${format} output requires ffmpeg${format === "mp3" ? " or lame" : ""}; use a .wav output path or install an encoder`);
270
+ }
271
+ export async function generateSpeechWithModelRouter(options) {
272
+ const response = await modelRouterFetch(vertexModelUrl(options.model), {
273
+ method: "POST",
274
+ headers: { "content-type": "application/json" },
275
+ body: JSON.stringify({
276
+ contents: [
277
+ {
278
+ role: "user",
279
+ parts: [{ text: options.text }],
280
+ },
281
+ ],
282
+ generationConfig: {
283
+ responseModalities: ["AUDIO"],
284
+ speechConfig: {
285
+ voiceConfig: {
286
+ prebuiltVoiceConfig: { voiceName: options.voice },
287
+ },
288
+ },
289
+ },
290
+ }),
291
+ signal: options.signal,
292
+ }, "generate_speech");
293
+ const payload = parseJsonResponse(await responseBytes(response, maximumJsonResponseBytes), "generate_speech");
294
+ const audio = inlineDataFromResponse(payload, "generate_speech", "audio/");
295
+ const { sampleRate, channels } = pcmFormatFromMimeType(audio.mimeType);
296
+ return encodeWave(waveFromPcm(audio.bytes, sampleRate, channels), options.format, options.signal);
297
+ }
298
+ //# sourceMappingURL=model-router-media.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model-router-media.js","sourceRoot":"","sources":["../src/model-router-media.ts"],"names":[],"mappings":"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"}
package/dist/tools.js CHANGED
@@ -2,7 +2,6 @@ import { spawn } from "node:child_process";
2
2
  import { existsSync, mkdirSync, realpathSync, } from "node:fs";
3
3
  import { open, readFile, realpath, stat, writeFile, } from "node:fs/promises";
4
4
  import path from "node:path";
5
- import { setTimeout as delay } from "node:timers/promises";
6
5
  import { Ajv } from "ajv";
7
6
  import addFormats from "ajv-formats";
8
7
  import { detect as detectEncoding } from "chardet";
@@ -17,6 +16,7 @@ import { FetchPageReader } from "./fetch-page.js";
17
16
  import { prepareImageForModel } from "./image.js";
18
17
  import { imageTextFromFallbackModel } from "./image-input-fallback.js";
19
18
  import { isCodeArenaMode } from "./mode.js";
19
+ import { generateImageWithModelRouter, generateSpeechWithModelRouter, } from "./model-router-media.js";
20
20
  import { HttpModelClient } from "./provider.js";
21
21
  import { RoutifyFetchPageReader, routifyWebFetchEnabled, } from "./routify-web-fetch.js";
22
22
  import { toolDefinitions } from "./spec.js";
@@ -361,58 +361,6 @@ function record(value) {
361
361
  function stringValue(value) {
362
362
  return typeof value === "string" ? value : "";
363
363
  }
364
- function dashScopeApiKey() {
365
- const apiKey = process.env.DASHSCOPE_API_KEY;
366
- if (!apiKey) {
367
- throw new Error("DashScope tools require DASHSCOPE_API_KEY");
368
- }
369
- return apiKey;
370
- }
371
- function dashScopeRoot() {
372
- return (process.env.DASHSCOPE_API_BASE ||
373
- process.env.DASHSCOPE_BASE_URL ||
374
- "https://dashscope.aliyuncs.com")
375
- .replace(/\/+$/, "")
376
- .replace(/\/(?:api\/v1|compatible-mode\/v1)$/i, "");
377
- }
378
- function dashScopeUrl(protocol, pathname) {
379
- const prefix = protocol === "api" ? "/api/v1" : "/compatible-mode/v1";
380
- const normalized = pathname.startsWith("/") ? pathname : `/${pathname}`;
381
- return `${dashScopeRoot()}${prefix}${normalized}`;
382
- }
383
- async function dashScopeFetch(protocol, pathname, init, label) {
384
- const headers = new Headers(init.headers);
385
- headers.set("authorization", `Bearer ${dashScopeApiKey()}`);
386
- const response = await fetch(dashScopeUrl(protocol, pathname), {
387
- ...init,
388
- headers,
389
- });
390
- if (response.ok)
391
- return response;
392
- const body = await response.text();
393
- let detail = body;
394
- try {
395
- const payload = record(JSON.parse(body));
396
- detail =
397
- stringValue(payload.message) ||
398
- stringValue(record(payload.error).message) ||
399
- body;
400
- }
401
- catch {
402
- // Keep the response text as the diagnostic.
403
- }
404
- throw new Error(`${label} failed (${response.status} ${response.statusText}): ${detail.slice(0, 2000)}`);
405
- }
406
- async function dashScopeJson(protocol, pathname, init, label) {
407
- const response = await dashScopeFetch(protocol, pathname, init, label);
408
- return record(await response.json());
409
- }
410
- function responseMessage(payload) {
411
- return (stringValue(payload.message) ||
412
- stringValue(record(payload.output).message) ||
413
- stringValue(payload.code) ||
414
- "unknown provider error");
415
- }
416
364
  async function responseBytes(response, limit = 4 * 1024 * 1024) {
417
365
  const bytes = Buffer.from(await response.arrayBuffer());
418
366
  if (bytes.length > limit) {
@@ -1019,9 +967,8 @@ class ArenaToolExecutor {
1019
967
  throw new Error("file_path must end in .jpg, .jpeg, or .png");
1020
968
  }
1021
969
  mkdirSync(path.dirname(target), { recursive: true });
1022
- const model = process.env.ARENA_IMAGE_MODEL || "qwen-image-2.0-pro";
1023
- const isQwenImage2 = model.startsWith("qwen-image-2.0");
1024
- const content = [];
970
+ const model = process.env.ARENA_IMAGE_MODEL || "gemini-2.5-flash-image";
971
+ const references = [];
1025
972
  for (const imagePath of images || []) {
1026
973
  const source = await this.confinedPath(imagePath);
1027
974
  const extension = path.extname(source).toLowerCase();
@@ -1033,96 +980,19 @@ class ArenaToolExecutor {
1033
980
  if (bytes.length > 20 * 1024 * 1024) {
1034
981
  throw new Error(`reference image exceeds 20 MB: ${imagePath}`);
1035
982
  }
1036
- content.push({
1037
- image: `data:${mimeType};base64,${bytes.toString("base64")}`,
983
+ references.push({
984
+ bytes,
985
+ fileName: path.basename(source),
986
+ mimeType,
1038
987
  });
1039
988
  }
1040
- content.push({ text: prompt });
1041
- const parameters = {
1042
- size: process.env.ARENA_IMAGE_SIZE ||
1043
- (isQwenImage2 ? "2048*2048" : "2K"),
1044
- watermark: false,
1045
- };
1046
- if (isQwenImage2) {
1047
- parameters.prompt_extend = true;
1048
- }
1049
- else {
1050
- parameters.n = 1;
1051
- if (!images?.length)
1052
- parameters.thinking_mode = true;
1053
- }
1054
- const headers = {
1055
- "content-type": "application/json",
1056
- };
1057
- if (!isQwenImage2)
1058
- headers["x-dashscope-async"] = "enable";
1059
- const submitted = await dashScopeJson("api", isQwenImage2
1060
- ? "/services/aigc/multimodal-generation/generation"
1061
- : "/services/aigc/image-generation/generation", {
1062
- method: "POST",
1063
- headers,
1064
- body: JSON.stringify({
1065
- model,
1066
- input: {
1067
- messages: [{ role: "user", content }],
1068
- },
1069
- parameters,
1070
- }),
989
+ await writeFile(target, await generateImageWithModelRouter({
990
+ images: references,
991
+ model,
992
+ prompt,
1071
993
  signal: this.signal,
1072
- }, "generate_image");
1073
- let completed = isQwenImage2
1074
- ? submitted
1075
- : undefined;
1076
- if (!isQwenImage2) {
1077
- const taskId = stringValue(record(submitted.output).task_id);
1078
- if (!taskId) {
1079
- throw new Error(`generate_image returned no task_id: ${responseMessage(submitted)}`);
1080
- }
1081
- const timeoutMs = Number(process.env.DASHSCOPE_TASK_TIMEOUT_MS || 5 * 60 * 1000);
1082
- const pollIntervalMs = Number(process.env.DASHSCOPE_TASK_POLL_INTERVAL_MS || 1000);
1083
- const deadline = Date.now() + timeoutMs;
1084
- while (Date.now() <= deadline) {
1085
- const task = await dashScopeJson("api", `/tasks/${encodeURIComponent(taskId)}`, { method: "GET", signal: this.signal }, "generate_image status");
1086
- const output = record(task.output);
1087
- const taskStatus = stringValue(output.task_status).toUpperCase();
1088
- if (taskStatus === "SUCCEEDED") {
1089
- completed = task;
1090
- break;
1091
- }
1092
- if (["FAILED", "CANCELED", "UNKNOWN"].includes(taskStatus)) {
1093
- throw new Error(`generate_image task ${taskStatus}: ${responseMessage(task)}`);
1094
- }
1095
- if (Date.now() + pollIntervalMs > deadline)
1096
- break;
1097
- await delay(pollIntervalMs, undefined, { signal: this.signal });
1098
- }
1099
- if (!completed) {
1100
- throw new Error(`generate_image timed out waiting for task ${taskId}`);
1101
- }
1102
- }
1103
- if (!completed) {
1104
- throw new Error("generate_image returned no response");
1105
- }
1106
- const output = record(completed.output);
1107
- const choices = Array.isArray(output.choices) ? output.choices : [];
1108
- const message = record(record(choices[0]).message);
1109
- const generatedContent = Array.isArray(message.content)
1110
- ? message.content
1111
- : [];
1112
- const generated = generatedContent
1113
- .map(record)
1114
- .find((item) => typeof item.image === "string");
1115
- const legacyResults = Array.isArray(output.results) ? output.results : [];
1116
- const imageUrl = stringValue(generated?.image) ||
1117
- stringValue(record(legacyResults[0]).url);
1118
- if (!imageUrl) {
1119
- throw new Error("generate_image response did not include an image URL");
1120
- }
1121
- const imageResponse = await fetch(imageUrl, { signal: this.signal });
1122
- if (!imageResponse.ok) {
1123
- throw new Error(`image download failed (${imageResponse.status})`);
1124
- }
1125
- await writeFile(target, await responseBytes(imageResponse, 25 * 1024 * 1024));
994
+ size: process.env.ARENA_IMAGE_SIZE || "1024*1024",
995
+ }));
1126
996
  return {
1127
997
  status: "success",
1128
998
  file_path: this.relativePath(target),
@@ -1137,65 +1007,68 @@ class ArenaToolExecutor {
1137
1007
  ".opus": "opus",
1138
1008
  }[extension];
1139
1009
  if (!format) {
1140
- throw new Error("DashScope speech output supports .mp3, .wav, or .opus");
1010
+ throw new Error("ModelRouter speech output supports .mp3, .wav, or .opus");
1141
1011
  }
1142
1012
  mkdirSync(path.dirname(target), { recursive: true });
1143
- const model = process.env.ARENA_SPEECH_MODEL || "qwen-audio-3.0-tts-flash";
1013
+ const model = process.env.ARENA_SPEECH_MODEL || "gemini-3.1-flash-tts-preview";
1144
1014
  const feminine = voiceIdentity.gender !== "masculine";
1145
1015
  const primaryLanguage = language.toLowerCase().split(/[-_]/)[0] || "";
1146
- const voices = primaryLanguage === "en"
1016
+ const geminiTts = /^gemini-.*-tts(?:-|$)/i.test(model);
1017
+ const voices = geminiTts
1147
1018
  ? feminine
1148
- ? ["loongeva_v3.6", "longanhuan_v3.6"]
1149
- : ["loongjohn", "longjielidou_v3.6"]
1150
- : feminine
1151
- ? ["longanhuan_v3.6"]
1152
- : ["longjielidou_v3.6"];
1019
+ ? [
1020
+ "Kore",
1021
+ "Aoede",
1022
+ "Zephyr",
1023
+ "Leda",
1024
+ "Callirrhoe",
1025
+ "Autonoe",
1026
+ "Despina",
1027
+ "Erinome",
1028
+ "Laomedeia",
1029
+ "Achernar",
1030
+ "Gacrux",
1031
+ "Pulcherrima",
1032
+ "Vindemiatrix",
1033
+ "Sulafat",
1034
+ ]
1035
+ : [
1036
+ "Puck",
1037
+ "Charon",
1038
+ "Fenrir",
1039
+ "Orus",
1040
+ "Enceladus",
1041
+ "Iapetus",
1042
+ "Umbriel",
1043
+ "Algieba",
1044
+ "Algenib",
1045
+ "Rasalgethi",
1046
+ "Alnilam",
1047
+ "Schedar",
1048
+ "Achird",
1049
+ "Zubenelgenubi",
1050
+ "Sadachbia",
1051
+ "Sadaltager",
1052
+ ]
1053
+ : primaryLanguage === "en"
1054
+ ? feminine
1055
+ ? ["loongeva_v3.6", "longanhuan_v3.6"]
1056
+ : ["loongjohn", "longjielidou_v3.6"]
1057
+ : feminine
1058
+ ? ["longanhuan_v3.6"]
1059
+ : ["longjielidou_v3.6"];
1153
1060
  const requestedIndex = Math.max(0, Math.trunc(Number(voiceIdentity.index || 0)));
1154
- const voice = process.env.DASHSCOPE_SPEECH_VOICE ||
1061
+ const voice = process.env.ARENA_SPEECH_VOICE ||
1155
1062
  voices[requestedIndex % voices.length] ||
1156
- voices[0];
1157
- const supportedHints = new Set([
1158
- "ar",
1159
- "de",
1160
- "en",
1161
- "es",
1162
- "fil",
1163
- "fr",
1164
- "id",
1165
- "it",
1166
- "ja",
1167
- "ko",
1168
- "ms",
1169
- "pt",
1170
- "ru",
1171
- "th",
1172
- "vi",
1173
- "zh",
1174
- ]);
1175
- const input = {
1176
- text,
1177
- voice,
1063
+ voices[0] ||
1064
+ (geminiTts ? "Kore" : "longanhuan_v3.6");
1065
+ await writeFile(target, await generateSpeechWithModelRouter({
1178
1066
  format,
1179
- sample_rate: 24000,
1180
- };
1181
- if (supportedHints.has(primaryLanguage)) {
1182
- input.language_hints = [primaryLanguage];
1183
- }
1184
- const payload = await dashScopeJson("api", "/services/audio/tts/SpeechSynthesizer", {
1185
- method: "POST",
1186
- headers: { "content-type": "application/json" },
1187
- body: JSON.stringify({ model, input }),
1067
+ model,
1188
1068
  signal: this.signal,
1189
- }, "generate_speech");
1190
- const audioUrl = stringValue(record(record(payload.output).audio).url);
1191
- if (!audioUrl) {
1192
- throw new Error(`generate_speech returned no audio URL: ${responseMessage(payload)}`);
1193
- }
1194
- const audioResponse = await fetch(audioUrl, { signal: this.signal });
1195
- if (!audioResponse.ok) {
1196
- throw new Error(`audio download failed (${audioResponse.status})`);
1197
- }
1198
- await writeFile(target, await responseBytes(audioResponse, 25 * 1024 * 1024));
1069
+ text,
1070
+ voice,
1071
+ }));
1199
1072
  return {
1200
1073
  status: "success",
1201
1074
  file_path: this.relativePath(target),