@midscene/core 1.9.6 → 1.9.7

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 (69) hide show
  1. package/dist/es/agent/agent.mjs +40 -8
  2. package/dist/es/agent/agent.mjs.map +1 -1
  3. package/dist/es/agent/tasks.mjs +3 -3
  4. package/dist/es/agent/tasks.mjs.map +1 -1
  5. package/dist/es/agent/utils.mjs +18 -3
  6. package/dist/es/agent/utils.mjs.map +1 -1
  7. package/dist/es/ai-model/prompt/describe.mjs +10 -2
  8. package/dist/es/ai-model/prompt/describe.mjs.map +1 -1
  9. package/dist/es/ai-model/prompt/markdown-generator.mjs +150 -40
  10. package/dist/es/ai-model/prompt/markdown-generator.mjs.map +1 -1
  11. package/dist/es/ai-model/prompt/recorder-generation-common.mjs +74 -14
  12. package/dist/es/ai-model/prompt/recorder-generation-common.mjs.map +1 -1
  13. package/dist/es/ai-model/prompt/recorder-metadata-generator.mjs +3 -5
  14. package/dist/es/ai-model/prompt/recorder-metadata-generator.mjs.map +1 -1
  15. package/dist/es/ai-model/prompt/recorder-ui-describer.mjs +10 -6
  16. package/dist/es/ai-model/prompt/recorder-ui-describer.mjs.map +1 -1
  17. package/dist/es/ai-model/prompt/yaml-generator.mjs +2 -2
  18. package/dist/es/ai-model/prompt/yaml-generator.mjs.map +1 -1
  19. package/dist/es/ai-model/service-caller/index.mjs +33 -3
  20. package/dist/es/ai-model/service-caller/index.mjs.map +1 -1
  21. package/dist/es/device/index.mjs.map +1 -1
  22. package/dist/es/recorder-ui-describer.mjs +33 -84
  23. package/dist/es/recorder-ui-describer.mjs.map +1 -1
  24. package/dist/es/service/index.mjs +11 -3
  25. package/dist/es/service/index.mjs.map +1 -1
  26. package/dist/es/service/utils.mjs +50 -1
  27. package/dist/es/service/utils.mjs.map +1 -1
  28. package/dist/es/types.mjs.map +1 -1
  29. package/dist/es/utils.mjs +2 -2
  30. package/dist/lib/agent/agent.js +39 -7
  31. package/dist/lib/agent/agent.js.map +1 -1
  32. package/dist/lib/agent/tasks.js +3 -3
  33. package/dist/lib/agent/tasks.js.map +1 -1
  34. package/dist/lib/agent/utils.js +20 -2
  35. package/dist/lib/agent/utils.js.map +1 -1
  36. package/dist/lib/ai-model/prompt/describe.js +10 -2
  37. package/dist/lib/ai-model/prompt/describe.js.map +1 -1
  38. package/dist/lib/ai-model/prompt/markdown-generator.js +150 -40
  39. package/dist/lib/ai-model/prompt/markdown-generator.js.map +1 -1
  40. package/dist/lib/ai-model/prompt/recorder-generation-common.js +75 -12
  41. package/dist/lib/ai-model/prompt/recorder-generation-common.js.map +1 -1
  42. package/dist/lib/ai-model/prompt/recorder-metadata-generator.js +2 -4
  43. package/dist/lib/ai-model/prompt/recorder-metadata-generator.js.map +1 -1
  44. package/dist/lib/ai-model/prompt/recorder-ui-describer.js +10 -6
  45. package/dist/lib/ai-model/prompt/recorder-ui-describer.js.map +1 -1
  46. package/dist/lib/ai-model/prompt/yaml-generator.js +2 -2
  47. package/dist/lib/ai-model/prompt/yaml-generator.js.map +1 -1
  48. package/dist/lib/ai-model/service-caller/index.js +33 -3
  49. package/dist/lib/ai-model/service-caller/index.js.map +1 -1
  50. package/dist/lib/device/index.js.map +1 -1
  51. package/dist/lib/recorder-ui-describer.js +33 -84
  52. package/dist/lib/recorder-ui-describer.js.map +1 -1
  53. package/dist/lib/service/index.js +10 -2
  54. package/dist/lib/service/index.js.map +1 -1
  55. package/dist/lib/service/utils.js +53 -1
  56. package/dist/lib/service/utils.js.map +1 -1
  57. package/dist/lib/types.js.map +1 -1
  58. package/dist/lib/utils.js +2 -2
  59. package/dist/types/agent/agent.d.ts +17 -6
  60. package/dist/types/agent/index.d.ts +1 -1
  61. package/dist/types/agent/tasks.d.ts +4 -2
  62. package/dist/types/agent/utils.d.ts +4 -1
  63. package/dist/types/ai-model/prompt/recorder-generation-common.d.ts +11 -7
  64. package/dist/types/ai-model/prompt/recorder-ui-describer.d.ts +1 -1
  65. package/dist/types/device/index.d.ts +6 -0
  66. package/dist/types/service/index.d.ts +1 -0
  67. package/dist/types/service/utils.d.ts +2 -0
  68. package/dist/types/types.d.ts +1 -0
  69. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"ai-model/prompt/recorder-ui-describer.mjs","sources":["../../../../src/ai-model/prompt/recorder-ui-describer.ts"],"sourcesContent":["export const RECORDER_UI_DESCRIBER_SYSTEM_PROMPT = `You convert Studio preview recorder UI events into semantic replay instructions.\n\nThe recorder works from screenshots and mapped real-device coordinates only. Infer stable UI intent from the highlighted BEFORE screenshot. The AFTER screenshot is contextual evidence for state changes and scroll destinations.\n\nOutput JSON only:\n{\n \"elementDescription\": \"short stable target/region description\",\n \"replayInstruction\": \"one executable natural-language replay step\",\n \"actionSummary\": \"short timeline summary\",\n \"scrollDestinationDescription\": \"for scroll only: concrete newly visible destination content or goal\",\n \"confidence\": \"high\" | \"medium\" | \"low\",\n \"error\"?: \"only if no useful visual description can be inferred\"\n}\n\nRules:\n- Do NOT output coordinates as the main description.\n- Do NOT mention \"near coordinates\", \"nearby element\", \"near point\", \"red marker\", highlighted box, highlighted element, or screenshot.\n- Prefer stable target descriptions in this order: exact visible text > label/placeholder > role + stable section/context > icon purpose > visual position.\n- Keep quoted UI text in the original UI language, for example \"使用文档\" or \"开始使用\".\n- Apply the platform guidance from the user event:\n - Web: button, input, link, menu item, tab, dialog, aria-label, placeholder, form section.\n - Mobile: tab, list item, text field, icon button, navigation bar, bottom bar, sheet, card, screen section.\n - Desktop/computer: menu item, toolbar button, dialog field, sidebar item, window control, file row, application region.\n- Pointer action rules:\n - Preserve event.actionType semantics. Tap, DoubleClick, LongPress, and RightClick must not all become Click.\n - Tap replayInstruction format: Tap on the element described as \"<elementDescription>\".\n - DoubleClick replayInstruction format: Double click on the element described as \"<elementDescription>\".\n - LongPress replayInstruction format: Long press the element described as \"<elementDescription>\".\n - RightClick replayInstruction format: Right click on the element described as \"<elementDescription>\".\n - Click replayInstruction format: Click on the element described as \"<elementDescription>\".\n- Input-specific rules:\n - The highlighted BEFORE screenshot marks the field that receives the text.\n - The screenshot after the action may show the typed value; use it only to confirm the field, never as the field description.\n - elementDescription must identify the field itself, for example \"年龄 input in the basic form\" or \"search input in the top navigation\".\n - Never use \"AI is analyzing element\", the typed value, or a generic \"input field\" as elementDescription.\n - Input replayInstruction format: Input \"<value>\" into the element described as \"<elementDescription>\".\n- Scroll target quality bar:\n - elementDescription describes the scrollable page, panel, list, table, or section.\n - scrollDestinationDescription is required and describes what the scroll is trying to reveal or reach, using newly visible headings, section titles, list items, or stable content from the AFTER screenshot.\n - Prefer descriptions like \"集成到 Playwright - Midscene - Vision-Driven UI Automation page, scrolling toward the API reference section\" or \"Android API documentation page, scrolling to the installation steps section\".\n - Do NOT write generic phrases like \"more content\", \"the page\", \"current screen\", or \"main scrollable area\".\n- Scroll replayInstruction format: Scroll the page/region with description \"<elementDescription>\" by value \"<recorded value>\" until \"<scrollDestinationDescription>\" is visible.\n- Scroll actionSummary format: Scroll <elementDescription> toward <scrollDestinationDescription>.\n- Drag/Swipe rules:\n - Drag replayInstruction format: Drag through the area described as \"<elementDescription>\".\n - Swipe replayInstruction format: Swipe through the area described as \"<elementDescription>\".\n - Describe start/end regions or the dragged UI control; do not describe only the gesture path.\n- KeyboardPress replayInstruction format: Press \"<value>\" on the element described as \"<elementDescription>\".\n- If uncertain, provide the best concrete visible text/role/context description. Set confidence to \"low\"; do not fall back to coordinates.`;\n"],"names":["RECORDER_UI_DESCRIBER_SYSTEM_PROMPT"],"mappings":"AAAO,MAAMA,sCAAsC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;0IAgDsF,CAAC"}
