@cjhyy/code-shell-web 0.9.43 → 0.9.46

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.
@@ -0,0 +1,10 @@
1
+ import React from "react";
2
+ import type { RemoteModelTraceApi } from "../model-trace-api.js";
3
+ /** Browser-only, read-only explorer shared by the Web workbench and paired mobile client. */
4
+ export declare function ModelTraceExplorer({ api, lang, initialSessionId, sessionTitles, onOpenSession, }: {
5
+ api: RemoteModelTraceApi;
6
+ lang?: "zh" | "en";
7
+ initialSessionId?: string;
8
+ sessionTitles?: Readonly<Record<string, string>>;
9
+ onOpenSession?: (id: string) => void;
10
+ }): React.JSX.Element;
@@ -0,0 +1,490 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React, { useEffect, useMemo, useRef, useState } from "react";
3
+ import { modelTraceCallTypes, modelTraceInputBoundary, presentModelTraceCallType, } from "./model-trace-presentation.js";
4
+ const labels = {
5
+ zh: {
6
+ title: "执行观测",
7
+ description: "按类型筛选同一份活动记录,再按会话查看执行、Prompt、响应、重试和工具事件。",
8
+ refresh: "刷新",
9
+ search: "搜索模型、用途、来源或编号",
10
+ session: "会话 ID",
11
+ model: "模型",
12
+ purpose: "技术用途标识",
13
+ filters: "更多筛选",
14
+ callType: "按类型查看",
15
+ allTypes: "全部调用",
16
+ source: "来源入口",
17
+ technical: "技术信息",
18
+ all: "全部状态",
19
+ running: "运行中",
20
+ completed: "已完成",
21
+ failed: "失败",
22
+ cancelled: "已取消",
23
+ empty: "没有符合条件的会话或独立调用",
24
+ sessions: "会话与独立调用",
25
+ sessionRecord: "会话",
26
+ standalone: "独立调用",
27
+ standaloneExecutions: "独立调用的执行",
28
+ execution: "执行",
29
+ loaded: "已加载调用",
30
+ select: "选择会话或独立调用,查看其中的执行和模型调用",
31
+ calls: "会话内的执行",
32
+ back: "返回",
33
+ next: "下一页",
34
+ previous: "上一页",
35
+ more: "加载更多调用",
36
+ loading: "读取中…",
37
+ input: "运行时输入快照",
38
+ inputHint: "执行时组装的逻辑快照,包含指令、消息历史、工具及参数;实际请求请查看具体尝试。",
39
+ mediaInput: "模型输入与资源信息",
40
+ mediaHint: "展示提交给模型的文本、参数及资源元数据;音频等二进制内容不在此展开。",
41
+ toolInput: "工具输入",
42
+ eventInput: "事件输入",
43
+ eventHint: "这是工具或其他事件的输入,不是模型 Prompt。",
44
+ fullInput: "完整输入 JSON",
45
+ systemPrompt: "系统指令",
46
+ messages: "消息历史与本轮输入",
47
+ tools: "可用工具定义",
48
+ parameters: "调用参数",
49
+ message: "消息",
50
+ userRole: "用户角色消息",
51
+ assistantRole: "助手历史消息",
52
+ systemRole: "系统消息",
53
+ developerRole: "开发者消息",
54
+ toolRole: "工具结果消息",
55
+ roleHint: "角色表示消息在模型输入中的位置;用户角色消息也可能由辅助流程生成。",
56
+ output: "输出",
57
+ attempts: "重试与请求",
58
+ events: "工具与生命周期",
59
+ attempt: "尝试",
60
+ wire: "实际发出的 Provider 请求",
61
+ wireHint: "此尝试提交给 Provider 的请求正文,敏感信息已脱敏,超长内容可能截断。",
62
+ logical: "逻辑输入",
63
+ externalInput: "外部 CLI 入口输入",
64
+ external: "外部 CLI 入口输入,未覆盖 CLI 内部的模型请求",
65
+ missing: "这条调用已过期或不再可用",
66
+ unrecorded: "正文未采集",
67
+ truncated: "正文已截断",
68
+ redacted: "正文已脱敏",
69
+ contentHint: "仅记录元数据。请在 Host 的执行观测设置中开启 Prompt 正文采集;历史内容不会补回。",
70
+ disabled: "Host 已关闭调用记录",
71
+ copy: "复制 JSON",
72
+ copied: "已复制",
73
+ copyFailed: "复制失败,请重试",
74
+ open: "打开原会话",
75
+ cost: "费用未记录",
76
+ costEstimate: "估算费用",
77
+ costPartial: "部分尝试费用未知",
78
+ tokens: "Token",
79
+ duration: "耗时",
80
+ ttft: "首 Token",
81
+ request: "模型调用",
82
+ },
83
+ en: {
84
+ title: "Execution traces",
85
+ description: "Filter the same activity history by type, then browse sessions, executions, prompts, responses, retries and tools.",
86
+ refresh: "Refresh",
87
+ search: "Search model, purpose, source or ID",
88
+ session: "Session ID",
89
+ model: "Model",
90
+ purpose: "Technical purpose",
91
+ filters: "More filters",
92
+ callType: "Browse by type",
93
+ allTypes: "All calls",
94
+ source: "Source entry",
95
+ technical: "Technical information",
96
+ all: "All statuses",
97
+ running: "Running",
98
+ completed: "Completed",
99
+ failed: "Failed",
100
+ cancelled: "Cancelled",
101
+ empty: "No matching sessions or standalone calls",
102
+ sessions: "Sessions and standalone calls",
103
+ sessionRecord: "Session",
104
+ standalone: "Standalone calls",
105
+ standaloneExecutions: "Standalone executions",
106
+ execution: "Execution",
107
+ loaded: "Loaded calls",
108
+ select: "Choose a session or standalone calls to inspect executions and model calls",
109
+ calls: "Executions in session",
110
+ back: "Back",
111
+ next: "Next page",
112
+ previous: "Previous page",
113
+ more: "Load more calls",
114
+ loading: "Loading…",
115
+ input: "Runtime input snapshot",
116
+ inputHint: "A logical snapshot of instructions, message history, tools and parameters. Select an attempt to inspect its outgoing request.",
117
+ mediaInput: "Model input and resource metadata",
118
+ mediaHint: "Text, parameters and resource metadata submitted to the model. Binary audio and other media are not expanded here.",
119
+ toolInput: "Tool input",
120
+ eventInput: "Event input",
121
+ eventHint: "This is input for a tool or another event, not a model Prompt.",
122
+ fullInput: "Full input JSON",
123
+ systemPrompt: "System instructions",
124
+ messages: "Message history and current input",
125
+ tools: "Available tool definitions",
126
+ parameters: "Call parameters",
127
+ message: "Message",
128
+ userRole: "User-role message",
129
+ assistantRole: "Assistant history message",
130
+ systemRole: "System message",
131
+ developerRole: "Developer message",
132
+ toolRole: "Tool result message",
133
+ roleHint: "Roles describe a message’s position in model input. User-role messages may also be generated by helper workflows.",
134
+ output: "Output",
135
+ attempts: "Attempts and requests",
136
+ events: "Tools and lifecycle",
137
+ attempt: "Attempt",
138
+ wire: "Actual outgoing Provider request",
139
+ wireHint: "The request body submitted to the Provider for this attempt, with sensitive information redacted and long content possibly truncated.",
140
+ logical: "Logical input",
141
+ externalInput: "External CLI entry input",
142
+ external: "External CLI entry input; internal model requests are not covered",
143
+ missing: "This call has expired or is unavailable",
144
+ unrecorded: "Content was not recorded",
145
+ truncated: "Content was truncated",
146
+ redacted: "Content was redacted",
147
+ contentHint: "Only metadata is recorded. Enable Prompt content recording in the Host’s execution trace settings. Earlier content cannot be recovered.",
148
+ disabled: "The Host disabled call recording",
149
+ copy: "Copy JSON",
150
+ copied: "Copied",
151
+ copyFailed: "Copy failed. Please retry",
152
+ open: "Open conversation",
153
+ cost: "Cost not recorded",
154
+ costEstimate: "Estimated cost",
155
+ costPartial: "Some attempt costs are unknown",
156
+ tokens: "Tokens",
157
+ duration: "Duration",
158
+ ttft: "First token",
159
+ request: "Model call",
160
+ },
161
+ };
162
+ const css = `
163
+ .cs-trace-explorer{display:flex;flex-direction:column;min-height:0;min-width:0;flex:1;height:auto;color:inherit;font:inherit;background:inherit;overflow:hidden}
164
+ .cs-trace-explorer *{box-sizing:border-box}.cs-trace-explorer header{padding:16px 20px 12px;border-bottom:1px solid #8884;flex-shrink:0}
165
+ .cs-trace-explorer h1{font-size:20px;margin:0}.cs-trace-explorer h2{font-size:14px;margin:0 0 12px}.cs-trace-explorer h3{font-size:13px;margin:12px 0 6px}
166
+ .cs-trace-explorer p{font-size:13px;line-height:1.6;margin:8px 0;overflow-wrap:anywhere}.cs-trace-explorer button,.cs-trace-explorer input,.cs-trace-explorer select{font:inherit;color:inherit;border:1px solid #8885;border-radius:7px;background:transparent;padding:7px 9px;min-width:0}
167
+ .cs-trace-explorer button{cursor:pointer}.cs-trace-explorer button:hover{background:#8882}.cs-trace-explorer button:disabled{opacity:.5;cursor:default}.cs-trace-explorer button:focus-visible{outline:2px solid #4c91ee;outline-offset:2px}
168
+ .cs-trace-explorer button[aria-pressed=true]{background:#4c91ee22;border-color:#4c91ee}.cs-trace-explorer .cs-t-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap}
169
+ .cs-trace-explorer .cs-t-filters{display:grid;grid-template-columns:1fr 1fr 1fr 1fr;gap:8px;margin-top:12px}.cs-trace-explorer .cs-t-filters label{font-size:12px;display:flex;flex-direction:column;gap:4px;min-width:0}
170
+ .cs-trace-explorer .cs-t-search{display:flex;gap:6px;flex-direction:column;margin-top:10px;font-size:12px}.cs-trace-explorer .cs-t-filter-fold{margin:8px 0 0}.cs-trace-explorer .cs-t-grid{min-height:0;flex:1;display:grid;grid-template-columns:minmax(180px,1fr) minmax(190px,1fr) minmax(300px,2fr)}
171
+ .cs-trace-explorer .cs-t-pane{overflow:auto;min-width:0;padding:16px;border-right:1px solid #8884}.cs-trace-explorer .cs-t-item{display:block;width:100%;text-align:left;margin-bottom:8px;overflow-wrap:anywhere;word-break:break-word;font-size:12px}
172
+ .cs-trace-explorer .cs-t-item strong{display:block;font-size:13px;margin-bottom:5px}.cs-trace-explorer .cs-t-muted{opacity:.7;font-size:12px}.cs-trace-explorer .cs-t-error{color:#c95252;white-space:pre-wrap}
173
+ .cs-trace-explorer .cs-t-type{display:inline-block;border:1px solid #8884;border-radius:5px;padding:2px 6px;font-size:12px;line-height:1.5;background:#8881}.cs-trace-explorer .cs-t-type-filter-field{display:flex;flex-direction:column;gap:4px;min-width:0;font-size:12px;margin-top:10px}.cs-trace-explorer .cs-t-type-filters{display:flex;gap:6px;overflow-x:auto;padding:3px;scroll-padding:3px;max-width:100%;overscroll-behavior-x:contain}.cs-trace-explorer .cs-t-type-filters button{flex:0 0 auto;white-space:nowrap;font-size:12px;padding:6px 9px;border-radius:16px}.cs-trace-explorer .cs-t-type-filters button[aria-pressed=true]{font-weight:600;background:#4c91ee33;border-color:#4c91ee}.cs-trace-explorer .cs-t-primary-filters{display:flex;flex-direction:column;min-width:0}.cs-trace-explorer .cs-t-message{margin:12px 0}
174
+ .cs-trace-explorer pre{font:12px/1.6 ui-monospace,monospace;white-space:pre-wrap;overflow-wrap:anywhere;word-break:break-word;max-height:65vh;overflow:auto;padding:12px;border:1px solid #8884;border-radius:8px;background:#8881}
175
+ .cs-trace-explorer details{margin:12px 0}.cs-trace-explorer summary{cursor:pointer;font-size:13px}.cs-trace-explorer .cs-t-back{display:none}.cs-trace-explorer dl{display:grid;grid-template-columns:auto 1fr;gap:6px 12px;font-size:12px;margin:12px 0}.cs-trace-explorer dd{margin:0;overflow-wrap:anywhere}.cs-trace-explorer footer{display:flex;gap:8px;align-items:center;justify-content:space-between;padding-top:12px}
176
+ @media(max-width:1000px){.cs-trace-explorer .cs-t-filters{grid-template-columns:1fr 1fr}.cs-trace-explorer .cs-t-grid{display:flex}.cs-trace-explorer .cs-t-pane{width:100%;border:0;display:none}.cs-trace-explorer .cs-t-pane[data-active=true]{display:block}.cs-trace-explorer .cs-t-back{display:inline-block;margin-bottom:12px}.cs-trace-explorer header{padding:10px;max-height:50%;overflow:auto}.cs-trace-explorer header>p{display:none}.cs-trace-explorer pre{max-height:none}}
177
+ `;
178
+ const errorText = (error) => (error instanceof Error ? error.message : String(error));
179
+ const ms = (start, end) => start !== undefined && end !== undefined
180
+ ? `${Math.max(0, end - start).toLocaleString()} ms`
181
+ : "—";
182
+ /** Browser-only, read-only explorer shared by the Web workbench and paired mobile client. */
183
+ export function ModelTraceExplorer({ api, lang = "zh", initialSessionId, sessionTitles, onOpenSession, }) {
184
+ const l = labels[lang];
185
+ const [search, setSearch] = useState("");
186
+ const [model, setModel] = useState("");
187
+ const [purpose, setPurpose] = useState("");
188
+ const [callType, setCallType] = useState("");
189
+ const [sessionId, setSessionId] = useState(initialSessionId ?? "");
190
+ const [status, setStatus] = useState("");
191
+ const [cursors, setCursors] = useState([undefined]);
192
+ const [pageIndex, setPageIndex] = useState(0);
193
+ const [page, setPage] = useState();
194
+ const [selectedSession, setSelectedSession] = useState();
195
+ const [traceId, setTraceId] = useState();
196
+ const [tracePage, setTracePage] = useState();
197
+ const [traceError, setTraceError] = useState("");
198
+ const [requestId, setRequestId] = useState();
199
+ const [detail, setDetail] = useState();
200
+ const [node, setNode] = useState({
201
+ kind: "request",
202
+ });
203
+ const [stage, setStage] = useState("list");
204
+ const [revision, setRevision] = useState(0);
205
+ const [loading, setLoading] = useState(false);
206
+ const [detailLoading, setDetailLoading] = useState(false);
207
+ const [error, setError] = useState("");
208
+ const [detailError, setDetailError] = useState("");
209
+ const [configHint, setConfigHint] = useState("");
210
+ const [copied, setCopied] = useState("");
211
+ const traceGeneration = useRef(0);
212
+ const sessionsHeading = useRef(null);
213
+ const executionsHeading = useRef(null);
214
+ const [moreLoading, setMoreLoading] = useState(false);
215
+ const filterKey = `${search}\u0000${model}\u0000${purpose}\u0000${callType}\u0000${sessionId}\u0000${status}`;
216
+ useEffect(() => {
217
+ setCursors([undefined]);
218
+ setPageIndex(0);
219
+ setPage(undefined);
220
+ setSelectedSession(undefined);
221
+ setTraceId(undefined);
222
+ setTracePage(undefined);
223
+ setRequestId(undefined);
224
+ setDetail(undefined);
225
+ setDetailError("");
226
+ setTraceError("");
227
+ setStage("list");
228
+ }, [api, filterKey]);
229
+ useEffect(() => {
230
+ let alive = true;
231
+ setLoading(true);
232
+ setError("");
233
+ const timer = setTimeout(() => {
234
+ void api
235
+ .sessions({
236
+ limit: 50,
237
+ cursor: cursors[pageIndex],
238
+ search: search || undefined,
239
+ model: model || undefined,
240
+ purpose: purpose || undefined,
241
+ callType: callType || undefined,
242
+ sessionId: sessionId || undefined,
243
+ status: status ? status : undefined,
244
+ })
245
+ .then((result) => {
246
+ if (alive)
247
+ setPage(result);
248
+ })
249
+ .catch((cause) => {
250
+ if (alive)
251
+ setError(errorText(cause));
252
+ })
253
+ .finally(() => {
254
+ if (alive)
255
+ setLoading(false);
256
+ });
257
+ }, 180);
258
+ return () => {
259
+ alive = false;
260
+ clearTimeout(timer);
261
+ };
262
+ }, [api, filterKey, cursors, pageIndex, revision]);
263
+ useEffect(() => {
264
+ let alive = true;
265
+ void api
266
+ .getConfig()
267
+ .then((config) => {
268
+ if (alive)
269
+ setConfigHint(!config.enabled ? l.disabled : !config.captureContent ? l.contentHint : "");
270
+ })
271
+ .catch((cause) => {
272
+ if (alive)
273
+ setConfigHint(errorText(cause));
274
+ });
275
+ return () => {
276
+ alive = false;
277
+ };
278
+ }, [api, revision, l]);
279
+ const callQuery = () => ({
280
+ search: search || undefined,
281
+ model: model || undefined,
282
+ purpose: purpose || undefined,
283
+ callType: callType || undefined,
284
+ status: status ? status : undefined,
285
+ ...(selectedSession?.sessionId
286
+ ? { sessionId: selectedSession.sessionId }
287
+ : { traceId: selectedSession?.traceId, withoutSession: true }),
288
+ });
289
+ useEffect(() => {
290
+ const generation = ++traceGeneration.current;
291
+ if (!selectedSession)
292
+ return;
293
+ const scopeKey = selectedSession.key;
294
+ setMoreLoading(false);
295
+ setTraceError("");
296
+ setTracePage(undefined);
297
+ void api
298
+ .list({ ...callQuery(), limit: 200 })
299
+ .then((result) => {
300
+ if (generation === traceGeneration.current)
301
+ setTracePage({ ...result, scopeKey });
302
+ })
303
+ .catch((cause) => {
304
+ if (generation === traceGeneration.current)
305
+ setTraceError(errorText(cause));
306
+ });
307
+ return () => {
308
+ traceGeneration.current++;
309
+ };
310
+ }, [api, selectedSession?.key, filterKey, revision]);
311
+ useEffect(() => {
312
+ let alive = true;
313
+ if (!requestId)
314
+ return;
315
+ setDetailLoading(true);
316
+ setDetailError("");
317
+ setDetail((current) => (current?.record.id === requestId ? current : undefined));
318
+ void api
319
+ .get(requestId)
320
+ .then((result) => {
321
+ if (alive)
322
+ setDetail(result);
323
+ })
324
+ .catch((cause) => {
325
+ if (alive)
326
+ setDetailError(errorText(cause));
327
+ })
328
+ .finally(() => {
329
+ if (alive)
330
+ setDetailLoading(false);
331
+ });
332
+ return () => {
333
+ alive = false;
334
+ };
335
+ }, [api, requestId, revision]);
336
+ const groups = useMemo(() => {
337
+ const result = new Map();
338
+ for (const item of tracePage?.items ?? [])
339
+ result.set(item.traceId, [...(result.get(item.traceId) ?? []), item]);
340
+ return [...result];
341
+ }, [tracePage]);
342
+ const visibleDetail = detail &&
343
+ detail.record.id === requestId &&
344
+ detail.record.traceId === traceId &&
345
+ selectedSession &&
346
+ (selectedSession.sessionId
347
+ ? detail.record.sessionId === selectedSession.sessionId
348
+ : detail.record.traceId === selectedSession.traceId && !detail.record.sessionId)
349
+ ? detail
350
+ : undefined;
351
+ const record = visibleDetail?.record;
352
+ const executionsLabel = selectedSession && !selectedSession.sessionId ? l.standaloneExecutions : l.calls;
353
+ const selected = node.kind === "attempt"
354
+ ? record?.attempts.find((attempt) => attempt.id === node.id)
355
+ : node.kind === "event"
356
+ ? visibleDetail?.events.find((event) => event.id === node.id)
357
+ : record;
358
+ const copy = async () => {
359
+ try {
360
+ await navigator.clipboard.writeText(JSON.stringify(selected, null, 2));
361
+ setCopied(l.copied);
362
+ }
363
+ catch {
364
+ setCopied(l.copyFailed);
365
+ }
366
+ };
367
+ const selectRequest = (call) => {
368
+ setRequestId(call.id);
369
+ setTraceId(call.traceId);
370
+ setNode({ kind: "request" });
371
+ setStage("detail");
372
+ setCopied("");
373
+ };
374
+ const more = async () => {
375
+ if (!tracePage?.nextCursor || moreLoading || !selectedSession)
376
+ return;
377
+ const scopeKey = selectedSession.key;
378
+ const generation = traceGeneration.current;
379
+ setMoreLoading(true);
380
+ try {
381
+ const result = await api.list({ ...callQuery(), cursor: tracePage.nextCursor, limit: 200 });
382
+ if (generation === traceGeneration.current)
383
+ setTracePage((current) => ({
384
+ ...result,
385
+ scopeKey,
386
+ items: [...(current?.scopeKey === scopeKey ? current.items : []), ...result.items],
387
+ }));
388
+ }
389
+ catch (cause) {
390
+ if (generation === traceGeneration.current)
391
+ setTraceError(errorText(cause));
392
+ }
393
+ finally {
394
+ if (generation === traceGeneration.current)
395
+ setMoreLoading(false);
396
+ }
397
+ };
398
+ const content = (value, state) => (_jsxs(_Fragment, { children: [state !== "recorded" && (_jsx("p", { className: "cs-t-muted", children: state === "not-recorded"
399
+ ? l.unrecorded
400
+ : state === "truncated"
401
+ ? l.truncated
402
+ : l.redacted })), value !== undefined && (_jsx("pre", { tabIndex: 0, children: typeof value === "string" ? value : JSON.stringify(value, null, 2) }))] }));
403
+ const callTypeBadge = (value) => {
404
+ const type = presentModelTraceCallType(value, lang);
405
+ return (_jsx("span", { className: "cs-t-type", "data-trace-call-type": type.type, title: type.description, children: type.label }));
406
+ };
407
+ const inputBoundary = selected && record ? modelTraceInputBoundary(node.kind, selected, record) : undefined;
408
+ const inputLabel = inputBoundary === "wire"
409
+ ? l.wire
410
+ : inputBoundary === "external"
411
+ ? l.externalInput
412
+ : inputBoundary === "media"
413
+ ? l.mediaInput
414
+ : inputBoundary === "event"
415
+ ? selected?.kind === "tool"
416
+ ? l.toolInput
417
+ : l.eventInput
418
+ : l.input;
419
+ const inputHint = inputBoundary === "wire"
420
+ ? l.wireHint
421
+ : inputBoundary === "external"
422
+ ? l.external
423
+ : inputBoundary === "media"
424
+ ? l.mediaHint
425
+ : inputBoundary === "event"
426
+ ? l.eventHint
427
+ : l.inputHint;
428
+ const logicalInput = inputBoundary === "logical" && selected?.input && typeof selected.input === "object"
429
+ ? selected.input
430
+ : undefined;
431
+ const roleLabel = (message) => {
432
+ const role = message && typeof message === "object" && "role" in message ? message.role : undefined;
433
+ return role === "user"
434
+ ? l.userRole
435
+ : role === "assistant"
436
+ ? l.assistantRole
437
+ : role === "system"
438
+ ? l.systemRole
439
+ : role === "developer"
440
+ ? l.developerRole
441
+ : role === "tool"
442
+ ? l.toolRole
443
+ : typeof role === "string"
444
+ ? `${l.message} · ${role}`
445
+ : l.message;
446
+ };
447
+ return (_jsxs("section", { className: "cs-trace-explorer", "aria-label": l.title, children: [_jsx("style", { children: css }), _jsxs("header", { children: [_jsxs("div", { className: "cs-t-row", children: [_jsx("h1", { children: l.title }), _jsx("button", { type: "button", disabled: loading, onClick: () => setRevision((value) => value + 1), children: l.refresh })] }), _jsx("p", { className: "cs-t-muted", children: l.description }), _jsxs("div", { className: "cs-t-primary-filters", children: [_jsxs("div", { className: "cs-t-type-filter-field", children: [_jsx("span", { className: "cs-t-muted", children: l.callType }), _jsxs("div", { className: "cs-t-type-filters", role: "group", "aria-label": l.callType, children: [_jsx("button", { type: "button", "data-trace-type-filter": "", "aria-pressed": callType === "", onClick: () => setCallType(""), children: l.allTypes }), modelTraceCallTypes.map((value) => {
448
+ const type = presentModelTraceCallType(value, lang);
449
+ return (_jsx("button", { type: "button", "data-trace-type-filter": value, "aria-pressed": callType === value, title: type.description, onClick: () => setCallType(value), children: type.label }, value));
450
+ })] })] }), _jsxs("label", { className: "cs-t-search", children: [_jsx("span", { className: "cs-t-muted", children: l.search }), _jsx("input", { "aria-label": l.search, type: "search", value: search, onChange: (event) => setSearch(event.target.value) })] })] }), _jsxs("details", { className: "cs-t-filter-fold", children: [_jsx("summary", { children: l.filters }), _jsxs("div", { className: "cs-t-filters", children: [_jsxs("label", { children: [l.session, _jsx("input", { value: sessionId, onChange: (event) => setSessionId(event.target.value) })] }), _jsxs("label", { children: [l.model, _jsx("input", { value: model, onChange: (event) => setModel(event.target.value) })] }), _jsxs("label", { children: [l.purpose, _jsx("input", { value: purpose, onChange: (event) => setPurpose(event.target.value) })] }), _jsxs("label", { children: [l.all, _jsxs("select", { value: status, onChange: (event) => setStatus(event.target.value), children: [_jsx("option", { value: "", children: l.all }), ["running", "completed", "failed", "cancelled"].map((value) => (_jsx("option", { value: value, children: l[value] }, value)))] })] })] })] }), configHint && (_jsx("p", { role: "status", className: "cs-t-muted", children: configHint })), error && (_jsx("p", { role: "alert", className: "cs-t-error", children: error }))] }), _jsxs("div", { className: "cs-t-grid", children: [_jsxs("div", { className: "cs-t-pane", "data-active": stage === "list", "aria-label": l.sessions, children: [_jsxs("h2", { ref: sessionsHeading, tabIndex: -1, children: [l.sessions, " \u00B7 ", page?.total ?? "—"] }), loading && _jsx("p", { role: "status", children: l.loading }), !loading && !page?.items.length && _jsx("p", { children: l.empty }), page?.items.map((item) => (_jsxs("button", { type: "button", className: "cs-t-item", "data-trace-session": item.key, "aria-pressed": selectedSession?.key === item.key, onClick: () => {
451
+ if (selectedSession?.key === item.key) {
452
+ setStage("tree");
453
+ return;
454
+ }
455
+ traceGeneration.current++;
456
+ setMoreLoading(false);
457
+ setTracePage(undefined);
458
+ setTraceError("");
459
+ setDetail(undefined);
460
+ setDetailError("");
461
+ setCopied("");
462
+ setSelectedSession(item);
463
+ setTraceId(item.latestTraceId);
464
+ setRequestId(item.latestRequestId);
465
+ setNode({ kind: "request" });
466
+ setStage("tree");
467
+ }, children: [_jsxs("strong", { children: [item.sessionId ? sessionTitles?.[item.sessionId] || l.sessionRecord : l.standalone, " · ", item.callCount, " ", l.request] }), _jsx("span", { className: "cs-t-muted", children: item.sessionId ?? item.traceId }), _jsx("div", { className: "cs-t-row", "data-trace-session-call-types": item.key, children: (item.callTypes?.length ? item.callTypes : ["other"]).map((type) => (_jsx(React.Fragment, { children: callTypeBadge(type) }, type))) }), _jsxs("p", { className: "cs-t-muted", children: [new Date(item.updatedAt).toLocaleString(lang), " \u00B7 ", item.traceCount, " ", l.execution] }), _jsxs("span", { children: [item.sources.join(" · "), " \u00B7 ", item.models.join(" · ")] })] }, item.key))), _jsxs("footer", { children: [_jsx("button", { type: "button", disabled: pageIndex === 0 || loading, onClick: () => setPageIndex((value) => value - 1), children: l.previous }), _jsx("span", { children: pageIndex + 1 }), _jsx("button", { type: "button", disabled: !page?.nextCursor || loading, onClick: () => {
468
+ setCursors((current) => [...current.slice(0, pageIndex + 1), page?.nextCursor]);
469
+ setPageIndex((value) => value + 1);
470
+ }, children: l.next })] })] }), _jsxs("div", { className: "cs-t-pane", "data-active": stage === "tree", "aria-label": executionsLabel, children: [_jsx("button", { type: "button", className: "cs-t-back", onClick: () => {
471
+ setStage("list");
472
+ requestAnimationFrame(() => sessionsHeading.current?.focus());
473
+ }, children: l.back }), _jsx("h2", { ref: executionsHeading, tabIndex: -1, children: executionsLabel }), !selectedSession ? (_jsx("p", { children: l.select })) : (_jsxs(_Fragment, { children: [_jsx("p", { className: "cs-t-muted", children: selectedSession.sessionId ?? selectedSession.traceId }), traceError && (_jsxs("div", { role: "alert", children: [_jsx("p", { className: "cs-t-error", children: traceError }), _jsx("button", { type: "button", onClick: () => setRevision((value) => value + 1), children: l.refresh })] })), !tracePage && !traceError && _jsx("p", { role: "status", children: l.loading }), tracePage && (_jsxs("p", { className: "cs-t-muted", children: [l.loaded, ": ", tracePage.items.length, " / ", tracePage.total] })), groups.map(([executionId, calls]) => (_jsxs("details", { open: true, "data-trace-group": executionId, children: [_jsxs("summary", { children: [l.execution, " \u00B7 ", new Date(calls[0].startedAt).toLocaleString(lang)] }), _jsx("p", { className: "cs-t-muted", children: executionId }), calls.map((call) => (_jsxs("div", { children: [_jsxs("button", { type: "button", className: "cs-t-item", "data-trace-request": call.id, "aria-pressed": requestId === call.id && node.kind === "request", onClick: () => selectRequest(call), children: [_jsx("strong", { children: call.model }), callTypeBadge(call.callType), _jsx("p", { className: "cs-t-muted", children: presentModelTraceCallType(call.callType, lang).description }), l.source, ": ", call.source, " \u00B7 ", l[call.status], " \u00B7", " ", ms(call.startedAt, call.endedAt)] }), record?.id === call.id &&
474
+ record.attempts.map((attempt) => (_jsxs("button", { type: "button", className: "cs-t-item", "aria-pressed": node.kind === "attempt" && node.id === attempt.id, onClick: () => {
475
+ setNode({ kind: "attempt", id: attempt.id });
476
+ setStage("detail");
477
+ }, children: [l.attempt, " ", attempt.number, " \u00B7 ", attempt.httpStatus ?? "—", " \u00B7", " ", l[attempt.status]] }, attempt.id)))] }, call.id)))] }, executionId))), tracePage?.nextCursor && (_jsx("button", { type: "button", disabled: moreLoading, onClick: () => void more(), children: l.more })), _jsx("h3", { children: l.events }), visibleDetail?.events
478
+ .filter((event) => event.traceId === traceId)
479
+ .map((event) => (_jsxs("button", { type: "button", className: "cs-t-item", "aria-pressed": node.kind === "event" && node.id === event.id, onClick: () => {
480
+ setNode({ kind: "event", id: event.id });
481
+ setStage("detail");
482
+ }, children: [event.kind, " \u00B7 ", event.name, " \u00B7 ", event.status] }, event.id)))] }))] }), _jsxs("div", { className: "cs-t-pane", "data-active": stage === "detail", "aria-label": l.request, children: [_jsx("button", { type: "button", className: "cs-t-back", onClick: () => {
483
+ setStage("tree");
484
+ requestAnimationFrame(() => executionsHeading.current?.focus());
485
+ }, children: l.back }), detailError && (_jsx("p", { role: "alert", className: "cs-t-error", children: detailError })), detailLoading && _jsx("p", { role: "status", children: l.loading }), detail === null && _jsx("p", { children: l.missing }), selected ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "cs-t-row", children: [_jsx("h2", { children: node.kind === "event" ? selected.name : record?.model }), _jsx("button", { type: "button", onClick: () => void copy(), children: l.copy }), record?.sessionId && onOpenSession && (_jsx("button", { type: "button", onClick: () => onOpenSession(record.sessionId), children: l.open }))] }), copied && _jsx("p", { role: "status", children: copied }), record && node.kind !== "event" && (_jsxs(_Fragment, { children: [callTypeBadge(record.callType), _jsx("p", { className: "cs-t-muted", children: presentModelTraceCallType(record.callType, lang).description }), _jsxs("p", { className: "cs-t-muted", children: [l.source, ": ", record.source] }), _jsxs("details", { children: [_jsx("summary", { children: l.technical }), _jsxs("dl", { children: [_jsx("dt", { children: l.purpose }), _jsx("dd", { children: record.purpose }), _jsx("dt", { children: l.session }), _jsx("dd", { children: record.sessionId ?? "—" }), _jsx("dt", { children: l.execution }), _jsx("dd", { children: record.runId ?? record.traceId })] })] })] })), _jsxs("dl", { children: [_jsx("dt", { children: "ID" }), _jsx("dd", { children: selected.id }), _jsx("dt", { children: l.duration }), _jsx("dd", { children: ms(selected.startedAt, selected.endedAt) }), _jsx("dt", { children: l.ttft }), _jsx("dd", { children: "firstTokenAt" in selected ? ms(selected.startedAt, selected.firstTokenAt) : "—" }), _jsx("dt", { children: l.tokens }), _jsx("dd", { children: "usage" in selected && selected.usage
486
+ ? `${selected.usage.promptTokens} / ${selected.usage.completionTokens}`
487
+ : "—" })] }), _jsx("p", { className: "cs-t-muted", children: "estimatedCostUsd" in selected && typeof selected.estimatedCostUsd === "number"
488
+ ? `${l.costEstimate}: $${selected.estimatedCostUsd.toFixed(6)}${"unknownCostAttempts" in selected && selected.unknownCostAttempts ? ` · ${l.costPartial}` : ""}`
489
+ : l.cost }), selected.error && _jsx("p", { className: "cs-t-error", children: selected.error }), _jsxs("details", { open: true, children: [_jsx("summary", { children: inputLabel }), _jsx("p", { className: "cs-t-muted", children: inputHint }), logicalInput ? (_jsxs(_Fragment, { children: [content(undefined, selected.inputState), logicalInput.systemPrompt !== undefined && (_jsxs(_Fragment, { children: [_jsx("h3", { children: l.systemPrompt }), content(logicalInput.systemPrompt, "recorded")] })), Array.isArray(logicalInput.messages) && (_jsxs(_Fragment, { children: [_jsx("h3", { children: l.messages }), _jsx("p", { className: "cs-t-muted", children: l.roleHint }), logicalInput.messages.map((message, index) => (_jsxs("div", { className: "cs-t-message", children: [_jsxs("h3", { children: [index + 1, ". ", roleLabel(message)] }), content(message, "recorded")] }, index)))] })), logicalInput.tools !== undefined && (_jsxs("details", { children: [_jsx("summary", { children: l.tools }), content(logicalInput.tools, "recorded")] })), logicalInput.parameters !== undefined && (_jsxs("details", { children: [_jsx("summary", { children: l.parameters }), content(logicalInput.parameters, "recorded")] })), _jsxs("details", { children: [_jsx("summary", { children: l.fullInput }), content(selected.input, "recorded")] })] })) : (content(selected.input, selected.inputState))] }, `${selected.id}:input`), _jsxs("details", { open: true, children: [_jsx("summary", { children: l.output }), content(selected.output, selected.outputState)] }, `${selected.id}:output`), node.kind === "request" && record && (_jsxs("details", { children: [_jsxs("summary", { children: [l.attempts, " (", record.attempts.length, ")"] }), record.attempts.map((attempt) => (_jsxs("button", { type: "button", className: "cs-t-item", onClick: () => setNode({ kind: "attempt", id: attempt.id }), children: [l.attempt, " ", attempt.number, " \u00B7 ", attempt.status, " \u00B7", " ", ms(attempt.startedAt, attempt.endedAt)] }, attempt.id)))] }))] })) : (!detailLoading && _jsx("p", { children: l.select }))] })] })] }));
490
+ }
@@ -0,0 +1,11 @@
1
+ import type { ModelTraceAttempt, ModelTraceCallType, ModelTraceEvent, ModelTraceRecord } from "../model-trace-api.js";
2
+ export type { ModelTraceCallType } from "../model-trace-api.js";
3
+ export declare const modelTraceCallTypes: readonly ["execution", "task", "panel", "summary", "context", "title", "goal", "memory", "suggestion", "speech", "transcription", "audio", "external", "other"];
4
+ /** Classification belongs to the Host; clients never guess intent from Prompt text. */
5
+ export declare function presentModelTraceCallType(value: unknown, lang: "zh" | "en"): {
6
+ type: ModelTraceCallType;
7
+ label: "运行 Prompt" | "子任务 Prompt" | "Panel 调用" | "摘要 / 压缩 Prompt" | "上下文整理 Prompt" | "标题生成 Prompt" | "目标检查 Prompt" | "记忆整理 Prompt" | "后续建议 Prompt" | "语音合成" | "语音转写" | "音频处理" | "外部 CLI 入口" | "其他调用" | "Execution Prompt" | "Subtask Prompt" | "Panel call" | "Summary and compaction Prompt" | "Context preparation Prompt" | "Title generation Prompt" | "Goal check Prompt" | "Memory maintenance Prompt" | "Follow-up suggestion Prompt" | "Speech synthesis" | "Speech transcription" | "Audio processing" | "External CLI entry" | "Other call";
8
+ description: "会话执行时组装的模型输入" | "委派给子任务的模型输入" | "由 Panel 发起的模型调用" | "生成摘要或压缩会话历史" | "整理本次执行所需的上下文" | "生成会话或任务标题" | "检查目标的进展与完成情况" | "提取、整理或更新记忆" | "生成会话的后续操作建议" | "将文本转换为语音" | "将语音转换为文字" | "对音频执行模型推理" | "提交给外部 CLI 的入口输入" | "调用未提供可识别的类型" | "Model input assembled for a session execution" | "Model input for a delegated task" | "A model call initiated by a Panel" | "Summarize or compact session history" | "Prepare context for an execution" | "Generate a session or task title" | "Check progress toward a goal" | "Extract, organize or update memory" | "Suggest next steps for a session" | "Convert text to speech" | "Convert speech to text" | "Run model inference on audio" | "Input submitted to an external CLI" | "The call has no recognized type";
9
+ };
10
+ /** The record keeps a logical snapshot even when a transport attempt contains provider wire. */
11
+ export declare function modelTraceInputBoundary(kind: "request" | "attempt" | "event", selected: ModelTraceRecord | ModelTraceAttempt | ModelTraceEvent, record: ModelTraceRecord): "logical" | "wire" | "external" | "media" | "event";
@@ -0,0 +1,73 @@
1
+ export const modelTraceCallTypes = [
2
+ "execution",
3
+ "task",
4
+ "panel",
5
+ "summary",
6
+ "context",
7
+ "title",
8
+ "goal",
9
+ "memory",
10
+ "suggestion",
11
+ "speech",
12
+ "transcription",
13
+ "audio",
14
+ "external",
15
+ "other",
16
+ ];
17
+ const callTypes = {
18
+ zh: {
19
+ execution: ["运行 Prompt", "会话执行时组装的模型输入"],
20
+ task: ["子任务 Prompt", "委派给子任务的模型输入"],
21
+ panel: ["Panel 调用", "由 Panel 发起的模型调用"],
22
+ summary: ["摘要 / 压缩 Prompt", "生成摘要或压缩会话历史"],
23
+ context: ["上下文整理 Prompt", "整理本次执行所需的上下文"],
24
+ title: ["标题生成 Prompt", "生成会话或任务标题"],
25
+ goal: ["目标检查 Prompt", "检查目标的进展与完成情况"],
26
+ memory: ["记忆整理 Prompt", "提取、整理或更新记忆"],
27
+ suggestion: ["后续建议 Prompt", "生成会话的后续操作建议"],
28
+ speech: ["语音合成", "将文本转换为语音"],
29
+ transcription: ["语音转写", "将语音转换为文字"],
30
+ audio: ["音频处理", "对音频执行模型推理"],
31
+ external: ["外部 CLI 入口", "提交给外部 CLI 的入口输入"],
32
+ other: ["其他调用", "调用未提供可识别的类型"],
33
+ },
34
+ en: {
35
+ execution: ["Execution Prompt", "Model input assembled for a session execution"],
36
+ task: ["Subtask Prompt", "Model input for a delegated task"],
37
+ panel: ["Panel call", "A model call initiated by a Panel"],
38
+ summary: ["Summary and compaction Prompt", "Summarize or compact session history"],
39
+ context: ["Context preparation Prompt", "Prepare context for an execution"],
40
+ title: ["Title generation Prompt", "Generate a session or task title"],
41
+ goal: ["Goal check Prompt", "Check progress toward a goal"],
42
+ memory: ["Memory maintenance Prompt", "Extract, organize or update memory"],
43
+ suggestion: ["Follow-up suggestion Prompt", "Suggest next steps for a session"],
44
+ speech: ["Speech synthesis", "Convert text to speech"],
45
+ transcription: ["Speech transcription", "Convert speech to text"],
46
+ audio: ["Audio processing", "Run model inference on audio"],
47
+ external: ["External CLI entry", "Input submitted to an external CLI"],
48
+ other: ["Other call", "The call has no recognized type"],
49
+ },
50
+ };
51
+ /** Classification belongs to the Host; clients never guess intent from Prompt text. */
52
+ export function presentModelTraceCallType(value, lang) {
53
+ const type = modelTraceCallTypes.includes(value)
54
+ ? value
55
+ : "other";
56
+ const [label, description] = callTypes[lang][type];
57
+ return { type, label, description };
58
+ }
59
+ /** The record keeps a logical snapshot even when a transport attempt contains provider wire. */
60
+ export function modelTraceInputBoundary(kind, selected, record) {
61
+ if (kind === "event")
62
+ return "event";
63
+ const attempt = kind === "attempt" ? selected : undefined;
64
+ if (attempt?.coverage === "provider-wire")
65
+ return "wire";
66
+ if (attempt?.coverage === "external-cli-input" ||
67
+ (kind === "request" && record.attempts.some((item) => item.coverage === "external-cli-input")))
68
+ return "external";
69
+ const type = presentModelTraceCallType(record.callType, "en").type;
70
+ if (type === "speech" || type === "transcription" || type === "audio")
71
+ return "media";
72
+ return "logical";
73
+ }