@prettier-ai/dsh-client-ui-trajectory 0.1.2-alpha.5 → 0.1.3-alpha.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/README.i18n.yaml +2 -2
- package/README.md +3 -3
- package/README.zh.md +3 -3
- package/lib/client.js +369 -99
- package/lib/types/client/locales.d.ts +1 -0
- package/package.json +17 -17
package/README.i18n.yaml
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
3
|
# after editing either side, bring the other along and re-record with:
|
|
4
4
|
# pnpm run verify-translation-pairing --write packages/client/ui-trajectory/README.md
|
|
5
|
-
README.md:
|
|
6
|
-
README.zh.md:
|
|
5
|
+
README.md: 3f4509c4bcd282e2bc06ca3c00de5308783501b8
|
|
6
|
+
README.zh.md: 75d00e677109c7fa60563d6fc60bac47a13ee006
|
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ English | [中文](README.zh.md)
|
|
|
9
9
|
|
|
10
10
|
## Summary
|
|
11
11
|
|
|
12
|
-
`dsh-client-ui-trajectory` is the Trajectory view of the dsh web client: it renders a turn-aware event ledger with selectable User, Assistant, Tool, and nested Subtool records, plus an interactive timing overview. Thick rules mark Turn boundaries, compact inline markers identify Steps, and selecting a record opens a local inspector for token usage, duration, Input, Output, Timing,
|
|
12
|
+
`dsh-client-ui-trajectory` is the Trajectory view of the dsh web client: it renders a turn-aware event ledger with selectable User, Assistant, Tool, and nested Subtool records, plus an interactive timing overview. Thick rules mark Turn boundaries, compact inline markers identify Steps, and selecting a record opens a local inspector for token usage, duration, Input, Output, Timing, durable images, and file-attachment summaries from user, assistant, or tool content. The view is a pure consumer: it registers target-specific Event Definitions, a Trajectory view builder, and one tab in the conversation's `conversation.view` slot ring, and provides no service and declares no Context merge. Its typed `trajectory` locale namespace owns every product-authored ledger, timeline, inspector, tooltip, and accessibility phrase; event content, tool names, identifiers, and provider diagnostics remain verbatim data. Long ledgers open at the current tail, page older history on demand, and mount only the visible row window.
|
|
13
13
|
|
|
14
14
|
## Table of Contents
|
|
15
15
|
|
|
@@ -29,7 +29,7 @@ Open the Trajectory tab in the conversation's view ring to inspect agent activit
|
|
|
29
29
|
|
|
30
30
|
### Inspecting records
|
|
31
31
|
|
|
32
|
-
Selection, timeline navigation, folding, and search cover the React-visible window. Request numbers and cumulative usage cover the complete resident snapshot. Selecting a record opens a local inspector for token usage, duration, Input, Output, Timing, and durable images. Image URLs use the Conversation-owned per-session cache, so Chat and Trajectory share one authorized read per attachment. A record without text
|
|
32
|
+
Selection, timeline navigation, folding, and search cover the React-visible window. Request numbers and cumulative usage cover the complete resident snapshot. Selecting a record opens a local inspector for token usage, duration, Input, Output, Timing, and durable images. Image URLs use the Conversation-owned per-session cache, so Chat and Trajectory share one authorized read per attachment. A user record shows the generic-file count beside its text, while a record without text shows its image and file counts. A standalone compaction request appears chronologically in its own `Between turns` section, while a numbered compaction remains inside its owning turn.
|
|
33
33
|
|
|
34
34
|
### The timing overview
|
|
35
35
|
|
|
@@ -47,7 +47,7 @@ The view is a pure projection: Trajectory-owned Definitions assemble business re
|
|
|
47
47
|
|
|
48
48
|
### Virtual rows
|
|
49
49
|
|
|
50
|
-
Long ledgers initially derive React data from 50 target Nodes ending at the mount-time tail. Later Nodes extend that anchored window without evicting its prefix, and the existing load control reveals earlier resident Nodes before requesting another Session page. Virtualization mounts only the visible row window plus a small overscan; request-only separators share the next measurable virtual item, while semantic row keys and ARIA indexes survive prepends. Content-only stream frames preserve virtual row keys and heights, reuse measurements, and do not issue repeated tail-scroll writes. Completed replies retain assembled blocks, timing, and usage in Trajectory target State, while the shared Session window keeps the raw Events.
|
|
50
|
+
Long ledgers initially derive React data from 50 target Nodes ending at the mount-time tail. Later Nodes extend that anchored window without evicting its prefix, and the existing load control reveals earlier resident Nodes before requesting another Session page. Virtualization mounts only the visible row window plus a small overscan; request-only separators share the next measurable virtual item, while semantic row keys and ARIA indexes survive prepends. The virtualizer owns bottom following for structural appends; non-virtual ledgers use a direct tail-position write. Content-only stream frames preserve virtual row keys and heights, reuse measurements, and do not issue repeated tail-scroll writes. Completed replies retain assembled blocks, timing, and usage in Trajectory target State, while the shared Session window keeps the raw Events.
|
|
51
51
|
|
|
52
52
|
### Layout
|
|
53
53
|
|
package/README.zh.md
CHANGED
|
@@ -9,7 +9,7 @@ kind: "package-reference"
|
|
|
9
9
|
|
|
10
10
|
## 概述
|
|
11
11
|
|
|
12
|
-
`dsh-client-ui-trajectory` 是 dsh Web 客户端的 Trajectory 视图:它渲染按轮次组织的事件记录表,其中可选择用户、助手、工具与嵌套子工具记录,并带交互式时间概览。较粗的分割线标示轮次边界,紧凑的行内标记标识步骤;选择记录会打开局部检查器,查看 token
|
|
12
|
+
`dsh-client-ui-trajectory` 是 dsh Web 客户端的 Trajectory 视图:它渲染按轮次组织的事件记录表,其中可选择用户、助手、工具与嵌套子工具记录,并带交互式时间概览。较粗的分割线标示轮次边界,紧凑的行内标记标识步骤;选择记录会打开局部检查器,查看 token 用量、耗时、输入、输出、计时,以及用户、助手或工具内容中的持久图片与文件附件摘要。该视图是纯消费方:它注册 target 专属 Event Definition、Trajectory view builder 以及对话 `conversation.view` slot 环中的一个视图标签页,不提供 service,也不声明 Context 合并。带类型的 `trajectory` locale namespace 拥有所有产品编写的 ledger、timeline、inspector、tooltip 与无障碍文案;事件内容、工具名称、标识符与 provider 诊断保持原始数据。长记录表打开时定位于当前尾部、按需加载更早历史,并且只挂载可见行窗口。
|
|
13
13
|
|
|
14
14
|
## 目录
|
|
15
15
|
|
|
@@ -29,7 +29,7 @@ kind: "package-reference"
|
|
|
29
29
|
|
|
30
30
|
### 检查记录
|
|
31
31
|
|
|
32
|
-
选择、时间线导航、折叠与搜索只覆盖 React 可见窗口。请求编号与累计用量覆盖完整的驻留 snapshot。选择记录会打开局部检查器,查看 token 用量、耗时、输入、输出、计时与持久图片。图片 URL 使用 Conversation 拥有的逐会话缓存,因此 Chat 与 Trajectory
|
|
32
|
+
选择、时间线导航、折叠与搜索只覆盖 React 可见窗口。请求编号与累计用量覆盖完整的驻留 snapshot。选择记录会打开局部检查器,查看 token 用量、耗时、输入、输出、计时与持久图片。图片 URL 使用 Conversation 拥有的逐会话缓存,因此 Chat 与 Trajectory 对每个附件共享一次已授权读取。用户记录会在文本旁显示通用文件数量;记录没有文本时,则显示图片与文件数量。独立运行的压缩请求会按时间顺序显示在自己的 `Between turns` 区段中,而带编号的压缩仍位于其所属轮次内。
|
|
33
33
|
|
|
34
34
|
### 时间概览
|
|
35
35
|
|
|
@@ -47,7 +47,7 @@ kind: "package-reference"
|
|
|
47
47
|
|
|
48
48
|
### 虚拟行
|
|
49
49
|
|
|
50
|
-
长记录表最初只从挂载时尾部结束的 50 个 target Node 派生 React 数据。后续 Node 会扩展这个固定起点的窗口而不会逐出其前缀;现有加载控件会先显露更早的驻留 Node,再请求下一个 Session 页面。虚拟化只挂载可见行窗口加少量缓冲;仅含请求的分隔行并入下一个具备可测高度的虚拟项,语义行键与 ARIA
|
|
50
|
+
长记录表最初只从挂载时尾部结束的 50 个 target Node 派生 React 数据。后续 Node 会扩展这个固定起点的窗口而不会逐出其前缀;现有加载控件会先显露更早的驻留 Node,再请求下一个 Session 页面。虚拟化只挂载可见行窗口加少量缓冲;仅含请求的分隔行并入下一个具备可测高度的虚拟项,语义行键与 ARIA 索引在向前补页后保持不变。虚拟化器负责结构性追加后的底部跟随;非虚拟记录表会直接写入末尾位置。仅含内容更新的流式帧会保持虚拟行的键与高度、复用测量结果,并且不会重复写入末尾滚动位置。已完成的回复会在 Trajectory target State 中保留组装后的 blocks、计时与用量,共享 Session 窗口则保留原始 Event。
|
|
51
51
|
|
|
52
52
|
### 布局
|
|
53
53
|
|
package/lib/client.js
CHANGED
|
@@ -216,6 +216,7 @@ window.__ModuleLoader__.load({
|
|
|
216
216
|
"layout.compacted": "上下文已压缩",
|
|
217
217
|
"layout.toolCallOnly": "仅工具调用",
|
|
218
218
|
"layout.imageOnly": "图片 ×{count}",
|
|
219
|
+
"layout.fileAttachments": "文件 ×{count}",
|
|
219
220
|
"layout.initialSystemPrompt": "初始系统提示词",
|
|
220
221
|
"layout.systemPromptUpdated": "系统提示词已更新",
|
|
221
222
|
"layout.toolsUpdated": "工具已更新",
|
|
@@ -393,6 +394,7 @@ window.__ModuleLoader__.load({
|
|
|
393
394
|
"layout.compacted": "Context compacted",
|
|
394
395
|
"layout.toolCallOnly": "Tool call only",
|
|
395
396
|
"layout.imageOnly": "Images ×{count}",
|
|
397
|
+
"layout.fileAttachments": "Files ×{count}",
|
|
396
398
|
"layout.initialSystemPrompt": "Initial System Prompt",
|
|
397
399
|
"layout.systemPromptUpdated": "System Prompt Updated",
|
|
398
400
|
"layout.toolsUpdated": "Tools Updated",
|
|
@@ -400,6 +402,345 @@ window.__ModuleLoader__.load({
|
|
|
400
402
|
"layout.compactionInterrupted": "Compaction was interrupted before completion."
|
|
401
403
|
};
|
|
402
404
|
//#endregion
|
|
405
|
+
//#region ../../util/values/src/index.ts
|
|
406
|
+
/** Whether a realm-owned intrinsic prototype is backed by its native constructor. */
|
|
407
|
+
function hasIntrinsicConstructor(prototype, name) {
|
|
408
|
+
const constructor = Object.getOwnPropertyDescriptor(prototype, "constructor")?.value;
|
|
409
|
+
if (typeof constructor !== "function") return false;
|
|
410
|
+
try {
|
|
411
|
+
return constructor.name === name && constructor.prototype === prototype && Function.prototype.toString.call(constructor) === `function ${name}() { [native code] }`;
|
|
412
|
+
} catch {
|
|
413
|
+
return false;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
/** Whether a candidate is one realm's intrinsic `Object.prototype`. */
|
|
417
|
+
function isIntrinsicObjectPrototype(value) {
|
|
418
|
+
return Object.getPrototypeOf(value) === null && hasIntrinsicConstructor(value, "Object");
|
|
419
|
+
}
|
|
420
|
+
/** Whether an array uses one realm's intrinsic `Array.prototype`, not a subclass or forged prototype. */
|
|
421
|
+
function hasPlainArrayPrototype(value) {
|
|
422
|
+
const prototype = Object.getPrototypeOf(value);
|
|
423
|
+
if (!Array.isArray(prototype) || !hasIntrinsicConstructor(prototype, "Array")) return false;
|
|
424
|
+
const objectPrototype = Object.getPrototypeOf(prototype);
|
|
425
|
+
return typeof objectPrototype === "object" && objectPrototype !== null && isIntrinsicObjectPrototype(objectPrototype);
|
|
426
|
+
}
|
|
427
|
+
/** Whether an object is a plain or null-prototype record from any JavaScript realm. */
|
|
428
|
+
function hasPlainObjectPrototype(value) {
|
|
429
|
+
const prototype = Object.getPrototypeOf(value);
|
|
430
|
+
return prototype === null || typeof prototype === "object" && isIntrinsicObjectPrototype(prototype);
|
|
431
|
+
}
|
|
432
|
+
/** Return every JSON-visible object key, or reject own data JSON would discard. */
|
|
433
|
+
function enumerableStringKeys(value) {
|
|
434
|
+
const keys = Reflect.ownKeys(value);
|
|
435
|
+
if (keys.some((key) => typeof key !== "string" || !Object.prototype.propertyIsEnumerable.call(value, key))) return void 0;
|
|
436
|
+
return keys;
|
|
437
|
+
}
|
|
438
|
+
/** Validate lossless JSON iteratively, optionally materializing a detached snapshot. */
|
|
439
|
+
function walkJsonValue(value, detach) {
|
|
440
|
+
const ancestors = /* @__PURE__ */ new Set();
|
|
441
|
+
let root;
|
|
442
|
+
const assign = (destination, item) => {
|
|
443
|
+
if (destination === void 0) return;
|
|
444
|
+
if (destination.kind === "root") root = item;
|
|
445
|
+
else if (destination.kind === "array") destination.target[destination.index] = item;
|
|
446
|
+
else Object.defineProperty(destination.target, destination.key, {
|
|
447
|
+
value: item,
|
|
448
|
+
enumerable: true,
|
|
449
|
+
configurable: true,
|
|
450
|
+
writable: true
|
|
451
|
+
});
|
|
452
|
+
};
|
|
453
|
+
const tasks = [{
|
|
454
|
+
kind: "visit",
|
|
455
|
+
value,
|
|
456
|
+
...detach ? { destination: { kind: "root" } } : {}
|
|
457
|
+
}];
|
|
458
|
+
for (let task = tasks.pop(); task !== void 0; task = tasks.pop()) {
|
|
459
|
+
if (task.kind === "leave") {
|
|
460
|
+
ancestors.delete(task.source);
|
|
461
|
+
continue;
|
|
462
|
+
}
|
|
463
|
+
if (task.kind === "array-item") {
|
|
464
|
+
if (!Object.prototype.hasOwnProperty.call(task.source, task.index)) return void 0;
|
|
465
|
+
tasks.push({
|
|
466
|
+
kind: "visit",
|
|
467
|
+
value: task.source[task.index],
|
|
468
|
+
...task.target === void 0 ? {} : { destination: {
|
|
469
|
+
kind: "array",
|
|
470
|
+
target: task.target,
|
|
471
|
+
index: task.index
|
|
472
|
+
} }
|
|
473
|
+
});
|
|
474
|
+
continue;
|
|
475
|
+
}
|
|
476
|
+
if (task.kind === "object-property") {
|
|
477
|
+
tasks.push({
|
|
478
|
+
kind: "visit",
|
|
479
|
+
value: task.source[task.key],
|
|
480
|
+
...task.target === void 0 ? {} : { destination: {
|
|
481
|
+
kind: "object",
|
|
482
|
+
target: task.target,
|
|
483
|
+
key: task.key
|
|
484
|
+
} }
|
|
485
|
+
});
|
|
486
|
+
continue;
|
|
487
|
+
}
|
|
488
|
+
const current = task.value;
|
|
489
|
+
if (current === null) {
|
|
490
|
+
assign(task.destination, null);
|
|
491
|
+
continue;
|
|
492
|
+
}
|
|
493
|
+
if (typeof current === "boolean" || typeof current === "string") {
|
|
494
|
+
assign(task.destination, current);
|
|
495
|
+
continue;
|
|
496
|
+
}
|
|
497
|
+
if (typeof current === "number") {
|
|
498
|
+
if (!Number.isFinite(current) || Object.is(current, -0)) return void 0;
|
|
499
|
+
assign(task.destination, current);
|
|
500
|
+
continue;
|
|
501
|
+
}
|
|
502
|
+
if (typeof current !== "object") return void 0;
|
|
503
|
+
if (ancestors.has(current)) return void 0;
|
|
504
|
+
if (Array.isArray(current)) {
|
|
505
|
+
if (!hasPlainArrayPrototype(current)) return void 0;
|
|
506
|
+
const length = current.length;
|
|
507
|
+
if (Reflect.ownKeys(current).length !== length + 1) return void 0;
|
|
508
|
+
const target = detach ? [] : void 0;
|
|
509
|
+
if (target !== void 0) assign(task.destination, target);
|
|
510
|
+
ancestors.add(current);
|
|
511
|
+
tasks.push({
|
|
512
|
+
kind: "leave",
|
|
513
|
+
source: current
|
|
514
|
+
});
|
|
515
|
+
for (let index = length - 1; index >= 0; index--) tasks.push({
|
|
516
|
+
kind: "array-item",
|
|
517
|
+
source: current,
|
|
518
|
+
index,
|
|
519
|
+
...target === void 0 ? {} : { target }
|
|
520
|
+
});
|
|
521
|
+
continue;
|
|
522
|
+
}
|
|
523
|
+
if (!hasPlainObjectPrototype(current)) return void 0;
|
|
524
|
+
const keys = enumerableStringKeys(current);
|
|
525
|
+
if (keys === void 0) return void 0;
|
|
526
|
+
const target = detach ? {} : void 0;
|
|
527
|
+
if (target !== void 0) assign(task.destination, target);
|
|
528
|
+
ancestors.add(current);
|
|
529
|
+
tasks.push({
|
|
530
|
+
kind: "leave",
|
|
531
|
+
source: current
|
|
532
|
+
});
|
|
533
|
+
for (let index = keys.length - 1; index >= 0; index--) {
|
|
534
|
+
const key = keys[index];
|
|
535
|
+
/* v8 ignore next -- the loop is bounded by the captured key count. */
|
|
536
|
+
if (key === void 0) return void 0;
|
|
537
|
+
tasks.push({
|
|
538
|
+
kind: "object-property",
|
|
539
|
+
source: current,
|
|
540
|
+
key,
|
|
541
|
+
...target === void 0 ? {} : { target }
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
return detach ? root : true;
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* Validate and detach lossless JSON in one read per property.
|
|
549
|
+
* @param value - candidate value to validate and detach.
|
|
550
|
+
* @returns the detached snapshot, or `undefined` when the value is not losslessly JSON-serializable.
|
|
551
|
+
*/
|
|
552
|
+
function snapshotJsonValue(value) {
|
|
553
|
+
return walkJsonValue(value, true);
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* Deep-freeze an object graph in place while leaving live AbortSignal objects mutable.
|
|
557
|
+
* @param value - value to freeze.
|
|
558
|
+
* @returns the same value after every reachable enumerable child is frozen.
|
|
559
|
+
*/
|
|
560
|
+
function deepFreeze(value) {
|
|
561
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
562
|
+
const pending = [{
|
|
563
|
+
kind: "visit",
|
|
564
|
+
node: value
|
|
565
|
+
}];
|
|
566
|
+
while (pending.length > 0) {
|
|
567
|
+
const task = pending.pop();
|
|
568
|
+
/* v8 ignore next -- the loop condition guarantees one pending task. */
|
|
569
|
+
if (task === void 0) continue;
|
|
570
|
+
if (task.kind === "property") {
|
|
571
|
+
pending.push({
|
|
572
|
+
kind: "visit",
|
|
573
|
+
node: task.source[task.key]
|
|
574
|
+
});
|
|
575
|
+
continue;
|
|
576
|
+
}
|
|
577
|
+
const node = task.node;
|
|
578
|
+
if (node === null || typeof node !== "object") continue;
|
|
579
|
+
if (node instanceof AbortSignal) continue;
|
|
580
|
+
if (seen.has(node)) continue;
|
|
581
|
+
seen.add(node);
|
|
582
|
+
Object.freeze(node);
|
|
583
|
+
const keys = Object.keys(node);
|
|
584
|
+
for (let index = keys.length - 1; index >= 0; index--) {
|
|
585
|
+
const key = keys[index];
|
|
586
|
+
/* v8 ignore next -- the loop is bounded by the captured key count. */
|
|
587
|
+
if (key === void 0) continue;
|
|
588
|
+
pending.push({
|
|
589
|
+
kind: "property",
|
|
590
|
+
source: node,
|
|
591
|
+
key
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
return value;
|
|
596
|
+
}
|
|
597
|
+
//#endregion
|
|
598
|
+
//#region ../../llm/llm/src/assistant-stream.ts
|
|
599
|
+
/** Lossless compact representation of one model-stream attempt. */
|
|
600
|
+
function safeTime(value) {
|
|
601
|
+
if (!Number.isSafeInteger(value)) throw new TypeError(`Assistant stream time must be a safe integer, got ${String(value)}`);
|
|
602
|
+
return value;
|
|
603
|
+
}
|
|
604
|
+
function safeIndex(value, label) {
|
|
605
|
+
if (!Number.isSafeInteger(value) || value < 0 || Object.is(value, -0)) throw new TypeError(`${label} index must be a non-negative safe integer`);
|
|
606
|
+
return value;
|
|
607
|
+
}
|
|
608
|
+
function snapshotChunk(chunk) {
|
|
609
|
+
const snapshot = snapshotJsonValue(chunk);
|
|
610
|
+
if (snapshot === void 0) throw new TypeError("Assistant stream chunk must be losslessly JSON-serializable");
|
|
611
|
+
return snapshot;
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* Expand compact records into the exact timed chunk sequence.
|
|
615
|
+
* @param stream - compact records from one durable Assistant settlement.
|
|
616
|
+
* @returns detached timed chunks with every original delta boundary preserved.
|
|
617
|
+
* @throws {TypeError} when a record or reconstructed timestamp is invalid.
|
|
618
|
+
*/
|
|
619
|
+
function expandAssistantStream(stream) {
|
|
620
|
+
const chunks = [];
|
|
621
|
+
for (const candidate of stream) {
|
|
622
|
+
const record = validateRecord(candidate);
|
|
623
|
+
if (record.type === "chunk") {
|
|
624
|
+
chunks.push({
|
|
625
|
+
time: record.time,
|
|
626
|
+
chunk: record.chunk
|
|
627
|
+
});
|
|
628
|
+
continue;
|
|
629
|
+
}
|
|
630
|
+
const members = record.type === "tool-call-chunks" ? record.args : record.texts;
|
|
631
|
+
let time = record.time0;
|
|
632
|
+
for (let index = 0; index < members.length; index += 1) {
|
|
633
|
+
if (index > 0) time += record.dt[index - 1];
|
|
634
|
+
let chunk;
|
|
635
|
+
if (record.type === "text-chunks") chunk = {
|
|
636
|
+
type: "text-delta",
|
|
637
|
+
index: record.index,
|
|
638
|
+
text: members[index]
|
|
639
|
+
};
|
|
640
|
+
else if (record.type === "reasoning-chunks") chunk = {
|
|
641
|
+
type: "reasoning-delta",
|
|
642
|
+
index: record.index,
|
|
643
|
+
text: members[index]
|
|
644
|
+
};
|
|
645
|
+
else chunk = {
|
|
646
|
+
type: "tool-call-delta",
|
|
647
|
+
index: record.index,
|
|
648
|
+
id: record.id,
|
|
649
|
+
...Object.hasOwn(record, "name") ? { name: record.name } : {},
|
|
650
|
+
argumentsDelta: members[index]
|
|
651
|
+
};
|
|
652
|
+
chunks.push({
|
|
653
|
+
time,
|
|
654
|
+
chunk
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
return chunks;
|
|
659
|
+
}
|
|
660
|
+
function validateRecord(value) {
|
|
661
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError("Assistant stream record must be an object");
|
|
662
|
+
const record = value;
|
|
663
|
+
switch (record.type) {
|
|
664
|
+
case "text-chunks":
|
|
665
|
+
case "reasoning-chunks": {
|
|
666
|
+
exactKeys(record, [
|
|
667
|
+
"type",
|
|
668
|
+
"time0",
|
|
669
|
+
"index",
|
|
670
|
+
"dt",
|
|
671
|
+
"texts"
|
|
672
|
+
], record.type);
|
|
673
|
+
const texts = stringArray(record.texts, `${record.type} texts`);
|
|
674
|
+
if (texts.length === 0) throw new TypeError(`${record.type} texts must be non-empty`);
|
|
675
|
+
validateRun(record, texts.length, record.type);
|
|
676
|
+
return record;
|
|
677
|
+
}
|
|
678
|
+
case "tool-call-chunks": {
|
|
679
|
+
exactKeys(record, Object.hasOwn(record, "name") ? [
|
|
680
|
+
"type",
|
|
681
|
+
"time0",
|
|
682
|
+
"index",
|
|
683
|
+
"dt",
|
|
684
|
+
"id",
|
|
685
|
+
"name",
|
|
686
|
+
"args"
|
|
687
|
+
] : [
|
|
688
|
+
"type",
|
|
689
|
+
"time0",
|
|
690
|
+
"index",
|
|
691
|
+
"dt",
|
|
692
|
+
"id",
|
|
693
|
+
"args"
|
|
694
|
+
], record.type);
|
|
695
|
+
const args = stringArray(record.args, "tool-call-chunks args");
|
|
696
|
+
if (args.length === 0) throw new TypeError("tool-call-chunks args must be non-empty");
|
|
697
|
+
if (typeof record.id !== "string" || record.id.length === 0) throw new TypeError("tool-call-chunks id must be a non-empty string");
|
|
698
|
+
if (record.name !== void 0 && (typeof record.name !== "string" || record.name.length === 0)) throw new TypeError("tool-call-chunks name must be a non-empty string");
|
|
699
|
+
validateRun(record, args.length, record.type);
|
|
700
|
+
return record;
|
|
701
|
+
}
|
|
702
|
+
case "chunk": {
|
|
703
|
+
exactKeys(record, [
|
|
704
|
+
"type",
|
|
705
|
+
"time",
|
|
706
|
+
"chunk"
|
|
707
|
+
], "chunk");
|
|
708
|
+
const time = safeTime(record.time);
|
|
709
|
+
if (typeof record.chunk !== "object" || record.chunk === null || Array.isArray(record.chunk)) throw new TypeError("Assistant stream raw chunk must be a lossless JSON object");
|
|
710
|
+
let chunk;
|
|
711
|
+
try {
|
|
712
|
+
chunk = snapshotChunk(record.chunk);
|
|
713
|
+
} catch (error) {
|
|
714
|
+
throw new TypeError("Assistant stream raw chunk must be a lossless JSON object", { cause: error });
|
|
715
|
+
}
|
|
716
|
+
return deepFreeze({
|
|
717
|
+
type: "chunk",
|
|
718
|
+
time,
|
|
719
|
+
chunk
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
default: throw new TypeError(`Unsupported Assistant stream record ${JSON.stringify(record.type)}`);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
function validateRun(record, members, label) {
|
|
726
|
+
safeTime(record.time0);
|
|
727
|
+
safeIndex(record.index, label);
|
|
728
|
+
if (!Array.isArray(record.dt) || record.dt.some((value) => !Number.isSafeInteger(value))) throw new TypeError(`${label} dt must contain safe integers`);
|
|
729
|
+
if (record.dt.length !== members - 1) throw new TypeError(`${label} dt length must be one less than its members`);
|
|
730
|
+
let time = record.time0;
|
|
731
|
+
for (const gap of record.dt) {
|
|
732
|
+
time += gap;
|
|
733
|
+
if (!Number.isSafeInteger(time)) throw new TypeError(`${label} member times must stay safe integers`);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
function stringArray(value, label) {
|
|
737
|
+
if (!Array.isArray(value) || value.some((member) => typeof member !== "string")) throw new TypeError(`${label} must be a string array`);
|
|
738
|
+
return value;
|
|
739
|
+
}
|
|
740
|
+
function exactKeys(record, keys, label) {
|
|
741
|
+
if (Object.keys(record).length !== keys.length || !keys.every((key) => Object.hasOwn(record, key))) throw new TypeError(`${label} Assistant stream record must contain exactly ${keys.join(", ")}`);
|
|
742
|
+
}
|
|
743
|
+
//#endregion
|
|
403
744
|
//#region lib/types/client/trajectory-definition-common.js
|
|
404
745
|
/**
|
|
405
746
|
* Wrap one contribution in the Engine-owned target envelope.
|
|
@@ -597,9 +938,6 @@ window.__ModuleLoader__.load({
|
|
|
597
938
|
}
|
|
598
939
|
//#endregion
|
|
599
940
|
//#region lib/types/client/trajectory-assistant-definition.js
|
|
600
|
-
function isChunkRunEvent(event) {
|
|
601
|
-
return event.type === "chunkrow/text-chunks" || event.type === "chunkrow/reasoning-chunks" || event.type === "chunkrow/tool-call-chunks";
|
|
602
|
-
}
|
|
603
941
|
function initialState(turn, step, startSeq, startTime, started) {
|
|
604
942
|
return {
|
|
605
943
|
turn,
|
|
@@ -647,9 +985,7 @@ window.__ModuleLoader__.load({
|
|
|
647
985
|
...current?.reasoningTokens === void 0 && next.reasoningTokens === void 0 ? {} : { reasoningTokens: (current?.reasoningTokens ?? 0) + (next.reasoningTokens ?? 0) }
|
|
648
986
|
};
|
|
649
987
|
}
|
|
650
|
-
function updateChunk(state,
|
|
651
|
-
if (match.event.type !== "assistant/chunk") return state;
|
|
652
|
-
const chunk = match.event.data.chunk;
|
|
988
|
+
function updateChunk(state, chunk, seq, time) {
|
|
653
989
|
if (chunk.type === "usage") return {
|
|
654
990
|
...state,
|
|
655
991
|
sawChunk: true,
|
|
@@ -719,79 +1055,16 @@ window.__ModuleLoader__.load({
|
|
|
719
1055
|
blocks,
|
|
720
1056
|
visibleBlocks,
|
|
721
1057
|
...visibleBlocks > 0 && state.firstVisibleSeq === void 0 ? {
|
|
722
|
-
firstVisibleSeq:
|
|
723
|
-
firstVisibleTime:
|
|
1058
|
+
firstVisibleSeq: seq,
|
|
1059
|
+
firstVisibleTime: time
|
|
724
1060
|
} : {},
|
|
725
|
-
...isTokenDelta(chunk) && state.firstTokenTime === void 0 ? { firstTokenTime:
|
|
1061
|
+
...isTokenDelta(chunk) && state.firstTokenTime === void 0 ? { firstTokenTime: time } : {}
|
|
726
1062
|
};
|
|
727
1063
|
}
|
|
728
|
-
function
|
|
729
|
-
|
|
730
|
-
const
|
|
731
|
-
|
|
732
|
-
let firstVisible;
|
|
733
|
-
let time = event.time;
|
|
734
|
-
for (let index = 0; index < fragments.length; index++) {
|
|
735
|
-
const fragment = fragments[index];
|
|
736
|
-
if (needsToken && firstTokenTime === void 0 && (nameStartsToken || fragment !== "")) firstTokenTime = time;
|
|
737
|
-
if (needsVisible && firstVisible === void 0 && (visibleFromStart || event.type !== "chunkrow/tool-call-chunks" && fragment.trim() !== "")) firstVisible = {
|
|
738
|
-
seq: event.seq + index,
|
|
739
|
-
time
|
|
740
|
-
};
|
|
741
|
-
if ((!needsToken || firstTokenTime !== void 0) && (!needsVisible || firstVisible !== void 0)) break;
|
|
742
|
-
time += event.data.dt[index] ?? 0;
|
|
743
|
-
}
|
|
744
|
-
return {
|
|
745
|
-
firstTokenTime,
|
|
746
|
-
firstVisible
|
|
747
|
-
};
|
|
748
|
-
}
|
|
749
|
-
function updateChunkRun(state, event) {
|
|
750
|
-
const blocks = [...state.blocks];
|
|
751
|
-
const previous = blocks[event.data.index];
|
|
752
|
-
const previousVisible = blockIsVisible(previous);
|
|
753
|
-
let visibleFromStart = state.visibleBlocks - Number(previousVisible) > 0;
|
|
754
|
-
if (event.type === "chunkrow/text-chunks") {
|
|
755
|
-
const text = previous?.kind === "text" ? previous.text : "";
|
|
756
|
-
visibleFromStart ||= text.trim() !== "";
|
|
757
|
-
blocks[event.data.index] = {
|
|
758
|
-
kind: "text",
|
|
759
|
-
text: text + event.data.texts.join("")
|
|
760
|
-
};
|
|
761
|
-
} else if (event.type === "chunkrow/reasoning-chunks") {
|
|
762
|
-
const text = previous?.kind === "reasoning" ? previous.text : "";
|
|
763
|
-
visibleFromStart ||= text.trim() !== "";
|
|
764
|
-
blocks[event.data.index] = {
|
|
765
|
-
kind: "reasoning",
|
|
766
|
-
text: text + event.data.texts.join("")
|
|
767
|
-
};
|
|
768
|
-
} else {
|
|
769
|
-
const base = previous?.kind === "tool-call" ? previous : {
|
|
770
|
-
kind: "tool-call",
|
|
771
|
-
callId: "",
|
|
772
|
-
name: "",
|
|
773
|
-
argsRaw: ""
|
|
774
|
-
};
|
|
775
|
-
blocks[event.data.index] = {
|
|
776
|
-
kind: "tool-call",
|
|
777
|
-
callId: base.callId || String(event.data.id),
|
|
778
|
-
name: Object.hasOwn(event.data, "name") ? event.data.name : base.name,
|
|
779
|
-
argsRaw: base.argsRaw + event.data.args.join("")
|
|
780
|
-
};
|
|
781
|
-
}
|
|
782
|
-
const boundaries = chunkRunBoundaries(event, state.firstTokenTime === void 0, state.firstVisibleSeq === void 0, visibleFromStart);
|
|
783
|
-
const visibleBlocks = state.visibleBlocks - Number(previousVisible) + Number(blockIsVisible(blocks[event.data.index]));
|
|
784
|
-
return {
|
|
785
|
-
...state,
|
|
786
|
-
sawChunk: true,
|
|
787
|
-
blocks,
|
|
788
|
-
visibleBlocks,
|
|
789
|
-
...boundaries.firstVisible === void 0 ? {} : {
|
|
790
|
-
firstVisibleSeq: boundaries.firstVisible.seq,
|
|
791
|
-
firstVisibleTime: boundaries.firstVisible.time
|
|
792
|
-
},
|
|
793
|
-
...boundaries.firstTokenTime === void 0 ? {} : { firstTokenTime: boundaries.firstTokenTime }
|
|
794
|
-
};
|
|
1064
|
+
function updateEmbedded(state, event) {
|
|
1065
|
+
let next = state;
|
|
1066
|
+
for (const member of expandAssistantStream(event.data.stream)) next = updateChunk(next, member.chunk, event.seq, member.time);
|
|
1067
|
+
return next;
|
|
795
1068
|
}
|
|
796
1069
|
function closedBoundary(context) {
|
|
797
1070
|
if (context.state?.stepEnd?.event.type === "step/end") return context.state.stepEnd.event;
|
|
@@ -802,17 +1075,14 @@ window.__ModuleLoader__.load({
|
|
|
802
1075
|
function fallbackState$1(context) {
|
|
803
1076
|
let state;
|
|
804
1077
|
for (const match of context.matches) {
|
|
805
|
-
if (isChunkRunEvent(match.event)) {
|
|
806
|
-
state ??= initialState(match.event.data.turn, match.event.data.step, match.event.seq, match.event.time, false);
|
|
807
|
-
state = updateChunkRun(state, match.event);
|
|
808
|
-
continue;
|
|
809
|
-
}
|
|
810
1078
|
const event = match.event;
|
|
811
|
-
if (event.type === "assistant/chunk") {
|
|
1079
|
+
if (event.type === "assistant/live-chunk") {
|
|
812
1080
|
state ??= initialState(event.data.turn, event.data.step, event.seq, event.time, false);
|
|
813
|
-
state = updateChunk(state,
|
|
814
|
-
} else if (event.type === "assistant/message") {
|
|
1081
|
+
state = updateChunk(state, event.data.chunk, event.seq, event.time);
|
|
1082
|
+
} else if (event.type === "assistant/message" || event.type === "assistant/attempt") {
|
|
815
1083
|
state ??= initialState(event.data.turn, event.data.step, event.seq, event.time, false);
|
|
1084
|
+
state = updateEmbedded(state, event);
|
|
1085
|
+
if (event.type === "assistant/attempt") continue;
|
|
816
1086
|
const blocks = toAssistantBlocks(event.data.message.content);
|
|
817
1087
|
state = {
|
|
818
1088
|
...state,
|
|
@@ -901,11 +1171,7 @@ window.__ModuleLoader__.load({
|
|
|
901
1171
|
id: `${event.data.turn}:${event.data.step}`,
|
|
902
1172
|
role: "start"
|
|
903
1173
|
};
|
|
904
|
-
if (event.type === "assistant/chunk" || event.type === "assistant/message" || event.type === "llm/retry" || event.type === "step/end") return {
|
|
905
|
-
id: `${event.data.turn}:${event.data.step}`,
|
|
906
|
-
role: "update"
|
|
907
|
-
};
|
|
908
|
-
if (isChunkRunEvent(event)) return {
|
|
1174
|
+
if (event.type === "assistant/live-chunk" || event.type === "assistant/message" || event.type === "assistant/attempt" || event.type === "llm/retry" || event.type === "step/end") return {
|
|
909
1175
|
id: `${event.data.turn}:${event.data.step}`,
|
|
910
1176
|
role: "update"
|
|
911
1177
|
};
|
|
@@ -916,16 +1182,17 @@ window.__ModuleLoader__.load({
|
|
|
916
1182
|
return initialState(match.event.data.turn, match.event.data.step, match.event.seq, match.event.time, true);
|
|
917
1183
|
},
|
|
918
1184
|
update: (context, match) => {
|
|
919
|
-
if (
|
|
920
|
-
if (match.event.type === "assistant/
|
|
1185
|
+
if (match.event.type === "assistant/live-chunk") return updateChunk(context.state, match.event.data.chunk, match.event.seq, match.event.time);
|
|
1186
|
+
if (match.event.type === "assistant/attempt") return updateEmbedded(context.state, match.event);
|
|
921
1187
|
if (match.event.type === "assistant/message") {
|
|
1188
|
+
const streamed = updateEmbedded(context.state, match.event);
|
|
922
1189
|
const blocks = toAssistantBlocks(match.event.data.message.content);
|
|
923
1190
|
return {
|
|
924
|
-
...
|
|
1191
|
+
...streamed,
|
|
925
1192
|
blocks,
|
|
926
1193
|
visibleBlocks: countVisibleBlocks(blocks),
|
|
927
1194
|
final: match,
|
|
928
|
-
usage:
|
|
1195
|
+
usage: streamed.usage ?? match.event.data.usage
|
|
929
1196
|
};
|
|
930
1197
|
}
|
|
931
1198
|
if (match.event.type === "step/end") return {
|
|
@@ -950,8 +1217,7 @@ window.__ModuleLoader__.load({
|
|
|
950
1217
|
},
|
|
951
1218
|
publication: (match) => {
|
|
952
1219
|
if (match.event.type === "step/start") return "none";
|
|
953
|
-
if (
|
|
954
|
-
if (match.event.type !== "assistant/chunk") return "immediate";
|
|
1220
|
+
if (match.event.type !== "assistant/live-chunk") return "immediate";
|
|
955
1221
|
const type = match.event.data.chunk.type;
|
|
956
1222
|
return type === "usage" || type === "finish" ? "none" : "animation-frame";
|
|
957
1223
|
},
|
|
@@ -5082,7 +5348,8 @@ window.__ModuleLoader__.load({
|
|
|
5082
5348
|
anchorTo: "end",
|
|
5083
5349
|
overscan: VIRTUAL_OVERSCAN_ROWS,
|
|
5084
5350
|
scrollMargin: virtualScrollMargin,
|
|
5085
|
-
scrollEndThreshold: BOTTOM_FOLLOW_THRESHOLD_PX
|
|
5351
|
+
scrollEndThreshold: BOTTOM_FOLLOW_THRESHOLD_PX,
|
|
5352
|
+
followOnAppend: "auto"
|
|
5086
5353
|
});
|
|
5087
5354
|
const virtualIndexByRecordId = (0, react.useMemo)(() => {
|
|
5088
5355
|
const indexes = /* @__PURE__ */ new Map();
|
|
@@ -5339,8 +5606,7 @@ window.__ModuleLoader__.load({
|
|
|
5339
5606
|
return;
|
|
5340
5607
|
}
|
|
5341
5608
|
if (!followsTableTail.current) return;
|
|
5342
|
-
if (virtualizationEnabled)
|
|
5343
|
-
else pane.scrollTop = pane.scrollHeight;
|
|
5609
|
+
if (!virtualizationEnabled) pane.scrollTop = pane.scrollHeight;
|
|
5344
5610
|
}, [
|
|
5345
5611
|
historyLoading,
|
|
5346
5612
|
historyStartSeq,
|
|
@@ -6859,8 +7125,9 @@ window.__ModuleLoader__.load({
|
|
|
6859
7125
|
const preview = previewContent(node.content);
|
|
6860
7126
|
const previewMarkdown = preview === "" ? void 0 : preview;
|
|
6861
7127
|
const images = imageBlockCount(node.content);
|
|
7128
|
+
const files = fileBlockCount(node.content);
|
|
6862
7129
|
return {
|
|
6863
|
-
text: previewMarkdown === void 0 && images > 0 ? t("layout.imageOnly", { count: images }) : "",
|
|
7130
|
+
text: [previewMarkdown === void 0 && images > 0 ? t("layout.imageOnly", { count: images }) : void 0, files > 0 ? t("layout.fileAttachments", { count: files }) : void 0].filter((value) => value !== void 0).join(" · "),
|
|
6864
7131
|
...previewMarkdown === void 0 ? {} : { previewMarkdown },
|
|
6865
7132
|
sourceSeq: node.seq,
|
|
6866
7133
|
messageSource: node.source,
|
|
@@ -7431,6 +7698,9 @@ window.__ModuleLoader__.load({
|
|
|
7431
7698
|
function imageBlockCount(content) {
|
|
7432
7699
|
return content.filter((block) => block.type === "image").length;
|
|
7433
7700
|
}
|
|
7701
|
+
function fileBlockCount(content) {
|
|
7702
|
+
return content.filter((block) => block.type === "file").length;
|
|
7703
|
+
}
|
|
7434
7704
|
function stringifySourceValue(value) {
|
|
7435
7705
|
return JSON.stringify(value, null, 2) || String(value);
|
|
7436
7706
|
}
|
|
@@ -172,6 +172,7 @@ export declare const zh: {
|
|
|
172
172
|
readonly 'layout.compacted': "上下文已压缩";
|
|
173
173
|
readonly 'layout.toolCallOnly': "仅工具调用";
|
|
174
174
|
readonly 'layout.imageOnly': "图片 ×{count}";
|
|
175
|
+
readonly 'layout.fileAttachments': "文件 ×{count}";
|
|
175
176
|
readonly 'layout.initialSystemPrompt': "初始系统提示词";
|
|
176
177
|
readonly 'layout.systemPromptUpdated': "系统提示词已更新";
|
|
177
178
|
readonly 'layout.toolsUpdated': "工具已更新";
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prettier-ai/dsh-client-ui-trajectory",
|
|
3
3
|
"description": "Trajectory event ledger with an interactive timing overview: pure-consumer plugin registering into the conversation ViewMap (no service)",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.3-alpha.1",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -50,23 +50,23 @@
|
|
|
50
50
|
"@types/react-dom": "~18.3.0",
|
|
51
51
|
"react": "^18.2.0",
|
|
52
52
|
"react-dom": "^18.2.0",
|
|
53
|
-
"@prettier-ai/dsh-agent": "^0.1.
|
|
54
|
-
"@prettier-ai/dsh-client-
|
|
55
|
-
"@prettier-ai/dsh-client-
|
|
56
|
-
"@prettier-ai/dsh-client-ui-conversation": "^0.1.
|
|
57
|
-
"@prettier-ai/dsh-client-
|
|
58
|
-
"@prettier-ai/dsh-
|
|
59
|
-
"@prettier-ai/dsh-
|
|
60
|
-
"@prettier-ai/dsh-tools": "^0.1.
|
|
53
|
+
"@prettier-ai/dsh-agent": "^0.1.3-alpha.1",
|
|
54
|
+
"@prettier-ai/dsh-client-ui-primitives": "^0.1.3-alpha.1",
|
|
55
|
+
"@prettier-ai/dsh-client-locale": "^0.1.3-alpha.1",
|
|
56
|
+
"@prettier-ai/dsh-client-ui-conversation": "^0.1.3-alpha.1",
|
|
57
|
+
"@prettier-ai/dsh-client-ui-slots": "^0.1.3-alpha.1",
|
|
58
|
+
"@prettier-ai/dsh-client-test-runtime": "^0.1.3-alpha.1",
|
|
59
|
+
"@prettier-ai/dsh-compaction": "^0.1.3-alpha.1",
|
|
60
|
+
"@prettier-ai/dsh-tools": "^0.1.3-alpha.1",
|
|
61
61
|
"@prettier-ai/cordis": "^4.0.2",
|
|
62
|
-
"@prettier-ai/dsh-client-store": "^0.1.
|
|
63
|
-
"@prettier-ai/dsh-
|
|
64
|
-
"@prettier-ai/dsh-
|
|
65
|
-
"@prettier-ai/dsh-
|
|
66
|
-
"@prettier-ai/dsh-
|
|
67
|
-
"@prettier-ai/dsh-
|
|
68
|
-
"@prettier-ai/dsh-
|
|
69
|
-
"@prettier-ai/dsh-
|
|
62
|
+
"@prettier-ai/dsh-client-store": "^0.1.3-alpha.1",
|
|
63
|
+
"@prettier-ai/dsh-client-ui-chat": "^0.1.3-alpha.1",
|
|
64
|
+
"@prettier-ai/dsh-api-session-controller": "^0.1.3-alpha.1",
|
|
65
|
+
"@prettier-ai/dsh-client-ui-session": "^0.1.3-alpha.1",
|
|
66
|
+
"@prettier-ai/dsh-llm": "^0.1.3-alpha.1",
|
|
67
|
+
"@prettier-ai/dsh-attachment": "^0.1.3-alpha.1",
|
|
68
|
+
"@prettier-ai/dsh-session": "^0.1.3-alpha.1",
|
|
69
|
+
"@prettier-ai/dsh-client-ui-renderer": "^0.1.3-alpha.1"
|
|
70
70
|
},
|
|
71
71
|
"files": [
|
|
72
72
|
"lib/index.js",
|