@midscene/core 1.12.6-beta-20260910093036.0 → 1.12.6

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 (84) hide show
  1. package/dist/es/agent/agent.mjs +12 -2
  2. package/dist/es/agent/agent.mjs.map +1 -1
  3. package/dist/es/agent/utils.mjs +1 -1
  4. package/dist/es/ai-model/service-caller/call-ai.mjs +49 -0
  5. package/dist/es/ai-model/service-caller/call-ai.mjs.map +1 -0
  6. package/dist/es/ai-model/service-caller/call.mjs +56 -0
  7. package/dist/es/ai-model/service-caller/call.mjs.map +1 -0
  8. package/dist/es/ai-model/service-caller/chat-completion/call.mjs +101 -0
  9. package/dist/es/ai-model/service-caller/chat-completion/call.mjs.map +1 -0
  10. package/dist/es/ai-model/service-caller/chat-completion/non-stream.mjs +80 -0
  11. package/dist/es/ai-model/service-caller/chat-completion/non-stream.mjs.map +1 -0
  12. package/dist/es/ai-model/service-caller/chat-completion/stream.mjs +87 -0
  13. package/dist/es/ai-model/service-caller/chat-completion/stream.mjs.map +1 -0
  14. package/dist/es/ai-model/service-caller/chat-completion/types.mjs +0 -0
  15. package/dist/es/ai-model/service-caller/chat-completion/utils.mjs +35 -0
  16. package/dist/es/ai-model/service-caller/chat-completion/utils.mjs.map +1 -0
  17. package/dist/es/ai-model/service-caller/codex/call-codex.mjs +77 -0
  18. package/dist/es/ai-model/service-caller/codex/call-codex.mjs.map +1 -0
  19. package/dist/es/ai-model/service-caller/{codex-app-server.mjs → codex/codex-app-server.mjs} +8 -17
  20. package/dist/es/ai-model/service-caller/codex/codex-app-server.mjs.map +1 -0
  21. package/dist/es/ai-model/service-caller/index.mjs +5 -582
  22. package/dist/es/ai-model/service-caller/openai-client.mjs +88 -0
  23. package/dist/es/ai-model/service-caller/openai-client.mjs.map +1 -0
  24. package/dist/es/ai-model/service-caller/proxy.mjs +66 -0
  25. package/dist/es/ai-model/service-caller/proxy.mjs.map +1 -0
  26. package/dist/es/ai-model/service-caller/utils.mjs +83 -0
  27. package/dist/es/ai-model/service-caller/utils.mjs.map +1 -0
  28. package/dist/es/report-html-template.mjs +1 -1
  29. package/dist/es/test-run-report.mjs.map +1 -1
  30. package/dist/es/types.mjs.map +1 -1
  31. package/dist/es/utils.mjs +1 -1
  32. package/dist/lib/agent/agent.js +12 -2
  33. package/dist/lib/agent/agent.js.map +1 -1
  34. package/dist/lib/agent/utils.js +1 -1
  35. package/dist/lib/ai-model/service-caller/call-ai.js +89 -0
  36. package/dist/lib/ai-model/service-caller/call-ai.js.map +1 -0
  37. package/dist/lib/ai-model/service-caller/call.js +90 -0
  38. package/dist/lib/ai-model/service-caller/call.js.map +1 -0
  39. package/dist/lib/ai-model/service-caller/chat-completion/call.js +135 -0
  40. package/dist/lib/ai-model/service-caller/chat-completion/call.js.map +1 -0
  41. package/dist/lib/ai-model/service-caller/chat-completion/non-stream.js +114 -0
  42. package/dist/lib/ai-model/service-caller/chat-completion/non-stream.js.map +1 -0
  43. package/dist/lib/ai-model/service-caller/chat-completion/stream.js +121 -0
  44. package/dist/lib/ai-model/service-caller/chat-completion/stream.js.map +1 -0
  45. package/dist/lib/ai-model/service-caller/chat-completion/types.js +20 -0
  46. package/dist/lib/ai-model/service-caller/chat-completion/types.js.map +1 -0
  47. package/dist/lib/ai-model/service-caller/chat-completion/utils.js +75 -0
  48. package/dist/lib/ai-model/service-caller/chat-completion/utils.js.map +1 -0
  49. package/dist/lib/ai-model/service-caller/codex/call-codex.js +111 -0
  50. package/dist/lib/ai-model/service-caller/codex/call-codex.js.map +1 -0
  51. package/dist/lib/ai-model/service-caller/{codex-app-server.js → codex/codex-app-server.js} +8 -17
  52. package/dist/lib/ai-model/service-caller/codex/codex-app-server.js.map +1 -0
  53. package/dist/lib/ai-model/service-caller/index.js +11 -596
  54. package/dist/lib/ai-model/service-caller/index.js.map +1 -1
  55. package/dist/lib/ai-model/service-caller/openai-client.js +132 -0
  56. package/dist/lib/ai-model/service-caller/openai-client.js.map +1 -0
  57. package/dist/lib/ai-model/service-caller/proxy.js +100 -0
  58. package/dist/lib/ai-model/service-caller/proxy.js.map +1 -0
  59. package/dist/lib/ai-model/service-caller/utils.js +144 -0
  60. package/dist/lib/ai-model/service-caller/utils.js.map +1 -0
  61. package/dist/lib/report-html-template.js +1 -1
  62. package/dist/lib/test-run-report.js.map +1 -1
  63. package/dist/lib/types.js.map +1 -1
  64. package/dist/lib/utils.js +1 -1
  65. package/dist/types/ai-model/service-caller/call-ai.d.ts +26 -0
  66. package/dist/types/ai-model/service-caller/call.d.ts +4 -0
  67. package/dist/types/ai-model/service-caller/chat-completion/call.d.ts +2 -0
  68. package/dist/types/ai-model/service-caller/chat-completion/non-stream.d.ts +2 -0
  69. package/dist/types/ai-model/service-caller/chat-completion/stream.d.ts +2 -0
  70. package/dist/types/ai-model/service-caller/chat-completion/types.d.ts +25 -0
  71. package/dist/types/ai-model/service-caller/chat-completion/utils.d.ts +12 -0
  72. package/dist/types/ai-model/service-caller/codex/call-codex.d.ts +2 -0
  73. package/dist/types/ai-model/service-caller/{codex-app-server.d.ts → codex/codex-app-server.d.ts} +4 -3
  74. package/dist/types/ai-model/service-caller/index.d.ts +4 -74
  75. package/dist/types/ai-model/service-caller/openai-client.d.ts +14 -0
  76. package/dist/types/ai-model/service-caller/proxy.d.ts +4 -0
  77. package/dist/types/ai-model/service-caller/types.d.ts +35 -0
  78. package/dist/types/ai-model/service-caller/utils.d.ts +40 -0
  79. package/dist/types/test-run-report.d.ts +0 -1
  80. package/dist/types/types.d.ts +3 -2
  81. package/package.json +4 -4
  82. package/dist/es/ai-model/service-caller/codex-app-server.mjs.map +0 -1
  83. package/dist/es/ai-model/service-caller/index.mjs.map +0 -1
  84. package/dist/lib/ai-model/service-caller/codex-app-server.js.map +0 -1
