@ynhcj/xiaoyi-channel 0.0.214-next → 0.0.215-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/compaction-provider.d.ts +6 -0
- package/dist/src/compaction-provider.js +23 -4
- package/dist/src/cron-query-handler.js +42 -32
- package/dist/src/provider.js +38 -2
- package/dist/src/tools/call-phone-tool.d.ts +0 -1
- package/dist/src/tools/call-phone-tool.js +5 -8
- package/package.json +1 -1
|
@@ -18,6 +18,12 @@ interface CompactionSessionSnapshot {
|
|
|
18
18
|
deviceType?: string;
|
|
19
19
|
appVer?: string;
|
|
20
20
|
sdkApiVersion?: string;
|
|
21
|
+
/** Auth headers from options.headers in the last normal request. */
|
|
22
|
+
requestHeaders?: Record<string, string>;
|
|
23
|
+
/** API key from the last normal request (for Bearer token auth). */
|
|
24
|
+
apiKey?: string;
|
|
25
|
+
/** Model-level headers from resolveDynamicModel (streamSimple adds these). */
|
|
26
|
+
modelHeaders?: Record<string, string>;
|
|
21
27
|
}
|
|
22
28
|
/** Store config captured from prepareExtraParams so summarize() can read it. */
|
|
23
29
|
export declare function setCompactionConfig(config: CompactionConfig | null): void;
|
|
@@ -123,8 +123,20 @@ export const xiaoyiCompactionProvider = {
|
|
|
123
123
|
}
|
|
124
124
|
promptText += `\n\n${instructions}`;
|
|
125
125
|
// ── Build request ─────────────────────────────────────────────
|
|
126
|
+
// Reconstruct the exact same headers that the normal request uses.
|
|
127
|
+
// streamSimple adds model.headers to the HTTP request directly, so
|
|
128
|
+
// we capture them separately from options.headers.
|
|
129
|
+
const reqHdrs = session?.requestHeaders;
|
|
130
|
+
const modelHdrs = session?.modelHeaders;
|
|
131
|
+
const reqKeys = reqHdrs ? Object.keys(reqHdrs) : [];
|
|
132
|
+
const modelKeys = modelHdrs ? Object.keys(modelHdrs) : [];
|
|
133
|
+
logger.log(`[compaction-provider] snapshot auth: hasApiKey=${!!session?.apiKey} ` +
|
|
134
|
+
`requestHeaders=[${reqKeys.join(",")}] modelHeaders=[${modelKeys.join(",")}]`);
|
|
126
135
|
const headers = {
|
|
127
136
|
"Content-Type": "application/json",
|
|
137
|
+
...modelHdrs,
|
|
138
|
+
...reqHdrs,
|
|
139
|
+
...(session?.apiKey ? { Authorization: `Bearer ${session.apiKey}` } : {}),
|
|
128
140
|
[HEADER_TRACE_ID]: traceId,
|
|
129
141
|
[HEADER_SESSION_ID]: sessionId,
|
|
130
142
|
[HEADER_INTERACTION_ID]: interactionId,
|
|
@@ -138,9 +150,6 @@ export const xiaoyiCompactionProvider = {
|
|
|
138
150
|
if (session?.sdkApiVersion) {
|
|
139
151
|
headers["x-sdk-api-version"] = session.sdkApiVersion;
|
|
140
152
|
}
|
|
141
|
-
if (cfg.apiKey) {
|
|
142
|
-
headers["Authorization"] = `Bearer ${cfg.apiKey}`;
|
|
143
|
-
}
|
|
144
153
|
const body = {
|
|
145
154
|
model: cfg.modelName,
|
|
146
155
|
messages: [
|
|
@@ -151,7 +160,17 @@ export const xiaoyiCompactionProvider = {
|
|
|
151
160
|
temperature: 0.3,
|
|
152
161
|
};
|
|
153
162
|
const url = `${cfg.baseUrl.replace(/\/+$/, "")}/chat/completions`;
|
|
154
|
-
|
|
163
|
+
// Log headers for diagnostics (redact sensitive values)
|
|
164
|
+
const safeHeaders = {};
|
|
165
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
166
|
+
if (key.toLowerCase().includes("auth") || key.toLowerCase().includes("api-key")) {
|
|
167
|
+
safeHeaders[key] = value ? `${value.slice(0, 4)}***` : "(empty)";
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
safeHeaders[key] = value;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
logger.log(`[compaction-provider] POST ${url} headers=${JSON.stringify(safeHeaders)}`);
|
|
155
174
|
// ── Call ──────────────────────────────────────────────────────
|
|
156
175
|
let response;
|
|
157
176
|
try {
|
|
@@ -110,7 +110,38 @@ export async function handleCronQueryEvent(context, cfg) {
|
|
|
110
110
|
// ── list ──────────────────────────────────────────────────────
|
|
111
111
|
case "list": {
|
|
112
112
|
const gatewayParams = buildListParams(params);
|
|
113
|
-
|
|
113
|
+
let gatewayResult = await callGatewayTool("cron.list", { timeoutMs: GATEWAY_TIMEOUT_MS }, gatewayParams);
|
|
114
|
+
// Scan for systemEvent jobs and migrate them to agentTurn
|
|
115
|
+
// Skip main-session jobs — gateway requires payload.kind="systemEvent" for those
|
|
116
|
+
const jobs = gatewayResult?.jobs ?? [];
|
|
117
|
+
const systemEventJobs = jobs.filter((j) => j?.payload?.kind === "systemEvent");
|
|
118
|
+
if (systemEventJobs.length > 0) {
|
|
119
|
+
log.log(`[CRON-QUERY] list: found ${systemEventJobs.length} systemEvent jobs, migrating to agentTurn`);
|
|
120
|
+
for (const job of systemEventJobs) {
|
|
121
|
+
const updatePatch = {
|
|
122
|
+
sessionTarget: "isolated",
|
|
123
|
+
description: job.payload?.text ?? job.payload?.message ?? "",
|
|
124
|
+
payload: {
|
|
125
|
+
kind: "agentTurn",
|
|
126
|
+
message: job.payload?.text ?? job.payload?.message ?? "",
|
|
127
|
+
},
|
|
128
|
+
delivery: {
|
|
129
|
+
channel: "xiaoyi-channel",
|
|
130
|
+
mode: "announce",
|
|
131
|
+
to: "default"
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
try {
|
|
135
|
+
await callGatewayTool("cron.update", { timeoutMs: GATEWAY_TIMEOUT_MS }, { id: job.id, patch: updatePatch });
|
|
136
|
+
log.log(`[CRON-QUERY] list: migrated job ${job.id} (${job.name ?? ""}) from systemEvent → agentTurn`);
|
|
137
|
+
}
|
|
138
|
+
catch (err) {
|
|
139
|
+
log.warn(`[CRON-QUERY] list: failed to migrate job ${job.id}:`, err);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// Re-fetch list after migration
|
|
143
|
+
gatewayResult = await callGatewayTool("cron.list", { timeoutMs: GATEWAY_TIMEOUT_MS }, gatewayParams);
|
|
144
|
+
}
|
|
114
145
|
result = transformListResponse(gatewayResult, params);
|
|
115
146
|
break;
|
|
116
147
|
}
|
|
@@ -255,8 +286,7 @@ function buildUpdateParams(jobId, params) {
|
|
|
255
286
|
const patch = params?.patch ?? params ?? {};
|
|
256
287
|
return {
|
|
257
288
|
id: jobId,
|
|
258
|
-
patch
|
|
259
|
-
description: patch?.payload?.message,
|
|
289
|
+
patch
|
|
260
290
|
};
|
|
261
291
|
}
|
|
262
292
|
// ============================================================================
|
|
@@ -300,6 +330,7 @@ function transformAddResponse(gatewayResult) {
|
|
|
300
330
|
id: gatewayResult?.id,
|
|
301
331
|
name: gatewayResult?.name,
|
|
302
332
|
enabled: gatewayResult?.enabled,
|
|
333
|
+
description: gatewayResult?.payload?.message,
|
|
303
334
|
createdAtMs: gatewayResult?.createdAtMs,
|
|
304
335
|
updatedAtMs: gatewayResult?.updatedAtMs,
|
|
305
336
|
schedule: gatewayResult?.schedule,
|
|
@@ -376,32 +407,23 @@ async function persistCronPushMap(sessionId, result) {
|
|
|
376
407
|
async function queryTimeListFromGateway(params) {
|
|
377
408
|
// Fetch from local runs folder and gateway RPC in parallel
|
|
378
409
|
const [localEntries, rpcResult] = await Promise.all([
|
|
379
|
-
readAllLocalRunLogs().catch((
|
|
380
|
-
logger.error(`[CRON-QUERY] readAllLocalRunLogs failed:`, err);
|
|
381
|
-
return [];
|
|
382
|
-
}),
|
|
410
|
+
readAllLocalRunLogs().catch(() => []),
|
|
383
411
|
callGatewayTool("cron.runs", { timeoutMs: GATEWAY_TIMEOUT_MS }, {
|
|
384
412
|
scope: "all",
|
|
385
413
|
limit: 200,
|
|
386
414
|
sortDir: "desc",
|
|
387
415
|
...(params ?? {}),
|
|
388
|
-
}).catch((
|
|
389
|
-
logger.error(`[CRON-QUERY] gateway cron.runs RPC failed:`, err);
|
|
390
|
-
return { entries: [] };
|
|
391
|
-
}),
|
|
416
|
+
}).catch(() => ({ entries: [] })),
|
|
392
417
|
]);
|
|
393
418
|
const gatewayEntries = rpcResult?.entries ?? [];
|
|
394
|
-
logger.log(`[CRON-QUERY] queryTimeList: local=${localEntries.length}, gateway=${gatewayEntries.length}`);
|
|
395
419
|
// Merge and deduplicate
|
|
396
420
|
const merged = mergeAndDedupeRunEntries(localEntries, gatewayEntries);
|
|
397
|
-
logger.log(`[CRON-QUERY] queryTimeList: merged=${merged.length} (after dedup)`);
|
|
398
421
|
if (merged.length === 0) {
|
|
399
422
|
return [];
|
|
400
423
|
}
|
|
401
424
|
// Filter to last 7 days
|
|
402
425
|
const sevenDaysAgo = Date.now() - 7 * 24 * 60 * 60 * 1000;
|
|
403
426
|
const recent = merged.filter((e) => e.ts && e.ts >= sevenDaysAgo);
|
|
404
|
-
logger.log(`[CRON-QUERY] queryTimeList: recent=${recent.length} (within 7 days, threshold=${new Date(sevenDaysAgo).toISOString()})`);
|
|
405
427
|
// Ensure .name is populated (gateway returns jobName; normalize to .name)
|
|
406
428
|
for (const run of recent) {
|
|
407
429
|
if (!run.name && run.jobName) {
|
|
@@ -479,7 +501,6 @@ function extractJobIdFromRunLogName(name) {
|
|
|
479
501
|
* may have renamed the file to .jsonl.migrated.
|
|
480
502
|
*/
|
|
481
503
|
async function readLocalRunLogsForJob(jobId) {
|
|
482
|
-
logger.log(`[CRON-QUERY] readLocalRunLogsForJob: jobId=${jobId}, dir=${LEGACY_CRON_RUNS_DIR}`);
|
|
483
504
|
// Try active file first, then archived variants
|
|
484
505
|
const candidates = [
|
|
485
506
|
`${jobId}.jsonl`,
|
|
@@ -490,13 +511,11 @@ async function readLocalRunLogsForJob(jobId) {
|
|
|
490
511
|
try {
|
|
491
512
|
const raw = await fsp.readFile(filePath, "utf-8");
|
|
492
513
|
if (raw.trim()) {
|
|
493
|
-
|
|
494
|
-
logger.log(`[CRON-QUERY] readLocalRunLogsForJob: found ${candidate}, bytes=${raw.length}, entries=${parsed.length}`);
|
|
495
|
-
return parsed;
|
|
514
|
+
return parseCronRunLogEntriesFromJsonl(raw, { jobId });
|
|
496
515
|
}
|
|
497
516
|
}
|
|
498
517
|
catch {
|
|
499
|
-
|
|
518
|
+
// Try next candidate
|
|
500
519
|
}
|
|
501
520
|
}
|
|
502
521
|
// Also scan for .migrated.N variants
|
|
@@ -508,9 +527,7 @@ async function readLocalRunLogsForJob(jobId) {
|
|
|
508
527
|
try {
|
|
509
528
|
const raw = await fsp.readFile(path.join(LEGACY_CRON_RUNS_DIR, entry.name), "utf-8");
|
|
510
529
|
if (raw.trim()) {
|
|
511
|
-
|
|
512
|
-
logger.log(`[CRON-QUERY] readLocalRunLogsForJob: found ${entry.name}, entries=${parsed.length}`);
|
|
513
|
-
return parsed;
|
|
530
|
+
return parseCronRunLogEntriesFromJsonl(raw, { jobId });
|
|
514
531
|
}
|
|
515
532
|
}
|
|
516
533
|
catch {
|
|
@@ -522,7 +539,6 @@ async function readLocalRunLogsForJob(jobId) {
|
|
|
522
539
|
catch {
|
|
523
540
|
// Directory listing failed, give up
|
|
524
541
|
}
|
|
525
|
-
logger.log(`[CRON-QUERY] readLocalRunLogsForJob: no local run-log files found for jobId=${jobId}`);
|
|
526
542
|
return [];
|
|
527
543
|
}
|
|
528
544
|
/**
|
|
@@ -533,29 +549,23 @@ async function readLocalRunLogsForJob(jobId) {
|
|
|
533
549
|
*/
|
|
534
550
|
async function readAllLocalRunLogs() {
|
|
535
551
|
try {
|
|
536
|
-
logger.log(`[CRON-QUERY] readAllLocalRunLogs: scanning ${LEGACY_CRON_RUNS_DIR}`);
|
|
537
552
|
const entries = await fsp.readdir(LEGACY_CRON_RUNS_DIR, { withFileTypes: true });
|
|
538
553
|
const runLogFiles = entries.filter((e) => e.isFile() && isRunLogFile(e.name));
|
|
539
|
-
logger.log(`[CRON-QUERY] readAllLocalRunLogs: total dir entries=${entries.length}, runLogFiles=${runLogFiles.length}` +
|
|
540
|
-
(runLogFiles.length > 0 ? ` files=[${runLogFiles.map(f => f.name).join(", ")}]` : ""));
|
|
541
554
|
const allEntries = [];
|
|
542
555
|
for (const file of runLogFiles) {
|
|
543
556
|
const jobId = extractJobIdFromRunLogName(file.name);
|
|
544
557
|
try {
|
|
545
558
|
const raw = await fsp.readFile(path.join(LEGACY_CRON_RUNS_DIR, file.name), "utf-8");
|
|
546
559
|
const parsed = parseCronRunLogEntriesFromJsonl(raw, { jobId });
|
|
547
|
-
logger.log(`[CRON-QUERY] readAllLocalRunLogs: file=${file.name} jobId=${jobId} bytes=${raw.length} entries=${parsed.length}`);
|
|
548
560
|
allEntries.push(...parsed);
|
|
549
561
|
}
|
|
550
|
-
catch
|
|
551
|
-
|
|
562
|
+
catch {
|
|
563
|
+
// Skip unreadable files
|
|
552
564
|
}
|
|
553
565
|
}
|
|
554
|
-
logger.log(`[CRON-QUERY] readAllLocalRunLogs: total local entries=${allEntries.length}`);
|
|
555
566
|
return allEntries;
|
|
556
567
|
}
|
|
557
|
-
catch
|
|
558
|
-
logger.warn(`[CRON-QUERY] readAllLocalRunLogs: failed to scan runs dir:`, err);
|
|
568
|
+
catch {
|
|
559
569
|
return [];
|
|
560
570
|
}
|
|
561
571
|
}
|
package/dist/src/provider.js
CHANGED
|
@@ -606,6 +606,16 @@ export const xiaoyiProvider = {
|
|
|
606
606
|
logger.log(`[ALS-PROOF] provider headers source=uid-fallback (ALS miss)`);
|
|
607
607
|
}
|
|
608
608
|
}
|
|
609
|
+
// Mutate model.headers so the LLM compaction path picks up trace context.
|
|
610
|
+
// The LLM path (summarizeViaLLM → summarizeInStages) bypasses wrapStreamFn
|
|
611
|
+
// but reuses the same model object, so mutating model.headers here ensures
|
|
612
|
+
// x-hag-trace-id et al are forwarded even when the custom compaction
|
|
613
|
+
// provider is not reached (e.g. queued compaction with different sessionManager).
|
|
614
|
+
if (model?.headers && dynamicHeaders[HEADER_TRACE_ID]) {
|
|
615
|
+
model.headers[HEADER_TRACE_ID] = dynamicHeaders[HEADER_TRACE_ID];
|
|
616
|
+
model.headers[HEADER_SESSION_ID] = dynamicHeaders[HEADER_SESSION_ID];
|
|
617
|
+
model.headers[HEADER_INTERACTION_ID] = dynamicHeaders[HEADER_INTERACTION_ID];
|
|
618
|
+
}
|
|
609
619
|
// 记录输入
|
|
610
620
|
logger.log(`[xiaoyiprovider] input messages count: ${context.messages?.length ?? 0}`);
|
|
611
621
|
if (context.systemPrompt) {
|
|
@@ -628,9 +638,32 @@ export const xiaoyiProvider = {
|
|
|
628
638
|
// During compaction, getCurrentSessionContext() returns null because
|
|
629
639
|
// compaction runs outside the original A2A async scope. By storing
|
|
630
640
|
// the snapshot from the last normal request, the CompactionProvider
|
|
631
|
-
// can reuse the same A2A traceId/sessionId/devicetype
|
|
632
|
-
// summarization API call.
|
|
641
|
+
// can reuse the same A2A traceId/sessionId/devicetype and auth
|
|
642
|
+
// headers in its summarization API call.
|
|
633
643
|
if (dynamicHeaders[HEADER_TRACE_ID]) {
|
|
644
|
+
const requestHeaders = {};
|
|
645
|
+
if (options?.headers) {
|
|
646
|
+
for (const [key, value] of Object.entries(options.headers)) {
|
|
647
|
+
if (typeof value === "string") {
|
|
648
|
+
requestHeaders[key] = value;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
// model.headers comes from resolveDynamicModel — streamSimple
|
|
653
|
+
// reads these directly when building the HTTP request.
|
|
654
|
+
const modelHeaders = {};
|
|
655
|
+
if (model?.headers) {
|
|
656
|
+
for (const [key, value] of Object.entries(model.headers)) {
|
|
657
|
+
if (typeof value === "string") {
|
|
658
|
+
modelHeaders[key] = value;
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
const apiKey = typeof options?.apiKey === "string" ? options.apiKey : undefined;
|
|
663
|
+
logger.log(`[compaction-provider] capturing snapshot: hasApiKey=${!!apiKey} ` +
|
|
664
|
+
`requestHeaders=[${Object.keys(requestHeaders).join(",")}] ` +
|
|
665
|
+
`modelHeaders=[${Object.keys(modelHeaders).join(",")}] ` +
|
|
666
|
+
`traceId=${dynamicHeaders[HEADER_TRACE_ID]}`);
|
|
634
667
|
setCompactionSessionSnapshot({
|
|
635
668
|
traceId: dynamicHeaders[HEADER_TRACE_ID] ?? "",
|
|
636
669
|
sessionId: dynamicHeaders[HEADER_SESSION_ID] ?? "",
|
|
@@ -638,6 +671,9 @@ export const xiaoyiProvider = {
|
|
|
638
671
|
deviceType: deviceType ?? undefined,
|
|
639
672
|
appVer: appVer ?? undefined,
|
|
640
673
|
sdkApiVersion: sdkApiVersion ?? undefined,
|
|
674
|
+
requestHeaders: Object.keys(requestHeaders).length > 0 ? requestHeaders : undefined,
|
|
675
|
+
modelHeaders: Object.keys(modelHeaders).length > 0 ? modelHeaders : undefined,
|
|
676
|
+
apiKey: apiKey ?? undefined,
|
|
641
677
|
});
|
|
642
678
|
}
|
|
643
679
|
// 在发送给模型前,优化 systemPrompt 结构
|
|
@@ -9,7 +9,7 @@ import { logger } from "../utils/logger.js";
|
|
|
9
9
|
export const callPhoneTool = {
|
|
10
10
|
name: "call_phone",
|
|
11
11
|
label: "Call Phone",
|
|
12
|
-
description: "拨打电话。需要提供要拨打的电话号码。slotId
|
|
12
|
+
description: "拨打电话。需要提供要拨打的电话号码。slotId参数可选,如果用户明确指定为主卡为0(主卡),如果用户明确要求使用副卡则设置为1,如果用户没有指定使用哪张卡,则此参数不填。注意:操作超时时间为60秒,请勿重复调用此工具,如果超时或失败,最多重试一次。回复约束:如果工具返回没有授权或者其他报错,只需要完整描述没有授权或者其他报错内容即可,不需要主动给用户提供解决方案,例如告诉用户如何授权,如何解决报错等都是不需要的,请严格遵守。",
|
|
13
13
|
parameters: {
|
|
14
14
|
type: "object",
|
|
15
15
|
properties: {
|
|
@@ -19,8 +19,7 @@ export const callPhoneTool = {
|
|
|
19
19
|
},
|
|
20
20
|
slotId: {
|
|
21
21
|
type: "number",
|
|
22
|
-
description: "SIM卡槽ID
|
|
23
|
-
default: 0,
|
|
22
|
+
description: "SIM卡槽ID,仅当用户明确指定主卡/副卡时才设置,否则不传",
|
|
24
23
|
},
|
|
25
24
|
},
|
|
26
25
|
required: ["phoneNumber"],
|
|
@@ -32,10 +31,8 @@ export const callPhoneTool = {
|
|
|
32
31
|
if (!params.phoneNumber || typeof params.phoneNumber !== "string" || params.phoneNumber.trim() === "") {
|
|
33
32
|
throw new Error("Missing required parameter: phoneNumber must be a non-empty string");
|
|
34
33
|
}
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
// Validate slotId (must be 0 or 1)
|
|
38
|
-
if (slotId !== 0 && slotId !== 1) {
|
|
34
|
+
// Validate slotId if provided (must be 0 or 1)
|
|
35
|
+
if (params.slotId !== undefined && params.slotId !== null && params.slotId !== 0 && params.slotId !== 1) {
|
|
39
36
|
throw new Error("Invalid slotId: must be 0 (primary SIM) or 1 (secondary SIM)");
|
|
40
37
|
}
|
|
41
38
|
// Get WebSocket manager
|
|
@@ -58,7 +55,7 @@ export const callPhoneTool = {
|
|
|
58
55
|
timeOut: 5,
|
|
59
56
|
intentParam: {
|
|
60
57
|
phoneNumber: params.phoneNumber.trim(),
|
|
61
|
-
slotId: slotId,
|
|
58
|
+
...(params.slotId !== undefined && params.slotId !== null ? { slotId: params.slotId } : {}),
|
|
62
59
|
},
|
|
63
60
|
permissionId: [],
|
|
64
61
|
achieveType: "INTENT",
|