{"version":3,"file":"ai-model/prompt/yaml-generator.mjs","sources":["../../../../src/ai-model/prompt/yaml-generator.ts"],"sourcesContent":["import type {\n  StreamingAIResponse,\n  StreamingCodeGenerationOptions,\n} from '@/types';\nimport { YAML_EXAMPLE_CODE } from '@midscene/shared/constants';\nimport type { IModelConfig } from '@midscene/shared/env';\nimport {\n  type MidsceneRecorderMarkdownScreenshotAsset,\n  type MidsceneRecorderTarget,\n  stringifyMidsceneRecorderTargetBlock,\n} from '@midscene/shared/recorder';\nimport {\n  type ChatCompletionMessageParam,\n  callAI,\n  callAIWithStringResponse,\n} from '../index';\nimport { type ModelRuntime, getModelRuntime } from '../models';\nimport {\n  type ChromeRecordedEvent,\n  type EventCounts,\n  type EventSummary,\n  type FilteredEvents,\n  type InputDescription,\n  type ProcessedEvent,\n  type RecorderGenerationContext,\n  type RecorderGenerationInput,\n  type RecorderGenerationOptions,\n  createEventCounts,\n  createMessageContent,\n  extractInputDescriptions,\n  filterEventsByType,\n  getScreenshotsForLLM,\n  prepareEventSummary,\n  prepareRecorderGenerationContext,\n  processEventsForLLM,\n  validateEvents,\n} from './recorder-generation-common';\n\nexport type YamlGenerationOptions = RecorderGenerationOptions;\nexport type RecorderYamlGenerationInput = RecorderGenerationInput;\n\nexport type {\n  ChromeRecordedEvent,\n  EventCounts,\n  EventSummary,\n  FilteredEvents,\n  InputDescription,\n  ProcessedEvent,\n  RecorderGenerationContext,\n};\n\nexport {\n  createEventCounts,\n  createMessageContent,\n  extractInputDescriptions,\n  filterEventsByType,\n  getScreenshotsForLLM,\n  prepareEventSummary,\n  prepareRecorderGenerationContext,\n  processEventsForLLM,\n  validateEvents,\n};\n\nconst getYamlLanguageInstruction = (language?: string) => {\n  const normalizedLanguage = language?.trim();\n  if (!normalizedLanguage) {\n    return '';\n  }\n\n  return `\nLanguage requirement:\n- Write all human-readable YAML content in ${normalizedLanguage}.\n- Keep YAML keys, field names, and Midscene API names unchanged.`;\n};\n\nconst createYamlPrompt = ({\n  yamlSummary,\n  screenshotAssets,\n  language,\n  targetBlock,\n  target,\n}: {\n  yamlSummary: EventSummary & { includeTimestamps: boolean };\n  screenshotAssets: MidsceneRecorderMarkdownScreenshotAsset[];\n  language?: string;\n  targetBlock: string;\n  target: MidsceneRecorderTarget;\n}): ChatCompletionMessageParam[] => {\n  const prompt: ChatCompletionMessageParam[] = [\n    {\n      role: 'system',\n      content: `You are an expert in Midscene.js YAML test generation. Generate clean, accurate YAML following these rules: ${YAML_EXAMPLE_CODE}`,\n    },\n    {\n      role: 'user',\n      content: `Generate YAML test for Midscene.js automation from recorded events.\n\nTarget platform:\n- Preserve this exact top-level target platform: ${target.platformId}\n- Use exactly one top-level target block.\n- The target block must be:\n${targetBlock}\n\nEvent Summary:\n${JSON.stringify(yamlSummary, null, 2)}\n\nScreenshot assets:\n${JSON.stringify(\n  screenshotAssets.map((asset) => ({\n    eventIndex: asset.eventIndex,\n    eventHashId: asset.eventHashId,\n    eventType: asset.eventType,\n    relativePath: asset.relativePath,\n    description: yamlSummary.events[asset.eventIndex]?.description,\n  })),\n  null,\n  2,\n)}\n\nConvert events:\n- navigation → target URL or aiAction only when the target platform supports it\n- click → aiTap with the semantic element description\n- input → aiInput with value and semantic locate\n- scroll → aiScroll with appropriate direction and semantic scroll area\n- keydown → aiKeyboardPress\n- Add aiAssert for important state changes\n- Prefer event.semantic.replayInstruction and event.semantic.elementDescription when event.semantic.source is \"aiDescribe\" or \"recorderAI\" and event.semantic.status is \"ready\".\n- If event.semantic.source is \"heuristic\" or event.semantic.status is \"pending\"/\"failed\", use the screenshot/context to write the best visual instruction, and avoid raw coordinates unless there is no reliable semantic description.\n- Screenshot assets are context only. Use their eventIndex/eventHashId relationship to understand the matching event, but do not include screenshot file paths in the YAML unless the Midscene YAML API explicitly needs them.${getYamlLanguageInstruction(language)}\n\nImportant: Return ONLY the raw YAML content. Do NOT wrap the response in markdown code blocks (no \\`\\`\\`yaml or \\`\\`\\`). Start directly with the YAML content.`,\n    },\n  ];\n\n  if (screenshotAssets.length > 0) {\n    prompt.push({\n      role: 'user',\n      content:\n        'Here are screenshots from the recording session to help you understand the context:',\n    });\n\n    prompt.push({\n      role: 'user',\n      content: screenshotAssets.flatMap((asset) => [\n        {\n          type: 'text',\n          text: `Screenshot asset for event #${asset.eventIndex + 1}: ${asset.relativePath}`,\n        },\n        {\n          type: 'image_url',\n          image_url: {\n            url: asset.dataUrl,\n          },\n        },\n      ]),\n    });\n  }\n\n  return prompt;\n};\n\nfunction createDefaultWebTarget(\n  events: ChromeRecordedEvent[],\n  options: YamlGenerationOptions,\n): MidsceneRecorderTarget {\n  const navigationEvents = events.filter(\n    (event) => event.type === 'navigation',\n  );\n  const firstUrl =\n    options.navigationInfo?.urls?.find(Boolean) ||\n    navigationEvents.find((event) => event.url)?.url ||\n    '';\n  const firstViewport =\n    options.navigationInfo?.initialViewport ||\n    events.find((event) => event.pageInfo)?.pageInfo;\n\n  return {\n    platformId: 'web',\n    deviceId: firstUrl || undefined,\n    label: firstUrl || 'Web',\n    values: {\n      url: firstUrl,\n      ...(firstViewport?.width ? { viewportWidth: firstViewport.width } : {}),\n      ...(firstViewport?.height\n        ? { viewportHeight: firstViewport.height }\n        : {}),\n    },\n  };\n}\n\nfunction normalizeGeneratedYaml(content: string) {\n  const trimmed = content.trim();\n  const fencedMatch = trimmed.match(/^```(?:ya?ml)?\\s*([\\s\\S]*?)\\s*```$/i);\n  return `${(fencedMatch?.[1] ?? trimmed).trim()}\\n`;\n}\n\nfunction resolveModelRuntime(model: IModelConfig | ModelRuntime): ModelRuntime {\n  if ('config' in model && 'adapter' in model) {\n    return model;\n  }\n  return getModelRuntime(model);\n}\n\nfunction createRecorderYamlPrompt(\n  input: RecorderYamlGenerationInput,\n): ChatCompletionMessageParam[] {\n  const { summary, screenshotAssets } = prepareRecorderGenerationContext(input);\n  const yamlSummary = {\n    ...summary,\n    target: input.target,\n    includeTimestamps: input.includeTimestamps || false,\n  };\n\n  return createYamlPrompt({\n    yamlSummary,\n    screenshotAssets,\n    language: input.language,\n    target: input.target,\n    targetBlock: stringifyMidsceneRecorderTargetBlock(input.target),\n  });\n}\n\nexport const generateRecorderYamlTest = async (\n  input: RecorderYamlGenerationInput,\n  model: IModelConfig | ModelRuntime,\n): Promise<string> => {\n  try {\n    const prompt = createRecorderYamlPrompt(input);\n    const response = await callAIWithStringResponse(\n      prompt,\n      resolveModelRuntime(model),\n    );\n\n    if (response?.content && typeof response.content === 'string') {\n      return normalizeGeneratedYaml(response.content);\n    }\n\n    throw new Error('Failed to generate recorder YAML test configuration');\n  } catch (error) {\n    throw new Error(`Failed to generate recorder YAML test: ${error}`);\n  }\n};\n\nexport const generateRecorderYamlTestStream = async (\n  input: RecorderYamlGenerationInput,\n  options: StreamingCodeGenerationOptions,\n  model: IModelConfig | ModelRuntime,\n): Promise<StreamingAIResponse> => {\n  try {\n    const prompt = createRecorderYamlPrompt(input);\n    const modelRuntime = resolveModelRuntime(model);\n    if (options.stream && options.onChunk) {\n      return await callAI(prompt, modelRuntime, {\n        stream: true,\n        onChunk: options.onChunk,\n      });\n    }\n\n    const response = await callAIWithStringResponse(prompt, modelRuntime);\n    if (response?.content && typeof response.content === 'string') {\n      return {\n        content: normalizeGeneratedYaml(response.content),\n        usage: response.usage,\n        isStreamed: false,\n      };\n    }\n\n    throw new Error('Failed to generate recorder YAML test configuration');\n  } catch (error) {\n    throw new Error(`Failed to generate recorder YAML test: ${error}`);\n  }\n};\n\n// YAML-specific generation functions\n\n/**\n * Generates YAML test configuration from recorded events using AI\n */\nexport const generateYamlTest = async (\n  events: ChromeRecordedEvent[],\n  options: YamlGenerationOptions,\n  model: IModelConfig | ModelRuntime,\n): Promise<string> => {\n  return generateRecorderYamlTest(\n    {\n      ...options,\n      target: createDefaultWebTarget(events, options),\n      events,\n    },\n    model,\n  );\n};\n\n/**\n * Generates YAML test configuration from recorded events using AI with streaming support\n */\nexport const generateYamlTestStream = async (\n  events: ChromeRecordedEvent[],\n  options: YamlGenerationOptions & StreamingCodeGenerationOptions,\n  model: IModelConfig | ModelRuntime,\n): Promise<StreamingAIResponse> => {\n  return generateRecorderYamlTestStream(\n    {\n      ...options,\n      target: createDefaultWebTarget(events, options),\n      events,\n    },\n    options,\n    model,\n  );\n};\n"],"names":["getYamlLanguageInstruction","language","normalizedLanguage","createYamlPrompt","yamlSummary","screenshotAssets","targetBlock","target","prompt","YAML_EXAMPLE_CODE","JSON","asset","createDefaultWebTarget","events","options","navigationEvents","event","firstUrl","Boolean","firstViewport","undefined","normalizeGeneratedYaml","content","trimmed","fencedMatch","resolveModelRuntime","model","getModelRuntime","createRecorderYamlPrompt","input","summary","prepareRecorderGenerationContext","stringifyMidsceneRecorderTargetBlock","generateRecorderYamlTest","response","callAIWithStringResponse","Error","error","generateRecorderYamlTestStream","modelRuntime","callAI","generateYamlTest","generateYamlTestStream"],"mappings":"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"}