@@ -0,0 +1,56 @@
1
+ import { getDebug } from "@midscene/shared/logger";
2
+ import { assert, uuid } from "@midscene/shared/utils";
3
+ import { chat } from "./chat-completion/call.mjs";
4
+ import { callCodex } from "./codex/call-codex.mjs";
5
+ import { isCodexAppServerProvider } from "./codex/codex-app-server.mjs";
6
+ import { isModelCallRecordingEnabled, recordModelCallEvent } from "./model-call-recorder.mjs";
7
+ import { buildUsageInfo, nextInternalCallId } from "./utils.mjs";
8
+ async function callAI(messages, modelRuntime, options) {
9
+ const isStreaming = options?.stream === true;
10
+ if (isStreaming) assert('function' == typeof options?.onChunk, 'onChunk is required when stream is true');
11
+ const executionId = modelRuntime.executionId ?? `unscoped-${uuid()}`;
12
+ const internalCallId = nextInternalCallId();
13
+ const { config: modelConfig } = modelRuntime;
14
+ const recordEvent = isModelCallRecordingEnabled() ? (event)=>{
15
+ recordModelCallEvent({
16
+ executionId,
17
+ callId: internalCallId,
18
+ semanticRetryAttempt: options?.semanticRetryAttempt,
19
+ slot: modelConfig.slot,
20
+ intent: modelConfig.intent,
21
+ modelFamily: modelConfig.modelFamily,
22
+ ...event
23
+ });
24
+ } : void 0;
25
+ const context = {
26
+ messages,
27
+ modelRuntime,
28
+ options,
29
+ executionId,
30
+ recordEvent
31
+ };
32
+ const result = await (isCodexAppServerProvider(modelConfig.openaiBaseURL) ? callCodex(context) : chat(context));
33
+ const { rawUsage, timeCost, requestId, responseModelName, ...response } = result;
34
+ const usage = buildUsageInfo({
35
+ usageData: rawUsage,
36
+ timeCost,
37
+ requestId,
38
+ responseModelName,
39
+ modelName: modelConfig.modelName,
40
+ modelDescription: modelConfig.modelDescription,
41
+ slot: modelConfig.slot,
42
+ internalCallId
43
+ });
44
+ const debugProfileStats = getDebug('ai:profile:stats');
45
+ const debugProfileDetail = getDebug('ai:profile:detail');
46
+ debugProfileStats(`model, ${modelConfig.modelName}, mode, ${modelConfig.modelFamily || 'default'}, streaming, ${response.isStreamed}, prompt-tokens, ${usage?.prompt_tokens ?? ''}, completion-tokens, ${usage?.completion_tokens ?? ''}, total-tokens, ${usage?.total_tokens ?? ''}, cached-input, ${usage?.cached_input ?? ''}, cost-ms, ${timeCost ?? ''}, requestId, ${requestId ?? ''}, slot, ${modelConfig.slot}, configured-temperature, ${modelConfig.temperature ?? ''}`);
47
+ debugProfileDetail(`model usage detail: ${JSON.stringify(usage)}`);
48
+ if (usage && modelRuntime.onUsage) modelRuntime.onUsage(usage);
49
+ return {
50
+ ...response,
51
+ usage
52
+ };
53
+ }
54
+ export { callAI };
55
+
56
+ //# sourceMappingURL=call.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-model/service-caller/call.mjs","sources":["../../../../src/ai-model/service-caller/call.ts"],"sourcesContent":["import { getDebug } from '@midscene/shared/logger';\nimport { assert, uuid } from '@midscene/shared/utils';\nimport type { ChatCompletionMessageParam } from 'openai/resources/index';\nimport type { ModelRuntime } from '../models';\nimport { chat } from './chat-completion/call';\nimport { callCodex } from './codex/call-codex';\nimport { isCodexAppServerProvider } from './codex/codex-app-server';\nimport {\n isModelCallRecordingEnabled,\n recordModelCallEvent,\n} from './model-call-recorder';\nimport type { AICallResult, CallAIOptions, ModelCallContext } from './types';\nimport { buildUsageInfo, nextInternalCallId } from './utils';\n\nexport async function callAI(\n messages: ChatCompletionMessageParam[],\n modelRuntime: ModelRuntime,\n options?: CallAIOptions,\n): Promise<AICallResult> {\n const isStreaming = options?.stream === true;\n if (isStreaming) {\n assert(\n typeof options?.onChunk === 'function',\n 'onChunk is required when stream is true',\n );\n }\n\n // Low-level callers without a TaskRunner still need a stable ID for the\n // lifetime of this model call (including its network retries).\n const executionId = modelRuntime.executionId ?? `unscoped-${uuid()}`;\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 const { config: modelConfig } = modelRuntime;\n\n const recordEvent = isModelCallRecordingEnabled()\n ? (event: Record<string, unknown>) => {\n void recordModelCallEvent({\n executionId,\n callId: internalCallId,\n semanticRetryAttempt: options?.semanticRetryAttempt,\n slot: modelConfig.slot,\n intent: modelConfig.intent,\n modelFamily: modelConfig.modelFamily,\n ...event,\n });\n }\n : undefined;\n\n const context: ModelCallContext = {\n messages,\n modelRuntime,\n options,\n executionId,\n recordEvent,\n };\n\n const result = await (isCodexAppServerProvider(modelConfig.openaiBaseURL)\n ? callCodex(context)\n : chat(context));\n\n const { rawUsage, timeCost, requestId, responseModelName, ...response } =\n result;\n\n const usage = buildUsageInfo({\n usageData: rawUsage,\n timeCost,\n requestId,\n responseModelName,\n modelName: modelConfig.modelName,\n modelDescription: modelConfig.modelDescription,\n slot: modelConfig.slot,\n internalCallId,\n });\n\n const debugProfileStats = getDebug('ai:profile:stats');\n const debugProfileDetail = getDebug('ai:profile:detail');\n debugProfileStats(\n `model, ${modelConfig.modelName}, mode, ${modelConfig.modelFamily || 'default'}, streaming, ${response.isStreamed}, prompt-tokens, ${usage?.prompt_tokens ?? ''}, completion-tokens, ${usage?.completion_tokens ?? ''}, total-tokens, ${usage?.total_tokens ?? ''}, cached-input, ${usage?.cached_input ?? ''}, cost-ms, ${timeCost ?? ''}, requestId, ${requestId ?? ''}, slot, ${modelConfig.slot}, configured-temperature, ${modelConfig.temperature ?? ''}`,\n );\n debugProfileDetail(`model usage detail: ${JSON.stringify(usage)}`);\n\n if (usage && modelRuntime.onUsage) {\n modelRuntime.onUsage(usage);\n }\n return { ...response, usage };\n}\n"],"names":["callAI","messages","modelRuntime","options","isStreaming","assert","executionId","uuid","internalCallId","nextInternalCallId","modelConfig","recordEvent","isModelCallRecordingEnabled","event","recordModelCallEvent","undefined","context","result","isCodexAppServerProvider","callCodex","chat","rawUsage","timeCost","requestId","responseModelName","response","usage","buildUsageInfo","debugProfileStats","getDebug","debugProfileDetail","JSON"],"mappings":";;;;;;;AAcO,eAAeA,OACpBC,QAAsC,EACtCC,YAA0B,EAC1BC,OAAuB;IAEvB,MAAMC,cAAcD,SAAS,WAAW;IACxC,IAAIC,aACFC,OACE,AAA4B,cAA5B,OAAOF,SAAS,SAChB;IAMJ,MAAMG,cAAcJ,aAAa,WAAW,IAAI,CAAC,SAAS,EAAEK,QAAQ;IAKpE,MAAMC,iBAAiBC;IAEvB,MAAM,EAAE,QAAQC,WAAW,EAAE,GAAGR;IAEhC,MAAMS,cAAcC,gCAChB,CAACC;QACMC,qBAAqB;YACxBR;YACA,QAAQE;YACR,sBAAsBL,SAAS;YAC/B,MAAMO,YAAY,IAAI;YACtB,QAAQA,YAAY,MAAM;YAC1B,aAAaA,YAAY,WAAW;YACpC,GAAGG,KAAK;QACV;IACF,IACAE;IAEJ,MAAMC,UAA4B;QAChCf;QACAC;QACAC;QACAG;QACAK;IACF;IAEA,MAAMM,SAAS,MAAOC,CAAAA,yBAAyBR,YAAY,aAAa,IACpES,UAAUH,WACVI,KAAKJ,QAAO;IAEhB,MAAM,EAAEK,QAAQ,EAAEC,QAAQ,EAAEC,SAAS,EAAEC,iBAAiB,EAAE,GAAGC,UAAU,GACrER;IAEF,MAAMS,QAAQC,eAAe;QAC3B,WAAWN;QACXC;QACAC;QACAC;QACA,WAAWd,YAAY,SAAS;QAChC,kBAAkBA,YAAY,gBAAgB;QAC9C,MAAMA,YAAY,IAAI;QACtBF;IACF;IAEA,MAAMoB,oBAAoBC,SAAS;IACnC,MAAMC,qBAAqBD,SAAS;IACpCD,kBACE,CAAC,OAAO,EAAElB,YAAY,SAAS,CAAC,QAAQ,EAAEA,YAAY,WAAW,IAAI,UAAU,aAAa,EAAEe,SAAS,UAAU,CAAC,iBAAiB,EAAEC,OAAO,iBAAiB,GAAG,qBAAqB,EAAEA,OAAO,qBAAqB,GAAG,gBAAgB,EAAEA,OAAO,gBAAgB,GAAG,gBAAgB,EAAEA,OAAO,gBAAgB,GAAG,WAAW,EAAEJ,YAAY,GAAG,aAAa,EAAEC,aAAa,GAAG,QAAQ,EAAEb,YAAY,IAAI,CAAC,0BAA0B,EAAEA,YAAY,WAAW,IAAI,IAAI;IAEjcoB,mBAAmB,CAAC,oBAAoB,EAAEC,KAAK,SAAS,CAACL,QAAQ;IAEjE,IAAIA,SAASxB,aAAa,OAAO,EAC/BA,aAAa,OAAO,CAACwB;IAEvB,OAAO;QAAE,GAAGD,QAAQ;QAAEC;IAAM;AAC9B"}
@@ -0,0 +1,101 @@
1
+ import { getDebug } from "@midscene/shared/logger";
2
+ import { createChatClient } from "../openai-client.mjs";
3
+ import { formatOpenAIAPIErrorDetails } from "../openai-error.mjs";
4
+ import { resolveEffectiveTimeoutMs } from "../request-timeout.mjs";
5
+ import { AIResponseParseError, getLatestResponseAttempt, stringifyForDebug } from "../utils.mjs";
6
+ import { callChatCompletionNonStreaming } from "./non-stream.mjs";
7
+ import { callChatCompletionStream } from "./stream.mjs";
8
+ import { applyImageDetail } from "./utils.mjs";
9
+ const chat = async ({ messages, modelRuntime, options, executionId, recordEvent })=>{
10
+ const debugCall = getDebug('ai:call');
11
+ const warnCall = getDebug('ai:call', {
12
+ console: true
13
+ });
14
+ const { config: modelConfig, adapter } = modelRuntime;
15
+ const isStreaming = options?.stream === true;
16
+ const modelCallInput = {
17
+ intent: modelConfig.intent,
18
+ userConfig: {
19
+ temperature: modelConfig.temperature,
20
+ reasoningEnabled: modelConfig.reasoningEnabled,
21
+ reasoningEffort: modelConfig.reasoningEffort,
22
+ reasoningBudget: modelConfig.reasoningBudget,
23
+ responseFormat: modelConfig.responseFormat
24
+ },
25
+ semanticRetryAttempt: options?.semanticRetryAttempt,
26
+ requiresOriginalImageDetail: options?.requiresOriginalImageDetail,
27
+ expectedJsonObjectResponse: options?.expectedJsonObjectResponse
28
+ };
29
+ const { config: adapterChatCompletionParams } = adapter.chatCompletion.buildChatCompletionParams(modelCallInput);
30
+ debugCall(`adapter chat completion params: ${stringifyForDebug({
31
+ config: adapterChatCompletionParams
32
+ })}`);
33
+ const requestConfig = {
34
+ ...adapterChatCompletionParams,
35
+ ...modelConfig.extraBody ?? {}
36
+ };
37
+ const imageDetail = adapter.chatCompletion.resolveImageDetail(modelCallInput);
38
+ const messagesWithImageDetail = applyImageDetail({
39
+ imageDetail,
40
+ messages
41
+ });
42
+ const effectiveTimeoutMs = resolveEffectiveTimeoutMs(modelConfig);
43
+ const { completion, modelName, openAIErrorResponseContext } = await createChatClient({
44
+ modelConfig,
45
+ executionId,
46
+ recordEvent
47
+ });
48
+ debugCall(`sending ${isStreaming ? 'streaming ' : ''}request to ${modelName}`);
49
+ const callChatCompletion = isStreaming ? callChatCompletionStream : callChatCompletionNonStreaming;
50
+ try {
51
+ const startTime = Date.now();
52
+ const { content, reasoningContent, rawChoiceMessage, rawUsage, requestId, responseModelName } = await callChatCompletion({
53
+ completion,
54
+ modelName,
55
+ openAIErrorResponseContext,
56
+ modelRuntime,
57
+ messages: messagesWithImageDetail,
58
+ requestConfig,
59
+ effectiveTimeoutMs,
60
+ abortSignal: options?.abortSignal,
61
+ onChunk: options?.onChunk,
62
+ recordEvent
63
+ });
64
+ const timeCost = Date.now() - startTime;
65
+ debugCall(`response reasoning content: ${reasoningContent}`);
66
+ debugCall(`response content: ${content}`);
67
+ recordEvent?.({
68
+ type: 'response',
69
+ attempt: getLatestResponseAttempt(openAIErrorResponseContext),
70
+ http: openAIErrorResponseContext.httpResponses?.at(-1),
71
+ final: {
72
+ content,
73
+ reasoningContent,
74
+ usage: rawUsage,
75
+ requestId,
76
+ timeCost,
77
+ responseModelName
78
+ }
79
+ });
80
+ return {
81
+ content,
82
+ reasoning_content: reasoningContent,
83
+ rawChoiceMessage,
84
+ rawUsage,
85
+ isStreamed: isStreaming,
86
+ timeCost,
87
+ requestId,
88
+ responseModelName
89
+ };
90
+ } catch (e) {
91
+ warnCall('call AI error', e);
92
+ if (e instanceof AIResponseParseError) throw e;
93
+ const newError = new Error(`failed to call ${isStreaming ? 'streaming ' : ''}AI model service (${modelName}): ${e.message}${formatOpenAIAPIErrorDetails(e, openAIErrorResponseContext)}\nTrouble shooting: https://midscenejs.com/model-provider.html`, {
94
+ cause: e
95
+ });
96
+ throw newError;
97
+ }
98
+ };
99
+ export { chat };
100
+
101
+ //# sourceMappingURL=call.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-model/service-caller/chat-completion/call.mjs","sources":["../../../../../src/ai-model/service-caller/chat-completion/call.ts"],"sourcesContent":["import { getDebug } from '@midscene/shared/logger';\nimport type { ChatCompletionCallInput } from '../../model-adapter/types';\nimport { createChatClient } from '../openai-client';\nimport { formatOpenAIAPIErrorDetails } from '../openai-error';\nimport { resolveEffectiveTimeoutMs } from '../request-timeout';\nimport type { ModelCallContext, ModelCallResult } from '../types';\nimport {\n AIResponseParseError,\n getLatestResponseAttempt,\n stringifyForDebug,\n} from '../utils';\nimport { callChatCompletionNonStreaming } from './non-stream';\nimport { callChatCompletionStream } from './stream';\nimport { applyImageDetail } from './utils';\n\nexport const chat = async ({\n messages,\n modelRuntime,\n options,\n executionId,\n recordEvent,\n}: ModelCallContext): Promise<ModelCallResult> => {\n const debugCall = getDebug('ai:call');\n const warnCall = getDebug('ai:call', { console: true });\n\n const { config: modelConfig, adapter } = modelRuntime;\n\n const isStreaming = options?.stream === true;\n\n const modelCallInput: ChatCompletionCallInput = {\n intent: modelConfig.intent,\n userConfig: {\n temperature: modelConfig.temperature,\n reasoningEnabled: modelConfig.reasoningEnabled,\n reasoningEffort: modelConfig.reasoningEffort,\n reasoningBudget: modelConfig.reasoningBudget,\n responseFormat: modelConfig.responseFormat,\n },\n semanticRetryAttempt: options?.semanticRetryAttempt,\n requiresOriginalImageDetail: options?.requiresOriginalImageDetail,\n expectedJsonObjectResponse: options?.expectedJsonObjectResponse,\n };\n\n const { config: adapterChatCompletionParams } =\n adapter.chatCompletion.buildChatCompletionParams(modelCallInput);\n\n debugCall(\n `adapter chat completion params: ${stringifyForDebug({\n config: adapterChatCompletionParams,\n })}`,\n );\n\n const requestConfig = {\n ...adapterChatCompletionParams,\n ...(modelConfig.extraBody ?? {}),\n };\n\n const imageDetail = adapter.chatCompletion.resolveImageDetail(modelCallInput);\n\n // Some adapters request original image detail to preserve screenshot\n // resolution for localization-sensitive tasks.\n const messagesWithImageDetail = applyImageDetail({ imageDetail, messages });\n\n const effectiveTimeoutMs = resolveEffectiveTimeoutMs(modelConfig);\n\n const { completion, modelName, openAIErrorResponseContext } =\n await createChatClient({\n modelConfig,\n executionId,\n recordEvent,\n });\n\n debugCall(\n `sending ${isStreaming ? 'streaming ' : ''}request to ${modelName}`,\n );\n\n const callChatCompletion = isStreaming\n ? callChatCompletionStream\n : callChatCompletionNonStreaming;\n\n try {\n const startTime = Date.now();\n\n const {\n content,\n reasoningContent,\n rawChoiceMessage,\n rawUsage,\n requestId,\n responseModelName,\n } = await callChatCompletion({\n completion,\n modelName,\n openAIErrorResponseContext,\n modelRuntime,\n messages: messagesWithImageDetail,\n requestConfig,\n effectiveTimeoutMs,\n abortSignal: options?.abortSignal,\n onChunk: options?.onChunk,\n recordEvent,\n });\n\n const timeCost = Date.now() - startTime;\n debugCall(`response reasoning content: ${reasoningContent}`);\n debugCall(`response content: ${content}`);\n\n recordEvent?.({\n type: 'response',\n attempt: getLatestResponseAttempt(openAIErrorResponseContext),\n http: openAIErrorResponseContext.httpResponses?.at(-1),\n final: {\n content,\n reasoningContent,\n usage: rawUsage,\n requestId,\n timeCost,\n responseModelName,\n },\n });\n\n return {\n content,\n reasoning_content: reasoningContent,\n rawChoiceMessage,\n rawUsage,\n isStreamed: isStreaming,\n timeCost,\n requestId,\n responseModelName,\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"],"names":["chat","messages","modelRuntime","options","executionId","recordEvent","debugCall","getDebug","warnCall","modelConfig","adapter","isStreaming","modelCallInput","adapterChatCompletionParams","stringifyForDebug","requestConfig","imageDetail","messagesWithImageDetail","applyImageDetail","effectiveTimeoutMs","resolveEffectiveTimeoutMs","completion","modelName","openAIErrorResponseContext","createChatClient","callChatCompletion","callChatCompletionStream","callChatCompletionNonStreaming","startTime","Date","content","reasoningContent","rawChoiceMessage","rawUsage","requestId","responseModelName","timeCost","getLatestResponseAttempt","e","AIResponseParseError","newError","Error","formatOpenAIAPIErrorDetails"],"mappings":";;;;;;;;AAeO,MAAMA,OAAO,OAAO,EACzBC,QAAQ,EACRC,YAAY,EACZC,OAAO,EACPC,WAAW,EACXC,WAAW,EACM;IACjB,MAAMC,YAAYC,SAAS;IAC3B,MAAMC,WAAWD,SAAS,WAAW;QAAE,SAAS;IAAK;IAErD,MAAM,EAAE,QAAQE,WAAW,EAAEC,OAAO,EAAE,GAAGR;IAEzC,MAAMS,cAAcR,SAAS,WAAW;IAExC,MAAMS,iBAA0C;QAC9C,QAAQH,YAAY,MAAM;QAC1B,YAAY;YACV,aAAaA,YAAY,WAAW;YACpC,kBAAkBA,YAAY,gBAAgB;YAC9C,iBAAiBA,YAAY,eAAe;YAC5C,iBAAiBA,YAAY,eAAe;YAC5C,gBAAgBA,YAAY,cAAc;QAC5C;QACA,sBAAsBN,SAAS;QAC/B,6BAA6BA,SAAS;QACtC,4BAA4BA,SAAS;IACvC;IAEA,MAAM,EAAE,QAAQU,2BAA2B,EAAE,GAC3CH,QAAQ,cAAc,CAAC,yBAAyB,CAACE;IAEnDN,UACE,CAAC,gCAAgC,EAAEQ,kBAAkB;QACnD,QAAQD;IACV,IAAI;IAGN,MAAME,gBAAgB;QACpB,GAAGF,2BAA2B;QAC9B,GAAIJ,YAAY,SAAS,IAAI,CAAC,CAAC;IACjC;IAEA,MAAMO,cAAcN,QAAQ,cAAc,CAAC,kBAAkB,CAACE;IAI9D,MAAMK,0BAA0BC,iBAAiB;QAAEF;QAAaf;IAAS;IAEzE,MAAMkB,qBAAqBC,0BAA0BX;IAErD,MAAM,EAAEY,UAAU,EAAEC,SAAS,EAAEC,0BAA0B,EAAE,GACzD,MAAMC,iBAAiB;QACrBf;QACAL;QACAC;IACF;IAEFC,UACE,CAAC,QAAQ,EAAEK,cAAc,eAAe,GAAG,WAAW,EAAEW,WAAW;IAGrE,MAAMG,qBAAqBd,cACvBe,2BACAC;IAEJ,IAAI;QACF,MAAMC,YAAYC,KAAK,GAAG;QAE1B,MAAM,EACJC,OAAO,EACPC,gBAAgB,EAChBC,gBAAgB,EAChBC,QAAQ,EACRC,SAAS,EACTC,iBAAiB,EAClB,GAAG,MAAMV,mBAAmB;YAC3BJ;YACAC;YACAC;YACArB;YACA,UAAUe;YACVF;YACAI;YACA,aAAahB,SAAS;YACtB,SAASA,SAAS;YAClBE;QACF;QAEA,MAAM+B,WAAWP,KAAK,GAAG,KAAKD;QAC9BtB,UAAU,CAAC,4BAA4B,EAAEyB,kBAAkB;QAC3DzB,UAAU,CAAC,kBAAkB,EAAEwB,SAAS;QAExCzB,cAAc;YACZ,MAAM;YACN,SAASgC,yBAAyBd;YAClC,MAAMA,2BAA2B,aAAa,EAAE,GAAG;YACnD,OAAO;gBACLO;gBACAC;gBACA,OAAOE;gBACPC;gBACAE;gBACAD;YACF;QACF;QAEA,OAAO;YACLL;YACA,mBAAmBC;YACnBC;YACAC;YACA,YAAYtB;YACZyB;YACAF;YACAC;QACF;IACF,EAAE,OAAOG,GAAQ;QACf9B,SAAS,iBAAiB8B;QAE1B,IAAIA,aAAaC,sBACf,MAAMD;QAGR,MAAME,WAAW,IAAIC,MACnB,CAAC,eAAe,EAAE9B,cAAc,eAAe,GAAG,kBAAkB,EAAEW,UAAU,GAAG,EAAEgB,EAAE,OAAO,GAAGI,4BAA4BJ,GAAGf,4BAA4B,8DAA8D,CAAC,EAC3N;YACE,OAAOe;QACT;QAEF,MAAME;IACR;AACF"}
@@ -0,0 +1,80 @@
1
+ import { getDebug } from "@midscene/shared/logger";
2
+ import { assert } from "@midscene/shared/utils";
3
+ import { buildRequestAbortSignal, isHardTimeoutError, restoreHardTimeoutError } from "../request-timeout.mjs";
4
+ import { AIResponseParseError, appendAIRequestFailureSummary, getLatestSuccessfulResponseRequestId, normalizeRetryCount, toError } from "../utils.mjs";
5
+ import { hasUsableText, resolveContentWithReasoningFallback } from "./utils.mjs";
6
+ const callChatCompletionNonStreaming = async ({ completion, modelName, openAIErrorResponseContext, modelRuntime, messages, requestConfig, effectiveTimeoutMs, abortSignal })=>{
7
+ const { config: modelConfig, adapter } = modelRuntime;
8
+ const warnCall = getDebug('ai:call', {
9
+ console: true
10
+ });
11
+ let content;
12
+ let reasoningContent = '';
13
+ let rawChoiceMessage;
14
+ let usage;
15
+ let requestId;
16
+ let responseModelName;
17
+ const retryCount = normalizeRetryCount(modelConfig.retryCount);
18
+ const retryInterval = modelConfig.retryInterval ?? 2000;
19
+ const maxAttempts = retryCount + 1;
20
+ let lastError;
21
+ const attemptErrors = [];
22
+ for(let attempt = 1; attempt <= maxAttempts; attempt++){
23
+ const { signal: attemptSignal, cleanup: cleanupAttemptSignal } = buildRequestAbortSignal(effectiveTimeoutMs, abortSignal);
24
+ try {
25
+ const result = await completion.create({
26
+ model: modelName,
27
+ messages,
28
+ ...requestConfig,
29
+ stream: false
30
+ }, {
31
+ signal: attemptSignal
32
+ });
33
+ requestId = getLatestSuccessfulResponseRequestId(openAIErrorResponseContext) ?? result._request_id;
34
+ if (!result.choices) throw new Error(`invalid response from LLM service: ${JSON.stringify(result)}`);
35
+ rawChoiceMessage = result.choices[0].message;
36
+ const parsedMessage = adapter.chatCompletion.extractContentAndReasoning(result.choices[0].message);
37
+ content = parsedMessage.content;
38
+ reasoningContent = parsedMessage.reasoning_content;
39
+ usage = result.usage;
40
+ responseModelName = result.model;
41
+ content = resolveContentWithReasoningFallback({
42
+ content,
43
+ reasoningContent,
44
+ useReasoningAsContentFallback: adapter.chatCompletion.useReasoningAsContentFallback
45
+ });
46
+ if (!hasUsableText(content)) throw new AIResponseParseError('empty content from AI model', content || '', void 0, rawChoiceMessage);
47
+ break;
48
+ } catch (error) {
49
+ lastError = restoreHardTimeoutError(toError(error), attemptSignal);
50
+ attemptErrors.push({
51
+ attempt,
52
+ error: lastError
53
+ });
54
+ const wasHardTimeout = isHardTimeoutError(lastError);
55
+ if (wasHardTimeout) warnCall(`AI call hit hard timeout (${effectiveTimeoutMs}ms, attempt ${attempt}/${maxAttempts}, model ${modelName}, slot ${modelConfig.slot})`);
56
+ if (abortSignal?.aborted) break;
57
+ if (attempt < maxAttempts) {
58
+ warnCall(`AI call failed (attempt ${attempt}/${maxAttempts}), retrying in ${retryInterval}ms... Error: ${lastError.message}`);
59
+ await new Promise((resolve)=>setTimeout(resolve, retryInterval));
60
+ }
61
+ } finally{
62
+ cleanupAttemptSignal();
63
+ }
64
+ }
65
+ if (!content) {
66
+ assert(lastError, 'AI model request failed without recording an attempt error');
67
+ throw appendAIRequestFailureSummary(lastError, attemptErrors, maxAttempts);
68
+ }
69
+ return {
70
+ content,
71
+ reasoningContent,
72
+ rawChoiceMessage,
73
+ rawUsage: usage,
74
+ requestId,
75
+ responseModelName
76
+ };
77
+ };
78
+ export { callChatCompletionNonStreaming };
79
+
80
+ //# sourceMappingURL=non-stream.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-model/service-caller/chat-completion/non-stream.mjs","sources":["../../../../../src/ai-model/service-caller/chat-completion/non-stream.ts"],"sourcesContent":["import { getDebug } from '@midscene/shared/logger';\nimport { assert } from '@midscene/shared/utils';\nimport type OpenAI from 'openai';\nimport {\n buildRequestAbortSignal,\n isHardTimeoutError,\n restoreHardTimeoutError,\n} from '../request-timeout';\nimport {\n AIResponseParseError,\n appendAIRequestFailureSummary,\n getLatestSuccessfulResponseRequestId,\n normalizeRetryCount,\n toError,\n} from '../utils';\nimport type {\n ChatCompletionCallOptions,\n ChatCompletionCallResult,\n} from './types';\nimport { hasUsableText, resolveContentWithReasoningFallback } from './utils';\n\nexport const callChatCompletionNonStreaming = async ({\n completion,\n modelName,\n openAIErrorResponseContext,\n modelRuntime,\n messages,\n requestConfig,\n effectiveTimeoutMs,\n abortSignal,\n}: ChatCompletionCallOptions): Promise<ChatCompletionCallResult> => {\n const { config: modelConfig, adapter } = modelRuntime;\n const warnCall = getDebug('ai:call', { console: true });\n let content: string | undefined;\n let reasoningContent = '';\n let rawChoiceMessage: unknown;\n let usage: OpenAI.CompletionUsage | undefined;\n let requestId: string | null | undefined;\n let responseModelName: string | undefined;\n\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, abortSignal);\n try {\n const result = await completion.create(\n {\n model: modelName,\n messages,\n ...requestConfig,\n stream: false,\n } as any,\n { signal: attemptSignal },\n );\n\n requestId =\n getLatestSuccessfulResponseRequestId(openAIErrorResponseContext) ??\n result._request_id;\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 = adapter.chatCompletion.extractContentAndReasoning(\n result.choices[0].message,\n );\n content = parsedMessage.content;\n reasoningContent = parsedMessage.reasoning_content;\n usage = result.usage;\n responseModelName = result.model;\n\n content = resolveContentWithReasoningFallback({\n content,\n reasoningContent,\n useReasoningAsContentFallback:\n adapter.chatCompletion.useReasoningAsContentFallback,\n });\n\n if (!hasUsableText(content)) {\n throw new AIResponseParseError(\n 'empty content from AI model',\n content || '',\n undefined,\n rawChoiceMessage,\n );\n }\n\n break; // Success, exit retry loop\n } catch (error) {\n lastError = restoreHardTimeoutError(toError(error), attemptSignal);\n attemptErrors.push({ attempt, error: lastError });\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 (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(lastError, attemptErrors, maxAttempts);\n }\n return {\n content,\n reasoningContent,\n rawChoiceMessage,\n rawUsage: usage,\n requestId,\n responseModelName,\n };\n};\n"],"names":["callChatCompletionNonStreaming","completion","modelName","openAIErrorResponseContext","modelRuntime","messages","requestConfig","effectiveTimeoutMs","abortSignal","modelConfig","adapter","warnCall","getDebug","content","reasoningContent","rawChoiceMessage","usage","requestId","responseModelName","retryCount","normalizeRetryCount","retryInterval","maxAttempts","lastError","attemptErrors","attempt","attemptSignal","cleanupAttemptSignal","buildRequestAbortSignal","result","getLatestSuccessfulResponseRequestId","Error","JSON","parsedMessage","resolveContentWithReasoningFallback","hasUsableText","AIResponseParseError","undefined","error","restoreHardTimeoutError","toError","wasHardTimeout","isHardTimeoutError","Promise","resolve","setTimeout","assert","appendAIRequestFailureSummary"],"mappings":";;;;;AAqBO,MAAMA,iCAAiC,OAAO,EACnDC,UAAU,EACVC,SAAS,EACTC,0BAA0B,EAC1BC,YAAY,EACZC,QAAQ,EACRC,aAAa,EACbC,kBAAkB,EAClBC,WAAW,EACe;IAC1B,MAAM,EAAE,QAAQC,WAAW,EAAEC,OAAO,EAAE,GAAGN;IACzC,MAAMO,WAAWC,SAAS,WAAW;QAAE,SAAS;IAAK;IACrD,IAAIC;IACJ,IAAIC,mBAAmB;IACvB,IAAIC;IACJ,IAAIC;IACJ,IAAIC;IACJ,IAAIC;IAGJ,MAAMC,aAAaC,oBAAoBX,YAAY,UAAU;IAC7D,MAAMY,gBAAgBZ,YAAY,aAAa,IAAI;IACnD,MAAMa,cAAcH,aAAa;IAEjC,IAAII;IACJ,MAAMC,gBAA4D,EAAE;IAEpE,IAAK,IAAIC,UAAU,GAAGA,WAAWH,aAAaG,UAAW;QACvD,MAAM,EAAE,QAAQC,aAAa,EAAE,SAASC,oBAAoB,EAAE,GAC5DC,wBAAwBrB,oBAAoBC;QAC9C,IAAI;YACF,MAAMqB,SAAS,MAAM5B,WAAW,MAAM,CACpC;gBACE,OAAOC;gBACPG;gBACA,GAAGC,aAAa;gBAChB,QAAQ;YACV,GACA;gBAAE,QAAQoB;YAAc;YAG1BT,YACEa,qCAAqC3B,+BACrC0B,OAAO,WAAW;YAEpB,IAAI,CAACA,OAAO,OAAO,EACjB,MAAM,IAAIE,MACR,CAAC,mCAAmC,EAAEC,KAAK,SAAS,CAACH,SAAS;YAIlEd,mBAAmBc,OAAO,OAAO,CAAC,EAAE,CAAC,OAAO;YAC5C,MAAMI,gBAAgBvB,QAAQ,cAAc,CAAC,0BAA0B,CACrEmB,OAAO,OAAO,CAAC,EAAE,CAAC,OAAO;YAE3BhB,UAAUoB,cAAc,OAAO;YAC/BnB,mBAAmBmB,cAAc,iBAAiB;YAClDjB,QAAQa,OAAO,KAAK;YACpBX,oBAAoBW,OAAO,KAAK;YAEhChB,UAAUqB,oCAAoC;gBAC5CrB;gBACAC;gBACA,+BACEJ,QAAQ,cAAc,CAAC,6BAA6B;YACxD;YAEA,IAAI,CAACyB,cAActB,UACjB,MAAM,IAAIuB,qBACR,+BACAvB,WAAW,IACXwB,QACAtB;YAIJ;QACF,EAAE,OAAOuB,OAAO;YACdf,YAAYgB,wBAAwBC,QAAQF,QAAQZ;YACpDF,cAAc,IAAI,CAAC;gBAAEC;gBAAS,OAAOF;YAAU;YAC/C,MAAMkB,iBAAiBC,mBAAmBnB;YAC1C,IAAIkB,gBACF9B,SACE,CAAC,0BAA0B,EAAEJ,mBAAmB,YAAY,EAAEkB,QAAQ,CAAC,EAAEH,YAAY,QAAQ,EAAEpB,UAAU,OAAO,EAAEO,YAAY,IAAI,CAAC,CAAC,CAAC;YAIzI,IAAID,aAAa,SACf;YAEF,IAAIiB,UAAUH,aAAa;gBACzBX,SACE,CAAC,wBAAwB,EAAEc,QAAQ,CAAC,EAAEH,YAAY,eAAe,EAAED,cAAc,aAAa,EAAEE,UAAU,OAAO,EAAE;gBAErH,MAAM,IAAIoB,QAAQ,CAACC,UAAYC,WAAWD,SAASvB;YACrD;QACF,SAAU;YACRM;QACF;IACF;IAEA,IAAI,CAACd,SAAS;QACZiC,OACEvB,WACA;QAEF,MAAMwB,8BAA8BxB,WAAWC,eAAeF;IAChE;IACA,OAAO;QACLT;QACAC;QACAC;QACA,UAAUC;QACVC;QACAC;IACF;AACF"}
@@ -0,0 +1,87 @@
1
+ import { assert } from "@midscene/shared/utils";
2
+ import { buildRequestAbortSignal, restoreHardTimeoutError } from "../request-timeout.mjs";
3
+ import { getLatestResponseAttempt, getLatestSuccessfulResponseRequestId, toError } from "../utils.mjs";
4
+ import { resolveContentWithReasoningFallback } from "./utils.mjs";
5
+ const callChatCompletionStream = async ({ completion, modelName, openAIErrorResponseContext, modelRuntime, messages, requestConfig, effectiveTimeoutMs, abortSignal, onChunk, recordEvent })=>{
6
+ assert('function' == typeof onChunk, 'onChunk is required when stream is true');
7
+ const { adapter } = modelRuntime;
8
+ let accumulated = '';
9
+ let accumulatedReasoning = '';
10
+ let usage;
11
+ let requestId;
12
+ let responseModelName;
13
+ const { signal: streamSignal, cleanup: cleanupStreamSignal } = buildRequestAbortSignal(effectiveTimeoutMs, abortSignal);
14
+ try {
15
+ const stream = await completion.create({
16
+ model: modelName,
17
+ messages,
18
+ ...requestConfig,
19
+ stream: true,
20
+ stream_options: {
21
+ ...requestConfig.stream_options,
22
+ include_usage: true
23
+ }
24
+ }, {
25
+ stream: true,
26
+ signal: streamSignal
27
+ });
28
+ requestId = getLatestSuccessfulResponseRequestId(openAIErrorResponseContext) ?? stream._request_id;
29
+ const streamAttempt = getLatestResponseAttempt(openAIErrorResponseContext);
30
+ let chunkSequence = 0;
31
+ for await (const chunk of stream){
32
+ chunkSequence += 1;
33
+ recordEvent?.({
34
+ type: 'chunk',
35
+ attempt: streamAttempt,
36
+ sequence: chunkSequence,
37
+ chunk
38
+ });
39
+ const parsedChunk = adapter.chatCompletion.extractContentAndReasoning(chunk.choices?.[0]?.delta);
40
+ const content = parsedChunk.content || '';
41
+ const reasoning_content = parsedChunk.reasoning_content || '';
42
+ if (chunk.usage) usage = chunk.usage;
43
+ if (chunk.model) responseModelName = chunk.model;
44
+ if (content || reasoning_content) {
45
+ accumulated += content;
46
+ accumulatedReasoning += reasoning_content;
47
+ const chunkData = {
48
+ content,
49
+ reasoning_content,
50
+ accumulated,
51
+ isComplete: false,
52
+ usage: void 0
53
+ };
54
+ onChunk(chunkData);
55
+ }
56
+ }
57
+ const finalAccumulated = resolveContentWithReasoningFallback({
58
+ content: accumulated,
59
+ reasoningContent: accumulatedReasoning,
60
+ useReasoningAsContentFallback: adapter.chatCompletion.useReasoningAsContentFallback
61
+ });
62
+ accumulated = finalAccumulated || '';
63
+ const finalChunk = {
64
+ content: '',
65
+ accumulated,
66
+ reasoning_content: '',
67
+ isComplete: true,
68
+ usage
69
+ };
70
+ onChunk(finalChunk);
71
+ } catch (error) {
72
+ throw restoreHardTimeoutError(toError(error), streamSignal);
73
+ } finally{
74
+ cleanupStreamSignal();
75
+ }
76
+ return {
77
+ content: accumulated,
78
+ reasoningContent: accumulatedReasoning,
79
+ rawChoiceMessage: void 0,
80
+ rawUsage: usage,
81
+ requestId,
82
+ responseModelName
83
+ };
84
+ };
85
+ export { callChatCompletionStream };
86
+
87
+ //# sourceMappingURL=stream.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-model/service-caller/chat-completion/stream.mjs","sources":["../../../../../src/ai-model/service-caller/chat-completion/stream.ts"],"sourcesContent":["import type { CodeGenerationChunk } from '@/types';\nimport { assert } from '@midscene/shared/utils';\nimport type OpenAI from 'openai';\nimport type { Stream } from 'openai/streaming';\nimport {\n buildRequestAbortSignal,\n restoreHardTimeoutError,\n} from '../request-timeout';\nimport {\n getLatestResponseAttempt,\n getLatestSuccessfulResponseRequestId,\n toError,\n} from '../utils';\nimport type {\n ChatCompletionCallOptions,\n ChatCompletionCallResult,\n} from './types';\nimport { resolveContentWithReasoningFallback } from './utils';\n\nexport const callChatCompletionStream = async ({\n completion,\n modelName,\n openAIErrorResponseContext,\n modelRuntime,\n messages,\n requestConfig,\n effectiveTimeoutMs,\n abortSignal,\n onChunk,\n recordEvent,\n}: ChatCompletionCallOptions): Promise<ChatCompletionCallResult> => {\n assert(\n typeof onChunk === 'function',\n 'onChunk is required when stream is true',\n );\n const { adapter } = modelRuntime;\n let accumulated = '';\n let accumulatedReasoning = '';\n let usage: OpenAI.CompletionUsage | undefined;\n let requestId: string | null | undefined;\n let responseModelName: string | undefined;\n const { signal: streamSignal, cleanup: cleanupStreamSignal } =\n buildRequestAbortSignal(effectiveTimeoutMs, abortSignal);\n try {\n const stream = (await completion.create(\n {\n model: modelName,\n messages,\n ...requestConfig,\n stream: true,\n stream_options: {\n ...(requestConfig.stream_options as\n | Record<string, unknown>\n | undefined),\n include_usage: true,\n },\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 const streamAttempt = getLatestResponseAttempt(openAIErrorResponseContext);\n\n let chunkSequence = 0;\n for await (const chunk of stream) {\n chunkSequence += 1;\n recordEvent?.({\n type: 'chunk',\n attempt: streamAttempt,\n sequence: chunkSequence,\n chunk,\n });\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 onChunk(chunkData);\n }\n }\n\n const finalAccumulated = resolveContentWithReasoningFallback({\n content: accumulated,\n reasoningContent: accumulatedReasoning,\n useReasoningAsContentFallback:\n adapter.chatCompletion.useReasoningAsContentFallback,\n });\n accumulated = finalAccumulated || '';\n\n // Send final chunk\n const finalChunk: CodeGenerationChunk = {\n content: '',\n accumulated,\n reasoning_content: '',\n isComplete: true,\n usage,\n };\n onChunk(finalChunk);\n } catch (error) {\n throw restoreHardTimeoutError(toError(error), streamSignal);\n } finally {\n cleanupStreamSignal();\n }\n return {\n content: accumulated,\n reasoningContent: accumulatedReasoning,\n rawChoiceMessage: undefined,\n rawUsage: usage,\n requestId,\n responseModelName,\n };\n};\n"],"names":["callChatCompletionStream","completion","modelName","openAIErrorResponseContext","modelRuntime","messages","requestConfig","effectiveTimeoutMs","abortSignal","onChunk","recordEvent","assert","adapter","accumulated","accumulatedReasoning","usage","requestId","responseModelName","streamSignal","cleanupStreamSignal","buildRequestAbortSignal","stream","getLatestSuccessfulResponseRequestId","streamAttempt","getLatestResponseAttempt","chunkSequence","chunk","parsedChunk","content","reasoning_content","chunkData","undefined","finalAccumulated","resolveContentWithReasoningFallback","finalChunk","error","restoreHardTimeoutError","toError"],"mappings":";;;;AAmBO,MAAMA,2BAA2B,OAAO,EAC7CC,UAAU,EACVC,SAAS,EACTC,0BAA0B,EAC1BC,YAAY,EACZC,QAAQ,EACRC,aAAa,EACbC,kBAAkB,EAClBC,WAAW,EACXC,OAAO,EACPC,WAAW,EACe;IAC1BC,OACE,AAAmB,cAAnB,OAAOF,SACP;IAEF,MAAM,EAAEG,OAAO,EAAE,GAAGR;IACpB,IAAIS,cAAc;IAClB,IAAIC,uBAAuB;IAC3B,IAAIC;IACJ,IAAIC;IACJ,IAAIC;IACJ,MAAM,EAAE,QAAQC,YAAY,EAAE,SAASC,mBAAmB,EAAE,GAC1DC,wBAAwBb,oBAAoBC;IAC9C,IAAI;QACF,MAAMa,SAAU,MAAMpB,WAAW,MAAM,CACrC;YACE,OAAOC;YACPG;YACA,GAAGC,aAAa;YAChB,QAAQ;YACR,gBAAgB;gBACd,GAAIA,cAAc,cAAc;gBAGhC,eAAe;YACjB;QACF,GACA;YACE,QAAQ;YACR,QAAQY;QACV;QAKFF,YACEM,qCAAqCnB,+BACrCkB,OAAO,WAAW;QACpB,MAAME,gBAAgBC,yBAAyBrB;QAE/C,IAAIsB,gBAAgB;QACpB,WAAW,MAAMC,SAASL,OAAQ;YAChCI,iBAAiB;YACjBf,cAAc;gBACZ,MAAM;gBACN,SAASa;gBACT,UAAUE;gBACVC;YACF;YACA,MAAMC,cAAcf,QAAQ,cAAc,CAAC,0BAA0B,CACnEc,MAAM,OAAO,EAAE,CAAC,EAAE,EAAE;YAEtB,MAAME,UAAUD,YAAY,OAAO,IAAI;YACvC,MAAME,oBAAoBF,YAAY,iBAAiB,IAAI;YAG3D,IAAID,MAAM,KAAK,EACbX,QAAQW,MAAM,KAAK;YAErB,IAAIA,MAAM,KAAK,EACbT,oBAAoBS,MAAM,KAAK;YAGjC,IAAIE,WAAWC,mBAAmB;gBAChChB,eAAee;gBACfd,wBAAwBe;gBACxB,MAAMC,YAAiC;oBACrCF;oBACAC;oBACAhB;oBACA,YAAY;oBACZ,OAAOkB;gBACT;gBACAtB,QAAQqB;YACV;QACF;QAEA,MAAME,mBAAmBC,oCAAoC;YAC3D,SAASpB;YACT,kBAAkBC;YAClB,+BACEF,QAAQ,cAAc,CAAC,6BAA6B;QACxD;QACAC,cAAcmB,oBAAoB;QAGlC,MAAME,aAAkC;YACtC,SAAS;YACTrB;YACA,mBAAmB;YACnB,YAAY;YACZE;QACF;QACAN,QAAQyB;IACV,EAAE,OAAOC,OAAO;QACd,MAAMC,wBAAwBC,QAAQF,QAAQjB;IAChD,SAAU;QACRC;IACF;IACA,OAAO;QACL,SAASN;QACT,kBAAkBC;QAClB,kBAAkBiB;QAClB,UAAUhB;QACVC;QACAC;IACF;AACF"}
@@ -0,0 +1,35 @@
1
+ import { getDebug } from "@midscene/shared/logger";
2
+ const applyImageDetail = ({ imageDetail, messages })=>{
3
+ if (!imageDetail) return messages;
4
+ return messages.map((msg)=>{
5
+ if (!Array.isArray(msg.content)) return msg;
6
+ const content = msg.content.map((part)=>{
7
+ if (part && 'image_url' === part.type && part.image_url?.url) return {
8
+ ...part,
9
+ image_url: {
10
+ ...part.image_url,
11
+ detail: imageDetail
12
+ }
13
+ };
14
+ return part;
15
+ });
16
+ return {
17
+ ...msg,
18
+ content
19
+ };
20
+ });
21
+ };
22
+ const hasUsableText = (value)=>'string' == typeof value && value.trim().length > 0;
23
+ const resolveContentWithReasoningFallback = ({ content, reasoningContent, useReasoningAsContentFallback })=>{
24
+ if (!hasUsableText(content) && useReasoningAsContentFallback && hasUsableText(reasoningContent)) {
25
+ const warnCall = getDebug('ai:call', {
26
+ console: true
27
+ });
28
+ warnCall('empty content from AI model, using reasoning content');
29
+ return reasoningContent;
30
+ }
31
+ return content;
32
+ };
33
+ export { applyImageDetail, hasUsableText, resolveContentWithReasoningFallback };
34
+
35
+ //# sourceMappingURL=utils.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-model/service-caller/chat-completion/utils.mjs","sources":["../../../../../src/ai-model/service-caller/chat-completion/utils.ts"],"sourcesContent":["import { getDebug } from '@midscene/shared/logger';\nimport type { ChatCompletionMessageParam } from 'openai/resources/index';\nimport type { ImageDetail } from '../../model-adapter/types';\n\nexport const applyImageDetail = ({\n imageDetail,\n messages,\n}: {\n imageDetail?: ImageDetail;\n messages: ChatCompletionMessageParam[];\n}): 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\nexport const hasUsableText = (\n value: string | null | undefined,\n): value is string => typeof value === 'string' && value.trim().length > 0;\n\nexport const resolveContentWithReasoningFallback = ({\n content,\n reasoningContent,\n useReasoningAsContentFallback,\n}: {\n content: string | undefined;\n reasoningContent: string;\n useReasoningAsContentFallback: boolean;\n}): string | undefined => {\n if (\n !hasUsableText(content) &&\n useReasoningAsContentFallback &&\n hasUsableText(reasoningContent)\n ) {\n const warnCall = getDebug('ai:call', { console: true });\n warnCall('empty content from AI model, using reasoning content');\n return reasoningContent;\n }\n\n return content;\n};\n"],"names":["applyImageDetail","imageDetail","messages","msg","Array","content","part","hasUsableText","value","resolveContentWithReasoningFallback","reasoningContent","useReasoningAsContentFallback","warnCall","getDebug"],"mappings":";AAIO,MAAMA,mBAAmB,CAAC,EAC/BC,WAAW,EACXC,QAAQ,EAIT;IACC,IAAI,CAACD,aACH,OAAOC;IAGT,OAAOA,SAAS,GAAG,CAAC,CAACC;QACnB,IAAI,CAACC,MAAM,OAAO,CAACD,IAAI,OAAO,GAC5B,OAAOA;QAGT,MAAME,UAAUF,IAAI,OAAO,CAAC,GAAG,CAAC,CAACG;YAC/B,IAAIA,QAAQA,AAAc,gBAAdA,KAAK,IAAI,IAAoBA,KAAK,SAAS,EAAE,KACvD,OAAO;gBACL,GAAGA,IAAI;gBACP,WAAW;oBACT,GAAGA,KAAK,SAAS;oBACjB,QAAQL;gBACV;YACF;YAEF,OAAOK;QACT;QAEA,OAAO;YACL,GAAGH,GAAG;YACNE;QACF;IACF;AACF;AAEO,MAAME,gBAAgB,CAC3BC,QACoB,AAAiB,YAAjB,OAAOA,SAAsBA,MAAM,IAAI,GAAG,MAAM,GAAG;AAElE,MAAMC,sCAAsC,CAAC,EAClDJ,OAAO,EACPK,gBAAgB,EAChBC,6BAA6B,EAK9B;IACC,IACE,CAACJ,cAAcF,YACfM,iCACAJ,cAAcG,mBACd;QACA,MAAME,WAAWC,SAAS,WAAW;YAAE,SAAS;QAAK;QACrDD,SAAS;QACT,OAAOF;IACT;IAEA,OAAOL;AACT"}
@@ -0,0 +1,77 @@
1
+ import { callAIWithCodexAppServer } from "./codex-app-server.mjs";
2
+ const callCodex = async ({ messages, modelRuntime, options, recordEvent })=>{
3
+ const { config: modelConfig, adapter } = modelRuntime;
4
+ let protocolChunkSequence = 0;
5
+ const codexStartTime = Date.now();
6
+ const recordCodexEvent = recordEvent ? (event)=>{
7
+ if ('chunk' === event.type) {
8
+ protocolChunkSequence += 1;
9
+ recordEvent({
10
+ ...event,
11
+ attempt: 1,
12
+ sequence: protocolChunkSequence,
13
+ provider: 'codex-app-server'
14
+ });
15
+ return;
16
+ }
17
+ recordEvent({
18
+ ...event,
19
+ attempt: 1,
20
+ provider: 'codex-app-server'
21
+ });
22
+ } : void 0;
23
+ try {
24
+ const { config, imageDetail } = adapter.buildCodexAppServerParams({
25
+ intent: modelConfig.intent,
26
+ userConfig: {
27
+ reasoningEnabled: modelConfig.reasoningEnabled,
28
+ reasoningEffort: modelConfig.reasoningEffort,
29
+ reasoningBudget: modelConfig.reasoningBudget
30
+ },
31
+ requiresOriginalImageDetail: options?.requiresOriginalImageDetail
32
+ });
33
+ const codexResult = await callAIWithCodexAppServer(messages, modelConfig, {
34
+ stream: options?.stream,
35
+ onChunk: options?.onChunk,
36
+ params: config,
37
+ abortSignal: options?.abortSignal,
38
+ imageDetail,
39
+ onRecordEvent: recordCodexEvent
40
+ });
41
+ const { protocolMetadata, usage: rawUsage, ...response } = codexResult;
42
+ const timeCost = Date.now() - codexStartTime;
43
+ recordEvent?.({
44
+ type: 'response',
45
+ attempt: 1,
46
+ provider: 'codex-app-server',
47
+ final: {
48
+ content: response.content,
49
+ reasoningContent: response.reasoning_content,
50
+ usage: rawUsage,
51
+ timeCost,
52
+ protocol: protocolMetadata
53
+ }
54
+ });
55
+ return {
56
+ ...response,
57
+ rawUsage,
58
+ timeCost,
59
+ requestId: protocolMetadata.turnId
60
+ };
61
+ } catch (error) {
62
+ recordEvent?.({
63
+ type: 'error',
64
+ attempt: 1,
65
+ provider: 'codex-app-server',
66
+ error: error instanceof Error ? {
67
+ name: error.name,
68
+ message: error.message,
69
+ stack: error.stack
70
+ } : String(error)
71
+ });
72
+ throw error;
73
+ }
74
+ };
75
+ export { callCodex };
76
+
77
+ //# sourceMappingURL=call-codex.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-model/service-caller/codex/call-codex.mjs","sources":["../../../../../src/ai-model/service-caller/codex/call-codex.ts"],"sourcesContent":["import type { ModelCallContext, ModelCallResult } from '../types';\nimport {\n type CodexAppServerRecordEvent,\n callAIWithCodexAppServer,\n} from './codex-app-server';\n\nexport const callCodex = async ({\n messages,\n modelRuntime,\n options,\n recordEvent,\n}: ModelCallContext): Promise<ModelCallResult> => {\n const { config: modelConfig, adapter } = modelRuntime;\n let protocolChunkSequence = 0;\n const codexStartTime = Date.now();\n const recordCodexEvent = recordEvent\n ? (event: CodexAppServerRecordEvent) => {\n if (event.type === 'chunk') {\n protocolChunkSequence += 1;\n recordEvent({\n ...event,\n attempt: 1,\n sequence: protocolChunkSequence,\n provider: 'codex-app-server',\n });\n return;\n }\n\n recordEvent({\n ...event,\n attempt: 1,\n provider: 'codex-app-server',\n });\n }\n : undefined;\n\n try {\n const { config, imageDetail } = adapter.buildCodexAppServerParams({\n intent: modelConfig.intent,\n userConfig: {\n reasoningEnabled: modelConfig.reasoningEnabled,\n reasoningEffort: modelConfig.reasoningEffort,\n reasoningBudget: modelConfig.reasoningBudget,\n },\n requiresOriginalImageDetail: options?.requiresOriginalImageDetail,\n });\n const codexResult = await callAIWithCodexAppServer(messages, modelConfig, {\n stream: options?.stream,\n onChunk: options?.onChunk,\n params: config,\n abortSignal: options?.abortSignal,\n imageDetail,\n onRecordEvent: recordCodexEvent,\n });\n const { protocolMetadata, usage: rawUsage, ...response } = codexResult;\n const timeCost = Date.now() - codexStartTime;\n recordEvent?.({\n type: 'response',\n attempt: 1,\n provider: 'codex-app-server',\n final: {\n content: response.content,\n reasoningContent: response.reasoning_content,\n usage: rawUsage,\n timeCost,\n protocol: protocolMetadata,\n },\n });\n return {\n ...response,\n rawUsage,\n timeCost,\n requestId: protocolMetadata.turnId,\n };\n } catch (error) {\n recordEvent?.({\n type: 'error',\n attempt: 1,\n provider: 'codex-app-server',\n error:\n error instanceof Error\n ? {\n name: error.name,\n message: error.message,\n stack: error.stack,\n }\n : String(error),\n });\n throw error;\n }\n};\n"],"names":["callCodex","messages","modelRuntime","options","recordEvent","modelConfig","adapter","protocolChunkSequence","codexStartTime","Date","recordCodexEvent","event","undefined","config","imageDetail","codexResult","callAIWithCodexAppServer","protocolMetadata","rawUsage","response","timeCost","error","Error","String"],"mappings":";AAMO,MAAMA,YAAY,OAAO,EAC9BC,QAAQ,EACRC,YAAY,EACZC,OAAO,EACPC,WAAW,EACM;IACjB,MAAM,EAAE,QAAQC,WAAW,EAAEC,OAAO,EAAE,GAAGJ;IACzC,IAAIK,wBAAwB;IAC5B,MAAMC,iBAAiBC,KAAK,GAAG;IAC/B,MAAMC,mBAAmBN,cACrB,CAACO;QACC,IAAIA,AAAe,YAAfA,MAAM,IAAI,EAAc;YAC1BJ,yBAAyB;YACzBH,YAAY;gBACV,GAAGO,KAAK;gBACR,SAAS;gBACT,UAAUJ;gBACV,UAAU;YACZ;YACA;QACF;QAEAH,YAAY;YACV,GAAGO,KAAK;YACR,SAAS;YACT,UAAU;QACZ;IACF,IACAC;IAEJ,IAAI;QACF,MAAM,EAAEC,MAAM,EAAEC,WAAW,EAAE,GAAGR,QAAQ,yBAAyB,CAAC;YAChE,QAAQD,YAAY,MAAM;YAC1B,YAAY;gBACV,kBAAkBA,YAAY,gBAAgB;gBAC9C,iBAAiBA,YAAY,eAAe;gBAC5C,iBAAiBA,YAAY,eAAe;YAC9C;YACA,6BAA6BF,SAAS;QACxC;QACA,MAAMY,cAAc,MAAMC,yBAAyBf,UAAUI,aAAa;YACxE,QAAQF,SAAS;YACjB,SAASA,SAAS;YAClB,QAAQU;YACR,aAAaV,SAAS;YACtBW;YACA,eAAeJ;QACjB;QACA,MAAM,EAAEO,gBAAgB,EAAE,OAAOC,QAAQ,EAAE,GAAGC,UAAU,GAAGJ;QAC3D,MAAMK,WAAWX,KAAK,GAAG,KAAKD;QAC9BJ,cAAc;YACZ,MAAM;YACN,SAAS;YACT,UAAU;YACV,OAAO;gBACL,SAASe,SAAS,OAAO;gBACzB,kBAAkBA,SAAS,iBAAiB;gBAC5C,OAAOD;gBACPE;gBACA,UAAUH;YACZ;QACF;QACA,OAAO;YACL,GAAGE,QAAQ;YACXD;YACAE;YACA,WAAWH,iBAAiB,MAAM;QACpC;IACF,EAAE,OAAOI,OAAO;QACdjB,cAAc;YACZ,MAAM;YACN,SAAS;YACT,UAAU;YACV,OACEiB,iBAAiBC,QACb;gBACE,MAAMD,MAAM,IAAI;gBAChB,SAASA,MAAM,OAAO;gBACtB,OAAOA,MAAM,KAAK;YACpB,IACAE,OAAOF;QACf;QACA,MAAMA;IACR;AACF"}
@@ -176,7 +176,6 @@ class CodexAppServerConnection {
176
176
  return this.closed;
177
177
  }