1
+ {"version":3,"file":"ai-model/prompt/recorder-ui-describer.mjs","sources":["../../../../src/ai-model/prompt/recorder-ui-describer.ts"],"sourcesContent":["export const RECORDER_UI_DESCRIBER_SYSTEM_PROMPT = `You convert Studio preview recorder UI events into semantic replay instructions.\n\nThe recorder works from screenshots and mapped real-device coordinates only. Infer stable UI intent from the highlighted BEFORE screenshot. The AFTER screenshot is contextual evidence for state changes and scroll destinations.\n\nOutput JSON only:\n{\n \"elementDescription\": \"short stable target/region description\",\n \"replayInstruction\": \"one executable natural-language replay step\",\n \"actionSummary\": \"short timeline summary\",\n \"scrollDestinationDescription\": \"for scroll only: concrete newly visible destination content or goal\",\n \"confidence\": \"high\" | \"medium\" | \"low\",\n \"error\"?: \"only if no useful visual description can be inferred\"\n}\n\nRules:\n- Do NOT output coordinates as the main description.\n- Do NOT mention \"near coordinates\", \"nearby element\", \"near point\", \"red marker\", highlighted box, highlighted element, or screenshot.\n- Prefer stable target descriptions in this order: exact stable control text > stable label > role + stable section/context > icon purpose > visual position.\n- Treat placeholder or hint text that can change by user, time, data, or context as dynamic. Do not use dynamic hint values as the primary target description; prefer role + stable region + intent.\n- For repeated collections, treat item identity text as dynamic unless the user is clearly verifying that exact item. This includes any list/grid/table/feed/menu or repeated record surface. Do not output descriptions like \"<role> titled/named '<content>'\"; prefer stable role + region + selection intent.\n- Keep quoted UI text in the original UI language when it is a stable control label.\n- Apply the platform guidance from the user event:\n - Web: button, input, link, menu item, tab, dialog, aria-label, placeholder, form section.\n - Mobile: tab, list item, text field, icon button, navigation bar, bottom bar, sheet, card, screen section.\n - Desktop/computer: menu item, toolbar button, dialog field, sidebar item, window control, file row, application region.\n- Pointer action rules:\n - Preserve event.actionType semantics. Tap, DoubleClick, LongPress, and RightClick must not all become Click.\n - Tap replayInstruction format: Tap on the element described as \"<elementDescription>\".\n - DoubleClick replayInstruction format: Double click on the element described as \"<elementDescription>\".\n - LongPress replayInstruction format: Long press the element described as \"<elementDescription>\".\n - RightClick replayInstruction format: Right click on the element described as \"<elementDescription>\".\n - Click replayInstruction format: Click on the element described as \"<elementDescription>\".\n- Input-specific rules:\n - The highlighted BEFORE screenshot marks the field that receives the text.\n - The screenshot after the action may show the typed value; use it only to confirm the field, never as the field description.\n - elementDescription must identify the field itself by stable field role, nearby label, region, section, or sequence intent.\n - Never use \"AI is analyzing element\", the typed value, the page title alone, or a generic \"input field\" as elementDescription.\n - For consecutive input events, distinguish fields by stable role, section, order, current focus, filled/empty state, and neighboring actions instead of reusing the same generic field description.\n - Input replayInstruction format: Input \"<value>\" into the element described as \"<elementDescription>\".\n- Scroll target quality bar:\n - elementDescription describes the scrollable page, panel, list, table, or section at the highlighted scroll point.\n - When multiple scrollable regions are visible, preserve the specific region where the scroll happened, such as left/right/top/bottom panel, navigation area, content pane, dialog body, table, list, or menu. Do not generalize a panel/list scroll into the whole page.\n - scrollDestinationDescription is required and describes what the scroll is trying to reveal or reach, using newly visible headings, section titles, list items, or stable content from the AFTER screenshot.\n - Prefer descriptions like \"Playwright integration documentation page, scrolling toward the API reference section\" or \"Android API documentation page, scrolling to the installation steps section\".\n - Do NOT write generic phrases like \"more content\", \"the page\", \"current screen\", or \"main scrollable area\".\n- Scroll replayInstruction format: Scroll the page/region with description \"<elementDescription>\" by value \"<recorded value>\" until \"<scrollDestinationDescription>\" is visible.\n- Scroll actionSummary format: Scroll <elementDescription> toward <scrollDestinationDescription>.\n- Drag/Swipe rules:\n - Drag replayInstruction format: Drag through the area described as \"<elementDescription>\".\n - Swipe replayInstruction format: Swipe through the area described as \"<elementDescription>\".\n - Describe start/end regions or the dragged UI control; do not describe only the gesture path.\n- KeyboardPress replayInstruction format: Press \"<value>\" on the element described as \"<elementDescription>\".\n- If uncertain, provide the best concrete visible text/role/context description. Set confidence to \"low\"; do not fall back to coordinates.`;\n"],"names":["RECORDER_UI_DESCRIBER_SYSTEM_PROMPT"],"mappings":"AAAO,MAAMA,sCAAsC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;0IAoDsF,CAAC"}
@@ -46,8 +46,8 @@ Convert events:
46
46
  - scroll → aiScroll with appropriate direction and semantic scroll area
47
47
  - keydown → aiKeyboardPress
48
48
  - Add aiAssert for important state changes
49
- - Prefer event.replayInstruction and event.elementDescription when descriptionSource is "ai".
50
- - If descriptionSource is "fallback", use the screenshot/context to write the best visual instruction, and avoid raw coordinates unless there is no reliable semantic description.
49
+ - Prefer event.semantic.replayInstruction and event.semantic.elementDescription when event.semantic.source is "aiDescribe" or "recorderAI" and event.semantic.status is "ready".
50
+ - 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.
51
51
  - 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)}
52
52
 
53
53
  Important: Return ONLY the raw YAML content. Do NOT wrap the response in markdown code blocks (no \`\`\`yaml or \`\`\`). Start directly with the YAML content.`
@@ -1 +1 @@
1
- {"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.replayInstruction and event.elementDescription when descriptionSource is \"ai\".\n- If descriptionSource is \"fallback\", 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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"}
1
+ {"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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"}
@@ -32,6 +32,28 @@ function stringifyForDebug(value) {
32
32
  return String(value);
33
33
  }
34
34
  }
