hazo_llm_api 1.0.3 → 1.0.4

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 (151) hide show
  1. package/README.md +4 -4
  2. package/package.json +1 -1
  3. package/techdoc.md +2 -2
  4. package/dist/components/hazo_llm_prompt_config/hazo_llm_prompt_config.d.ts +0 -16
  5. package/dist/components/hazo_llm_prompt_config/hazo_llm_prompt_config.d.ts.map +0 -1
  6. package/dist/components/hazo_llm_prompt_config/hazo_llm_prompt_config.js +0 -258
  7. package/dist/components/hazo_llm_prompt_config/hazo_llm_prompt_config.js.map +0 -1
  8. package/dist/components/hazo_llm_prompt_config/index.d.ts +0 -8
  9. package/dist/components/hazo_llm_prompt_config/index.d.ts.map +0 -1
  10. package/dist/components/hazo_llm_prompt_config/index.js +0 -7
  11. package/dist/components/hazo_llm_prompt_config/index.js.map +0 -1
  12. package/dist/components/hazo_llm_prompt_config/types.d.ts +0 -74
  13. package/dist/components/hazo_llm_prompt_config/types.d.ts.map +0 -1
  14. package/dist/components/hazo_llm_prompt_config/types.js +0 -8
  15. package/dist/components/hazo_llm_prompt_config/types.js.map +0 -1
  16. package/dist/components/index.d.ts +0 -7
  17. package/dist/components/index.d.ts.map +0 -1
  18. package/dist/components/index.js +0 -7
  19. package/dist/components/index.js.map +0 -1
  20. package/dist/components/layout/index.d.ts +0 -7
  21. package/dist/components/layout/index.d.ts.map +0 -1
  22. package/dist/components/layout/index.js +0 -7
  23. package/dist/components/layout/index.js.map +0 -1
  24. package/dist/components/layout/layout.d.ts +0 -21
  25. package/dist/components/layout/layout.d.ts.map +0 -1
  26. package/dist/components/layout/layout.js +0 -18
  27. package/dist/components/layout/layout.js.map +0 -1
  28. package/dist/index.d.ts +0 -15
  29. package/dist/index.d.ts.map +0 -1
  30. package/dist/index.js +0 -21
  31. package/dist/index.js.map +0 -1
  32. package/dist/lib/config/config_parser.d.ts +0 -131
  33. package/dist/lib/config/config_parser.d.ts.map +0 -1
  34. package/dist/lib/config/config_parser.js +0 -297
  35. package/dist/lib/config/config_parser.js.map +0 -1
  36. package/dist/lib/config/index.d.ts +0 -8
  37. package/dist/lib/config/index.d.ts.map +0 -1
  38. package/dist/lib/config/index.js +0 -22
  39. package/dist/lib/config/index.js.map +0 -1
  40. package/dist/lib/config/provider_loader.d.ts +0 -113
  41. package/dist/lib/config/provider_loader.d.ts.map +0 -1
  42. package/dist/lib/config/provider_loader.js +0 -169
  43. package/dist/lib/config/provider_loader.js.map +0 -1
  44. package/dist/lib/database/index.d.ts +0 -8
  45. package/dist/lib/database/index.d.ts.map +0 -1
  46. package/dist/lib/database/index.js +0 -10
  47. package/dist/lib/database/index.js.map +0 -1
  48. package/dist/lib/database/init_database.d.ts +0 -118
  49. package/dist/lib/database/init_database.d.ts.map +0 -1
  50. package/dist/lib/database/init_database.js +0 -524
  51. package/dist/lib/database/init_database.js.map +0 -1
  52. package/dist/lib/database/utils.d.ts +0 -50
  53. package/dist/lib/database/utils.d.ts.map +0 -1
  54. package/dist/lib/database/utils.js +0 -78
  55. package/dist/lib/database/utils.js.map +0 -1
  56. package/dist/lib/index.d.ts +0 -14
  57. package/dist/lib/index.d.ts.map +0 -1
  58. package/dist/lib/index.js +0 -17
  59. package/dist/lib/index.js.map +0 -1
  60. package/dist/lib/llm_api/hazo_llm_image_image.d.ts +0 -26
  61. package/dist/lib/llm_api/hazo_llm_image_image.d.ts.map +0 -1
  62. package/dist/lib/llm_api/hazo_llm_image_image.js +0 -94
  63. package/dist/lib/llm_api/hazo_llm_image_image.js.map +0 -1
  64. package/dist/lib/llm_api/hazo_llm_image_image_text.d.ts +0 -26
  65. package/dist/lib/llm_api/hazo_llm_image_image_text.d.ts.map +0 -1
  66. package/dist/lib/llm_api/hazo_llm_image_image_text.js +0 -222
  67. package/dist/lib/llm_api/hazo_llm_image_image_text.js.map +0 -1
  68. package/dist/lib/llm_api/hazo_llm_image_text.d.ts +0 -20
  69. package/dist/lib/llm_api/hazo_llm_image_text.d.ts.map +0 -1
  70. package/dist/lib/llm_api/hazo_llm_image_text.js +0 -78
  71. package/dist/lib/llm_api/hazo_llm_image_text.js.map +0 -1
  72. package/dist/lib/llm_api/hazo_llm_text_image.d.ts +0 -20
  73. package/dist/lib/llm_api/hazo_llm_text_image.d.ts.map +0 -1
  74. package/dist/lib/llm_api/hazo_llm_text_image.js +0 -69
  75. package/dist/lib/llm_api/hazo_llm_text_image.js.map +0 -1
  76. package/dist/lib/llm_api/hazo_llm_text_image_text.d.ts +0 -26
  77. package/dist/lib/llm_api/hazo_llm_text_image_text.d.ts.map +0 -1
  78. package/dist/lib/llm_api/hazo_llm_text_image_text.js +0 -154
  79. package/dist/lib/llm_api/hazo_llm_text_image_text.js.map +0 -1
  80. package/dist/lib/llm_api/hazo_llm_text_text.d.ts +0 -20
  81. package/dist/lib/llm_api/hazo_llm_text_text.d.ts.map +0 -1
  82. package/dist/lib/llm_api/hazo_llm_text_text.js +0 -91
  83. package/dist/lib/llm_api/hazo_llm_text_text.js.map +0 -1
  84. package/dist/lib/llm_api/index.d.ts +0 -191
  85. package/dist/lib/llm_api/index.d.ts.map +0 -1
  86. package/dist/lib/llm_api/index.js +0 -1168
  87. package/dist/lib/llm_api/index.js.map +0 -1
  88. package/dist/lib/llm_api/provider_helper.d.ts +0 -163
  89. package/dist/lib/llm_api/provider_helper.d.ts.map +0 -1
  90. package/dist/lib/llm_api/provider_helper.js +0 -346
  91. package/dist/lib/llm_api/provider_helper.js.map +0 -1
  92. package/dist/lib/llm_api/types.d.ts +0 -525
  93. package/dist/lib/llm_api/types.d.ts.map +0 -1
  94. package/dist/lib/llm_api/types.js +0 -49
  95. package/dist/lib/llm_api/types.js.map +0 -1
  96. package/dist/lib/prompts/get_prompt.d.ts +0 -50
  97. package/dist/lib/prompts/get_prompt.d.ts.map +0 -1
  98. package/dist/lib/prompts/get_prompt.js +0 -232
  99. package/dist/lib/prompts/get_prompt.js.map +0 -1
  100. package/dist/lib/prompts/index.d.ts +0 -9
  101. package/dist/lib/prompts/index.d.ts.map +0 -1
  102. package/dist/lib/prompts/index.js +0 -9
  103. package/dist/lib/prompts/index.js.map +0 -1
  104. package/dist/lib/prompts/prompt_cache.d.ts +0 -151
  105. package/dist/lib/prompts/prompt_cache.d.ts.map +0 -1
  106. package/dist/lib/prompts/prompt_cache.js +0 -276
  107. package/dist/lib/prompts/prompt_cache.js.map +0 -1
  108. package/dist/lib/prompts/substitute_variables.d.ts +0 -38
  109. package/dist/lib/prompts/substitute_variables.d.ts.map +0 -1
  110. package/dist/lib/prompts/substitute_variables.js +0 -175
  111. package/dist/lib/prompts/substitute_variables.js.map +0 -1
  112. package/dist/lib/providers/gemini/gemini_client.d.ts +0 -25
  113. package/dist/lib/providers/gemini/gemini_client.d.ts.map +0 -1
  114. package/dist/lib/providers/gemini/gemini_client.js +0 -235
  115. package/dist/lib/providers/gemini/gemini_client.js.map +0 -1
  116. package/dist/lib/providers/gemini/gemini_provider.d.ts +0 -111
  117. package/dist/lib/providers/gemini/gemini_provider.d.ts.map +0 -1
  118. package/dist/lib/providers/gemini/gemini_provider.js +0 -431
  119. package/dist/lib/providers/gemini/gemini_provider.js.map +0 -1
  120. package/dist/lib/providers/gemini/index.d.ts +0 -8
  121. package/dist/lib/providers/gemini/index.d.ts.map +0 -1
  122. package/dist/lib/providers/gemini/index.js +0 -8
  123. package/dist/lib/providers/gemini/index.js.map +0 -1
  124. package/dist/lib/providers/index.d.ts +0 -8
  125. package/dist/lib/providers/index.d.ts.map +0 -1
  126. package/dist/lib/providers/index.js +0 -8
  127. package/dist/lib/providers/index.js.map +0 -1
  128. package/dist/lib/providers/qwen/index.d.ts +0 -8
  129. package/dist/lib/providers/qwen/index.d.ts.map +0 -1
  130. package/dist/lib/providers/qwen/index.js +0 -8
  131. package/dist/lib/providers/qwen/index.js.map +0 -1
  132. package/dist/lib/providers/qwen/qwen_client.d.ts +0 -154
  133. package/dist/lib/providers/qwen/qwen_client.d.ts.map +0 -1
  134. package/dist/lib/providers/qwen/qwen_client.js +0 -1002
  135. package/dist/lib/providers/qwen/qwen_client.js.map +0 -1
  136. package/dist/lib/providers/qwen/qwen_provider.d.ts +0 -139
  137. package/dist/lib/providers/qwen/qwen_provider.d.ts.map +0 -1
  138. package/dist/lib/providers/qwen/qwen_provider.js +0 -304
  139. package/dist/lib/providers/qwen/qwen_provider.js.map +0 -1
  140. package/dist/lib/providers/registry.d.ts +0 -66
  141. package/dist/lib/providers/registry.d.ts.map +0 -1
  142. package/dist/lib/providers/registry.js +0 -158
  143. package/dist/lib/providers/registry.js.map +0 -1
  144. package/dist/lib/providers/types.d.ts +0 -145
  145. package/dist/lib/providers/types.d.ts.map +0 -1
  146. package/dist/lib/providers/types.js +0 -37
  147. package/dist/lib/providers/types.js.map +0 -1
  148. package/dist/server.d.ts +0 -26
  149. package/dist/server.d.ts.map +0 -1
  150. package/dist/server.js +0 -49
  151. package/dist/server.js.map +0 -1
