@xuda.io/ai_module 1.1.5595 → 1.1.5597

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/index.mjs +482 -52
  2. package/package.json +1 -1
package/index.mjs CHANGED
@@ -1490,6 +1490,9 @@ export const get_chat_conversation = async function (req) {
1490
1490
 
1491
1491
  for (let conversation_item_doc of conversation_items.docs || []) {
1492
1492
  conversation_item_doc.metadata = conversation_item_doc.metadata || {};
1493
+ if (conversation_item_doc.plan_mode && typeof conversation_item_doc.plan_accepted === 'undefined') {
1494
+ conversation_item_doc.plan_accepted = false;
1495
+ }
1493
1496
  if (!conversation_item_doc.read) {
1494
1497
  conversation_item_doc.read = {};
1495
1498
  }
@@ -4299,15 +4302,30 @@ const contactGuardrailAgent = new Agent({
4299
4302
  // };
4300
4303
 
4301
4304
  export const submit_chat_conversation = async function (req, job_id, headers) {
4302
- const { profile_id, uid, metadata = {} } = req;
4305
+ const { profile_id, uid, metadata = {}, accepted_plan_item_id } = req;
4303
4306
  let { conversation_id } = req;
4304
4307
  try {
4305
4308
  if (!conversation_id) throw new Error('missing conversation_id');
4306
4309
  const account_profile_info = await get_active_account_profile_info(uid, profile_id);
4307
4310
 
4308
4311
  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;
4312
+ let accepted_plan_item;
4313
+ if (accepted_plan_item_id) {
4314
+ accepted_plan_item = await db_module.get_app_couch_doc_native(account_profile_info.app_id, accepted_plan_item_id);
4315
+ if (accepted_plan_item.docType !== 'chat_conversation_item') throw new Error('accepted plan item must be a chat_conversation_item');
4316
+ if (accepted_plan_item.conversation_id !== conversation_id) throw new Error('accepted plan item does not belong to this conversation');
4317
+ if (!normalize_boolean(accepted_plan_item.plan_mode)) throw new Error('accepted plan item is not a plan-mode item');
4318
+
4319
+ if (!accepted_plan_item.plan_accepted) {
4320
+ accepted_plan_item.plan_accepted = true;
4321
+ accepted_plan_item.plan_accepted_at = Date.now();
4322
+ accepted_plan_item.plan_accepted_by_uid = uid;
4323
+ await db_module.save_app_couch_doc_native(account_profile_info.app_id, accepted_plan_item);
4324
+ }
4325
+ }
4326
+
4327
+ const normalized_plan_mode = accepted_plan_item_id ? false : normalize_boolean(req.plan_mode ?? conversation_doc.plan_mode);
4328
+ const route_to_studio = conversation_doc.conversation_type === 'studio' || req.conversation_type === 'studio' || normalized_plan_mode || Boolean(accepted_plan_item_id);
4311
4329
 
4312
4330
  if (route_to_studio && conversation_doc.conversation_type !== 'studio') {
4313
4331
  conversation_doc.conversation_type = 'studio';
@@ -4317,71 +4335,76 @@ export const submit_chat_conversation = async function (req, job_id, headers) {
4317
4335
 
4318
4336
  req.plan_mode = normalized_plan_mode;
4319
4337
  req.conversation_doc = conversation_doc;
4338
+ req.accepted_plan_item = accepted_plan_item;
4320
4339
  req.metadata = metadata || {};
4321
4340
  let ret;
4322
4341
 
4323
- switch (conversation_doc.reference_type) {
4324
- case 'contacts': {
4325
- req._thread_reentry = Date.now() - conversation_doc.date_created_ts > 60000;
4342
+ if (route_to_studio) {
4343
+ ret = await chat_studio(req, job_id, headers);
4344
+ } else {
4345
+ switch (conversation_doc.reference_type) {
4346
+ case 'contacts': {
4347
+ req._thread_reentry = Date.now() - conversation_doc.date_created_ts > 60000;
4326
4348
 
4327
- account_msa.ts_contact(uid, conversation_doc.reference_id);
4349
+ account_msa.ts_contact(uid, conversation_doc.reference_id);
4328
4350
 
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
- }
4351
+ switch (conversation_doc.conversation_type) {
4352
+ case 'note': {
4353
+ ret = await chat_note(req, job_id, headers);
4354
+ break;
4355
+ }
4356
+ case 'ai_chat': {
4357
+ ret = await ai_chat_conversation(req, job_id, headers);
4358
+ break;
4359
+ }
4338
4360
 
4339
- case 'email': {
4340
- ret = await chat_email(req, job_id, headers);
4341
- break;
4342
- }
4361
+ case 'email': {
4362
+ ret = await chat_email(req, job_id, headers);
4363
+ break;
4364
+ }
4343
4365
 
4344
- case 'chat': {
4345
- ret = await contact_chat_conversation(req, job_id, headers);
4346
- break;
4366
+ case 'chat': {
4367
+ ret = await contact_chat_conversation(req, job_id, headers);
4368
+ break;
4369
+ }
4370
+
4371
+ default:
4372
+ ret = { code: -44, data: 'invalid conversation_type ' + conversation_doc.conversation_type };
4373
+ break;
4347
4374
  }
4348
4375
 
4349
- default:
4350
- ret = { code: -44, data: 'invalid conversation_type ' + conversation_doc.conversation_type };
4351
- break;
4376
+ break;
4352
4377
  }
4353
4378
 
4354
- break;
4355
- }
4379
+ case 'ai_chats': {
4380
+ ret = await ai_chat_conversation(req, job_id, headers);
4381
+ break;
4382
+ }
4356
4383
 
4357
- case 'ai_chats': {
4358
- ret = await ai_chat_conversation(req, job_id, headers);
4359
- break;
4360
- }
4384
+ case 'apps': {
4385
+ ret = await chat_studio(req, job_id, headers);
4386
+ break;
4387
+ }
4361
4388
 
4362
- case 'apps': {
4363
- ret = await chat_studio(req, job_id, headers);
4364
- break;
4365
- }
4389
+ case 'ai_agent': {
4390
+ ret = await ai_chat_conversation(req, job_id, headers);
4391
+ break;
4392
+ }
4366
4393
 
4367
- case 'ai_agent': {
4368
- ret = await ai_chat_conversation(req, job_id, headers);
4369
- break;
4370
- }
4394
+ case 'studio': {
4395
+ ret = await chat_studio(req, job_id, headers);
4396
+ break;
4397
+ }
4371
4398
 
4372
- case 'studio': {
4373
- ret = await chat_studio(req, job_id, headers);
4374
- break;
4375
- }
4399
+ case 'dashboard': {
4400
+ ret = await dashboard_chat(req, job_id, headers);
4401
+ break;
4402
+ }
4376
4403
 
4377
- case 'dashboard': {
4378
- // ??
4379
- break;
4404
+ default:
4405
+ ret = await ai_chat_conversation(req, job_id, headers);
4406
+ break;
4380
4407
  }
4381
-
4382
- default:
4383
- ret = await ai_chat_conversation(req, job_id, headers);
4384
- break;
4385
4408
  }
4386
4409
 
4387
4410
  // if (route_to_studio) {
@@ -4704,7 +4727,7 @@ const chat_email = async function (req, job_id, headers) {
4704
4727
  const chat_studio = async function (req, job_id, headers) {
4705
4728
  const { uid, profile_id } = req;
4706
4729
  const account_profile_info = await get_active_account_profile_info(uid, profile_id);
4707
- let { prompt, conversation_doc, attachments = [], plan_mode = false, stream = true } = req;
4730
+ let { prompt, conversation_doc, attachments = [], plan_mode = false, stream = true, accepted_plan_item_id, accepted_plan_item } = req;
4708
4731
 
4709
4732
  const conversation_id = conversation_doc._id;
4710
4733
  const normalized_plan_mode = normalize_boolean(plan_mode);
@@ -4800,6 +4823,8 @@ const chat_studio = async function (req, job_id, headers) {
4800
4823
 
4801
4824
  const items = (ret.docs || []).reverse().filter((item) => item.text || item.prompt || item?.file?.filename);
4802
4825
  const conversation_history = items.map(formatConversationHistoryItem).join('\n\n');
4826
+ const accepted_plan_text = accepted_plan_item_id ? (accepted_plan_item?.studio_result || accepted_plan_item?.text || accepted_plan_item?.prompt || '').toString().trim() : '';
4827
+ const accepted_plan_context = accepted_plan_text ? `\nThe user has accepted the following plan. Execute it end-to-end:\n\n${accepted_plan_text}\n` : '';
4803
4828
 
4804
4829
  return `You are working inside Xuda Studio.
4805
4830
 
@@ -4807,6 +4832,7 @@ Use the conversation history below as the main source of context for the current
4807
4832
  The last user message is the latest request that should be handled now.
4808
4833
  When plan_mode is true, create a plan only and do not save studio docs.
4809
4834
  When plan_mode is false, generate the studio docs and save them into the project.
4835
+ ${accepted_plan_context}
4810
4836
 
4811
4837
  Conversation history:
4812
4838
  ${conversation_history || `User (studio): ${prompt}`}
@@ -4914,6 +4940,7 @@ ${conversation_history || `User (studio): ${prompt}`}
4914
4940
  rtl: _common.detectRTL(prompt),
4915
4941
  job_id,
4916
4942
  plan_mode: normalized_plan_mode,
4943
+ plan_accepted: false,
4917
4944
  studio_method: plugin_method,
4918
4945
  target_app_id,
4919
4946
  };
@@ -5031,6 +5058,7 @@ ${conversation_history || `User (studio): ${prompt}`}
5031
5058
  rtl: _common.detectRTL(response_text),
5032
5059
  job_id,
5033
5060
  plan_mode: normalized_plan_mode,
5061
+ plan_accepted: false,
5034
5062
  studio_method: plugin_method,
5035
5063
  studio_result,
5036
5064
  target_app_id,
@@ -5058,6 +5086,408 @@ ${conversation_history || `User (studio): ${prompt}`}
5058
5086
  }
5059
5087
  };
5060
5088
 
5089
+ const get_dashboard_chat_context = function (req, conversation_doc) {
5090
+ return (req.context || req.dashboard_context || req.metadata?.context || conversation_doc.context || conversation_doc.dashboard_context || conversation_doc.metadata?.context || '').toString().trim().toLowerCase();
5091
+ };
5092
+
5093
+ const get_dashboard_cpi_keywords = function (context, prompt = '') {
5094
+ const haystack = `${context || ''} ${prompt || ''}`.toLowerCase();
5095
+ const keyword_sets = {
5096
+ data: ['data', 'database', 'db', 'dbs', 'table', 'tables', 'couch', 'read', 'create', 'update', 'delete', 'restore', 'count'],
5097
+ account: ['account', 'profile', 'profiles', 'user', 'users', 'contact', 'contacts', 'member', 'members', 'share', 'team'],
5098
+ apps: ['app', 'apps', 'project', 'projects', 'config', 'backup', 'release', 'notification', 'studio', 'mini'],
5099
+ deploy: ['deploy', 'deployment', 'instance', 'server', 'region', 'domain', 'dns', 'traffic', 'firewall', 'usage', 'build'],
5100
+ drive: ['drive', 'file', 'files', 'folder', 'upload', 'extract', 'rename', 'workspace'],
5101
+ plugins: ['plugin', 'plugins', 'install', 'uninstall', 'category', 'categories'],
5102
+ email: ['email', 'mailbox', 'mailboxes', 'mail'],
5103
+ logs: ['log', 'logs', 'security', 'session', 'traffic'],
5104
+ ai: ['ai', 'chat', 'agent', 'workspace', 'conversation'],
5105
+ };
5106
+
5107
+ for (const [key, keywords] of Object.entries(keyword_sets)) {
5108
+ if (haystack.includes(key) || keywords.some((keyword) => haystack.includes(keyword))) {
5109
+ return keywords;
5110
+ }
5111
+ }
5112
+
5113
+ return [];
5114
+ };
5115
+
5116
+ const get_dashboard_cpi_zod_item = function (key, val = {}) {
5117
+ let schema;
5118
+
5119
+ if (val.enum?.length) {
5120
+ schema = z.enum(val.enum);
5121
+ } else {
5122
+ switch (val.type) {
5123
+ case 'string':
5124
+ schema = z.string();
5125
+ break;
5126
+ case 'number':
5127
+ case 'integer':
5128
+ schema = z.number();
5129
+ if (val.type === 'integer') schema = schema.int();
5130
+ break;
5131
+ case 'boolean':
5132
+ schema = z.boolean();
5133
+ break;
5134
+ case 'array':
5135
+ schema = z.array(get_dashboard_cpi_zod_item('', val.items || { type: 'any' }));
5136
+ break;
5137
+ case 'object': {
5138
+ const props = {};
5139
+ const fields = val.items?.properties || val.properties || {};
5140
+ const required = val.items?.required || val.required || [];
5141
+ for (const [item_key, item_val] of Object.entries(fields)) {
5142
+ props[item_key] = get_dashboard_cpi_zod_item(item_key, item_val);
5143
+ if (!required.includes(item_key)) {
5144
+ props[item_key] = props[item_key].optional();
5145
+ }
5146
+ }
5147
+ schema = z.object(props);
5148
+ break;
5149
+ }
5150
+ default:
5151
+ schema = z.any();
5152
+ break;
5153
+ }
5154
+ }
5155
+
5156
+ if (!val.mandatory) {
5157
+ schema = schema.optional();
5158
+ }
5159
+
5160
+ const valid_values = val.options?.length ? ` Valid values: ${val.options.join(', ')}.` : '';
5161
+ const desc = val.description ? `${val.name || key.replace(/_/g, ' ')} - ${val.description}${valid_values}` : valid_values;
5162
+ return desc ? schema.describe(desc) : schema;
5163
+ };
5164
+
5165
+ const get_dashboard_cpi_tools = function ({ uid, profile_id, gtp_token, app_id, context, prompt }) {
5166
+ const keywords = get_dashboard_cpi_keywords(context, prompt);
5167
+ const tools = [];
5168
+ const selected_methods = [];
5169
+ const method_entries = [];
5170
+
5171
+ for (const [method_name, method_prop] of Object.entries(_conf.cpi_methods || {})) {
5172
+ if (!method_prop) continue;
5173
+ if (method_prop.private) continue;
5174
+ if (!method_prop.description && !method_prop.tooltip) continue;
5175
+
5176
+ const method_text = `${method_name} ${method_prop.name || ''} ${method_prop.description || ''} ${method_prop.tooltip || ''} ${method_prop.microservice || ''}`.toLowerCase();
5177
+ if (keywords.length && !keywords.some((keyword) => method_text.includes(keyword))) continue;
5178
+
5179
+ method_entries.push({ method_name, method_prop, score: method_prop.mcp ? 2 : 1 });
5180
+ }
5181
+
5182
+ method_entries.sort((a, b) => b.score - a.score || a.method_name.localeCompare(b.method_name));
5183
+
5184
+ for (const { method_name, method_prop } of method_entries.slice(0, 96)) {
5185
+ const inputSchema = {};
5186
+ for (const [field_key, field_val] of Object.entries(method_prop.fields || {})) {
5187
+ inputSchema[field_key] = get_dashboard_cpi_zod_item(field_key, field_val);
5188
+ }
5189
+
5190
+ selected_methods.push(method_name);
5191
+ tools.push(
5192
+ tool({
5193
+ name: method_name.substring(0, 60),
5194
+ description: method_prop.description || method_prop.tooltip || method_name,
5195
+ parameters: z.object(inputSchema),
5196
+ async execute(params) {
5197
+ try {
5198
+ const response = await fetch(`http://localhost:3007/cpi/${method_name}`, {
5199
+ method: 'POST',
5200
+ headers: {
5201
+ Accept: 'application/json',
5202
+ 'Content-Type': 'application/json',
5203
+ },
5204
+ body: JSON.stringify({
5205
+ ...params,
5206
+ uid,
5207
+ profile_id,
5208
+ app_id,
5209
+ reference_id: app_id,
5210
+ gtp_token,
5211
+ }),
5212
+ });
5213
+ const ret = await response.json();
5214
+ return JSON.stringify(ret || '', null, 2);
5215
+ } catch (err) {
5216
+ return `CPI method ${method_name} failed: ${err.message}`;
5217
+ }
5218
+ },
5219
+ }),
5220
+ );
5221
+ }
5222
+
5223
+ return { tools, selected_methods };
5224
+ };
5225
+
5226
+ const dashboard_chat = async function (req, job_id, headers) {
5227
+ const { uid, profile_id, gtp_token } = req;
5228
+ const account_profile_info = await get_active_account_profile_info(uid, profile_id);
5229
+ let { prompt, conversation_doc, attachments = [], stream = true, ai_model = _conf.default_ai_model, metadata = {} } = req;
5230
+
5231
+ const conversation_id = conversation_doc._id;
5232
+ const dashboard_context = get_dashboard_chat_context(req, conversation_doc);
5233
+ const target_app_id = (await _common.get_project_app_id(req.app_id || conversation_doc.reference_id || account_profile_info.app_id, true)) || account_profile_info.app_id;
5234
+ const prompt_conversation_item_id = await _common.xuda_get_uuid('chat_conversation_item');
5235
+ const response_conversation_item_id = await _common.xuda_get_uuid('chat_conversation_item');
5236
+ const request_started_at = Date.now();
5237
+
5238
+ let stream_delta_seq = 0;
5239
+ let stream_delta_text = '';
5240
+ const emitToDashboard = (type, content, params) => {
5241
+ const is_stream_delta = type === 'stream_delta';
5242
+ const is_stream_end = type === 'stream_end';
5243
+ const seq = is_stream_delta ? ++stream_delta_seq : undefined;
5244
+ if (is_stream_delta) {
5245
+ stream_delta_text += content || '';
5246
+ }
5247
+ ws_dashboard_msa.emit_message_to_dashboard({
5248
+ service: type,
5249
+ to: uid,
5250
+ data: {
5251
+ conversation_id,
5252
+ job_id,
5253
+ delta: content,
5254
+ ...(type === 'stream_start' ? { request_started_at } : {}),
5255
+ ...(seq ? { seq } : {}),
5256
+ ...(is_stream_end
5257
+ ? {
5258
+ stream_id: response_conversation_item_id,
5259
+ final_seq: stream_delta_seq,
5260
+ text: stream_delta_text,
5261
+ }
5262
+ : {}),
5263
+ prompt_conversation_item_id,
5264
+ response_conversation_item_id,
5265
+ params,
5266
+ },
5267
+ });
5268
+ };
5269
+
5270
+ const streamText = function (text, chunk_size = 280) {
5271
+ if (!stream || !text) return;
5272
+ for (let i = 0; i < text.length; i += chunk_size) {
5273
+ emitToDashboard('stream_delta', text.slice(i, i + chunk_size));
5274
+ }
5275
+ };
5276
+
5277
+ const saveUserItem = async function () {
5278
+ const conversation_item_reference_id = await add_conversation_item(uid, profile_id, conversation_id, prompt, 'dashboard', conversation_doc.reference_id, { context: dashboard_context, target_app_id });
5279
+ const out_conversation_item_obj = {
5280
+ _id: prompt_conversation_item_id,
5281
+ stat: 3,
5282
+ docType: 'chat_conversation_item',
5283
+ uid,
5284
+ conversation_type: 'dashboard',
5285
+ type: 'dashboard',
5286
+ date_created_ts: Date.now(),
5287
+ ts: Date.now(),
5288
+ conversation_id,
5289
+ text: prompt,
5290
+ reference_id: conversation_doc.reference_id,
5291
+ conversation_item_reference_id,
5292
+ direction: 'out',
5293
+ role: 'user',
5294
+ read: { [uid]: Date.now() },
5295
+ rtl: _common.detectRTL(prompt),
5296
+ job_id,
5297
+ context: dashboard_context,
5298
+ target_app_id,
5299
+ metadata,
5300
+ };
5301
+
5302
+ const save_ret = await db_module.save_app_couch_doc(account_profile_info.app_id, out_conversation_item_obj);
5303
+ if (save_ret.code < 0) throw new Error(save_ret.data);
5304
+ return save_ret;
5305
+ };
5306
+
5307
+ const saveAssistantItem = async function (text, extra = {}) {
5308
+ const in_conversation_item_obj = {
5309
+ _id: response_conversation_item_id,
5310
+ stat: 3,
5311
+ docType: 'chat_conversation_item',
5312
+ uid,
5313
+ conversation_type: 'dashboard',
5314
+ type: 'dashboard',
5315
+ date_created_ts: Date.now(),
5316
+ ts: Date.now(),
5317
+ conversation_id,
5318
+ text,
5319
+ reference_id: conversation_doc.reference_id,
5320
+ direction: 'in',
5321
+ role: 'assistant',
5322
+ read: { [uid]: Date.now() },
5323
+ rtl: _common.detectRTL(text),
5324
+ job_id,
5325
+ context: dashboard_context,
5326
+ target_app_id,
5327
+ prompt_conversation_item_id,
5328
+ ...extra,
5329
+ };
5330
+
5331
+ return await db_module.save_app_couch_doc_native(account_profile_info.app_id, in_conversation_item_obj);
5332
+ };
5333
+
5334
+ const formatDashboardHistoryItem = function (item) {
5335
+ const role = item.role === 'assistant' ? 'Assistant' : 'User';
5336
+ const type = item.conversation_type || item.type || 'message';
5337
+ const file_name = item?.file?.filename ? ` file=${item.file.filename}` : '';
5338
+ let text = (item.text || item.prompt || '').trim();
5339
+ if (text.length > 2500) {
5340
+ text = `${text.slice(0, 2500)}...`;
5341
+ }
5342
+
5343
+ return `${role} (${type}${file_name}): ${text}`;
5344
+ };
5345
+
5346
+ const getDashboardContextPrompt = async function () {
5347
+ const ret = await db_module.find_app_couch_query(account_profile_info.app_id, {
5348
+ selector: {
5349
+ docType: 'chat_conversation_item',
5350
+ conversation_id,
5351
+ stat: 3,
5352
+ },
5353
+ limit: 12,
5354
+ sort: [{ date_created_ts: 'desc' }],
5355
+ });
5356
+
5357
+ const items = (ret.docs || []).reverse().filter((item) => item.text || item.prompt || item?.file?.filename);
5358
+ const conversation_history = items.map(formatDashboardHistoryItem).join('\n\n');
5359
+
5360
+ return `You are handling the latest Xuda dashboard chat request.
5361
+
5362
+ Dashboard context/tab: ${dashboard_context || 'unknown'}
5363
+ Target app_id/reference_id: ${target_app_id}
5364
+
5365
+ Use the conversation history, attachment transcripts, and latest user message to infer the dashboard area when context is unknown.
5366
+
5367
+ Conversation history:
5368
+ ${conversation_history || `User (dashboard): ${prompt}`}
5369
+ `;
5370
+ };
5371
+
5372
+ try {
5373
+ emitToDashboard('stream_start');
5374
+ emitToDashboard('stream_phase', dashboard_context === 'console' ? 'Starting console request' : 'Starting dashboard request');
5375
+
5376
+ await saveUserItem();
5377
+
5378
+ if (attachments?.length) {
5379
+ emitToDashboard('stream_phase', 'Analyzing attachments', { update: true });
5380
+ await attachment_handler(uid, account_profile_info.app_id, conversation_id, attachments, account_profile_info);
5381
+ }
5382
+
5383
+ conversation_doc = await db_module.get_app_couch_doc_native(account_profile_info.app_id, conversation_id);
5384
+ conversation_doc.ts = Date.now();
5385
+ conversation_doc.stat = 2;
5386
+ conversation_doc.request_started_at = request_started_at;
5387
+ conversation_doc.job_id = job_id;
5388
+ conversation_doc.context = dashboard_context;
5389
+ conversation_doc.target_app_id = target_app_id;
5390
+ await db_module.save_app_couch_doc_native(account_profile_info.app_id, conversation_doc);
5391
+
5392
+ if (dashboard_context === 'console') {
5393
+ const codex_ret = await execute_codex_request({ ...req, conversation_id, conversation_doc, prompt, attachments, stream: false }, job_id, headers);
5394
+ const codex_events = codex_ret?.data?.events || [];
5395
+ const last_message = [...codex_events].reverse().find((event) => event?.item?.type === 'agent_message' && event.item.text)?.item?.text;
5396
+ const response_text = codex_ret.code < 0 ? `Console request failed: ${get_error_message(codex_ret.data, 'Codex request failed')}` : last_message || 'Console request completed.';
5397
+
5398
+ emitToDashboard('response_start');
5399
+ streamText(response_text);
5400
+ emitToDashboard('stream_end');
5401
+
5402
+ conversation_doc = await db_module.get_app_couch_doc_native(account_profile_info.app_id, conversation_id);
5403
+ conversation_doc.ts = Date.now();
5404
+ conversation_doc.stat = 3;
5405
+ conversation_doc.process_stat = codex_ret.code < 0 ? 'partial' : 'full';
5406
+ await db_module.save_app_couch_doc_native(account_profile_info.app_id, conversation_doc);
5407
+
5408
+ return await saveAssistantItem(response_text, { codex_result: codex_ret });
5409
+ }
5410
+
5411
+ const dashboard_prompt = await getDashboardContextPrompt();
5412
+ const app_obj = await get_app_obj(target_app_id);
5413
+ const userName = await account_ms.get_user_name(uid);
5414
+ const account_info = (await get_account_name({ uid })).data;
5415
+ const context = { ...{ app_id: target_app_id, uid, profile_id, userName, account_info, app_obj, account_id: uid, dashboard_context }, ...extractClientInfo(headers) };
5416
+ const cpi_tools_ret = get_dashboard_cpi_tools({ uid, profile_id, gtp_token, app_id: target_app_id, context: dashboard_context, prompt: dashboard_prompt });
5417
+
5418
+ if (!cpi_tools_ret.tools.length) {
5419
+ throw new Error(`no CPI methods available for dashboard context "${dashboard_context || 'unknown'}"`);
5420
+ }
5421
+
5422
+ const agent = new Agent({
5423
+ name: 'Dashboard CPI Agent',
5424
+ instructions: `
5425
+ You are Xuda dashboard chat.
5426
+ The user is interacting with the dashboard tab/context "${dashboard_context || 'unknown'}" for app_id "${target_app_id}".
5427
+ Use the available CPI tools to inspect, update, or explain dashboard data. Prefer tool calls over guessing.
5428
+ If the screenshot or prompt implies the tab/context, infer the relevant dashboard area and choose the closest CPI method.
5429
+ For data/database requests, use the database/table CPI methods and keep app_id/reference_id anchored to "${target_app_id}".
5430
+ Summarize the action and important CPI result fields clearly. Do not invent successful changes; report tool errors as errors.
5431
+ Available CPI methods: ${cpi_tools_ret.selected_methods.join(', ')}
5432
+ `.trim(),
5433
+ model: ai_model,
5434
+ tools: cpi_tools_ret.tools,
5435
+ });
5436
+
5437
+ agent.on('agent_tool_start', (context, tool) => {
5438
+ emitToDashboard('stream_phase', `Starting ${tool?.name?.replaceAll('_', ' ')} tool`, { update: true });
5439
+ });
5440
+
5441
+ emitToDashboard('stream_phase', 'Submitting dashboard request', { update: true });
5442
+ const runner = new Runner();
5443
+ const output = await runner.run(agent, dashboard_prompt, { context, stream });
5444
+ let response_text = '';
5445
+
5446
+ if (stream) {
5447
+ const output_stream = output.toTextStream({ compatibleWithNodeStreams: true });
5448
+ let response_started = false;
5449
+ for await (const chunk of output_stream) {
5450
+ if (!response_started) {
5451
+ response_started = true;
5452
+ emitToDashboard('stream_phase', 'Streaming results', { update: true });
5453
+ emitToDashboard('response_start');
5454
+ }
5455
+ const text = chunk.toString();
5456
+ response_text += text;
5457
+ emitToDashboard('stream_delta', text);
5458
+ }
5459
+ }
5460
+
5461
+ response_text = response_text || output?.state?._currentStep?.output || output?.finalOutput || output?.output || 'Dashboard request completed.';
5462
+ emitToDashboard('stream_end');
5463
+
5464
+ conversation_doc = await db_module.get_app_couch_doc_native(account_profile_info.app_id, conversation_id);
5465
+ conversation_doc.ts = Date.now();
5466
+ conversation_doc.stat = 3;
5467
+ conversation_doc.process_stat = 'full';
5468
+ await db_module.save_app_couch_doc_native(account_profile_info.app_id, conversation_doc);
5469
+
5470
+ return await saveAssistantItem(response_text, {
5471
+ cpi_methods: cpi_tools_ret.selected_methods,
5472
+ ai_agent_id: 'dashboard_cpi_agent',
5473
+ });
5474
+ } catch (err) {
5475
+ const error_message = get_error_message(err, 'dashboard request failed');
5476
+ emitToDashboard('stream_phase', 'Dashboard request failed', { update: true });
5477
+ emitToDashboard('response_start');
5478
+ streamText(`Dashboard request failed: ${error_message}`);
5479
+ emitToDashboard('stream_end');
5480
+
5481
+ conversation_doc = await db_module.get_app_couch_doc_native(account_profile_info.app_id, conversation_id);
5482
+ conversation_doc.ts = Date.now();
5483
+ conversation_doc.stat = 3;
5484
+ conversation_doc.process_stat = 'partial';
5485
+ await db_module.save_app_couch_doc_native(account_profile_info.app_id, conversation_doc);
5486
+
5487
+ return { code: -17, data: error_message };
5488
+ }
5489
+ };
5490
+
5061
5491
  const get_ai_agent_tools = async function ({ ai_agent_doc, agent_id, reference_type, prompt_suggestion_activated, chat_suggestion_activated, gtp_token, uid, account_profile_info, job_id, headers, context, app_id }) {
5062
5492
  let tools = [];
5063
5493
  let tool_resources = {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xuda.io/ai_module",
3
- "version": "1.1.5595",
3
+ "version": "1.1.5597",
4
4
  "description": "Xuda AI Module",
5
5
  "main": "index.mjs",
6
6
  "type": "module",