178
178
  async runTurn({ messages, modelConfig, stream, onChunk, params, abortSignal, imageDetail, onRecordEvent }) {
179
- const startTime = Date.now();
180
179
  const timeoutMs = modelConfig.timeout || CODEX_DEFAULT_TIMEOUT_MS;
181
180
  const deadlineAt = Date.now() + timeoutMs;
182
181
  const isStreaming = !!(stream && onChunk);
@@ -319,12 +318,7 @@ class CodexAppServerConnection {
319
318
  continue;
320
319
  }
321
320
  if ('thread/tokenUsage/updated' === method && params.threadId === threadId && params.turnId === turnId) {
322
- latestUsage = this.mapUsage({
323
- usage: params,
324
- modelConfig,
325
- turnId,
326
- startTime
327
- });
321
+ latestUsage = this.mapUsage(params);
328
322
  continue;
329
323
  }
330
324
  if ('turn/completed' === method && params.threadId === threadId && params.turn?.id === turnId) {
@@ -431,23 +425,20 @@ class CodexAppServerConnection {
431
425
  method: 'initialized'
432
426
  });
433
427
  }
434
- mapUsage({ usage, modelConfig, turnId, startTime }) {
428
+ mapUsage(usage) {
435
429
  const tokenUsage = usage.tokenUsage;
436
430
  const picked = tokenUsage?.last || tokenUsage?.total;
437
431
  if (!picked) return;
438
432
  return {
439
- ...picked,
440
433
  prompt_tokens: picked.inputTokens ?? 0,
441
434
  completion_tokens: picked.outputTokens ?? 0,
442
435
  total_tokens: picked.totalTokens ?? 0,
443
- cached_input: picked.cachedInputTokens ?? 0,
444
- time_cost: Date.now() - startTime,
445
- model_name: modelConfig.modelName,
446
- model_description: modelConfig.modelDescription,
447
- response_model_name: void 0,
448
- slot: modelConfig.slot,
449
- intent: void 0,
450
- request_id: turnId
436
+ prompt_tokens_details: {
437
+ cached_tokens: picked.cachedInputTokens ?? 0
438
+ },
439
+ completion_tokens_details: {
440
+ reasoning_tokens: picked.reasoningOutputTokens ?? 0
441
+ }
451
442
  };
452
443
  }
453
444
  isRequestMessage(message) {