@ynhcj/xiaoyi-channel 0.0.179-next → 0.0.180-next
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/provider.js +66 -8
- package/package.json +1 -1
package/dist/src/provider.js
CHANGED
|
@@ -393,6 +393,28 @@ function trimUserMetadata(text) {
|
|
|
393
393
|
text = text.replace(/\n*Sender \(untrusted metadata\):\n```json\n[\s\S]*?\n```\n*/, "\n");
|
|
394
394
|
return text.replace(/\n{3,}/g, "\n\n");
|
|
395
395
|
}
|
|
396
|
+
/**
|
|
397
|
+
* Extract A2A taskId and deviceType from Conversation info JSON.
|
|
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.
|
|
401
|
+
*
|
|
402
|
+
* This is the most reliable source during steer scenarios: ALS still holds
|
|
403
|
+
* the first message's taskId, but the text extraction sees the latest
|
|
404
|
+
* injected user message which carries the updated taskId.
|
|
405
|
+
*/
|
|
406
|
+
function extractA2AFromConversationInfo(text) {
|
|
407
|
+
const match = text.match(/Conversation info \(untrusted metadata\):\n```json\n([\s\S]*?)\n```/);
|
|
408
|
+
if (!match)
|
|
409
|
+
return null;
|
|
410
|
+
const msgIdMatch = match[1].match(/"message_id"\s*:\s*"([^"]+)"/);
|
|
411
|
+
if (!msgIdMatch)
|
|
412
|
+
return null;
|
|
413
|
+
const parts = msgIdMatch[1].split("_");
|
|
414
|
+
if (parts.length < 3 || parts[0] !== "xiaoyi")
|
|
415
|
+
return null;
|
|
416
|
+
return { taskId: parts[1], deviceType: parts[2] };
|
|
417
|
+
}
|
|
396
418
|
export const xiaoyiProvider = {
|
|
397
419
|
id: "xiaoyiprovider",
|
|
398
420
|
label: "Xiaoyi Provider",
|
|
@@ -461,11 +483,39 @@ export const xiaoyiProvider = {
|
|
|
461
483
|
return underlying;
|
|
462
484
|
return async (model, context, options) => {
|
|
463
485
|
const dynamicHeaders = {};
|
|
486
|
+
// ── Extract A2A taskId/deviceType from Conversation info ──
|
|
487
|
+
// bot.ts stores taskId_deviceType as MessageSid, which the framework
|
|
488
|
+
// renders as message_id in the Conversation info JSON block.
|
|
489
|
+
// This is the priority source: it reflects the latest user message
|
|
490
|
+
// even during steer when ALS still holds the first message's taskId.
|
|
491
|
+
let extractedTaskId = null;
|
|
492
|
+
let extractedDeviceType = null;
|
|
493
|
+
if (context.messages) {
|
|
494
|
+
for (let i = context.messages.length - 1; i >= 0; i--) {
|
|
495
|
+
const msg = context.messages[i];
|
|
496
|
+
if (msg.role !== "user")
|
|
497
|
+
continue;
|
|
498
|
+
const text = typeof msg.content === "string"
|
|
499
|
+
? msg.content
|
|
500
|
+
: Array.isArray(msg.content)
|
|
501
|
+
? msg.content.find((b) => b.type === "text")?.text ?? ""
|
|
502
|
+
: "";
|
|
503
|
+
if (!text)
|
|
504
|
+
continue;
|
|
505
|
+
const extracted = extractA2AFromConversationInfo(text);
|
|
506
|
+
if (extracted) {
|
|
507
|
+
extractedTaskId = extracted.taskId;
|
|
508
|
+
extractedDeviceType = extracted.deviceType;
|
|
509
|
+
break;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
464
513
|
// ── Build dynamic headers ────────────────────────────
|
|
465
514
|
// Priority:
|
|
466
515
|
// 1. Cron-triggered: uid → cronUuid, with cron-specific headers
|
|
467
|
-
// 2. Xiaoyi A2A:
|
|
468
|
-
// 3.
|
|
516
|
+
// 2. Xiaoyi A2A: taskId from Conversation info text (latest msg, steer-safe)
|
|
517
|
+
// 3. ALS fallback: rawTaskId from AsyncLocalStorage
|
|
518
|
+
// 4. UID-based fallback: sha256(uid).hex[:32]_timestamp
|
|
469
519
|
const isCron = isCronTriggered(context.messages);
|
|
470
520
|
if (isCron) {
|
|
471
521
|
// fire 期 jobId 桥:把首条消息 `[cron:<jobId> ...]` 解析出的真实 jobId
|
|
@@ -497,11 +547,18 @@ export const xiaoyiProvider = {
|
|
|
497
547
|
dynamicHeaders["x-cron-flag"] = "begin";
|
|
498
548
|
logger.log(`[ALS-PROOF] provider headers source=cron`);
|
|
499
549
|
}
|
|
550
|
+
else if (extractedTaskId) {
|
|
551
|
+
const sessionId = extractedTaskId.split("&")[0];
|
|
552
|
+
const interactionId = extractedTaskId.split("&")[1] ?? "";
|
|
553
|
+
dynamicHeaders[HEADER_TRACE_ID] = extractedTaskId;
|
|
554
|
+
dynamicHeaders[HEADER_SESSION_ID] = sessionId;
|
|
555
|
+
dynamicHeaders[HEADER_INTERACTION_ID] = interactionId;
|
|
556
|
+
logger.log(`[ALS-PROOF] provider headers source=text-extract traceId=${extractedTaskId} sessionId=${sessionId} interactionId=${interactionId}`);
|
|
557
|
+
}
|
|
500
558
|
else {
|
|
501
|
-
// ALS
|
|
502
|
-
//
|
|
503
|
-
//
|
|
504
|
-
// id as sessionId with empty interactionId.
|
|
559
|
+
// ALS fallback: rawTaskId from the per-turn AsyncLocalStorage scope.
|
|
560
|
+
// Text extraction (above) is preferred because during steer the ALS
|
|
561
|
+
// scope may still hold the first message's taskId.
|
|
505
562
|
const als = getCurrentSessionContext();
|
|
506
563
|
const rawTaskId = als?.taskId;
|
|
507
564
|
if (rawTaskId) {
|
|
@@ -533,8 +590,9 @@ export const xiaoyiProvider = {
|
|
|
533
590
|
if (context.systemPrompt) {
|
|
534
591
|
logger.log(`[xiaoyiprovider] system prompt length: ${context.systemPrompt.length}`);
|
|
535
592
|
}
|
|
536
|
-
// deviceType:
|
|
537
|
-
const deviceType =
|
|
593
|
+
// deviceType: prefer text-extracted value, ALS as fallback.
|
|
594
|
+
const deviceType = extractedDeviceType
|
|
595
|
+
?? getCurrentSessionContext()?.deviceType;
|
|
538
596
|
// app_ver and sdk_api_version from session context (ALS)
|
|
539
597
|
const appVer = sessionCtx?.appVer;
|
|
540
598
|
const sdkApiVersion = sessionCtx?.sdkApiVersion;
|