@agent-wechat/agent-wechat 1.0.0-beta.6 → 1.0.0-beta.7
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/index.js +110 -12
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -377,6 +377,13 @@ var NativeWeChatClient = class {
|
|
|
377
377
|
throw new Error("Invalid native message identity");
|
|
378
378
|
return message;
|
|
379
379
|
}
|
|
380
|
+
/** Resolve only the stored original in this message's own conversation. */
|
|
381
|
+
async quotedMessage(accountId, messageId) {
|
|
382
|
+
const quote = await this.request(`v1/accounts/${id(accountId)}/messages/${id(messageId)}/quote`);
|
|
383
|
+
if (quote.accountId !== accountId || quote.messageId !== messageId || typeof quote.threadId !== "string" || !["resolved", "embedded", "missing", "invalid", "unsupported", "ambiguous"].includes(quote.state) || (quote.state === "resolved" ? !quote.message || quote.message.accountId !== accountId || quote.message.threadId !== quote.threadId || quote.message.id === messageId : quote.message != null))
|
|
384
|
+
throw new Error("Invalid native quote identity");
|
|
385
|
+
return quote;
|
|
386
|
+
}
|
|
380
387
|
/** Account-wide local search, with the daemon's query-bound opaque cursor. */
|
|
381
388
|
async searchMessages(accountId, options) {
|
|
382
389
|
const result = await this.request(`v1/accounts/${id(accountId)}/messages${listQuery(options)}`);
|
|
@@ -1095,6 +1102,84 @@ async function prepareImageRepresentation(bytes, mime, cfg) {
|
|
|
1095
1102
|
}
|
|
1096
1103
|
throw Error("Native model image representation exceeds its bound");
|
|
1097
1104
|
}
|
|
1105
|
+
async function prepareNativeQuote(params) {
|
|
1106
|
+
const { client, account, message } = params;
|
|
1107
|
+
const guard = () => {
|
|
1108
|
+
params.signal?.throwIfAborted();
|
|
1109
|
+
params.assertCurrent?.();
|
|
1110
|
+
};
|
|
1111
|
+
guard();
|
|
1112
|
+
const comment = (message.body ?? "").slice(0, 16384);
|
|
1113
|
+
const unavailable = (state) => ({ body: `${comment}${comment ? "\n\n" : ""}[Quoted message ${state}; original lookup is confined to this chat]`, pending: false, retryable: state === "unavailable", quote: { state } });
|
|
1114
|
+
if (!message.quote) return unavailable("invalid");
|
|
1115
|
+
await params.beforeMediaAccess?.();
|
|
1116
|
+
guard();
|
|
1117
|
+
let resolution;
|
|
1118
|
+
try {
|
|
1119
|
+
resolution = await client.quotedMessage(account.native.accountId, message.id);
|
|
1120
|
+
} catch (error) {
|
|
1121
|
+
guard();
|
|
1122
|
+
if (!(error instanceof NativeApiError)) throw error;
|
|
1123
|
+
return unavailable("unavailable");
|
|
1124
|
+
}
|
|
1125
|
+
guard();
|
|
1126
|
+
if (resolution.accountId !== message.accountId || resolution.messageId !== message.id || resolution.threadId !== message.threadId) throw Error("Native quote identity does not match its message");
|
|
1127
|
+
if (resolution.state === "embedded") {
|
|
1128
|
+
if (resolution.message != null || (message.quote.type === 1 ? typeof resolution.embeddedText !== "string" || resolution.embeddedText.length > 16384 : message.quote.type !== 3 || resolution.embeddedImage !== true)) throw Error("Invalid embedded quote resolution");
|
|
1129
|
+
const author = { senderId: resolution.senderId ?? null, direction: null };
|
|
1130
|
+
if (params.allowQuotedMessage && !await params.allowQuotedMessage(author)) return unavailable("not permitted");
|
|
1131
|
+
guard();
|
|
1132
|
+
const quote2 = { state: "embedded", senderId: resolution.senderId ?? void 0, type: message.quote.type };
|
|
1133
|
+
const prefix2 = `${comment}${comment ? "\n\n" : ""}[Quoted ${message.quote.type === 3 ? "image" : "text"} from ${resolution.senderId ?? "unknown sender"}; embedded in quote, original not stored]
|
|
1134
|
+
`;
|
|
1135
|
+
if (message.quote.type === 1) return { body: `${prefix2}${resolution.embeddedText}
|
|
1136
|
+
[/Quoted message]`, pending: false, quote: quote2 };
|
|
1137
|
+
const beforeMediaAccess2 = async () => {
|
|
1138
|
+
await params.beforeMediaAccess?.();
|
|
1139
|
+
guard();
|
|
1140
|
+
if (params.allowQuotedMessage && !await params.allowQuotedMessage(author)) throw Error("Quoted message is no longer permitted");
|
|
1141
|
+
guard();
|
|
1142
|
+
};
|
|
1143
|
+
const prepared2 = await prepareNativeAttachment({ ...params, message: { ...message, type: 3, appMessageType: void 0, quote: void 0 }, beforeMediaAccess: beforeMediaAccess2 });
|
|
1144
|
+
return {
|
|
1145
|
+
...prepared2,
|
|
1146
|
+
body: `${prefix2}${prepared2.body}
|
|
1147
|
+
[/Quoted message]`,
|
|
1148
|
+
pending: false,
|
|
1149
|
+
quote: quote2,
|
|
1150
|
+
...prepared2.pending ? { attachment: { kind: "image", status: "pending", filename: "quoted-image" } } : {}
|
|
1151
|
+
};
|
|
1152
|
+
}
|
|
1153
|
+
if (resolution.state !== "resolved") {
|
|
1154
|
+
if (!["missing", "invalid", "unsupported", "ambiguous"].includes(resolution.state) || resolution.message != null) throw Error("Invalid native quote resolution");
|
|
1155
|
+
return unavailable(resolution.state);
|
|
1156
|
+
}
|
|
1157
|
+
const original = resolution.message;
|
|
1158
|
+
if (!original || original.accountId !== message.accountId || original.threadId !== message.threadId || original.id === message.id || original.serverId !== message.quote.serverId || original.type !== message.quote.type || ![1, 3].includes(original.type)) throw Error("Native quoted original crosses its message boundary");
|
|
1159
|
+
if (params.allowQuotedMessage && !await params.allowQuotedMessage(original)) return unavailable("not permitted");
|
|
1160
|
+
guard();
|
|
1161
|
+
const quote = { state: "resolved", messageId: original.id, senderId: original.senderId ?? void 0, type: original.type };
|
|
1162
|
+
const prefix = `${comment}${comment ? "\n\n" : ""}[Quoted ${original.type === 3 ? "image" : "text"} from ${original.senderId ?? "unknown sender"}; message ${original.id}]
|
|
1163
|
+
`;
|
|
1164
|
+
if (original.type === 1) return { body: `${prefix}${(original.body ?? "").slice(0, 16384)}
|
|
1165
|
+
[/Quoted message]`, pending: false, quote };
|
|
1166
|
+
const beforeMediaAccess = async () => {
|
|
1167
|
+
await params.beforeMediaAccess?.();
|
|
1168
|
+
guard();
|
|
1169
|
+
if (params.allowQuotedMessage && !await params.allowQuotedMessage(original)) throw Error("Quoted message is no longer permitted");
|
|
1170
|
+
guard();
|
|
1171
|
+
};
|
|
1172
|
+
const prepared = await prepareNativeAttachment({ ...params, message: original, beforeMediaAccess });
|
|
1173
|
+
return {
|
|
1174
|
+
...prepared,
|
|
1175
|
+
body: `${prefix}${prepared.body}
|
|
1176
|
+
[/Quoted message]`,
|
|
1177
|
+
pending: false,
|
|
1178
|
+
quote,
|
|
1179
|
+
...prepared.pending ? { attachment: { kind: "image", status: "pending", filename: "quoted-image" } } : {},
|
|
1180
|
+
suppliedAssets: prepared.suppliedAssets?.map((asset) => ({ ...asset, sourceMessageId: original.id }))
|
|
1181
|
+
};
|
|
1182
|
+
}
|
|
1098
1183
|
async function prepareNativeAttachment(params) {
|
|
1099
1184
|
const { client, account, message, core } = params;
|
|
1100
1185
|
const attachmentKind = kind(message.type);
|
|
@@ -1108,6 +1193,9 @@ async function prepareNativeAttachment(params) {
|
|
|
1108
1193
|
await params.beforeMediaAccess?.();
|
|
1109
1194
|
guard();
|
|
1110
1195
|
};
|
|
1196
|
+
if (message.type === 49 && message.appMessageType === 57) return prepareNativeQuote(params);
|
|
1197
|
+
if (message.type === 49 && message.appMessageType === 19) return { body: message.body?.slice(0, 65536) || "[Forwarded chat history unavailable]", pending: false };
|
|
1198
|
+
if (message.type === 49 && message.appMessageType != null && message.appMessageType !== 6) return { body: `[Unsupported WeChat app message subtype ${message.appMessageType}]`, pending: false, retryable: false };
|
|
1111
1199
|
if (!attachmentKind) return { body: message.type === 1 ? message.body ?? "" : `[Unsupported WeChat message type ${message.type}]`, pending: false };
|
|
1112
1200
|
const accountId = account.native.accountId;
|
|
1113
1201
|
const maxBytes = Math.min(account.mediaMaxMb * 1024 * 1024, 128 * 1024 * 1024);
|
|
@@ -1511,7 +1599,7 @@ function nativeDispatchManifest(params) {
|
|
|
1511
1599
|
const history = params.history ?? [];
|
|
1512
1600
|
if (history.length > 200 || history.some((message) => message.accountId !== params.message.accountId || message.threadId !== params.message.threadId || message.id === params.message.id)) throw Error("Invalid native history manifest");
|
|
1513
1601
|
const assets = Object.freeze((params.assets ?? []).map((asset) => {
|
|
1514
|
-
if (typeof asset.id !== "string" || !asset.id || asset.id.length > 2048 || !/^[a-f0-9]{64}$/.test(asset.sha256) || !Number.isSafeInteger(asset.byteLength) || asset.byteLength < 1 || asset.byteLength > 128 * 1024 * 1024 || typeof asset.path !== "string" || !isAbsolute3(asset.path) || asset.path.length > 4096 || typeof asset.mime !== "string" || asset.mime.length > 128 || !/^[a-z0-9.+-]+\/[a-z0-9.+-]+$/.test(asset.mime) || !["image", "audio", "video", "file"].includes(asset.kind) || !["model_image", "file_reference"].includes(asset.role) || asset.role === "model_image" && (asset.kind !== "image" || !asset.mime.startsWith("image/") || !asset.runtimeMedia) || !["trigger", "history"].includes(asset.contextRole) || asset.contextRole === "history" && !history.some((message) => message.id === asset.messageId)) throw Error("Invalid native supplied asset manifest");
|
|
1602
|
+
if (typeof asset.id !== "string" || !asset.id || asset.id.length > 2048 || !/^[a-f0-9]{64}$/.test(asset.sha256) || !Number.isSafeInteger(asset.byteLength) || asset.byteLength < 1 || asset.byteLength > 128 * 1024 * 1024 || typeof asset.path !== "string" || !isAbsolute3(asset.path) || asset.path.length > 4096 || typeof asset.mime !== "string" || asset.mime.length > 128 || !/^[a-z0-9.+-]+\/[a-z0-9.+-]+$/.test(asset.mime) || !["image", "audio", "video", "file"].includes(asset.kind) || !["model_image", "file_reference"].includes(asset.role) || asset.role === "model_image" && (asset.kind !== "image" || !asset.mime.startsWith("image/") || !asset.runtimeMedia) || !["trigger", "history"].includes(asset.contextRole) || asset.sourceMessageId != null && (typeof asset.sourceMessageId !== "string" || !asset.sourceMessageId || asset.sourceMessageId.length > 8192 || ![params.message, ...history].some((message) => message.id === asset.messageId && message.type === 49 && message.appMessageType === 57 && message.quote?.type === 3)) || asset.contextRole === "history" && !history.some((message) => message.id === asset.messageId)) throw Error("Invalid native supplied asset manifest");
|
|
1515
1603
|
return Object.freeze({ ...asset });
|
|
1516
1604
|
}));
|
|
1517
1605
|
const messageSid = `wechat-native:${params.message.accountId}:${params.message.id}`;
|
|
@@ -1537,14 +1625,16 @@ function nativeDispatchManifest(params) {
|
|
|
1537
1625
|
params.message.type,
|
|
1538
1626
|
params.message.body,
|
|
1539
1627
|
params.message.mentions,
|
|
1540
|
-
params.message.serverId
|
|
1628
|
+
params.message.serverId,
|
|
1629
|
+
params.message.appMessageType,
|
|
1630
|
+
params.message.quote
|
|
1541
1631
|
])),
|
|
1542
1632
|
role: "trigger"
|
|
1543
1633
|
})]),
|
|
1544
1634
|
...history.length ? { historyInputs: Object.freeze(history.map((message) => Object.freeze({
|
|
1545
1635
|
id: message.id,
|
|
1546
1636
|
version: message.version ?? "",
|
|
1547
|
-
contentSha256: digest(JSON.stringify([message.id, message.version, message.senderId, message.type, message.body, message.mentions, message.serverId])),
|
|
1637
|
+
contentSha256: digest(JSON.stringify([message.id, message.version, message.senderId, message.type, message.body, message.mentions, message.serverId, message.appMessageType, message.quote])),
|
|
1548
1638
|
role: "history"
|
|
1549
1639
|
}))) } : {},
|
|
1550
1640
|
assets,
|
|
@@ -1842,8 +1932,9 @@ async function dispatchNativeMessage(params) {
|
|
|
1842
1932
|
return "ignored";
|
|
1843
1933
|
}
|
|
1844
1934
|
let preparedMedia;
|
|
1935
|
+
const allowQuotedMessage = async (original) => original.direction === "outgoing" || !!original.senderId && (await senderAdmission(original.senderId)).ingress.admission === "dispatch";
|
|
1845
1936
|
try {
|
|
1846
|
-
preparedMedia = await (params.prepareAttachment ?? prepareNativeAttachment)({ client, account, message, core, cfg });
|
|
1937
|
+
preparedMedia = await (params.prepareAttachment ?? prepareNativeAttachment)({ client, account, message, core, cfg, allowQuotedMessage });
|
|
1847
1938
|
} catch {
|
|
1848
1939
|
state.update(message.id, { state: "needs_review" });
|
|
1849
1940
|
return "needs_review";
|
|
@@ -1856,7 +1947,7 @@ async function dispatchNativeMessage(params) {
|
|
|
1856
1947
|
if (item.message.senderId && (await senderAdmission(item.message.senderId)).ingress.admission === "dispatch") admitted.push(item.message);
|
|
1857
1948
|
}
|
|
1858
1949
|
const history = await prepareNativeHistory(admitted, async (historical) => transcribeNativeAttachment(
|
|
1859
|
-
await (params.prepareAttachment ?? prepareNativeAttachment)({ client, account, message: historical, core, cfg }),
|
|
1950
|
+
await (params.prepareAttachment ?? prepareNativeAttachment)({ client, account, message: historical, core, cfg, allowQuotedMessage }),
|
|
1860
1951
|
core,
|
|
1861
1952
|
cfg
|
|
1862
1953
|
));
|
|
@@ -1912,7 +2003,7 @@ async function dispatchNativeMessage(params) {
|
|
|
1912
2003
|
history: admitted
|
|
1913
2004
|
});
|
|
1914
2005
|
state.beginDispatch(message.id, dispatchManifest, buffered.map((item) => item.message.id));
|
|
1915
|
-
const callback = readPolicy !== "on_inbound" && (message.type === 1 || preparedMedia.attachment?.status === "ready" && preparedMedia.suppliedAssets?.length) ? state.preparedCallback([message.id], readPolicy) : void 0;
|
|
2006
|
+
const callback = readPolicy !== "on_inbound" && (message.type === 1 || message.type === 49 && message.appMessageType === 19 && !!message.body || preparedMedia.quote?.type === 1 && ["resolved", "embedded"].includes(preparedMedia.quote.state) || preparedMedia.attachment?.status === "ready" && preparedMedia.suppliedAssets?.length) ? state.preparedCallback([message.id], readPolicy) : void 0;
|
|
1916
2007
|
if (callback) trackNativePreparedInput(dispatchManifest, callback);
|
|
1917
2008
|
let failed = false;
|
|
1918
2009
|
let delivery = 0;
|
|
@@ -2590,8 +2681,11 @@ function threadId(value) {
|
|
|
2590
2681
|
return string(string(value, "thread ID").replace(/^(?:agent-)?wechat:(?:group:)?/i, ""), "thread ID");
|
|
2591
2682
|
}
|
|
2592
2683
|
function messageResult(message, retrievalTool = "wechat_media") {
|
|
2593
|
-
const
|
|
2594
|
-
const
|
|
2684
|
+
const isQuote = message.type === 49 && message.appMessageType === 57;
|
|
2685
|
+
const isForward = message.type === 49 && message.appMessageType === 19;
|
|
2686
|
+
const kind2 = isQuote ? "quote" : isForward ? "forwarded_history" : message.type === 3 ? "image" : message.type === 47 ? "sticker" : message.type === 34 ? "voice" : message.type === 43 ? "video" : message.type === 49 && (message.appMessageType == null || message.appMessageType === 6) ? "file" : "unsupported";
|
|
2687
|
+
const body = message.type === 1 || isForward ? message.body ?? "" : isQuote ? `${message.body ?? ""}
|
|
2688
|
+
[Quoted original not retrieved; use ${retrievalTool} with this message ID]` : `[WeChat ${kind2} message; attachment content has not been retrieved by this tool]`;
|
|
2595
2689
|
return {
|
|
2596
2690
|
id: message.id,
|
|
2597
2691
|
threadId: message.threadId,
|
|
@@ -2599,10 +2693,12 @@ function messageResult(message, retrievalTool = "wechat_media") {
|
|
|
2599
2693
|
senderId: message.senderId,
|
|
2600
2694
|
direction: message.direction,
|
|
2601
2695
|
type: message.type,
|
|
2696
|
+
appMessageType: message.appMessageType,
|
|
2697
|
+
quote: message.quote,
|
|
2602
2698
|
createdAt: message.createdAt,
|
|
2603
2699
|
body: body.slice(0, 4e3),
|
|
2604
2700
|
bodyTruncated: body.length > 4e3,
|
|
2605
|
-
attachment: message.type !== 1 && kind2 !== "unsupported" ? { kind: kind2, retrievalTool, messageId: message.id } : void 0
|
|
2701
|
+
attachment: message.type !== 1 && !isForward && kind2 !== "unsupported" ? { kind: kind2, retrievalTool, messageId: message.id } : void 0
|
|
2606
2702
|
};
|
|
2607
2703
|
}
|
|
2608
2704
|
function createNativeHistoryTools(context, dependencies = {}) {
|
|
@@ -2720,6 +2816,7 @@ function createLookupTools(context, dependencies, archive) {
|
|
|
2720
2816
|
waitMs: 2e3,
|
|
2721
2817
|
signal,
|
|
2722
2818
|
assertCurrent: guard,
|
|
2819
|
+
allowQuotedMessage: async (original) => original.direction === "outgoing" || await allowed(original.senderId ?? void 0),
|
|
2723
2820
|
beforeMediaAccess: async () => {
|
|
2724
2821
|
if (!await allowed(requesterSenderId) || message.direction !== "outgoing" && !await allowed(message.senderId ?? void 0)) throw Error("WeChat conversation access is no longer allowed");
|
|
2725
2822
|
}
|
|
@@ -2741,8 +2838,9 @@ function createLookupTools(context, dependencies, archive) {
|
|
|
2741
2838
|
const details = {
|
|
2742
2839
|
messageId: message.id,
|
|
2743
2840
|
threadId: message.threadId,
|
|
2744
|
-
status: prepared.pending ? "pending" : prepared.attachment?.status ?? "unsupported",
|
|
2841
|
+
status: prepared.pending ? "pending" : prepared.attachment?.status ?? prepared.quote?.state ?? "unsupported",
|
|
2745
2842
|
attachment: prepared.attachment,
|
|
2843
|
+
quote: prepared.quote,
|
|
2746
2844
|
variant: prepared.variant,
|
|
2747
2845
|
jobId: prepared.jobId,
|
|
2748
2846
|
retryable: prepared.retryable ?? false,
|
|
@@ -2786,13 +2884,13 @@ function createLookupTools(context, dependencies, archive) {
|
|
|
2786
2884
|
tool(archiveNames[1], "search", "Search locally stored messages across every thread in the configured WeChat account, including other senders. Optional threadId narrows the search. Follow nextCursor with the same query and thread selection; no remote backfill or read marking.", { query: querySchema, threadId: messageIdSchema, ...pageSchema }, ["query"]),
|
|
2787
2885
|
tool(archiveNames[2], "history", "Read the locally stored history of any thread in the configured WeChat account, newest first, without sender filtering or read marking. Use wechat_archive_threads to find thread IDs.", { threadId: messageIdSchema, ...pageSchema }, ["threadId"]),
|
|
2788
2886
|
tool(archiveNames[3], "message", "Look up a stored message in any thread of the configured WeChat account by its exact opaque message ID. Use wechat_archive_media to retrieve its attachment.", { messageId: messageIdSchema }, ["messageId"]),
|
|
2789
|
-
tool(archiveNames[4], "media", "Retrieve an attachment from any thread in the configured WeChat account by its message ID. Returns verified managed file paths and an image preview for vision. Cached bytes first; missing media requests a bounded server-owned download unless download=false. Preserve pending/expired/unavailable results and never automatically retry retryable=false. No sends or read marking.", mediaSchema, ["messageId"])
|
|
2887
|
+
tool(archiveNames[4], "media", "Retrieve an attachment or quoted text/image from any thread in the configured WeChat account by its message ID. Use the parent quote message ID for quotes. Returns verified managed file paths and an image preview for vision. Cached bytes first; missing media requests a bounded server-owned download unless download=false. Preserve pending/expired/unavailable results and never automatically retry retryable=false. No sends or read marking.", mediaSchema, ["messageId"])
|
|
2790
2888
|
];
|
|
2791
2889
|
return [
|
|
2792
2890
|
tool(names[0], "history", "Read the current WeChat conversation's locally stored messages, newest first, beyond the recent inbound buffer. Follow nextCursor for older history; no remote history backfill or read marking.", { ...pageSchema }),
|
|
2793
2891
|
tool(names[1], "search", "Search the current WeChat conversation's locally stored message text by case-insensitive substring, beyond buffered messages. Follow nextCursor with the same query in this conversation.", { query: querySchema, ...pageSchema }, ["query"]),
|
|
2794
2892
|
tool(names[2], "message", "Look up a stored message by its exact opaque message ID in the current WeChat conversation. Media is described without downloading it; use wechat_media to retrieve its attachment. Long text is bounded.", { messageId: messageIdSchema }, ["messageId"]),
|
|
2795
|
-
tool(names[3], "media", "Retrieve a message attachment from the current WeChat conversation. Returns a verified local file path and an image preview for vision when applicable. Uses cached bytes first; download defaults to true and requests a bounded server-owned transfer if missing. Pending results may be checked again; never automatically retry confirmed expiry or retryable=false. Does not send messages or mark the thread read.", mediaSchema, ["messageId"])
|
|
2893
|
+
tool(names[3], "media", "Retrieve a message attachment or quoted text/image from the current WeChat conversation. Use the parent quote message ID for quotes. Returns a verified local file path and an image preview for vision when applicable. Uses cached bytes first; download defaults to true and requests a bounded server-owned transfer if missing. Pending results may be checked again; never automatically retry confirmed expiry or retryable=false. Does not send messages or mark the thread read.", mediaSchema, ["messageId"])
|
|
2796
2894
|
];
|
|
2797
2895
|
}
|
|
2798
2896
|
function registerNativeHistoryTools(api) {
|