35
+ function getErrorMessage(error) {
36
+ return error instanceof Error ? error.message : String(error);
37
+ }
38
+ function toError(error) {
39
+ return error instanceof Error ? error : new Error(String(error));
40
+ }
41
+ function normalizeRetryCount(retryCount) {
42
+ if ('number' != typeof retryCount || !Number.isFinite(retryCount)) return 1;
43
+ return Math.max(0, Math.floor(retryCount));
44
+ }
45
+ function appendAIRequestFailureSummary(error, attemptErrors, maxAttempts) {
46
+ const failedAttempts = attemptErrors.length;
47
+ const retries = Math.max(0, failedAttempts - 1);
48
+ const retryLabel = 1 === retries ? 'retry' : 'retries';
49
+ const originalMessage = error.message;
50
+ const previousAttemptErrors = attemptErrors.slice(0, -1);
51
+ error.message = `AI model request failed after ${retries} ${retryLabel} (${failedAttempts}/${maxAttempts} attempts). Last error: ${originalMessage}`;
52
+ if (0 === previousAttemptErrors.length) return error;
53
+ const details = previousAttemptErrors.map(({ attempt, error })=>`Attempt ${attempt}: ${getErrorMessage(error)}`).join('\n');
54
+ error.message = `${error.message}\nPrevious AI call attempt errors:\n${details}`;
55
+ return error;
56
+ }
35
57
  async function createChatClient({ modelConfig }) {
36
58
  const { socksProxy, httpProxy, modelName, openaiBaseURL, openaiApiKey, openaiExtraConfig, modelDescription, modelFamily, createOpenAIClient, timeout } = modelConfig;
37
59
  let proxyAgent;
@@ -288,10 +310,11 @@ async function callAI(messages, modelRuntime, options) {
288
310
  content = accumulated;
289
311
  debugProfileStats(`streaming model, ${modelName}, mode, ${modelFamily || 'default'}, cost-ms, ${timeCost}, temperature, ${temperature ?? ''}`);
290
312
  } else {
291
- const retryCount = modelConfig.retryCount ?? 1;
313
+ const retryCount = normalizeRetryCount(modelConfig.retryCount);
292
314
  const retryInterval = modelConfig.retryInterval ?? 2000;
293
315
  const maxAttempts = retryCount + 1;
294
316
  let lastError;
317
+ const attemptErrors = [];
295
318
  for(let attempt = 1; attempt <= maxAttempts; attempt++){
296
319
  const { signal: attemptSignal, cleanup: cleanupAttemptSignal } = buildRequestAbortSignal(effectiveTimeoutMs, options?.abortSignal);
297
320
  try {
@@ -321,7 +344,11 @@ async function callAI(messages, modelRuntime, options) {
321
344
  if (!hasUsableText(content)) throw new AIResponseParseError('empty content from AI model', JSON.stringify(result), buildUsageInfo(usage, requestId), rawChoiceMessage);
322
345
  break;
323
346
  } catch (error) {
324
- lastError = error;
347
+ lastError = toError(error);
348
+ attemptErrors.push({
349
+ attempt,
350
+ error
351
+ });
325
352
  const wasHardTimeout = isHardTimeoutError(lastError);
326
353
  if (wasHardTimeout) warnCall(`AI call hit hard timeout (${effectiveTimeoutMs}ms, attempt ${attempt}/${maxAttempts}, model ${modelName}, slot ${modelConfig.slot})`);
327
354
  if (options?.abortSignal?.aborted) break;
@@ -333,7 +360,10 @@ async function callAI(messages, modelRuntime, options) {
333
360
  cleanupAttemptSignal();
334
361
  }
335
362
  }
336
- if (!content) throw lastError;
363
+ if (!content) {
364
+ assert(lastError, 'AI model request failed without recording an attempt error');
365
+ throw appendAIRequestFailureSummary(lastError, attemptErrors, maxAttempts);
366
+ }
337
367
  }
338
368
  debugCall(`response reasoning content: ${accumulatedReasoning}`);
339
369
  debugCall(`response content: ${content}`);
@@ -1 +1 @@
1
- {"version":3,"file":"ai-model/service-caller/index.mjs","sources":["../../../../src/ai-model/service-caller/index.ts"],"sourcesContent":["import type { AIUsageInfo } from '@/types';\nimport type { CodeGenerationChunk, StreamingCallback } from '@/types';\n\n// Error class that preserves usage and rawResponse when AI call parsing fails\nexport class AIResponseParseError extends Error {\n usage?: AIUsageInfo;\n /**\n * Adapter-extracted content used by Midscene for parsing. This is not the\n * full provider response or choices[0].message.\n */\n rawResponse: string;\n rawChoiceMessage?: unknown;\n\n constructor(\n message: string,\n rawResponse: string,\n usage?: AIUsageInfo,\n rawChoiceMessage?: unknown,\n ) {\n super(message);\n this.name = 'AIResponseParseError';\n this.rawResponse = rawResponse;\n this.usage = usage;\n this.rawChoiceMessage = rawChoiceMessage;\n }\n}\nimport {\n type IModelConfig,\n MIDSCENE_LANGFUSE_DEBUG,\n MIDSCENE_LANGSMITH_DEBUG,\n type TModelFamily,\n globalConfigManager,\n} from '@midscene/shared/env';\n\nimport { getDebug } from '@midscene/shared/logger';\nimport { assert, ifInBrowser } from '@midscene/shared/utils';\nimport OpenAI from 'openai';\nimport type { ChatCompletionMessageParam } from 'openai/resources/index';\nimport type { Stream } from 'openai/streaming';\nimport { type ModelRuntime, getModelRuntime } from '../models';\nimport type { AIArgs } from '../types';\nimport {\n callAIWithCodexAppServer,\n isCodexAppServerProvider,\n} from './codex-app-server';\nimport type { JsonParserSource } from './json';\nimport {\n buildRequestAbortSignal,\n isHardTimeoutError,\n resolveEffectiveTimeoutMs,\n} from './request-timeout';\nexport {\n extractJSONFromCodeBlock,\n normalJsonParser,\n safeParseJson,\n} from './json';\nexport type { JsonParser } from './json';\n\nfunction stringifyForDebug(value: unknown): string {\n try {\n return JSON.stringify(value);\n } catch (_error) {\n return String(value);\n }\n}\n\nexport async function createChatClient({\n modelConfig,\n}: {\n modelConfig: IModelConfig;\n}): Promise<{\n completion: OpenAI.Chat.Completions;\n modelName: string;\n modelDescription: string;\n modelFamily: TModelFamily | undefined;\n}> {\n const {\n socksProxy,\n httpProxy,\n modelName,\n openaiBaseURL,\n openaiApiKey,\n openaiExtraConfig,\n modelDescription,\n modelFamily,\n createOpenAIClient,\n timeout,\n } = modelConfig;\n\n let proxyAgent: any = undefined;\n const warnClient = getDebug('ai:call', { console: true });\n const debugProxy = getDebug('ai:call:proxy');\n const warnProxy = getDebug('ai:call:proxy', { console: true });\n\n // Helper function to sanitize proxy URL for logging (remove credentials)\n // Uses URL API instead of regex to avoid ReDoS vulnerabilities\n const sanitizeProxyUrl = (url: string): string => {\n try {\n const parsed = new URL(url);\n if (parsed.username) {\n // Keep username for debugging, hide password for security\n parsed.password = '****';\n return parsed.href;\n }\n return url;\n } catch {\n // If URL parsing fails, return original URL (will be caught later)\n return url;\n }\n };\n\n if (httpProxy) {\n debugProxy('using http proxy', sanitizeProxyUrl(httpProxy));\n if (ifInBrowser) {\n warnProxy(\n 'HTTP proxy is configured but not supported in browser environment',\n );\n } else {\n // Dynamic import with variable to avoid bundler static analysis\n const moduleName = 'undici';\n const { ProxyAgent } = await import(moduleName);\n proxyAgent = new ProxyAgent({\n uri: httpProxy,\n // Note: authentication is handled via the URI (e.g., http://user:pass@proxy.com:8080)\n });\n }\n } else if (socksProxy) {\n debugProxy('using socks proxy', sanitizeProxyUrl(socksProxy));\n if (ifInBrowser) {\n warnProxy(\n 'SOCKS proxy is configured but not supported in browser environment',\n );\n } else {\n try {\n // Dynamic import with variable to avoid bundler static analysis\n const moduleName = 'fetch-socks';\n const { socksDispatcher } = await import(moduleName);\n // Parse SOCKS proxy URL (e.g., socks5://127.0.0.1:1080)\n const proxyUrl = new URL(socksProxy);\n\n // Validate hostname\n if (!proxyUrl.hostname) {\n throw new Error('SOCKS proxy URL must include a valid hostname');\n }\n\n // Validate and parse port\n const port = Number.parseInt(proxyUrl.port, 10);\n if (!proxyUrl.port || Number.isNaN(port)) {\n throw new Error('SOCKS proxy URL must include a valid port');\n }\n\n // Parse SOCKS version from protocol\n const protocol = proxyUrl.protocol.replace(':', '');\n const socksType =\n protocol === 'socks4' ? 4 : protocol === 'socks5' ? 5 : 5;\n\n proxyAgent = socksDispatcher({\n type: socksType,\n host: proxyUrl.hostname,\n port,\n ...(proxyUrl.username\n ? {\n userId: decodeURIComponent(proxyUrl.username),\n password: decodeURIComponent(proxyUrl.password || ''),\n }\n : {}),\n });\n debugProxy('socks proxy configured successfully', {\n type: socksType,\n host: proxyUrl.hostname,\n port: port,\n });\n } catch (error) {\n warnProxy('Failed to configure SOCKS proxy:', error);\n throw new Error(\n `Invalid SOCKS proxy URL: ${socksProxy}. Expected format: socks4://host:port, socks5://host:port, or with authentication: socks5://user:pass@host:port`,\n );\n }\n }\n }\n\n const effectiveTimeoutMs = resolveEffectiveTimeoutMs({ timeout });\n const openAIOptions = {\n baseURL: openaiBaseURL,\n apiKey: openaiApiKey,\n // Use fetchOptions.dispatcher for fetch-based SDK instead of httpAgent\n // Note: Type assertion needed due to undici version mismatch between dependencies\n ...(proxyAgent ? { fetchOptions: { dispatcher: proxyAgent as any } } : {}),\n ...openaiExtraConfig,\n // Midscene already handles retries in callAI(), so disable SDK-level retries\n // to avoid duplicate attempts and duplicated backoff latency.\n maxRetries: 0,\n // When disabled (timeoutMs === null) fall through to the SDK default so\n // only the caller-provided abortSignal can cancel the request.\n ...(effectiveTimeoutMs !== null ? { timeout: effectiveTimeoutMs } : {}),\n dangerouslyAllowBrowser: true,\n };\n\n const baseOpenAI = new OpenAI(openAIOptions);\n\n let openai: OpenAI = baseOpenAI;\n\n // LangSmith wrapper\n if (\n openai &&\n globalConfigManager.getEnvConfigInBoolean(MIDSCENE_LANGSMITH_DEBUG)\n ) {\n if (ifInBrowser) {\n throw new Error('langsmith is not supported in browser');\n }\n warnClient('DEBUGGING MODE: langsmith wrapper enabled');\n // Use variable to prevent static analysis by bundlers\n const langsmithModule = 'langsmith/wrappers';\n const { wrapOpenAI } = await import(langsmithModule);\n openai = wrapOpenAI(openai);\n }\n\n // Langfuse wrapper\n if (\n openai &&\n globalConfigManager.getEnvConfigInBoolean(MIDSCENE_LANGFUSE_DEBUG)\n ) {\n if (ifInBrowser) {\n throw new Error('langfuse is not supported in browser');\n }\n warnClient('DEBUGGING MODE: langfuse wrapper enabled');\n // Use variable to prevent static analysis by bundlers\n const langfuseModule = '@langfuse/openai';\n const { observeOpenAI } = await import(langfuseModule);\n openai = observeOpenAI(openai);\n }\n\n if (createOpenAIClient) {\n const wrappedClient = await createOpenAIClient(baseOpenAI, openAIOptions);\n\n if (wrappedClient) {\n openai = wrappedClient as OpenAI;\n }\n }\n\n return {\n completion: openai.chat.completions,\n modelName,\n modelDescription,\n modelFamily,\n };\n}\n\nexport async function callAI(\n messages: ChatCompletionMessageParam[],\n modelRuntime: ModelRuntime,\n options?: {\n stream?: boolean;\n onChunk?: StreamingCallback;\n abortSignal?: AbortSignal;\n requiresOriginalImageDetail?: boolean;\n },\n): Promise<{\n content: string;\n reasoning_content?: string;\n rawChoiceMessage?: unknown;\n usage?: AIUsageInfo;\n isStreamed: boolean;\n}> {\n const { config: modelConfig, adapter } = modelRuntime;\n\n if (isCodexAppServerProvider(modelConfig.openaiBaseURL)) {\n return callAIWithCodexAppServer(messages, modelConfig, {\n stream: options?.stream,\n onChunk: options?.onChunk,\n reasoningEnabled: modelConfig.reasoningEnabled,\n abortSignal: options?.abortSignal,\n });\n }\n\n const { completion, modelName, modelDescription, modelFamily } =\n await createChatClient({\n modelConfig,\n });\n const effectiveTimeoutMs = resolveEffectiveTimeoutMs(modelConfig);\n\n const extraBody = modelConfig.extraBody;\n\n const debugCall = getDebug('ai:call');\n const warnCall = getDebug('ai:call', { console: true });\n const debugProfileStats = getDebug('ai:profile:stats');\n const debugProfileDetail = getDebug('ai:profile:detail');\n\n const startTime = Date.now();\n\n const isStreaming = options?.stream && options?.onChunk;\n const chatCompletionInput = {\n intent: modelConfig.intent,\n userConfig: {\n temperature: modelConfig.temperature,\n reasoningEnabled: modelConfig.reasoningEnabled,\n reasoningEffort: modelConfig.reasoningEffort,\n reasoningBudget: modelConfig.reasoningBudget,\n },\n requiresOriginalImageDetail: options?.requiresOriginalImageDetail,\n };\n const { config: adapterChatCompletionParams } =\n adapter.chatCompletion.buildChatCompletionParams(chatCompletionInput);\n debugCall(\n `adapter chat completion params: ${stringifyForDebug({\n config: adapterChatCompletionParams,\n })}`,\n );\n let content: string | undefined;\n let accumulated = '';\n let accumulatedReasoning = '';\n let rawChoiceMessage: unknown;\n let usage: OpenAI.CompletionUsage | undefined;\n let timeCost: number | undefined;\n let requestId: string | null | undefined;\n let responseModelName: string | undefined;\n\n const hasUsableText = (value: string | null | undefined): value is string =>\n typeof value === 'string' && value.trim().length > 0;\n\n const buildUsageInfo = (\n usageData?: OpenAI.CompletionUsage,\n requestId?: string | null,\n ) => {\n if (!usageData) return undefined;\n\n const cachedInputTokens = (\n usageData as { prompt_tokens_details?: { cached_tokens?: number } }\n )?.prompt_tokens_details?.cached_tokens;\n\n return {\n ...usageData,\n prompt_tokens: usageData.prompt_tokens ?? 0,\n completion_tokens: usageData.completion_tokens ?? 0,\n total_tokens: usageData.total_tokens ?? 0,\n cached_input: cachedInputTokens ?? 0,\n time_cost: timeCost ?? 0,\n model_name: modelName,\n model_description: modelDescription,\n response_model_name: responseModelName,\n slot: modelConfig.slot,\n // Agent task layers fill semantic intent after the raw model call.\n intent: undefined,\n request_id: requestId ?? undefined,\n } satisfies AIUsageInfo;\n };\n\n const requestConfig = {\n ...adapterChatCompletionParams,\n ...(extraBody ?? {}),\n };\n const temperature = requestConfig.temperature;\n\n const imageDetail =\n adapter.chatCompletion.resolveImageDetail(chatCompletionInput);\n\n // Some adapters request original image detail to preserve screenshot\n // resolution for localization-sensitive tasks.\n const messagesWithImageDetail: ChatCompletionMessageParam[] = (() => {\n if (!imageDetail) {\n return messages;\n }\n\n return messages.map((msg) => {\n if (!Array.isArray(msg.content)) {\n return msg;\n }\n\n const content = msg.content.map((part) => {\n if (part && part.type === 'image_url' && part.image_url?.url) {\n return {\n ...part,\n image_url: {\n ...part.image_url,\n detail: imageDetail,\n },\n };\n }\n return part;\n });\n\n return {\n ...msg,\n content,\n } as ChatCompletionMessageParam;\n });\n })();\n\n try {\n debugCall(\n `sending ${isStreaming ? 'streaming ' : ''}request to ${modelName}`,\n );\n\n if (isStreaming) {\n const { signal: streamSignal, cleanup: cleanupStreamSignal } =\n buildRequestAbortSignal(effectiveTimeoutMs, options?.abortSignal);\n try {\n const stream = (await completion.create(\n {\n model: modelName,\n messages: messagesWithImageDetail,\n ...requestConfig,\n stream: true,\n },\n {\n stream: true,\n signal: streamSignal,\n },\n )) as Stream<OpenAI.Chat.Completions.ChatCompletionChunk> & {\n _request_id?: string | null;\n };\n\n requestId = stream._request_id;\n\n for await (const chunk of stream) {\n const parsedChunk = adapter.chatCompletion.extractContentAndReasoning(\n chunk.choices?.[0]?.delta,\n );\n const content = parsedChunk.content || '';\n const reasoning_content = parsedChunk.reasoning_content || '';\n\n // Check for usage info in any chunk (OpenAI provides usage in separate chunks)\n if (chunk.usage) {\n usage = chunk.usage;\n }\n if (chunk.model) {\n responseModelName = chunk.model;\n }\n\n if (content || reasoning_content) {\n accumulated += content;\n accumulatedReasoning += reasoning_content;\n const chunkData: CodeGenerationChunk = {\n content,\n reasoning_content,\n accumulated,\n isComplete: false,\n usage: undefined,\n };\n options.onChunk!(chunkData);\n }\n\n // Check if stream is complete\n if (chunk.choices?.[0]?.finish_reason) {\n timeCost = Date.now() - startTime;\n\n // If usage is not available from the stream, provide a basic usage info\n if (!usage) {\n // Estimate token counts based on content length (rough approximation)\n const estimatedTokens = Math.max(\n 1,\n Math.floor(accumulated.length / 4),\n );\n usage = {\n prompt_tokens: estimatedTokens,\n completion_tokens: estimatedTokens,\n total_tokens: estimatedTokens * 2,\n };\n }\n\n // Send final chunk\n const finalChunk: CodeGenerationChunk = {\n content: '',\n accumulated,\n reasoning_content: '',\n isComplete: true,\n usage: buildUsageInfo(usage, requestId),\n };\n options.onChunk!(finalChunk);\n break;\n }\n }\n } finally {\n cleanupStreamSignal();\n }\n content = accumulated;\n debugProfileStats(\n `streaming model, ${modelName}, mode, ${modelFamily || 'default'}, cost-ms, ${timeCost}, temperature, ${temperature ?? ''}`,\n );\n } else {\n // Non-streaming with retry logic\n const retryCount = modelConfig.retryCount ?? 1;\n const retryInterval = modelConfig.retryInterval ?? 2000;\n const maxAttempts = retryCount + 1; // retryCount=1 means 2 total attempts (1 initial + 1 retry)\n\n let lastError: Error | undefined;\n\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n const { signal: attemptSignal, cleanup: cleanupAttemptSignal } =\n buildRequestAbortSignal(effectiveTimeoutMs, options?.abortSignal);\n try {\n const result = await completion.create(\n {\n model: modelName,\n messages: messagesWithImageDetail,\n ...requestConfig,\n stream: false,\n } as any,\n { signal: attemptSignal },\n );\n\n timeCost = Date.now() - startTime;\n\n debugProfileStats(\n `model, ${modelName}, mode, ${modelFamily || 'default'}, prompt-tokens, ${result.usage?.prompt_tokens || ''}, completion-tokens, ${result.usage?.completion_tokens || ''}, total-tokens, ${result.usage?.total_tokens || ''}, cost-ms, ${timeCost}, requestId, ${result._request_id || ''}, temperature, ${temperature ?? ''}`,\n );\n\n debugProfileDetail(\n `model usage detail: ${JSON.stringify(result.usage)}`,\n );\n\n if (!result.choices) {\n throw new Error(\n `invalid response from LLM service: ${JSON.stringify(result)}`,\n );\n }\n\n rawChoiceMessage = result.choices[0].message;\n const parsedMessage =\n adapter.chatCompletion.extractContentAndReasoning(\n result.choices[0].message,\n );\n content = parsedMessage.content;\n accumulatedReasoning = parsedMessage.reasoning_content;\n usage = result.usage;\n requestId = result._request_id;\n responseModelName = result.model;\n\n if (!hasUsableText(content) && hasUsableText(accumulatedReasoning)) {\n warnCall('empty content from AI model, using reasoning content');\n content = accumulatedReasoning;\n }\n\n if (!hasUsableText(content)) {\n throw new AIResponseParseError(\n 'empty content from AI model',\n JSON.stringify(result),\n buildUsageInfo(usage, requestId),\n rawChoiceMessage,\n );\n }\n\n break; // Success, exit retry loop\n } catch (error) {\n lastError = error as Error;\n const wasHardTimeout = isHardTimeoutError(lastError);\n if (wasHardTimeout) {\n warnCall(\n `AI call hit hard timeout (${effectiveTimeoutMs}ms, attempt ${attempt}/${maxAttempts}, model ${modelName}, slot ${modelConfig.slot})`,\n );\n }\n // Do not retry if the request was aborted by the caller\n if (options?.abortSignal?.aborted) {\n break;\n }\n if (attempt < maxAttempts) {\n warnCall(\n `AI call failed (attempt ${attempt}/${maxAttempts}), retrying in ${retryInterval}ms... Error: ${lastError.message}`,\n );\n await new Promise((resolve) => setTimeout(resolve, retryInterval));\n }\n } finally {\n cleanupAttemptSignal();\n }\n }\n\n if (!content) {\n throw lastError;\n }\n }\n\n debugCall(`response reasoning content: ${accumulatedReasoning}`);\n debugCall(`response content: ${content}`);\n\n // Ensure we always have usage info for streaming responses\n if (isStreaming && !usage) {\n // Estimate token counts based on content length (rough approximation)\n const estimatedTokens = Math.max(\n 1,\n Math.floor((content || '').length / 4),\n );\n usage = {\n prompt_tokens: estimatedTokens,\n completion_tokens: estimatedTokens,\n total_tokens: estimatedTokens * 2,\n } as OpenAI.CompletionUsage;\n }\n\n return {\n content: content || '',\n reasoning_content: accumulatedReasoning || undefined,\n rawChoiceMessage,\n usage: buildUsageInfo(usage, requestId),\n isStreamed: !!isStreaming,\n };\n } catch (e: any) {\n warnCall('call AI error', e);\n\n if (e instanceof AIResponseParseError) {\n throw e;\n }\n\n const newError = new Error(\n `failed to call ${isStreaming ? 'streaming ' : ''}AI model service (${modelName}): ${e.message}\\nTrouble shooting: https://midscenejs.com/model-provider.html`,\n {\n cause: e,\n },\n );\n throw newError;\n }\n}\n\nexport async function callAIWithObjectResponse<T>(\n messages: ChatCompletionMessageParam[],\n // Keep IModelConfig compatibility for midscene-example/connectivity-test/tests/connectivity.test.ts; internal workflow callers should pass ModelRuntime instead.\n model: IModelConfig | ModelRuntime,\n options?: {\n abortSignal?: AbortSignal;\n jsonParserSource?: JsonParserSource;\n },\n): Promise<{\n // TODO: `content` is a misleading name here because this is already the parsed object response. Consider renaming it to `object` or `data`.\n content: T;\n contentString: string;\n usage?: AIUsageInfo;\n reasoning_content?: string;\n rawChoiceMessage?: unknown;\n}> {\n const modelRuntime = resolveCompatibleModelRuntime(model);\n const { config: modelConfig, adapter } = modelRuntime;\n const response = await callAI(messages, modelRuntime, {\n abortSignal: options?.abortSignal,\n });\n assert(response, 'empty response');\n let jsonContent: unknown;\n try {\n jsonContent = adapter.jsonParser(response.content, {\n source: options?.jsonParserSource ?? 'generic-object',\n });\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n throw new AIResponseParseError(\n errorMessage,\n response.content,\n response.usage,\n );\n }\n if (typeof jsonContent !== 'object') {\n throw new AIResponseParseError(\n `failed to parse json response from model (${modelConfig.modelName}): ${response.content}`,\n response.content,\n response.usage,\n response.rawChoiceMessage,\n );\n }\n return {\n content: jsonContent as T,\n contentString: response.content,\n usage: response.usage,\n reasoning_content: response.reasoning_content,\n rawChoiceMessage: response.rawChoiceMessage,\n };\n}\n\nfunction resolveCompatibleModelRuntime(\n model: IModelConfig | ModelRuntime,\n): ModelRuntime {\n if ('config' in model && 'adapter' in model) {\n return model;\n }\n\n return getModelRuntime(model);\n}\n\nexport async function callAIWithStringResponse(\n msgs: AIArgs,\n modelRuntime: ModelRuntime,\n options?: {\n abortSignal?: AbortSignal;\n },\n): Promise<{\n content: string;\n usage?: AIUsageInfo;\n rawChoiceMessage?: unknown;\n}> {\n const { content, usage, rawChoiceMessage } = await callAI(\n msgs,\n modelRuntime,\n {\n abortSignal: options?.abortSignal,\n },\n );\n return { content, usage, rawChoiceMessage };\n}\n"],"names":["AIResponseParseError","Error","message","rawResponse","usage","rawChoiceMessage","stringifyForDebug","value","JSON","_error","String","createChatClient","modelConfig","socksProxy","httpProxy","modelName","openaiBaseURL","openaiApiKey","openaiExtraConfig","modelDescription","modelFamily","createOpenAIClient","timeout","proxyAgent","warnClient","getDebug","debugProxy","warnProxy","sanitizeProxyUrl","url","parsed","URL","ifInBrowser","moduleName","ProxyAgent","socksDispatcher","proxyUrl","port","Number","protocol","socksType","decodeURIComponent","error","effectiveTimeoutMs","resolveEffectiveTimeoutMs","openAIOptions","baseOpenAI","OpenAI","openai","globalConfigManager","MIDSCENE_LANGSMITH_DEBUG","langsmithModule","wrapOpenAI","MIDSCENE_LANGFUSE_DEBUG","langfuseModule","observeOpenAI","wrappedClient","callAI","messages","modelRuntime","options","adapter","isCodexAppServerProvider","callAIWithCodexAppServer","completion","extraBody","debugCall","warnCall","debugProfileStats","debugProfileDetail","startTime","Date","isStreaming","chatCompletionInput","adapterChatCompletionParams","content","accumulated","accumulatedReasoning","timeCost","requestId","responseModelName","hasUsableText","buildUsageInfo","usageData","cachedInputTokens","undefined","requestConfig","temperature","imageDetail","messagesWithImageDetail","msg","Array","part","streamSignal","cleanupStreamSignal","buildRequestAbortSignal","stream","chunk","parsedChunk","reasoning_content","chunkData","estimatedTokens","Math","finalChunk","retryCount","retryInterval","maxAttempts","lastError","attempt","attemptSignal","cleanupAttemptSignal","result","parsedMessage","wasHardTimeout","isHardTimeoutError","Promise","resolve","setTimeout","e","newError","callAIWithObjectResponse","model","resolveCompatibleModelRuntime","response","assert","jsonContent","errorMessage","getModelRuntime","callAIWithStringResponse","msgs"],"mappings":"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"}
1
+ {"version":3,"file":"ai-model/service-caller/index.mjs","sources":["../../../../src/ai-model/service-caller/index.ts"],"sourcesContent":["import type { AIUsageInfo } from '@/types';\nimport type { CodeGenerationChunk, StreamingCallback } from '@/types';\n\n// Error class that preserves usage and rawResponse when AI call parsing fails\nexport class AIResponseParseError extends Error {\n usage?: AIUsageInfo;\n /**\n * Adapter-extracted content used by Midscene for parsing. This is not the\n * full provider response or choices[0].message.\n */\n rawResponse: string;\n rawChoiceMessage?: unknown;\n\n constructor(\n message: string,\n rawResponse: string,\n usage?: AIUsageInfo,\n rawChoiceMessage?: unknown,\n ) {\n super(message);\n this.name = 'AIResponseParseError';\n this.rawResponse = rawResponse;\n this.usage = usage;\n this.rawChoiceMessage = rawChoiceMessage;\n }\n}\nimport {\n type IModelConfig,\n MIDSCENE_LANGFUSE_DEBUG,\n MIDSCENE_LANGSMITH_DEBUG,\n type TModelFamily,\n globalConfigManager,\n} from '@midscene/shared/env';\n\nimport { getDebug } from '@midscene/shared/logger';\nimport { assert, ifInBrowser } from '@midscene/shared/utils';\nimport OpenAI from 'openai';\nimport type { ChatCompletionMessageParam } from 'openai/resources/index';\nimport type { Stream } from 'openai/streaming';\nimport { type ModelRuntime, getModelRuntime } from '../models';\nimport type { AIArgs } from '../types';\nimport {\n callAIWithCodexAppServer,\n isCodexAppServerProvider,\n} from './codex-app-server';\nimport type { JsonParserSource } from './json';\nimport {\n buildRequestAbortSignal,\n isHardTimeoutError,\n resolveEffectiveTimeoutMs,\n} from './request-timeout';\nexport {\n extractJSONFromCodeBlock,\n normalJsonParser,\n safeParseJson,\n} from './json';\nexport type { JsonParser } from './json';\n\nfunction stringifyForDebug(value: unknown): string {\n try {\n return JSON.stringify(value);\n } catch (_error) {\n return String(value);\n }\n}\n\nfunction getErrorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nfunction toError(error: unknown): Error {\n return error instanceof Error ? error : new Error(String(error));\n}\n\nfunction normalizeRetryCount(retryCount: unknown): number {\n if (typeof retryCount !== 'number' || !Number.isFinite(retryCount)) {\n return 1;\n }\n\n return Math.max(0, Math.floor(retryCount));\n}\n\nfunction appendAIRequestFailureSummary<T extends Error>(\n error: T,\n attemptErrors: Array<{ attempt: number; error: unknown }>,\n maxAttempts: number,\n): T {\n const failedAttempts = attemptErrors.length;\n const retries = Math.max(0, failedAttempts - 1);\n const retryLabel = retries === 1 ? 'retry' : 'retries';\n const originalMessage = error.message;\n const previousAttemptErrors = attemptErrors.slice(0, -1);\n\n error.message = `AI model request failed after ${retries} ${retryLabel} (${failedAttempts}/${maxAttempts} attempts). Last error: ${originalMessage}`;\n\n if (previousAttemptErrors.length === 0) {\n return error;\n }\n\n const details = previousAttemptErrors\n .map(\n ({ attempt, error }) => `Attempt ${attempt}: ${getErrorMessage(error)}`,\n )\n .join('\\n');\n\n error.message = `${error.message}\\nPrevious AI call attempt errors:\\n${details}`;\n return error;\n}\n\nexport async function createChatClient({\n modelConfig,\n}: {\n modelConfig: IModelConfig;\n}): Promise<{\n completion: OpenAI.Chat.Completions;\n modelName: string;\n modelDescription: string;\n modelFamily: TModelFamily | undefined;\n}> {\n const {\n socksProxy,\n httpProxy,\n modelName,\n openaiBaseURL,\n openaiApiKey,\n openaiExtraConfig,\n modelDescription,\n modelFamily,\n createOpenAIClient,\n timeout,\n } = modelConfig;\n\n let proxyAgent: any = undefined;\n const warnClient = getDebug('ai:call', { console: true });\n const debugProxy = getDebug('ai:call:proxy');\n const warnProxy = getDebug('ai:call:proxy', { console: true });\n\n // Helper function to sanitize proxy URL for logging (remove credentials)\n // Uses URL API instead of regex to avoid ReDoS vulnerabilities\n const sanitizeProxyUrl = (url: string): string => {\n try {\n const parsed = new URL(url);\n if (parsed.username) {\n // Keep username for debugging, hide password for security\n parsed.password = '****';\n return parsed.href;\n }\n return url;\n } catch {\n // If URL parsing fails, return original URL (will be caught later)\n return url;\n }\n };\n\n if (httpProxy) {\n debugProxy('using http proxy', sanitizeProxyUrl(httpProxy));\n if (ifInBrowser) {\n warnProxy(\n 'HTTP proxy is configured but not supported in browser environment',\n );\n } else {\n // Dynamic import with variable to avoid bundler static analysis\n const moduleName = 'undici';\n const { ProxyAgent } = await import(moduleName);\n proxyAgent = new ProxyAgent({\n uri: httpProxy,\n // Note: authentication is handled via the URI (e.g., http://user:pass@proxy.com:8080)\n });\n }\n } else if (socksProxy) {\n debugProxy('using socks proxy', sanitizeProxyUrl(socksProxy));\n if (ifInBrowser) {\n warnProxy(\n 'SOCKS proxy is configured but not supported in browser environment',\n );\n } else {\n try {\n // Dynamic import with variable to avoid bundler static analysis\n const moduleName = 'fetch-socks';\n const { socksDispatcher } = await import(moduleName);\n // Parse SOCKS proxy URL (e.g., socks5://127.0.0.1:1080)\n const proxyUrl = new URL(socksProxy);\n\n // Validate hostname\n if (!proxyUrl.hostname) {\n throw new Error('SOCKS proxy URL must include a valid hostname');\n }\n\n // Validate and parse port\n const port = Number.parseInt(proxyUrl.port, 10);\n if (!proxyUrl.port || Number.isNaN(port)) {\n throw new Error('SOCKS proxy URL must include a valid port');\n }\n\n // Parse SOCKS version from protocol\n const protocol = proxyUrl.protocol.replace(':', '');\n const socksType =\n protocol === 'socks4' ? 4 : protocol === 'socks5' ? 5 : 5;\n\n proxyAgent = socksDispatcher({\n type: socksType,\n host: proxyUrl.hostname,\n port,\n ...(proxyUrl.username\n ? {\n userId: decodeURIComponent(proxyUrl.username),\n password: decodeURIComponent(proxyUrl.password || ''),\n }\n : {}),\n });\n debugProxy('socks proxy configured successfully', {\n type: socksType,\n host: proxyUrl.hostname,\n port: port,\n });\n } catch (error) {\n warnProxy('Failed to configure SOCKS proxy:', error);\n throw new Error(\n `Invalid SOCKS proxy URL: ${socksProxy}. Expected format: socks4://host:port, socks5://host:port, or with authentication: socks5://user:pass@host:port`,\n );\n }\n }\n }\n\n const effectiveTimeoutMs = resolveEffectiveTimeoutMs({ timeout });\n const openAIOptions = {\n baseURL: openaiBaseURL,\n apiKey: openaiApiKey,\n // Use fetchOptions.dispatcher for fetch-based SDK instead of httpAgent\n // Note: Type assertion needed due to undici version mismatch between dependencies\n ...(proxyAgent ? { fetchOptions: { dispatcher: proxyAgent as any } } : {}),\n ...openaiExtraConfig,\n // Midscene already handles retries in callAI(), so disable SDK-level retries\n // to avoid duplicate attempts and duplicated backoff latency.\n maxRetries: 0,\n // When disabled (timeoutMs === null) fall through to the SDK default so\n // only the caller-provided abortSignal can cancel the request.\n ...(effectiveTimeoutMs !== null ? { timeout: effectiveTimeoutMs } : {}),\n dangerouslyAllowBrowser: true,\n };\n\n const baseOpenAI = new OpenAI(openAIOptions);\n\n let openai: OpenAI = baseOpenAI;\n\n // LangSmith wrapper\n if (\n openai &&\n globalConfigManager.getEnvConfigInBoolean(MIDSCENE_LANGSMITH_DEBUG)\n ) {\n if (ifInBrowser) {\n throw new Error('langsmith is not supported in browser');\n }\n warnClient('DEBUGGING MODE: langsmith wrapper enabled');\n // Use variable to prevent static analysis by bundlers\n const langsmithModule = 'langsmith/wrappers';\n const { wrapOpenAI } = await import(langsmithModule);\n openai = wrapOpenAI(openai);\n }\n\n // Langfuse wrapper\n if (\n openai &&\n globalConfigManager.getEnvConfigInBoolean(MIDSCENE_LANGFUSE_DEBUG)\n ) {\n if (ifInBrowser) {\n throw new Error('langfuse is not supported in browser');\n }\n warnClient('DEBUGGING MODE: langfuse wrapper enabled');\n // Use variable to prevent static analysis by bundlers\n const langfuseModule = '@langfuse/openai';\n const { observeOpenAI } = await import(langfuseModule);\n openai = observeOpenAI(openai);\n }\n\n if (createOpenAIClient) {\n const wrappedClient = await createOpenAIClient(baseOpenAI, openAIOptions);\n\n if (wrappedClient) {\n openai = wrappedClient as OpenAI;\n }\n }\n\n return {\n completion: openai.chat.completions,\n modelName,\n modelDescription,\n modelFamily,\n };\n}\n\nexport async function callAI(\n messages: ChatCompletionMessageParam[],\n modelRuntime: ModelRuntime,\n options?: {\n stream?: boolean;\n onChunk?: StreamingCallback;\n abortSignal?: AbortSignal;\n requiresOriginalImageDetail?: boolean;\n },\n): Promise<{\n content: string;\n reasoning_content?: string;\n rawChoiceMessage?: unknown;\n usage?: AIUsageInfo;\n isStreamed: boolean;\n}> {\n const { config: modelConfig, adapter } = modelRuntime;\n\n if (isCodexAppServerProvider(modelConfig.openaiBaseURL)) {\n return callAIWithCodexAppServer(messages, modelConfig, {\n stream: options?.stream,\n onChunk: options?.onChunk,\n reasoningEnabled: modelConfig.reasoningEnabled,\n abortSignal: options?.abortSignal,\n });\n }\n\n const { completion, modelName, modelDescription, modelFamily } =\n await createChatClient({\n modelConfig,\n });\n const effectiveTimeoutMs = resolveEffectiveTimeoutMs(modelConfig);\n\n const extraBody = modelConfig.extraBody;\n\n const debugCall = getDebug('ai:call');\n const warnCall = getDebug('ai:call', { console: true });\n const debugProfileStats = getDebug('ai:profile:stats');\n const debugProfileDetail = getDebug('ai:profile:detail');\n\n const startTime = Date.now();\n\n const isStreaming = options?.stream && options?.onChunk;\n const chatCompletionInput = {\n intent: modelConfig.intent,\n userConfig: {\n temperature: modelConfig.temperature,\n reasoningEnabled: modelConfig.reasoningEnabled,\n reasoningEffort: modelConfig.reasoningEffort,\n reasoningBudget: modelConfig.reasoningBudget,\n },\n requiresOriginalImageDetail: options?.requiresOriginalImageDetail,\n };\n const { config: adapterChatCompletionParams } =\n adapter.chatCompletion.buildChatCompletionParams(chatCompletionInput);\n debugCall(\n `adapter chat completion params: ${stringifyForDebug({\n config: adapterChatCompletionParams,\n })}`,\n );\n let content: string | undefined;\n let accumulated = '';\n let accumulatedReasoning = '';\n let rawChoiceMessage: unknown;\n let usage: OpenAI.CompletionUsage | undefined;\n let timeCost: number | undefined;\n let requestId: string | null | undefined;\n let responseModelName: string | undefined;\n\n const hasUsableText = (value: string | null | undefined): value is string =>\n typeof value === 'string' && value.trim().length > 0;\n\n const buildUsageInfo = (\n usageData?: OpenAI.CompletionUsage,\n requestId?: string | null,\n ) => {\n if (!usageData) return undefined;\n\n const cachedInputTokens = (\n usageData as { prompt_tokens_details?: { cached_tokens?: number } }\n )?.prompt_tokens_details?.cached_tokens;\n\n return {\n ...usageData,\n prompt_tokens: usageData.prompt_tokens ?? 0,\n completion_tokens: usageData.completion_tokens ?? 0,\n total_tokens: usageData.total_tokens ?? 0,\n cached_input: cachedInputTokens ?? 0,\n time_cost: timeCost ?? 0,\n model_name: modelName,\n model_description: modelDescription,\n response_model_name: responseModelName,\n slot: modelConfig.slot,\n // Agent task layers fill semantic intent after the raw model call.\n intent: undefined,\n request_id: requestId ?? undefined,\n } satisfies AIUsageInfo;\n };\n\n const requestConfig = {\n ...adapterChatCompletionParams,\n ...(extraBody ?? {}),\n };\n const temperature = requestConfig.temperature;\n\n const imageDetail =\n adapter.chatCompletion.resolveImageDetail(chatCompletionInput);\n\n // Some adapters request original image detail to preserve screenshot\n // resolution for localization-sensitive tasks.\n const messagesWithImageDetail: ChatCompletionMessageParam[] = (() => {\n if (!imageDetail) {\n return messages;\n }\n\n return messages.map((msg) => {\n if (!Array.isArray(msg.content)) {\n return msg;\n }\n\n const content = msg.content.map((part) => {\n if (part && part.type === 'image_url' && part.image_url?.url) {\n return {\n ...part,\n image_url: {\n ...part.image_url,\n detail: imageDetail,\n },\n };\n }\n return part;\n });\n\n return {\n ...msg,\n content,\n } as ChatCompletionMessageParam;\n });\n })();\n\n try {\n debugCall(\n `sending ${isStreaming ? 'streaming ' : ''}request to ${modelName}`,\n );\n\n if (isStreaming) {\n const { signal: streamSignal, cleanup: cleanupStreamSignal } =\n buildRequestAbortSignal(effectiveTimeoutMs, options?.abortSignal);\n try {\n const stream = (await completion.create(\n {\n model: modelName,\n messages: messagesWithImageDetail,\n ...requestConfig,\n stream: true,\n },\n {\n stream: true,\n signal: streamSignal,\n },\n )) as Stream<OpenAI.Chat.Completions.ChatCompletionChunk> & {\n _request_id?: string | null;\n };\n\n requestId = stream._request_id;\n\n for await (const chunk of stream) {\n const parsedChunk = adapter.chatCompletion.extractContentAndReasoning(\n chunk.choices?.[0]?.delta,\n );\n const content = parsedChunk.content || '';\n const reasoning_content = parsedChunk.reasoning_content || '';\n\n // Check for usage info in any chunk (OpenAI provides usage in separate chunks)\n if (chunk.usage) {\n usage = chunk.usage;\n }\n if (chunk.model) {\n responseModelName = chunk.model;\n }\n\n if (content || reasoning_content) {\n accumulated += content;\n accumulatedReasoning += reasoning_content;\n const chunkData: CodeGenerationChunk = {\n content,\n reasoning_content,\n accumulated,\n isComplete: false,\n usage: undefined,\n };\n options.onChunk!(chunkData);\n }\n\n // Check if stream is complete\n if (chunk.choices?.[0]?.finish_reason) {\n timeCost = Date.now() - startTime;\n\n // If usage is not available from the stream, provide a basic usage info\n if (!usage) {\n // Estimate token counts based on content length (rough approximation)\n const estimatedTokens = Math.max(\n 1,\n Math.floor(accumulated.length / 4),\n );\n usage = {\n prompt_tokens: estimatedTokens,\n completion_tokens: estimatedTokens,\n total_tokens: estimatedTokens * 2,\n };\n }\n\n // Send final chunk\n const finalChunk: CodeGenerationChunk = {\n content: '',\n accumulated,\n reasoning_content: '',\n isComplete: true,\n usage: buildUsageInfo(usage, requestId),\n };\n options.onChunk!(finalChunk);\n break;\n }\n }\n } finally {\n cleanupStreamSignal();\n }\n content = accumulated;\n debugProfileStats(\n `streaming model, ${modelName}, mode, ${modelFamily || 'default'}, cost-ms, ${timeCost}, temperature, ${temperature ?? ''}`,\n );\n } else {\n // Non-streaming with retry logic\n const retryCount = normalizeRetryCount(modelConfig.retryCount);\n const retryInterval = modelConfig.retryInterval ?? 2000;\n const maxAttempts = retryCount + 1; // retryCount=1 means 2 total attempts (1 initial + 1 retry)\n\n let lastError: Error | undefined;\n const attemptErrors: Array<{ attempt: number; error: unknown }> = [];\n\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n const { signal: attemptSignal, cleanup: cleanupAttemptSignal } =\n buildRequestAbortSignal(effectiveTimeoutMs, options?.abortSignal);\n try {\n const result = await completion.create(\n {\n model: modelName,\n messages: messagesWithImageDetail,\n ...requestConfig,\n stream: false,\n } as any,\n { signal: attemptSignal },\n );\n\n timeCost = Date.now() - startTime;\n\n debugProfileStats(\n `model, ${modelName}, mode, ${modelFamily || 'default'}, prompt-tokens, ${result.usage?.prompt_tokens || ''}, completion-tokens, ${result.usage?.completion_tokens || ''}, total-tokens, ${result.usage?.total_tokens || ''}, cost-ms, ${timeCost}, requestId, ${result._request_id || ''}, temperature, ${temperature ?? ''}`,\n );\n\n debugProfileDetail(\n `model usage detail: ${JSON.stringify(result.usage)}`,\n );\n\n if (!result.choices) {\n throw new Error(\n `invalid response from LLM service: ${JSON.stringify(result)}`,\n );\n }\n\n rawChoiceMessage = result.choices[0].message;\n const parsedMessage =\n adapter.chatCompletion.extractContentAndReasoning(\n result.choices[0].message,\n );\n content = parsedMessage.content;\n accumulatedReasoning = parsedMessage.reasoning_content;\n usage = result.usage;\n requestId = result._request_id;\n responseModelName = result.model;\n\n if (!hasUsableText(content) && hasUsableText(accumulatedReasoning)) {\n warnCall('empty content from AI model, using reasoning content');\n content = accumulatedReasoning;\n }\n\n if (!hasUsableText(content)) {\n throw new AIResponseParseError(\n 'empty content from AI model',\n JSON.stringify(result),\n buildUsageInfo(usage, requestId),\n rawChoiceMessage,\n );\n }\n\n break; // Success, exit retry loop\n } catch (error) {\n lastError = toError(error);\n attemptErrors.push({ attempt, error });\n const wasHardTimeout = isHardTimeoutError(lastError);\n if (wasHardTimeout) {\n warnCall(\n `AI call hit hard timeout (${effectiveTimeoutMs}ms, attempt ${attempt}/${maxAttempts}, model ${modelName}, slot ${modelConfig.slot})`,\n );\n }\n // Do not retry if the request was aborted by the caller\n if (options?.abortSignal?.aborted) {\n break;\n }\n if (attempt < maxAttempts) {\n warnCall(\n `AI call failed (attempt ${attempt}/${maxAttempts}), retrying in ${retryInterval}ms... Error: ${lastError.message}`,\n );\n await new Promise((resolve) => setTimeout(resolve, retryInterval));\n }\n } finally {\n cleanupAttemptSignal();\n }\n }\n\n if (!content) {\n assert(\n lastError,\n 'AI model request failed without recording an attempt error',\n );\n throw appendAIRequestFailureSummary(\n lastError,\n attemptErrors,\n maxAttempts,\n );\n }\n }\n\n debugCall(`response reasoning content: ${accumulatedReasoning}`);\n debugCall(`response content: ${content}`);\n\n // Ensure we always have usage info for streaming responses\n if (isStreaming && !usage) {\n // Estimate token counts based on content length (rough approximation)\n const estimatedTokens = Math.max(\n 1,\n Math.floor((content || '').length / 4),\n );\n usage = {\n prompt_tokens: estimatedTokens,\n completion_tokens: estimatedTokens,\n total_tokens: estimatedTokens * 2,\n } as OpenAI.CompletionUsage;\n }\n\n return {\n content: content || '',\n reasoning_content: accumulatedReasoning || undefined,\n rawChoiceMessage,\n usage: buildUsageInfo(usage, requestId),\n isStreamed: !!isStreaming,\n };\n } catch (e: any) {\n warnCall('call AI error', e);\n\n if (e instanceof AIResponseParseError) {\n throw e;\n }\n\n const newError = new Error(\n `failed to call ${isStreaming ? 'streaming ' : ''}AI model service (${modelName}): ${e.message}\\nTrouble shooting: https://midscenejs.com/model-provider.html`,\n {\n cause: e,\n },\n );\n throw newError;\n }\n}\n\nexport async function callAIWithObjectResponse<T>(\n messages: ChatCompletionMessageParam[],\n // Keep IModelConfig compatibility for midscene-example/connectivity-test/tests/connectivity.test.ts; internal workflow callers should pass ModelRuntime instead.\n model: IModelConfig | ModelRuntime,\n options?: {\n abortSignal?: AbortSignal;\n jsonParserSource?: JsonParserSource;\n },\n): Promise<{\n // TODO: `content` is a misleading name here because this is already the parsed object response. Consider renaming it to `object` or `data`.\n content: T;\n contentString: string;\n usage?: AIUsageInfo;\n reasoning_content?: string;\n rawChoiceMessage?: unknown;\n}> {\n const modelRuntime = resolveCompatibleModelRuntime(model);\n const { config: modelConfig, adapter } = modelRuntime;\n const response = await callAI(messages, modelRuntime, {\n abortSignal: options?.abortSignal,\n });\n assert(response, 'empty response');\n let jsonContent: unknown;\n try {\n jsonContent = adapter.jsonParser(response.content, {\n source: options?.jsonParserSource ?? 'generic-object',\n });\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : String(error);\n throw new AIResponseParseError(\n errorMessage,\n response.content,\n response.usage,\n );\n }\n if (typeof jsonContent !== 'object') {\n throw new AIResponseParseError(\n `failed to parse json response from model (${modelConfig.modelName}): ${response.content}`,\n response.content,\n response.usage,\n response.rawChoiceMessage,\n );\n }\n return {\n content: jsonContent as T,\n contentString: response.content,\n usage: response.usage,\n reasoning_content: response.reasoning_content,\n rawChoiceMessage: response.rawChoiceMessage,\n };\n}\n\nfunction resolveCompatibleModelRuntime(\n model: IModelConfig | ModelRuntime,\n): ModelRuntime {\n if ('config' in model && 'adapter' in model) {\n return model;\n }\n\n return getModelRuntime(model);\n}\n\nexport async function callAIWithStringResponse(\n msgs: AIArgs,\n modelRuntime: ModelRuntime,\n options?: {\n abortSignal?: AbortSignal;\n },\n): Promise<{\n content: string;\n usage?: AIUsageInfo;\n rawChoiceMessage?: unknown;\n}> {\n const { content, usage, rawChoiceMessage } = await callAI(\n msgs,\n modelRuntime,\n {\n abortSignal: options?.abortSignal,\n },\n );\n return { content, usage, rawChoiceMessage };\n}\n"],"names":["AIResponseParseError","Error","message","rawResponse","usage","rawChoiceMessage","stringifyForDebug","value","JSON","_error","String","getErrorMessage","error","toError","normalizeRetryCount","retryCount","Number","Math","appendAIRequestFailureSummary","attemptErrors","maxAttempts","failedAttempts","retries","retryLabel","originalMessage","previousAttemptErrors","details","attempt","createChatClient","modelConfig","socksProxy","httpProxy","modelName","openaiBaseURL","openaiApiKey","openaiExtraConfig","modelDescription","modelFamily","createOpenAIClient","timeout","proxyAgent","warnClient","getDebug","debugProxy","warnProxy","sanitizeProxyUrl","url","parsed","URL","ifInBrowser","moduleName","ProxyAgent","socksDispatcher","proxyUrl","port","protocol","socksType","decodeURIComponent","effectiveTimeoutMs","resolveEffectiveTimeoutMs","openAIOptions","baseOpenAI","OpenAI","openai","globalConfigManager","MIDSCENE_LANGSMITH_DEBUG","langsmithModule","wrapOpenAI","MIDSCENE_LANGFUSE_DEBUG","langfuseModule","observeOpenAI","wrappedClient","callAI","messages","modelRuntime","options","adapter","isCodexAppServerProvider","callAIWithCodexAppServer","completion","extraBody","debugCall","warnCall","debugProfileStats","debugProfileDetail","startTime","Date","isStreaming","chatCompletionInput","adapterChatCompletionParams","content","accumulated","accumulatedReasoning","timeCost","requestId","responseModelName","hasUsableText","buildUsageInfo","usageData","cachedInputTokens","undefined","requestConfig","temperature","imageDetail","messagesWithImageDetail","msg","Array","part","streamSignal","cleanupStreamSignal","buildRequestAbortSignal","stream","chunk","parsedChunk","reasoning_content","chunkData","estimatedTokens","finalChunk","retryInterval","lastError","attemptSignal","cleanupAttemptSignal","result","parsedMessage","wasHardTimeout","isHardTimeoutError","Promise","resolve","setTimeout","assert","e","newError","callAIWithObjectResponse","model","resolveCompatibleModelRuntime","response","jsonContent","errorMessage","getModelRuntime","callAIWithStringResponse","msgs"],"mappings":"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"}