@midscene/core 1.5.5 → 1.5.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/utils.mjs +1 -1
- package/dist/es/ai-model/service-caller/codex-app-server.mjs +584 -0
- package/dist/es/ai-model/service-caller/codex-app-server.mjs.map +1 -0
- package/dist/es/ai-model/service-caller/index.mjs +2 -0
- package/dist/es/ai-model/service-caller/index.mjs.map +1 -1
- package/dist/es/utils.mjs +2 -2
- package/dist/lib/agent/utils.js +1 -1
- package/dist/lib/ai-model/service-caller/codex-app-server.js +633 -0
- package/dist/lib/ai-model/service-caller/codex-app-server.js.map +1 -0
- package/dist/lib/ai-model/service-caller/index.js +2 -0
- package/dist/lib/ai-model/service-caller/index.js.map +1 -1
- package/dist/lib/utils.js +2 -2
- package/dist/types/ai-model/service-caller/codex-app-server.d.ts +46 -0
- package/package.json +2 -2
package/dist/lib/agent/utils.js
CHANGED
|
@@ -201,7 +201,7 @@ async function matchElementFromCache(context, cacheEntry, cachePrompt, cacheable
|
|
|
201
201
|
return;
|
|
202
202
|
}
|
|
203
203
|
}
|
|
204
|
-
const getMidsceneVersion = ()=>"1.5.
|
|
204
|
+
const getMidsceneVersion = ()=>"1.5.6";
|
|
205
205
|
const parsePrompt = (prompt)=>{
|
|
206
206
|
if ('string' == typeof prompt) return {
|
|
207
207
|
textPrompt: prompt,
|
|
@@ -0,0 +1,633 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __webpack_require__ = {};
|
|
3
|
+
(()=>{
|
|
4
|
+
__webpack_require__.d = (exports1, definition)=>{
|
|
5
|
+
for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
get: definition[key]
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
})();
|
|
11
|
+
(()=>{
|
|
12
|
+
__webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
|
|
13
|
+
})();
|
|
14
|
+
(()=>{
|
|
15
|
+
__webpack_require__.r = (exports1)=>{
|
|
16
|
+
if ('undefined' != typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
|
|
17
|
+
value: 'Module'
|
|
18
|
+
});
|
|
19
|
+
Object.defineProperty(exports1, '__esModule', {
|
|
20
|
+
value: true
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
})();
|
|
24
|
+
var __webpack_exports__ = {};
|
|
25
|
+
__webpack_require__.r(__webpack_exports__);
|
|
26
|
+
__webpack_require__.d(__webpack_exports__, {
|
|
27
|
+
callAIWithCodexAppServer: ()=>callAIWithCodexAppServer,
|
|
28
|
+
__shutdownCodexAppServerForTests: ()=>__shutdownCodexAppServerForTests,
|
|
29
|
+
isCodexAppServerProvider: ()=>isCodexAppServerProvider,
|
|
30
|
+
normalizeCodexLocalImagePath: ()=>normalizeCodexLocalImagePath,
|
|
31
|
+
resolveCodexReasoningEffort: ()=>resolveCodexReasoningEffort,
|
|
32
|
+
buildCodexTurnPayloadFromMessages: ()=>buildCodexTurnPayloadFromMessages
|
|
33
|
+
});
|
|
34
|
+
const logger_namespaceObject = require("@midscene/shared/logger");
|
|
35
|
+
const utils_namespaceObject = require("@midscene/shared/utils");
|
|
36
|
+
function _define_property(obj, key, value) {
|
|
37
|
+
if (key in obj) Object.defineProperty(obj, key, {
|
|
38
|
+
value: value,
|
|
39
|
+
enumerable: true,
|
|
40
|
+
configurable: true,
|
|
41
|
+
writable: true
|
|
42
|
+
});
|
|
43
|
+
else obj[key] = value;
|
|
44
|
+
return obj;
|
|
45
|
+
}
|
|
46
|
+
const CODEX_PROVIDER_SCHEME = 'codex://';
|
|
47
|
+
const CODEX_DEFAULT_TIMEOUT_MS = 600000;
|
|
48
|
+
const CODEX_DEFAULT_PROCESS_START_TIMEOUT_MS = 15000;
|
|
49
|
+
const CODEX_DEFAULT_CLEANUP_TIMEOUT_MS = 8000;
|
|
50
|
+
const CODEX_TEXT_INPUT_MAX_LENGTH = 262144;
|
|
51
|
+
const debugCodex = (0, logger_namespaceObject.getDebug)('ai:call:codex');
|
|
52
|
+
const warnCodex = (0, logger_namespaceObject.getDebug)('ai:call:codex', {
|
|
53
|
+
console: true
|
|
54
|
+
});
|
|
55
|
+
class SerializedRunner {
|
|
56
|
+
async run(work) {
|
|
57
|
+
const previous = this.tail;
|
|
58
|
+
let release;
|
|
59
|
+
this.tail = new Promise((resolve)=>{
|
|
60
|
+
release = resolve;
|
|
61
|
+
});
|
|
62
|
+
await previous;
|
|
63
|
+
try {
|
|
64
|
+
return await work();
|
|
65
|
+
} finally{
|
|
66
|
+
release();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
constructor(){
|
|
70
|
+
_define_property(this, "tail", Promise.resolve());
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const isCodexAppServerProvider = (baseURL)=>{
|
|
74
|
+
if (!baseURL) return false;
|
|
75
|
+
return baseURL.trim().toLowerCase().startsWith(CODEX_PROVIDER_SCHEME);
|
|
76
|
+
};
|
|
77
|
+
const isAbortError = (error)=>{
|
|
78
|
+
if (!error) return false;
|
|
79
|
+
if (error instanceof Error && 'AbortError' === error.name) return true;
|
|
80
|
+
const message = error instanceof Error ? error.message : String(error ?? 'unknown error');
|
|
81
|
+
return /aborted|abort/i.test(message);
|
|
82
|
+
};
|
|
83
|
+
const toNonEmptyString = (value)=>{
|
|
84
|
+
if ('string' != typeof value) return;
|
|
85
|
+
const trimmed = value.trim();
|
|
86
|
+
return trimmed || void 0;
|
|
87
|
+
};
|
|
88
|
+
const normalizeCodexLocalImagePath = (imageUrl, platform = process.platform)=>{
|
|
89
|
+
if (!imageUrl.startsWith('file://')) return imageUrl;
|
|
90
|
+
try {
|
|
91
|
+
const parsed = new URL(imageUrl);
|
|
92
|
+
const pathname = decodeURIComponent(parsed.pathname);
|
|
93
|
+
const host = parsed.hostname.toLowerCase();
|
|
94
|
+
if ('win32' === platform) {
|
|
95
|
+
const windowsPath = pathname.replace(/\//g, '\\').replace(/^\\([A-Za-z]:)/, '$1');
|
|
96
|
+
if (host && 'localhost' !== host) return `\\\\${parsed.hostname}${windowsPath}`;
|
|
97
|
+
return windowsPath;
|
|
98
|
+
}
|
|
99
|
+
if (host && 'localhost' !== host) return `//${parsed.hostname}${pathname}`;
|
|
100
|
+
return pathname;
|
|
101
|
+
} catch {
|
|
102
|
+
return decodeURIComponent(imageUrl.slice(7));
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
const extractTextFromMessage = (message)=>{
|
|
106
|
+
const content = message.content;
|
|
107
|
+
if ('string' == typeof content) return content;
|
|
108
|
+
if (Array.isArray(content)) return content.map((part)=>{
|
|
109
|
+
if (!part || 'object' != typeof part) return '';
|
|
110
|
+
if ('text' === part.type && 'string' == typeof part.text) return part.text;
|
|
111
|
+
if ('input_text' === part.type && 'string' == typeof part.text) return part.text;
|
|
112
|
+
return '';
|
|
113
|
+
}).filter(Boolean).join('\n');
|
|
114
|
+
return '';
|
|
115
|
+
};
|
|
116
|
+
const extractImageInputs = (message, imageDetailOverride)=>{
|
|
117
|
+
const content = message.content;
|
|
118
|
+
if (!Array.isArray(content)) return [];
|
|
119
|
+
const inputs = [];
|
|
120
|
+
for (const part of content){
|
|
121
|
+
if (!part || 'object' != typeof part) continue;
|
|
122
|
+
const partType = String(part.type || '');
|
|
123
|
+
const imageUrl = 'image_url' === partType ? toNonEmptyString(part.image_url?.url) : 'input_image' === partType ? toNonEmptyString(part.image_url || part.url) : void 0;
|
|
124
|
+
if (!imageUrl) continue;
|
|
125
|
+
const detail = imageDetailOverride || toNonEmptyString(part.image_url?.detail) || toNonEmptyString(part.detail);
|
|
126
|
+
if (imageUrl.startsWith('/') || imageUrl.startsWith('./') || imageUrl.startsWith('../') || imageUrl.startsWith('file://')) {
|
|
127
|
+
const path = imageUrl.startsWith('file://') ? normalizeCodexLocalImagePath(imageUrl) : imageUrl;
|
|
128
|
+
inputs.push({
|
|
129
|
+
type: 'localImage',
|
|
130
|
+
path,
|
|
131
|
+
...detail ? {
|
|
132
|
+
detail
|
|
133
|
+
} : {}
|
|
134
|
+
});
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
inputs.push({
|
|
138
|
+
type: 'image',
|
|
139
|
+
url: imageUrl,
|
|
140
|
+
...detail ? {
|
|
141
|
+
detail
|
|
142
|
+
} : {}
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
return inputs;
|
|
146
|
+
};
|
|
147
|
+
const resolveCodexReasoningEffort = ({ deepThink, modelConfig })=>{
|
|
148
|
+
if (true === deepThink) return 'high';
|
|
149
|
+
if (false === deepThink) return 'low';
|
|
150
|
+
const normalized = modelConfig.reasoningEffort?.trim().toLowerCase();
|
|
151
|
+
if ('low' === normalized || 'medium' === normalized || 'high' === normalized || 'xhigh' === normalized) return normalized;
|
|
152
|
+
if (true === modelConfig.reasoningEnabled) return 'high';
|
|
153
|
+
if (false === modelConfig.reasoningEnabled) return 'low';
|
|
154
|
+
};
|
|
155
|
+
const buildCodexTurnPayloadFromMessages = (messages, options)=>{
|
|
156
|
+
const developerInstructionParts = [];
|
|
157
|
+
const transcriptParts = [];
|
|
158
|
+
const imageInputs = [];
|
|
159
|
+
for (const message of messages){
|
|
160
|
+
const role = String(message.role || 'user');
|
|
161
|
+
const text = extractTextFromMessage(message);
|
|
162
|
+
if ('system' === role) {
|
|
163
|
+
if (text.trim()) developerInstructionParts.push(text.trim());
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
const roleTag = role.toUpperCase();
|
|
167
|
+
if (text.trim()) transcriptParts.push(`[${roleTag}]\n${text.trim()}`);
|
|
168
|
+
else transcriptParts.push(`[${roleTag}]\n(no text content)`);
|
|
169
|
+
if ('user' === role) imageInputs.push(...extractImageInputs(message, options?.imageDetailOverride));
|
|
170
|
+
}
|
|
171
|
+
const fullTranscript = transcriptParts.join('\n\n');
|
|
172
|
+
const transcriptText = (fullTranscript.length > CODEX_TEXT_INPUT_MAX_LENGTH ? fullTranscript.slice(-CODEX_TEXT_INPUT_MAX_LENGTH) : fullTranscript) || 'Please answer the latest user request.';
|
|
173
|
+
const input = [
|
|
174
|
+
{
|
|
175
|
+
type: 'text',
|
|
176
|
+
text: transcriptText,
|
|
177
|
+
text_elements: []
|
|
178
|
+
},
|
|
179
|
+
...imageInputs
|
|
180
|
+
];
|
|
181
|
+
const developerInstructions = developerInstructionParts.length ? developerInstructionParts.join('\n\n') : void 0;
|
|
182
|
+
return {
|
|
183
|
+
developerInstructions,
|
|
184
|
+
input
|
|
185
|
+
};
|
|
186
|
+
};
|
|
187
|
+
class CodexAppServerConnection {
|
|
188
|
+
static async create() {
|
|
189
|
+
if (utils_namespaceObject.ifInBrowser) throw new Error('codex app-server provider is not supported in browser runtime');
|
|
190
|
+
const childProcessModuleName = 'node:child_process';
|
|
191
|
+
const readlineModuleName = 'node:readline';
|
|
192
|
+
const { spawn } = await import(childProcessModuleName);
|
|
193
|
+
const readline = await import(readlineModuleName);
|
|
194
|
+
const child = spawn('codex', [
|
|
195
|
+
'app-server'
|
|
196
|
+
], {
|
|
197
|
+
stdio: [
|
|
198
|
+
'pipe',
|
|
199
|
+
'pipe',
|
|
200
|
+
'pipe'
|
|
201
|
+
]
|
|
202
|
+
});
|
|
203
|
+
if (!child.stdin || !child.stdout || !child.stderr) throw new Error('failed to start codex app-server: stdio unavailable');
|
|
204
|
+
const lineReader = readline.createInterface({
|
|
205
|
+
input: child.stdout,
|
|
206
|
+
crlfDelay: 1 / 0
|
|
207
|
+
});
|
|
208
|
+
const connection = new CodexAppServerConnection(child, lineReader);
|
|
209
|
+
connection.detachFromEventLoop();
|
|
210
|
+
connection.attachProcessListeners();
|
|
211
|
+
await connection.initializeHandshake();
|
|
212
|
+
return connection;
|
|
213
|
+
}
|
|
214
|
+
isClosed() {
|
|
215
|
+
return this.closed;
|
|
216
|
+
}
|
|
217
|
+
async runTurn({ messages, modelConfig, stream, onChunk, deepThink, abortSignal }) {
|
|
218
|
+
const startTime = Date.now();
|
|
219
|
+
const timeoutMs = modelConfig.timeout || CODEX_DEFAULT_TIMEOUT_MS;
|
|
220
|
+
const deadlineAt = Date.now() + timeoutMs;
|
|
221
|
+
const isStreaming = !!(stream && onChunk);
|
|
222
|
+
const imageDetailOverride = 'gpt-5' === modelConfig.modelFamily ? 'original' : void 0;
|
|
223
|
+
const { developerInstructions, input } = buildCodexTurnPayloadFromMessages(messages, {
|
|
224
|
+
imageDetailOverride
|
|
225
|
+
});
|
|
226
|
+
const effort = resolveCodexReasoningEffort({
|
|
227
|
+
deepThink,
|
|
228
|
+
modelConfig
|
|
229
|
+
});
|
|
230
|
+
let threadId;
|
|
231
|
+
let turnId;
|
|
232
|
+
let latestErrorMessage;
|
|
233
|
+
let accumulatedText = '';
|
|
234
|
+
let accumulatedReasoning = '';
|
|
235
|
+
let latestUsage;
|
|
236
|
+
const emitChunk = ({ content, reasoning, isComplete, usage })=>{
|
|
237
|
+
if (!isStreaming || !onChunk) return;
|
|
238
|
+
const chunk = {
|
|
239
|
+
content,
|
|
240
|
+
reasoning_content: reasoning,
|
|
241
|
+
accumulated: accumulatedText,
|
|
242
|
+
isComplete,
|
|
243
|
+
usage
|
|
244
|
+
};
|
|
245
|
+
onChunk(chunk);
|
|
246
|
+
};
|
|
247
|
+
try {
|
|
248
|
+
const threadStartResponse = await this.request({
|
|
249
|
+
method: 'thread/start',
|
|
250
|
+
params: {
|
|
251
|
+
model: modelConfig.modelName,
|
|
252
|
+
cwd: process.cwd(),
|
|
253
|
+
approvalPolicy: 'never',
|
|
254
|
+
sandbox: 'read-only',
|
|
255
|
+
ephemeral: true,
|
|
256
|
+
experimentalRawEvents: false,
|
|
257
|
+
persistExtendedHistory: false,
|
|
258
|
+
developerInstructions: developerInstructions || null
|
|
259
|
+
},
|
|
260
|
+
deadlineAt,
|
|
261
|
+
abortSignal
|
|
262
|
+
});
|
|
263
|
+
threadId = threadStartResponse?.thread?.id;
|
|
264
|
+
if (!threadId) throw new Error('thread/start did not return a thread id');
|
|
265
|
+
const turnStartResponse = await this.request({
|
|
266
|
+
method: 'turn/start',
|
|
267
|
+
params: {
|
|
268
|
+
threadId,
|
|
269
|
+
input,
|
|
270
|
+
effort
|
|
271
|
+
},
|
|
272
|
+
deadlineAt,
|
|
273
|
+
abortSignal
|
|
274
|
+
});
|
|
275
|
+
turnId = turnStartResponse?.turn?.id;
|
|
276
|
+
if (!turnId) throw new Error('turn/start did not return a turn id');
|
|
277
|
+
let turnStatus;
|
|
278
|
+
while(!turnStatus){
|
|
279
|
+
const message = await this.nextMessage({
|
|
280
|
+
deadlineAt,
|
|
281
|
+
abortSignal
|
|
282
|
+
});
|
|
283
|
+
if (this.isResponseMessage(message)) continue;
|
|
284
|
+
if (this.isRequestMessage(message)) {
|
|
285
|
+
await this.respondToServerRequest(message);
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
const notification = message;
|
|
289
|
+
const method = notification.method;
|
|
290
|
+
const params = notification.params || {};
|
|
291
|
+
if ('error' === method) {
|
|
292
|
+
const messageText = params.error?.message || params.message || 'codex app-server reported turn error';
|
|
293
|
+
latestErrorMessage = String(messageText);
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
if ('item/agentMessage/delta' === method && params.threadId === threadId && params.turnId === turnId) {
|
|
297
|
+
const delta = String(params.delta || '');
|
|
298
|
+
if (delta) {
|
|
299
|
+
accumulatedText += delta;
|
|
300
|
+
emitChunk({
|
|
301
|
+
content: delta,
|
|
302
|
+
reasoning: '',
|
|
303
|
+
isComplete: false
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
if (('item/reasoning/summaryTextDelta' === method || 'item/reasoning/textDelta' === method) && params.threadId === threadId && params.turnId === turnId) {
|
|
309
|
+
const delta = String(params.delta || '');
|
|
310
|
+
if (delta) {
|
|
311
|
+
accumulatedReasoning += delta;
|
|
312
|
+
emitChunk({
|
|
313
|
+
content: '',
|
|
314
|
+
reasoning: delta,
|
|
315
|
+
isComplete: false
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
continue;
|
|
319
|
+
}
|
|
320
|
+
if ('item/completed' === method && params.threadId === threadId && params.turnId === turnId && params.item?.type === 'agentMessage' && 'string' == typeof params.item?.text && !accumulatedText) {
|
|
321
|
+
accumulatedText = params.item.text;
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
if ('thread/tokenUsage/updated' === method && params.threadId === threadId && params.turnId === turnId) {
|
|
325
|
+
latestUsage = this.mapUsage({
|
|
326
|
+
usage: params,
|
|
327
|
+
modelConfig,
|
|
328
|
+
turnId,
|
|
329
|
+
startTime
|
|
330
|
+
});
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
if ('turn/completed' === method && params.threadId === threadId && params.turn?.id === turnId) {
|
|
334
|
+
turnStatus = String(params.turn.status || '');
|
|
335
|
+
latestErrorMessage = params.turn?.error?.message || latestErrorMessage || void 0;
|
|
336
|
+
break;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
if ('completed' !== turnStatus) throw new Error(latestErrorMessage || `codex turn finished with status "${turnStatus || 'unknown'}"`);
|
|
340
|
+
if (isStreaming) emitChunk({
|
|
341
|
+
content: '',
|
|
342
|
+
reasoning: '',
|
|
343
|
+
isComplete: true,
|
|
344
|
+
usage: latestUsage
|
|
345
|
+
});
|
|
346
|
+
return {
|
|
347
|
+
content: accumulatedText,
|
|
348
|
+
reasoning_content: accumulatedReasoning || void 0,
|
|
349
|
+
usage: latestUsage,
|
|
350
|
+
isStreamed: isStreaming
|
|
351
|
+
};
|
|
352
|
+
} catch (error) {
|
|
353
|
+
if (isAbortError(error) && threadId && turnId) await this.request({
|
|
354
|
+
method: 'turn/interrupt',
|
|
355
|
+
params: {
|
|
356
|
+
threadId,
|
|
357
|
+
turnId
|
|
358
|
+
},
|
|
359
|
+
deadlineAt: Date.now() + 5000
|
|
360
|
+
}).catch(()=>{});
|
|
361
|
+
throw error;
|
|
362
|
+
} finally{
|
|
363
|
+
if (threadId) await this.request({
|
|
364
|
+
method: 'thread/unsubscribe',
|
|
365
|
+
params: {
|
|
366
|
+
threadId
|
|
367
|
+
},
|
|
368
|
+
deadlineAt: Date.now() + CODEX_DEFAULT_CLEANUP_TIMEOUT_MS
|
|
369
|
+
}).catch((error)=>{
|
|
370
|
+
warnCodex(`failed to unsubscribe codex thread ${threadId}: ${String(error)}`);
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
async dispose() {
|
|
375
|
+
if (this.closed) return;
|
|
376
|
+
this.closed = true;
|
|
377
|
+
try {
|
|
378
|
+
this.lineReader?.close?.();
|
|
379
|
+
} catch {}
|
|
380
|
+
try {
|
|
381
|
+
this.child?.stdin?.end?.();
|
|
382
|
+
} catch {}
|
|
383
|
+
try {
|
|
384
|
+
this.child?.kill?.();
|
|
385
|
+
} catch {}
|
|
386
|
+
}
|
|
387
|
+
attachProcessListeners() {
|
|
388
|
+
this.lineReader.on('line', (line)=>{
|
|
389
|
+
this.lineBuffer.push(line);
|
|
390
|
+
});
|
|
391
|
+
this.child.stderr.on('data', (chunk)=>{
|
|
392
|
+
const text = Buffer.isBuffer(chunk) ? chunk.toString('utf8') : String(chunk);
|
|
393
|
+
this.stderrBuffer += text;
|
|
394
|
+
if (this.stderrBuffer.length > 8192) this.stderrBuffer = this.stderrBuffer.slice(-8192);
|
|
395
|
+
});
|
|
396
|
+
this.child.on('exit', (code)=>{
|
|
397
|
+
this.closed = true;
|
|
398
|
+
this.lastExitCode = code;
|
|
399
|
+
});
|
|
400
|
+
this.child.on('error', (error)=>{
|
|
401
|
+
this.closed = true;
|
|
402
|
+
this.processErrorMessage = error.message;
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
detachFromEventLoop() {
|
|
406
|
+
this.child.unref?.();
|
|
407
|
+
this.child.stdin?.unref?.();
|
|
408
|
+
this.child.stdout?.unref?.();
|
|
409
|
+
this.child.stderr?.unref?.();
|
|
410
|
+
}
|
|
411
|
+
async initializeHandshake() {
|
|
412
|
+
const deadlineAt = Date.now() + CODEX_DEFAULT_PROCESS_START_TIMEOUT_MS;
|
|
413
|
+
await this.request({
|
|
414
|
+
method: 'initialize',
|
|
415
|
+
params: {
|
|
416
|
+
clientInfo: {
|
|
417
|
+
name: 'midscene_codex_provider',
|
|
418
|
+
title: 'Midscene Codex Provider',
|
|
419
|
+
version: '1.0.0'
|
|
420
|
+
},
|
|
421
|
+
capabilities: {
|
|
422
|
+
experimentalApi: false
|
|
423
|
+
}
|
|
424
|
+
},
|
|
425
|
+
deadlineAt
|
|
426
|
+
});
|
|
427
|
+
await this.sendMessage({
|
|
428
|
+
method: 'initialized'
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
mapUsage({ usage, modelConfig, turnId, startTime }) {
|
|
432
|
+
const tokenUsage = usage.tokenUsage;
|
|
433
|
+
const picked = tokenUsage?.last || tokenUsage?.total;
|
|
434
|
+
if (!picked) return;
|
|
435
|
+
return {
|
|
436
|
+
prompt_tokens: picked.inputTokens ?? 0,
|
|
437
|
+
completion_tokens: picked.outputTokens ?? 0,
|
|
438
|
+
total_tokens: picked.totalTokens ?? 0,
|
|
439
|
+
cached_input: picked.cachedInputTokens ?? 0,
|
|
440
|
+
time_cost: Date.now() - startTime,
|
|
441
|
+
model_name: modelConfig.modelName,
|
|
442
|
+
model_description: modelConfig.modelDescription,
|
|
443
|
+
intent: modelConfig.intent,
|
|
444
|
+
request_id: turnId
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
isRequestMessage(message) {
|
|
448
|
+
return 'string' == typeof message?.method && message?.id !== void 0;
|
|
449
|
+
}
|
|
450
|
+
isResponseMessage(message) {
|
|
451
|
+
return message?.id !== void 0 && (message?.result !== void 0 || message?.error !== void 0) && 'string' != typeof message?.method;
|
|
452
|
+
}
|
|
453
|
+
async request({ method, params, deadlineAt, abortSignal }) {
|
|
454
|
+
const requestId = this.nextRequestId++;
|
|
455
|
+
await this.sendMessage({
|
|
456
|
+
id: requestId,
|
|
457
|
+
method,
|
|
458
|
+
params
|
|
459
|
+
});
|
|
460
|
+
while(true){
|
|
461
|
+
const message = await this.nextMessage({
|
|
462
|
+
deadlineAt,
|
|
463
|
+
abortSignal,
|
|
464
|
+
includePending: false
|
|
465
|
+
});
|
|
466
|
+
if (this.isResponseMessage(message) && message.id === requestId) {
|
|
467
|
+
if (message.error) throw new Error(`codex app-server ${method} failed: ${message.error.message || 'unknown error'}`);
|
|
468
|
+
return message.result || {};
|
|
469
|
+
}
|
|
470
|
+
if (this.isRequestMessage(message)) {
|
|
471
|
+
await this.respondToServerRequest(message);
|
|
472
|
+
continue;
|
|
473
|
+
}
|
|
474
|
+
this.pendingMessages.push(message);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
async respondToServerRequest(request) {
|
|
478
|
+
const requestId = request.id;
|
|
479
|
+
const method = request.method;
|
|
480
|
+
let result = {};
|
|
481
|
+
if ('item/commandExecution/requestApproval' === method) result = {
|
|
482
|
+
decision: 'decline'
|
|
483
|
+
};
|
|
484
|
+
else if ('item/fileChange/requestApproval' === method) result = {
|
|
485
|
+
decision: 'decline'
|
|
486
|
+
};
|
|
487
|
+
else if ('mcpServer/elicitation/request' === method) result = {
|
|
488
|
+
action: 'cancel',
|
|
489
|
+
content: null
|
|
490
|
+
};
|
|
491
|
+
else {
|
|
492
|
+
if ('item/tool/requestUserInput' !== method) return void await this.sendMessage({
|
|
493
|
+
id: requestId,
|
|
494
|
+
error: {
|
|
495
|
+
code: -32601,
|
|
496
|
+
message: `unsupported server request: ${method}`
|
|
497
|
+
}
|
|
498
|
+
});
|
|
499
|
+
result = {
|
|
500
|
+
answers: []
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
await this.sendMessage({
|
|
504
|
+
id: requestId,
|
|
505
|
+
result
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
async nextMessage({ deadlineAt, abortSignal, includePending = true }) {
|
|
509
|
+
if (includePending && this.pendingMessages.length) return this.pendingMessages.shift();
|
|
510
|
+
while(true){
|
|
511
|
+
if (abortSignal?.aborted) throw new Error('codex app-server request aborted');
|
|
512
|
+
if (deadlineAt && Date.now() > deadlineAt) throw new Error('codex app-server request timed out');
|
|
513
|
+
if (this.lineBuffer.length) {
|
|
514
|
+
const line = this.lineBuffer.shift();
|
|
515
|
+
const trimmed = line.trim();
|
|
516
|
+
if (!trimmed) continue;
|
|
517
|
+
let parsed;
|
|
518
|
+
try {
|
|
519
|
+
parsed = JSON.parse(trimmed);
|
|
520
|
+
} catch (error) {
|
|
521
|
+
warnCodex(`ignored non-JSON message from codex app-server: ${trimmed}`);
|
|
522
|
+
continue;
|
|
523
|
+
}
|
|
524
|
+
return parsed;
|
|
525
|
+
}
|
|
526
|
+
if (this.closed) throw this.createClosedConnectionError();
|
|
527
|
+
await new Promise((resolve)=>setTimeout(resolve, 20));
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
async sendMessage(payload) {
|
|
531
|
+
if (this.closed) throw this.createClosedConnectionError();
|
|
532
|
+
const line = JSON.stringify(payload);
|
|
533
|
+
await new Promise((resolve, reject)=>{
|
|
534
|
+
this.child.stdin.write(`${line}\n`, (error)=>{
|
|
535
|
+
if (error) return void reject(new Error(`failed writing to codex app-server stdin: ${error.message}`));
|
|
536
|
+
resolve();
|
|
537
|
+
});
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
createClosedConnectionError() {
|
|
541
|
+
const stderr = this.stderrBuffer.trim();
|
|
542
|
+
if (this.processErrorMessage) return new Error(stderr ? `codex app-server process error: ${this.processErrorMessage}. stderr=${stderr}` : `codex app-server process error: ${this.processErrorMessage}`);
|
|
543
|
+
return new Error(stderr ? `codex app-server connection closed (exitCode=${this.lastExitCode}). stderr=${stderr}` : `codex app-server connection closed (exitCode=${this.lastExitCode})`);
|
|
544
|
+
}
|
|
545
|
+
constructor(child, lineReader){
|
|
546
|
+
_define_property(this, "child", void 0);
|
|
547
|
+
_define_property(this, "lineReader", void 0);
|
|
548
|
+
_define_property(this, "pendingMessages", []);
|
|
549
|
+
_define_property(this, "lineBuffer", []);
|
|
550
|
+
_define_property(this, "nextRequestId", 1);
|
|
551
|
+
_define_property(this, "closed", false);
|
|
552
|
+
_define_property(this, "lastExitCode", null);
|
|
553
|
+
_define_property(this, "processErrorMessage", null);
|
|
554
|
+
_define_property(this, "stderrBuffer", '');
|
|
555
|
+
this.child = child;
|
|
556
|
+
this.lineReader = lineReader;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
class CodexAppServerConnectionManager {
|
|
560
|
+
async runTurn({ messages, modelConfig, stream, onChunk, deepThink, abortSignal }) {
|
|
561
|
+
return this.runner.run(async ()=>{
|
|
562
|
+
const connection = await this.getConnection();
|
|
563
|
+
try {
|
|
564
|
+
return await connection.runTurn({
|
|
565
|
+
messages,
|
|
566
|
+
modelConfig,
|
|
567
|
+
stream,
|
|
568
|
+
onChunk,
|
|
569
|
+
deepThink,
|
|
570
|
+
abortSignal
|
|
571
|
+
});
|
|
572
|
+
} catch (error) {
|
|
573
|
+
if (connection.isClosed() || !isAbortError(error)) await this.resetConnection();
|
|
574
|
+
throw error;
|
|
575
|
+
}
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
async shutdownForTests() {
|
|
579
|
+
await this.resetConnection();
|
|
580
|
+
}
|
|
581
|
+
async getConnection() {
|
|
582
|
+
if (!this.connection || this.connection.isClosed()) {
|
|
583
|
+
this.connection = await CodexAppServerConnection.create();
|
|
584
|
+
debugCodex('started long-lived codex app-server connection');
|
|
585
|
+
}
|
|
586
|
+
return this.connection;
|
|
587
|
+
}
|
|
588
|
+
async resetConnection() {
|
|
589
|
+
if (!this.connection) return;
|
|
590
|
+
const staleConnection = this.connection;
|
|
591
|
+
this.connection = null;
|
|
592
|
+
await staleConnection.dispose();
|
|
593
|
+
debugCodex('reset codex app-server connection');
|
|
594
|
+
}
|
|
595
|
+
constructor(){
|
|
596
|
+
_define_property(this, "connection", null);
|
|
597
|
+
_define_property(this, "runner", new SerializedRunner());
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
const codexConnectionManager = new CodexAppServerConnectionManager();
|
|
601
|
+
async function callAIWithCodexAppServer(messages, modelConfig, options) {
|
|
602
|
+
if (utils_namespaceObject.ifInBrowser) throw new Error('codex app-server provider is not supported in browser runtime');
|
|
603
|
+
return codexConnectionManager.runTurn({
|
|
604
|
+
messages,
|
|
605
|
+
modelConfig,
|
|
606
|
+
stream: options?.stream,
|
|
607
|
+
onChunk: options?.onChunk,
|
|
608
|
+
deepThink: options?.deepThink,
|
|
609
|
+
abortSignal: options?.abortSignal
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
async function __shutdownCodexAppServerForTests() {
|
|
613
|
+
await codexConnectionManager.shutdownForTests();
|
|
614
|
+
}
|
|
615
|
+
exports.__shutdownCodexAppServerForTests = __webpack_exports__.__shutdownCodexAppServerForTests;
|
|
616
|
+
exports.buildCodexTurnPayloadFromMessages = __webpack_exports__.buildCodexTurnPayloadFromMessages;
|
|
617
|
+
exports.callAIWithCodexAppServer = __webpack_exports__.callAIWithCodexAppServer;
|
|
618
|
+
exports.isCodexAppServerProvider = __webpack_exports__.isCodexAppServerProvider;
|
|
619
|
+
exports.normalizeCodexLocalImagePath = __webpack_exports__.normalizeCodexLocalImagePath;
|
|
620
|
+
exports.resolveCodexReasoningEffort = __webpack_exports__.resolveCodexReasoningEffort;
|
|
621
|
+
for(var __rspack_i in __webpack_exports__)if (-1 === [
|
|
622
|
+
"__shutdownCodexAppServerForTests",
|
|
623
|
+
"buildCodexTurnPayloadFromMessages",
|
|
624
|
+
"callAIWithCodexAppServer",
|
|
625
|
+
"isCodexAppServerProvider",
|
|
626
|
+
"normalizeCodexLocalImagePath",
|
|
627
|
+
"resolveCodexReasoningEffort"
|
|
628
|
+
].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
|
|
629
|
+
Object.defineProperty(exports, '__esModule', {
|
|
630
|
+
value: true
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
//# sourceMappingURL=codex-app-server.js.map
|