Skip to content

Commit 8ebda5d

Browse files
author
linyuan.yang
committed
polish
1 parent 8ade07d commit 8ebda5d

5 files changed

Lines changed: 87 additions & 10 deletions

File tree

packages/scorpio.ai/src/Saver/ConversationCompactor.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ export class ConversationCompactor {
7272
const summary = !result ? '' :
7373
typeof result.content === 'string'
7474
? result.content
75-
: result.content.map(p => p.text ?? '').join('');
75+
: result.content.map(p => (p.type === 'text' ? (p.text ?? '') : '')).join('');
7676

7777
this.logger?.info(`Compact complete, summary length: ${summary.length}`);
7878

packages/scorpio.ai/src/Saver/IAgentSaverService.ts

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,66 @@ export interface ChatToolCall {
2222
type?: string;
2323
}
2424

25+
// ─── Content parts (discriminated union by `type`) ───────────────────────────
26+
27+
/**
28+
* 多模态消息内容 part 的判别符
29+
*
30+
* 该集合由本仓库内部 + 直连 LLM/Channel 时实际出现的取值聚合而来。
31+
* 透传给 SDK 时还可能出现 provider 专属取值,由 `ContentPart` 末尾的兜底分支兼容。
32+
*/
33+
export const ContentPartType = {
34+
Text: 'text',
35+
/** Anthropic / ACP 风格:data + mimeType */
36+
Image: 'image',
37+
/** OpenAI 兼容风格:image_url.url */
38+
ImageUrl: 'image_url',
39+
Audio: 'audio',
40+
} as const;
41+
export type ContentPartType = typeof ContentPartType[keyof typeof ContentPartType];
42+
43+
/** 文本 part,可选携带 Anthropic 的 cache 标记 */
44+
interface TextPart {
45+
type: 'text';
46+
text: string;
47+
cache_control?: any;
48+
}
49+
50+
/** Anthropic / ACP 风格的图像 part(base64 + mimeType) */
51+
interface ImagePart {
52+
type: 'image';
53+
data: string;
54+
mimeType?: string;
55+
}
56+
57+
/** OpenAI 兼容风格的图像 part(dataUrl 或外链) */
58+
interface ImageUrlPart {
59+
type: 'image_url';
60+
image_url: { url: string };
61+
mimeType?: string;
62+
}
63+
64+
/** 音频 part(base64 + mimeType) */
65+
interface AudioPart {
66+
type: 'audio';
67+
data: string;
68+
mimeType?: string;
69+
}
70+
71+
/**
72+
* 多模态消息 part 的判别联合类型。
73+
*
74+
* - 已知形状:`TextPart` / `ImagePart` / `ImageUrlPart` / `AudioPart`
75+
* - 末尾的开放分支用于透传 provider 专属 part(如 `tool_use` / `tool_result` / `thinking` 等),
76+
* 避免类型阻塞迭代;仍保留 `type: string` 以维持运行时一致性。
77+
*/
78+
export type ContentPart =
79+
| TextPart
80+
| ImagePart
81+
| ImageUrlPart
82+
| AudioPart
83+
| { type: string; [key: string]: any };
84+
2585
/**
2686
* 中性化的消息结构,不依赖任何 LLM 框架
2787
*
@@ -32,7 +92,7 @@ export interface ChatToolCall {
3292
*/
3393
export interface ChatMessage {
3494
role: MessageRole;
35-
content: string | Array<{ type: string; text?: string; [key: string]: any }>;
95+
content: string | ContentPart[];
3696
/** AI 消息发起的工具调用列表 */
3797
tool_calls?: ChatToolCall[];
3898
/** Tool 消息关联的 tool_call_id */

packages/scorpio.ai/src/Saver/index.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,18 @@
44
*/
55

66
// ===== 接口 + Symbol Token + 中性类型 =====
7-
export { IAgentSaverService, MessageRole, type StoredMessage, type ChatMessage, type ChatToolCall, type ChatMessageOptions, type MessageContent, type TokenUsage } from "./IAgentSaverService";
7+
export {
8+
IAgentSaverService,
9+
MessageRole,
10+
ContentPartType,
11+
type StoredMessage,
12+
type ChatMessage,
13+
type ChatToolCall,
14+
type ChatMessageOptions,
15+
type MessageContent,
16+
type ContentPart,
17+
type TokenUsage,
18+
} from "./IAgentSaverService";
819

920
// ===== LangChain 转换(仅在 Agent 执行层需要) =====
1021
export { toChatMessage, toBaseMessage, toBaseMessages } from "./messageConverter";

packages/scorpio.ai/src/Utils/contentUtils.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,23 @@
1-
import type { MessageContent } from "../Saver/IAgentSaverService";
1+
import type { ContentPart, MessageContent } from "../Saver/IAgentSaverService";
2+
3+
type TextPart = Extract<ContentPart, { type: 'text' }>;
4+
const isTextPart = (p: ContentPart): p is TextPart => p.type === 'text';
25

36
/** Extract a plain-text representation from MessageContent. */
47
export function contentToString(content: MessageContent): string {
58
if (typeof content === 'string') return content;
69
if (!Array.isArray(content)) return '';
710
return content
8-
.filter(c => c.type === 'text' && c.text)
9-
.map(c => c.text!)
11+
.filter(isTextPart)
12+
.map(p => p.text)
13+
.filter((t): t is string => !!t)
1014
.join('\n');
1115
}
1216

1317
/** Remove empty/whitespace-only text parts from MessageContent. */
1418
export function trimContent(content: MessageContent): MessageContent {
1519
if (typeof content === 'string') return content.trim();
16-
return content.filter(p => p.type !== 'text' || p.text?.trim());
20+
return content.filter(p => !isTextPart(p) || !!p.text?.trim());
1721
}
1822

1923
/** Check if MessageContent is empty. */
@@ -62,8 +66,6 @@ export function detectMediaType(filePath: string): { mimeType: string; category:
6266
return { mimeType, category };
6367
}
6468

65-
export type ContentPart = { type: string; text?: string; [key: string]: any };
66-
6769
export let maxImageSize: number | undefined;
6870

6971
export function setMaxImageSize(size: number | undefined) {

packages/scorpio.ai/src/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,10 @@ export {
166166
type MessageContent,
167167
type TokenUsage,
168168

169+
// ContentPart 判别联合
170+
ContentPartType,
171+
type ContentPart,
172+
169173
// 实现类
170174
AgentMemorySaver,
171175
AgentFileSaver,
@@ -343,6 +347,6 @@ export { MessageDispatcher, MessageType, summarizeMultimodal } from "./User";
343347
// Utils - 工具函数
344348
// ========================================
345349
export { contentToString, trimContent, isEmptyContent, readImageAsDataUrl, readMediaAsContentPart, detectMediaType, setMaxImageSize, resizeImageIfNeeded } from "./Utils/contentUtils";
346-
export type { MediaCategory, ContentPart } from "./Utils/contentUtils";
350+
export type { MediaCategory } from "./Utils/contentUtils";
347351
export { withRetry } from "./Utils/withRetry";
348352
export { UsageTracker, type UsageData } from "./Utils/UsageTracker";

0 commit comments

Comments
 (0)