@icen.ai/ui 0.5.0 → 0.8.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/AGENTS.md +262 -0
- package/README.md +52 -13
- package/dist/base.css +33 -5
- package/dist/behaviors/accordion.d.mts +7 -0
- package/dist/behaviors/accordion.mjs +37 -11
- package/dist/behaviors/ai-chat.d.mts +59 -0
- package/dist/behaviors/ai-chat.mjs +580 -0
- package/dist/behaviors/ai-composer.d.mts +137 -0
- package/dist/behaviors/ai-composer.mjs +2114 -0
- package/dist/behaviors/ai-core.d.mts +156 -0
- package/dist/behaviors/ai-core.mjs +504 -0
- package/dist/behaviors/ai-diff.d.mts +53 -0
- package/dist/behaviors/ai-diff.mjs +413 -0
- package/dist/behaviors/ai-panel.d.mts +101 -0
- package/dist/behaviors/ai-panel.mjs +1087 -0
- package/dist/behaviors/ai-provider.d.mts +198 -0
- package/dist/behaviors/ai-provider.mjs +1061 -0
- package/dist/behaviors/ai-tool.d.mts +26 -0
- package/dist/behaviors/ai-tool.mjs +2173 -0
- package/dist/behaviors/ai-tools.d.mts +84 -0
- package/dist/behaviors/ai-tools.mjs +1762 -0
- package/dist/behaviors/back-top.d.mts +20 -0
- package/dist/behaviors/back-top.mjs +64 -32
- package/dist/behaviors/carousel.d.mts +7 -0
- package/dist/behaviors/carousel.mjs +72 -27
- package/dist/behaviors/charts.d.mts +296 -0
- package/dist/behaviors/charts.mjs +1364 -440
- package/dist/behaviors/command-palette.d.mts +42 -0
- package/dist/behaviors/command-palette.mjs +167 -27
- package/dist/behaviors/context-menu.d.mts +53 -0
- package/dist/behaviors/context-menu.mjs +127 -39
- package/dist/behaviors/controls.d.mts +59 -0
- package/dist/behaviors/controls.mjs +601 -0
- package/dist/behaviors/copy.d.mts +7 -0
- package/dist/behaviors/copy.mjs +41 -15
- package/dist/behaviors/datatable.d.mts +90 -0
- package/dist/behaviors/datatable.mjs +279 -53
- package/dist/behaviors/date-picker.d.mts +4 -0
- package/dist/behaviors/date-picker.mjs +276 -69
- package/dist/behaviors/dropdown.d.mts +8 -0
- package/dist/behaviors/dropdown.mjs +171 -28
- package/dist/behaviors/events.d.mts +330 -0
- package/dist/behaviors/events.mjs +208 -0
- package/dist/behaviors/input.d.mts +4 -0
- package/dist/behaviors/input.mjs +52 -32
- package/dist/behaviors/modal.d.mts +30 -0
- package/dist/behaviors/modal.mjs +87 -16
- package/dist/behaviors/nav.d.mts +7 -0
- package/dist/behaviors/nav.mjs +57 -15
- package/dist/behaviors/notification.d.mts +188 -0
- package/dist/behaviors/notification.mjs +274 -140
- package/dist/behaviors/popover.d.mts +82 -0
- package/dist/behaviors/popover.mjs +92 -5
- package/dist/behaviors/select.d.mts +6 -0
- package/dist/behaviors/select.mjs +287 -45
- package/dist/behaviors/sidebar.d.mts +7 -0
- package/dist/behaviors/sidebar.mjs +44 -13
- package/dist/behaviors/slider.d.mts +7 -0
- package/dist/behaviors/slider.mjs +55 -43
- package/dist/behaviors/split-pane.d.mts +8 -0
- package/dist/behaviors/split-pane.mjs +70 -29
- package/dist/behaviors/tabs.d.mts +9 -0
- package/dist/behaviors/tabs.mjs +102 -26
- package/dist/behaviors/tag-input.d.mts +7 -0
- package/dist/behaviors/tag-input.mjs +66 -17
- package/dist/behaviors/theme.d.mts +36 -0
- package/dist/behaviors/theme.mjs +51 -5
- package/dist/behaviors/toast.d.mts +48 -0
- package/dist/behaviors/toast.mjs +98 -25
- package/dist/behaviors/tree.d.mts +7 -0
- package/dist/behaviors/tree.mjs +39 -15
- package/dist/behaviors/upload.d.mts +4 -0
- package/dist/behaviors/upload.mjs +79 -44
- package/dist/cli.mjs +1 -1
- package/dist/components/ai-chat.css +1037 -0
- package/dist/components/ai-chat.mjs +3 -0
- package/dist/components/ai-composer.mjs +3 -0
- package/dist/components/ai-context.mjs +2 -0
- package/dist/components/ai-diff.css +311 -0
- package/dist/components/ai-diff.mjs +3 -0
- package/dist/components/ai-files.mjs +3 -0
- package/dist/components/ai-message.mjs +3 -0
- package/dist/components/ai-panel.css +682 -0
- package/dist/components/ai-reasoning.mjs +3 -0
- package/dist/components/ai-subagent.mjs +2 -0
- package/dist/components/ai-todo.mjs +2 -0
- package/dist/components/ai-tool-call.mjs +2 -0
- package/dist/components/ai-tool.css +532 -0
- package/dist/components/ai-usage.mjs +3 -0
- package/dist/components/badge.css +5 -9
- package/dist/components/breadcrumb.css +0 -36
- package/dist/components/btn.css +4 -7
- package/dist/components/card.css +3 -6
- package/dist/components/chart-bar.css +10 -0
- package/dist/components/chart-calendar.mjs +4 -0
- package/dist/components/chart-gauge.css +1 -1
- package/dist/components/chart-heatmap.css +40 -0
- package/dist/components/chart-pie.css +0 -6
- package/dist/components/chart-scatter.css +24 -0
- package/dist/components/chart-scatter.mjs +4 -0
- package/dist/components/charts.css +87 -2
- package/dist/components/charts.mjs +1 -0
- package/dist/components/command-palette.css +2 -2
- package/dist/components/content.css +11 -25
- package/dist/components/copy.css +8 -8
- package/dist/components/datatable.css +5 -9
- package/dist/components/date-picker.css +4 -8
- package/dist/components/empty.css +0 -4
- package/dist/components/feedback.css +10 -17
- package/dist/components/form.css +13 -14
- package/dist/components/input.css +2 -2
- package/dist/components/layout.css +36 -36
- package/dist/components/media.css +12 -8
- package/dist/components/menu.css +2 -2
- package/dist/components/modal.css +1 -1
- package/dist/components/nav.css +11 -6
- package/dist/components/notification.css +27 -28
- package/dist/components/pagination.css +4 -2
- package/dist/components/pagination.mjs +1 -0
- package/dist/components/panel.css +16 -16
- package/dist/components/pill.css +15 -15
- package/dist/components/rating.mjs +1 -0
- package/dist/components/segmented.css +14 -6
- package/dist/components/segmented.mjs +1 -0
- package/dist/components/select.css +79 -24
- package/dist/components/sidebar.css +1 -1
- package/dist/components/split-pane.css +0 -1
- package/dist/components/stat.css +14 -18
- package/dist/components/steps.css +15 -5
- package/dist/components/steps.mjs +1 -0
- package/dist/components/switch.css +11 -13
- package/dist/components/switch.mjs +1 -0
- package/dist/components/table.css +46 -47
- package/dist/components/table.mjs +1 -0
- package/dist/components/tabs.css +57 -45
- package/dist/components/tag-input.css +5 -10
- package/dist/components/toast.css +16 -13
- package/dist/components/tooltip.css +2 -2
- package/dist/components/upload.css +0 -15
- package/dist/index.d.mts +39 -0
- package/dist/index.mjs +11243 -3312
- package/dist/registry.json +147 -9
- package/dist/retro-effects.css +23 -32
- package/dist/tokens/colors.css +4 -1
- package/dist/tokens/retro-effects.css +23 -32
- package/dist/tokens/style-profiles.css +24 -2
- package/dist/tokens/typography.css +2 -0
- package/dist/tokens.css +30 -3
- package/dist/ui.css +3420 -645
- package/docs/spec/ai-native.md +662 -0
- package/package.json +15 -5
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
type AiStatus = 'pending' | 'running' | 'streaming' | 'approval' | 'done' | 'error' | 'cancelled';
|
|
2
|
+
/** 状态机七态(规格 §1 的唯一语言)——集群共享(切 is-* 类 / 校验 wire status 用) */
|
|
3
|
+
declare const AI_STATUSES: readonly AiStatus[];
|
|
4
|
+
/** 中文状态标签:等待/执行中/流式中/待确认/完成/失败/已取消 */
|
|
5
|
+
declare function aiStatusLabel(s: AiStatus): string;
|
|
6
|
+
interface AiUsage {
|
|
7
|
+
input?: number;
|
|
8
|
+
output?: number;
|
|
9
|
+
cacheRead?: number;
|
|
10
|
+
cacheWrite?: number;
|
|
11
|
+
reasoning?: number;
|
|
12
|
+
total?: number;
|
|
13
|
+
}
|
|
14
|
+
/** 六键用量求和(非破坏性:不动入参,返回新对象);语义对齐 ai-provider 的同名私有实现,集群共用 */
|
|
15
|
+
declare function addUsage(sum: AiUsage, u: AiUsage): AiUsage;
|
|
16
|
+
type AiKindTint = 'accent' | 'success' | 'warning' | 'error' | 'info' | 'muted';
|
|
17
|
+
interface AiKindDef {
|
|
18
|
+
/** 一行中文名(tool-call 卡片的标题区展示) */
|
|
19
|
+
label: string;
|
|
20
|
+
/** 内联 SVG 字符串(lucide 风格 24×24 stroke);渲染时务必经 svgIcon() 消毒 */
|
|
21
|
+
icon: string;
|
|
22
|
+
/** 图标/状态点着色槽,默认 accent */
|
|
23
|
+
tint?: AiKindTint;
|
|
24
|
+
/** 从 input 提取一行摘要(路径/命令等);无信息时返回 '' */
|
|
25
|
+
summarize?: (input: unknown) => string;
|
|
26
|
+
}
|
|
27
|
+
/** tool-call 归一化模型(subagent 的嵌套活动流复用同一模型——递归即继承) */
|
|
28
|
+
interface AiToolCallModel {
|
|
29
|
+
id: string;
|
|
30
|
+
name: string;
|
|
31
|
+
kind: string;
|
|
32
|
+
status: AiStatus;
|
|
33
|
+
input?: unknown;
|
|
34
|
+
output?: unknown;
|
|
35
|
+
errorText?: string;
|
|
36
|
+
approval?: {
|
|
37
|
+
reason?: string;
|
|
38
|
+
};
|
|
39
|
+
durationMs?: number;
|
|
40
|
+
usage?: AiUsage;
|
|
41
|
+
/** subagent 嵌套活动流 */
|
|
42
|
+
activities?: AiToolCallModel[];
|
|
43
|
+
}
|
|
44
|
+
/** 注册(或覆盖)一个 kind;name 大小写不敏感。第三方扩展入口。 */
|
|
45
|
+
declare function registerAiKind(name: string, def: AiKindDef): void;
|
|
46
|
+
/** 取 kind 定义;未注册回退内置 'note'。 */
|
|
47
|
+
declare function getAiKind(name: string): AiKindDef;
|
|
48
|
+
/** 从 tool 名推断 kind:Bash/Shell→shell、Read→read、mcp__*→mcp 等。 */
|
|
49
|
+
declare function inferKind(toolName: string): string;
|
|
50
|
+
/**
|
|
51
|
+
* 归一化 tool-call:
|
|
52
|
+
* OpenAI tool_calls 项(function.name / function.arguments JSON 串)
|
|
53
|
+
* Anthropic tool_use block(input)/ tool_result block(content / is_error)
|
|
54
|
+
* AI SDK tool-* part(toolCallId / toolName / input / output)
|
|
55
|
+
* 素朴对象(id / name / kind / status / input / output / errorText),字符串视为 name
|
|
56
|
+
*/
|
|
57
|
+
declare function normalizeToolCall(raw: unknown): AiToolCallModel;
|
|
58
|
+
/**
|
|
59
|
+
* 归一化用量:
|
|
60
|
+
* OpenAI usage(prompt_tokens / completion_tokens / prompt_tokens_details.cached_tokens /
|
|
61
|
+
* completion_tokens_details.reasoning_tokens / total_tokens)
|
|
62
|
+
* Anthropic usage(input_tokens / output_tokens / cache_read_input_tokens /
|
|
63
|
+
* cache_creation_input_tokens)
|
|
64
|
+
* DeepSeek usage(顶层 prompt_cache_hit_tokens → cacheRead)
|
|
65
|
+
* Kimi usage(顶层 cached_tokens → cacheRead;prompt_tokens_details.cache_write_tokens → cacheWrite)
|
|
66
|
+
* Gemini usageMetadata(promptTokenCount / candidatesTokenCount /
|
|
67
|
+
* cachedContentTokenCount / thoughtsTokenCount / totalTokenCount)
|
|
68
|
+
* 素朴对象(input / output / cacheRead / cacheWrite / reasoning / total)
|
|
69
|
+
*/
|
|
70
|
+
declare function normalizeUsage(raw: unknown): AiUsage;
|
|
71
|
+
/** 1234 → "1.2k";999 → "999";2_400_000 → "2.4M" */
|
|
72
|
+
declare function formatTokens(n: number): string;
|
|
73
|
+
/** 1234 → "1.2s";800 → "800ms";65_000 → "1m5s";3_700_000 → "1h2m" */
|
|
74
|
+
declare function formatDuration(ms: number): string;
|
|
75
|
+
interface AiTextPart {
|
|
76
|
+
type: 'text';
|
|
77
|
+
text: string;
|
|
78
|
+
/** 流式状态(AI SDK TextUIPart 对齐);渲染不依赖,消费方可选 */
|
|
79
|
+
state?: 'streaming' | 'done';
|
|
80
|
+
}
|
|
81
|
+
/** 媒体部件族:url 与 data 二选一(data 为无前缀 base64,必须伴随 mimeType) */
|
|
82
|
+
interface AiImagePart {
|
|
83
|
+
type: 'image';
|
|
84
|
+
url?: string;
|
|
85
|
+
data?: string;
|
|
86
|
+
mimeType?: string;
|
|
87
|
+
/** 替代文本 */
|
|
88
|
+
alt?: string;
|
|
89
|
+
}
|
|
90
|
+
interface AiAudioPart {
|
|
91
|
+
type: 'audio';
|
|
92
|
+
url?: string;
|
|
93
|
+
data?: string;
|
|
94
|
+
mimeType?: string;
|
|
95
|
+
}
|
|
96
|
+
interface AiVideoPart {
|
|
97
|
+
type: 'video';
|
|
98
|
+
url?: string;
|
|
99
|
+
data?: string;
|
|
100
|
+
mimeType?: string;
|
|
101
|
+
}
|
|
102
|
+
interface AiFilePart {
|
|
103
|
+
type: 'file';
|
|
104
|
+
url?: string;
|
|
105
|
+
data?: string;
|
|
106
|
+
mimeType?: string;
|
|
107
|
+
/** 文件名(url 槽对 openai 族承载 Files API file_id 时也作展示名) */
|
|
108
|
+
filename?: string;
|
|
109
|
+
}
|
|
110
|
+
type AiMediaPart = AiImagePart | AiAudioPart | AiVideoPart | AiFilePart;
|
|
111
|
+
/** MCP ResourceLink:引用未内联的资源 */
|
|
112
|
+
interface AiResourceLinkPart {
|
|
113
|
+
type: 'resource-link';
|
|
114
|
+
uri: string;
|
|
115
|
+
name?: string;
|
|
116
|
+
mimeType?: string;
|
|
117
|
+
}
|
|
118
|
+
type AiContentPart = AiTextPart | AiMediaPart | AiResourceLinkPart;
|
|
119
|
+
/** 消息与工具回执的内容:字符串(纯文本捷径)或部件数组(多模态) */
|
|
120
|
+
type AiContent = string | AiContentPart[];
|
|
121
|
+
/** 单部件守卫:type 合法且槽位类型正确(text 看 text、resource-link 看 uri、媒体看 url 或 data+mimeType) */
|
|
122
|
+
declare function isAiContentPart(v: unknown): v is AiContentPart;
|
|
123
|
+
/** 渲染地址归一:url 直取;data + mimeType 拼 data URI(base64) */
|
|
124
|
+
declare function aiContentUrl(part: AiContentPart): string | undefined;
|
|
125
|
+
/**
|
|
126
|
+
* 归一化内容部件:OpenAI content parts / Anthropic content blocks /
|
|
127
|
+
* MCP content 数组(含 EmbeddedResource)/ 素朴(本类型族原样通过)。
|
|
128
|
+
* 未知形态剔除;空结果返回 []。
|
|
129
|
+
*/
|
|
130
|
+
declare function normalizeContentParts(raw: unknown): AiContentPart[];
|
|
131
|
+
/** MCP 场景别名(同 normalizeContentParts,可发现性) */
|
|
132
|
+
declare const normalizeMcpContent: typeof normalizeContentParts;
|
|
133
|
+
/** 拼接全部 text 部件(复制按钮 / 不支持多模态的降级传输用) */
|
|
134
|
+
declare function contentToText(content: AiContent): string;
|
|
135
|
+
/**
|
|
136
|
+
* token 估算(无依赖启发式):CJK 字符 ×0.6 + 其余字符 ÷4;
|
|
137
|
+
* 媒体部件给经验值。传 AiUsage 时直接返回归一 total。
|
|
138
|
+
*/
|
|
139
|
+
declare function estimateTokens(content: AiContent | AiUsage): number;
|
|
140
|
+
/**
|
|
141
|
+
* 下一轮上下文估算(上下文环的正确口径,spec §10.4):
|
|
142
|
+
* input + cacheRead + cacheWrite + output(+ 单列的 reasoning)——
|
|
143
|
+
* 本次输出会成为下轮输入的一部分。与 auditor.summary()(累计计费)语义不同。
|
|
144
|
+
*/
|
|
145
|
+
declare function contextEstimate(usage: AiUsage): number;
|
|
146
|
+
/** 极简 DOM 构件:createElement + 可选 className + 可选 textContent —— 集群共用,免四处 createElement 三连 */
|
|
147
|
+
declare function h<K extends keyof HTMLElementTagNameMap>(tag: K, className?: string, text?: string): HTMLElementTagNameMap[K];
|
|
148
|
+
/**
|
|
149
|
+
* 把内联 SVG 字符串解析为消毒后的 SVGElement:
|
|
150
|
+
* - 剥全部 on* 事件属性;剔除 script / foreignObject 子树
|
|
151
|
+
* - 剔除 javascript: 协议的 href / xlink:href(纵深防御)
|
|
152
|
+
* - SSR(无 document / DOMParser)返回 null
|
|
153
|
+
*/
|
|
154
|
+
declare function svgIcon(svg: string): SVGElement | null;
|
|
155
|
+
|
|
156
|
+
export { AI_STATUSES, type AiAudioPart, type AiContent, type AiContentPart, type AiFilePart, type AiImagePart, type AiKindDef, type AiKindTint, type AiMediaPart, type AiResourceLinkPart, type AiStatus, type AiTextPart, type AiToolCallModel, type AiUsage, type AiVideoPart, addUsage, aiContentUrl, aiStatusLabel, contentToText, contextEstimate, estimateTokens, formatDuration, formatTokens, getAiKind, h, inferKind, isAiContentPart, normalizeContentParts, normalizeMcpContent, normalizeToolCall, normalizeUsage, registerAiKind, svgIcon };
|
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
// src/behaviors/ai-core.ts
|
|
2
|
+
var AI_STATUSES = [
|
|
3
|
+
"pending",
|
|
4
|
+
"running",
|
|
5
|
+
"streaming",
|
|
6
|
+
"approval",
|
|
7
|
+
"done",
|
|
8
|
+
"error",
|
|
9
|
+
"cancelled"
|
|
10
|
+
];
|
|
11
|
+
function isAiStatus(v) {
|
|
12
|
+
return AI_STATUSES.includes(v);
|
|
13
|
+
}
|
|
14
|
+
function aiStatusLabel(s) {
|
|
15
|
+
const labels = {
|
|
16
|
+
pending: "\u7B49\u5F85",
|
|
17
|
+
running: "\u6267\u884C\u4E2D",
|
|
18
|
+
streaming: "\u6D41\u5F0F\u4E2D",
|
|
19
|
+
approval: "\u5F85\u786E\u8BA4",
|
|
20
|
+
done: "\u5B8C\u6210",
|
|
21
|
+
error: "\u5931\u8D25",
|
|
22
|
+
cancelled: "\u5DF2\u53D6\u6D88"
|
|
23
|
+
};
|
|
24
|
+
return labels[s] ?? s;
|
|
25
|
+
}
|
|
26
|
+
var USAGE_KEYS = ["input", "output", "cacheRead", "cacheWrite", "reasoning", "total"];
|
|
27
|
+
function addUsage(sum2, u) {
|
|
28
|
+
const out = { ...sum2 };
|
|
29
|
+
for (const k of USAGE_KEYS) {
|
|
30
|
+
const v = u[k];
|
|
31
|
+
if (typeof v === "number" && Number.isFinite(v)) out[k] = (out[k] ?? 0) + v;
|
|
32
|
+
}
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
35
|
+
var svg = (body) => `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${body}</svg>`;
|
|
36
|
+
var ICON_TERMINAL = svg('<path d="m4 17 6-6-6-6"/><path d="M12 19h8"/>');
|
|
37
|
+
var ICON_FILE = svg(
|
|
38
|
+
'<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/>'
|
|
39
|
+
);
|
|
40
|
+
var ICON_FILE_PEN = svg(
|
|
41
|
+
'<path d="M12.5 22H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8l5 5v3.5"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M19 6.5 16.5 4"/><path d="M21.378 16.626a1 1 0 0 0-3.004-3.004l-4.01 4.012a2 2 0 0 0-.506.854l-.837 2.87a.5.5 0 0 0 .62.62l2.87-.837a2 2 0 0 0 .854-.506z"/>'
|
|
42
|
+
);
|
|
43
|
+
var ICON_FILE_PLUS = svg(
|
|
44
|
+
'<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M9 15h6"/><path d="M12 12v6"/>'
|
|
45
|
+
);
|
|
46
|
+
var ICON_TRASH = svg(
|
|
47
|
+
'<path d="M3 6h18"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><path d="M10 11v6"/><path d="M14 11v6"/>'
|
|
48
|
+
);
|
|
49
|
+
var ICON_SEARCH = svg('<circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/>');
|
|
50
|
+
var ICON_FOLDER_SEARCH = svg(
|
|
51
|
+
'<path d="M11 20H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H20a2 2 0 0 1 2 2v4"/><circle cx="17" cy="17" r="3"/><path d="m21 21-1.9-1.9"/>'
|
|
52
|
+
);
|
|
53
|
+
var ICON_SCAN_SEARCH = svg(
|
|
54
|
+
'<path d="M3 7V5a2 2 0 0 1 2-2h2"/><path d="M17 3h2a2 2 0 0 1 2 2v2"/><path d="M21 17v2a2 2 0 0 1-2 2h-2"/><path d="M7 21H5a2 2 0 0 1-2-2v-2"/><circle cx="12" cy="12" r="3"/><path d="m16 16-1.9-1.9"/>'
|
|
55
|
+
);
|
|
56
|
+
var ICON_GLOBE = svg(
|
|
57
|
+
'<circle cx="12" cy="12" r="10"/><path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20"/><path d="M2 12h20"/>'
|
|
58
|
+
);
|
|
59
|
+
var ICON_DOWNLOAD = svg(
|
|
60
|
+
'<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="m7 10 5 5 5-5"/><path d="M12 15V3"/>'
|
|
61
|
+
);
|
|
62
|
+
var ICON_BLOCKS = svg(
|
|
63
|
+
'<rect width="7" height="7" x="4" y="4" rx="1"/><rect width="7" height="7" x="13" y="4" rx="1"/><rect width="7" height="7" x="4" y="13" rx="1"/><rect width="7" height="7" x="13" y="13" rx="1"/>'
|
|
64
|
+
);
|
|
65
|
+
var ICON_ZAP = svg(
|
|
66
|
+
'<path d="M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z"/>'
|
|
67
|
+
);
|
|
68
|
+
var ICON_LIST = svg(
|
|
69
|
+
'<path d="M3 12h.01"/><path d="M3 18h.01"/><path d="M3 6h.01"/><path d="M8 12h13"/><path d="M8 18h13"/><path d="M8 6h13"/>'
|
|
70
|
+
);
|
|
71
|
+
var ICON_LIST_CHECKS = svg(
|
|
72
|
+
'<path d="m3 17 2 2 4-4"/><path d="m3 7 2 2 4-4"/><path d="M13 6h8"/><path d="M13 12h8"/><path d="M13 18h8"/>'
|
|
73
|
+
);
|
|
74
|
+
var ICON_BOT = svg(
|
|
75
|
+
'<path d="M12 8V4H8"/><rect width="16" height="12" x="4" y="8" rx="2"/><path d="M2 14h2"/><path d="M20 14h2"/><path d="M15 13v2"/><path d="M9 13v2"/>'
|
|
76
|
+
);
|
|
77
|
+
var ICON_CHART = svg(
|
|
78
|
+
'<path d="M3 3v18h18"/><path d="M7 15l4-6 3 4 5-8"/>'
|
|
79
|
+
);
|
|
80
|
+
var ICON_FILE_TEXT = svg(
|
|
81
|
+
'<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="M10 9H8"/><path d="M16 13H8"/><path d="M16 17H8"/>'
|
|
82
|
+
);
|
|
83
|
+
function isObj(v) {
|
|
84
|
+
return typeof v === "object" && v !== null;
|
|
85
|
+
}
|
|
86
|
+
function pickStr(input, keys) {
|
|
87
|
+
if (!isObj(input)) return void 0;
|
|
88
|
+
for (const k of keys) {
|
|
89
|
+
const v = input[k];
|
|
90
|
+
if (typeof v === "string" && v) return v;
|
|
91
|
+
}
|
|
92
|
+
return void 0;
|
|
93
|
+
}
|
|
94
|
+
function pickNum(src, keys) {
|
|
95
|
+
if (!isObj(src)) return void 0;
|
|
96
|
+
for (const k of keys) {
|
|
97
|
+
const v = src[k];
|
|
98
|
+
if (typeof v === "number" && Number.isFinite(v)) return v;
|
|
99
|
+
}
|
|
100
|
+
return void 0;
|
|
101
|
+
}
|
|
102
|
+
function firstLine(s, max = 80) {
|
|
103
|
+
const line = s.split("\n", 1)[0]?.trim() ?? "";
|
|
104
|
+
return line.length > max ? `${line.slice(0, max)}\u2026` : line;
|
|
105
|
+
}
|
|
106
|
+
function filePathOf(input) {
|
|
107
|
+
return pickStr(input, ["file_path", "path", "file", "filename"]);
|
|
108
|
+
}
|
|
109
|
+
var summarizePath = (input) => filePathOf(input);
|
|
110
|
+
function summarizeRead(input) {
|
|
111
|
+
const p = filePathOf(input);
|
|
112
|
+
if (!p) return void 0;
|
|
113
|
+
const offset = pickNum(input, ["offset", "start_line"]);
|
|
114
|
+
if (offset == null) return p;
|
|
115
|
+
const limit = pickNum(input, ["limit", "end_line"]);
|
|
116
|
+
return limit != null ? `${p}:${offset}-${offset + limit}` : `${p}:${offset}`;
|
|
117
|
+
}
|
|
118
|
+
function summarizeShell(input) {
|
|
119
|
+
const cmd = typeof input === "string" ? input : pickStr(input, ["command", "cmd", "script"]);
|
|
120
|
+
return cmd ? firstLine(cmd) : void 0;
|
|
121
|
+
}
|
|
122
|
+
function summarizeGrep(input) {
|
|
123
|
+
const pattern = pickStr(input, ["pattern", "regex", "query"]);
|
|
124
|
+
const p = filePathOf(input);
|
|
125
|
+
if (pattern) return p ? `${firstLine(pattern, 40)} \xB7 ${p}` : firstLine(pattern);
|
|
126
|
+
return p;
|
|
127
|
+
}
|
|
128
|
+
var sum = (fn) => {
|
|
129
|
+
return (input) => fn(input) ?? "";
|
|
130
|
+
};
|
|
131
|
+
var BUILTIN_KINDS = {
|
|
132
|
+
shell: { label: "\u547D\u4EE4", icon: ICON_TERMINAL, tint: "accent", summarize: sum(summarizeShell) },
|
|
133
|
+
read: { label: "\u8BFB\u53D6", icon: ICON_FILE, tint: "info", summarize: sum(summarizeRead) },
|
|
134
|
+
edit: { label: "\u7F16\u8F91", icon: ICON_FILE_PEN, tint: "warning", summarize: sum(summarizePath) },
|
|
135
|
+
write: { label: "\u5199\u5165", icon: ICON_FILE_PLUS, tint: "warning", summarize: sum(summarizePath) },
|
|
136
|
+
rm: { label: "\u5220\u9664", icon: ICON_TRASH, tint: "error", summarize: sum(summarizePath) },
|
|
137
|
+
grep: { label: "\u641C\u7D22", icon: ICON_SEARCH, tint: "info", summarize: sum(summarizeGrep) },
|
|
138
|
+
glob: { label: "\u5339\u914D", icon: ICON_FOLDER_SEARCH, tint: "info", summarize: sum((i) => pickStr(i, ["pattern", "glob", "path"])) },
|
|
139
|
+
browser: { label: "\u6D4F\u89C8", icon: ICON_GLOBE, tint: "accent", summarize: sum((i) => pickStr(i, ["url", "uri", "target"])) },
|
|
140
|
+
search: { label: "\u68C0\u7D22", icon: ICON_SCAN_SEARCH, tint: "accent", summarize: sum((i) => pickStr(i, ["query", "q", "pattern"])) },
|
|
141
|
+
fetch: { label: "\u8BF7\u6C42", icon: ICON_DOWNLOAD, tint: "info", summarize: sum((i) => pickStr(i, ["url", "uri", "endpoint"])) },
|
|
142
|
+
mcp: { label: "MCP", icon: ICON_BLOCKS, tint: "muted", summarize: sum((i) => pickStr(i, ["tool", "method", "name"])) },
|
|
143
|
+
skill: { label: "\u6280\u80FD", icon: ICON_ZAP, tint: "accent", summarize: sum((i) => pickStr(i, ["skill", "name", "command"])) },
|
|
144
|
+
todo: {
|
|
145
|
+
label: "\u5F85\u529E",
|
|
146
|
+
icon: ICON_LIST,
|
|
147
|
+
tint: "info",
|
|
148
|
+
summarize: sum((input) => {
|
|
149
|
+
const items = input && typeof input === "object" && Array.isArray(input.items) ? input.items : Array.isArray(input) ? input : [];
|
|
150
|
+
const done = items.filter((it) => it?.status === "done").length;
|
|
151
|
+
return items.length > 0 ? `${done}/${items.length} \u5B8C\u6210` : "";
|
|
152
|
+
})
|
|
153
|
+
},
|
|
154
|
+
plan: { label: "\u8BA1\u5212", icon: ICON_LIST_CHECKS, tint: "info" },
|
|
155
|
+
subagent: { label: "\u5B50\u4EE3\u7406", icon: ICON_BOT, tint: "accent", summarize: sum((i) => pickStr(i, ["task", "description", "prompt", "message", "subagent_type"])) },
|
|
156
|
+
note: { label: "\u7B14\u8BB0", icon: ICON_FILE_TEXT, tint: "muted" },
|
|
157
|
+
chart: { label: "\u56FE\u8868", icon: ICON_CHART, tint: "info", summarize: sum((i) => pickStr(i, ["title", "name"])) }
|
|
158
|
+
};
|
|
159
|
+
var kindRegistry = new Map(Object.entries(BUILTIN_KINDS));
|
|
160
|
+
function registerAiKind(name, def) {
|
|
161
|
+
const key = name.trim().toLowerCase();
|
|
162
|
+
if (key) kindRegistry.set(key, def);
|
|
163
|
+
}
|
|
164
|
+
function getAiKind(name) {
|
|
165
|
+
return kindRegistry.get(name.trim().toLowerCase()) ?? BUILTIN_KINDS.note;
|
|
166
|
+
}
|
|
167
|
+
var KIND_RULES = [
|
|
168
|
+
[/^mcp__/, "mcp"],
|
|
169
|
+
[/chart|plot|visuali[sz]e|render_chart/, "chart"],
|
|
170
|
+
[/bash|shell|terminal|powershell|zsh|\bcmd\b|exec/, "shell"],
|
|
171
|
+
[/todo/, "todo"],
|
|
172
|
+
[/sub.?agent|^task$|dispatch|spawn|agent/, "subagent"],
|
|
173
|
+
[/plan/, "plan"],
|
|
174
|
+
[/skill/, "skill"],
|
|
175
|
+
[/note|memo/, "note"],
|
|
176
|
+
[/grep|ripgrep|\brg\b|search.*(code|file|text)/, "grep"],
|
|
177
|
+
[/glob|find|\bls\b|list.?file/, "glob"],
|
|
178
|
+
[/edit|patch|replace|modify|rename/, "edit"],
|
|
179
|
+
[/write|create|new.?file/, "write"],
|
|
180
|
+
[/delete|remove|\brm\b|trash|unlink/, "rm"],
|
|
181
|
+
[/read|view|open|cat/, "read"],
|
|
182
|
+
[/fetch|curl|wget|http|download|request/, "fetch"],
|
|
183
|
+
[/browse|navigate|\bclick\b|page/, "browser"],
|
|
184
|
+
[/search|query|lookup/, "search"]
|
|
185
|
+
];
|
|
186
|
+
function inferKind(toolName) {
|
|
187
|
+
const n = toolName.trim().toLowerCase();
|
|
188
|
+
if (!n) return "note";
|
|
189
|
+
for (const [re, kind] of KIND_RULES) {
|
|
190
|
+
if (re.test(n)) return kind;
|
|
191
|
+
}
|
|
192
|
+
return "note";
|
|
193
|
+
}
|
|
194
|
+
var WIRE_STATUS = {
|
|
195
|
+
"tool-input-streaming": "pending",
|
|
196
|
+
// AI SDK
|
|
197
|
+
"tool-input-available": "pending",
|
|
198
|
+
// AI SDK
|
|
199
|
+
"tool-output-available": "done",
|
|
200
|
+
// AI SDK
|
|
201
|
+
"tool-output-error": "error",
|
|
202
|
+
// AI SDK
|
|
203
|
+
"approval-requested": "approval",
|
|
204
|
+
// AI SDK
|
|
205
|
+
"tool_call_result": "done"
|
|
206
|
+
// AG-UI TOOL_CALL_RESULT
|
|
207
|
+
};
|
|
208
|
+
function str(v) {
|
|
209
|
+
return typeof v === "string" && v ? v : void 0;
|
|
210
|
+
}
|
|
211
|
+
function normalizeToolCall(raw) {
|
|
212
|
+
if (typeof raw === "string") {
|
|
213
|
+
return { id: "", name: raw, kind: inferKind(raw), status: "pending" };
|
|
214
|
+
}
|
|
215
|
+
const src = isObj(raw) ? raw : {};
|
|
216
|
+
const fn = isObj(src.function) ? src.function : void 0;
|
|
217
|
+
const type = str(src.type) ?? "";
|
|
218
|
+
const name = str(src.name) ?? str(src.toolName) ?? (fn ? str(fn.name) : void 0) ?? "";
|
|
219
|
+
const id = str(src.id) ?? str(src.toolCallId) ?? str(src.tool_use_id) ?? "";
|
|
220
|
+
let input = src.input ?? src.args ?? src.arguments ?? (fn ? fn.arguments : void 0);
|
|
221
|
+
if (typeof input === "string" && input) {
|
|
222
|
+
try {
|
|
223
|
+
input = JSON.parse(input);
|
|
224
|
+
} catch {
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
let output = src.output ?? src.result;
|
|
228
|
+
if (output === void 0 && type === "tool_result") output = src.content;
|
|
229
|
+
let errorText = str(src.errorText) ?? (typeof src.error === "string" ? src.error : void 0);
|
|
230
|
+
let status = WIRE_STATUS[type];
|
|
231
|
+
if (!status && type === "tool_use") status = "pending";
|
|
232
|
+
if (!status && type === "tool_result") status = src.is_error ? "error" : "done";
|
|
233
|
+
const rawStatus = str(src.status) ?? "";
|
|
234
|
+
if (!status && isAiStatus(rawStatus)) status = rawStatus;
|
|
235
|
+
if (!status) status = errorText ? "error" : output !== void 0 ? "done" : "pending";
|
|
236
|
+
if (status === "error" && !errorText && typeof output === "string") errorText = output;
|
|
237
|
+
const approval = isObj(src.approval) ? { reason: str(src.approval.reason) } : void 0;
|
|
238
|
+
const durationMs = pickNum(src, ["durationMs", "duration"]);
|
|
239
|
+
const usageRaw = normalizeUsage(src.usage);
|
|
240
|
+
const usage = Object.keys(usageRaw).length > 0 ? usageRaw : void 0;
|
|
241
|
+
const activities = Array.isArray(src.activities) ? src.activities.map((a) => normalizeToolCall(a)) : void 0;
|
|
242
|
+
return {
|
|
243
|
+
id,
|
|
244
|
+
name,
|
|
245
|
+
kind: str(src.kind) ?? inferKind(name),
|
|
246
|
+
status,
|
|
247
|
+
input,
|
|
248
|
+
output,
|
|
249
|
+
errorText,
|
|
250
|
+
approval,
|
|
251
|
+
durationMs,
|
|
252
|
+
usage,
|
|
253
|
+
activities
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
function normalizeUsage(raw) {
|
|
257
|
+
if (!isObj(raw)) return {};
|
|
258
|
+
const usage = {};
|
|
259
|
+
const input = pickNum(raw, ["input"]) ?? pickNum(raw, ["prompt_tokens"]) ?? pickNum(raw, ["promptTokenCount"]) ?? pickNum(raw, ["input_tokens"]);
|
|
260
|
+
const output = pickNum(raw, ["output"]) ?? pickNum(raw, ["completion_tokens"]) ?? pickNum(raw, ["candidatesTokenCount"]) ?? pickNum(raw, ["output_tokens"]);
|
|
261
|
+
const cacheRead = pickNum(raw, ["cacheRead", "cache_read"]) ?? pickNum(raw, ["cache_read_input_tokens"]) ?? pickNum(raw, ["prompt_cache_hit_tokens"]) ?? pickNum(raw, ["cached_tokens"]) ?? pickNum(raw, ["cachedContentTokenCount"]) ?? (isObj(raw.prompt_tokens_details) ? pickNum(raw.prompt_tokens_details, ["cached_tokens"]) : void 0);
|
|
262
|
+
const cacheWrite = pickNum(raw, ["cacheWrite", "cache_write"]) ?? pickNum(raw, ["cache_creation_input_tokens"]) ?? pickNum(raw, ["cacheCreationTokenCount"]) ?? (isObj(raw.prompt_tokens_details) ? pickNum(raw.prompt_tokens_details, ["cache_write_tokens"]) : void 0);
|
|
263
|
+
const reasoning = pickNum(raw, ["reasoning"]) ?? pickNum(raw, ["thoughtsTokenCount"]) ?? (isObj(raw.completion_tokens_details) ? pickNum(raw.completion_tokens_details, ["reasoning_tokens"]) : void 0);
|
|
264
|
+
const total = pickNum(raw, ["total"]) ?? pickNum(raw, ["total_tokens"]) ?? pickNum(raw, ["totalTokenCount"]) ?? (input != null || output != null || cacheRead != null || cacheWrite != null || reasoning != null ? (input ?? 0) + (output ?? 0) + (cacheRead ?? 0) + (cacheWrite ?? 0) + (reasoning ?? 0) : void 0);
|
|
265
|
+
if (input != null) usage.input = input;
|
|
266
|
+
if (output != null) usage.output = output;
|
|
267
|
+
if (cacheRead != null) usage.cacheRead = cacheRead;
|
|
268
|
+
if (cacheWrite != null) usage.cacheWrite = cacheWrite;
|
|
269
|
+
if (reasoning != null) usage.reasoning = reasoning;
|
|
270
|
+
if (total != null) usage.total = total;
|
|
271
|
+
return usage;
|
|
272
|
+
}
|
|
273
|
+
function trim1(v) {
|
|
274
|
+
const s = (Math.round(v * 10) / 10).toFixed(1);
|
|
275
|
+
return s.endsWith(".0") ? s.slice(0, -2) : s;
|
|
276
|
+
}
|
|
277
|
+
function formatTokens(n) {
|
|
278
|
+
if (!Number.isFinite(n)) return "0";
|
|
279
|
+
const abs = Math.abs(n);
|
|
280
|
+
if (abs < 1e3) return String(Math.round(n));
|
|
281
|
+
if (abs < 1e6) return `${trim1(n / 1e3)}k`;
|
|
282
|
+
return `${trim1(n / 1e6)}M`;
|
|
283
|
+
}
|
|
284
|
+
function formatDuration(ms) {
|
|
285
|
+
if (!Number.isFinite(ms) || ms <= 0) return "0ms";
|
|
286
|
+
if (ms < 1e3) return `${Math.round(ms)}ms`;
|
|
287
|
+
if (ms < 6e4) return `${trim1(ms / 1e3)}s`;
|
|
288
|
+
const m = Math.floor(ms / 6e4);
|
|
289
|
+
const s = Math.round(ms % 6e4 / 1e3);
|
|
290
|
+
if (ms < 36e5) return s > 0 ? `${m}m${s}s` : `${m}m`;
|
|
291
|
+
const h2 = Math.floor(ms / 36e5);
|
|
292
|
+
const rm = Math.round(ms % 36e5 / 6e4);
|
|
293
|
+
return rm > 0 ? `${h2}h${rm}m` : `${h2}h`;
|
|
294
|
+
}
|
|
295
|
+
var PART_TYPES = /* @__PURE__ */ new Set(["text", "image", "audio", "video", "file", "resource-link"]);
|
|
296
|
+
function isAiContentPart(v) {
|
|
297
|
+
if (!isObj(v)) return false;
|
|
298
|
+
const t = v.type;
|
|
299
|
+
if (typeof t !== "string" || !PART_TYPES.has(t)) return false;
|
|
300
|
+
if (t === "text") return typeof v.text === "string";
|
|
301
|
+
if (t === "resource-link") return typeof v.uri === "string";
|
|
302
|
+
return typeof v.url === "string" || typeof v.data === "string" && typeof v.mimeType === "string";
|
|
303
|
+
}
|
|
304
|
+
function aiContentUrl(part) {
|
|
305
|
+
if (part.type === "text" || part.type === "resource-link") return void 0;
|
|
306
|
+
if (typeof part.url === "string" && part.url) return part.url;
|
|
307
|
+
if (typeof part.data === "string" && part.data && typeof part.mimeType === "string") {
|
|
308
|
+
return `data:${part.mimeType};base64,${part.data}`;
|
|
309
|
+
}
|
|
310
|
+
return void 0;
|
|
311
|
+
}
|
|
312
|
+
function splitDataUri(uri) {
|
|
313
|
+
const m = /^data:([^;,]+);base64,(.*)$/s.exec(uri);
|
|
314
|
+
return m ? { mimeType: m[1] ?? "", data: m[2] ?? "" } : void 0;
|
|
315
|
+
}
|
|
316
|
+
function partFromOpenAi(item) {
|
|
317
|
+
const type = str(item.type) ?? "";
|
|
318
|
+
if (type === "text" && typeof item.text === "string") return { type: "text", text: item.text };
|
|
319
|
+
if (type === "image_url") {
|
|
320
|
+
const iu = isObj(item.image_url) ? item.image_url : void 0;
|
|
321
|
+
const url = iu ? str(iu.url) : void 0;
|
|
322
|
+
if (!url) return void 0;
|
|
323
|
+
const inline = splitDataUri(url);
|
|
324
|
+
return inline ? { type: "image", data: inline.data, mimeType: inline.mimeType } : { type: "image", url };
|
|
325
|
+
}
|
|
326
|
+
if (type === "input_audio") {
|
|
327
|
+
const ia = isObj(item.input_audio) ? item.input_audio : void 0;
|
|
328
|
+
const data = ia ? str(ia.data) : void 0;
|
|
329
|
+
const fmt = ia ? str(ia.format) : void 0;
|
|
330
|
+
if (!data) return void 0;
|
|
331
|
+
return { type: "audio", data, mimeType: fmt ? `audio/${fmt}` : "audio/wav" };
|
|
332
|
+
}
|
|
333
|
+
if (type === "file") {
|
|
334
|
+
const f = isObj(item.file) ? item.file : void 0;
|
|
335
|
+
const fileId = f ? str(f.file_id) ?? str(f.filename) : void 0;
|
|
336
|
+
if (!fileId) return void 0;
|
|
337
|
+
return { type: "file", url: fileId, filename: str(f?.filename) };
|
|
338
|
+
}
|
|
339
|
+
return void 0;
|
|
340
|
+
}
|
|
341
|
+
function partFromAnthropicSource(mediaType, src, fallbackMime) {
|
|
342
|
+
if (!isObj(src)) return void 0;
|
|
343
|
+
if (str(src.type) === "url") {
|
|
344
|
+
const url = str(src.url);
|
|
345
|
+
return url ? { type: mediaType, url } : void 0;
|
|
346
|
+
}
|
|
347
|
+
const data = str(src.data);
|
|
348
|
+
const mimeType = str(src.media_type) ?? fallbackMime;
|
|
349
|
+
return data ? { type: mediaType, data, mimeType } : void 0;
|
|
350
|
+
}
|
|
351
|
+
function partFromAnthropic(block) {
|
|
352
|
+
const type = str(block.type) ?? "";
|
|
353
|
+
if (type === "text" && typeof block.text === "string") return { type: "text", text: block.text };
|
|
354
|
+
if (type === "image") return partFromAnthropicSource("image", block.source, "image/png");
|
|
355
|
+
if (type === "audio") return partFromAnthropicSource("audio", block.source, "audio/wav");
|
|
356
|
+
if (type === "document") return partFromAnthropicSource("file", block.source, "application/pdf");
|
|
357
|
+
return void 0;
|
|
358
|
+
}
|
|
359
|
+
function partFromMcp(item) {
|
|
360
|
+
const type = str(item.type) ?? "";
|
|
361
|
+
if (type === "text" && typeof item.text === "string") return { type: "text", text: item.text };
|
|
362
|
+
if (type === "image" || type === "audio") {
|
|
363
|
+
const data = str(item.data);
|
|
364
|
+
const mimeType = str(item.mimeType) ?? (type === "image" ? "image/png" : "audio/wav");
|
|
365
|
+
return data ? { type, data, mimeType } : void 0;
|
|
366
|
+
}
|
|
367
|
+
if (type === "resource_link") {
|
|
368
|
+
const uri = str(item.uri) ?? str(item.url);
|
|
369
|
+
return uri ? { type: "resource-link", uri, name: str(item.name), mimeType: str(item.mimeType) } : void 0;
|
|
370
|
+
}
|
|
371
|
+
if (type === "resource") {
|
|
372
|
+
const res = isObj(item.resource) ? item.resource : void 0;
|
|
373
|
+
if (!res) return void 0;
|
|
374
|
+
const uri = str(res.uri);
|
|
375
|
+
const mimeType = str(res.mimeType) ?? "";
|
|
376
|
+
const text = str(res.text);
|
|
377
|
+
if (text) return { type: "text", text };
|
|
378
|
+
const blob = str(res.blob);
|
|
379
|
+
if (blob) {
|
|
380
|
+
if (mimeType.startsWith("image/")) return { type: "image", data: blob, mimeType };
|
|
381
|
+
if (mimeType.startsWith("audio/")) return { type: "audio", data: blob, mimeType };
|
|
382
|
+
if (mimeType.startsWith("video/")) return { type: "video", data: blob, mimeType };
|
|
383
|
+
return { type: "file", data: blob, mimeType: mimeType || "application/octet-stream", filename: uri?.split("/").pop() };
|
|
384
|
+
}
|
|
385
|
+
return uri ? { type: "resource-link", uri, mimeType: mimeType || void 0 } : void 0;
|
|
386
|
+
}
|
|
387
|
+
return void 0;
|
|
388
|
+
}
|
|
389
|
+
function normalizeContentParts(raw) {
|
|
390
|
+
if (typeof raw === "string") return raw ? [{ type: "text", text: raw }] : [];
|
|
391
|
+
if (!Array.isArray(raw)) return [];
|
|
392
|
+
const out = [];
|
|
393
|
+
for (const item of raw) {
|
|
394
|
+
if (isAiContentPart(item)) {
|
|
395
|
+
out.push(item);
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
if (!isObj(item)) continue;
|
|
399
|
+
const part = partFromOpenAi(item) ?? partFromAnthropic(item) ?? partFromMcp(item);
|
|
400
|
+
if (part) out.push(part);
|
|
401
|
+
}
|
|
402
|
+
return out;
|
|
403
|
+
}
|
|
404
|
+
var normalizeMcpContent = normalizeContentParts;
|
|
405
|
+
function contentToText(content) {
|
|
406
|
+
if (typeof content === "string") return content;
|
|
407
|
+
return content.filter((p) => p.type === "text").map((p) => p.text).join("");
|
|
408
|
+
}
|
|
409
|
+
var CJK_RE = /[\u2e80-\u9fff\uf900-\ufaff\u3040-\u30ff\uac00-\ud7af]/g;
|
|
410
|
+
function estimatePartTokens(part) {
|
|
411
|
+
switch (part.type) {
|
|
412
|
+
case "text":
|
|
413
|
+
return estimateTextTokens(part.text);
|
|
414
|
+
case "image":
|
|
415
|
+
return 1e3;
|
|
416
|
+
/* 经验值:中等分辨率视觉 token */
|
|
417
|
+
case "audio":
|
|
418
|
+
return 1500;
|
|
419
|
+
/* 无时长信息的中短音频 */
|
|
420
|
+
case "video":
|
|
421
|
+
return 6e3;
|
|
422
|
+
/* 前瞻位经验值 */
|
|
423
|
+
case "file":
|
|
424
|
+
return 500;
|
|
425
|
+
/* 文档内容未知 */
|
|
426
|
+
case "resource-link":
|
|
427
|
+
return estimateTextTokens(part.name ?? part.uri);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
function estimateTextTokens(text) {
|
|
431
|
+
if (!text) return 0;
|
|
432
|
+
const cjk = text.match(CJK_RE)?.length ?? 0;
|
|
433
|
+
const rest = text.length - cjk;
|
|
434
|
+
return Math.max(1, Math.round(cjk * 0.6 + rest / 4));
|
|
435
|
+
}
|
|
436
|
+
function estimateTokens(content) {
|
|
437
|
+
if (typeof content === "string") return estimateTextTokens(content);
|
|
438
|
+
if (Array.isArray(content)) return content.reduce((acc, p) => acc + estimatePartTokens(p), 0);
|
|
439
|
+
const u = normalizeUsage(content);
|
|
440
|
+
return u.total ?? (u.input ?? 0) + (u.output ?? 0) + (u.cacheRead ?? 0) + (u.cacheWrite ?? 0) + (u.reasoning ?? 0);
|
|
441
|
+
}
|
|
442
|
+
function contextEstimate(usage) {
|
|
443
|
+
const u = normalizeUsage(usage);
|
|
444
|
+
return (u.input ?? 0) + (u.cacheRead ?? 0) + (u.cacheWrite ?? 0) + (u.output ?? 0) + (u.reasoning ?? 0);
|
|
445
|
+
}
|
|
446
|
+
function h(tag, className, text) {
|
|
447
|
+
const node = document.createElement(tag);
|
|
448
|
+
if (className) node.className = className;
|
|
449
|
+
if (text != null) node.textContent = text;
|
|
450
|
+
return node;
|
|
451
|
+
}
|
|
452
|
+
function svgIcon(svg2) {
|
|
453
|
+
if (typeof document === "undefined" || typeof DOMParser === "undefined") return null;
|
|
454
|
+
let parsed;
|
|
455
|
+
try {
|
|
456
|
+
parsed = new DOMParser().parseFromString(svg2, "image/svg+xml");
|
|
457
|
+
} catch {
|
|
458
|
+
return null;
|
|
459
|
+
}
|
|
460
|
+
const root = parsed.documentElement;
|
|
461
|
+
if (!root || root.tagName.toLowerCase() !== "svg" || parsed.querySelector("parsererror")) {
|
|
462
|
+
return null;
|
|
463
|
+
}
|
|
464
|
+
const sanitize = (el) => {
|
|
465
|
+
for (const attr of Array.from(el.attributes)) {
|
|
466
|
+
const name = attr.name.toLowerCase();
|
|
467
|
+
if (name.startsWith("on")) el.removeAttribute(attr.name);
|
|
468
|
+
if ((name === "href" || name === "xlink:href") && attr.value.trim().toLowerCase().startsWith("javascript:")) {
|
|
469
|
+
el.removeAttribute(attr.name);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
for (const child of Array.from(el.children)) {
|
|
473
|
+
const tag = child.tagName.toLowerCase();
|
|
474
|
+
if (tag === "script" || tag === "foreignobject") {
|
|
475
|
+
child.remove();
|
|
476
|
+
continue;
|
|
477
|
+
}
|
|
478
|
+
sanitize(child);
|
|
479
|
+
}
|
|
480
|
+
};
|
|
481
|
+
sanitize(root);
|
|
482
|
+
return document.importNode(root, true);
|
|
483
|
+
}
|
|
484
|
+
export {
|
|
485
|
+
AI_STATUSES,
|
|
486
|
+
addUsage,
|
|
487
|
+
aiContentUrl,
|
|
488
|
+
aiStatusLabel,
|
|
489
|
+
contentToText,
|
|
490
|
+
contextEstimate,
|
|
491
|
+
estimateTokens,
|
|
492
|
+
formatDuration,
|
|
493
|
+
formatTokens,
|
|
494
|
+
getAiKind,
|
|
495
|
+
h,
|
|
496
|
+
inferKind,
|
|
497
|
+
isAiContentPart,
|
|
498
|
+
normalizeContentParts,
|
|
499
|
+
normalizeMcpContent,
|
|
500
|
+
normalizeToolCall,
|
|
501
|
+
normalizeUsage,
|
|
502
|
+
registerAiKind,
|
|
503
|
+
svgIcon
|
|
504
|
+
};
|