@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.
Files changed (87) hide show
  1. package/.env.example +54 -19
  2. package/README.md +218 -45
  3. package/dist/agent.d.ts +4 -2
  4. package/dist/agent.js +167 -21
  5. package/dist/agent.js.map +1 -1
  6. package/dist/anthropic.d.ts +1 -0
  7. package/dist/anthropic.js +90 -23
  8. package/dist/anthropic.js.map +1 -1
  9. package/dist/attachments.d.ts +1 -1
  10. package/dist/attachments.js +16 -4
  11. package/dist/attachments.js.map +1 -1
  12. package/dist/brave-search.d.ts +10 -0
  13. package/dist/brave-search.js +238 -0
  14. package/dist/brave-search.js.map +1 -0
  15. package/dist/canonical-data.js +2 -2
  16. package/dist/canonical-data.js.map +1 -1
  17. package/dist/cli.js +64 -18
  18. package/dist/cli.js.map +1 -1
  19. package/dist/code-tools.d.ts +0 -5
  20. package/dist/code-tools.js +29 -353
  21. package/dist/code-tools.js.map +1 -1
  22. package/dist/compaction.js +8 -2
  23. package/dist/compaction.js.map +1 -1
  24. package/dist/config.d.ts +30 -0
  25. package/dist/config.js +57 -3
  26. package/dist/config.js.map +1 -1
  27. package/dist/fetch-page.d.ts +1 -0
  28. package/dist/fetch-page.js +1 -1
  29. package/dist/fetch-page.js.map +1 -1
  30. package/dist/history.d.ts +3 -2
  31. package/dist/history.js +7 -2
  32. package/dist/history.js.map +1 -1
  33. package/dist/image-input-fallback.d.ts +9 -0
  34. package/dist/image-input-fallback.js +47 -0
  35. package/dist/image-input-fallback.js.map +1 -0
  36. package/dist/image.d.ts +16 -0
  37. package/dist/image.js +380 -0
  38. package/dist/image.js.map +1 -0
  39. package/dist/mock.d.ts +1 -0
  40. package/dist/mock.js +38 -0
  41. package/dist/mock.js.map +1 -1
  42. package/dist/model-capabilities.js +12 -2
  43. package/dist/model-capabilities.js.map +1 -1
  44. package/dist/model-diagnostics.js +16 -5
  45. package/dist/model-diagnostics.js.map +1 -1
  46. package/dist/model-router-media.d.ts +26 -0
  47. package/dist/model-router-media.js +274 -0
  48. package/dist/model-router-media.js.map +1 -0
  49. package/dist/openai-responses.d.ts +28 -0
  50. package/dist/openai-responses.js +473 -0
  51. package/dist/openai-responses.js.map +1 -0
  52. package/dist/process-manager.d.ts +38 -0
  53. package/dist/process-manager.js +486 -0
  54. package/dist/process-manager.js.map +1 -0
  55. package/dist/prompts.js +3 -1
  56. package/dist/prompts.js.map +1 -1
  57. package/dist/provider.d.ts +7 -0
  58. package/dist/provider.js +291 -27
  59. package/dist/provider.js.map +1 -1
  60. package/dist/routify-web-fetch.d.ts +17 -0
  61. package/dist/routify-web-fetch.js +188 -0
  62. package/dist/routify-web-fetch.js.map +1 -0
  63. package/dist/sdk.d.ts +9 -2
  64. package/dist/sdk.js +5 -2
  65. package/dist/sdk.js.map +1 -1
  66. package/dist/server.d.ts +3 -0
  67. package/dist/server.js +112 -21
  68. package/dist/server.js.map +1 -1
  69. package/dist/spec.js +15 -1
  70. package/dist/spec.js.map +1 -1
  71. package/dist/tools.d.ts +2 -1
  72. package/dist/tools.js +250 -340
  73. package/dist/tools.js.map +1 -1
  74. package/dist/tui.d.ts +3 -0
  75. package/dist/tui.js +18 -11
  76. package/dist/tui.js.map +1 -1
  77. package/dist/types.d.ts +17 -6
  78. package/dist/voice-registry.d.ts +30 -0
  79. package/dist/voice-registry.js +82 -0
  80. package/dist/voice-registry.js.map +1 -0
  81. package/dist/web/app.js +67 -6
  82. package/dist/web/app.js.map +1 -1
  83. package/docs/BINARY_DISTRIBUTION.md +25 -18
  84. package/docs/MODES_AND_RELEASES.md +8 -4
  85. package/docs/SDK.md +28 -4
  86. package/package.json +21 -3
  87. package/web/styles.css +28 -7
@@ -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 { chmod, mkdir, readFile, readdir, realpath, rm, stat, writeFile, } from "node:fs/promises";
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
- "qwen-plus";
1800
- const searchStrategy = depth === "1" ? "turbo" : "max";
1801
- const payload = await dashScopeJson("api", "/services/aigc/text-generation/generation", {
1802
- method: "POST",
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
- }, "web_search");
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 = await this.pageReader.readFormatted(url, format);
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
- if (![".jpg", ".jpeg", ".png"].includes(path.extname(target).toLowerCase())) {
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
- "qwen-image-2.0-pro";
1996
- const isQwenImage2 = model.startsWith("qwen-image-2.0");
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
- content.push({
2010
- image: `data:${mimeType};base64,${bytes.toString("base64")}`,
1760
+ references.push({
1761
+ bytes,
1762
+ fileName: path.basename(source),
1763
+ mimeType,
2011
1764
  });
2012
1765
  }
2013
- content.push({ text: prompt });
2014
- const parameters = {
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
- (isQwenImage2 ? "2048*2048" : "2K"),
2018
- watermark: false,
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)}`,