@xuda.io/ai_module 1.1.5588 → 1.1.5590
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/index.mjs +452 -13
- package/index_ms.mjs +4 -0
- package/index_msa.mjs +4 -0
- package/package.json +3 -2
- package/tests/execute-codex-request.mjs +74 -0
package/index.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import { z } from 'zod';
|
|
|
4
4
|
import fs from 'fs';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import { pathToFileURL } from 'node:url';
|
|
7
|
+
import { spawn } from 'node:child_process';
|
|
7
8
|
|
|
8
9
|
import sharp from 'sharp';
|
|
9
10
|
|
|
@@ -24,11 +25,133 @@ import process from 'process';
|
|
|
24
25
|
import { File } from 'node:buffer'; // Node 20+
|
|
25
26
|
const { rm, readFile, unlink, writeFile } = fs.promises;
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
const run_process = function (command, args, input, options = {}) {
|
|
29
|
+
return new Promise((resolve, reject) => {
|
|
30
|
+
const { onStdout, onStderr, ...spawn_options } = options;
|
|
31
|
+
const child = spawn(command, args, {
|
|
32
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
33
|
+
...spawn_options,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
let stdout = '';
|
|
37
|
+
let stderr = '';
|
|
38
|
+
|
|
39
|
+
child.stdout.on('data', (chunk) => {
|
|
40
|
+
const text = chunk.toString();
|
|
41
|
+
stdout += text;
|
|
42
|
+
if (onStdout) {
|
|
43
|
+
onStdout(text);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
child.stderr.on('data', (chunk) => {
|
|
48
|
+
const text = chunk.toString();
|
|
49
|
+
stderr += text;
|
|
50
|
+
if (onStderr) {
|
|
51
|
+
onStderr(text);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
child.on('error', reject);
|
|
56
|
+
child.on('close', (exit_code) => {
|
|
57
|
+
resolve({ exit_code, stdout, stderr });
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
if (input) {
|
|
61
|
+
child.stdin.write(input);
|
|
62
|
+
}
|
|
63
|
+
child.stdin.end();
|
|
64
|
+
});
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const normalize_boolean = function (value) {
|
|
68
|
+
if (typeof value === 'string') {
|
|
69
|
+
return ['true', '1', 'yes', 'on'].includes(value.toLowerCase());
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return Boolean(value);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const is_valid_codex_remote_host = function (ip) {
|
|
76
|
+
return typeof ip === 'string' && /^[a-zA-Z0-9.:%@_-]+$/.test(ip) && !ip.startsWith('-') && !ip.includes('..');
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const get_codex_remote_host = function (ip) {
|
|
80
|
+
return ip.includes('@') ? ip : `root@${ip}`;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const get_codex_local_attachment_path = function (attachment) {
|
|
84
|
+
if (!attachment || typeof attachment !== 'object') return null;
|
|
85
|
+
|
|
86
|
+
return attachment.local_path || attachment.local_file_path || (attachment.is_local ? attachment.path || attachment.file_path || attachment.filename : null);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const get_codex_remote_attachment_path = function (attachment) {
|
|
90
|
+
if (!attachment) return null;
|
|
91
|
+
if (typeof attachment === 'string') return attachment;
|
|
92
|
+
if (typeof attachment !== 'object') return null;
|
|
93
|
+
|
|
94
|
+
return attachment.remote_path || (attachment.is_remote ? attachment.path || attachment.file_path || attachment.filename : null);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const is_codex_image_attachment = function (attachment_path) {
|
|
98
|
+
return typeof attachment_path === 'string' && /\.(png|jpe?g|webp|gif)$/i.test(attachment_path);
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const parse_codex_jsonl = function (stdout) {
|
|
102
|
+
const events = [];
|
|
103
|
+
for (const line of stdout.split(/\r?\n/)) {
|
|
104
|
+
if (!line.trim()) continue;
|
|
105
|
+
try {
|
|
106
|
+
events.push(JSON.parse(line));
|
|
107
|
+
} catch (err) {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return events;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const create_codex_jsonl_stream_parser = function (onEvent) {
|
|
115
|
+
let buffer = '';
|
|
116
|
+
|
|
117
|
+
return function (chunk) {
|
|
118
|
+
buffer += chunk;
|
|
119
|
+
const lines = buffer.split(/\r?\n/);
|
|
120
|
+
buffer = lines.pop() || '';
|
|
121
|
+
|
|
122
|
+
for (const line of lines) {
|
|
123
|
+
if (!line.trim()) continue;
|
|
124
|
+
try {
|
|
125
|
+
onEvent(JSON.parse(line));
|
|
126
|
+
} catch (err) {
|
|
127
|
+
onEvent({
|
|
128
|
+
type: 'raw',
|
|
129
|
+
text: line,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const get_openai_codex_cli_command = function () {
|
|
137
|
+
return _conf.openai_codex_command || 'codex';
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const get_openai_codex_exec_args = function () {
|
|
141
|
+
const args = ['exec', '--json'];
|
|
142
|
+
const bypass_approvals_and_sandbox = typeof _conf.openai_codex_bypass_approvals_and_sandbox === 'undefined' ? true : _conf.openai_codex_bypass_approvals_and_sandbox;
|
|
143
|
+
const sandbox = _conf.openai_codex_sandbox;
|
|
144
|
+
|
|
145
|
+
if (bypass_approvals_and_sandbox) {
|
|
146
|
+
args.push('--dangerously-bypass-approvals-and-sandbox');
|
|
147
|
+
} else if (sandbox) {
|
|
148
|
+
args.push('--sandbox', sandbox);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return args;
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
global._conf = JSON.parse(fs.readFileSync(path.join(process.env.XUDA_HOME, process.env.XUDA_CONFIG), 'utf8'));
|
|
32
155
|
|
|
33
156
|
const _defaultAppConfig = {
|
|
34
157
|
credential: admin.credential.cert(_conf.firebase_serviceAccount),
|
|
@@ -1082,6 +1205,259 @@ const check_studio_doc_tool = tool({
|
|
|
1082
1205
|
},
|
|
1083
1206
|
});
|
|
1084
1207
|
|
|
1208
|
+
export const execute_codex_request = async function (req_or_ip, prompt_arg, attachments_arg = []) {
|
|
1209
|
+
let emitToDashboard = function () {};
|
|
1210
|
+
let streamText = function () {};
|
|
1211
|
+
let stream_enabled = false;
|
|
1212
|
+
|
|
1213
|
+
try {
|
|
1214
|
+
const is_req_call = req_or_ip && typeof req_or_ip === 'object' && !Array.isArray(req_or_ip);
|
|
1215
|
+
const ip = is_req_call ? req_or_ip.ip : req_or_ip;
|
|
1216
|
+
const prompt = is_req_call ? req_or_ip.prompt : prompt_arg;
|
|
1217
|
+
let attachments = is_req_call ? req_or_ip.attachments || [] : attachments_arg;
|
|
1218
|
+
const uid = is_req_call ? req_or_ip.uid : undefined;
|
|
1219
|
+
const conversation_id = is_req_call ? req_or_ip.conversation_id || req_or_ip.conversation_doc?._id : undefined;
|
|
1220
|
+
const job_id = is_req_call ? prompt_arg || req_or_ip.job_id : undefined;
|
|
1221
|
+
stream_enabled = is_req_call ? req_or_ip.stream !== false && req_or_ip.stream !== 'false' : false;
|
|
1222
|
+
const prompt_conversation_item_id = is_req_call ? await _common.xuda_get_uuid('chat_conversation_item') : undefined;
|
|
1223
|
+
const response_conversation_item_id = is_req_call ? await _common.xuda_get_uuid('chat_conversation_item') : undefined;
|
|
1224
|
+
|
|
1225
|
+
let stream_delta_seq = 0;
|
|
1226
|
+
let stream_delta_text = '';
|
|
1227
|
+
let response_started = false;
|
|
1228
|
+
|
|
1229
|
+
emitToDashboard = (type, content, params) => {
|
|
1230
|
+
if (!stream_enabled || !uid) return;
|
|
1231
|
+
|
|
1232
|
+
const is_stream_delta = type === 'stream_delta';
|
|
1233
|
+
const is_stream_end = type === 'stream_end';
|
|
1234
|
+
const seq = is_stream_delta ? ++stream_delta_seq : undefined;
|
|
1235
|
+
if (is_stream_delta) {
|
|
1236
|
+
stream_delta_text += content || '';
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
ws_dashboard_msa.emit_message_to_dashboard({
|
|
1240
|
+
service: type,
|
|
1241
|
+
to: uid,
|
|
1242
|
+
data: {
|
|
1243
|
+
conversation_id,
|
|
1244
|
+
job_id,
|
|
1245
|
+
delta: content,
|
|
1246
|
+
...(seq ? { seq } : {}),
|
|
1247
|
+
...(is_stream_end
|
|
1248
|
+
? {
|
|
1249
|
+
stream_id: response_conversation_item_id,
|
|
1250
|
+
final_seq: stream_delta_seq,
|
|
1251
|
+
text: stream_delta_text,
|
|
1252
|
+
}
|
|
1253
|
+
: {}),
|
|
1254
|
+
prompt_conversation_item_id,
|
|
1255
|
+
response_conversation_item_id,
|
|
1256
|
+
params,
|
|
1257
|
+
},
|
|
1258
|
+
});
|
|
1259
|
+
};
|
|
1260
|
+
|
|
1261
|
+
const ensureResponseStarted = function () {
|
|
1262
|
+
if (!response_started) {
|
|
1263
|
+
response_started = true;
|
|
1264
|
+
emitToDashboard('response_start');
|
|
1265
|
+
}
|
|
1266
|
+
};
|
|
1267
|
+
|
|
1268
|
+
streamText = function (text, chunk_size = 280) {
|
|
1269
|
+
if (!stream_enabled || !text) return;
|
|
1270
|
+
ensureResponseStarted();
|
|
1271
|
+
for (let i = 0; i < text.length; i += chunk_size) {
|
|
1272
|
+
emitToDashboard('stream_delta', text.slice(i, i + chunk_size));
|
|
1273
|
+
}
|
|
1274
|
+
};
|
|
1275
|
+
|
|
1276
|
+
if (!is_valid_codex_remote_host(ip)) {
|
|
1277
|
+
return { code: -1, data: 'invalid remote ip' };
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
if (typeof prompt !== 'string' || !prompt.trim()) {
|
|
1281
|
+
return { code: -2, data: 'prompt is mandatory field' };
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
if (!Array.isArray(attachments)) {
|
|
1285
|
+
attachments = attachments ? [attachments] : [];
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
const reasoning = [];
|
|
1289
|
+
let streamed_agent_messages = new Set();
|
|
1290
|
+
const pushReasoning = function (type, text, metadata = {}) {
|
|
1291
|
+
if (!text) return;
|
|
1292
|
+
reasoning.push({
|
|
1293
|
+
type,
|
|
1294
|
+
text,
|
|
1295
|
+
...metadata,
|
|
1296
|
+
});
|
|
1297
|
+
};
|
|
1298
|
+
|
|
1299
|
+
const handleCodexEvent = function (event) {
|
|
1300
|
+
if (!event || typeof event !== 'object') return;
|
|
1301
|
+
|
|
1302
|
+
switch (event.type) {
|
|
1303
|
+
case 'thread.started':
|
|
1304
|
+
emitToDashboard('stream_phase', 'Starting Codex thread', { update: true, thread_id: event.thread_id });
|
|
1305
|
+
break;
|
|
1306
|
+
case 'turn.started':
|
|
1307
|
+
emitToDashboard('stream_phase', 'Reasoning through Codex request', { update: true });
|
|
1308
|
+
break;
|
|
1309
|
+
case 'item.started': {
|
|
1310
|
+
const item = event.item || {};
|
|
1311
|
+
if (item.type === 'command_execution') {
|
|
1312
|
+
const command = item.command || '';
|
|
1313
|
+
pushReasoning('command_started', command, { item_id: item.id });
|
|
1314
|
+
emitToDashboard('stream_phase', 'Running remote command', { update: true, command });
|
|
1315
|
+
}
|
|
1316
|
+
break;
|
|
1317
|
+
}
|
|
1318
|
+
case 'item.completed': {
|
|
1319
|
+
const item = event.item || {};
|
|
1320
|
+
if (item.type === 'agent_message') {
|
|
1321
|
+
const text = item.text || '';
|
|
1322
|
+
pushReasoning('agent_message', text, { item_id: item.id });
|
|
1323
|
+
emitToDashboard('stream_phase', text.trim(), { update: true, reasoning: true });
|
|
1324
|
+
if (!streamed_agent_messages.has(item.id)) {
|
|
1325
|
+
streamed_agent_messages.add(item.id);
|
|
1326
|
+
streamText(`${text.trim()}\n\n`);
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
if (item.type === 'command_execution') {
|
|
1331
|
+
const command = item.command || '';
|
|
1332
|
+
const output = (item.aggregated_output || '').trim();
|
|
1333
|
+
pushReasoning('command_completed', command, {
|
|
1334
|
+
item_id: item.id,
|
|
1335
|
+
exit_code: item.exit_code,
|
|
1336
|
+
status: item.status,
|
|
1337
|
+
output: output.length > 4000 ? `${output.slice(0, 4000)}...` : output,
|
|
1338
|
+
});
|
|
1339
|
+
emitToDashboard('stream_phase', item.exit_code === 0 ? 'Remote command completed' : 'Remote command failed', { update: true, command, exit_code: item.exit_code });
|
|
1340
|
+
}
|
|
1341
|
+
break;
|
|
1342
|
+
}
|
|
1343
|
+
case 'error':
|
|
1344
|
+
pushReasoning('error', event.message);
|
|
1345
|
+
emitToDashboard('stream_phase', event.message, { update: true, error: true });
|
|
1346
|
+
break;
|
|
1347
|
+
case 'turn.completed':
|
|
1348
|
+
emitToDashboard('stream_phase', 'Codex request completed', { update: true, usage: event.usage });
|
|
1349
|
+
break;
|
|
1350
|
+
case 'turn.failed':
|
|
1351
|
+
pushReasoning('error', event.error?.message || 'Codex request failed');
|
|
1352
|
+
emitToDashboard('stream_phase', 'Codex request failed', { update: true, error: true });
|
|
1353
|
+
break;
|
|
1354
|
+
default:
|
|
1355
|
+
break;
|
|
1356
|
+
}
|
|
1357
|
+
};
|
|
1358
|
+
|
|
1359
|
+
const image_paths = [];
|
|
1360
|
+
const local_attachments = [];
|
|
1361
|
+
const remote_attachments = [];
|
|
1362
|
+
|
|
1363
|
+
for (const attachment of attachments) {
|
|
1364
|
+
const attachment_path = get_codex_remote_attachment_path(attachment);
|
|
1365
|
+
const local_attachment_path = get_codex_local_attachment_path(attachment);
|
|
1366
|
+
|
|
1367
|
+
if (local_attachment_path && is_codex_image_attachment(local_attachment_path)) {
|
|
1368
|
+
image_paths.push(local_attachment_path);
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
if (local_attachment_path) {
|
|
1372
|
+
local_attachments.push(local_attachment_path);
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
if (attachment_path) {
|
|
1376
|
+
remote_attachments.push(attachment_path);
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
emitToDashboard('stream_start');
|
|
1381
|
+
emitToDashboard('stream_phase', 'Starting Codex request');
|
|
1382
|
+
|
|
1383
|
+
let codex_prompt = `You are OpenAI Codex running on the local Xuda server.
|
|
1384
|
+
|
|
1385
|
+
The target remote machine is ${get_codex_remote_host(ip)}.
|
|
1386
|
+
Execute shell commands on the remote machine through SSH, for example:
|
|
1387
|
+
ssh -o BatchMode=yes -o StrictHostKeyChecking=no ${get_codex_remote_host(ip)} "<command>"
|
|
1388
|
+
|
|
1389
|
+
Do not assume the current local filesystem is the remote machine. Use SSH for inspection, edits, installs, and command execution unless the user explicitly asks for local work.
|
|
1390
|
+
|
|
1391
|
+
User request:
|
|
1392
|
+
${prompt}`;
|
|
1393
|
+
|
|
1394
|
+
if (local_attachments.length) {
|
|
1395
|
+
codex_prompt += `\n\nAttachments available to Codex on the local Xuda server:\n${local_attachments.map((attachment_path) => `- ${attachment_path}`).join('\n')}`;
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
if (remote_attachments.length) {
|
|
1399
|
+
codex_prompt += `\n\nAttachments available on the remote machine:\n${remote_attachments.map((attachment_path) => `- ${attachment_path}`).join('\n')}`;
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
const openai_codex_command = get_openai_codex_cli_command();
|
|
1403
|
+
const codex_args = get_openai_codex_exec_args();
|
|
1404
|
+
for (const image_path of image_paths) {
|
|
1405
|
+
codex_args.push('--image', image_path);
|
|
1406
|
+
}
|
|
1407
|
+
codex_args.push('-');
|
|
1408
|
+
|
|
1409
|
+
const ret = await run_process(openai_codex_command, codex_args, codex_prompt, {
|
|
1410
|
+
env: {
|
|
1411
|
+
...process.env,
|
|
1412
|
+
OPENAI_API_KEY: _conf.OPENAI_API_KEY,
|
|
1413
|
+
},
|
|
1414
|
+
onStdout: create_codex_jsonl_stream_parser(handleCodexEvent),
|
|
1415
|
+
});
|
|
1416
|
+
const events = parse_codex_jsonl(ret.stdout);
|
|
1417
|
+
|
|
1418
|
+
if (ret.exit_code !== 0) {
|
|
1419
|
+
emitToDashboard('stream_phase', 'Codex request failed', { update: true });
|
|
1420
|
+
streamText(`Codex request failed: ${ret.stderr || ret.stdout || `exit code ${ret.exit_code}`}`);
|
|
1421
|
+
emitToDashboard('stream_end');
|
|
1422
|
+
|
|
1423
|
+
return {
|
|
1424
|
+
code: -3,
|
|
1425
|
+
data: {
|
|
1426
|
+
provider: 'openai_codex_cli',
|
|
1427
|
+
remote_host: get_codex_remote_host(ip),
|
|
1428
|
+
command: `${openai_codex_command} ${codex_args.filter((arg) => arg !== '-').join(' ')}`,
|
|
1429
|
+
exit_code: ret.exit_code,
|
|
1430
|
+
stdout: ret.stdout,
|
|
1431
|
+
stderr: ret.stderr,
|
|
1432
|
+
events,
|
|
1433
|
+
reasoning,
|
|
1434
|
+
},
|
|
1435
|
+
};
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
emitToDashboard('stream_end');
|
|
1439
|
+
|
|
1440
|
+
return {
|
|
1441
|
+
code: 0,
|
|
1442
|
+
data: {
|
|
1443
|
+
provider: 'openai_codex_cli',
|
|
1444
|
+
remote_host: get_codex_remote_host(ip),
|
|
1445
|
+
command: `${openai_codex_command} ${codex_args.filter((arg) => arg !== '-').join(' ')}`,
|
|
1446
|
+
stdout: ret.stdout,
|
|
1447
|
+
stderr: ret.stderr,
|
|
1448
|
+
events,
|
|
1449
|
+
reasoning,
|
|
1450
|
+
},
|
|
1451
|
+
};
|
|
1452
|
+
} catch (err) {
|
|
1453
|
+
console.error(err);
|
|
1454
|
+
emitToDashboard('stream_phase', 'Codex request failed', { update: true });
|
|
1455
|
+
streamText(`Codex request failed: ${err.message}`);
|
|
1456
|
+
emitToDashboard('stream_end');
|
|
1457
|
+
return { code: -1, data: err.message };
|
|
1458
|
+
}
|
|
1459
|
+
};
|
|
1460
|
+
|
|
1085
1461
|
export const get_chat_conversation = async function (req) {
|
|
1086
1462
|
let { conversation_id, order = 'asc', limit = 9999, skip = 0, _id, uid } = req;
|
|
1087
1463
|
|
|
@@ -1100,6 +1476,7 @@ export const get_chat_conversation = async function (req) {
|
|
|
1100
1476
|
conversation_doc = await db_module.get_app_couch_doc_native(reference_doc.shared_from_app_id, reference_doc.share_item_id);
|
|
1101
1477
|
conversation_doc.reference_doc = reference_doc;
|
|
1102
1478
|
}
|
|
1479
|
+
conversation_doc.request_started_at = conversation_doc.request_started_at || null;
|
|
1103
1480
|
let conversation_items = await db_module.find_app_couch_query(account_profile_info.app_id, {
|
|
1104
1481
|
selector: {
|
|
1105
1482
|
docType: 'chat_conversation_item',
|
|
@@ -1602,6 +1979,7 @@ export const unarchive_ai_chat = async function (req) {
|
|
|
1602
1979
|
conversation_doc.stat = 3;
|
|
1603
1980
|
conversation_doc.ts = Date.now();
|
|
1604
1981
|
const save_ret = await db_module.save_app_couch_doc(account_profile_info.app_id, conversation_doc);
|
|
1982
|
+
await update_conversation_items_stat(account_profile_info.app_id, 3, conversation_id);
|
|
1605
1983
|
|
|
1606
1984
|
return save_ret;
|
|
1607
1985
|
} catch (err) {
|
|
@@ -2262,7 +2640,7 @@ export const uninstall_ai_agent = async function (req) {
|
|
|
2262
2640
|
}
|
|
2263
2641
|
};
|
|
2264
2642
|
export const unarchive_ai_agent = async function (req) {
|
|
2265
|
-
let { agent_id, uid } = req;
|
|
2643
|
+
let { agent_id, uid, conversation_id } = req;
|
|
2266
2644
|
|
|
2267
2645
|
const account_profile_info = await get_active_account_profile_info(uid);
|
|
2268
2646
|
|
|
@@ -2277,8 +2655,9 @@ export const unarchive_ai_agent = async function (req) {
|
|
|
2277
2655
|
agent_doc.ts = Date.now();
|
|
2278
2656
|
|
|
2279
2657
|
const save_ret = await db_module.save_app_couch_doc(account_profile_info.app_id, agent_doc);
|
|
2658
|
+
const updated_conversation_items = await update_conversation_stat(account_profile_info.app_id, agent_id, 3, conversation_id);
|
|
2280
2659
|
|
|
2281
|
-
return save_ret;
|
|
2660
|
+
return { code: 1, data: { save_ret, updated_conversation_items } };
|
|
2282
2661
|
} catch (err) {
|
|
2283
2662
|
return { code: -9, data: err.message };
|
|
2284
2663
|
}
|
|
@@ -2286,11 +2665,12 @@ export const unarchive_ai_agent = async function (req) {
|
|
|
2286
2665
|
|
|
2287
2666
|
const update_conversation_items_stat = async function (app_id, stat, target_conversation_id) {
|
|
2288
2667
|
const ts = Date.now();
|
|
2668
|
+
const stat_selector = stat === 5 ? { $lt: 4 } : 5;
|
|
2289
2669
|
const items_ret = await db_module.find_app_couch_query(app_id, {
|
|
2290
2670
|
selector: {
|
|
2291
2671
|
docType: 'chat_conversation_item',
|
|
2292
2672
|
conversation_id: target_conversation_id,
|
|
2293
|
-
stat:
|
|
2673
|
+
stat: stat_selector,
|
|
2294
2674
|
},
|
|
2295
2675
|
limit: 9999,
|
|
2296
2676
|
});
|
|
@@ -2308,7 +2688,7 @@ const update_conversation_items_stat = async function (app_id, stat, target_conv
|
|
|
2308
2688
|
return docs.length;
|
|
2309
2689
|
};
|
|
2310
2690
|
|
|
2311
|
-
const
|
|
2691
|
+
const update_conversation_stat = async function (app_id, agent_id, stat, conversation_id) {
|
|
2312
2692
|
const conversation_ids = [];
|
|
2313
2693
|
|
|
2314
2694
|
if (conversation_id) {
|
|
@@ -2349,7 +2729,7 @@ export const archive_ai_agent = async function (req) {
|
|
|
2349
2729
|
agent_doc.stat = 5;
|
|
2350
2730
|
agent_doc.ts = Date.now();
|
|
2351
2731
|
const save_ret = await db_module.save_app_couch_doc(account_profile_info.app_id, agent_doc);
|
|
2352
|
-
const updated_conversation_items = await
|
|
2732
|
+
const updated_conversation_items = await update_conversation_stat(account_profile_info.app_id, agent_id, 5, conversation_id);
|
|
2353
2733
|
|
|
2354
2734
|
return { code: 1, data: { save_ret, updated_conversation_items } };
|
|
2355
2735
|
} catch (err) {
|
|
@@ -3682,7 +4062,7 @@ export const create_openai_conversation = async function () {
|
|
|
3682
4062
|
};
|
|
3683
4063
|
|
|
3684
4064
|
export const create_conversation = async function (req, job_id, headers) {
|
|
3685
|
-
const { profile_id, uid, prompt, perform_ai_execution = true, email_id, direction = 'out', from_mailbox, date_created, ai_model = _conf.default_ai_model } = req;
|
|
4065
|
+
const { profile_id, uid, prompt, perform_ai_execution = true, email_id, direction = 'out', from_mailbox, date_created, ai_model = _conf.default_ai_model, plan_mode = false } = req;
|
|
3686
4066
|
let { reference_type, reference_id = '', conversation_type, email_recipient_type } = req;
|
|
3687
4067
|
|
|
3688
4068
|
const account_profile_info = await get_active_account_profile_info(uid, profile_id);
|
|
@@ -3737,6 +4117,7 @@ export const create_conversation = async function (req, job_id, headers) {
|
|
|
3737
4117
|
from_mailbox,
|
|
3738
4118
|
email_recipient_type,
|
|
3739
4119
|
model: ai_model,
|
|
4120
|
+
plan_mode: normalize_boolean(plan_mode),
|
|
3740
4121
|
};
|
|
3741
4122
|
const save_ret = await db_module.save_app_couch_doc(account_profile_info.app_id, conversation_doc);
|
|
3742
4123
|
|
|
@@ -3925,10 +4306,59 @@ export const submit_chat_conversation = async function (req, job_id, headers) {
|
|
|
3925
4306
|
const account_profile_info = await get_active_account_profile_info(uid, profile_id);
|
|
3926
4307
|
|
|
3927
4308
|
let conversation_doc = await db_module.get_app_couch_doc_native(account_profile_info.app_id, conversation_id);
|
|
4309
|
+
const normalized_plan_mode = normalize_boolean(req.plan_mode ?? conversation_doc.plan_mode);
|
|
4310
|
+
const route_to_studio = conversation_doc.conversation_type === 'studio' || req.conversation_type === 'studio' || normalized_plan_mode;
|
|
4311
|
+
|
|
4312
|
+
if (route_to_studio && conversation_doc.conversation_type !== 'studio') {
|
|
4313
|
+
conversation_doc.conversation_type = 'studio';
|
|
4314
|
+
conversation_doc.plan_mode = normalized_plan_mode;
|
|
4315
|
+
await db_module.save_app_couch_doc_native(account_profile_info.app_id, conversation_doc);
|
|
4316
|
+
}
|
|
4317
|
+
|
|
4318
|
+
req.plan_mode = normalized_plan_mode;
|
|
3928
4319
|
req.conversation_doc = conversation_doc;
|
|
3929
4320
|
req.metadata = metadata || {};
|
|
3930
4321
|
let ret;
|
|
3931
|
-
|
|
4322
|
+
|
|
4323
|
+
switch (conversation_doc.reference_type) {
|
|
4324
|
+
case 'contacts': {
|
|
4325
|
+
req._thread_reentry = Date.now() - conversation_doc.date_created_ts > 60000;
|
|
4326
|
+
|
|
4327
|
+
account_msa.ts_contact(uid, conversation_doc.reference_id);
|
|
4328
|
+
|
|
4329
|
+
switch (conversation_doc.conversation_type) {
|
|
4330
|
+
case 'note': {
|
|
4331
|
+
ret = await chat_note(req, job_id, headers);
|
|
4332
|
+
break;
|
|
4333
|
+
}
|
|
4334
|
+
case 'ai_chat': {
|
|
4335
|
+
ret = await ai_chat_conversation(req, job_id, headers);
|
|
4336
|
+
break;
|
|
4337
|
+
}
|
|
4338
|
+
|
|
4339
|
+
case 'email': {
|
|
4340
|
+
ret = await chat_email(req, job_id, headers);
|
|
4341
|
+
break;
|
|
4342
|
+
}
|
|
4343
|
+
|
|
4344
|
+
case 'chat': {
|
|
4345
|
+
ret = await contact_chat_conversation(req, job_id, headers);
|
|
4346
|
+
break;
|
|
4347
|
+
}
|
|
4348
|
+
|
|
4349
|
+
default:
|
|
4350
|
+
ret = { code: -44, data: 'invalid conversation_type ' + conversation_doc.conversation_type };
|
|
4351
|
+
break;
|
|
4352
|
+
}
|
|
4353
|
+
|
|
4354
|
+
break;
|
|
4355
|
+
}
|
|
4356
|
+
|
|
4357
|
+
default:
|
|
4358
|
+
break;
|
|
4359
|
+
}
|
|
4360
|
+
|
|
4361
|
+
if (route_to_studio) {
|
|
3932
4362
|
ret = await chat_studio(req, job_id, headers);
|
|
3933
4363
|
} else if (conversation_doc.reference_type === 'contacts') {
|
|
3934
4364
|
req._thread_reentry = Date.now() - conversation_doc.date_created_ts > 60000;
|
|
@@ -4251,7 +4681,7 @@ const chat_studio = async function (req, job_id, headers) {
|
|
|
4251
4681
|
let { prompt, conversation_doc, attachments = [], plan_mode = false, stream = true } = req;
|
|
4252
4682
|
|
|
4253
4683
|
const conversation_id = conversation_doc._id;
|
|
4254
|
-
const normalized_plan_mode =
|
|
4684
|
+
const normalized_plan_mode = normalize_boolean(plan_mode);
|
|
4255
4685
|
const save_result_to_studio = normalized_plan_mode ? false : true;
|
|
4256
4686
|
const plugin_name = '@xuda.io/xuda-library-plugin-studio-ai-app-builder';
|
|
4257
4687
|
const plugin_method = normalized_plan_mode ? 'app_planner' : 'app_builder';
|
|
@@ -4259,6 +4689,7 @@ const chat_studio = async function (req, job_id, headers) {
|
|
|
4259
4689
|
|
|
4260
4690
|
const prompt_conversation_item_id = await _common.xuda_get_uuid('chat_conversation_item');
|
|
4261
4691
|
const response_conversation_item_id = await _common.xuda_get_uuid('chat_conversation_item');
|
|
4692
|
+
const request_started_at = Date.now();
|
|
4262
4693
|
|
|
4263
4694
|
let stream_delta_seq = 0;
|
|
4264
4695
|
let stream_delta_text = '';
|
|
@@ -4276,6 +4707,7 @@ const chat_studio = async function (req, job_id, headers) {
|
|
|
4276
4707
|
conversation_id,
|
|
4277
4708
|
job_id,
|
|
4278
4709
|
delta: content,
|
|
4710
|
+
...(type === 'stream_start' ? { request_started_at } : {}),
|
|
4279
4711
|
...(seq ? { seq } : {}),
|
|
4280
4712
|
...(is_stream_end
|
|
4281
4713
|
? {
|
|
@@ -4432,6 +4864,7 @@ ${conversation_history || `User (studio): ${prompt}`}
|
|
|
4432
4864
|
|
|
4433
4865
|
try {
|
|
4434
4866
|
emitToDashboard('stream_start');
|
|
4867
|
+
if (normalized_plan_mode) emitToDashboard('plan_sream_start');
|
|
4435
4868
|
emitToDashboard('stream_phase', normalized_plan_mode ? 'Planning studio request' : 'Building studio request');
|
|
4436
4869
|
|
|
4437
4870
|
const conversation_item_reference_id = await add_conversation_item(uid, profile_id, conversation_id, prompt, 'studio', undefined, { plan_mode: normalized_plan_mode });
|
|
@@ -4470,6 +4903,7 @@ ${conversation_history || `User (studio): ${prompt}`}
|
|
|
4470
4903
|
conversation_doc = await db_module.get_app_couch_doc_native(account_profile_info.app_id, conversation_id);
|
|
4471
4904
|
conversation_doc.ts = Date.now();
|
|
4472
4905
|
conversation_doc.stat = 2;
|
|
4906
|
+
conversation_doc.request_started_at = request_started_at;
|
|
4473
4907
|
conversation_doc.job_id = job_id;
|
|
4474
4908
|
conversation_doc.plan_mode = normalized_plan_mode;
|
|
4475
4909
|
conversation_doc.target_app_id = target_app_id;
|
|
@@ -4544,6 +4978,7 @@ ${conversation_history || `User (studio): ${prompt}`}
|
|
|
4544
4978
|
|
|
4545
4979
|
emitToDashboard('stream_phase', normalized_plan_mode ? 'Streaming studio plan' : 'Streaming studio result', { update: true });
|
|
4546
4980
|
streamText(response_text);
|
|
4981
|
+
if (normalized_plan_mode) emitToDashboard('plan_sream_end');
|
|
4547
4982
|
emitToDashboard('stream_end');
|
|
4548
4983
|
|
|
4549
4984
|
conversation_doc = await db_module.get_app_couch_doc_native(account_profile_info.app_id, conversation_id);
|
|
@@ -4590,6 +5025,7 @@ ${conversation_history || `User (studio): ${prompt}`}
|
|
|
4590
5025
|
const error_message = get_error_message(err, 'studio request failed');
|
|
4591
5026
|
emitToDashboard('stream_phase', 'Studio request failed', { update: true });
|
|
4592
5027
|
streamText(`Studio request failed: ${error_message}`);
|
|
5028
|
+
if (normalized_plan_mode) emitToDashboard('plan_sream_end');
|
|
4593
5029
|
emitToDashboard('stream_end');
|
|
4594
5030
|
|
|
4595
5031
|
return { code: -16, data: error_message };
|
|
@@ -4930,6 +5366,7 @@ const ai_chat_conversation = async function (req, job_id, headers) {
|
|
|
4930
5366
|
|
|
4931
5367
|
const prompt_conversation_item_id = await _common.xuda_get_uuid('chat_conversation_item');
|
|
4932
5368
|
const response_conversation_item_id = await _common.xuda_get_uuid('chat_conversation_item');
|
|
5369
|
+
const request_started_at = Date.now();
|
|
4933
5370
|
|
|
4934
5371
|
let out_conversation_item_obj = {
|
|
4935
5372
|
_id: prompt_conversation_item_id,
|
|
@@ -4971,6 +5408,7 @@ const ai_chat_conversation = async function (req, job_id, headers) {
|
|
|
4971
5408
|
conversation_id,
|
|
4972
5409
|
job_id,
|
|
4973
5410
|
delta: content,
|
|
5411
|
+
...(type === 'stream_start' ? { request_started_at } : {}),
|
|
4974
5412
|
...(seq ? { seq } : {}),
|
|
4975
5413
|
...(is_stream_end
|
|
4976
5414
|
? {
|
|
@@ -5106,6 +5544,7 @@ const ai_chat_conversation = async function (req, job_id, headers) {
|
|
|
5106
5544
|
|
|
5107
5545
|
conversation_doc.stat = 2;
|
|
5108
5546
|
conversation_doc.ts = Date.now();
|
|
5547
|
+
conversation_doc.request_started_at = request_started_at;
|
|
5109
5548
|
conversation_doc.ai_agents = ai_agents;
|
|
5110
5549
|
conversation_doc.job_id = job_id;
|
|
5111
5550
|
await db_module.save_app_couch_doc_native(account_profile_info.app_id, conversation_doc);
|
package/index_ms.mjs
CHANGED
|
@@ -221,6 +221,10 @@ export const get_person_info = async function (...args) {
|
|
|
221
221
|
return await broker.send_to_queue("get_person_info", ...args);
|
|
222
222
|
};
|
|
223
223
|
|
|
224
|
+
export const execute_codex_request = async function (...args) {
|
|
225
|
+
return await broker.send_to_queue("execute_codex_request", ...args);
|
|
226
|
+
};
|
|
227
|
+
|
|
224
228
|
export const open_ai_alive_check = async function (...args) {
|
|
225
229
|
return await broker.send_to_queue("open_ai_alive_check", ...args);
|
|
226
230
|
};
|
package/index_msa.mjs
CHANGED
|
@@ -221,6 +221,10 @@ export const get_person_info = function (...args) {
|
|
|
221
221
|
broker.send_to_queue_async("get_person_info", ...args);
|
|
222
222
|
};
|
|
223
223
|
|
|
224
|
+
export const execute_codex_request = function (...args) {
|
|
225
|
+
broker.send_to_queue_async("execute_codex_request", ...args);
|
|
226
|
+
};
|
|
227
|
+
|
|
224
228
|
export const open_ai_alive_check = function (...args) {
|
|
225
229
|
broker.send_to_queue_async("open_ai_alive_check", ...args);
|
|
226
230
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xuda.io/ai_module",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.5590",
|
|
4
4
|
"description": "Xuda AI Module",
|
|
5
5
|
"main": "index.mjs",
|
|
6
6
|
"type": "module",
|
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {},
|
|
29
29
|
"scripts": {
|
|
30
|
-
"pub": "npm version patch --force && npm publish"
|
|
30
|
+
"pub": "npm version patch --force && npm publish",
|
|
31
|
+
"test:codex": "node tests/execute-codex-request.mjs"
|
|
31
32
|
},
|
|
32
33
|
"author": "Xuda Llc",
|
|
33
34
|
"license": "ISC"
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import process from 'node:process';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = path.dirname(__filename);
|
|
7
|
+
const repoRoot = path.resolve(__dirname, '../../..');
|
|
8
|
+
|
|
9
|
+
const parseArgs = function (argv) {
|
|
10
|
+
const args = { attachments: [] };
|
|
11
|
+
|
|
12
|
+
for (let i = 0; i < argv.length; i++) {
|
|
13
|
+
const token = argv[i];
|
|
14
|
+
const next = argv[i + 1];
|
|
15
|
+
|
|
16
|
+
if (token === '--ip' && next) {
|
|
17
|
+
args.ip = next;
|
|
18
|
+
i += 1;
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if ((token === '--prompt' || token === '--prompat') && next) {
|
|
23
|
+
args.prompt = next;
|
|
24
|
+
i += 1;
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if ((token === '--attachment' || token === '--attach') && next) {
|
|
29
|
+
args.attachments.push(next);
|
|
30
|
+
i += 1;
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (token === '--help') {
|
|
35
|
+
args.help = true;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return args;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const usage = `
|
|
43
|
+
Usage:
|
|
44
|
+
node cpi/ai_module/tests/execute-codex-request.mjs [options]
|
|
45
|
+
|
|
46
|
+
Options:
|
|
47
|
+
--ip <host> Remote server IP/host. Default: 134.122.125.174
|
|
48
|
+
--prompt <text> Codex prompt. Default: create locksmith website
|
|
49
|
+
--attachment <path> Remote attachment path. Can be repeated.
|
|
50
|
+
--help Show this help.
|
|
51
|
+
|
|
52
|
+
Environment:
|
|
53
|
+
XUDA_HOME Defaults to repo root.
|
|
54
|
+
XUDA_CONFIG Defaults to config_dev.json.
|
|
55
|
+
`;
|
|
56
|
+
|
|
57
|
+
const args = parseArgs(process.argv.slice(2));
|
|
58
|
+
|
|
59
|
+
if (args.help) {
|
|
60
|
+
console.log(usage.trim());
|
|
61
|
+
process.exit(0);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
process.env.XUDA_HOME ||= repoRoot;
|
|
65
|
+
process.env.XUDA_CONFIG ||= 'config_dev.json';
|
|
66
|
+
|
|
67
|
+
const ip = args.ip || '134.122.125.174';
|
|
68
|
+
const prompt = args.prompt || 'create locksmith website';
|
|
69
|
+
const attachments = args.attachments;
|
|
70
|
+
|
|
71
|
+
const aiModule = await import('../index.mjs');
|
|
72
|
+
const response = await aiModule.execute_codex_request(ip, prompt, attachments);
|
|
73
|
+
|
|
74
|
+
console.log(JSON.stringify(response, null, 2));
|