@ynhcj/xiaoyi-channel 1.1.25 → 1.1.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/bot.js +2 -2
- package/dist/src/provider.js +50 -70
- package/dist/src/tools/image-reading-tool.js +7 -6
- package/package.json +1 -1
package/dist/src/bot.js
CHANGED
|
@@ -270,7 +270,7 @@ export async function handleXYMessage(params) {
|
|
|
270
270
|
SenderId: parsed.sessionId,
|
|
271
271
|
Provider: "xiaoyi-channel",
|
|
272
272
|
Surface: "xiaoyi-channel",
|
|
273
|
-
MessageSid:
|
|
273
|
+
MessageSid: `xiaoyi_${parsed.taskId}_${deviceType}`,
|
|
274
274
|
Timestamp: Date.now(),
|
|
275
275
|
WasMentioned: false,
|
|
276
276
|
CommandAuthorized: true,
|
|
@@ -532,7 +532,7 @@ async function dispatchSteerWhenReady(params) {
|
|
|
532
532
|
SenderId: sessionId,
|
|
533
533
|
Provider: "xiaoyi-channel",
|
|
534
534
|
Surface: "xiaoyi-channel",
|
|
535
|
-
MessageSid:
|
|
535
|
+
MessageSid: `xiaoyi_${params.parsed.taskId}_${params.deviceType}`,
|
|
536
536
|
Timestamp: Date.now(),
|
|
537
537
|
WasMentioned: false,
|
|
538
538
|
CommandAuthorized: true,
|
package/dist/src/provider.js
CHANGED
|
@@ -46,6 +46,11 @@ function getFirstUserText(messages) {
|
|
|
46
46
|
}
|
|
47
47
|
/** Regex to match `[cron:<uuid> <title>]` anywhere in text. */
|
|
48
48
|
const CRON_TAG_RE = /\[cron:[^\s\]]+\s+([^\]]+)\]/;
|
|
49
|
+
/** Extract the cron job UUID from the first user message, e.g. `[cron:abc123 ...]` → `abc123`. */
|
|
50
|
+
function extractCronUuid(messages) {
|
|
51
|
+
const match = getFirstUserText(messages).match(/\[cron:([^\s\]]+)/i);
|
|
52
|
+
return match ? match[1] : undefined;
|
|
53
|
+
}
|
|
49
54
|
/** Check if the request is triggered by a cron job by inspecting the first user message. */
|
|
50
55
|
function isCronTriggered(messages) {
|
|
51
56
|
return /\[cron:/i.test(getFirstUserText(messages));
|
|
@@ -231,8 +236,6 @@ const HEADER_SESSION_ID = "x-session-id";
|
|
|
231
236
|
const HEADER_INTERACTION_ID = "x-interaction-id";
|
|
232
237
|
/** Internal key for passing fallback uid prefix from prepareExtraParams to wrapStreamFn. */
|
|
233
238
|
const FALLBACK_PREFIX_KEY = "_xiaoyi_fallback_prefix";
|
|
234
|
-
/** Internal key for passing deviceType from prepareExtraParams to wrapStreamFn. */
|
|
235
|
-
const DEVICE_TYPE_KEY = "_xiaoyi_device_type";
|
|
236
239
|
const SELF_EVOLUTION_PROMPT_BEGIN = "<self_evolution_prompt>";
|
|
237
240
|
const SELF_EVOLUTION_PROMPT_END = "</self_evolution_prompt>";
|
|
238
241
|
const SELF_EVOLUTION_ENABLED_PROMPT_SECTION = `
|
|
@@ -392,7 +395,9 @@ function trimUserMetadata(text) {
|
|
|
392
395
|
}
|
|
393
396
|
/**
|
|
394
397
|
* Extract A2A taskId and deviceType from Conversation info JSON.
|
|
395
|
-
* bot.ts stores them as MessageSid = "
|
|
398
|
+
* bot.ts stores them as MessageSid = "xiaoyi_taskId_deviceType".
|
|
399
|
+
* The "xiaoyi_" prefix ensures extraction only happens for messages
|
|
400
|
+
* routed through xiaoyi-channel, not other channels sharing the provider.
|
|
396
401
|
*/
|
|
397
402
|
function extractA2AFromConversationInfo(text) {
|
|
398
403
|
const match = text.match(/Conversation info \(untrusted metadata\):\n```json\n([\s\S]*?)\n```/);
|
|
@@ -402,9 +407,9 @@ function extractA2AFromConversationInfo(text) {
|
|
|
402
407
|
if (!msgIdMatch)
|
|
403
408
|
return null;
|
|
404
409
|
const parts = msgIdMatch[1].split("_");
|
|
405
|
-
if (parts.length <
|
|
410
|
+
if (parts.length < 3 || parts[0] !== "xiaoyi")
|
|
406
411
|
return null;
|
|
407
|
-
return { taskId: parts[
|
|
412
|
+
return { taskId: parts[1], deviceType: parts[2] };
|
|
408
413
|
}
|
|
409
414
|
export const xiaoyiProvider = {
|
|
410
415
|
id: "xiaoyiprovider",
|
|
@@ -413,31 +418,12 @@ export const xiaoyiProvider = {
|
|
|
413
418
|
auth: [],
|
|
414
419
|
isCacheTtlEligible: () => true,
|
|
415
420
|
/**
|
|
416
|
-
*
|
|
417
|
-
*
|
|
418
|
-
*
|
|
419
|
-
*
|
|
420
|
-
* 1. Session context (from AsyncLocalStorage, set by bot.ts)
|
|
421
|
-
* 2. uid-based fallback: sha256(uid).hex[:32]_timestamp
|
|
422
|
-
* 3. No uid available → return undefined (no headers injected)
|
|
421
|
+
* Store uid-based fallback prefix for lazy timestamp generation in wrapStreamFn.
|
|
422
|
+
* Session-level headers (traceId / sessionId / interactionId) are resolved
|
|
423
|
+
* directly in wrapStreamFn via cron detection, Conversation info extraction,
|
|
424
|
+
* or uid fallback.
|
|
423
425
|
*/
|
|
424
426
|
prepareExtraParams: (ctx) => {
|
|
425
|
-
const sessionCtx = getCurrentSessionContext();
|
|
426
|
-
if (sessionCtx) {
|
|
427
|
-
const taskId = sessionCtx.taskId;
|
|
428
|
-
const sessionId = taskId.split("&")[0];
|
|
429
|
-
const interactionId = taskId.split("&")[1] || "";
|
|
430
|
-
return {
|
|
431
|
-
...ctx.extraParams,
|
|
432
|
-
[HEADER_TRACE_ID]: taskId,
|
|
433
|
-
[HEADER_SESSION_ID]: sessionId,
|
|
434
|
-
[HEADER_INTERACTION_ID]: interactionId,
|
|
435
|
-
[DEVICE_TYPE_KEY]: sessionCtx.deviceType ?? "",
|
|
436
|
-
};
|
|
437
|
-
}
|
|
438
|
-
// Fallback: store uid prefix for lazy timestamp generation in wrapStreamFn.
|
|
439
|
-
// This ensures each model call gets a fresh timestamp instead of reusing
|
|
440
|
-
// the same one across tool-use loops and retries.
|
|
441
427
|
const uid = getUidFromConfig(ctx.config);
|
|
442
428
|
if (!uid)
|
|
443
429
|
return undefined;
|
|
@@ -488,51 +474,46 @@ export const xiaoyiProvider = {
|
|
|
488
474
|
}
|
|
489
475
|
}
|
|
490
476
|
// ── Build dynamic headers ────────────────────────────
|
|
491
|
-
|
|
492
|
-
|
|
477
|
+
// Priority:
|
|
478
|
+
// 1. Cron-triggered: uid → cronUuid, with cron-specific headers
|
|
479
|
+
// 2. Xiaoyi A2A: taskId extracted from Conversation info (xiaoyi_ prefix)
|
|
480
|
+
// 3. UID-based fallback: sha256(uid).hex[:32]_timestamp
|
|
481
|
+
const isCron = isCronTriggered(context.messages);
|
|
482
|
+
if (isCron) {
|
|
483
|
+
const fallbackPrefix = ctx.extraParams?.[FALLBACK_PREFIX_KEY];
|
|
493
484
|
if (typeof fallbackPrefix === "string") {
|
|
494
|
-
// Fallback mode: generate fresh timestamp per request
|
|
495
|
-
const isCron = isCronTriggered(context.messages);
|
|
496
485
|
const fallbackValue = `${fallbackPrefix}_${Date.now()}`;
|
|
497
|
-
dynamicHeaders[HEADER_TRACE_ID] =
|
|
486
|
+
dynamicHeaders[HEADER_TRACE_ID] = `cron_${fallbackValue}`;
|
|
498
487
|
dynamicHeaders[HEADER_SESSION_ID] = fallbackValue;
|
|
499
488
|
dynamicHeaders[HEADER_INTERACTION_ID] = fallbackValue;
|
|
500
|
-
if (isCron) {
|
|
501
|
-
const cronTitle = extractCronTitle(context.messages);
|
|
502
|
-
if (cronTitle)
|
|
503
|
-
dynamicHeaders["x-cron-title"] = encodeURIComponent(cronTitle);
|
|
504
|
-
if (context.messages?.length === 1)
|
|
505
|
-
dynamicHeaders["x-cron-flag"] = "begin";
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
else if (extractedTaskId) {
|
|
509
|
-
// Session mode: taskId extracted from Conversation info
|
|
510
|
-
const traceId = extractedTaskId;
|
|
511
|
-
const sessionId = traceId.split("&")[0];
|
|
512
|
-
const interactionId = traceId.split("&")[1] ?? "";
|
|
513
|
-
const isCron = isCronTriggered(context.messages);
|
|
514
|
-
dynamicHeaders[HEADER_TRACE_ID] = isCron ? `cron_${traceId}_${Date.now()}` : traceId;
|
|
515
|
-
if (isCron) {
|
|
516
|
-
const cronTitle = extractCronTitle(context.messages);
|
|
517
|
-
if (cronTitle)
|
|
518
|
-
dynamicHeaders["x-cron-title"] = encodeURIComponent(cronTitle);
|
|
519
|
-
if (context.messages?.length === 1)
|
|
520
|
-
dynamicHeaders["x-cron-flag"] = "begin";
|
|
521
|
-
}
|
|
522
|
-
dynamicHeaders[HEADER_SESSION_ID] = sessionId;
|
|
523
|
-
dynamicHeaders[HEADER_INTERACTION_ID] = interactionId;
|
|
524
489
|
}
|
|
525
490
|
else {
|
|
526
|
-
|
|
527
|
-
const
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
491
|
+
const cronUuid = extractCronUuid(context.messages) ?? "cron";
|
|
492
|
+
const cronSessionId = `cron_${cronUuid}_${Date.now()}`;
|
|
493
|
+
dynamicHeaders[HEADER_TRACE_ID] = cronSessionId;
|
|
494
|
+
dynamicHeaders[HEADER_SESSION_ID] = cronUuid;
|
|
495
|
+
dynamicHeaders[HEADER_INTERACTION_ID] = cronSessionId;
|
|
496
|
+
}
|
|
497
|
+
const cronTitle = extractCronTitle(context.messages);
|
|
498
|
+
if (cronTitle)
|
|
499
|
+
dynamicHeaders["x-cron-title"] = encodeURIComponent(cronTitle);
|
|
500
|
+
if (context.messages?.length === 1)
|
|
501
|
+
dynamicHeaders["x-cron-flag"] = "begin";
|
|
502
|
+
}
|
|
503
|
+
else if (extractedTaskId) {
|
|
504
|
+
const sessionId = extractedTaskId.split("&")[0];
|
|
505
|
+
const interactionId = extractedTaskId.split("&")[1] ?? "";
|
|
506
|
+
dynamicHeaders[HEADER_TRACE_ID] = extractedTaskId;
|
|
507
|
+
dynamicHeaders[HEADER_SESSION_ID] = sessionId;
|
|
508
|
+
dynamicHeaders[HEADER_INTERACTION_ID] = interactionId;
|
|
509
|
+
}
|
|
510
|
+
else {
|
|
511
|
+
const fallbackPrefix = ctx.extraParams?.[FALLBACK_PREFIX_KEY];
|
|
512
|
+
if (typeof fallbackPrefix === "string") {
|
|
513
|
+
const fallbackValue = `${fallbackPrefix}_${Date.now()}`;
|
|
514
|
+
dynamicHeaders[HEADER_TRACE_ID] = fallbackValue;
|
|
515
|
+
dynamicHeaders[HEADER_SESSION_ID] = fallbackValue;
|
|
516
|
+
dynamicHeaders[HEADER_INTERACTION_ID] = fallbackValue;
|
|
536
517
|
}
|
|
537
518
|
}
|
|
538
519
|
// 记录输入
|
|
@@ -546,9 +527,8 @@ export const xiaoyiProvider = {
|
|
|
546
527
|
logger.log(`[xiaoyiprovider] system prompt length: ${context.systemPrompt.length}`);
|
|
547
528
|
}
|
|
548
529
|
// deviceType: prefer value extracted from Conversation info,
|
|
549
|
-
// then
|
|
550
|
-
const
|
|
551
|
-
const deviceType = (extractedDeviceType || extraParamsDeviceType)
|
|
530
|
+
// then ALS fallback.
|
|
531
|
+
const deviceType = extractedDeviceType
|
|
552
532
|
?? getCurrentSessionContext()?.deviceType;
|
|
553
533
|
// 在发送给模型前,优化 systemPrompt 结构
|
|
554
534
|
if (context.systemPrompt) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// Image Reading tool implementation
|
|
2
|
+
import { createHash } from "crypto";
|
|
2
3
|
import { XYFileUploadService } from "../file-upload.js";
|
|
3
4
|
import fetch from "node-fetch";
|
|
4
5
|
import fs from "fs/promises";
|
|
@@ -50,9 +51,9 @@ async function processImageInput(imageInput, uploadService) {
|
|
|
50
51
|
* Call image understanding API with streaming response
|
|
51
52
|
* Supports both single image and multiple images (imageUrls array)
|
|
52
53
|
*/
|
|
53
|
-
async function callImageUnderstandingAPI(imageUrls, text, apiKey, uid, fileUploadUrl) {
|
|
54
|
+
async function callImageUnderstandingAPI(imageUrls, text, apiKey, uid, fileUploadUrl, sessionId) {
|
|
54
55
|
const apiUrl = `${fileUploadUrl}/celia-claw/v1/sse-api/skill/execute`;
|
|
55
|
-
const traceId =
|
|
56
|
+
const traceId = `${createHash("sha256").update(uid).digest("hex").slice(0, 32)}_${Date.now()}`;
|
|
56
57
|
const headers = {
|
|
57
58
|
"Content-Type": "application/json",
|
|
58
59
|
"Accept": "text/event-stream",
|
|
@@ -60,14 +61,14 @@ async function callImageUnderstandingAPI(imageUrls, text, apiKey, uid, fileUploa
|
|
|
60
61
|
"x-api-key": apiKey,
|
|
61
62
|
"x-request-from": "openclaw",
|
|
62
63
|
"x-uid": uid,
|
|
63
|
-
"x-skill-id": "
|
|
64
|
+
"x-skill-id": "xiaoyi_image_comprehension",
|
|
64
65
|
"x-prd-pkg-name": "com.huawei.hag",
|
|
65
66
|
};
|
|
66
67
|
const payload = {
|
|
67
68
|
version: "1.0",
|
|
68
69
|
session: {
|
|
69
70
|
isNew: false,
|
|
70
|
-
sessionId
|
|
71
|
+
sessionId,
|
|
71
72
|
interactionId: 0,
|
|
72
73
|
},
|
|
73
74
|
endpoint: {
|
|
@@ -165,7 +166,7 @@ async function callImageUnderstandingAPI(imageUrls, text, apiKey, uid, fileUploa
|
|
|
165
166
|
* Supports both local file paths and remote URLs, up to 10 images at once.
|
|
166
167
|
*/
|
|
167
168
|
export function createImageReadingTool(ctx) {
|
|
168
|
-
const { config } = ctx;
|
|
169
|
+
const { config, sessionId } = ctx;
|
|
169
170
|
return {
|
|
170
171
|
name: "image_reading",
|
|
171
172
|
label: "Image Reading",
|
|
@@ -209,7 +210,7 @@ export function createImageReadingTool(ctx) {
|
|
|
209
210
|
allImageUrls.push(await processImageInput(imageInput, uploadService));
|
|
210
211
|
}
|
|
211
212
|
// Call image understanding API with all image URLs
|
|
212
|
-
const caption = await callImageUnderstandingAPI(allImageUrls, prompt, config.apiKey, config.uid, config.fileUploadUrl);
|
|
213
|
+
const caption = await callImageUnderstandingAPI(allImageUrls, prompt, config.apiKey, config.uid, config.fileUploadUrl, sessionId);
|
|
213
214
|
return {
|
|
214
215
|
content: [
|
|
215
216
|
{
|