@@ -1,1002 +0,0 @@
1
- /**
2
- * Qwen API Client Module
3
- *
4
- * Handles communication with Alibaba Cloud DashScope Qwen API.
5
- * Uses OpenAI-compatible chat completions endpoint.
6
- */
7
- // =============================================================================
8
- // Qwen API Client
9
- // =============================================================================
10
- /**
11
- * Call the Qwen API with messages and optional image data
12
- * @param api_url - The Qwen API endpoint URL
13
- * @param api_key - The API key for authentication
14
- * @param model - The model name to use
15
- * @param messages - Array of messages (system + user)
16
- * @param logger - Logger instance
17
- * @param generation_config - Optional generation configuration parameters
18
- * @returns LLM response with generated text or error
19
- */
20
- export async function call_qwen_api(api_url, api_key, model, messages, logger, generation_config) {
21
- const file_name = 'qwen_client.ts';
22
- try {
23
- // Build the request body
24
- const request_body = {
25
- model,
26
- messages,
27
- };
28
- // Add generation config parameters if provided
29
- if (generation_config) {
30
- if (generation_config.temperature !== undefined) {
31
- request_body.temperature = generation_config.temperature;
32
- }
33
- if (generation_config.max_tokens !== undefined) {
34
- request_body.max_tokens = generation_config.max_tokens;
35
- }
36
- if (generation_config.top_p !== undefined) {
37
- request_body.top_p = generation_config.top_p;
38
- }
39
- if (generation_config.top_k !== undefined) {
40
- request_body.top_k = generation_config.top_k;
41
- }
42
- if (generation_config.n !== undefined) {
43
- request_body.n = generation_config.n;
44
- }
45
- if (generation_config.stop !== undefined && generation_config.stop.length > 0) {
46
- request_body.stop = generation_config.stop;
47
- }
48
- if (generation_config.presence_penalty !== undefined) {
49
- request_body.presence_penalty = generation_config.presence_penalty;
50
- }
51
- if (generation_config.frequency_penalty !== undefined) {
52
- request_body.frequency_penalty = generation_config.frequency_penalty;
53
- }
54
- }
55
- // Log the API call
56
- logger.debug('Calling Qwen API', {
57
- file: file_name,
58
- line: 125,
59
- data: {
60
- api_url,
61
- model,
62
- message_count: messages.length,
63
- has_generation_config: !!generation_config,
64
- generation_config: generation_config || 'none (using defaults)',
65
- },
66
- });
67
- // Make the API request
68
- const response = await fetch(api_url, {
69
- method: 'POST',
70
- headers: {
71
- 'Content-Type': 'application/json',
72
- 'Authorization': `Bearer ${api_key}`,
73
- },
74
- body: JSON.stringify(request_body),
75
- });
76
- // Parse the response
77
- const response_data = await response.json();
78
- // Check for errors
79
- if (!response.ok || response_data.error) {
80
- const error_message = response_data.error?.message || `HTTP ${response.status}`;
81
- logger.error('Qwen API returned error', {
82
- file: file_name,
83
- line: 152,
84
- data: {
85
- status: response.status,
86
- error: response_data.error,
87
- request_model: model,
88
- },
89
- });
90
- return {
91
- success: false,
92
- error: error_message,
93
- raw_response: response_data,
94
- };
95
- }
96
- // Extract the generated text from response
97
- const generated_text = extract_text_from_response(response_data, logger);
98
- if (generated_text) {
99
- return {
100
- success: true,
101
- text: generated_text,
102
- raw_response: response_data,
103
- };
104
- }
105
- else {
106
- logger.warn('No text content in Qwen response', {
107
- file: file_name,
108
- line: 177,
109
- data: { raw_response: response_data },
110
- });
111
- return {
112
- success: false,
113
- error: 'No text content in response',
114
- raw_response: response_data,
115
- };
116
- }
117
- }
118
- catch (error) {
119
- const error_message = error instanceof Error ? error.message : String(error);
120
- logger.error('Failed to call Qwen API', {
121
- file: file_name,
122
- line: 188,
123
- data: { error: error_message, model },
124
- });
125
- return {
126
- success: false,
127
- error: error_message,
128
- };
129
- }
130
- }
131
- // =============================================================================
132
- // Response Parsing Functions
133
- // =============================================================================
134
- /**
135
- * Extract text content from Qwen API response
136
- * @param response - The Qwen API response
137
- * @param logger - Logger instance
138
- * @returns The generated text or null if not found
139
- */
140
- function extract_text_from_response(response, logger) {
141
- const file_name = 'qwen_client.ts';
142
- if (!response.choices || response.choices.length === 0) {
143
- logger.debug('No choices in Qwen response', {
144
- file: file_name,
145
- line: 210,
146
- });
147
- return null;
148
- }
149
- const first_choice = response.choices[0];
150
- if (!first_choice.message || !first_choice.message.content) {
151
- logger.debug('No message content in first choice', {
152
- file: file_name,
153
- line: 218,
154
- });
155
- return null;
156
- }
157
- return first_choice.message.content;
158
- }
159
- /**
160
- * Get the default Qwen API URL
161
- * @returns The default DashScope API URL
162
- */
163
- export function get_qwen_api_url() {
164
- return 'https://dashscope-intl.aliyuncs.com/compatible-mode/v1/chat/completions';
165
- }
166
- /**
167
- * Get the DashScope image generation API URL
168
- * @param model - The model name (e.g., qwen-image-plus)
169
- * @returns The DashScope image generation API URL
170
- */
171
- export function get_qwen_image_api_url(model) {
172
- // DashScope image generation endpoint format
173
- // Try the standard text-to-image endpoint first
174
- return 'https://dashscope-intl.aliyuncs.com/api/v1/services/aigc/text2image/image-synthesis';
175
- }
176
- /**
177
- * Get the DashScope image editing API URL
178
- * @param model - The model name (e.g., qwen-image-edit)
179
- * @returns The DashScope image editing API URL
180
- */
181
- export function get_qwen_image_edit_api_url(model) {
182
- // DashScope image editing uses MultiModalConversation API
183
- // Uses the same chat completions endpoint as other multimodal operations
184
- return 'https://dashscope-intl.aliyuncs.com/compatible-mode/v1/chat/completions';
185
- }
186
- /**
187
- * Build Qwen messages array from prompt and optional images
188
- * @param prompt - The text prompt
189
- * @param system_instruction - Optional system instruction
190
- * @param b64_data - Optional array of base64 encoded images
191
- * @returns Array of Qwen messages
192
- */
193
- export function build_qwen_messages(prompt, system_instruction, b64_data) {
194
- const messages = [];
195
- // Add system message if provided
196
- if (system_instruction) {
197
- messages.push({
198
- role: 'system',
199
- content: system_instruction,
200
- });
201
- }
202
- // Build user message content
203
- if (b64_data && b64_data.length > 0) {
204
- // Multi-modal message with images
205
- const content = [];
206
- // Add images first
207
- for (const img of b64_data) {
208
- content.push({
209
- type: 'image_url',
210
- image_url: {
211
- url: `data:${img.mime_type};base64,${img.data}`,
212
- },
213
- });
214
- }
215
- // Add text prompt
216
- content.push({
217
- type: 'text',
218
- text: prompt,
219
- });
220
- messages.push({
221
- role: 'user',
222
- content,
223
- });
224
- }
225
- else {
226
- // Text-only message
227
- messages.push({
228
- role: 'user',
229
- content: prompt,
230
- });
231
- }
232
- return messages;
233
- }
234
- /**
235
- * Call the DashScope image generation API
236
- * @param api_url - The DashScope image generation API endpoint URL
237
- * @param api_key - The API key for authentication
238
- * @param model - The model name to use (e.g., qwen-image-plus)
239
- * @param prompt - The text prompt for image generation
240
- * @param logger - Logger instance
241
- * @param generation_config - Optional generation configuration parameters
242
- * @returns LLM response with generated image or error
243
- */
244
- export async function call_qwen_image_api(api_url, api_key, model, prompt, logger, generation_config) {
245
- const file_name = 'qwen_client.ts';
246
- try {
247
- // Build the request body for image generation
248
- // DashScope image generation API format
249
- // Allowed sizes: 1664*928, 1472*1140, 1328*1328, 1140*1472, 928*1664
250
- const request_body = {
251
- model,
252
- input: {
253
- prompt,
254
- },
255
- parameters: {
256
- // Use square format as default (1328*1328)
257
- size: '1328*1328',
258
- },
259
- };
260
- // Add generation config parameters if provided
261
- // Note: DashScope image generation may support different parameters
262
- // For now, we'll use basic parameters
263
- if (generation_config) {
264
- const params = {};
265
- if (generation_config.n !== undefined) {
266
- params.n = generation_config.n;
267
- }
268
- // Add other parameters as needed for image generation
269
- if (Object.keys(params).length > 0) {
270
- request_body.parameters = params;
271
- }
272
- }
273
- // Log the API call
274
- logger.debug('Calling DashScope image generation API', {
275
- file: file_name,
276
- line: 380,
277
- data: {
278
- api_url,
279
- model,
280
- prompt_length: prompt.length,
281
- has_generation_config: !!generation_config,
282
- },
283
- });
284
- // Make the API request
285
- // DashScope image generation requires async mode
286
- const response = await fetch(api_url, {
287
- method: 'POST',
288
- headers: {
289
- 'Content-Type': 'application/json',
290
- 'Authorization': `Bearer ${api_key}`,
291
- 'X-DashScope-Async': 'enable', // Enable async mode
292
- },
293
- body: JSON.stringify(request_body),
294
- });
295
- let parsed_response;
296
- const response_text = await response.text();
297
- try {
298
- parsed_response = JSON.parse(response_text);
299
- // Log the initial response to see what we get
300
- logger.debug('DashScope image generation initial response', {
301
- file: file_name,
302
- line: 450,
303
- data: {
304
- status: response.status,
305
- response_preview: response_text.substring(0, 1000),
306
- },
307
- });
308
- }
309
- catch {
310
- // If not JSON, use the text as error message
311
- parsed_response = { error: { message: response_text } };
312
- }
313
- // Check for errors
314
- if (!response.ok || parsed_response.error || parsed_response.code) {
315
- const error_message = parsed_response.error?.message ||
316
- parsed_response.message ||
317
- `HTTP ${response.status}: ${response_text.substring(0, 500)}`;
318
- logger.error('DashScope image generation API returned error', {
319
- file: file_name,
320
- line: 420,
321
- data: {
322
- status: response.status,
323
- status_text: response.statusText,
324
- error: parsed_response.error,
325
- code: parsed_response.code,
326
- message: parsed_response.message,
327
- request_model: model,
328
- request_body: JSON.stringify(request_body),
329
- full_response: response_text.substring(0, 1000), // Limit to first 1000 chars
330
- },
331
- });
332
- return {
333
- success: false,
334
- error: error_message,
335
- raw_response: parsed_response,
336
- };
337
- }
338
- // Handle async task - if task_id is returned, we need to poll for results
339
- const task_id = parsed_response.output?.task_id || parsed_response.task_id;
340
- if (task_id && !parsed_response.output?.results) {
341
- // Async mode - poll for results
342
- logger.info('DashScope returned async task_id, polling for results', {
343
- file: file_name,
344
- line: 440,
345
- data: { task_id },
346
- });
347
- // Poll the task status endpoint
348
- // DashScope uses: /api/v1/tasks/{task_id}
349
- const status_url = `https://dashscope-intl.aliyuncs.com/api/v1/tasks/${task_id}`;
350
- let attempts = 0;
351
- const max_attempts = 60; // Poll for up to 60 seconds (image generation can take time)
352
- while (attempts < max_attempts) {
353
- await new Promise(resolve => setTimeout(resolve, 2000)); // Wait 2 seconds between polls
354
- const status_response = await fetch(status_url, {
355
- headers: {
356
- 'Authorization': `Bearer ${api_key}`,
357
- },
358
- });
359
- if (!status_response.ok) {
360
- const error_text = await status_response.text();
361
- logger.warn('Task status check failed', {
362
- file: file_name,
363
- line: 510,
364
- data: { status: status_response.status, error: error_text },
365
- });
366
- attempts++;
367
- continue;
368
- }
369
- const status_data_raw = await status_response.json();
370
- const status_data = status_data_raw;
371
- // Check task status FIRST - if it's "FAILED" or "CANCELED", return error immediately
372
- const task_status = status_data_raw.output?.task_status ||
373
- status_data_raw.task_status ||
374
- status_data_raw.status;
375
- // Log the status response for debugging (less frequently to reduce log noise)
376
- if (attempts % 5 === 0 || task_status === 'FAILED' || task_status === 'SUCCEEDED') {
377
- logger.debug('Polling task status response', {
378
- file: file_name,
379
- line: 530,
380
- data: {
381
- task_id,
382
- attempt: attempts + 1,
383
- max_attempts,
384
- response_status: status_response.status,
385
- task_status,
386
- response_data: JSON.stringify(status_data_raw).substring(0, 500), // First 500 chars
387
- },
388
- });
389
- }
390
- // Check for FAILED or CANCELED status immediately
391
- if (task_status === 'FAILED' || task_status === 'CANCELED') {
392
- // Extract error message from output if available
393
- const output = status_data_raw.output;
394
- const error_msg = output?.message ||
395
- status_data.error?.message ||
396
- status_data.message ||
397
- `Task ${task_status.toLowerCase()}`;
398
- logger.error('Image generation task failed', {
399
- file: file_name,
400
- line: 545,
401
- data: { task_id, task_status, error_code: output?.code, error_message: error_msg },
402
- });
403
- return {
404
- success: false,
405
- error: error_msg,
406
- raw_response: status_data,
407
- };
408
- }
409
- // Check if task is complete and has results
410
- // DashScope may return results in different structures
411
- if (status_data.output?.results && status_data.output.results.length > 0) {
412
- // Results are ready
413
- parsed_response.output = status_data.output;
414
- break;
415
- }
416
- // Also check for direct results in response (some API versions)
417
- if (status_data_raw.results &&
418
- Array.isArray(status_data_raw.results) &&
419
- status_data_raw.results.length > 0) {
420
- parsed_response.output = {
421
- results: status_data_raw.results,
422
- };
423
- break;
424
- }
425
- // If task is SUCCEEDED but no results yet, continue polling
426
- if (task_status === 'SUCCEEDED') {
427
- // Results should be available, but if not, continue polling
428
- attempts++;
429
- continue;
430
- }
431
- // Check for task failure in error fields
432
- if (status_data.error || status_data.code) {
433
- const error_msg = status_data.error?.message || status_data.message || 'Task failed';
434
- return {
435
- success: false,
436
- error: error_msg,
437
- raw_response: status_data,
438
- };
439
- }
440
- // If task is still PENDING or RUNNING, continue polling
441
- if (task_status === 'PENDING' || task_status === 'RUNNING' || !task_status) {
442
- attempts++;
443
- continue;
444
- }
445
- // Default: continue polling
446
- attempts++;
447
- }
448
- if (attempts >= max_attempts) {
449
- return {
450
- success: false,
451
- error: 'Image generation timed out - task did not complete in time',
452
- raw_response: parsed_response,
453
- };
454
- }
455
- }
456
- // Extract image URL from response
457
- const results = parsed_response.output?.results;
458
- if (results && results.length > 0 && results[0].url) {
459
- // Fetch the image from the URL and convert to base64
460
- const image_url = results[0].url;
461
- const image_response = await fetch(image_url);
462
- if (!image_response.ok) {
463
- return {
464
- success: false,
465
- error: `Failed to fetch generated image: HTTP ${image_response.status}`,
466
- };
467
- }
468
- const image_array_buffer = await image_response.arrayBuffer();
469
- // Convert ArrayBuffer to base64 in chunks to avoid stack overflow
470
- const uint8_array = new Uint8Array(image_array_buffer);
471
- const chunk_size = 8192; // Process in 8KB chunks
472
- let binary_string = '';
473
- for (let i = 0; i < uint8_array.length; i += chunk_size) {
474
- const chunk = uint8_array.slice(i, i + chunk_size);
475
- binary_string += String.fromCharCode(...chunk);
476
- }
477
- const image_base64 = btoa(binary_string);
478
- // Get content type from response headers
479
- const content_type = image_response.headers.get('content-type') || 'image/png';
480
- return {
481
- success: true,
482
- image_b64: image_base64,
483
- image_mime_type: content_type,
484
- raw_response: parsed_response,
485
- };
486
- }
487
- logger.warn('No image URL in DashScope response', {
488
- file: file_name,
489
- line: 470,
490
- data: { raw_response: parsed_response },
491
- });
492
- return {
493
- success: false,
494
- error: 'No image URL in response',
495
- raw_response: parsed_response,
496
- };
497
- }
498
- catch (error) {
499
- const error_message = error instanceof Error ? error.message : String(error);
500
- logger.error('Failed to call DashScope image generation API', {
501
- file: file_name,
502
- line: 480,
503
- data: { error: error_message, model },
504
- });
505
- return {
506
- success: false,
507
- error: error_message,
508
- };
509
- }
510
- }
511
- /**
512
- * Call the DashScope image editing API
513
- * @param api_url - The DashScope image editing API endpoint URL
514
- * @param api_key - The API key for authentication
515
- * @param model - The model name to use (e.g., qwen-image-edit)
516
- * @param prompt - The text prompt describing the transformation
517
- * @param input_images - Array of base64 encoded input images with mime types (1-3 images supported)
518
- * @param logger - Logger instance
519
- * @param generation_config - Optional generation configuration parameters
520
- * @returns LLM response with edited image or error
521
- */
522
- export async function call_qwen_image_edit_api(api_url, api_key, model, prompt, input_images, logger, generation_config) {
523
- const file_name = 'qwen_client.ts';
524
- try {
525
- // Check if this is the multimodal generation endpoint (different format)
526
- const is_multimodal_endpoint = api_url.includes('/multimodal-generation/');
527
- let request_body;
528
- if (is_multimodal_endpoint) {
529
- // Multimodal generation endpoint uses input format with images array
530
- // Support multiple images (1-3 images as per API requirements)
531
- const content = [];
532
- // Add all images first
533
- for (const img of input_images) {
534
- content.push({
535
- image: `data:${img.mime_type};base64,${img.data}`,
536
- });
537
- }
538
- // Add text prompt after images
539
- content.push({
540
- text: prompt,
541
- });
542
- request_body = {
543
- model,
544
- input: {
545
- messages: [
546
- {
547
- role: 'user',
548
- content,
549
- },
550
- ],
551
- },
552
- parameters: {
553
- result_format: 'message',
554
- },
555
- };
556
- }
557
- else {
558
- // Chat completions endpoint uses messages format
559
- // Support multiple images
560
- const messages = build_qwen_messages(prompt, undefined, input_images);
561
- request_body = {
562
- model,
563
- messages,
564
- result_format: 'message', // Required for multimodal responses with images
565
- stream: false, // Disable streaming for image editing
566
- };
567
- }
568
- // Add generation config parameters if provided
569
- if (generation_config) {
570
- if (is_multimodal_endpoint) {
571
- // For multimodal endpoint, add to parameters object
572
- const params = request_body.parameters;
573
- if (generation_config.temperature !== undefined) {
574
- params.temperature = generation_config.temperature;
575
- }
576
- if (generation_config.max_tokens !== undefined) {
577
- params.max_tokens = generation_config.max_tokens;
578
- }
579
- if (generation_config.top_p !== undefined) {
580
- params.top_p = generation_config.top_p;
581
- }
582
- if (generation_config.top_k !== undefined) {
583
- params.top_k = generation_config.top_k;
584
- }
585
- }
586
- else {
587
- // For chat completions endpoint, add to root
588
- if (generation_config.temperature !== undefined) {
589
- request_body.temperature = generation_config.temperature;
590
- }
591
- if (generation_config.max_tokens !== undefined) {
592
- request_body.max_tokens = generation_config.max_tokens;
593
- }
594
- if (generation_config.top_p !== undefined) {
595
- request_body.top_p = generation_config.top_p;
596
- }
597
- if (generation_config.top_k !== undefined) {
598
- request_body.top_k = generation_config.top_k;
599
- }
600
- if (generation_config.n !== undefined) {
601
- request_body.n = generation_config.n;
602
- }
603
- if (generation_config.stop !== undefined && generation_config.stop.length > 0) {
604
- request_body.stop = generation_config.stop;
605
- }
606
- if (generation_config.presence_penalty !== undefined) {
607
- request_body.presence_penalty = generation_config.presence_penalty;
608
- }
609
- if (generation_config.frequency_penalty !== undefined) {
610
- request_body.frequency_penalty = generation_config.frequency_penalty;
611
- }
612
- }
613
- }
614
- // Log the API call
615
- logger.debug('Calling DashScope image editing API', {
616
- file: file_name,
617
- line: 730,
618
- data: {
619
- api_url,
620
- model,
621
- prompt_length: prompt.length,
622
- image_count: input_images.length,
623
- image_mime_types: input_images.map(img => img.mime_type),
624
- has_generation_config: !!generation_config,
625
- },
626
- });
627
- // Make the API request
628
- // DashScope image editing uses chat completions endpoint
629
- const response = await fetch(api_url, {
630
- method: 'POST',
631
- headers: {
632
- 'Content-Type': 'application/json',
633
- 'Authorization': `Bearer ${api_key}`,
634
- },
635
- body: JSON.stringify(request_body),
636
- });
637
- // Parse the response - handle both chat completions and multimodal generation formats
638
- const raw_response = await response.json();
639
- // Log the initial response
640
- logger.debug('DashScope image editing initial response', {
641
- file: file_name,
642
- line: 854,
643
- data: {
644
- status: response.status,
645
- response_preview: JSON.stringify(raw_response).substring(0, 1000),
646
- },
647
- });
648
- // Check for errors (handle both response formats)
649
- // For multimodal endpoint, successful responses have 'output' field, errors have 'error' or 'code' field
650
- if (!response.ok) {
651
- // HTTP error status
652
- const error_data = raw_response.error;
653
- const error_message = error_data?.message ||
654
- raw_response?.message ||
655
- `HTTP ${response.status}`;
656
- logger.error('DashScope image editing API returned HTTP error', {
657
- file: file_name,
658
- line: 870,
659
- data: {
660
- status: response.status,
661
- error: error_data,
662
- request_model: model,
663
- },
664
- });
665
- return {
666
- success: false,
667
- error: error_message,
668
- raw_response: raw_response,
669
- };
670
- }
671
- // Check for API-level errors in response body
672
- // Only check for actual error fields, not the entire response object
673
- const error_field = raw_response.error;
674
- const error_code = raw_response.code;
675
- const has_error = error_field !== undefined || (error_code !== undefined && error_code !== '200' && error_code !== 'Success');
676
- if (has_error) {
677
- const error_message = error_field?.message ||
678
- raw_response?.message ||
679
- `API returned error code: ${error_code}`;
680
- logger.error('DashScope image editing API returned error', {
681
- file: file_name,
682
- line: 895,
683
- data: {
684
- status: response.status,
685
- error: error_field || { code: error_code, message: raw_response?.message },
686
- request_model: model,
687
- },
688
- });
689
- return {
690
- success: false,
691
- error: error_message,
692
- raw_response: raw_response,
693
- };
694
- }
695
- // Handle multimodal generation endpoint response format (wraps in output object)
696
- let response_data;
697
- let raw_response_data = raw_response;
698
- if (is_multimodal_endpoint && raw_response.output) {
699
- // Multimodal generation endpoint wraps response in output object
700
- const multimodal_response = raw_response;
701
- response_data = multimodal_response.output;
702
- raw_response_data = multimodal_response; // Keep full response for logging
703
- }
704
- else {
705
- // Chat completions format (direct response)
706
- response_data = raw_response;
707
- }
708
- // Extract image from response
709
- // For image editing, the response content may contain image URLs or base64 data
710
- if (!response_data.choices || response_data.choices.length === 0) {
711
- logger.warn('No choices in DashScope image editing response', {
712
- file: file_name,
713
- line: 843,
714
- data: {
715
- raw_response: response_data,
716
- response_keys: Object.keys(response_data || {}),
717
- full_response: JSON.stringify(response_data, null, 2).substring(0, 2000),
718
- },
719
- });
720
- return {
721
- success: false,
722
- error: 'No content in response - API returned no choices. Check logs for full response details.',
723
- raw_response: response_data,
724
- };
725
- }
726
- const first_choice = response_data.choices[0];
727
- const content = first_choice.message?.content;
728
- if (!content) {
729
- logger.warn('No content in first choice', {
730
- file: file_name,
731
- line: 920,
732
- data: {
733
- raw_response: response_data,
734
- first_choice: first_choice,
735
- message: first_choice.message,
736
- full_response: JSON.stringify(response_data, null, 2).substring(0, 2000),
737
- },
738
- });
739
- return {
740
- success: false,
741
- error: 'No content in response - message has no content. Check logs for full response details.',
742
- raw_response: response_data,
743
- };
744
- }
745
- // Handle multimodal generation endpoint format - content is array with {image: "url"} objects
746
- if (Array.isArray(content)) {
747
- const content_array = content;
748
- for (const item of content_array) {
749
- // Check for multimodal format: {image: "url"} (multimodal generation endpoint)
750
- if (item.image && typeof item.image === 'string') {
751
- const image_url = item.image;
752
- try {
753
- const image_response = await fetch(image_url);
754
- if (!image_response.ok) {
755
- return {
756
- success: false,
757
- error: `Failed to fetch edited image: HTTP ${image_response.status}`,
758
- };
759
- }
760
- const image_array_buffer = await image_response.arrayBuffer();
761
- const uint8_array = new Uint8Array(image_array_buffer);
762
- const chunk_size = 8192;
763
- let binary_string = '';
764
- for (let i = 0; i < uint8_array.length; i += chunk_size) {
765
- const chunk = uint8_array.slice(i, i + chunk_size);
766
- binary_string += String.fromCharCode(...chunk);
767
- }
768
- const image_base64 = btoa(binary_string);
769
- const content_type = image_response.headers.get('content-type') || 'image/png';
770
- return {
771
- success: true,
772
- image_b64: image_base64,
773
- image_mime_type: content_type,
774
- raw_response: response_data,
775
- };
776
- }
777
- catch (fetch_error) {
778
- logger.error('Failed to fetch image from URL', {
779
- file: file_name,
780
- line: 960,
781
- data: { error: fetch_error instanceof Error ? fetch_error.message : String(fetch_error), url: image_url },
782
- });
783
- return {
784
- success: false,
785
- error: 'Failed to fetch image from URL in response',
786
- raw_response: response_data,
787
- };
788
- }
789
- }
790
- // Check for chat completions format: {image_url: {url: "..."}}
791
- if (item.image_url?.url) {
792
- const image_url = item.image_url.url;
793
- // Handle data URLs
794
- if (image_url.startsWith('data:')) {
795
- const base64_match = image_url.match(/data:image\/([^;]+);base64,([A-Za-z0-9+/=]+)/);
796
- if (base64_match) {
797
- const mime_type = `image/${base64_match[1]}`;
798
- const base64_data = base64_match[2];
799
- return {
800
- success: true,
801
- image_b64: base64_data,
802
- image_mime_type: mime_type,
803
- raw_response: response_data,
804
- };
805
- }
806
- }
807
- else {
808
- // Fetch from URL
809
- try {
810
- const image_response = await fetch(image_url);
811
- if (!image_response.ok) {
812
- return {
813
- success: false,
814
- error: `Failed to fetch edited image: HTTP ${image_response.status}`,
815
- };
816
- }
817
- const image_array_buffer = await image_response.arrayBuffer();
818
- const uint8_array = new Uint8Array(image_array_buffer);
819
- const chunk_size = 8192;
820
- let binary_string = '';
821
- for (let i = 0; i < uint8_array.length; i += chunk_size) {
822
- const chunk = uint8_array.slice(i, i + chunk_size);
823
- binary_string += String.fromCharCode(...chunk);
824
- }
825
- const image_base64 = btoa(binary_string);
826
- const content_type = image_response.headers.get('content-type') || 'image/png';
827
- return {
828
- success: true,
829
- image_b64: image_base64,
830
- image_mime_type: content_type,
831
- raw_response: response_data,
832
- };
833
- }
834
- catch (fetch_error) {
835
- logger.error('Failed to fetch image from URL', {
836
- file: file_name,
837
- line: 1000,
838
- data: { error: fetch_error instanceof Error ? fetch_error.message : String(fetch_error), url: image_url },
839
- });
840
- return {
841
- success: false,
842
- error: 'Failed to fetch image from URL in response',
843
- raw_response: response_data,
844
- };
845
- }
846
- }
847
- }
848
- }
849
- }
850
- // Check if content is a string (might contain image URL or base64)
851
- if (typeof content === 'string') {
852
- // Try to extract image URL or base64 from content
853
- // Image URLs typically start with http:// or https://
854
- const url_match = content.match(/https?:\/\/[^\s]+/);
855
- if (url_match) {
856
- const image_url = url_match[0];
857
- try {
858
- const image_response = await fetch(image_url);
859
- if (!image_response.ok) {
860
- return {
861
- success: false,
862
- error: `Failed to fetch edited image: HTTP ${image_response.status}`,
863
- };
864
- }
865
- const image_array_buffer = await image_response.arrayBuffer();
866
- const uint8_array = new Uint8Array(image_array_buffer);
867
- const chunk_size = 8192;
868
- let binary_string = '';
869
- for (let i = 0; i < uint8_array.length; i += chunk_size) {
870
- const chunk = uint8_array.slice(i, i + chunk_size);
871
- binary_string += String.fromCharCode(...chunk);
872
- }
873
- const image_base64 = btoa(binary_string);
874
- const content_type = image_response.headers.get('content-type') || 'image/png';
875
- return {
876
- success: true,
877
- image_b64: image_base64,
878
- image_mime_type: content_type,
879
- raw_response: response_data,
880
- };
881
- }
882
- catch (fetch_error) {
883
- logger.error('Failed to fetch image from URL', {
884
- file: file_name,
885
- line: 890,
886
- data: { error: fetch_error instanceof Error ? fetch_error.message : String(fetch_error), url: image_url },
887
- });
888
- return {
889
- success: false,
890
- error: 'Failed to fetch image from URL in response',
891
- raw_response: response_data,
892
- };
893
- }
894
- }
895
- // Check if content contains base64 image data
896
- const base64_match = content.match(/data:image\/([^;]+);base64,([A-Za-z0-9+/=]+)/);
897
- if (base64_match) {
898
- const mime_type = `image/${base64_match[1]}`;
899
- const base64_data = base64_match[2];
900
- return {
901
- success: true,
902
- image_b64: base64_data,
903
- image_mime_type: mime_type,
904
- raw_response: response_data,
905
- };
906
- }
907
- // If no image found, return text response
908
- return {
909
- success: true,
910
- text: content,
911
- raw_response: response_data,
912
- };
913
- }
914
- // If content is an array (multimodal), look for image_url
915
- if (Array.isArray(content)) {
916
- const content_array = content;
917
- for (const item of content_array) {
918
- if (item.type === 'image_url' && item.image_url?.url) {
919
- const image_url = item.image_url.url;
920
- // Handle data URLs
921
- if (image_url.startsWith('data:')) {
922
- const base64_match = image_url.match(/data:image\/([^;]+);base64,([A-Za-z0-9+/=]+)/);
923
- if (base64_match) {
924
- const mime_type = `image/${base64_match[1]}`;
925
- const base64_data = base64_match[2];
926
- return {
927
- success: true,
928
- image_b64: base64_data,
929
- image_mime_type: mime_type,
930
- raw_response: response_data,
931
- };
932
- }
933
- }
934
- else {
935
- // Fetch from URL
936
- try {
937
- const image_response = await fetch(image_url);
938
- if (!image_response.ok) {
939
- return {
940
- success: false,
941
- error: `Failed to fetch edited image: HTTP ${image_response.status}`,
942
- };
943
- }
944
- const image_array_buffer = await image_response.arrayBuffer();
945
- const uint8_array = new Uint8Array(image_array_buffer);
946
- const chunk_size = 8192;
947
- let binary_string = '';
948
- for (let i = 0; i < uint8_array.length; i += chunk_size) {
949
- const chunk = uint8_array.slice(i, i + chunk_size);
950
- binary_string += String.fromCharCode(...chunk);
951
- }
952
- const image_base64 = btoa(binary_string);
953
- const content_type = image_response.headers.get('content-type') || 'image/png';
954
- return {
955
- success: true,
956
- image_b64: image_base64,
957
- image_mime_type: content_type,
958
- raw_response: response_data,
959
- };
960
- }
961
- catch (fetch_error) {
962
- logger.error('Failed to fetch image from URL', {
963
- file: file_name,
964
- line: 950,
965
- data: { error: fetch_error instanceof Error ? fetch_error.message : String(fetch_error), url: image_url },
966
- });
967
- return {
968
- success: false,
969
- error: 'Failed to fetch image from URL in response',
970
- raw_response: response_data,
971
- };
972
- }
973
- }
974
- }
975
- }
976
- }
977
- // If we get here, no image was found
978
- logger.warn('No image found in DashScope image editing response', {
979
- file: file_name,
980
- line: 960,
981
- data: { raw_response: response_data },
982
- });
983
- return {
984
- success: false,
985
- error: 'No image found in response',
986
- raw_response: response_data,
987
- };
988
- }
989
- catch (error) {
990
- const error_message = error instanceof Error ? error.message : String(error);
991
- logger.error('Failed to call DashScope image editing API', {
992
- file: file_name,
993
- line: 970,
994
- data: { error: error_message, model },
995
- });
996
- return {
997
- success: false,
998
- error: error_message,
999
- };
1000
- }
1001
- }
1002
- //# sourceMappingURL=qwen_client.js.map