dsh-context 0.40.1 → 0.41.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/lib/client.js +106 -27
- package/lib/index.d.ts +20 -12
- package/lib/index.js +39 -5
- package/package.json +2 -1
package/lib/client.js
CHANGED
|
@@ -37,10 +37,11 @@ window.__ModuleLoader__.load({
|
|
|
37
37
|
"timing.title": "耗时统计",
|
|
38
38
|
"timing.hint": "整个会话的活跃时长分布",
|
|
39
39
|
"timing.total": "总活跃时长",
|
|
40
|
-
"timing.
|
|
40
|
+
"timing.ttft": "模型等待",
|
|
41
|
+
"timing.gen": "模型生成",
|
|
41
42
|
"timing.tools": "工具执行",
|
|
42
43
|
"timing.other": "其他开销",
|
|
43
|
-
"timing.
|
|
44
|
+
"timing.callTimes": "{n}次",
|
|
44
45
|
"timing.toolTimes": "{n}次",
|
|
45
46
|
"timing.empty": "暂无耗时数据 · 对话开始后这里会显示时长分布",
|
|
46
47
|
"tokens.title": "Token 统计",
|
|
@@ -312,10 +313,11 @@ window.__ModuleLoader__.load({
|
|
|
312
313
|
"timing.title": "Timing Stats",
|
|
313
314
|
"timing.hint": "Whole-session active-time split",
|
|
314
315
|
"timing.total": "Active Time",
|
|
315
|
-
"timing.
|
|
316
|
+
"timing.ttft": "TTFT",
|
|
317
|
+
"timing.gen": "LLM Gen",
|
|
316
318
|
"timing.tools": "Tool runs",
|
|
317
319
|
"timing.other": "Overhead",
|
|
318
|
-
"timing.
|
|
320
|
+
"timing.callTimes": "{n} calls",
|
|
319
321
|
"timing.toolTimes": "{n} runs",
|
|
320
322
|
"timing.empty": "No timing data yet — durations appear as the conversation runs",
|
|
321
323
|
"tokens.title": "Token Stats",
|
|
@@ -997,7 +999,8 @@ window.__ModuleLoader__.load({
|
|
|
997
999
|
const t = value;
|
|
998
1000
|
for (const k of [
|
|
999
1001
|
"wallMs",
|
|
1000
|
-
"
|
|
1002
|
+
"ttftMs",
|
|
1003
|
+
"genMs",
|
|
1001
1004
|
"calls",
|
|
1002
1005
|
"toolsMs",
|
|
1003
1006
|
"toolCalls"
|
|
@@ -1038,7 +1041,8 @@ window.__ModuleLoader__.load({
|
|
|
1038
1041
|
}
|
|
1039
1042
|
return {
|
|
1040
1043
|
wallMs: msNumOf(data.wallMs),
|
|
1041
|
-
|
|
1044
|
+
ttftMs: msNumOf(data.ttftMs),
|
|
1045
|
+
genMs: msNumOf(data.genMs),
|
|
1042
1046
|
calls: msNumOf(data.calls),
|
|
1043
1047
|
toolsMs: msNumOf(data.toolsMs),
|
|
1044
1048
|
toolCalls: msNumOf(data.toolCalls),
|
|
@@ -1268,6 +1272,63 @@ window.__ModuleLoader__.load({
|
|
|
1268
1272
|
return;
|
|
1269
1273
|
}
|
|
1270
1274
|
}
|
|
1275
|
+
/**
|
|
1276
|
+
* Walk a (possibly traced/proxied) service value down a plain-property path,
|
|
1277
|
+
* degrading at the FIRST throw. cordis's undeclared-service proxies are one
|
|
1278
|
+
* hostile object class — any accessor backed by host state can throw too —
|
|
1279
|
+
* and the no-white-screen contract needs the whole chain guarded, not just
|
|
1280
|
+
* the `ctx.get` call.
|
|
1281
|
+
*/
|
|
1282
|
+
function readKeysOf(value, ...keys) {
|
|
1283
|
+
let current = value;
|
|
1284
|
+
for (const key of keys) {
|
|
1285
|
+
if (current === null || typeof current !== "object") return void 0;
|
|
1286
|
+
try {
|
|
1287
|
+
current = current[key];
|
|
1288
|
+
} catch {
|
|
1289
|
+
return;
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
return current;
|
|
1293
|
+
}
|
|
1294
|
+
/** The `page` verb of a history face, re-proved and bound to its owner. */
|
|
1295
|
+
function readPageOf(face) {
|
|
1296
|
+
const fn = readKeysOf(face, "page");
|
|
1297
|
+
return typeof fn === "function" ? fn.bind(face) : void 0;
|
|
1298
|
+
}
|
|
1299
|
+
/** The `history` verb of the legacy api client face, re-proved and bound. */
|
|
1300
|
+
function readHistoryOf(face) {
|
|
1301
|
+
const sessions = readKeysOf(face, "api", "sessions");
|
|
1302
|
+
const fn = readKeysOf(sessions, "history");
|
|
1303
|
+
return typeof fn === "function" ? fn.bind(sessions) : void 0;
|
|
1304
|
+
}
|
|
1305
|
+
/**
|
|
1306
|
+
* The 0.1.2+ gateway history page verb, resolved through the DECLARED inject
|
|
1307
|
+
* (see {@link watchHistoryFaces}) and bound up front: a method extracted
|
|
1308
|
+
* unbound loses `this`, and the traced `remote` proxy that hands it out
|
|
1309
|
+
* requires the inject to resolve at all.
|
|
1310
|
+
*/
|
|
1311
|
+
let declaredPage;
|
|
1312
|
+
/**
|
|
1313
|
+
* Register the plugin's 0.1.2+ history faces with the harness through the
|
|
1314
|
+
* DECLARED inject — both `remote` AND `remote.session` (the ui-chat idiom)
|
|
1315
|
+
* must be in one fiber's requirement list, because the traced `remote`
|
|
1316
|
+
* proxy resolves `.session` through the context and each name needs the
|
|
1317
|
+
* other's declaration. Pre-0.1.2 hosts never provide either name, so the
|
|
1318
|
+
* callback simply never fires and the legacy `connection` face carries the
|
|
1319
|
+
* reads. The callback re-runs on every unload/remount, so it owns the
|
|
1320
|
+
* slot's lifetime. The face itself is re-proven (a never-fired or hostile
|
|
1321
|
+
* invocation leaves the slot unset — nothing here can throw).
|
|
1322
|
+
*/
|
|
1323
|
+
function watchHistoryFaces(ctx) {
|
|
1324
|
+
ctx.inject(["remote", "remote.session"], (c) => {
|
|
1325
|
+
const session = c.remote?.session;
|
|
1326
|
+
declaredPage = session !== void 0 ? readPageOf(session) : void 0;
|
|
1327
|
+
return () => {
|
|
1328
|
+
declaredPage = void 0;
|
|
1329
|
+
};
|
|
1330
|
+
});
|
|
1331
|
+
}
|
|
1271
1332
|
/** The rows array of a history/page response, under every served envelope. */
|
|
1272
1333
|
function rowsOf(response) {
|
|
1273
1334
|
let payload = response;
|
|
@@ -1291,15 +1352,17 @@ window.__ModuleLoader__.load({
|
|
|
1291
1352
|
* pre-0.1.2 api client verb (`connection.api.sessions.history`) beneath it.
|
|
1292
1353
|
* Both cut message-aligned pages, so the returned read covers `seq` whenever
|
|
1293
1354
|
* the durable log still holds it: the newer face pins the inclusive cut to
|
|
1294
|
-
* the seq itself, the legacy reader uses the exclusive bound one past it
|
|
1295
|
-
*
|
|
1296
|
-
*
|
|
1355
|
+
* the seq itself, the legacy reader uses the exclusive bound one past it —
|
|
1356
|
+
* and because a non-declared read of the traced remote proxy throws
|
|
1357
|
+
* ("cannot get property … without inject"), the page face prefers the
|
|
1358
|
+
* injection-resolved slot and only then tries the reflect read. Every
|
|
1359
|
+
* service property access degrades at its own guard instead of taking the
|
|
1360
|
+
* view down. Undefined when no face exists (older hosts) — callers keep
|
|
1361
|
+
* their static degradation.
|
|
1297
1362
|
*/
|
|
1298
1363
|
function pageReadersOf(ctx, sessionId) {
|
|
1299
|
-
const
|
|
1300
|
-
const
|
|
1301
|
-
const historyFace = serviceOf(ctx, "connection")?.api?.sessions;
|
|
1302
|
-
const history = historyFace !== void 0 && typeof historyFace.history === "function" ? historyFace.history.bind(historyFace) : void 0;
|
|
1364
|
+
const page = declaredPage ?? readPageOf(serviceOf(ctx, "remote.session"));
|
|
1365
|
+
const history = readHistoryOf(serviceOf(ctx, "connection"));
|
|
1303
1366
|
if (page === void 0 && history === void 0) return void 0;
|
|
1304
1367
|
return (seq) => {
|
|
1305
1368
|
if (page !== void 0) return page({
|
|
@@ -5059,7 +5122,7 @@ window.__ModuleLoader__.load({
|
|
|
5059
5122
|
return function PluginInfo() {
|
|
5060
5123
|
const [latest, setLatest] = React.useState(null);
|
|
5061
5124
|
React.useEffect(() => {
|
|
5062
|
-
if ("0.
|
|
5125
|
+
if ("0.41.1".includes("-dev")) return;
|
|
5063
5126
|
let on = true;
|
|
5064
5127
|
fetchLatestVersion().then((v) => {
|
|
5065
5128
|
if (on && v) setLatest(v);
|
|
@@ -5068,8 +5131,8 @@ window.__ModuleLoader__.load({
|
|
|
5068
5131
|
on = false;
|
|
5069
5132
|
};
|
|
5070
5133
|
}, []);
|
|
5071
|
-
const update = latest !== null && isNewerVersion(latest, "0.
|
|
5072
|
-
const nameText = "dsh-context (v0.
|
|
5134
|
+
const update = latest !== null && isNewerVersion(latest, "0.41.1") ? latest : null;
|
|
5135
|
+
const nameText = "dsh-context (v0.41.1)";
|
|
5073
5136
|
const nameValue = [nameText];
|
|
5074
5137
|
if (update) nameValue.push(/* @__PURE__ */ React.createElement("span", {
|
|
5075
5138
|
key: "update",
|
|
@@ -5477,20 +5540,27 @@ window.__ModuleLoader__.load({
|
|
|
5477
5540
|
let segments = [];
|
|
5478
5541
|
let rows = [];
|
|
5479
5542
|
if (timing !== null && (wall > 0 || timing.calls > 0 || timing.toolCalls > 0)) {
|
|
5480
|
-
const
|
|
5481
|
-
const
|
|
5482
|
-
const
|
|
5543
|
+
const ttft = Math.min(timing.ttftMs, wall);
|
|
5544
|
+
const gen = Math.max(0, Math.min(timing.genMs, wall - ttft));
|
|
5545
|
+
const toolRing = Math.max(0, Math.min(timing.toolsMs, wall - ttft - gen));
|
|
5546
|
+
const other = Math.max(0, wall - ttft - gen - toolRing);
|
|
5483
5547
|
const share = (ms) => wall > 0 ? ms / wall : 0;
|
|
5484
5548
|
const countOf = (ms, times) => {
|
|
5485
5549
|
const dur = fmtDuration(ms, lang);
|
|
5486
5550
|
if (times === void 0) return dur;
|
|
5487
5551
|
return ms > 0 ? `${dur} · ${times}` : times;
|
|
5488
5552
|
};
|
|
5553
|
+
const callTimes = t("timing.callTimes", { n: fmt(timing.calls) });
|
|
5489
5554
|
segments = [
|
|
5490
5555
|
{
|
|
5491
|
-
key: "
|
|
5556
|
+
key: "ttft",
|
|
5492
5557
|
color: "#3b82f6",
|
|
5493
|
-
value: share(
|
|
5558
|
+
value: share(ttft)
|
|
5559
|
+
},
|
|
5560
|
+
{
|
|
5561
|
+
key: "gen",
|
|
5562
|
+
color: "#8b5cf6",
|
|
5563
|
+
value: share(gen)
|
|
5494
5564
|
},
|
|
5495
5565
|
{
|
|
5496
5566
|
key: "tools",
|
|
@@ -5505,12 +5575,20 @@ window.__ModuleLoader__.load({
|
|
|
5505
5575
|
];
|
|
5506
5576
|
rows = [
|
|
5507
5577
|
{
|
|
5508
|
-
key: "
|
|
5578
|
+
key: "ttft",
|
|
5509
5579
|
color: "#3b82f6",
|
|
5510
|
-
label: t("timing.
|
|
5511
|
-
dim: timing.
|
|
5512
|
-
pct: fmtShare(timing.
|
|
5513
|
-
count: countOf(timing.
|
|
5580
|
+
label: t("timing.ttft"),
|
|
5581
|
+
dim: timing.ttftMs === 0,
|
|
5582
|
+
pct: fmtShare(timing.ttftMs, wall),
|
|
5583
|
+
count: countOf(timing.ttftMs, callTimes)
|
|
5584
|
+
},
|
|
5585
|
+
{
|
|
5586
|
+
key: "gen",
|
|
5587
|
+
color: "#8b5cf6",
|
|
5588
|
+
label: t("timing.gen"),
|
|
5589
|
+
dim: timing.genMs === 0,
|
|
5590
|
+
pct: fmtShare(timing.genMs, wall),
|
|
5591
|
+
count: countOf(timing.genMs, callTimes)
|
|
5514
5592
|
},
|
|
5515
5593
|
{
|
|
5516
5594
|
key: "tools",
|
|
@@ -6431,7 +6509,7 @@ window.__ModuleLoader__.load({
|
|
|
6431
6509
|
}
|
|
6432
6510
|
//#endregion
|
|
6433
6511
|
//#region \0dsh-global-css:/home/runner/work/dsh-context/dsh-context/src/client/styles/base.css.mjs
|
|
6434
|
-
const css$13 = ".lc-root{box-sizing:border-box;height:100%;color:var(--dsw-alias-label-primary);padding:16px 20px 32px;font-size:13px;overflow-y:auto}.lc-card{background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);border-radius:10px;margin-bottom:14px;padding:14px 16px}.lc-card-title{flex-wrap:wrap;align-items:baseline;gap:8px;margin-bottom:10px;font-weight:600;display:flex}.lc-card-title-text{white-space:nowrap;flex:none}.lc-gran,.lc-kinds{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:6px;gap:2px;margin-left:auto;padding:1px;display:flex}.lc-trend-ctl{align-items:center;gap:6px;margin-left:auto;display:flex}.lc-trend-ctl .lc-gran{margin-left:0}.lc-gran-btn{color:var(--dsw-alias-label-secondary);cursor:pointer;transition:color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);background:0 0;border:0;border-radius:5px;padding:3px 8px;font-family:inherit;font-size:11px;line-height:1}.lc-gran-btn:hover{color:var(--dsw-alias-label-primary)}.lc-gran-on,.lc-gran-on:hover{background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground)}.lc-tip{z-index:6;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);width:max-content;color:var(--dsw-alias-label-primary);box-shadow:var(--dsw-shadow-lv3,0 2px 8px #0000002e);pointer-events:none;opacity:0;transition:opacity var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:6px;padding:6px 10px;font-size:12px;position:absolute}.lc-events,.lc-fa-list{flex-direction:column;gap:2px;height:320px;display:flex;overflow-y:auto}.lc-cols{flex-wrap:wrap;gap:14px;margin-bottom:14px;display:flex}.lc-col{flex:1;min-width:280px}.lc-cols>.lc-card,.lc-col>.lc-card:last-child{margin-bottom:0}.lc-col-browser{flex-direction:column;display:flex}.lc-col-browser>.lc-card{flex:1}.lc-head>.lc-card{flex:1 1 0;min-width:0;container:lc-head-card/inline-size}.lc-head>.lc-card:first-child{flex-direction:column;display:flex}.lc-head>.lc-card:first-child .lc-stats{flex:1;align-content:stretch}.lc-head>.lc-card:first-child .lc-stat{justify-content:center}.lc-empty{color:var(--dsw-alias-label-secondary);text-align:center;padding:18px 0}.lc-error{flex-direction:column;align-items:center;gap:8px;padding:40px 16px;display:flex}.lc-error-msg{font-family:var(--ds-font-family-code,ui-monospace, SFMono-Regular, Menlo, monospace);color:var(--dsw-alias-state-error-primary);background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);overflow-wrap:anywhere;border-radius:6px;max-width:100%;padding:4px 8px;font-size:12px}.lc-error-retry{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);font:inherit;cursor:pointer;transition:border-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:6px;padding:4px 14px;font-size:12px}.lc-error-retry:hover{border-color:var(--dsw-alias-label-primary)}.lc-foot{color:var(--dsw-alias-label-secondary);margin-top:4px;font-size:12px}[data-conversation-scroll]:has(.lc-root)>[data-composer-seat]:not(:has([data-approval-key],[data-question-key],[data-plan-review-key])){display:none}";
|
|
6512
|
+
const css$13 = ".lc-root{box-sizing:border-box;height:100%;color:var(--dsw-alias-label-primary);padding:16px 20px 32px;font-size:13px;overflow-y:auto}.lc-card{background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);border-radius:10px;margin-bottom:14px;padding:14px 16px}.lc-root .lc-card{margin-bottom:7px;padding:7px 8px}.lc-root .lc-cols{gap:7px;margin-bottom:7px}.lc-card-title{flex-wrap:wrap;align-items:baseline;gap:8px;margin-bottom:10px;font-weight:600;display:flex}.lc-card-title-text{white-space:nowrap;flex:none}.lc-gran,.lc-kinds{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:6px;gap:2px;margin-left:auto;padding:1px;display:flex}.lc-trend-ctl{align-items:center;gap:6px;margin-left:auto;display:flex}.lc-trend-ctl .lc-gran{margin-left:0}.lc-gran-btn{color:var(--dsw-alias-label-secondary);cursor:pointer;transition:color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);background:0 0;border:0;border-radius:5px;padding:3px 8px;font-family:inherit;font-size:11px;line-height:1}.lc-gran-btn:hover{color:var(--dsw-alias-label-primary)}.lc-gran-on,.lc-gran-on:hover{background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground)}.lc-tip{z-index:6;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);width:max-content;color:var(--dsw-alias-label-primary);box-shadow:var(--dsw-shadow-lv3,0 2px 8px #0000002e);pointer-events:none;opacity:0;transition:opacity var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:6px;padding:6px 10px;font-size:12px;position:absolute}.lc-events,.lc-fa-list{flex-direction:column;gap:2px;height:320px;display:flex;overflow-y:auto}.lc-cols{flex-wrap:wrap;gap:14px;margin-bottom:14px;display:flex}.lc-col{flex:1;min-width:280px}.lc-cols>.lc-card,.lc-col>.lc-card:last-child{margin-bottom:0}.lc-col-browser{flex-direction:column;display:flex}.lc-col-browser>.lc-card{flex:1}.lc-head>.lc-card{flex:1 1 0;min-width:0;container:lc-head-card/inline-size}.lc-head>.lc-card:first-child{flex-direction:column;display:flex}.lc-head>.lc-card:first-child .lc-stats{flex:1;align-content:stretch}.lc-head>.lc-card:first-child .lc-stat{justify-content:center}.lc-empty{color:var(--dsw-alias-label-secondary);text-align:center;padding:18px 0}.lc-error{flex-direction:column;align-items:center;gap:8px;padding:40px 16px;display:flex}.lc-error-msg{font-family:var(--ds-font-family-code,ui-monospace, SFMono-Regular, Menlo, monospace);color:var(--dsw-alias-state-error-primary);background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);overflow-wrap:anywhere;border-radius:6px;max-width:100%;padding:4px 8px;font-size:12px}.lc-error-retry{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);font:inherit;cursor:pointer;transition:border-color var(--ds-transition-duration,.2s) var(--ds-ease-in-out,ease-in-out);border-radius:6px;padding:4px 14px;font-size:12px}.lc-error-retry:hover{border-color:var(--dsw-alias-label-primary)}.lc-foot{color:var(--dsw-alias-label-secondary);margin-top:4px;font-size:12px}[data-conversation-scroll]:has(.lc-root)>[data-composer-seat]:not(:has([data-approval-key],[data-question-key],[data-plan-review-key])){display:none}";
|
|
6435
6513
|
const tagId$13 = "dsh-context/base.css";
|
|
6436
6514
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$13) + "]") === null) {
|
|
6437
6515
|
const tag = document.createElement("style");
|
|
@@ -6617,6 +6695,7 @@ window.__ModuleLoader__.load({
|
|
|
6617
6695
|
}, "dsh-context: dictionaries");
|
|
6618
6696
|
const t = ctx.locale.bind(NS);
|
|
6619
6697
|
const kit = makeViewKit(t);
|
|
6698
|
+
watchHistoryFaces(ctx);
|
|
6620
6699
|
const settings = createContextSettings();
|
|
6621
6700
|
const ContextView = makeContextView(ctx, kit, settings);
|
|
6622
6701
|
ctx.slots.inject("conversation.view", () => {
|
package/lib/index.d.ts
CHANGED
|
@@ -112,14 +112,18 @@ interface TimelineState {
|
|
|
112
112
|
timing?: TimingTotals;
|
|
113
113
|
/**
|
|
114
114
|
* The open step's start instant, armed by `step/start` and consumed by the
|
|
115
|
-
* `assistant/message` (
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
115
|
+
* `assistant/message` (TTFT/generation split) and `step/end` (wall time)
|
|
116
|
+
* that follow it; `assistant/chunk` stamps `firstToken` on the step's first
|
|
117
|
+
* token delta — absent when the stream carried none (legacy or aborted
|
|
118
|
+
* steps), which leaves that call's model time unattributed. One slot, not a
|
|
119
|
+
* map: steps are sequential in the log, so the newest `step/start` is the
|
|
120
|
+
* one those events close — a hostile interleaved log degrades to skipped
|
|
121
|
+
* durations, never to unbounded state. Same arm/remove lifecycle as
|
|
122
|
+
* `pendingShadowedSeqs`.
|
|
120
123
|
*/
|
|
121
124
|
stepStart?: {
|
|
122
125
|
time: number;
|
|
126
|
+
firstToken?: number;
|
|
123
127
|
};
|
|
124
128
|
/**
|
|
125
129
|
* Tool callId → the call's name and start instant, armed by `tool/call` and
|
|
@@ -288,18 +292,22 @@ interface ToolTimingTotals {
|
|
|
288
292
|
}
|
|
289
293
|
/**
|
|
290
294
|
* Whole-session timing totals, host-folded from the durable `step/start` /
|
|
291
|
-
`step/end` / `tool/call` / `tool/result` lifecycle
|
|
292
|
-
* COMPLETE session log — the same never-trimmed
|
|
293
|
-
* are wall-clock milliseconds: `wallMs` sums
|
|
294
|
-
* step-start →
|
|
295
|
-
*
|
|
295
|
+
`step/end` / `assistant/chunk` / `tool/call` / `tool/result` lifecycle
|
|
296
|
+
* (running totals over the COMPLETE session log — the same never-trimmed
|
|
297
|
+
* framing as `cost`). Durations are wall-clock milliseconds: `wallMs` sums
|
|
298
|
+
* whole steps, `ttftMs` the step-start → first-token slice (the model wait)
|
|
299
|
+
* and `genMs` the first-token → assistant-message slice (the generation) —
|
|
300
|
+
* both only over calls whose stream carried a token delta, `toolsMs` the sum
|
|
301
|
+
* of per-call tool durations (parallel calls each count, so it can overlap).
|
|
296
302
|
* Absent until the first step lifecycle completes in the log.
|
|
297
303
|
*/
|
|
298
304
|
interface TimingTotals {
|
|
299
305
|
/** Summed wall time of completed steps (the session's active time). */
|
|
300
306
|
wallMs: number;
|
|
301
|
-
/** Summed step-start →
|
|
302
|
-
|
|
307
|
+
/** Summed step-start → first-token time (the model wait, TTFT). */
|
|
308
|
+
ttftMs: number;
|
|
309
|
+
/** Summed first-token → assistant-message time (the generation). */
|
|
310
|
+
genMs: number;
|
|
303
311
|
/** Completed model calls (assistant messages folded). */
|
|
304
312
|
calls: number;
|
|
305
313
|
/** Summed per-call durations of completed tool calls. */
|
package/lib/index.js
CHANGED
|
@@ -993,6 +993,21 @@ function durOf(from, to) {
|
|
|
993
993
|
return Math.max(0, to - from);
|
|
994
994
|
}
|
|
995
995
|
/**
|
|
996
|
+
* Whether a stream chunk carries a non-empty token delta — the first-token
|
|
997
|
+
* marker the TTFT fold waits for (the same rule as the harness's own
|
|
998
|
+
* session-stats fold). Shape-guarded: a malformed chunk is just not a token.
|
|
999
|
+
*/
|
|
1000
|
+
function isTokenDelta(chunk) {
|
|
1001
|
+
if (chunk === null || typeof chunk !== "object") return false;
|
|
1002
|
+
const c = chunk;
|
|
1003
|
+
switch (c.type) {
|
|
1004
|
+
case "text-delta":
|
|
1005
|
+
case "reasoning-delta": return typeof c.text === "string" && c.text !== "";
|
|
1006
|
+
case "tool-call-delta": return typeof c.argumentsDelta === "string" && c.argumentsDelta !== "" || c.name !== void 0;
|
|
1007
|
+
default: return false;
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
/**
|
|
996
1011
|
* The fold's private timing accumulator: created on first use, and CLONED on
|
|
997
1012
|
* every later ensure() (see `applyTimeline`) — the object left in the
|
|
998
1013
|
* persisted previous state is never written into in place.
|
|
@@ -1000,7 +1015,8 @@ function durOf(from, to) {
|
|
|
1000
1015
|
function ensureTiming(st) {
|
|
1001
1016
|
if (st.timing === void 0) st.timing = {
|
|
1002
1017
|
wallMs: 0,
|
|
1003
|
-
|
|
1018
|
+
ttftMs: 0,
|
|
1019
|
+
genMs: 0,
|
|
1004
1020
|
calls: 0,
|
|
1005
1021
|
toolsMs: 0,
|
|
1006
1022
|
toolCalls: 0,
|
|
@@ -1091,6 +1107,17 @@ function applyTimeline(state, event, bounds) {
|
|
|
1091
1107
|
};
|
|
1092
1108
|
}
|
|
1093
1109
|
break;
|
|
1110
|
+
case "assistant/chunk": {
|
|
1111
|
+
const start = state.stepStart;
|
|
1112
|
+
if (start === void 0 || start.firstToken !== void 0) return state;
|
|
1113
|
+
if (!isTokenDelta(data?.chunk)) return state;
|
|
1114
|
+
const s = ensure();
|
|
1115
|
+
s.stepStart = {
|
|
1116
|
+
time: start.time,
|
|
1117
|
+
firstToken: event.time
|
|
1118
|
+
};
|
|
1119
|
+
break;
|
|
1120
|
+
}
|
|
1094
1121
|
case "step/start": {
|
|
1095
1122
|
const s = ensure();
|
|
1096
1123
|
s.stepStart = { time: event.time };
|
|
@@ -1177,7 +1204,10 @@ function applyTimeline(state, event, bounds) {
|
|
|
1177
1204
|
const timing = ensureTiming(s);
|
|
1178
1205
|
timing.calls += 1;
|
|
1179
1206
|
const stepStart = state.stepStart;
|
|
1180
|
-
if (stepStart !== void 0
|
|
1207
|
+
if (stepStart !== void 0 && stepStart.firstToken !== void 0) {
|
|
1208
|
+
timing.ttftMs += durOf(stepStart.time, stepStart.firstToken);
|
|
1209
|
+
timing.genMs += durOf(stepStart.firstToken, event.time);
|
|
1210
|
+
}
|
|
1181
1211
|
const asstMsg = deriveEventMessage(event);
|
|
1182
1212
|
applySurface(s, event, event.type, data, asstMsg);
|
|
1183
1213
|
break;
|
|
@@ -1404,7 +1434,8 @@ const toolTimingSchema = z.object({
|
|
|
1404
1434
|
}).strict();
|
|
1405
1435
|
const timingTotalsSchema = z.object({
|
|
1406
1436
|
wallMs: z.number().nonnegative(),
|
|
1407
|
-
|
|
1437
|
+
ttftMs: z.number().nonnegative(),
|
|
1438
|
+
genMs: z.number().nonnegative(),
|
|
1408
1439
|
calls: z.number().int().nonnegative(),
|
|
1409
1440
|
toolsMs: z.number().nonnegative(),
|
|
1410
1441
|
toolCalls: z.number().int().nonnegative(),
|
|
@@ -1460,7 +1491,10 @@ const timelineStateSchema = z.object({
|
|
|
1460
1491
|
}).strict().optional(),
|
|
1461
1492
|
archiveFloor: z.number().optional(),
|
|
1462
1493
|
timing: timingTotalsSchema.optional(),
|
|
1463
|
-
stepStart: z.object({
|
|
1494
|
+
stepStart: z.object({
|
|
1495
|
+
time: z.number(),
|
|
1496
|
+
firstToken: z.number().optional()
|
|
1497
|
+
}).strict().optional(),
|
|
1464
1498
|
callNames: z.record(z.string(), z.object({
|
|
1465
1499
|
name: z.string(),
|
|
1466
1500
|
start: z.number()
|
|
@@ -1499,7 +1533,7 @@ function createContextTimelineDefinition(config) {
|
|
|
1499
1533
|
},
|
|
1500
1534
|
init: () => createTimelineState(),
|
|
1501
1535
|
apply: (state, event) => applyTimeline(state, event, bounds),
|
|
1502
|
-
stateVersion:
|
|
1536
|
+
stateVersion: 12
|
|
1503
1537
|
};
|
|
1504
1538
|
}
|
|
1505
1539
|
//#endregion
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-context",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.41.1",
|
|
4
4
|
"description": "A DeepSeek Harness plugin for context insight and management, with context dashboard and context command, for understanding how the context is made of, and how it evolves.",
|
|
5
5
|
"author": "bowenliang123",
|
|
6
6
|
"repository": {
|
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
},
|
|
50
50
|
"client": {
|
|
51
51
|
"inject": [
|
|
52
|
+
"@deepseek-ai/dsh-api-remotes",
|
|
52
53
|
"@deepseek-ai/dsh-client-connection",
|
|
53
54
|
"@deepseek-ai/dsh-client-locale",
|
|
54
55
|
"@deepseek-ai/dsh-client-ui-conversation",
|