@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.
- package/dist/es/agent/agent.mjs +12 -2
- package/dist/es/agent/agent.mjs.map +1 -1
- package/dist/es/agent/utils.mjs +1 -1
- package/dist/es/ai-model/service-caller/call-ai.mjs +49 -0
- package/dist/es/ai-model/service-caller/call-ai.mjs.map +1 -0
- package/dist/es/ai-model/service-caller/call.mjs +56 -0
- package/dist/es/ai-model/service-caller/call.mjs.map +1 -0
- package/dist/es/ai-model/service-caller/chat-completion/call.mjs +101 -0
- package/dist/es/ai-model/service-caller/chat-completion/call.mjs.map +1 -0
- package/dist/es/ai-model/service-caller/chat-completion/non-stream.mjs +80 -0
- package/dist/es/ai-model/service-caller/chat-completion/non-stream.mjs.map +1 -0
- package/dist/es/ai-model/service-caller/chat-completion/stream.mjs +87 -0
- package/dist/es/ai-model/service-caller/chat-completion/stream.mjs.map +1 -0
- package/dist/es/ai-model/service-caller/chat-completion/types.mjs +0 -0
- package/dist/es/ai-model/service-caller/chat-completion/utils.mjs +35 -0
- package/dist/es/ai-model/service-caller/chat-completion/utils.mjs.map +1 -0
- package/dist/es/ai-model/service-caller/codex/call-codex.mjs +77 -0
- package/dist/es/ai-model/service-caller/codex/call-codex.mjs.map +1 -0
- package/dist/es/ai-model/service-caller/{codex-app-server.mjs → codex/codex-app-server.mjs} +8 -17
- package/dist/es/ai-model/service-caller/codex/codex-app-server.mjs.map +1 -0
- package/dist/es/ai-model/service-caller/index.mjs +5 -582
- package/dist/es/ai-model/service-caller/openai-client.mjs +88 -0
- package/dist/es/ai-model/service-caller/openai-client.mjs.map +1 -0
- package/dist/es/ai-model/service-caller/proxy.mjs +66 -0
- package/dist/es/ai-model/service-caller/proxy.mjs.map +1 -0
- package/dist/es/ai-model/service-caller/utils.mjs +83 -0
- package/dist/es/ai-model/service-caller/utils.mjs.map +1 -0
- package/dist/es/report-html-template.mjs +1 -1
- package/dist/es/test-run-report.mjs.map +1 -1
- package/dist/es/types.mjs.map +1 -1
- package/dist/es/utils.mjs +1 -1
- package/dist/lib/agent/agent.js +12 -2
- package/dist/lib/agent/agent.js.map +1 -1
- package/dist/lib/agent/utils.js +1 -1
- package/dist/lib/ai-model/service-caller/call-ai.js +89 -0
- package/dist/lib/ai-model/service-caller/call-ai.js.map +1 -0
- package/dist/lib/ai-model/service-caller/call.js +90 -0
- package/dist/lib/ai-model/service-caller/call.js.map +1 -0
- package/dist/lib/ai-model/service-caller/chat-completion/call.js +135 -0
- package/dist/lib/ai-model/service-caller/chat-completion/call.js.map +1 -0
- package/dist/lib/ai-model/service-caller/chat-completion/non-stream.js +114 -0
- package/dist/lib/ai-model/service-caller/chat-completion/non-stream.js.map +1 -0
- package/dist/lib/ai-model/service-caller/chat-completion/stream.js +121 -0
- package/dist/lib/ai-model/service-caller/chat-completion/stream.js.map +1 -0
- package/dist/lib/ai-model/service-caller/chat-completion/types.js +20 -0
- package/dist/lib/ai-model/service-caller/chat-completion/types.js.map +1 -0
- package/dist/lib/ai-model/service-caller/chat-completion/utils.js +75 -0
- package/dist/lib/ai-model/service-caller/chat-completion/utils.js.map +1 -0
- package/dist/lib/ai-model/service-caller/codex/call-codex.js +111 -0
- package/dist/lib/ai-model/service-caller/codex/call-codex.js.map +1 -0
- package/dist/lib/ai-model/service-caller/{codex-app-server.js → codex/codex-app-server.js} +8 -17
- package/dist/lib/ai-model/service-caller/codex/codex-app-server.js.map +1 -0
- package/dist/lib/ai-model/service-caller/index.js +11 -596
- package/dist/lib/ai-model/service-caller/index.js.map +1 -1
- package/dist/lib/ai-model/service-caller/openai-client.js +132 -0
- package/dist/lib/ai-model/service-caller/openai-client.js.map +1 -0
- package/dist/lib/ai-model/service-caller/proxy.js +100 -0
- package/dist/lib/ai-model/service-caller/proxy.js.map +1 -0
- package/dist/lib/ai-model/service-caller/utils.js +144 -0
- package/dist/lib/ai-model/service-caller/utils.js.map +1 -0
- package/dist/lib/report-html-template.js +1 -1
- package/dist/lib/test-run-report.js.map +1 -1
- package/dist/lib/types.js.map +1 -1
- package/dist/lib/utils.js +1 -1
- package/dist/types/ai-model/service-caller/call-ai.d.ts +26 -0
- package/dist/types/ai-model/service-caller/call.d.ts +4 -0
- package/dist/types/ai-model/service-caller/chat-completion/call.d.ts +2 -0
- package/dist/types/ai-model/service-caller/chat-completion/non-stream.d.ts +2 -0
- package/dist/types/ai-model/service-caller/chat-completion/stream.d.ts +2 -0
- package/dist/types/ai-model/service-caller/chat-completion/types.d.ts +25 -0
- package/dist/types/ai-model/service-caller/chat-completion/utils.d.ts +12 -0
- package/dist/types/ai-model/service-caller/codex/call-codex.d.ts +2 -0
- package/dist/types/ai-model/service-caller/{codex-app-server.d.ts → codex/codex-app-server.d.ts} +4 -3
- package/dist/types/ai-model/service-caller/index.d.ts +4 -74
- package/dist/types/ai-model/service-caller/openai-client.d.ts +14 -0
- package/dist/types/ai-model/service-caller/proxy.d.ts +4 -0
- package/dist/types/ai-model/service-caller/types.d.ts +35 -0
- package/dist/types/ai-model/service-caller/utils.d.ts +40 -0
- package/dist/types/test-run-report.d.ts +0 -1
- package/dist/types/types.d.ts +3 -2
- package/package.json +4 -4
- package/dist/es/ai-model/service-caller/codex-app-server.mjs.map +0 -1
- package/dist/es/ai-model/service-caller/index.mjs.map +0 -1
- 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"}
|
|
File without changes
|
|
@@ -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(
|
|
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
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
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) {
|