@aalis/schema-message 0.5.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ace Nyan <ace@acenyan.com>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,25 @@
1
+ # @aalis/schema-message
2
+
3
+ 类型/接口/能力声明包(只导出 `type` 与 `*Capabilities`,不含运行时逻辑)
4
+
5
+ ## 角色
6
+
7
+ 类型/接口/能力声明包(只导出 `type` 与 `*Capabilities`,不含运行时逻辑)
8
+
9
+ ## 安装
10
+
11
+ ```bash
12
+ pnpm add @aalis/schema-message
13
+ ```
14
+
15
+ ## 使用
16
+
17
+ ```ts
18
+ import type { /* ... */ } from '@aalis/schema-message';
19
+ ```
20
+
21
+ 本包为 *-api 类型包;实现见对应的运行时插件。
22
+
23
+ ## 许可
24
+
25
+ 见仓库根目录 LICENSE。
@@ -0,0 +1,35 @@
1
+ /** 附件 kind 显示名(中文,用作占位符前缀)。新增 kind 在此处加常量即可。 */
2
+ export declare const AttachmentRefKind: {
3
+ readonly Image: "图片";
4
+ readonly Audio: "音频";
5
+ readonly Video: "视频";
6
+ readonly File: "文件";
7
+ };
8
+ export type AttachmentRefKind = (typeof AttachmentRefKind)[keyof typeof AttachmentRefKind];
9
+ export interface AttachmentRef {
10
+ kind: AttachmentRefKind;
11
+ /** 可选语义描述(视觉概要 / 文件备注 / 音频转写片段等) */
12
+ desc?: string;
13
+ /** 引用:本地路径 / file:// / http(s) URL;调用方决定如何解析 */
14
+ ref: string;
15
+ }
16
+ /**
17
+ * 把 ref 描述对象格式化为统一占位符字符串。
18
+ *
19
+ * { kind: '图片', desc: 'x', ref: 'p' } → '[图片: x | ref:p]'
20
+ * { kind: '图片', ref: 'p' } → '[图片 | ref:p]'
21
+ *
22
+ * desc 为空字符串视同未提供(与历史行为一致)。
23
+ */
24
+ export declare function formatAttachmentRef(r: AttachmentRef): string;
25
+ /**
26
+ * 在文本中扫描所有形如 `[<kind>(: <desc>)? | ref:<ref>]` 的占位符。
27
+ * `<ref>` 内不允许出现 `]`(这是契约:写入时由 formatAttachmentRef 保证)。
28
+ */
29
+ export declare function parseAttachmentRefs(text: string): AttachmentRef[];
30
+ /**
31
+ * 构造一个用于在文本中匹配「指定 kind + 指定 ref」的全部已存在占位符的正则。
32
+ * 主要给 plugin-media 的 update_image_description 工具用,让它不必重新硬编码格式。
33
+ */
34
+ export declare function buildAttachmentRefMatcher(kind: AttachmentRefKind, ref: string): RegExp;
35
+ //# sourceMappingURL=attachment-ref.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attachment-ref.d.ts","sourceRoot":"","sources":["../src/attachment-ref.ts"],"names":[],"mappings":"AAyBA,gDAAgD;AAChD,eAAO,MAAM,iBAAiB;;;;;CAKpB,CAAC;AAEX,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,OAAO,iBAAiB,CAAC,CAAC;AAK3F,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,iBAAiB,CAAC;IACxB,oCAAoC;IACpC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gDAAgD;IAChD,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,aAAa,GAAG,MAAM,CAI5D;AAOD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,EAAE,CAUjE;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,iBAAiB,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAItF"}
@@ -0,0 +1,76 @@
1
+ // ============================================================
2
+ // attachment-ref.ts — 跨插件统一的「附件引用」字符串契约
3
+ //
4
+ // Aalis 在多个地方需要把附件(图片 / 音频 / 视频 / 文件)以可读、可解析的
5
+ // 形式塞回 LLM 上下文里。历史上有四个调用点各自硬编码 `[图片: desc | ref:xxx]`
6
+ // 这种格式:
7
+ // - plugin-adapter-onebot 入站构造图片占位
8
+ // - plugin-image-sender 出站归档自己刚发的图
9
+ // - plugin-media tools.ts regex 重写历史描述
10
+ // - plugin-image-recognition 解析历史图片引用
11
+ // 任何一处格式漂移都会让其它三处的解析悄悄断链。
12
+ //
13
+ // 本模块提供单一格式来源 + 类型安全的 kind 枚举:
14
+ // formatAttachmentRef({ kind: AttachmentRefKind.Image, desc: '一只猫', ref: 'data/x.png' })
15
+ // === '[图片: 一只猫 | ref:data/x.png]'
16
+ // formatAttachmentRef({ kind: AttachmentRefKind.Image, ref: 'data/x.png' })
17
+ // === '[图片 | ref:data/x.png]'
18
+ // parseAttachmentRefs(text)
19
+ // === [{ kind: '图片', desc?: string, ref: string }, ...]
20
+ //
21
+ // 设计约束:
22
+ // - 输出必须 byte-for-byte 兼容历史格式(数据库里已有的字符串不重写)。
23
+ // - parser 不消耗 desc 中的转义;上游写入时确保 desc 不含 `]` / `|`。
24
+ // ============================================================
25
+ /** 附件 kind 显示名(中文,用作占位符前缀)。新增 kind 在此处加常量即可。 */
26
+ export const AttachmentRefKind = {
27
+ Image: '图片',
28
+ Audio: '音频',
29
+ Video: '视频',
30
+ File: '文件',
31
+ };
32
+ /** 所有 kind 显示名的联合,供正则构造时迭代。 */
33
+ const ALL_KINDS = Object.values(AttachmentRefKind);
34
+ /**
35
+ * 把 ref 描述对象格式化为统一占位符字符串。
36
+ *
37
+ * { kind: '图片', desc: 'x', ref: 'p' } → '[图片: x | ref:p]'
38
+ * { kind: '图片', ref: 'p' } → '[图片 | ref:p]'
39
+ *
40
+ * desc 为空字符串视同未提供(与历史行为一致)。
41
+ */
42
+ export function formatAttachmentRef(r) {
43
+ const desc = r.desc?.trim();
44
+ if (desc)
45
+ return `[${r.kind}: ${desc} | ref:${r.ref}]`;
46
+ return `[${r.kind} | ref:${r.ref}]`;
47
+ }
48
+ // 内部:把所有 kind 拼成 alternation `图片|音频|视频|文件`
49
+ function kindAlternation() {
50
+ return ALL_KINDS.map(k => k.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|');
51
+ }
52
+ /**
53
+ * 在文本中扫描所有形如 `[<kind>(: <desc>)? | ref:<ref>]` 的占位符。
54
+ * `<ref>` 内不允许出现 `]`(这是契约:写入时由 formatAttachmentRef 保证)。
55
+ */
56
+ export function parseAttachmentRefs(text) {
57
+ const re = new RegExp(`\\[(${kindAlternation()})(?:: ([^\\]\\n|]+?))? \\| ref:([^\\]\\n]+?)\\]`, 'g');
58
+ const out = [];
59
+ for (const m of text.matchAll(re)) {
60
+ const kind = m[1];
61
+ const desc = m[2]?.trim();
62
+ const ref = m[3].trim();
63
+ out.push(desc ? { kind, desc, ref } : { kind, ref });
64
+ }
65
+ return out;
66
+ }
67
+ /**
68
+ * 构造一个用于在文本中匹配「指定 kind + 指定 ref」的全部已存在占位符的正则。
69
+ * 主要给 plugin-media 的 update_image_description 工具用,让它不必重新硬编码格式。
70
+ */
71
+ export function buildAttachmentRefMatcher(kind, ref) {
72
+ const escapedKind = kind.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
73
+ const escapedRef = ref.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
74
+ return new RegExp(`\\[${escapedKind}(?:: [^\\]\\n]*?)? \\| ref:${escapedRef}\\]`, 'g');
75
+ }
76
+ //# sourceMappingURL=attachment-ref.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attachment-ref.js","sourceRoot":"","sources":["../src/attachment-ref.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,wCAAwC;AACxC,EAAE;AACF,8CAA8C;AAC9C,sDAAsD;AACtD,QAAQ;AACR,qCAAqC;AACrC,qCAAqC;AACrC,yCAAyC;AACzC,wCAAwC;AACxC,0BAA0B;AAC1B,EAAE;AACF,+BAA+B;AAC/B,2FAA2F;AAC3F,uCAAuC;AACvC,8EAA8E;AAC9E,kCAAkC;AAClC,8BAA8B;AAC9B,4DAA4D;AAC5D,EAAE;AACF,QAAQ;AACR,gDAAgD;AAChD,sDAAsD;AACtD,+DAA+D;AAE/D,gDAAgD;AAChD,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,KAAK,EAAE,IAAI;IACX,KAAK,EAAE,IAAI;IACX,KAAK,EAAE,IAAI;IACX,IAAI,EAAE,IAAI;CACF,CAAC;AAIX,+BAA+B;AAC/B,MAAM,SAAS,GAAiC,MAAM,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC;AAUjF;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB,CAAC,CAAgB;IAClD,MAAM,IAAI,GAAG,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC;IAC5B,IAAI,IAAI;QAAE,OAAO,IAAI,CAAC,CAAC,IAAI,KAAK,IAAI,UAAU,CAAC,CAAC,GAAG,GAAG,CAAC;IACvD,OAAO,IAAI,CAAC,CAAC,IAAI,UAAU,CAAC,CAAC,GAAG,GAAG,CAAC;AACtC,CAAC;AAED,2CAA2C;AAC3C,SAAS,eAAe;IACtB,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAChF,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,MAAM,EAAE,GAAG,IAAI,MAAM,CAAC,OAAO,eAAe,EAAE,iDAAiD,EAAE,GAAG,CAAC,CAAC;IACtG,MAAM,GAAG,GAAoB,EAAE,CAAC;IAChC,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC;QAClC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,CAAsB,CAAC;QACvC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;QAC1B,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACxB,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;IACvD,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,yBAAyB,CAAC,IAAuB,EAAE,GAAW;IAC5E,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC;IAChE,MAAM,UAAU,GAAG,GAAG,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC;IAC9D,OAAO,IAAI,MAAM,CAAC,MAAM,WAAW,8BAA8B,UAAU,KAAK,EAAE,GAAG,CAAC,CAAC;AACzF,CAAC"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * 用户身份标识工具函数
3
+ *
4
+ * 统一 userId / nickname 的使用模式,避免各插件实现不一致。
5
+ *
6
+ * 由 schema-message 提供(cleanup-9 从 core 迁出——core 不应知晓
7
+ * IM 平台层的「发送者」「昵称」「OpenAI Message.name 字段」等概念)。
8
+ */
9
+ /**
10
+ * 获取消息发送者的显示标签。
11
+ * 同时展示 nickname 和 userId 帮助 LLM 关联身份。
12
+ * - 两者都有时返回 `昵称(ID)`
13
+ * - 只有 nickname 返回 nickname
14
+ * - 只有 userId 返回 userId
15
+ * - 都无返回 undefined
16
+ */
17
+ export declare function getSenderLabel(nickname?: string, userId?: string): string | undefined;
18
+ /**
19
+ * 将发送者标签格式化为消息前缀。
20
+ * 有标签时返回 `[label]: content`,无标签时原样返回 content。
21
+ */
22
+ export declare function prefixSender(content: string, nickname?: string, userId?: string): string;
23
+ /**
24
+ * 获取适用于 Message.name / OpenAI API name 字段的安全标识符。
25
+ * 使用 userId(稳定不变)而非 nickname(可变)。
26
+ * 返回 undefined 表示不设置 name 字段。
27
+ */
28
+ export declare function getMessageName(userId?: string): string | undefined;
29
+ //# sourceMappingURL=identity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identity.d.ts","sourceRoot":"","sources":["../src/identity.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAIrF;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAGxF;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAElE"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * 用户身份标识工具函数
3
+ *
4
+ * 统一 userId / nickname 的使用模式,避免各插件实现不一致。
5
+ *
6
+ * 由 schema-message 提供(cleanup-9 从 core 迁出——core 不应知晓
7
+ * IM 平台层的「发送者」「昵称」「OpenAI Message.name 字段」等概念)。
8
+ */
9
+ /**
10
+ * 获取消息发送者的显示标签。
11
+ * 同时展示 nickname 和 userId 帮助 LLM 关联身份。
12
+ * - 两者都有时返回 `昵称(ID)`
13
+ * - 只有 nickname 返回 nickname
14
+ * - 只有 userId 返回 userId
15
+ * - 都无返回 undefined
16
+ */
17
+ export function getSenderLabel(nickname, userId) {
18
+ const nick = nickname?.trim();
19
+ if (nick && userId)
20
+ return `${nick}(${userId})`;
21
+ return nick || userId || undefined;
22
+ }
23
+ /**
24
+ * 将发送者标签格式化为消息前缀。
25
+ * 有标签时返回 `[label]: content`,无标签时原样返回 content。
26
+ */
27
+ export function prefixSender(content, nickname, userId) {
28
+ const label = getSenderLabel(nickname, userId);
29
+ return label ? `[${label}]: ${content}` : content;
30
+ }
31
+ /**
32
+ * 获取适用于 Message.name / OpenAI API name 字段的安全标识符。
33
+ * 使用 userId(稳定不变)而非 nickname(可变)。
34
+ * 返回 undefined 表示不设置 name 字段。
35
+ */
36
+ export function getMessageName(userId) {
37
+ return userId || undefined;
38
+ }
39
+ //# sourceMappingURL=identity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identity.js","sourceRoot":"","sources":["../src/identity.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAAC,QAAiB,EAAE,MAAe;IAC/D,MAAM,IAAI,GAAG,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC9B,IAAI,IAAI,IAAI,MAAM;QAAE,OAAO,GAAG,IAAI,IAAI,MAAM,GAAG,CAAC;IAChD,OAAO,IAAI,IAAI,MAAM,IAAI,SAAS,CAAC;AACrC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,OAAe,EAAE,QAAiB,EAAE,MAAe;IAC9E,MAAM,KAAK,GAAG,cAAc,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC/C,OAAO,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,OAAO,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;AACpD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,MAAe;IAC5C,OAAO,MAAM,IAAI,SAAS,CAAC;AAC7B,CAAC"}
@@ -0,0 +1,309 @@
1
+ /**
2
+ * OpenAI/DeepSeek chat completions 中 assistant 消息携带的工具调用载荷。
3
+ * 与 Message 同源同生命周期,故所属本包。
4
+ */
5
+ export interface ToolCall {
6
+ id: string;
7
+ type: 'function';
8
+ function: {
9
+ name: string;
10
+ arguments: string;
11
+ };
12
+ }
13
+ /**
14
+ * 内容时间线分段(按到达顺序记录助手输出的真实结构)。
15
+ * - text:正常对话文本
16
+ * - reasoning_text:思考/推理文本(部分模型如 DeepSeek-R1、Ollama thinking 会产出)
17
+ * - tool_call:工具调用片段(startTime/endTime 用于时长展示)
18
+ *
19
+ * 该数组若存在则为渲染顺序的真相;同时 message.content / reasoningContent
20
+ * 仍保留为派生镜像,供 LLM API 与历史压缩等纯文本消费者使用。
21
+ */
22
+ export type ContentSegment = {
23
+ type: 'text';
24
+ content: string;
25
+ } | {
26
+ type: 'reasoning_text';
27
+ content: string;
28
+ } | {
29
+ type: 'tool_call';
30
+ name: string;
31
+ args: Record<string, unknown>;
32
+ result?: string;
33
+ startTime?: number;
34
+ endTime?: number;
35
+ };
36
+ /**
37
+ * 标准 LLM role(OpenAI / DeepSeek / Ollama 等 chat 协议直接接受的四种)。
38
+ * 出口适配器只看到这四种;任何 WellKnownRole 以外的扩展 role 需在出口转译为其中之一。
39
+ */
40
+ export type WellKnownRole = 'system' | 'user' | 'assistant' | 'tool';
41
+ /**
42
+ * 消息 role:标准四种 + 任意扩展字符串。
43
+ *
44
+ * 设计:使用 `WellKnownRole | (string & {})` 模式既保留四种标准 role 的字面量自动补全/收窄,
45
+ * 又允许任意自定义 role(如 `'notice'`、未来可能的 `'event'` / `'observation'` 等)。
46
+ *
47
+ * 约束:自定义 role 仅用于 Aalis 内部存储/检索/渲染;调用 LLM 前必须由 provider 适配器
48
+ * 转译为 WellKnownRole 之一(典型做法:notice → system,并在 content 前加 `[系统通知]` 前缀)。
49
+ */
50
+ export type MessageRole = WellKnownRole | (string & {});
51
+ export interface Message {
52
+ role: MessageRole;
53
+ content: string | null;
54
+ toolCalls?: ToolCall[];
55
+ toolCallId?: string;
56
+ name?: string;
57
+ /**
58
+ * 子分类(与 role 正交的语义维度)。设计目标:
59
+ * - 让所有 role 共用同一个子类入口,避免 system.name / notice.metadata.noticeType / assistant.metadata.kind 三套互不相通的"伪子分类"。
60
+ * - 统一过滤/渲染判断:`m.kind === 'event-marker'` 这种写法跨 role 通用。
61
+ *
62
+ * 约定的语义类(详见 `WELL_KNOWN_KINDS` / `CONTROL_KINDS`):
63
+ * - `'event-marker'` :system 控制类标记(如压缩分隔条),不应进入 LLM 上下文。
64
+ * - `'cross-session-delegation'` :notice 子类——来自另一会话的 agent 委派任务。
65
+ * - `'outbound-image'` :assistant 子类——agent 已发出的图片。
66
+ * - notice 的平台事件类型 :`'poke' | 'group_recall' | 'group_increase' | ...`(取自 OneBot 等适配器)。
67
+ *
68
+ * 第三方插件可定义自己的 kind 字符串,但请避开 `WELL_KNOWN_KINDS` 中已有的语义。
69
+ */
70
+ kind?: string;
71
+ timestamp?: number;
72
+ reasoningContent?: string | null;
73
+ /**
74
+ * 助手输出的有序时间线(含 text / reasoning_text / tool_call)。
75
+ * 仅 assistant 消息可能携带;存在时为 UI 渲染的权威来源,
76
+ * content 与 reasoningContent 应与之保持一致(由生产方在累积时同步写)。
77
+ */
78
+ segments?: ContentSegment[];
79
+ /** 图片列表(base64 data URL 或 HTTP URL),用于多模态 LLM */
80
+ images?: string[];
81
+ /**
82
+ * 音频列表(base64 data URL / 本地路径 / file:// / http(s) URL),
83
+ * 用于支持原生音频输入的 LLM(如 Gemma 3n E 系列、Gemini、GPT-4o-audio)。
84
+ * Provider 实现需自行解析为各 API 期望的格式(base64 / file ref 等)。
85
+ */
86
+ audios?: string[];
87
+ /** 元数据:用于标记消息来源等信息(不会发送给 LLM) */
88
+ metadata?: Record<string, unknown>;
89
+ }
90
+ /**
91
+ * 多模态附件统一载体(v2 新主字段)。
92
+ * 取代 images[] / files[]:所有适配器(OneBot / WebUI / CLI 等)应优先填 attachments,
93
+ * 旧的 images / files 字段保留以兼容老的预处理器与历史代码,框架内的归一化函数会双向同步。
94
+ */
95
+ export interface MessageAttachment {
96
+ /** 媒介类型 */
97
+ kind: 'image' | 'audio' | 'video' | 'file';
98
+ /** 内容:base64 data URL / http(s) URL / file:// URI;下游决定如何解析 */
99
+ data: string;
100
+ /** MIME 类型,尽量提供以便分发 */
101
+ mimeType?: string;
102
+ /** 文件名(如有) */
103
+ name?: string;
104
+ /** 来源标识(platform 内部 ID 等,用于幂等与去重) */
105
+ sourceId?: string;
106
+ /** 字节大小(如已知,便于上限/计费判断) */
107
+ byteSize?: number;
108
+ /** 时长秒(仅音视频,如已知) */
109
+ durationSec?: number;
110
+ /**
111
+ * 出站附件:文本描述(vision/asr 结果)。由生产者(如 send_attachment)填充,
112
+ * 供全局出站归档写入 `[类型: desc | ref:xxx]` 标记,让 memory_recall 能命中。
113
+ */
114
+ description?: string;
115
+ /**
116
+ * 出站附件:稳定引用标识(http(s) url / storage uri)。用于出站归档与历史回放;
117
+ * 缺省时全局归档回退用 data。与平台落盘后改写的 data 解耦,避免归档到临时/不可访问路径。
118
+ */
119
+ ref?: string;
120
+ /**
121
+ * 出站附件:跳过全局归档。用于 history_ref 重发等场景(同一媒体已在档,
122
+ * 避免向量库膨胀与重复入档)。缺省 false。
123
+ */
124
+ skipArchive?: boolean;
125
+ }
126
+ export interface IncomingMessage {
127
+ content: string;
128
+ sessionId: string;
129
+ platform: string;
130
+ userId?: string;
131
+ /** 用户昵称 */
132
+ nickname?: string;
133
+ /**
134
+ * 平台侧消息 ID(如 OneBot 的 message_id)。
135
+ * 由适配器填充;归档插件会写入 metadata.messageId,供"引用回复"反查归档原文以保留图片描述等富信息。
136
+ */
137
+ messageId?: string;
138
+ /**
139
+ * 多模态附件统一载体(唯一入口)。
140
+ * 所有平台适配器(OneBot / WebUI / CLI 等)都应只填此字段;
141
+ * plugin-media 在 preprocess 阶段会为每条 attachment 生成文本描述写入 _attachmentDescriptions。
142
+ */
143
+ attachments?: MessageAttachment[];
144
+ /**
145
+ * 预处理器为各 attachments 生成的文本描述(按 attachments 下标对齐;未识别项为 undefined)。
146
+ * 由 plugin-media 写入。
147
+ */
148
+ _attachmentDescriptions?: Array<string | undefined>;
149
+ /** 会话类型:群聊、私聊、频道等 */
150
+ sessionType?: 'group' | 'private' | 'channel';
151
+ /** 消息来源标识(用于并发隔离:同一 session 不同来源互不打断) */
152
+ source?: string;
153
+ /** 群名称(仅群聊时可用) */
154
+ groupName?: string;
155
+ /** 群组 ID(直接字段,无需从 sessionId 解析) */
156
+ groupId?: string;
157
+ /**
158
+ * 群聊中发送者在群内的角色:owner=群主, admin=管理员, member=普通成员。
159
+ * 仅群聊有效,由适配器从平台消息 sender 字段或主动查询填充。
160
+ */
161
+ senderRole?: 'owner' | 'admin' | 'member';
162
+ /** 群聊中发送者的专属头衔(如 "群主"、"打卡王" 等),仅群聊有效。 */
163
+ senderTitle?: string;
164
+ /**
165
+ * 群聊中 self 账号(机器人自身)在该群内的角色。
166
+ * 适配器应在群消息处理时主动查询(带缓存)并填充,用于让 agent 正确认知自身权限。
167
+ */
168
+ selfRole?: 'owner' | 'admin' | 'member';
169
+ /** 群聊中 self 账号的专属头衔(如有)。 */
170
+ selfTitle?: string;
171
+ /** 引用回复的原消息 */
172
+ replyTo?: {
173
+ messageId: string;
174
+ content?: string;
175
+ userId?: string;
176
+ nickname?: string;
177
+ };
178
+ /** 通知子类型(如 poke、group_upload 等非消息事件) */
179
+ noticeType?: string;
180
+ /**
181
+ * 触发类型(适配器侧设置,下游插件可据此区分主发言者语义):
182
+ * - 'direct' 私聊或单一用户直连(默认语义:userId 是主发言者)
183
+ * - 'immediate' 群聊中被 @/名字主动触发(userId 是主发言者)
184
+ * - 'interval' 群聊中因消息频率/活跃度被动触发(无明确主发言者,userId 仅为最后一条消息发送者)
185
+ * - 'idle' 空闲自动触发(无 userId / 无主发言者)
186
+ * - 'proactive' 由另一会话的 agent 通过工具发起跨会话委派(content 是任务描述而非用户消息)
187
+ * 未设置时下游插件按 'direct' 兼容处理。
188
+ */
189
+ triggerType?: 'direct' | 'immediate' | 'interval' | 'idle' | 'proactive';
190
+ /**
191
+ * 代理身份(与 platform/userId 解耦):当本条消息并非由人类直接发送,而是由 scheduler、
192
+ * idle-trigger、proactive 委派等系统侧触发器投递时,记录"AI 应代谁执行"。
193
+ *
194
+ * 与 platform/userId 的区别:
195
+ * - platform/userId 表示消息的物理来源(路由+发言者标识),写归档、做用户档案/关系;
196
+ * - actor 表示授权身份,agent 构造 ToolCallContext 时优先使用 actor,
197
+ * 从而让 authority 守卫按 actor 的 (platform, userId) 查权限等级。
198
+ *
199
+ * 触发器(如 scheduler)应在创建任务时 snapshot 调用者身份,触发时回填此字段;
200
+ * 不能由 LLM/AI 在工具入参中自由指定,避免提权。
201
+ */
202
+ actor?: {
203
+ platform: string;
204
+ userId: string;
205
+ };
206
+ }
207
+ export interface OutgoingMessage {
208
+ content: string;
209
+ sessionId: string;
210
+ platform?: string;
211
+ reasoningContent?: string;
212
+ /** 助手输出的有序时间线(与 Message.segments 含义一致),存在时为 webui 等消费者顺序渲染的依据 */
213
+ segments?: ContentSegment[];
214
+ /**
215
+ * 助手要附带发送的多模态附件(图片/音频/视频/文件)。
216
+ * 适配器(OneBot / WebUI 等)应优先发结构化 attachments,把远程 URL 主动下载为本地文件后用 file:// 形式发送。
217
+ * 若 attachments 为空但 content 内含 `<image url="...">` 标记,则由适配器解析嵌入式发图(旧路径,仍兼容)。
218
+ */
219
+ attachments?: MessageAttachment[];
220
+ /** 消息来源:agent = AI 回复(可分条延迟发送),其他来源默认立即整条发送 */
221
+ source?: 'agent' | 'system' | 'command';
222
+ /** 本条回复的 LLM 元数据(供 webui 实时展示,不需持久化)。 */
223
+ modelInfo?: {
224
+ provider?: string;
225
+ model?: string;
226
+ promptTokens?: number;
227
+ completionTokens?: number;
228
+ totalTokens?: number;
229
+ elapsedMs?: number;
230
+ };
231
+ }
232
+ /** 流式消息片段 */
233
+ export interface StreamChunkMessage {
234
+ sessionId: string;
235
+ platform?: string;
236
+ contentDelta?: string;
237
+ reasoningDelta?: string;
238
+ /**
239
+ * 工具调用生成进度提示。当 LLM 正在生成 tool_call(不发文本/reasoning)时,
240
+ * provider 每收到一段 tool_calls delta 会通过此字段上报,让 UI 显示「正在生成工具调用」。
241
+ * 仅用于 UI 提示,不影响最终 tool_call segment 的下发。
242
+ */
243
+ toolCallProgress?: {
244
+ index: number;
245
+ name: string;
246
+ charsAccumulated: number;
247
+ };
248
+ done?: boolean;
249
+ /** 当工具调用次数达到上限时为 true,前端可据此提示用户继续 */
250
+ toolLimitReached?: boolean;
251
+ }
252
+ declare module '@aalis/core' {
253
+ interface AalisEvents {
254
+ 'inbound:message': [message: IncomingMessage];
255
+ /**
256
+ * 入站消息已落库(来自 message-archive.archiveIncoming)。无论是否触发 agent 回复都会发出。
257
+ *
258
+ * payload 字段:
259
+ * - `incoming`:原始入参(含 platform/userId/nickname/groupName/triggerType 等会话上下文,未必持久化)
260
+ * - `archivedMessage`:实际写入 memory 的 `Message`(经过预处理器变换后的最终内容,可能与 `incoming.content` 不同)
261
+ */
262
+ 'inbound:message:archived': [data: {
263
+ sessionId: string;
264
+ incoming: IncomingMessage;
265
+ archivedMessage: Message;
266
+ }];
267
+ 'outbound:message': [message: OutgoingMessage];
268
+ 'outbound:stream': [chunk: StreamChunkMessage];
269
+ }
270
+ }
271
+ export type _MessageRef = Message;
272
+ /**
273
+ * 已知的 Message.kind 语义常量。第三方插件可使用新值;本表仅作为框架内的契约。
274
+ *
275
+ * - `EventMarker`:纯 UI/控制标记(如对话压缩分隔条)。LLM 出口与抽取均应排除。
276
+ * - `CrossSessionDelegation`:跨会话委派——另一 agent 通过工具向本会话派发任务。
277
+ * - `OutboundImage`:assistant 已发出的图片占位(content 为 attachment ref 标签)。
278
+ * - `OutboundAudio`:assistant 已发出的语音占位。
279
+ * - `OutboundVideo`:assistant 已发出的视频占位。
280
+ */
281
+ export declare const WellKnownKinds: {
282
+ readonly EventMarker: "event-marker";
283
+ readonly CrossSessionDelegation: "cross-session-delegation";
284
+ readonly OutboundImage: "outbound-image";
285
+ readonly OutboundAudio: "outbound-audio";
286
+ readonly OutboundVideo: "outbound-video";
287
+ };
288
+ export type WellKnownKind = (typeof WellKnownKinds)[keyof typeof WellKnownKinds];
289
+ /**
290
+ * 控制类 kind 集合:这些消息不携带可供模型理解或抽取的语义内容,
291
+ * 仅用于 UI / 内部状态。LLM 出口、信息抽取等流程默认应排除。
292
+ */
293
+ export declare const CONTROL_KINDS: ReadonlyArray<string>;
294
+ /**
295
+ * 把 Aalis 内部 role 转译为 LLM 协议接受的 WellKnownRole。
296
+ * 未知 role 一律回落为 'system',避免任何漏网造成 provider 报错。
297
+ */
298
+ export declare function toLLMRole(role: MessageRole): WellKnownRole;
299
+ /**
300
+ * 准备发往 LLM provider 的消息:把所有自定义 role 转译为 WellKnownRole,
301
+ * 同时给 content 加上可读前缀(kind 优先,其次 role)。
302
+ * provider 适配器应在序列化前调用该函数,确保协议合法。
303
+ *
304
+ * 不修改原对象;返回浅拷贝数组与必要时的消息浅拷贝。
305
+ */
306
+ export declare function prepareLLMMessages<T extends Pick<Message, 'role' | 'content' | 'kind'>>(messages: T[]): T[];
307
+ export { type AttachmentRef, AttachmentRefKind, buildAttachmentRefMatcher, formatAttachmentRef, parseAttachmentRefs, } from './attachment-ref.js';
308
+ export { getMessageName, getSenderLabel, prefixSender } from './identity.js';
309
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js ADDED
@@ -0,0 +1,102 @@
1
+ // ============================================================
2
+ // @aalis/schema-message — 消息层契约
3
+ //
4
+ // 本包持有 Aalis 全部"消息载体"类型,分两层:
5
+ //
6
+ // 1. LLM 协议层(OpenAI/DeepSeek format):
7
+ // - Message:LLM 对话上下文消息(role / content / toolCalls / segments ...)
8
+ // - ContentSegment:助手输出的有序时间线分段(text / reasoning_text / tool_call)
9
+ // - ToolCall:助手消息的 tool_calls 载荷(同为 OpenAI chat 协议字段)
10
+ //
11
+ // 2. 平台适配层(Aalis 边界消息形态):
12
+ // - IncomingMessage:从平台适配器(OneBot / WebUI / CLI 等)流入的原始消息
13
+ // - OutgoingMessage:发往平台的回复消息
14
+ // - StreamChunkMessage:流式回复片段(用于 WebUI 等支持流式的前端)
15
+ //
16
+ // 同时通过 declaration merging 将下列事件注入 `AalisEvents`:
17
+ // - 'inbound:message'
18
+ // - 'inbound:message:archived'
19
+ // - 'outbound:message'
20
+ // - 'outbound:stream'
21
+ //
22
+ // 依赖:@aalis/core(类型锚点)+ @aalis/util-text-normalize(prepareLLMMessages 的 UTF-16 规整)。
23
+ // ============================================================
24
+ import { toWellFormedText } from '@aalis/util-text-normalize';
25
+ // ============================================================
26
+ // LLM 出口工具:自定义 role → WellKnownRole 转译
27
+ // ============================================================
28
+ /**
29
+ * 已知的 Message.kind 语义常量。第三方插件可使用新值;本表仅作为框架内的契约。
30
+ *
31
+ * - `EventMarker`:纯 UI/控制标记(如对话压缩分隔条)。LLM 出口与抽取均应排除。
32
+ * - `CrossSessionDelegation`:跨会话委派——另一 agent 通过工具向本会话派发任务。
33
+ * - `OutboundImage`:assistant 已发出的图片占位(content 为 attachment ref 标签)。
34
+ * - `OutboundAudio`:assistant 已发出的语音占位。
35
+ * - `OutboundVideo`:assistant 已发出的视频占位。
36
+ */
37
+ export const WellKnownKinds = {
38
+ EventMarker: 'event-marker',
39
+ CrossSessionDelegation: 'cross-session-delegation',
40
+ OutboundImage: 'outbound-image',
41
+ OutboundAudio: 'outbound-audio',
42
+ OutboundVideo: 'outbound-video',
43
+ };
44
+ /**
45
+ * 控制类 kind 集合:这些消息不携带可供模型理解或抽取的语义内容,
46
+ * 仅用于 UI / 内部状态。LLM 出口、信息抽取等流程默认应排除。
47
+ */
48
+ export const CONTROL_KINDS = [WellKnownKinds.EventMarker];
49
+ /** 自定义 role 转译为 LLM 接受的 WellKnownRole 的默认映射。 */
50
+ const CUSTOM_ROLE_MAP = {
51
+ notice: 'system',
52
+ };
53
+ /** 自定义 role 在 LLM 视角下的内容前缀(仅当转译为 system 时使用)。 */
54
+ const CUSTOM_ROLE_PREFIX = {
55
+ notice: '[系统通知]',
56
+ };
57
+ /**
58
+ * Kind 级别的内容前缀(优先级高于 role 前缀)。当 message.kind 命中时,
59
+ * 用此前缀替换 role 前缀,从而精确表达子语义(例如「跨会话委派」与普通通知区分)。
60
+ */
61
+ const KIND_PREFIX = {
62
+ [WellKnownKinds.CrossSessionDelegation]: '[跨会话委派]',
63
+ };
64
+ /**
65
+ * 把 Aalis 内部 role 转译为 LLM 协议接受的 WellKnownRole。
66
+ * 未知 role 一律回落为 'system',避免任何漏网造成 provider 报错。
67
+ */
68
+ export function toLLMRole(role) {
69
+ if (role === 'system' || role === 'user' || role === 'assistant' || role === 'tool') {
70
+ return role;
71
+ }
72
+ return CUSTOM_ROLE_MAP[role] ?? 'system';
73
+ }
74
+ /**
75
+ * 准备发往 LLM provider 的消息:把所有自定义 role 转译为 WellKnownRole,
76
+ * 同时给 content 加上可读前缀(kind 优先,其次 role)。
77
+ * provider 适配器应在序列化前调用该函数,确保协议合法。
78
+ *
79
+ * 不修改原对象;返回浅拷贝数组与必要时的消息浅拷贝。
80
+ */
81
+ export function prepareLLMMessages(messages) {
82
+ return messages.map(m => {
83
+ const llmRole = toLLMRole(m.role);
84
+ const prefix = (m.kind && KIND_PREFIX[m.kind]) ?? CUSTOM_ROLE_PREFIX[m.role];
85
+ const needsRoleRewrite = llmRole !== m.role;
86
+ // 边界硬保证:所有发往 LLM 的 string content 先规整成良构 UTF-16(孤代理→�)。杜绝孤代理经
87
+ // JSON.stringify 编成 `\ud83d`(半个代理对)被严格 JSON 解析器(如 DeepSeek 服务端)拒收、
88
+ // 致整条请求 400。对良构内容为 no-op(原样返回、不新建字符串),故不改变现有行为;对任何插件
89
+ // 产生的内容在此唯一出口自动生效,第三方插件无需感知"孤代理"、无需调用任何东西。
90
+ const content = typeof m.content === 'string' ? toWellFormedText(m.content) : m.content;
91
+ const contentChanged = content !== m.content;
92
+ const needsPrefix = !!prefix && typeof content === 'string' && content.length > 0;
93
+ if (!needsRoleRewrite && !needsPrefix && !contentChanged)
94
+ return m;
95
+ const newContent = needsPrefix ? `${prefix} ${content}` : (content ?? null);
96
+ return { ...m, role: llmRole, content: newContent };
97
+ });
98
+ }
99
+ export { AttachmentRefKind, buildAttachmentRefMatcher, formatAttachmentRef, parseAttachmentRefs, } from './attachment-ref.js';
100
+ // ----- 身份标识工具(cleanup-9 从 core 迁入) -----
101
+ export { getMessageName, getSenderLabel, prefixSender } from './identity.js';
102
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@aalis/schema-message",
3
+ "version": "0.5.1",
4
+ "license": "MIT",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/AalisLabs/Aalis.git",
8
+ "directory": "packages/schema-message"
9
+ },
10
+ "author": "Ace Nyan <ace@acenyan.com>",
11
+ "bugs": {
12
+ "url": "https://github.com/AalisLabs/Aalis/issues"
13
+ },
14
+ "homepage": "https://github.com/AalisLabs/Aalis#readme",
15
+ "type": "module",
16
+ "main": "dist/index.js",
17
+ "types": "dist/index.d.ts",
18
+ "files": [
19
+ "dist"
20
+ ],
21
+ "dependencies": {
22
+ "@aalis/util-text-normalize": ">=0.5.1 <1.0.0"
23
+ },
24
+ "devDependencies": {
25
+ "@types/node": "^22.0.0",
26
+ "typescript": "^5.7.0",
27
+ "@aalis/core": "0.10.0"
28
+ },
29
+ "aalis": {
30
+ "types": true
31
+ },
32
+ "peerDependencies": {
33
+ "@aalis/core": ">=0.2.0 <1.0.0"
34
+ },
35
+ "keywords": [
36
+ "aalis",
37
+ "aalis-schema"
38
+ ],
39
+ "scripts": {
40
+ "build": "tsc",
41
+ "dev": "tsc --watch"
42
+ }
43
+ }