@xuda.io/ai_module 1.1.5589 → 1.1.5591
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 +454 -11
- 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
|
});
|
|
@@ -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,63 @@ 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
|
+
case 'studio': {
|
|
4358
|
+
break;
|
|
4359
|
+
}
|
|
4360
|
+
|
|
4361
|
+
default:
|
|
4362
|
+
break;
|
|
4363
|
+
}
|
|
4364
|
+
|
|
4365
|
+
if (route_to_studio) {
|
|
3932
4366
|
ret = await chat_studio(req, job_id, headers);
|
|
3933
4367
|
} else if (conversation_doc.reference_type === 'contacts') {
|
|
3934
4368
|
req._thread_reentry = Date.now() - conversation_doc.date_created_ts > 60000;
|
|
@@ -4251,7 +4685,7 @@ const chat_studio = async function (req, job_id, headers) {
|
|
|
4251
4685
|
let { prompt, conversation_doc, attachments = [], plan_mode = false, stream = true } = req;
|
|
4252
4686
|
|
|
4253
4687
|
const conversation_id = conversation_doc._id;
|
|
4254
|
-
const normalized_plan_mode =
|
|
4688
|
+
const normalized_plan_mode = normalize_boolean(plan_mode);
|
|
4255
4689
|
const save_result_to_studio = normalized_plan_mode ? false : true;
|
|
4256
4690
|
const plugin_name = '@xuda.io/xuda-library-plugin-studio-ai-app-builder';
|
|
4257
4691
|
const plugin_method = normalized_plan_mode ? 'app_planner' : 'app_builder';
|
|
@@ -4259,6 +4693,7 @@ const chat_studio = async function (req, job_id, headers) {
|
|
|
4259
4693
|
|
|
4260
4694
|
const prompt_conversation_item_id = await _common.xuda_get_uuid('chat_conversation_item');
|
|
4261
4695
|
const response_conversation_item_id = await _common.xuda_get_uuid('chat_conversation_item');
|
|
4696
|
+
const request_started_at = Date.now();
|
|
4262
4697
|
|
|
4263
4698
|
let stream_delta_seq = 0;
|
|
4264
4699
|
let stream_delta_text = '';
|
|
@@ -4276,6 +4711,7 @@ const chat_studio = async function (req, job_id, headers) {
|
|
|
4276
4711
|
conversation_id,
|
|
4277
4712
|
job_id,
|
|
4278
4713
|
delta: content,
|
|
4714
|
+
...(type === 'stream_start' ? { request_started_at } : {}),
|
|
4279
4715
|
...(seq ? { seq } : {}),
|
|
4280
4716
|
...(is_stream_end
|
|
4281
4717
|
? {
|
|
@@ -4432,6 +4868,7 @@ ${conversation_history || `User (studio): ${prompt}`}
|
|
|
4432
4868
|
|
|
4433
4869
|
try {
|
|
4434
4870
|
emitToDashboard('stream_start');
|
|
4871
|
+
if (normalized_plan_mode) emitToDashboard('plan_sream_start');
|
|
4435
4872
|
emitToDashboard('stream_phase', normalized_plan_mode ? 'Planning studio request' : 'Building studio request');
|
|
4436
4873
|
|
|
4437
4874
|
const conversation_item_reference_id = await add_conversation_item(uid, profile_id, conversation_id, prompt, 'studio', undefined, { plan_mode: normalized_plan_mode });
|
|
@@ -4470,6 +4907,7 @@ ${conversation_history || `User (studio): ${prompt}`}
|
|
|
4470
4907
|
conversation_doc = await db_module.get_app_couch_doc_native(account_profile_info.app_id, conversation_id);
|
|
4471
4908
|
conversation_doc.ts = Date.now();
|
|
4472
4909
|
conversation_doc.stat = 2;
|
|
4910
|
+
conversation_doc.request_started_at = request_started_at;
|
|
4473
4911
|
conversation_doc.job_id = job_id;
|
|
4474
4912
|
conversation_doc.plan_mode = normalized_plan_mode;
|
|
4475
4913
|
conversation_doc.target_app_id = target_app_id;
|
|
@@ -4544,6 +4982,7 @@ ${conversation_history || `User (studio): ${prompt}`}
|
|
|
4544
4982
|
|
|
4545
4983
|
emitToDashboard('stream_phase', normalized_plan_mode ? 'Streaming studio plan' : 'Streaming studio result', { update: true });
|
|
4546
4984
|
streamText(response_text);
|
|
4985
|
+
if (normalized_plan_mode) emitToDashboard('plan_sream_end');
|
|
4547
4986
|
emitToDashboard('stream_end');
|
|
4548
4987
|
|
|
4549
4988
|
conversation_doc = await db_module.get_app_couch_doc_native(account_profile_info.app_id, conversation_id);
|
|
@@ -4590,6 +5029,7 @@ ${conversation_history || `User (studio): ${prompt}`}
|
|
|
4590
5029
|
const error_message = get_error_message(err, 'studio request failed');
|
|
4591
5030
|
emitToDashboard('stream_phase', 'Studio request failed', { update: true });
|
|
4592
5031
|
streamText(`Studio request failed: ${error_message}`);
|
|
5032
|
+
if (normalized_plan_mode) emitToDashboard('plan_sream_end');
|
|
4593
5033
|
emitToDashboard('stream_end');
|
|
4594
5034
|
|
|
4595
5035
|
return { code: -16, data: error_message };
|
|
@@ -4930,6 +5370,7 @@ const ai_chat_conversation = async function (req, job_id, headers) {
|
|
|
4930
5370
|
|
|
4931
5371
|
const prompt_conversation_item_id = await _common.xuda_get_uuid('chat_conversation_item');
|
|
4932
5372
|
const response_conversation_item_id = await _common.xuda_get_uuid('chat_conversation_item');
|
|
5373
|
+
const request_started_at = Date.now();
|
|
4933
5374
|
|
|
4934
5375
|
let out_conversation_item_obj = {
|
|
4935
5376
|
_id: prompt_conversation_item_id,
|
|
@@ -4971,6 +5412,7 @@ const ai_chat_conversation = async function (req, job_id, headers) {
|
|
|
4971
5412
|
conversation_id,
|
|
4972
5413
|
job_id,
|
|
4973
5414
|
delta: content,
|
|
5415
|
+
...(type === 'stream_start' ? { request_started_at } : {}),
|
|
4974
5416
|
...(seq ? { seq } : {}),
|
|
4975
5417
|
...(is_stream_end
|
|
4976
5418
|
? {
|
|
@@ -5106,6 +5548,7 @@ const ai_chat_conversation = async function (req, job_id, headers) {
|
|
|
5106
5548
|
|
|
5107
5549
|
conversation_doc.stat = 2;
|
|
5108
5550
|
conversation_doc.ts = Date.now();
|
|
5551
|
+
conversation_doc.request_started_at = request_started_at;
|
|
5109
5552
|
conversation_doc.ai_agents = ai_agents;
|
|
5110
5553
|
conversation_doc.job_id = job_id;
|
|
5111
5554
|
await db_module.save_app_couch_doc_native(account_profile_info.app_id, conversation_doc);
|
package/index_ms.mjs
CHANGED
|
@@ -17,6 +17,10 @@
|
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
|
|
20
|
+
export const execute_codex_request = async function (...args) {
|
|
21
|
+
return await broker.send_to_queue("execute_codex_request", ...args);
|
|
22
|
+
};
|
|
23
|
+
|
|
20
24
|
export const get_chat_conversation = async function (...args) {
|
|
21
25
|
return await broker.send_to_queue("get_chat_conversation", ...args);
|
|
22
26
|
};
|
package/index_msa.mjs
CHANGED
|
@@ -17,6 +17,10 @@
|
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
|
|
20
|
+
export const execute_codex_request = function (...args) {
|
|
21
|
+
broker.send_to_queue_async("execute_codex_request", ...args);
|
|
22
|
+
};
|
|
23
|
+
|
|
20
24
|
export const get_chat_conversation = function (...args) {
|
|
21
25
|
broker.send_to_queue_async("get_chat_conversation", ...args);
|
|
22
26
|
};
|
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.5591",
|
|
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));
|