seedream-adapter.ts 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. /**
  2. * Seedream (ByteDance / Doubao / Ark) Image Generation Adapter
  3. *
  4. * Uses OpenAI-compatible synchronous API format.
  5. * Endpoint: https://ark.cn-beijing.volces.com/api/v3/images/generations
  6. *
  7. * Supported models:
  8. * - doubao-seedream-5-0-260128 (latest / Lite, text2img + img2img + multi-ref + group)
  9. * - doubao-seedream-4-5-251128
  10. * - doubao-seedream-4-0-250828
  11. * - doubao-seedream-3-0-t2i-250415
  12. *
  13. * API docs: https://www.volcengine.com/docs/6791/1399028
  14. */
  15. import type {
  16. ImageGenerationConfig,
  17. ImageGenerationOptions,
  18. ImageGenerationResult,
  19. } from '../types';
  20. const DEFAULT_MODEL = 'doubao-seedream-5-0-260128';
  21. const DEFAULT_BASE_URL = 'https://ark.cn-beijing.volces.com';
  22. /**
  23. * Map our aspect ratio + size to Seedream size format "WxH".
  24. * Seedream requires minimum 3,686,400 pixels total.
  25. * Common sizes: 2048x2048 (2K), 2560x1440 (16:9), 1920x1920.
  26. */
  27. function resolveSeedreamSize(options: ImageGenerationOptions): string {
  28. if (options.width && options.height) {
  29. // Ensure minimum pixel count (3,686,400)
  30. const pixels = options.width * options.height;
  31. if (pixels < 3_686_400) {
  32. // Scale up proportionally
  33. const scale = Math.ceil(Math.sqrt(3_686_400 / pixels));
  34. return `${options.width * scale}x${options.height * scale}`;
  35. }
  36. return `${options.width}x${options.height}`;
  37. }
  38. // Default to 2K for quality
  39. return '2K';
  40. }
  41. /**
  42. * Lightweight connectivity test — validates API key by making a minimal
  43. * request that triggers auth check. 401/403 means key invalid.
  44. */
  45. export async function testSeedreamConnectivity(
  46. config: ImageGenerationConfig,
  47. ): Promise<{ success: boolean; message: string }> {
  48. const baseUrl = config.baseUrl || DEFAULT_BASE_URL;
  49. try {
  50. // Send a request with empty prompt — auth failure (401/403) means bad key,
  51. // any other error (400) means key is valid but request is intentionally bad
  52. const response = await fetch(`${baseUrl}/api/v3/images/generations`, {
  53. method: 'POST',
  54. headers: {
  55. 'Content-Type': 'application/json',
  56. Authorization: `Bearer ${config.apiKey}`,
  57. },
  58. body: JSON.stringify({
  59. model: config.model || DEFAULT_MODEL,
  60. prompt: '',
  61. size: '1x1',
  62. }),
  63. });
  64. if (response.status === 401 || response.status === 403) {
  65. const text = await response.text();
  66. return {
  67. success: false,
  68. message: `Seedream auth failed (${response.status}): ${text}`,
  69. };
  70. }
  71. return { success: true, message: 'Connected to Seedream' };
  72. } catch (err) {
  73. return { success: false, message: `Seedream connectivity error: ${err}` };
  74. }
  75. }
  76. export async function generateWithSeedream(
  77. config: ImageGenerationConfig,
  78. options: ImageGenerationOptions,
  79. ): Promise<ImageGenerationResult> {
  80. const baseUrl = config.baseUrl || DEFAULT_BASE_URL;
  81. const response = await fetch(`${baseUrl}/api/v3/images/generations`, {
  82. method: 'POST',
  83. headers: {
  84. 'Content-Type': 'application/json',
  85. Authorization: `Bearer ${config.apiKey}`,
  86. },
  87. body: JSON.stringify({
  88. model: config.model || DEFAULT_MODEL,
  89. prompt: options.prompt,
  90. size: resolveSeedreamSize(options),
  91. watermark: false,
  92. }),
  93. });
  94. if (!response.ok) {
  95. const text = await response.text();
  96. throw new Error(`Seedream generation failed (${response.status}): ${text}`);
  97. }
  98. const data = await response.json();
  99. // OpenAI-compatible response format: { data: [{ url, b64_json, ... }] }
  100. const imageData = data.data?.[0];
  101. if (!imageData) {
  102. throw new Error('Seedream returned empty response');
  103. }
  104. return {
  105. url: imageData.url,
  106. base64: imageData.b64_json,
  107. width: options.width || 1024,
  108. height: options.height || 1024,
  109. };
  110. }