{"version":3,"file":"api/controllers/queries.mjs","sources":["webpack://@agent-tars/server/./src/api/controllers/queries.ts"],"sourcesContent":["/*\n * Copyright (c) 2025 Bytedance, Inc. and its affiliates.\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { Request, Response } from 'express';\nimport { AgentTARSServer } from '../../server';\nimport {\n  ChatCompletionContentPart,\n  ChatCompletionContentPartImage,\n  ImageCompressor,\n  formatBytes,\n} from '@agent-tars/core';\nimport { createErrorResponse } from '../../utils/error-handler';\n\n// 创建一个单例图像压缩器以供所有函数共享\nconst imageCompressor = new ImageCompressor({\n  quality: 5,\n  format: 'webp',\n});\n\n/**\n * Compress images in query content if present\n * @param query - The query content that may contain images\n * @returns Processed query with compressed images\n */\nasync function compressImagesInQuery(\n  query: string | ChatCompletionContentPart[],\n): Promise<string | ChatCompletionContentPart[]> {\n  try {\n    // Handle different query formats\n    if (typeof query === 'string') {\n      return query; // Text only, no compression needed\n    }\n\n    // Handle array of content parts (multimodal format)\n    if (Array.isArray(query)) {\n      const compressedQuery = await Promise.all(\n        query.map(async (part: ChatCompletionContentPart) => {\n          if (part.type === 'image_url' && part.image_url?.url) {\n            return await compressImageUrl(part);\n          }\n          return part;\n        }),\n      );\n      return compressedQuery;\n    }\n\n    return query;\n  } catch (error) {\n    console.error('Error compressing images in query:', error);\n    // Return original query if compression fails\n    return query;\n  }\n}\n\n/**\n * Compress a single image URL\n * @param imagePart - Content part containing image URL\n * @returns Compressed image content part\n */\nasync function compressImageUrl(\n  imagePart: ChatCompletionContentPartImage,\n): Promise<ChatCompletionContentPartImage> {\n  try {\n    const imageUrl = imagePart!.image_url.url;\n\n    // Skip if not a base64 image\n    if (!imageUrl.startsWith('data:image/')) {\n      return imagePart;\n    }\n\n    // Extract base64 data\n    const base64Data = imageUrl.replace(/^data:image\\/\\w+;base64,/, '');\n    const originalBuffer = Buffer.from(base64Data, 'base64');\n    const originalSize = originalBuffer.length;\n\n    // Compress the image\n    const compressedBuffer = await imageCompressor.compressToBuffer(originalBuffer);\n    const compressedSize = compressedBuffer.length;\n\n    // Convert compressed buffer to base64\n    const compressedBase64 = `data:image/webp;base64,${compressedBuffer.toString('base64')}`;\n\n    // Log compression stats\n    const compressionRatio = originalSize / compressedSize;\n    const compressionPercentage = ((1 - compressedSize / originalSize) * 100).toFixed(2);\n\n    console.log('Image compression stats:', {\n      original: formatBytes(originalSize),\n      compressed: formatBytes(compressedSize),\n      ratio: `${compressionRatio.toFixed(2)}x (${compressionPercentage}% smaller)`,\n      format: 'webp',\n      quality: 80,\n    });\n\n    return {\n      ...imagePart,\n      image_url: {\n        url: compressedBase64,\n      },\n    };\n  } catch (error) {\n    console.error('Error compressing individual image:', error);\n    // Return original image part if compression fails\n    return imagePart;\n  }\n}\n\n/**\n * Execute a non-streaming query\n */\nexport async function executeQuery(req: Request, res: Response) {\n  const { sessionId, query } = req.body;\n\n  if (!sessionId) {\n    return res.status(400).json({ error: 'Session ID is required' });\n  }\n\n  if (!query) {\n    return res.status(400).json({ error: 'Query is required' });\n  }\n\n  const server = req.app.locals.server as AgentTARSServer;\n  if (!server.sessions[sessionId]) {\n    return res.status(404).json({ error: 'Session not found' });\n  }\n\n  try {\n    // Compress images in query before processing\n    const compressedQuery = await compressImagesInQuery(query);\n\n    // Use enhanced error handling in runQuery\n    const response = await server.sessions[sessionId].runQuery(compressedQuery);\n\n    if (response.success) {\n      res.status(200).json({ result: response.result });\n    } else {\n      // Send structured error response with 500 status\n      res.status(500).json(response);\n    }\n  } catch (error) {\n    // This should never happen with the new error handling, but just in case\n    console.error(`Unexpected error processing query in session ${sessionId}:`, error);\n    res.status(500).json(createErrorResponse(error));\n  }\n}\n\n/**\n * Execute a streaming query\n */\nexport async function executeStreamingQuery(req: Request, res: Response) {\n  const { sessionId, query } = req.body;\n\n  if (!sessionId) {\n    return res.status(400).json({ error: 'Session ID is required' });\n  }\n\n  if (!query) {\n    return res.status(400).json({ error: 'Query is required' });\n  }\n\n  const server = req.app.locals.server as AgentTARSServer;\n  if (!server.sessions[sessionId]) {\n    return res.status(404).json({ error: 'Session not found' });\n  }\n\n  try {\n    // Set response headers for streaming\n    res.setHeader('Content-Type', 'text/event-stream');\n    res.setHeader('Cache-Control', 'no-cache');\n    res.setHeader('Connection', 'keep-alive');\n\n    // Compress images in query before processing\n    const compressedQuery = await compressImagesInQuery(query);\n\n    // Get streaming response - any errors will be returned as events\n    const eventStream = await server.sessions[sessionId].runQueryStreaming(compressedQuery);\n\n    // Stream events one by one\n    for await (const event of eventStream) {\n      // Check for error events\n      const isErrorEvent = event.type === 'system' && event.level === 'error';\n\n      // Only send data when connection is still open\n      if (!res.closed) {\n        res.write(`data: ${JSON.stringify(event)}\\n\\n`);\n\n        // If we encounter an error event, end streaming\n        if (isErrorEvent) {\n          break;\n        }\n      } else {\n        break;\n      }\n    }\n\n    // End the stream response\n    if (!res.closed) {\n      res.end();\n    }\n  } catch (error) {\n    // This should almost never happen with the new error handling\n    console.error(`Critical error in streaming query for session ${sessionId}:`, error);\n\n    if (!res.headersSent) {\n      res.status(500).json(createErrorResponse(error));\n    } else {\n      const errorObj = createErrorResponse(error);\n      res.write(\n        `data: ${JSON.stringify({\n          type: 'system',\n          level: 'error',\n          message: errorObj.error.message,\n          timestamp: Date.now(),\n        })}\\n\\n`,\n      );\n      res.end();\n    }\n  }\n}\n\n/**\n * Abort a running query\n */\nexport async function abortQuery(req: Request, res: Response) {\n  const { sessionId } = req.body;\n\n  if (!sessionId) {\n    return res.status(400).json({ error: 'Session ID is required' });\n  }\n\n  const server = req.app.locals.server as AgentTARSServer;\n  if (!server.sessions[sessionId]) {\n    return res.status(404).json({ error: 'Session not found' });\n  }\n\n  try {\n    const aborted = await server.sessions[sessionId].abortQuery();\n    res.status(200).json({ success: aborted });\n  } catch (error) {\n    console.error(`Error aborting query in session ${sessionId}:`, error);\n    res.status(500).json({ error: 'Failed to abort query' });\n  }\n}\n"],"names":["imageCompressor","ImageCompressor","compressImagesInQuery","query","Array","compressedQuery","Promise","part","_part_image_url","compressImageUrl","error","console","imagePart","imageUrl","base64Data","originalBuffer","Buffer","originalSize","compressedBuffer","compressedSize","compressedBase64","compressionRatio","compressionPercentage","formatBytes","executeQuery","req","res","sessionId","server","response","createErrorResponse","executeStreamingQuery","eventStream","event","isErrorEvent","JSON","errorObj","Date","abortQuery","aborted"],"mappings":"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"}