@qwen-code/arena-agent 0.0.9-dev.8 → 0.0.10-dev.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/.env.example +54 -19
- package/README.md +218 -45
- package/dist/agent.d.ts +4 -2
- package/dist/agent.js +167 -21
- package/dist/agent.js.map +1 -1
- package/dist/anthropic.d.ts +1 -0
- package/dist/anthropic.js +90 -23
- package/dist/anthropic.js.map +1 -1
- package/dist/attachments.d.ts +1 -1
- package/dist/attachments.js +16 -4
- package/dist/attachments.js.map +1 -1
- package/dist/brave-search.d.ts +10 -0
- package/dist/brave-search.js +238 -0
- package/dist/brave-search.js.map +1 -0
- package/dist/canonical-data.js +2 -2
- package/dist/canonical-data.js.map +1 -1
- package/dist/cli.js +64 -18
- package/dist/cli.js.map +1 -1
- package/dist/code-tools.d.ts +0 -5
- package/dist/code-tools.js +29 -353
- package/dist/code-tools.js.map +1 -1
- package/dist/compaction.js +8 -2
- package/dist/compaction.js.map +1 -1
- package/dist/config.d.ts +30 -0
- package/dist/config.js +57 -3
- package/dist/config.js.map +1 -1
- package/dist/fetch-page.d.ts +1 -0
- package/dist/fetch-page.js +1 -1
- package/dist/fetch-page.js.map +1 -1
- package/dist/history.d.ts +3 -2
- package/dist/history.js +7 -2
- package/dist/history.js.map +1 -1
- package/dist/image-input-fallback.d.ts +9 -0
- package/dist/image-input-fallback.js +47 -0
- package/dist/image-input-fallback.js.map +1 -0
- package/dist/image.d.ts +16 -0
- package/dist/image.js +380 -0
- package/dist/image.js.map +1 -0
- package/dist/mock.d.ts +1 -0
- package/dist/mock.js +38 -0
- package/dist/mock.js.map +1 -1
- package/dist/model-capabilities.js +12 -2
- package/dist/model-capabilities.js.map +1 -1
- package/dist/model-diagnostics.js +16 -5
- package/dist/model-diagnostics.js.map +1 -1
- package/dist/model-router-media.d.ts +26 -0
- package/dist/model-router-media.js +274 -0
- package/dist/model-router-media.js.map +1 -0
- package/dist/openai-responses.d.ts +28 -0
- package/dist/openai-responses.js +473 -0
- package/dist/openai-responses.js.map +1 -0
- package/dist/process-manager.d.ts +38 -0
- package/dist/process-manager.js +486 -0
- package/dist/process-manager.js.map +1 -0
- package/dist/prompts.js +3 -1
- package/dist/prompts.js.map +1 -1
- package/dist/provider.d.ts +7 -0
- package/dist/provider.js +291 -27
- package/dist/provider.js.map +1 -1
- package/dist/routify-web-fetch.d.ts +17 -0
- package/dist/routify-web-fetch.js +188 -0
- package/dist/routify-web-fetch.js.map +1 -0
- package/dist/sdk.d.ts +9 -2
- package/dist/sdk.js +5 -2
- package/dist/sdk.js.map +1 -1
- package/dist/server.d.ts +3 -0
- package/dist/server.js +112 -21
- package/dist/server.js.map +1 -1
- package/dist/spec.js +15 -1
- package/dist/spec.js.map +1 -1
- package/dist/tools.d.ts +2 -1
- package/dist/tools.js +250 -340
- package/dist/tools.js.map +1 -1
- package/dist/tui.d.ts +3 -0
- package/dist/tui.js +18 -11
- package/dist/tui.js.map +1 -1
- package/dist/types.d.ts +17 -6
- package/dist/voice-registry.d.ts +30 -0
- package/dist/voice-registry.js +82 -0
- package/dist/voice-registry.js.map +1 -0
- package/dist/web/app.js +67 -6
- package/dist/web/app.js.map +1 -1
- package/docs/BINARY_DISTRIBUTION.md +25 -18
- package/docs/MODES_AND_RELEASES.md +8 -4
- package/docs/SDK.md +28 -4
- package/package.json +21 -3
- package/web/styles.css +28 -7
package/dist/code-tools.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
3
|
import { closeSync, existsSync, mkdirSync, openSync, realpathSync, } from "node:fs";
|
|
4
|
-
import {
|
|
5
|
-
import { createConnection } from "node:net";
|
|
4
|
+
import { mkdir, readFile, readdir, realpath, rm, stat, writeFile, } from "node:fs/promises";
|
|
6
5
|
import { tmpdir } from "node:os";
|
|
7
6
|
import path from "node:path";
|
|
8
7
|
import { setTimeout as delay } from "node:timers/promises";
|
|
@@ -11,7 +10,11 @@ import addFormats from "ajv-formats";
|
|
|
11
10
|
import { detect as detectEncoding } from "chardet";
|
|
12
11
|
import iconv from "iconv-lite";
|
|
13
12
|
import mime from "mime/lite";
|
|
13
|
+
import { executeBraveWebSearch } from "./brave-search.js";
|
|
14
14
|
import { FetchPageReader } from "./fetch-page.js";
|
|
15
|
+
import { generatedImageBytesForExtension } from "./image.js";
|
|
16
|
+
import { generateImageWithModelRouter, } from "./model-router-media.js";
|
|
17
|
+
import { executeRoutifyWebFetch, routifyWebFetchEnabled, } from "./routify-web-fetch.js";
|
|
15
18
|
import { toolDefinitions } from "./spec.js";
|
|
16
19
|
const knownTextApplicationMimeTypes = new Set([
|
|
17
20
|
"application/ecmascript",
|
|
@@ -387,69 +390,6 @@ function record(value) {
|
|
|
387
390
|
function stringValue(value) {
|
|
388
391
|
return typeof value === "string" ? value : "";
|
|
389
392
|
}
|
|
390
|
-
function dashScopeApiKey() {
|
|
391
|
-
const apiKey = process.env.DASHSCOPE_API_KEY;
|
|
392
|
-
if (!apiKey) {
|
|
393
|
-
throw new Error("DashScope tools require DASHSCOPE_API_KEY");
|
|
394
|
-
}
|
|
395
|
-
return apiKey;
|
|
396
|
-
}
|
|
397
|
-
function dashScopeRoot() {
|
|
398
|
-
return (process.env.DASHSCOPE_API_BASE ||
|
|
399
|
-
process.env.DASHSCOPE_BASE_URL ||
|
|
400
|
-
"https://dashscope.aliyuncs.com")
|
|
401
|
-
.replace(/\/+$/, "")
|
|
402
|
-
.replace(/\/(?:api\/v1|compatible-mode\/v1)$/i, "");
|
|
403
|
-
}
|
|
404
|
-
function dashScopeUrl(protocol, pathname) {
|
|
405
|
-
const prefix = protocol === "api" ? "/api/v1" : "/compatible-mode/v1";
|
|
406
|
-
const normalized = pathname.startsWith("/") ? pathname : `/${pathname}`;
|
|
407
|
-
return `${dashScopeRoot()}${prefix}${normalized}`;
|
|
408
|
-
}
|
|
409
|
-
async function dashScopeFetch(protocol, pathname, init, label) {
|
|
410
|
-
const headers = new Headers(init.headers);
|
|
411
|
-
headers.set("authorization", `Bearer ${dashScopeApiKey()}`);
|
|
412
|
-
const maximumRetries = Math.max(0, Number(process.env.CODE_HARNESS_TOOL_MAX_RETRIES || 5));
|
|
413
|
-
for (let attempt = 0;; attempt += 1) {
|
|
414
|
-
const response = await fetch(dashScopeUrl(protocol, pathname), {
|
|
415
|
-
...init,
|
|
416
|
-
headers,
|
|
417
|
-
});
|
|
418
|
-
if (response.ok)
|
|
419
|
-
return response;
|
|
420
|
-
const body = await response.text();
|
|
421
|
-
let detail = body;
|
|
422
|
-
try {
|
|
423
|
-
const payload = record(JSON.parse(body));
|
|
424
|
-
detail =
|
|
425
|
-
stringValue(payload.message) ||
|
|
426
|
-
stringValue(record(payload.error).message) ||
|
|
427
|
-
body;
|
|
428
|
-
}
|
|
429
|
-
catch {
|
|
430
|
-
// Keep the response text as the diagnostic.
|
|
431
|
-
}
|
|
432
|
-
const retryable = response.status === 429 || response.status >= 500;
|
|
433
|
-
if (!retryable || attempt >= maximumRetries) {
|
|
434
|
-
throw new Error(`${label} failed (${response.status} ${response.statusText}): ${detail.slice(0, 2000)}`);
|
|
435
|
-
}
|
|
436
|
-
const retryAfter = Number(response.headers.get("retry-after"));
|
|
437
|
-
const waitMs = Number.isFinite(retryAfter) && retryAfter > 0
|
|
438
|
-
? Math.min(60_000, retryAfter * 1000)
|
|
439
|
-
: Math.min(30_000, 2_000 * 2 ** attempt);
|
|
440
|
-
await delay(waitMs, undefined, { signal: init.signal || undefined });
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
async function dashScopeJson(protocol, pathname, init, label) {
|
|
444
|
-
const response = await dashScopeFetch(protocol, pathname, init, label);
|
|
445
|
-
return record(await response.json());
|
|
446
|
-
}
|
|
447
|
-
function responseMessage(payload) {
|
|
448
|
-
return (stringValue(payload.message) ||
|
|
449
|
-
stringValue(record(payload.output).message) ||
|
|
450
|
-
stringValue(payload.code) ||
|
|
451
|
-
"unknown provider error");
|
|
452
|
-
}
|
|
453
393
|
function contentTypeForFile(filePath) {
|
|
454
394
|
const extension = path.extname(filePath).toLowerCase();
|
|
455
395
|
if ([".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx", ".mts", ".cts"].includes(extension)) {
|
|
@@ -604,13 +544,6 @@ function validationIssues(toolName, value, errors, definitions) {
|
|
|
604
544
|
}
|
|
605
545
|
return issues;
|
|
606
546
|
}
|
|
607
|
-
async function responseBytes(response, limit = 4 * 1024 * 1024) {
|
|
608
|
-
const bytes = Buffer.from(await response.arrayBuffer());
|
|
609
|
-
if (bytes.length > limit) {
|
|
610
|
-
throw new Error(`download exceeds ${limit} bytes`);
|
|
611
|
-
}
|
|
612
|
-
return bytes;
|
|
613
|
-
}
|
|
614
547
|
const defaultMediaCacheTtlMs = 24 * 60 * 60 * 1000;
|
|
615
548
|
const mediaRequestInflight = new Map();
|
|
616
549
|
let pexelsQuota;
|
|
@@ -1382,21 +1315,6 @@ export class CodeToolExecutor {
|
|
|
1382
1315
|
return virtualized;
|
|
1383
1316
|
}
|
|
1384
1317
|
async fullStackBash(command, workdir) {
|
|
1385
|
-
// The captured Full Stack sandbox exposes PostgreSQL and psql from the
|
|
1386
|
-
// beginning of the session, not only after build_and_start. Materialize
|
|
1387
|
-
// that baseline before every bash call so early drizzle/psql commands see
|
|
1388
|
-
// the same environment. ensureFullStackDatabase is a cheap port check
|
|
1389
|
-
// after the first call.
|
|
1390
|
-
const databaseError = await this.ensureFullStackDatabase();
|
|
1391
|
-
if (databaseError) {
|
|
1392
|
-
return {
|
|
1393
|
-
status: "error",
|
|
1394
|
-
message: `Fullstack sandbox setup failed: ${databaseError}`,
|
|
1395
|
-
stdout: "",
|
|
1396
|
-
stderr: databaseError,
|
|
1397
|
-
};
|
|
1398
|
-
}
|
|
1399
|
-
await this.preparePostgresCliShim();
|
|
1400
1318
|
const configuredTimeout = Number(process.env.CODE_HARNESS_BASH_TIMEOUT_SECONDS || 300);
|
|
1401
1319
|
const timeoutSeconds = Number.isFinite(configuredTimeout) && configuredTimeout > 0
|
|
1402
1320
|
? configuredTimeout
|
|
@@ -1415,128 +1333,6 @@ export class CodeToolExecutor {
|
|
|
1415
1333
|
}
|
|
1416
1334
|
return { status: "success", stdout, stderr };
|
|
1417
1335
|
}
|
|
1418
|
-
async portIsOpen(hostname, port, timeoutMs = 500) {
|
|
1419
|
-
return new Promise((resolve) => {
|
|
1420
|
-
const socket = createConnection({ host: hostname, port });
|
|
1421
|
-
const finish = (open) => {
|
|
1422
|
-
socket.removeAllListeners();
|
|
1423
|
-
socket.destroy();
|
|
1424
|
-
resolve(open);
|
|
1425
|
-
};
|
|
1426
|
-
socket.setTimeout(timeoutMs);
|
|
1427
|
-
socket.once("connect", () => finish(true));
|
|
1428
|
-
socket.once("error", () => finish(false));
|
|
1429
|
-
socket.once("timeout", () => finish(false));
|
|
1430
|
-
});
|
|
1431
|
-
}
|
|
1432
|
-
async fullStackDatabaseUrl() {
|
|
1433
|
-
const envPath = path.join(this.workspaceReal, ".env");
|
|
1434
|
-
let configured = process.env.DATABASE_URL || "";
|
|
1435
|
-
try {
|
|
1436
|
-
const source = await readFile(envPath, "utf8");
|
|
1437
|
-
const match = source.match(/^\s*DATABASE_URL\s*=\s*([^\r\n]+)\s*$/m);
|
|
1438
|
-
if (match?.[1])
|
|
1439
|
-
configured = match[1].replace(/^['"]|['"]$/g, "");
|
|
1440
|
-
}
|
|
1441
|
-
catch (error) {
|
|
1442
|
-
if (error.code !== "ENOENT")
|
|
1443
|
-
throw error;
|
|
1444
|
-
}
|
|
1445
|
-
return new URL(configured || "postgresql://postgres:postgres@127.0.0.1:5432/app_db");
|
|
1446
|
-
}
|
|
1447
|
-
async ensureFullStackDatabase() {
|
|
1448
|
-
const databaseUrl = await this.fullStackDatabaseUrl();
|
|
1449
|
-
const hostname = databaseUrl.hostname;
|
|
1450
|
-
const port = Number(databaseUrl.port || 5432);
|
|
1451
|
-
const username = decodeURIComponent(databaseUrl.username || "postgres");
|
|
1452
|
-
const password = decodeURIComponent(databaseUrl.password || "postgres");
|
|
1453
|
-
const database = databaseUrl.pathname.replace(/^\//, "") || "app_db";
|
|
1454
|
-
if (await this.portIsOpen(hostname, port))
|
|
1455
|
-
return undefined;
|
|
1456
|
-
if (!["127.0.0.1", "localhost", "::1"].includes(hostname)) {
|
|
1457
|
-
return `PostgreSQL is not reachable at ${hostname}:${port}`;
|
|
1458
|
-
}
|
|
1459
|
-
let dockerInfo;
|
|
1460
|
-
try {
|
|
1461
|
-
dockerInfo = await this.runProcess("docker", ["info"], 15_000);
|
|
1462
|
-
}
|
|
1463
|
-
catch (error) {
|
|
1464
|
-
return `PostgreSQL is not running and Docker is unavailable: ${errorMessage(error)}`;
|
|
1465
|
-
}
|
|
1466
|
-
if (dockerInfo.exitCode !== 0) {
|
|
1467
|
-
return (dockerInfo.stderr.trim() ||
|
|
1468
|
-
"PostgreSQL is not running and Docker is unavailable");
|
|
1469
|
-
}
|
|
1470
|
-
const containerName = this.postgresContainerName();
|
|
1471
|
-
const inspection = await this.runProcess("docker", ["inspect", "-f", "{{.State.Running}}", containerName], 15_000);
|
|
1472
|
-
let started;
|
|
1473
|
-
if (inspection.exitCode === 0) {
|
|
1474
|
-
started = inspection.stdout.trim() === "true"
|
|
1475
|
-
? { exitCode: 0, stdout: "", stderr: "" }
|
|
1476
|
-
: await this.runProcess("docker", ["start", containerName], 60_000);
|
|
1477
|
-
}
|
|
1478
|
-
else {
|
|
1479
|
-
started = await this.runProcess("docker", [
|
|
1480
|
-
"run",
|
|
1481
|
-
"-d",
|
|
1482
|
-
"--name",
|
|
1483
|
-
containerName,
|
|
1484
|
-
"-e",
|
|
1485
|
-
`POSTGRES_USER=${username}`,
|
|
1486
|
-
"-e",
|
|
1487
|
-
`POSTGRES_PASSWORD=${password}`,
|
|
1488
|
-
"-e",
|
|
1489
|
-
`POSTGRES_DB=${database}`,
|
|
1490
|
-
"-p",
|
|
1491
|
-
`127.0.0.1:${port}:5432`,
|
|
1492
|
-
process.env.CODE_HARNESS_POSTGRES_IMAGE || "postgres:16-alpine",
|
|
1493
|
-
], 2 * 60_000);
|
|
1494
|
-
}
|
|
1495
|
-
if (started.exitCode !== 0) {
|
|
1496
|
-
return started.stderr.trim() || "Failed to start PostgreSQL container";
|
|
1497
|
-
}
|
|
1498
|
-
for (let attempt = 0; attempt < 60; attempt += 1) {
|
|
1499
|
-
if (await this.portIsOpen(hostname, port, 1000)) {
|
|
1500
|
-
const ready = await this.runProcess("docker", [
|
|
1501
|
-
"exec",
|
|
1502
|
-
containerName,
|
|
1503
|
-
"pg_isready",
|
|
1504
|
-
"-U",
|
|
1505
|
-
username,
|
|
1506
|
-
"-d",
|
|
1507
|
-
database,
|
|
1508
|
-
], 5_000);
|
|
1509
|
-
if (ready.exitCode === 0)
|
|
1510
|
-
return undefined;
|
|
1511
|
-
}
|
|
1512
|
-
await delay(500);
|
|
1513
|
-
}
|
|
1514
|
-
return `PostgreSQL did not become ready at ${hostname}:${port}`;
|
|
1515
|
-
}
|
|
1516
|
-
postgresContainerName() {
|
|
1517
|
-
const digest = createHash("sha256")
|
|
1518
|
-
.update(this.workspaceReal)
|
|
1519
|
-
.digest("hex")
|
|
1520
|
-
.slice(0, 12);
|
|
1521
|
-
return `code-harness-postgres-${digest}`;
|
|
1522
|
-
}
|
|
1523
|
-
async preparePostgresCliShim() {
|
|
1524
|
-
const containerName = this.postgresContainerName();
|
|
1525
|
-
let inspection;
|
|
1526
|
-
try {
|
|
1527
|
-
inspection = await this.runProcess("docker", ["inspect", "-f", "{{.State.Running}}", containerName], 15_000);
|
|
1528
|
-
}
|
|
1529
|
-
catch {
|
|
1530
|
-
return;
|
|
1531
|
-
}
|
|
1532
|
-
if (inspection.exitCode !== 0 || inspection.stdout.trim() !== "true")
|
|
1533
|
-
return;
|
|
1534
|
-
const binDirectory = path.join(this.workspaceReal, ".code-harness", "bin");
|
|
1535
|
-
const wrapperPath = path.join(binDirectory, "psql");
|
|
1536
|
-
await mkdir(binDirectory, { recursive: true });
|
|
1537
|
-
await writeFile(wrapperPath, `#!/bin/sh\nexec docker exec -i ${containerName} psql "$@"\n`, "utf8");
|
|
1538
|
-
await chmod(wrapperPath, 0o755);
|
|
1539
|
-
}
|
|
1540
1336
|
buildAndStartFailure(stage, message, logs) {
|
|
1541
1337
|
return {
|
|
1542
1338
|
status: "error",
|
|
@@ -1569,11 +1365,6 @@ export class CodeToolExecutor {
|
|
|
1569
1365
|
}
|
|
1570
1366
|
async buildAndStart() {
|
|
1571
1367
|
const timeoutMs = Number(process.env.CODE_HARNESS_FULLSTACK_BUILD_TIMEOUT_MS || 300_000);
|
|
1572
|
-
const databaseError = await this.ensureFullStackDatabase();
|
|
1573
|
-
if (databaseError) {
|
|
1574
|
-
return this.buildAndStartFailure("database", `Fullstack sandbox setup failed: ${databaseError}`, databaseError);
|
|
1575
|
-
}
|
|
1576
|
-
await this.preparePostgresCliShim();
|
|
1577
1368
|
const nextBinary = path.join(this.workspaceReal, "node_modules", ".bin", "next");
|
|
1578
1369
|
if (!existsSync(nextBinary)) {
|
|
1579
1370
|
const installed = await this.runNpmProcess(["install"], timeoutMs);
|
|
@@ -1672,10 +1463,8 @@ export class CodeToolExecutor {
|
|
|
1672
1463
|
return new Promise((resolve, reject) => {
|
|
1673
1464
|
const env = {
|
|
1674
1465
|
...process.env,
|
|
1675
|
-
HOME: this.workspace,
|
|
1676
1466
|
ARENA_WORKSPACE: this.workspace,
|
|
1677
1467
|
CODE_HARNESS_WORKSPACE: this.workspace,
|
|
1678
|
-
PATH: `${path.join(this.workspaceReal, ".code-harness", "bin")}${path.delimiter}${process.env.PATH || ""}`,
|
|
1679
1468
|
// 生产 sandbox 是 C collation(参考 find|sort 为逐字节序),宿主
|
|
1680
1469
|
// 常见 en_US.UTF-8 会翻转大小写次序;统一成 C.UTF-8 对齐生产。
|
|
1681
1470
|
LANG: "C.UTF-8",
|
|
@@ -1796,55 +1585,17 @@ export class CodeToolExecutor {
|
|
|
1796
1585
|
async webSearch(query, depth) {
|
|
1797
1586
|
const model = process.env.CODE_HARNESS_WEB_SEARCH_MODEL ||
|
|
1798
1587
|
process.env.ARENA_WEB_SEARCH_MODEL ||
|
|
1799
|
-
"
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
headers: { "content-type": "application/json" },
|
|
1804
|
-
body: JSON.stringify({
|
|
1805
|
-
model,
|
|
1806
|
-
input: {
|
|
1807
|
-
messages: [{ role: "user", content: query }],
|
|
1808
|
-
},
|
|
1809
|
-
parameters: {
|
|
1810
|
-
enable_search: true,
|
|
1811
|
-
result_format: "message",
|
|
1812
|
-
search_options: {
|
|
1813
|
-
forced_search: true,
|
|
1814
|
-
enable_source: true,
|
|
1815
|
-
enable_citation: true,
|
|
1816
|
-
citation_format: "[<number>]",
|
|
1817
|
-
search_strategy: searchStrategy,
|
|
1818
|
-
},
|
|
1819
|
-
},
|
|
1820
|
-
}),
|
|
1588
|
+
"brave.llm-context";
|
|
1589
|
+
return executeBraveWebSearch(query, depth, {
|
|
1590
|
+
model,
|
|
1591
|
+
profile: "code-arena",
|
|
1821
1592
|
signal: this.signal,
|
|
1822
|
-
}
|
|
1823
|
-
const output = record(payload.output);
|
|
1824
|
-
const searchInfo = record(output.search_info);
|
|
1825
|
-
const searchResults = Array.isArray(searchInfo.search_results)
|
|
1826
|
-
? searchInfo.search_results
|
|
1827
|
-
: [];
|
|
1828
|
-
const limit = { "1": 3, "2": 5, "3": 8 }[depth] || 5;
|
|
1829
|
-
// 参考轨迹条目:{id, url, title, description[, pageAge]};
|
|
1830
|
-
// DashScope 无 pageAge,按可选键省略。
|
|
1831
|
-
return {
|
|
1832
|
-
status: "success",
|
|
1833
|
-
results: searchResults.slice(0, limit).map((value, index) => {
|
|
1834
|
-
const item = record(value);
|
|
1835
|
-
return {
|
|
1836
|
-
id: Number(item.index) || index + 1,
|
|
1837
|
-
url: stringValue(item.url),
|
|
1838
|
-
title: stringValue(item.title),
|
|
1839
|
-
description: stringValue(item.content) ||
|
|
1840
|
-
stringValue(item.snippet) ||
|
|
1841
|
-
stringValue(item.summary),
|
|
1842
|
-
};
|
|
1843
|
-
}),
|
|
1844
|
-
};
|
|
1593
|
+
});
|
|
1845
1594
|
}
|
|
1846
1595
|
async webFetch(url, format) {
|
|
1847
|
-
const result =
|
|
1596
|
+
const result = routifyWebFetchEnabled()
|
|
1597
|
+
? await executeRoutifyWebFetch(url, format, { signal: this.signal })
|
|
1598
|
+
: await this.pageReader.readFormatted(url, format);
|
|
1848
1599
|
return {
|
|
1849
1600
|
status: "success",
|
|
1850
1601
|
title: String(result.title || "No Title Found"),
|
|
@@ -1986,15 +1737,15 @@ export class CodeToolExecutor {
|
|
|
1986
1737
|
}
|
|
1987
1738
|
this.generatedImageCount += 1;
|
|
1988
1739
|
const target = await this.confinedPath(rawPath);
|
|
1989
|
-
|
|
1740
|
+
const extension = path.extname(target).toLowerCase();
|
|
1741
|
+
if (![".jpg", ".jpeg", ".png"].includes(extension)) {
|
|
1990
1742
|
throw new Error("file_path must end in .jpg, .jpeg, or .png");
|
|
1991
1743
|
}
|
|
1992
1744
|
mkdirSync(path.dirname(target), { recursive: true });
|
|
1993
1745
|
const model = process.env.CODE_HARNESS_IMAGE_MODEL ||
|
|
1994
1746
|
process.env.ARENA_IMAGE_MODEL ||
|
|
1995
|
-
"
|
|
1996
|
-
const
|
|
1997
|
-
const content = [];
|
|
1747
|
+
"gemini-2.5-flash-image";
|
|
1748
|
+
const references = [];
|
|
1998
1749
|
for (const imagePath of images || []) {
|
|
1999
1750
|
const source = await this.confinedPath(imagePath);
|
|
2000
1751
|
const extension = path.extname(source).toLowerCase();
|
|
@@ -2006,97 +1757,22 @@ export class CodeToolExecutor {
|
|
|
2006
1757
|
if (bytes.length > 20 * 1024 * 1024) {
|
|
2007
1758
|
throw new Error(`reference image exceeds 20 MB: ${imagePath}`);
|
|
2008
1759
|
}
|
|
2009
|
-
|
|
2010
|
-
|
|
1760
|
+
references.push({
|
|
1761
|
+
bytes,
|
|
1762
|
+
fileName: path.basename(source),
|
|
1763
|
+
mimeType,
|
|
2011
1764
|
});
|
|
2012
1765
|
}
|
|
2013
|
-
|
|
2014
|
-
|
|
1766
|
+
const generated = await generateImageWithModelRouter({
|
|
1767
|
+
images: references,
|
|
1768
|
+
model,
|
|
1769
|
+
prompt,
|
|
1770
|
+
signal: this.signal,
|
|
2015
1771
|
size: process.env.CODE_HARNESS_IMAGE_SIZE ||
|
|
2016
1772
|
process.env.ARENA_IMAGE_SIZE ||
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
if (isQwenImage2) {
|
|
2021
|
-
parameters.prompt_extend = true;
|
|
2022
|
-
}
|
|
2023
|
-
else {
|
|
2024
|
-
parameters.n = 1;
|
|
2025
|
-
if (!images?.length)
|
|
2026
|
-
parameters.thinking_mode = true;
|
|
2027
|
-
}
|
|
2028
|
-
const headers = {
|
|
2029
|
-
"content-type": "application/json",
|
|
2030
|
-
};
|
|
2031
|
-
if (!isQwenImage2)
|
|
2032
|
-
headers["x-dashscope-async"] = "enable";
|
|
2033
|
-
const submitted = await dashScopeJson("api", isQwenImage2
|
|
2034
|
-
? "/services/aigc/multimodal-generation/generation"
|
|
2035
|
-
: "/services/aigc/image-generation/generation", {
|
|
2036
|
-
method: "POST",
|
|
2037
|
-
headers,
|
|
2038
|
-
body: JSON.stringify({
|
|
2039
|
-
model,
|
|
2040
|
-
input: {
|
|
2041
|
-
messages: [{ role: "user", content }],
|
|
2042
|
-
},
|
|
2043
|
-
parameters,
|
|
2044
|
-
}),
|
|
2045
|
-
signal: this.signal,
|
|
2046
|
-
}, "generate_image");
|
|
2047
|
-
let completed = isQwenImage2
|
|
2048
|
-
? submitted
|
|
2049
|
-
: undefined;
|
|
2050
|
-
if (!isQwenImage2) {
|
|
2051
|
-
const taskId = stringValue(record(submitted.output).task_id);
|
|
2052
|
-
if (!taskId) {
|
|
2053
|
-
throw new Error(`generate_image returned no task_id: ${responseMessage(submitted)}`);
|
|
2054
|
-
}
|
|
2055
|
-
const timeoutMs = Number(process.env.DASHSCOPE_TASK_TIMEOUT_MS || 5 * 60 * 1000);
|
|
2056
|
-
const pollIntervalMs = Number(process.env.DASHSCOPE_TASK_POLL_INTERVAL_MS || 1000);
|
|
2057
|
-
const deadline = Date.now() + timeoutMs;
|
|
2058
|
-
while (Date.now() <= deadline) {
|
|
2059
|
-
const task = await dashScopeJson("api", `/tasks/${encodeURIComponent(taskId)}`, { method: "GET", signal: this.signal }, "generate_image status");
|
|
2060
|
-
const output = record(task.output);
|
|
2061
|
-
const taskStatus = stringValue(output.task_status).toUpperCase();
|
|
2062
|
-
if (taskStatus === "SUCCEEDED") {
|
|
2063
|
-
completed = task;
|
|
2064
|
-
break;
|
|
2065
|
-
}
|
|
2066
|
-
if (["FAILED", "CANCELED", "UNKNOWN"].includes(taskStatus)) {
|
|
2067
|
-
throw new Error(`generate_image task ${taskStatus}: ${responseMessage(task)}`);
|
|
2068
|
-
}
|
|
2069
|
-
if (Date.now() + pollIntervalMs > deadline)
|
|
2070
|
-
break;
|
|
2071
|
-
await delay(pollIntervalMs, undefined, { signal: this.signal });
|
|
2072
|
-
}
|
|
2073
|
-
if (!completed) {
|
|
2074
|
-
throw new Error(`generate_image timed out waiting for task ${taskId}`);
|
|
2075
|
-
}
|
|
2076
|
-
}
|
|
2077
|
-
if (!completed) {
|
|
2078
|
-
throw new Error("generate_image returned no response");
|
|
2079
|
-
}
|
|
2080
|
-
const output = record(completed.output);
|
|
2081
|
-
const choices = Array.isArray(output.choices) ? output.choices : [];
|
|
2082
|
-
const message = record(record(choices[0]).message);
|
|
2083
|
-
const generatedContent = Array.isArray(message.content)
|
|
2084
|
-
? message.content
|
|
2085
|
-
: [];
|
|
2086
|
-
const generated = generatedContent
|
|
2087
|
-
.map(record)
|
|
2088
|
-
.find((item) => typeof item.image === "string");
|
|
2089
|
-
const legacyResults = Array.isArray(output.results) ? output.results : [];
|
|
2090
|
-
const imageUrl = stringValue(generated?.image) ||
|
|
2091
|
-
stringValue(record(legacyResults[0]).url);
|
|
2092
|
-
if (!imageUrl) {
|
|
2093
|
-
throw new Error("generate_image response did not include an image URL");
|
|
2094
|
-
}
|
|
2095
|
-
const imageResponse = await fetch(imageUrl, { signal: this.signal });
|
|
2096
|
-
if (!imageResponse.ok) {
|
|
2097
|
-
throw new Error(`image download failed (${imageResponse.status})`);
|
|
2098
|
-
}
|
|
2099
|
-
await writeFile(target, await responseBytes(imageResponse, 25 * 1024 * 1024));
|
|
1773
|
+
"1024*1024",
|
|
1774
|
+
});
|
|
1775
|
+
await writeFile(target, await generatedImageBytesForExtension(generated.bytes, generated.mimeType, extension));
|
|
2100
1776
|
return {
|
|
2101
1777
|
status: "success",
|
|
2102
1778
|
message: `Image saved to ${this.relativePath(target)}`,
|