grok-image-adapter.ts 2.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. /**
  2. * Grok (xAI) Image Generation Adapter
  3. *
  4. * Uses OpenAI-compatible synchronous API format.
  5. * Endpoint: https://api.x.ai/v1/images/generations
  6. *
  7. * Supported models:
  8. * - grok-imagine-image (standard, $0.02/image)
  9. * - grok-imagine-image-pro (pro quality, $0.07/image)
  10. *
  11. * Authentication: Bearer token via Authorization header
  12. *
  13. * API docs: https://docs.x.ai/developers/rest-api-reference/inference/images
  14. */
  15. import type {
  16. ImageGenerationConfig,
  17. ImageGenerationOptions,
  18. ImageGenerationResult,
  19. } from '../types';
  20. const DEFAULT_MODEL = 'grok-imagine-image';
  21. const DEFAULT_BASE_URL = 'https://api.x.ai/v1';
  22. /**
  23. * Lightweight connectivity test — validates API key by making a minimal
  24. * request that triggers auth check. 401/403 means key invalid.
  25. */
  26. export async function testGrokImageConnectivity(
  27. config: ImageGenerationConfig,
  28. ): Promise<{ success: boolean; message: string }> {
  29. const baseUrl = config.baseUrl || DEFAULT_BASE_URL;
  30. try {
  31. const response = await fetch(`${baseUrl}/images/generations`, {
  32. method: 'POST',
  33. headers: {
  34. 'Content-Type': 'application/json',
  35. Authorization: `Bearer ${config.apiKey}`,
  36. },
  37. body: JSON.stringify({
  38. model: config.model || DEFAULT_MODEL,
  39. prompt: '',
  40. n: 1,
  41. }),
  42. });
  43. if (response.status === 401 || response.status === 403) {
  44. const text = await response.text();
  45. return {
  46. success: false,
  47. message: `Grok Image auth failed (${response.status}): ${text}`,
  48. };
  49. }
  50. return { success: true, message: 'Connected to Grok Image' };
  51. } catch (err) {
  52. return { success: false, message: `Grok Image connectivity error: ${err}` };
  53. }
  54. }
  55. export async function generateWithGrokImage(
  56. config: ImageGenerationConfig,
  57. options: ImageGenerationOptions,
  58. ): Promise<ImageGenerationResult> {
  59. const baseUrl = config.baseUrl || DEFAULT_BASE_URL;
  60. const response = await fetch(`${baseUrl}/images/generations`, {
  61. method: 'POST',
  62. headers: {
  63. 'Content-Type': 'application/json',
  64. Authorization: `Bearer ${config.apiKey}`,
  65. },
  66. body: JSON.stringify({
  67. model: config.model || DEFAULT_MODEL,
  68. prompt: options.prompt,
  69. n: 1,
  70. response_format: 'url',
  71. }),
  72. });
  73. if (!response.ok) {
  74. const text = await response.text();
  75. throw new Error(`Grok image generation failed (${response.status}): ${text}`);
  76. }
  77. const data = await response.json();
  78. // OpenAI-compatible response format: { data: [{ url, revised_prompt }] }
  79. const imageData = data.data?.[0];
  80. if (!imageData) {
  81. throw new Error('Grok returned empty image response');
  82. }
  83. return {
  84. url: imageData.url,
  85. base64: imageData.b64_json,
  86. width: options.width || 1024,
  87. height: options.height || 1024,
  88. };
  89. }