@midscene/core 1.10.4-beta-20260715032253.0 → 1.10.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/es/agent/utils.mjs +1 -1
- package/dist/es/ai-model/service-caller/index.mjs +6 -3
- package/dist/es/ai-model/service-caller/index.mjs.map +1 -1
- package/dist/es/ai-model/service-caller/openai-error.mjs +18 -0
- package/dist/es/ai-model/service-caller/openai-error.mjs.map +1 -1
- package/dist/es/utils.mjs +2 -2
- package/dist/lib/agent/utils.js +1 -1
- package/dist/lib/ai-model/service-caller/index.js +6 -3
- package/dist/lib/ai-model/service-caller/index.js.map +1 -1
- package/dist/lib/ai-model/service-caller/openai-error.js +18 -0
- package/dist/lib/ai-model/service-caller/openai-error.js.map +1 -1
- package/dist/lib/utils.js +2 -2
- package/dist/types/ai-model/service-caller/openai-error.d.ts +6 -0
- package/package.json +2 -2
package/dist/es/agent/utils.mjs
CHANGED
|
@@ -173,7 +173,7 @@ async function matchElementFromCache(context, cacheEntry, cachePrompt, cacheable
|
|
|
173
173
|
return;
|
|
174
174
|
}
|
|
175
175
|
}
|
|
176
|
-
const getMidsceneVersion = ()=>"1.10.4
|
|
176
|
+
const getMidsceneVersion = ()=>"1.10.4";
|
|
177
177
|
const parsePrompt = (prompt)=>{
|
|
178
178
|
if ('string' == typeof prompt) return {
|
|
179
179
|
textPrompt: prompt,
|
|
@@ -38,6 +38,9 @@ function stringifyForDebug(value) {
|
|
|
38
38
|
return String(value);
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
|
+
function getLatestSuccessfulResponseRequestId(context) {
|
|
42
|
+
return context.responseRequestIds?.reduce((latestRequestId, response)=>response.ok ? response.requestId : latestRequestId, void 0);
|
|
43
|
+
}
|
|
41
44
|
function getErrorMessage(error) {
|
|
42
45
|
return error instanceof Error ? error.message : String(error);
|
|
43
46
|
}
|
|
@@ -290,7 +293,7 @@ async function callAI(messages, modelRuntime, options) {
|
|
|
290
293
|
stream: true,
|
|
291
294
|
signal: streamSignal
|
|
292
295
|
});
|
|
293
|
-
requestId = stream._request_id;
|
|
296
|
+
requestId = getLatestSuccessfulResponseRequestId(openAIErrorResponseContext) ?? stream._request_id;
|
|
294
297
|
for await (const chunk of stream){
|
|
295
298
|
const parsedChunk = adapter.chatCompletion.extractContentAndReasoning(chunk.choices?.[0]?.delta);
|
|
296
299
|
const content = parsedChunk.content || '';
|
|
@@ -360,7 +363,8 @@ async function callAI(messages, modelRuntime, options) {
|
|
|
360
363
|
signal: attemptSignal
|
|
361
364
|
});
|
|
362
365
|
timeCost = Date.now() - startTime;
|
|
363
|
-
|
|
366
|
+
requestId = getLatestSuccessfulResponseRequestId(openAIErrorResponseContext) ?? result._request_id;
|
|
367
|
+
debugProfileStats(`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, ${requestId || ''}, temperature, ${temperature ?? ''}`);
|
|
364
368
|
debugProfileDetail(`model usage detail: ${JSON.stringify(result.usage)}`);
|
|
365
369
|
if (!result.choices) throw new Error(`invalid response from LLM service: ${JSON.stringify(result)}`);
|
|
366
370
|
rawChoiceMessage = result.choices[0].message;
|
|
@@ -368,7 +372,6 @@ async function callAI(messages, modelRuntime, options) {
|
|
|
368
372
|
content = parsedMessage.content;
|
|
369
373
|
accumulatedReasoning = parsedMessage.reasoning_content;
|
|
370
374
|
usage = result.usage;
|
|
371
|
-
requestId = result._request_id;
|
|
372
375
|
responseModelName = result.model;
|
|
373
376
|
content = resolveContentWithReasoningFallback(content, accumulatedReasoning);
|
|
374
377
|
if (!hasUsableText(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 } from '../models';\nimport type { AIArgs } from '../types';\nimport {\n callAIWithCodexAppServer,\n isCodexAppServerProvider,\n} from './codex-app-server';\nimport type { JsonParserSource } from './json';\nimport {\n type OpenAIErrorResponseContext,\n formatOpenAIAPIErrorDetails,\n wrapOpenAICompatibleFetch,\n} from './openai-error';\nimport {\n buildRequestAbortSignal,\n isHardTimeoutError,\n resolveEffectiveTimeoutMs,\n} from './request-timeout';\nexport {\n extractJSONFromCodeBlock,\n parseModelResponseJson,\n} from './json';\nexport type { JsonParser } from './json';\n\n/**\n * Internal field name stamped onto every AIUsageInfo shaped by callAI().\n * Used for cross-path dedup when the provider does not return a request_id.\n */\nexport const INTERNAL_CALL_ID_FIELD = '_midscene_call_id';\n\nlet internalCallIdCounter = 0;\nfunction nextInternalCallId(): string {\n internalCallIdCounter += 1;\n return `call_${internalCallIdCounter}`;\n}\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 openAIErrorResponseContext: OpenAIErrorResponseContext;\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 openAIErrorResponseContext: OpenAIErrorResponseContext = {};\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 fetch: wrapOpenAICompatibleFetch(openAIErrorResponseContext),\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 openAIErrorResponseContext,\n };\n}\n\ninterface CallAIOptions {\n stream?: boolean;\n onChunk?: StreamingCallback;\n abortSignal?: AbortSignal;\n requiresOriginalImageDetail?: boolean;\n}\n\nexport async function callAI(\n messages: ChatCompletionMessageParam[],\n modelRuntime: ModelRuntime,\n options?: CallAIOptions,\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 // Stable internal ID for this call, used by the agent to deduplicate usage\n // across the onUsage callback and the task-dump-based collectUsageMetrics()\n // path when the provider does not return a request_id.\n const internalCallId = nextInternalCallId();\n\n if (isCodexAppServerProvider(modelConfig.openaiBaseURL)) {\n const codexResult = await callAIWithCodexAppServer(messages, modelConfig, {\n stream: options?.stream,\n onChunk: options?.onChunk,\n reasoningEnabled: modelConfig.reasoningEnabled,\n abortSignal: options?.abortSignal,\n });\n if (codexResult.usage) {\n (codexResult.usage as any)[INTERNAL_CALL_ID_FIELD] = internalCallId;\n if (modelRuntime.onUsage) {\n modelRuntime.onUsage(codexResult.usage);\n }\n }\n return codexResult;\n }\n\n const {\n completion,\n modelName,\n modelDescription,\n modelFamily,\n openAIErrorResponseContext,\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 // Tracks whether onUsage has already been fired for this call (e.g. from\n // the streaming final-chunk handler), so the final return does not double-fire.\n let usageReported = false;\n\n const hasUsableText = (value: string | null | undefined): value is string =>\n typeof value === 'string' && value.trim().length > 0;\n\n const resolveContentWithReasoningFallback = (\n contentValue: string | undefined,\n reasoningContent: string,\n ) => {\n if (\n !hasUsableText(contentValue) &&\n adapter.chatCompletion.useReasoningAsContentFallback &&\n hasUsableText(reasoningContent)\n ) {\n warnCall('empty content from AI model, using reasoning content');\n return reasoningContent;\n }\n\n return contentValue;\n };\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 // Left undefined at the raw call layer. The agent's onUsage callback\n // fills it from modelConfig.slot for metrics collection, and task\n // layers use withUsageIntent() to stamp a more specific semantic\n // intent (e.g. 'planning', 'insight') when attaching usage to tasks.\n intent: undefined,\n request_id: requestId ?? undefined,\n // Internal stable ID for cross-path dedup when request_id is absent.\n [INTERNAL_CALL_ID_FIELD]: internalCallId,\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 const finalAccumulated = resolveContentWithReasoningFallback(\n accumulated,\n accumulatedReasoning,\n );\n accumulated = finalAccumulated || '';\n\n // Send final chunk\n const finalUsage = buildUsageInfo(usage, requestId);\n if (finalUsage && modelRuntime.onUsage) {\n modelRuntime.onUsage(finalUsage);\n usageReported = true;\n }\n const finalChunk: CodeGenerationChunk = {\n content: '',\n accumulated,\n reasoning_content: '',\n isComplete: true,\n usage: finalUsage,\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 content = resolveContentWithReasoningFallback(\n content,\n accumulatedReasoning,\n );\n\n if (!hasUsableText(content)) {\n const errorUsage = buildUsageInfo(usage, requestId);\n if (errorUsage && modelRuntime.onUsage) {\n modelRuntime.onUsage(errorUsage);\n }\n throw new AIResponseParseError(\n 'empty content from AI model',\n content || '',\n errorUsage,\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 const finalUsage = buildUsageInfo(usage, requestId);\n // Report usage to the runtime-level collector if not already reported\n // (e.g. from the streaming final-chunk handler).\n if (!usageReported && finalUsage && modelRuntime.onUsage) {\n modelRuntime.onUsage(finalUsage);\n }\n\n return {\n content: content || '',\n reasoning_content: accumulatedReasoning || undefined,\n rawChoiceMessage,\n usage: finalUsage,\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}${formatOpenAIAPIErrorDetails(e, openAIErrorResponseContext)}\\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 modelRuntime: 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 { 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\nexport async function callAIWithStringResponse(\n msgs: AIArgs,\n modelRuntime: ModelRuntime,\n options?: Pick<CallAIOptions, 'abortSignal' | 'requiresOriginalImageDetail'>,\n): Promise<{\n content: string;\n usage?: AIUsageInfo;\n rawChoiceMessage?: unknown;\n}> {\n const { content, usage, rawChoiceMessage } = await callAI(\n msgs,\n modelRuntime,\n options,\n );\n return { content, usage, rawChoiceMessage };\n}\n"],"names":["AIResponseParseError","Error","message","rawResponse","usage","rawChoiceMessage","INTERNAL_CALL_ID_FIELD","internalCallIdCounter","nextInternalCallId","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","openAIErrorResponseContext","openAIOptions","wrapOpenAICompatibleFetch","baseOpenAI","OpenAI","openai","globalConfigManager","MIDSCENE_LANGSMITH_DEBUG","langsmithModule","wrapOpenAI","MIDSCENE_LANGFUSE_DEBUG","langfuseModule","observeOpenAI","wrappedClient","callAI","messages","modelRuntime","options","adapter","internalCallId","isCodexAppServerProvider","codexResult","callAIWithCodexAppServer","completion","extraBody","debugCall","warnCall","debugProfileStats","debugProfileDetail","startTime","Date","isStreaming","chatCompletionInput","adapterChatCompletionParams","content","accumulated","accumulatedReasoning","timeCost","requestId","responseModelName","usageReported","hasUsableText","resolveContentWithReasoningFallback","contentValue","reasoningContent","buildUsageInfo","usageData","cachedInputTokens","undefined","requestConfig","temperature","imageDetail","messagesWithImageDetail","msg","Array","part","streamSignal","cleanupStreamSignal","buildRequestAbortSignal","stream","chunk","parsedChunk","reasoning_content","chunkData","estimatedTokens","finalAccumulated","finalUsage","finalChunk","retryInterval","lastError","attemptSignal","cleanupAttemptSignal","result","parsedMessage","errorUsage","wasHardTimeout","isHardTimeoutError","Promise","resolve","setTimeout","assert","e","newError","formatOpenAIAPIErrorDetails","callAIWithObjectResponse","response","jsonContent","errorMessage","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 } from '../models';\nimport type { AIArgs } from '../types';\nimport {\n callAIWithCodexAppServer,\n isCodexAppServerProvider,\n} from './codex-app-server';\nimport type { JsonParserSource } from './json';\nimport {\n type OpenAIErrorResponseContext,\n formatOpenAIAPIErrorDetails,\n wrapOpenAICompatibleFetch,\n} from './openai-error';\nimport {\n buildRequestAbortSignal,\n isHardTimeoutError,\n resolveEffectiveTimeoutMs,\n} from './request-timeout';\nexport {\n extractJSONFromCodeBlock,\n parseModelResponseJson,\n} from './json';\nexport type { JsonParser } from './json';\n\n/**\n * Internal field name stamped onto every AIUsageInfo shaped by callAI().\n * Used for cross-path dedup when the provider does not return a request_id.\n */\nexport const INTERNAL_CALL_ID_FIELD = '_midscene_call_id';\n\nlet internalCallIdCounter = 0;\nfunction nextInternalCallId(): string {\n internalCallIdCounter += 1;\n return `call_${internalCallIdCounter}`;\n}\n\nfunction stringifyForDebug(value: unknown): string {\n try {\n return JSON.stringify(value);\n } catch (_error) {\n return String(value);\n }\n}\n\nfunction getLatestSuccessfulResponseRequestId(\n context: OpenAIErrorResponseContext,\n): string | undefined {\n return context.responseRequestIds?.reduce<string | undefined>(\n (latestRequestId, response) =>\n response.ok ? response.requestId : latestRequestId,\n undefined,\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 openAIErrorResponseContext: OpenAIErrorResponseContext;\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 openAIErrorResponseContext: OpenAIErrorResponseContext = {};\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 fetch: wrapOpenAICompatibleFetch(openAIErrorResponseContext),\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 openAIErrorResponseContext,\n };\n}\n\ninterface CallAIOptions {\n stream?: boolean;\n onChunk?: StreamingCallback;\n abortSignal?: AbortSignal;\n requiresOriginalImageDetail?: boolean;\n}\n\nexport async function callAI(\n messages: ChatCompletionMessageParam[],\n modelRuntime: ModelRuntime,\n options?: CallAIOptions,\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 // Stable internal ID for this call, used by the agent to deduplicate usage\n // across the onUsage callback and the task-dump-based collectUsageMetrics()\n // path when the provider does not return a request_id.\n const internalCallId = nextInternalCallId();\n\n if (isCodexAppServerProvider(modelConfig.openaiBaseURL)) {\n const codexResult = await callAIWithCodexAppServer(messages, modelConfig, {\n stream: options?.stream,\n onChunk: options?.onChunk,\n reasoningEnabled: modelConfig.reasoningEnabled,\n abortSignal: options?.abortSignal,\n });\n if (codexResult.usage) {\n (codexResult.usage as any)[INTERNAL_CALL_ID_FIELD] = internalCallId;\n if (modelRuntime.onUsage) {\n modelRuntime.onUsage(codexResult.usage);\n }\n }\n return codexResult;\n }\n\n const {\n completion,\n modelName,\n modelDescription,\n modelFamily,\n openAIErrorResponseContext,\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 // Tracks whether onUsage has already been fired for this call (e.g. from\n // the streaming final-chunk handler), so the final return does not double-fire.\n let usageReported = false;\n\n const hasUsableText = (value: string | null | undefined): value is string =>\n typeof value === 'string' && value.trim().length > 0;\n\n const resolveContentWithReasoningFallback = (\n contentValue: string | undefined,\n reasoningContent: string,\n ) => {\n if (\n !hasUsableText(contentValue) &&\n adapter.chatCompletion.useReasoningAsContentFallback &&\n hasUsableText(reasoningContent)\n ) {\n warnCall('empty content from AI model, using reasoning content');\n return reasoningContent;\n }\n\n return contentValue;\n };\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 // Left undefined at the raw call layer. The agent's onUsage callback\n // fills it from modelConfig.slot for metrics collection, and task\n // layers use withUsageIntent() to stamp a more specific semantic\n // intent (e.g. 'planning', 'insight') when attaching usage to tasks.\n intent: undefined,\n request_id: requestId ?? undefined,\n // Internal stable ID for cross-path dedup when request_id is absent.\n [INTERNAL_CALL_ID_FIELD]: internalCallId,\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 =\n getLatestSuccessfulResponseRequestId(openAIErrorResponseContext) ??\n 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 const finalAccumulated = resolveContentWithReasoningFallback(\n accumulated,\n accumulatedReasoning,\n );\n accumulated = finalAccumulated || '';\n\n // Send final chunk\n const finalUsage = buildUsageInfo(usage, requestId);\n if (finalUsage && modelRuntime.onUsage) {\n modelRuntime.onUsage(finalUsage);\n usageReported = true;\n }\n const finalChunk: CodeGenerationChunk = {\n content: '',\n accumulated,\n reasoning_content: '',\n isComplete: true,\n usage: finalUsage,\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 requestId =\n getLatestSuccessfulResponseRequestId(openAIErrorResponseContext) ??\n result._request_id;\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, ${requestId || ''}, 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 responseModelName = result.model;\n\n content = resolveContentWithReasoningFallback(\n content,\n accumulatedReasoning,\n );\n\n if (!hasUsableText(content)) {\n const errorUsage = buildUsageInfo(usage, requestId);\n if (errorUsage && modelRuntime.onUsage) {\n modelRuntime.onUsage(errorUsage);\n }\n throw new AIResponseParseError(\n 'empty content from AI model',\n content || '',\n errorUsage,\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 const finalUsage = buildUsageInfo(usage, requestId);\n // Report usage to the runtime-level collector if not already reported\n // (e.g. from the streaming final-chunk handler).\n if (!usageReported && finalUsage && modelRuntime.onUsage) {\n modelRuntime.onUsage(finalUsage);\n }\n\n return {\n content: content || '',\n reasoning_content: accumulatedReasoning || undefined,\n rawChoiceMessage,\n usage: finalUsage,\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}${formatOpenAIAPIErrorDetails(e, openAIErrorResponseContext)}\\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 modelRuntime: 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 { 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\nexport async function callAIWithStringResponse(\n msgs: AIArgs,\n modelRuntime: ModelRuntime,\n options?: Pick<CallAIOptions, 'abortSignal' | 'requiresOriginalImageDetail'>,\n): Promise<{\n content: string;\n usage?: AIUsageInfo;\n rawChoiceMessage?: unknown;\n}> {\n const { content, usage, rawChoiceMessage } = await callAI(\n msgs,\n modelRuntime,\n options,\n );\n return { content, usage, rawChoiceMessage };\n}\n"],"names":["AIResponseParseError","Error","message","rawResponse","usage","rawChoiceMessage","INTERNAL_CALL_ID_FIELD","internalCallIdCounter","nextInternalCallId","stringifyForDebug","value","JSON","_error","String","getLatestSuccessfulResponseRequestId","context","latestRequestId","response","undefined","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","openAIErrorResponseContext","openAIOptions","wrapOpenAICompatibleFetch","baseOpenAI","OpenAI","openai","globalConfigManager","MIDSCENE_LANGSMITH_DEBUG","langsmithModule","wrapOpenAI","MIDSCENE_LANGFUSE_DEBUG","langfuseModule","observeOpenAI","wrappedClient","callAI","messages","modelRuntime","options","adapter","internalCallId","isCodexAppServerProvider","codexResult","callAIWithCodexAppServer","completion","extraBody","debugCall","warnCall","debugProfileStats","debugProfileDetail","startTime","Date","isStreaming","chatCompletionInput","adapterChatCompletionParams","content","accumulated","accumulatedReasoning","timeCost","requestId","responseModelName","usageReported","hasUsableText","resolveContentWithReasoningFallback","contentValue","reasoningContent","buildUsageInfo","usageData","cachedInputTokens","requestConfig","temperature","imageDetail","messagesWithImageDetail","msg","Array","part","streamSignal","cleanupStreamSignal","buildRequestAbortSignal","stream","chunk","parsedChunk","reasoning_content","chunkData","estimatedTokens","finalAccumulated","finalUsage","finalChunk","retryInterval","lastError","attemptSignal","cleanupAttemptSignal","result","parsedMessage","errorUsage","wasHardTimeout","isHardTimeoutError","Promise","resolve","setTimeout","assert","e","newError","formatOpenAIAPIErrorDetails","callAIWithObjectResponse","jsonContent","errorMessage","callAIWithStringResponse","msgs"],"mappings":"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"}
|
|
@@ -52,6 +52,16 @@ function wrapOpenAICompatibleFetch(context) {
|
|
|
52
52
|
});
|
|
53
53
|
throw error;
|
|
54
54
|
}
|
|
55
|
+
const requestId = response.headers.get('x-request-id') ?? response.headers.get('x-model-request-id');
|
|
56
|
+
if (requestId) {
|
|
57
|
+
context.responseRequestIds ??= [];
|
|
58
|
+
context.responseRequestIds.push({
|
|
59
|
+
attempt,
|
|
60
|
+
requestId,
|
|
61
|
+
status: response.status,
|
|
62
|
+
ok: response.ok
|
|
63
|
+
});
|
|
64
|
+
}
|
|
55
65
|
if (!response.ok) {
|
|
56
66
|
const rawResponseBody = await response.clone().text().catch(()=>void 0);
|
|
57
67
|
if (void 0 !== rawResponseBody) {
|
|
@@ -72,6 +82,14 @@ function formatOpenAIAPIErrorDetails(_error, context) {
|
|
|
72
82
|
const rawResponseBodyDetails = context.rawResponseBodies.map(({ attempt, body })=>`Attempt ${attempt}: ${truncateErrorResponseBody(body)}`).join('\n');
|
|
73
83
|
details.push(`OpenAI raw error response bodies:\n${rawResponseBodyDetails}`);
|
|
74
84
|
}
|
|
85
|
+
const errorResponseRequestIds = context.responseRequestIds?.filter(({ ok })=>!ok);
|
|
86
|
+
if (errorResponseRequestIds?.length === 1) {
|
|
87
|
+
const { attempt, requestId, status } = errorResponseRequestIds[0];
|
|
88
|
+
details.push(`OpenAI error response request ID (attempt ${attempt}, status ${status}): ${requestId}`);
|
|
89
|
+
} else if (errorResponseRequestIds?.length) {
|
|
90
|
+
const requestIdDetails = errorResponseRequestIds.map(({ attempt, requestId, status })=>`Attempt ${attempt} (status ${status}): ${requestId}`).join('\n');
|
|
91
|
+
details.push(`OpenAI error response request IDs:\n${requestIdDetails}`);
|
|
92
|
+
}
|
|
75
93
|
if (context.fetchErrors?.length === 1) details.push(`OpenAI fetch error (attempt ${context.fetchErrors[0].attempt}): ${context.fetchErrors[0].error}`);
|
|
76
94
|
else if (context.fetchErrors?.length) {
|
|
77
95
|
const fetchErrorDetails = context.fetchErrors.map(({ attempt, error })=>`Attempt ${attempt}: ${error}`).join('\n');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ai-model/service-caller/openai-error.mjs","sources":["../../../../src/ai-model/service-caller/openai-error.ts"],"sourcesContent":["import { getDebug } from '@midscene/shared/logger';\n\nconst MAX_ERROR_RESPONSE_BODY_LENGTH = 4000;\nconst MAX_FETCH_ERROR_LENGTH = 4000;\n\nconst debugOpenAIFetch = getDebug('ai:call');\n\nexport interface OpenAIErrorResponseContext {\n rawResponseBodies?: Array<{\n attempt: number;\n body: string;\n }>;\n fetchErrors?: Array<{\n attempt: number;\n error: string;\n }>;\n}\n\nfunction truncateText(text: string, maxLength: number): string {\n if (text.length <= maxLength) {\n return text;\n }\n\n return `${text.slice(0, maxLength)}... [truncated, ${text.length} chars total]`;\n}\n\nfunction truncateErrorResponseBody(body: string): string {\n return truncateText(body, MAX_ERROR_RESPONSE_BODY_LENGTH);\n}\n\nfunction getErrorCode(error: unknown): string | undefined {\n if (!error || typeof error !== 'object') {\n return undefined;\n }\n\n const code = (error as { code?: unknown }).code;\n return typeof code === 'string' ? code : undefined;\n}\n\nfunction formatErrorSummary(error: unknown): string {\n if (error instanceof Error) {\n const code = getErrorCode(error);\n const codeText = code ? ` [${code}]` : '';\n return `${error.name}${codeText}: ${error.message}`;\n }\n\n return String(error);\n}\n\nfunction formatFetchErrorForReport(error: unknown): string {\n const details = [formatErrorSummary(error)];\n const cause =\n error && typeof error === 'object'\n ? (error as { cause?: unknown }).cause\n : undefined;\n\n if (cause !== undefined) {\n details.push(`Cause: ${formatErrorSummary(cause)}`);\n }\n\n return truncateText(details.join('\\n'), MAX_FETCH_ERROR_LENGTH);\n}\n\n// Mirrors OpenAI SDK's default fetch selection:\n// openai@6.3.0 src/client.ts sets `this.fetch = options.fetch ?? Shims.getDefaultFetch()`,\n// and src/internal/shims.ts resolves that default to global `fetch`.\nfunction getDefaultFetch(): typeof fetch {\n if (typeof globalThis.fetch === 'function') {\n return globalThis.fetch;\n }\n\n throw new Error(\n '`fetch` is not defined as a global; check that the runtime provides globalThis.fetch or polyfill it before creating the OpenAI client',\n );\n}\n\nexport function wrapOpenAICompatibleFetch(\n context: OpenAIErrorResponseContext,\n): typeof fetch {\n const baseFetch = getDefaultFetch();\n let attempt = 0;\n\n return async (input, init) => {\n attempt += 1;\n let response: Response;\n try {\n response = await baseFetch(input, init);\n } catch (error) {\n const fetchErrorSummary = formatFetchErrorForReport(error);\n debugOpenAIFetch('OpenAI-compatible fetch failed', fetchErrorSummary);\n context.fetchErrors ??= [];\n context.fetchErrors.push({\n attempt,\n error: fetchErrorSummary,\n });\n throw error;\n }\n\n if (!response.ok) {\n // OpenAI SDK only exposes the `error` field for JSON error responses.\n // Non-standard provider bodies like `{ err: 'xxx' }` would otherwise be\n // hidden from Midscene's final error message.\n const rawResponseBody = await response\n .clone()\n .text()\n .catch(() => undefined);\n\n if (rawResponseBody !== undefined) {\n context.rawResponseBodies ??= [];\n context.rawResponseBodies.push({\n attempt,\n body: rawResponseBody,\n });\n }\n }\n\n return response;\n };\n}\n\nexport function formatOpenAIAPIErrorDetails(\n _error: unknown,\n context: OpenAIErrorResponseContext,\n): string {\n const details: string[] = [];\n\n if (context.rawResponseBodies?.length === 1) {\n details.push(\n `OpenAI raw error response body: ${truncateErrorResponseBody(\n context.rawResponseBodies[0].body,\n )}`,\n );\n } else if (context.rawResponseBodies?.length) {\n const rawResponseBodyDetails = context.rawResponseBodies\n .map(\n ({ attempt, body }) =>\n `Attempt ${attempt}: ${truncateErrorResponseBody(body)}`,\n )\n .join('\\n');\n\n details.push(\n `OpenAI raw error response bodies:\\n${rawResponseBodyDetails}`,\n );\n }\n\n if (context.fetchErrors?.length === 1) {\n details.push(\n `OpenAI fetch error (attempt ${context.fetchErrors[0].attempt}): ${context.fetchErrors[0].error}`,\n );\n } else if (context.fetchErrors?.length) {\n const fetchErrorDetails = context.fetchErrors\n .map(({ attempt, error }) => `Attempt ${attempt}: ${error}`)\n .join('\\n');\n\n details.push(`OpenAI fetch errors:\\n${fetchErrorDetails}`);\n }\n\n if (!details.length) {\n return '';\n }\n\n return `\\n${details.join('\\n')}`;\n}\n"],"names":["MAX_ERROR_RESPONSE_BODY_LENGTH","MAX_FETCH_ERROR_LENGTH","debugOpenAIFetch","getDebug","truncateText","text","maxLength","truncateErrorResponseBody","body","getErrorCode","error","code","undefined","formatErrorSummary","Error","codeText","String","formatFetchErrorForReport","details","cause","getDefaultFetch","globalThis","wrapOpenAICompatibleFetch","context","baseFetch","attempt","input","init","response","fetchErrorSummary","rawResponseBody","formatOpenAIAPIErrorDetails","_error","rawResponseBodyDetails","fetchErrorDetails"],"mappings":";AAEA,MAAMA,iCAAiC;AACvC,MAAMC,yBAAyB;AAE/B,MAAMC,mBAAmBC,SAAS;
|
|
1
|
+
{"version":3,"file":"ai-model/service-caller/openai-error.mjs","sources":["../../../../src/ai-model/service-caller/openai-error.ts"],"sourcesContent":["import { getDebug } from '@midscene/shared/logger';\n\nconst MAX_ERROR_RESPONSE_BODY_LENGTH = 4000;\nconst MAX_FETCH_ERROR_LENGTH = 4000;\n\nconst debugOpenAIFetch = getDebug('ai:call');\n\nexport interface OpenAIErrorResponseContext {\n responseRequestIds?: Array<{\n attempt: number;\n requestId: string;\n status: number;\n ok: boolean;\n }>;\n rawResponseBodies?: Array<{\n attempt: number;\n body: string;\n }>;\n fetchErrors?: Array<{\n attempt: number;\n error: string;\n }>;\n}\n\nfunction truncateText(text: string, maxLength: number): string {\n if (text.length <= maxLength) {\n return text;\n }\n\n return `${text.slice(0, maxLength)}... [truncated, ${text.length} chars total]`;\n}\n\nfunction truncateErrorResponseBody(body: string): string {\n return truncateText(body, MAX_ERROR_RESPONSE_BODY_LENGTH);\n}\n\nfunction getErrorCode(error: unknown): string | undefined {\n if (!error || typeof error !== 'object') {\n return undefined;\n }\n\n const code = (error as { code?: unknown }).code;\n return typeof code === 'string' ? code : undefined;\n}\n\nfunction formatErrorSummary(error: unknown): string {\n if (error instanceof Error) {\n const code = getErrorCode(error);\n const codeText = code ? ` [${code}]` : '';\n return `${error.name}${codeText}: ${error.message}`;\n }\n\n return String(error);\n}\n\nfunction formatFetchErrorForReport(error: unknown): string {\n const details = [formatErrorSummary(error)];\n const cause =\n error && typeof error === 'object'\n ? (error as { cause?: unknown }).cause\n : undefined;\n\n if (cause !== undefined) {\n details.push(`Cause: ${formatErrorSummary(cause)}`);\n }\n\n return truncateText(details.join('\\n'), MAX_FETCH_ERROR_LENGTH);\n}\n\n// Mirrors OpenAI SDK's default fetch selection:\n// openai@6.3.0 src/client.ts sets `this.fetch = options.fetch ?? Shims.getDefaultFetch()`,\n// and src/internal/shims.ts resolves that default to global `fetch`.\nfunction getDefaultFetch(): typeof fetch {\n if (typeof globalThis.fetch === 'function') {\n return globalThis.fetch;\n }\n\n throw new Error(\n '`fetch` is not defined as a global; check that the runtime provides globalThis.fetch or polyfill it before creating the OpenAI client',\n );\n}\n\nexport function wrapOpenAICompatibleFetch(\n context: OpenAIErrorResponseContext,\n): typeof fetch {\n const baseFetch = getDefaultFetch();\n let attempt = 0;\n\n return async (input, init) => {\n attempt += 1;\n let response: Response;\n try {\n response = await baseFetch(input, init);\n } catch (error) {\n const fetchErrorSummary = formatFetchErrorForReport(error);\n debugOpenAIFetch('OpenAI-compatible fetch failed', fetchErrorSummary);\n context.fetchErrors ??= [];\n context.fetchErrors.push({\n attempt,\n error: fetchErrorSummary,\n });\n throw error;\n }\n\n const requestId =\n response.headers.get('x-request-id') ??\n response.headers.get('x-model-request-id');\n\n if (requestId) {\n context.responseRequestIds ??= [];\n context.responseRequestIds.push({\n attempt,\n requestId,\n status: response.status,\n ok: response.ok,\n });\n }\n\n if (!response.ok) {\n // OpenAI SDK only exposes the `error` field for JSON error responses.\n // Non-standard provider bodies like `{ err: 'xxx' }` would otherwise be\n // hidden from Midscene's final error message.\n const rawResponseBody = await response\n .clone()\n .text()\n .catch(() => undefined);\n\n if (rawResponseBody !== undefined) {\n context.rawResponseBodies ??= [];\n context.rawResponseBodies.push({\n attempt,\n body: rawResponseBody,\n });\n }\n }\n\n return response;\n };\n}\n\nexport function formatOpenAIAPIErrorDetails(\n _error: unknown,\n context: OpenAIErrorResponseContext,\n): string {\n const details: string[] = [];\n\n if (context.rawResponseBodies?.length === 1) {\n details.push(\n `OpenAI raw error response body: ${truncateErrorResponseBody(\n context.rawResponseBodies[0].body,\n )}`,\n );\n } else if (context.rawResponseBodies?.length) {\n const rawResponseBodyDetails = context.rawResponseBodies\n .map(\n ({ attempt, body }) =>\n `Attempt ${attempt}: ${truncateErrorResponseBody(body)}`,\n )\n .join('\\n');\n\n details.push(\n `OpenAI raw error response bodies:\\n${rawResponseBodyDetails}`,\n );\n }\n\n const errorResponseRequestIds = context.responseRequestIds?.filter(\n ({ ok }) => !ok,\n );\n if (errorResponseRequestIds?.length === 1) {\n const { attempt, requestId, status } = errorResponseRequestIds[0];\n details.push(\n `OpenAI error response request ID (attempt ${attempt}, status ${status}): ${requestId}`,\n );\n } else if (errorResponseRequestIds?.length) {\n const requestIdDetails = errorResponseRequestIds\n .map(\n ({ attempt, requestId, status }) =>\n `Attempt ${attempt} (status ${status}): ${requestId}`,\n )\n .join('\\n');\n details.push(`OpenAI error response request IDs:\\n${requestIdDetails}`);\n }\n\n if (context.fetchErrors?.length === 1) {\n details.push(\n `OpenAI fetch error (attempt ${context.fetchErrors[0].attempt}): ${context.fetchErrors[0].error}`,\n );\n } else if (context.fetchErrors?.length) {\n const fetchErrorDetails = context.fetchErrors\n .map(({ attempt, error }) => `Attempt ${attempt}: ${error}`)\n .join('\\n');\n\n details.push(`OpenAI fetch errors:\\n${fetchErrorDetails}`);\n }\n\n if (!details.length) {\n return '';\n }\n\n return `\\n${details.join('\\n')}`;\n}\n"],"names":["MAX_ERROR_RESPONSE_BODY_LENGTH","MAX_FETCH_ERROR_LENGTH","debugOpenAIFetch","getDebug","truncateText","text","maxLength","truncateErrorResponseBody","body","getErrorCode","error","code","undefined","formatErrorSummary","Error","codeText","String","formatFetchErrorForReport","details","cause","getDefaultFetch","globalThis","wrapOpenAICompatibleFetch","context","baseFetch","attempt","input","init","response","fetchErrorSummary","requestId","rawResponseBody","formatOpenAIAPIErrorDetails","_error","rawResponseBodyDetails","errorResponseRequestIds","ok","status","requestIdDetails","fetchErrorDetails"],"mappings":"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"}
|