@khorsheed/dsh-context-lens 0.0.0-stage → 0.1.0
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/CHANGELOG.md +16 -0
- package/README.en.md +53 -0
- package/README.i18n.yaml +6 -0
- package/README.md +52 -2
- package/cordis.patch.yml +13 -0
- package/lib/client.js +26953 -0
- package/lib/index.js +3943 -0
- package/lib/typert.host.d.ts +3 -0
- package/lib/typert.host.js +1569 -0
- package/lib/typert.remote-client.d.ts +40 -0
- package/lib/typert.remote-client.js +1202 -0
- package/lib/types/changes.d.ts +72 -0
- package/lib/types/changes.js +328 -0
- package/lib/types/client/AgentTabs.d.ts +34 -0
- package/lib/types/client/AgentTabs.js +89 -0
- package/lib/types/client/ChangeList.d.ts +28 -0
- package/lib/types/client/ChangeList.js +109 -0
- package/lib/types/client/ConfigFacts.d.ts +21 -0
- package/lib/types/client/ConfigFacts.js +30 -0
- package/lib/types/client/HeaderUtility.d.ts +12 -0
- package/lib/types/client/HeaderUtility.js +8 -0
- package/lib/types/client/InspectorPanel.d.ts +5 -0
- package/lib/types/client/InspectorPanel.js +486 -0
- package/lib/types/client/RawView.d.ts +18 -0
- package/lib/types/client/RawView.js +8 -0
- package/lib/types/client/RequestFacts.d.ts +19 -0
- package/lib/types/client/RequestFacts.js +39 -0
- package/lib/types/client/RoomDecisionsView.d.ts +24 -0
- package/lib/types/client/RoomDecisionsView.js +52 -0
- package/lib/types/client/SchemaFields.d.ts +13 -0
- package/lib/types/client/SchemaFields.js +96 -0
- package/lib/types/client/SegCtl.d.ts +32 -0
- package/lib/types/client/SegCtl.js +12 -0
- package/lib/types/client/SegmentBlocks.d.ts +28 -0
- package/lib/types/client/SegmentBlocks.js +225 -0
- package/lib/types/client/TimingView.d.ts +17 -0
- package/lib/types/client/TimingView.js +98 -0
- package/lib/types/client/TokenView.d.ts +21 -0
- package/lib/types/client/TokenView.js +22 -0
- package/lib/types/client/ToolCatalog.d.ts +9 -0
- package/lib/types/client/ToolCatalog.js +51 -0
- package/lib/types/client/Trend.d.ts +25 -0
- package/lib/types/client/Trend.js +100 -0
- package/lib/types/client/TurnAction.d.ts +14 -0
- package/lib/types/client/TurnAction.js +31 -0
- package/lib/types/client/TurnMenu.d.ts +36 -0
- package/lib/types/client/TurnMenu.js +50 -0
- package/lib/types/client/aggregate.d.ts +141 -0
- package/lib/types/client/aggregate.js +213 -0
- package/lib/types/client/contract.d.ts +58 -0
- package/lib/types/client/contract.js +1 -0
- package/lib/types/client/definition.d.ts +47 -0
- package/lib/types/client/definition.js +25 -0
- package/lib/types/client/icons-local.d.ts +19 -0
- package/lib/types/client/icons-local.js +3 -0
- package/lib/types/client/icons.d.ts +18 -0
- package/lib/types/client/icons.js +2 -0
- package/lib/types/client/index.d.ts +42 -0
- package/lib/types/client/index.js +139 -0
- package/lib/types/client/locales.d.ts +365 -0
- package/lib/types/client/locales.js +705 -0
- package/lib/types/client/preset-visibility.d.ts +99 -0
- package/lib/types/client/preset-visibility.js +108 -0
- package/lib/types/client/scroll-sync.d.ts +54 -0
- package/lib/types/client/scroll-sync.js +118 -0
- package/lib/types/client/timing.d.ts +29 -0
- package/lib/types/client/timing.js +117 -0
- package/lib/types/client/wire.d.ts +13 -0
- package/lib/types/client/wire.js +11 -0
- package/lib/types/index.d.ts +32 -0
- package/lib/types/index.js +25 -0
- package/lib/types/log-lane.d.ts +198 -0
- package/lib/types/log-lane.js +841 -0
- package/lib/types/messages.d.ts +96 -0
- package/lib/types/messages.js +254 -0
- package/lib/types/prefix-diff.d.ts +4 -0
- package/lib/types/prefix-diff.js +20 -0
- package/lib/types/remote.d.ts +45 -0
- package/lib/types/remote.js +99 -0
- package/lib/types/room.d.ts +66 -0
- package/lib/types/room.js +101 -0
- package/lib/types/segmenter.d.ts +100 -0
- package/lib/types/segmenter.js +184 -0
- package/lib/types/service.d.ts +145 -0
- package/lib/types/service.js +493 -0
- package/lib/types/store.d.ts +424 -0
- package/lib/types/store.js +1920 -0
- package/lib/types/tool-summary.d.ts +6 -0
- package/lib/types/tool-summary.js +7 -0
- package/lib/types/types.d.ts +703 -0
- package/lib/types/types.js +18 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +133 -3
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Render one {@link ResponseSummary} for the card title / anchor header:
|
|
3
|
+
* the tool case groups repeated names as `name ×N` (single calls keep hints), the text case is the
|
|
4
|
+
* caller's localized assistant reply, the reasoning case is a pure thinking response, an empty
|
|
5
|
+
* response is the plain dash.
|
|
6
|
+
*/
|
|
7
|
+
export function summaryText(summary, assistantReply, compaction = 'Compaction', thinking = 'Thinking') {
|
|
8
|
+
switch (summary.kind) {
|
|
9
|
+
case 'compaction':
|
|
10
|
+
return compaction;
|
|
11
|
+
case 'tool':
|
|
12
|
+
if (summary.toolCounts?.length)
|
|
13
|
+
return summary.toolCounts.map(tool => `${tool.name}${tool.count > 1 ? ` ×${tool.count}` : ''}`).join(' · ');
|
|
14
|
+
// Older hosts only expose the first name and a total; never guess repeated tools.
|
|
15
|
+
return `${summary.name}${summary.hint === undefined ? '' : `:${summary.hint}`}${summary.more === undefined ? '' : ` +${summary.more}`}`;
|
|
16
|
+
case 'text':
|
|
17
|
+
return assistantReply;
|
|
18
|
+
case 'reasoning':
|
|
19
|
+
return thinking;
|
|
20
|
+
case 'none':
|
|
21
|
+
return '—';
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Resolve an assistant message id to its turn: the turn whose
|
|
26
|
+
* `closingMessageId` matches, else the latest turn (the list arrives newest
|
|
27
|
+
* last), else undefined when the session has no captured turns.
|
|
28
|
+
*/
|
|
29
|
+
export function resolveTurnSeq(turns, messageId) {
|
|
30
|
+
if (turns.length === 0)
|
|
31
|
+
return undefined;
|
|
32
|
+
if (messageId !== undefined) {
|
|
33
|
+
const match = turns.find(turn => turn.closingMessageId === messageId);
|
|
34
|
+
if (match !== undefined)
|
|
35
|
+
return match.turnSeq;
|
|
36
|
+
}
|
|
37
|
+
return turns[turns.length - 1]?.turnSeq;
|
|
38
|
+
}
|
|
39
|
+
/** The latest turn's seq (the panel's default), undefined on an empty list. */
|
|
40
|
+
export function latestTurnSeq(turns) {
|
|
41
|
+
return turns[turns.length - 1]?.turnSeq;
|
|
42
|
+
}
|
|
43
|
+
/** Turns per backfill window (the store's default `maxTurnsPerSession`). */
|
|
44
|
+
export const WINDOW_TURNS = 30;
|
|
45
|
+
/** The unloaded runs: the store's (numbered with room rounds), else derived from the log's host-turn range. */
|
|
46
|
+
export function turnGaps(list, turns) {
|
|
47
|
+
if (list?.unloaded !== undefined)
|
|
48
|
+
return list.unloaded.map(run => ({ from: run.firstHost, to: run.lastHost, shown: { from: run.from, to: run.to } }));
|
|
49
|
+
return unloadedGaps(list?.range, turns);
|
|
50
|
+
}
|
|
51
|
+
/** A gap's displayed span. */
|
|
52
|
+
export const gapShown = (gap) => gap.shown ?? gap;
|
|
53
|
+
/** A turn's displayed number: its place in the session's numbering, else its host turn. */
|
|
54
|
+
export const turnNumber = (turn) => turn.ordinal ?? turn.hostTurn;
|
|
55
|
+
/** The runs of turns in the log's range that the loaded list lacks, oldest first. */
|
|
56
|
+
export function unloadedGaps(range, turns) {
|
|
57
|
+
if (range === undefined)
|
|
58
|
+
return [];
|
|
59
|
+
const loaded = new Set(turns.map(turn => turn.hostTurn).filter((n) => n !== undefined));
|
|
60
|
+
const gaps = [];
|
|
61
|
+
for (let n = range.first; n <= range.last; n += 1) {
|
|
62
|
+
if (loaded.has(n))
|
|
63
|
+
continue;
|
|
64
|
+
const last = gaps.at(-1);
|
|
65
|
+
if (last !== undefined && last.to === n - 1)
|
|
66
|
+
last.to = n;
|
|
67
|
+
else
|
|
68
|
+
gaps.push({ from: n, to: n });
|
|
69
|
+
}
|
|
70
|
+
return gaps;
|
|
71
|
+
}
|
|
72
|
+
/** Total input = uncached + cache-read + cache-write (the host's usage fields are disjoint). */
|
|
73
|
+
export function inputTotal(usage) {
|
|
74
|
+
return usage.inputTokens + (usage.cacheReadTokens ?? 0) + (usage.cacheWriteTokens ?? 0);
|
|
75
|
+
}
|
|
76
|
+
/** Input NOT served from cache = uncached + cache-write (a cache write was processed in full too). */
|
|
77
|
+
export function inputMiss(usage) {
|
|
78
|
+
return usage.inputTokens + (usage.cacheWriteTokens ?? 0);
|
|
79
|
+
}
|
|
80
|
+
/** Cache-hit percentage of the total input, one decimal; null when nothing was read. */
|
|
81
|
+
export function cachePct(usage) {
|
|
82
|
+
const total = inputTotal(usage);
|
|
83
|
+
// A provider that does not report cache reads: unknown, not 0%.
|
|
84
|
+
if (total === 0 || usage.cacheReadTokens === undefined)
|
|
85
|
+
return null;
|
|
86
|
+
return Math.round(((usage.cacheReadTokens ?? 0) / total) * 1000) / 10;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* One request's composition by category: the system/history/added message
|
|
90
|
+
* items plus the tools segment as `tool-definition`, sorted by chars desc.
|
|
91
|
+
*/
|
|
92
|
+
export function aggregateCategories(request) {
|
|
93
|
+
const byCategory = new Map();
|
|
94
|
+
const add = (category, chars) => {
|
|
95
|
+
const entry = byCategory.get(category) ?? { category, count: 0, chars: 0 };
|
|
96
|
+
entry.count += 1;
|
|
97
|
+
entry.chars += chars;
|
|
98
|
+
byCategory.set(category, entry);
|
|
99
|
+
};
|
|
100
|
+
for (const item of [...request.segments.system, ...request.segments.history, ...request.segments.added]) {
|
|
101
|
+
add(item.category, item.chars);
|
|
102
|
+
}
|
|
103
|
+
for (const tool of request.segments.tools)
|
|
104
|
+
add('tool-definition', tool.chars);
|
|
105
|
+
return [...byCategory.values()].sort((a, b) => b.chars - a.chars);
|
|
106
|
+
}
|
|
107
|
+
/** The timing vocabulary, in timeline order (the design's six-way split). */
|
|
108
|
+
export const TIMING_KINDS = ['wait', 'reasoning', 'tool-args', 'text', 'tool-exec', 'other'];
|
|
109
|
+
const ZERO_KINDS = () => ({
|
|
110
|
+
wait: 0, reasoning: 0, 'tool-args': 0, text: 0, 'tool-exec': 0, other: 0,
|
|
111
|
+
});
|
|
112
|
+
/**
|
|
113
|
+
* Reduce the turn's lanes to the timing tab's data. Lanes are strictly
|
|
114
|
+
* ordered and carry their scope, so a scope filter is exact; gaps between
|
|
115
|
+
* lanes (inter-request overhead) fold into `other`, the reference design's
|
|
116
|
+
* 调用间隙并入其他开销. Whole-turn, the bucket sums equal the host's
|
|
117
|
+
* `TurnTiming.buckets` by construction (the host sums the same lanes).
|
|
118
|
+
*/
|
|
119
|
+
export function summarizeLanes(lanes, scopeId) {
|
|
120
|
+
const filtered = (scopeId === undefined ? lanes : lanes.filter(lane => lane.scopeId === scopeId))
|
|
121
|
+
.slice()
|
|
122
|
+
.sort((a, b) => a.startedAt - b.startedAt || a.requestSeq - b.requestSeq);
|
|
123
|
+
const buckets = ZERO_KINDS();
|
|
124
|
+
const counts = ZERO_KINDS();
|
|
125
|
+
const tools = new Map();
|
|
126
|
+
const requests = new Map();
|
|
127
|
+
let t0 = 0;
|
|
128
|
+
let t1 = 0;
|
|
129
|
+
let cursor;
|
|
130
|
+
for (const lane of filtered) {
|
|
131
|
+
const end = lane.startedAt + lane.durationMs;
|
|
132
|
+
if (cursor === undefined) {
|
|
133
|
+
t0 = lane.startedAt;
|
|
134
|
+
}
|
|
135
|
+
else if (lane.startedAt > cursor) {
|
|
136
|
+
// The inter-lane gap is turn overhead the host never lane-tags.
|
|
137
|
+
buckets.other += lane.startedAt - cursor;
|
|
138
|
+
}
|
|
139
|
+
cursor = cursor === undefined ? end : Math.max(cursor, end);
|
|
140
|
+
t1 = Math.max(t1, end);
|
|
141
|
+
buckets[lane.kind] += lane.durationMs;
|
|
142
|
+
counts[lane.kind] += 1;
|
|
143
|
+
if (lane.tool !== undefined) {
|
|
144
|
+
const aggregate = tools.get(lane.tool) ?? { tool: lane.tool, calls: 0, argsMs: 0, execMs: 0 };
|
|
145
|
+
if (lane.kind === 'tool-exec') {
|
|
146
|
+
aggregate.calls += 1;
|
|
147
|
+
aggregate.execMs += lane.durationMs;
|
|
148
|
+
}
|
|
149
|
+
else if (lane.kind === 'tool-args') {
|
|
150
|
+
aggregate.argsMs += lane.durationMs;
|
|
151
|
+
}
|
|
152
|
+
tools.set(lane.tool, aggregate);
|
|
153
|
+
}
|
|
154
|
+
const request = requests.get(lane.requestSeq) ?? {
|
|
155
|
+
requestSeq: lane.requestSeq, scopeId: lane.scopeId,
|
|
156
|
+
startedAt: lane.startedAt, endedAt: end, byKind: ZERO_KINDS(),
|
|
157
|
+
};
|
|
158
|
+
request.startedAt = Math.min(request.startedAt, lane.startedAt);
|
|
159
|
+
request.endedAt = Math.max(request.endedAt, end);
|
|
160
|
+
request.byKind[lane.kind] += lane.durationMs;
|
|
161
|
+
requests.set(lane.requestSeq, request);
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
buckets,
|
|
165
|
+
counts,
|
|
166
|
+
t0,
|
|
167
|
+
t1,
|
|
168
|
+
tools: [...tools.values()].sort((a, b) => b.execMs - a.execMs),
|
|
169
|
+
requests: [...requests.values()].sort((a, b) => a.startedAt - b.startedAt || a.requestSeq - b.requestSeq),
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
/** Thousands-separated integer (the design's fmt). */
|
|
173
|
+
export function fmt(n) {
|
|
174
|
+
return n.toLocaleString('en-US');
|
|
175
|
+
}
|
|
176
|
+
/** Compact count: 12.3k over a thousand (the design's fmtK). */
|
|
177
|
+
export function fmtK(n) {
|
|
178
|
+
return n >= 1000 ? `${(n / 1000).toFixed(1)}k` : String(n);
|
|
179
|
+
}
|
|
180
|
+
/** Wall-clock HH:MM of a timestamp (the timeline scale). */
|
|
181
|
+
export function fmtClock(timestamp) {
|
|
182
|
+
const date = new Date(timestamp);
|
|
183
|
+
const pad = (value) => String(value).padStart(2, '0');
|
|
184
|
+
return `${pad(date.getHours())}:${pad(date.getMinutes())}`;
|
|
185
|
+
}
|
|
186
|
+
/** Row-level duration: milliseconds under a second, whole seconds above (the design's fmtDur). */
|
|
187
|
+
export function fmtDur(ms) {
|
|
188
|
+
return fmtSpan(ms);
|
|
189
|
+
}
|
|
190
|
+
/** Span duration: one decimal in seconds, minutes above 60s (the design's ms()). */
|
|
191
|
+
export function fmtSpan(ms) {
|
|
192
|
+
return ms >= 60000
|
|
193
|
+
? `${Math.floor(Math.round(ms / 100) / 600)}m ${(Math.round(ms / 100) % 600 / 10).toFixed(1)}s`
|
|
194
|
+
: `${(ms / 1000).toFixed(1)}s`;
|
|
195
|
+
}
|
|
196
|
+
/** Exact full-text matches, excluding the first occurrence from excess size. */
|
|
197
|
+
export function duplicateStats(items) {
|
|
198
|
+
const counts = new Map();
|
|
199
|
+
let extraChars = 0;
|
|
200
|
+
for (const item of items) {
|
|
201
|
+
if (item.full.length < 40)
|
|
202
|
+
continue;
|
|
203
|
+
const count = counts.get(item.full) ?? 0;
|
|
204
|
+
if (count > 0)
|
|
205
|
+
extraChars += item.chars;
|
|
206
|
+
counts.set(item.full, count + 1);
|
|
207
|
+
}
|
|
208
|
+
return { counts, extraChars };
|
|
209
|
+
}
|
|
210
|
+
/** Public ordinal only. The internal seq must never be a display fallback. */
|
|
211
|
+
export function requestNumberText(request) {
|
|
212
|
+
return request?.requestNumber === undefined ? '—' : String(request.requestNumber);
|
|
213
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slot-facing types of the context-lens client half: the injected faces
|
|
3
|
+
* and the composed props of its three entries — the right-Sidebar page tab
|
|
4
|
+
* (`sidebar.right.pane.tab`), the per-turn icon in the assistant message's
|
|
5
|
+
* action row (`conversation.chat.assistant-actions`), and the session-header
|
|
6
|
+
* utility (`conversation.session.header.utilities`).
|
|
7
|
+
*/
|
|
8
|
+
import type { SessionId } from '@deepseek-ai/dsh-api-remotes/client';
|
|
9
|
+
import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
|
|
10
|
+
import type { RemoteResult, TypertRemoteNamespaceMap } from '@deepseek-ai/dsh-typert-protocol';
|
|
11
|
+
import type { SessionTrend, CapturedRequest, ListTurnsResult, ScopedRefusal, TurnOverview, TurnTiming } from '../types.ts';
|
|
12
|
+
/** The requestInspector Remote namespace (as mounted by this plugin). */
|
|
13
|
+
export type InspectorRemote = TypertRemoteNamespaceMap['requestInspector'];
|
|
14
|
+
/** Business face injected into the right-Sidebar tab body. */
|
|
15
|
+
export interface InspectorTabInjected {
|
|
16
|
+
getSessionTrend?: (sessionId: SessionId) => Promise<RemoteResult<SessionTrend>>;
|
|
17
|
+
/** Light turn list of one session plus its backfill watermark, newest last. */
|
|
18
|
+
listTurns: (sessionId: SessionId) => Promise<RemoteResult<ListTurnsResult>>;
|
|
19
|
+
/**
|
|
20
|
+
* Page the chat's loaded history back until it covers log `seq` (the
|
|
21
|
+
* session's own jump loader, the one message-timeline uses). Absent on a
|
|
22
|
+
* host without a session binding; a jump then only scrolls what is loaded.
|
|
23
|
+
*/
|
|
24
|
+
loadThrough?: (sessionId: SessionId, seq: number) => Promise<void>;
|
|
25
|
+
/** Move the loaded window to the turns ending at `end` (latest when null); answers the new list. */
|
|
26
|
+
loadTurns?: (sessionId: SessionId, end: number | null) => Promise<RemoteResult<ListTurnsResult>>;
|
|
27
|
+
/** One turn's scope/request overview — the left column, no segments. */
|
|
28
|
+
getTurn: (sessionId: SessionId, turnSeq: number) => Promise<RemoteResult<TurnOverview | undefined>>;
|
|
29
|
+
/**
|
|
30
|
+
* One request's full four-segment breakdown — the right column. The
|
|
31
|
+
* {@link ScopedRefusal} half only answers a query that carried a scope
|
|
32
|
+
* filter; the panel never sends one and narrows it away at the call site.
|
|
33
|
+
*/
|
|
34
|
+
getRequest: (sessionId: SessionId, seq: number) => Promise<RemoteResult<CapturedRequest | ScopedRefusal | undefined>>;
|
|
35
|
+
/** One turn's whole-turn timing aggregation — the 耗时分布 tab. */
|
|
36
|
+
getTurnTiming: (sessionId: SessionId, turnSeq: number) => Promise<RemoteResult<TurnTiming | undefined>>;
|
|
37
|
+
}
|
|
38
|
+
/** Full props of the right-Sidebar tab body entry. */
|
|
39
|
+
export type InspectorTabProps = PropsRuntime<'sidebar.right.pane.tab'> & InjectFace<InspectorTabInjected> & PropsLocale<'requestInspector'>;
|
|
40
|
+
/**
|
|
41
|
+
* Business face injected into the assistant message's turn action: resolve
|
|
42
|
+
* the message's turn (`TurnListItem.closingMessageId`, latest-turn fallback)
|
|
43
|
+
* and open the inspector tab on it.
|
|
44
|
+
*/
|
|
45
|
+
export interface InspectorActionInjected {
|
|
46
|
+
/** Resolve `messageId`'s turn and open the tab there. Never throws. */
|
|
47
|
+
open: (sessionId: SessionId, messageId: string) => Promise<void>;
|
|
48
|
+
}
|
|
49
|
+
/** Full props of the assistant-message turn action entry. */
|
|
50
|
+
export type InspectorActionProps = PropsRuntime<'conversation.chat.assistant-actions'> & InjectFace<InspectorActionInjected> & PropsLocale<'requestInspector'>;
|
|
51
|
+
/** Business face injected into the session-header utility: open the tab at the latest turn. */
|
|
52
|
+
export interface InspectorUtilityInjected {
|
|
53
|
+
/** Open the inspector tab at the session's latest turn. Never throws. */
|
|
54
|
+
openLatest: (sessionId: SessionId) => void;
|
|
55
|
+
}
|
|
56
|
+
/** Full props of the session-header utility entry. */
|
|
57
|
+
export type InspectorUtilityProps = PropsRuntime<'conversation.session.header.utilities'> & InjectFace<InspectorUtilityInjected> & PropsLocale<'requestInspector'>;
|
|
58
|
+
//# sourceMappingURL=contract.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stage one of this package's right-Sidebar registration: what the
|
|
3
|
+
* `context-lens` tab type IS.
|
|
4
|
+
*
|
|
5
|
+
* The type is a page, not a viewer: it claims no address and is opened by
|
|
6
|
+
* kind — by the session-header utility, by the assistant message's turn
|
|
7
|
+
* action, or from the guide — through `ctx.sidebarRight.openTab(
|
|
8
|
+
* 'context-lens', { params: { turnSeq } })`. Pages deduplicate within a
|
|
9
|
+
* pane, so a repeat open re-navigates the one tab and the body follows
|
|
10
|
+
* `navigation.params`.
|
|
11
|
+
*
|
|
12
|
+
* @module @khorsheed/dsh-context-lens/client
|
|
13
|
+
*/
|
|
14
|
+
import type { SidebarRightTabDefinition } from '@deepseek-ai/dsh-client-ui-sidebar-right/client';
|
|
15
|
+
import type { TranslateNS } from '@deepseek-ai/dsh-client-locale/client';
|
|
16
|
+
/** The tab kind this package owns. */
|
|
17
|
+
export declare const REQUEST_INSPECTOR_KIND = "context-lens";
|
|
18
|
+
/** This implementation's identity in the tab system, and the key its body registers under. */
|
|
19
|
+
export declare const REQUEST_INSPECTOR_TAB_ID = "@khorsheed/dsh-context-lens";
|
|
20
|
+
/** Navigation params an `openTab` caller hands the tab: which turn to show. */
|
|
21
|
+
export interface RequestInspectorTabParams {
|
|
22
|
+
/** 1-based per-session turn number; absent = the latest turn. */
|
|
23
|
+
readonly turnSeq?: number;
|
|
24
|
+
/**
|
|
25
|
+
* Inspect ANOTHER session than the one the sidebar belongs to (a room
|
|
26
|
+
* member's child session); `messageId` picks the turn that message closed.
|
|
27
|
+
* The next navigation without a session returns to the sidebar's own.
|
|
28
|
+
*/
|
|
29
|
+
readonly sessionId?: string;
|
|
30
|
+
readonly messageId?: string;
|
|
31
|
+
/** A room decision (`room/decision` id): opens the turn holding it on the 房间 tab, that call selected. */
|
|
32
|
+
readonly roomDecision?: string;
|
|
33
|
+
}
|
|
34
|
+
declare module '@deepseek-ai/dsh-client-ui-sidebar-right/client' {
|
|
35
|
+
interface SidebarRightTabParamsMap {
|
|
36
|
+
/** The context-lens page's initial turn. */
|
|
37
|
+
'context-lens': RequestInspectorTabParams;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The type's registry definition. No `priority`: the default `extension` band
|
|
42
|
+
* is exactly what a type shipped from outside the product is.
|
|
43
|
+
* @param t - namespace-bound translate, read fresh on every label call.
|
|
44
|
+
* @returns the definition to register.
|
|
45
|
+
*/
|
|
46
|
+
export declare function requestInspectorDefinition(t: TranslateNS<'requestInspector'>): SidebarRightTabDefinition;
|
|
47
|
+
//# sourceMappingURL=definition.d.ts.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { IconRequestInspectorOutlineMedium } from "./icons-local.js";
|
|
2
|
+
/** The tab kind this package owns. */
|
|
3
|
+
export const REQUEST_INSPECTOR_KIND = 'context-lens';
|
|
4
|
+
/** This implementation's identity in the tab system, and the key its body registers under. */
|
|
5
|
+
export const REQUEST_INSPECTOR_TAB_ID = '@khorsheed/dsh-context-lens';
|
|
6
|
+
/**
|
|
7
|
+
* The type's registry definition. No `priority`: the default `extension` band
|
|
8
|
+
* is exactly what a type shipped from outside the product is.
|
|
9
|
+
* @param t - namespace-bound translate, read fresh on every label call.
|
|
10
|
+
* @returns the definition to register.
|
|
11
|
+
*/
|
|
12
|
+
export function requestInspectorDefinition(t) {
|
|
13
|
+
return {
|
|
14
|
+
id: REQUEST_INSPECTOR_TAB_ID,
|
|
15
|
+
kind: REQUEST_INSPECTOR_KIND,
|
|
16
|
+
title: () => t('tab.title'),
|
|
17
|
+
guide: [{
|
|
18
|
+
id: REQUEST_INSPECTOR_KIND,
|
|
19
|
+
order: 60,
|
|
20
|
+
title: () => t('tab.title'),
|
|
21
|
+
description: () => t('guide.description'),
|
|
22
|
+
icon: IconRequestInspectorOutlineMedium,
|
|
23
|
+
}],
|
|
24
|
+
};
|
|
25
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package-owned icons for glyphs ui-primitives does not ship — the canvas /
|
|
3
|
+
* message-tools supplement pattern; upstream glyphs come from the generated
|
|
4
|
+
* icons.tsx (scripts/sync-icon-artwork.mts), which never touches this file.
|
|
5
|
+
* The context-lens glyph is a stacked-list sheet with a probe dot — the
|
|
6
|
+
* four-segment breakdown read at a glance.
|
|
7
|
+
*
|
|
8
|
+
* @module @khorsheed/dsh-context-lens/client
|
|
9
|
+
*/
|
|
10
|
+
/** Shared props for every product icon component (mirrors the upstream IconProps). */
|
|
11
|
+
export interface IconProps {
|
|
12
|
+
/** Square edge in px; defaults to the glyph's own drawn size. */
|
|
13
|
+
size?: number | undefined;
|
|
14
|
+
/** Extra class for layout placement; color rides currentColor. */
|
|
15
|
+
className?: string | undefined;
|
|
16
|
+
}
|
|
17
|
+
/** The context-lens glyph: a stacked breakdown sheet. */
|
|
18
|
+
export declare const IconRequestInspectorOutlineMedium: ({ size, className }: IconProps) => import("react").JSX.Element;
|
|
19
|
+
//# sourceMappingURL=icons-local.d.ts.map
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/** The context-lens glyph: a stacked breakdown sheet. */
|
|
3
|
+
export const IconRequestInspectorOutlineMedium = ({ size = 16, className }) => (_jsxs("svg", { width: size, height: size, className: className, viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", strokeWidth: 1.2, children: [_jsx("rect", { x: "2.5", y: "1.5", width: "11", height: "13", rx: "2", stroke: "currentColor" }), _jsx("path", { d: "M5 5.2H11", stroke: "currentColor" }), _jsx("path", { d: "M5 8H11", stroke: "currentColor" }), _jsx("path", { d: "M5 10.8H8.6", stroke: "currentColor" }), _jsx("circle", { cx: "11.2", cy: "11.2", r: "1.7", fill: "currentColor", stroke: "none" })] }));
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generated by scripts/sync-icon-artwork.mts from @deepseek-ai/dsh-client-ui-primitives
|
|
3
|
+
* (v0.2.1-rc.1, harness 1600d8a1e1) — do not edit by hand. The 0.1.5 and rc.1 host lines
|
|
4
|
+
* share no icon export name, so the rc.1 artwork is inlined here: pure SVG,
|
|
5
|
+
* no host runtime identity, identical on every line. Re-run the generator
|
|
6
|
+
* after adding an icon import or bumping the harness checkout.
|
|
7
|
+
*
|
|
8
|
+
* @module @khorsheed/dsh-context-lens/client
|
|
9
|
+
*/
|
|
10
|
+
/** Shared props for every product icon component (mirrors the upstream IconProps). */
|
|
11
|
+
export interface IconProps {
|
|
12
|
+
/** Square edge in px; defaults to the glyph's own drawn size. */
|
|
13
|
+
size?: number | undefined;
|
|
14
|
+
/** Extra class for layout placement; color rides currentColor. */
|
|
15
|
+
className?: string | undefined;
|
|
16
|
+
}
|
|
17
|
+
export declare const IconChevronDownOutlineMedium: ({ size, className }: IconProps) => import("react").JSX.Element;
|
|
18
|
+
//# sourceMappingURL=icons.d.ts.map
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
export const IconChevronDownOutlineMedium = ({ size = 14, className }) => (_jsx("svg", { width: size, height: size, className: className, viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", strokeWidth: 1.3, children: _jsx("path", { d: "M4 6L7.29289 9.29289C7.68342 9.68342 8.31658 9.68342 8.70711 9.29289L12 6", stroke: "currentColor" }) }));
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Request-inspector, browser half: mounts this package's generated Remote
|
|
3
|
+
* contribution so `remote.requestInspector` exists, then surfaces the panel —
|
|
4
|
+
* the `context-lens` right-Sidebar page tab (the official two-stage path:
|
|
5
|
+
* the type into `ctx.sidebarRightTabs`, the body into the keyed seat under the
|
|
6
|
+
* type's id), the per-turn icon in the assistant message's action row
|
|
7
|
+
* (`conversation.chat.assistant-actions`), and the session-header utility
|
|
8
|
+
* (`conversation.session.header.utilities`). All three registrations ride
|
|
9
|
+
* registration-level toggles on the preset-visibility criterion (the package's
|
|
10
|
+
* own row, root-mounted by its bundle patch, grants every session; a
|
|
11
|
+
* preset-mounted row grants that preset's sessions); every unreadable path
|
|
12
|
+
* fails open.
|
|
13
|
+
*
|
|
14
|
+
* @module @khorsheed/dsh-context-lens/client
|
|
15
|
+
*/
|
|
16
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
17
|
+
export { InspectorPanel } from './InspectorPanel.tsx';
|
|
18
|
+
export { TokenView } from './TokenView.tsx';
|
|
19
|
+
export { TimingView } from './TimingView.tsx';
|
|
20
|
+
export { SegmentBlocks, OutputBlock } from './SegmentBlocks.tsx';
|
|
21
|
+
export { RawView } from './RawView.tsx';
|
|
22
|
+
export { SegCtl } from './SegCtl.tsx';
|
|
23
|
+
export { TurnMenu, turnTitle } from './TurnMenu.tsx';
|
|
24
|
+
export { TurnInspectAction } from './TurnAction.tsx';
|
|
25
|
+
export { HeaderInspectUtility } from './HeaderUtility.tsx';
|
|
26
|
+
export { scrollConversationToTurn, scrollConversationToBottom, preferredScrollBehavior, } from './scroll-sync.ts';
|
|
27
|
+
export { REQUEST_INSPECTOR_KIND, REQUEST_INSPECTOR_TAB_ID, requestInspectorDefinition } from './definition.tsx';
|
|
28
|
+
export type { RequestInspectorTabParams } from './definition.tsx';
|
|
29
|
+
export { InspectorTabVisibility, RegistrationToggle, REQUEST_INSPECTOR_ROW_MODULE, } from './preset-visibility.ts';
|
|
30
|
+
export type { InspectorPluginInventorySnapshot } from './preset-visibility.ts';
|
|
31
|
+
export { resolveTurnSeq, latestTurnSeq, aggregateCategories, summarizeLanes } from './aggregate.ts';
|
|
32
|
+
export type { InspectorRemote, InspectorTabInjected, InspectorTabProps, InspectorActionInjected, InspectorActionProps, InspectorUtilityInjected, InspectorUtilityProps, } from './contract.ts';
|
|
33
|
+
/** Required services: slots, the remote channel, the locale, the session list (the visibility criterion), and the right-Sidebar faces. */
|
|
34
|
+
export declare const inject: string[];
|
|
35
|
+
/**
|
|
36
|
+
* Client plugin body: mount the Remote, register the dictionaries, then the
|
|
37
|
+
* tab type (toggled on the visibility criterion), its body, and the two entry
|
|
38
|
+
* points.
|
|
39
|
+
* @param ctx - client root context.
|
|
40
|
+
*/
|
|
41
|
+
export declare function apply(ctx: Context): Promise<() => Promise<void>>;
|
|
42
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import requestInspectorRemote from '@khorsheed/dsh-context-lens/remote';
|
|
2
|
+
import { resolveTurnSeq } from "./aggregate.js";
|
|
3
|
+
import { inspectorWire } from "./wire.js";
|
|
4
|
+
import { REQUEST_INSPECTOR_KIND, REQUEST_INSPECTOR_TAB_ID, requestInspectorDefinition } from "./definition.js";
|
|
5
|
+
import { HeaderInspectUtility } from "./HeaderUtility.js";
|
|
6
|
+
import { InspectorPanel } from "./InspectorPanel.js";
|
|
7
|
+
import { en, NS, zh } from "./locales.js";
|
|
8
|
+
import { InspectorTabVisibility, RegistrationToggle, } from "./preset-visibility.js";
|
|
9
|
+
import { TurnInspectAction } from "./TurnAction.js";
|
|
10
|
+
export { InspectorPanel } from "./InspectorPanel.js";
|
|
11
|
+
export { TokenView } from "./TokenView.js";
|
|
12
|
+
export { TimingView } from "./TimingView.js";
|
|
13
|
+
export { SegmentBlocks, OutputBlock } from "./SegmentBlocks.js";
|
|
14
|
+
export { RawView } from "./RawView.js";
|
|
15
|
+
export { SegCtl } from "./SegCtl.js";
|
|
16
|
+
export { TurnMenu, turnTitle } from "./TurnMenu.js";
|
|
17
|
+
export { TurnInspectAction } from "./TurnAction.js";
|
|
18
|
+
export { HeaderInspectUtility } from "./HeaderUtility.js";
|
|
19
|
+
export { scrollConversationToTurn, scrollConversationToBottom, preferredScrollBehavior, } from "./scroll-sync.js";
|
|
20
|
+
export { REQUEST_INSPECTOR_KIND, REQUEST_INSPECTOR_TAB_ID, requestInspectorDefinition } from "./definition.js";
|
|
21
|
+
export { InspectorTabVisibility, RegistrationToggle, REQUEST_INSPECTOR_ROW_MODULE, } from "./preset-visibility.js";
|
|
22
|
+
export { resolveTurnSeq, latestTurnSeq, aggregateCategories, summarizeLanes } from "./aggregate.js";
|
|
23
|
+
function mainSessionId(list) {
|
|
24
|
+
const view = list;
|
|
25
|
+
return Object.values(view.byId).find(s => (s.retainedBy?.mainView ?? 0) > 0)?.id ?? view.current;
|
|
26
|
+
}
|
|
27
|
+
/** Required services: slots, the remote channel, the locale, the session list (the visibility criterion), and the right-Sidebar faces. */
|
|
28
|
+
export const inject = ['slots', 'remote', 'locale', 'sessions', 'sidebarRight', 'sidebarRightTabs'];
|
|
29
|
+
/**
|
|
30
|
+
* Client plugin body: mount the Remote, register the dictionaries, then the
|
|
31
|
+
* tab type (toggled on the visibility criterion), its body, and the two entry
|
|
32
|
+
* points.
|
|
33
|
+
* @param ctx - client root context.
|
|
34
|
+
*/
|
|
35
|
+
export async function apply(ctx) {
|
|
36
|
+
const disposers = [];
|
|
37
|
+
try {
|
|
38
|
+
disposers.push(await ctx.remote.$mount(requestInspectorRemote));
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
// A duplicate mount (two compositions) fails loud here but breaks nothing
|
|
42
|
+
// else: the namespace exists once, which is all callers probe.
|
|
43
|
+
/* v8 ignore next -- double-mount is a composition error, not a runtime path */
|
|
44
|
+
ctx.logger.error(error);
|
|
45
|
+
}
|
|
46
|
+
ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'context-lens: dictionaries');
|
|
47
|
+
const t = ctx.locale.bind(NS);
|
|
48
|
+
// Read lazily: a composition without the host half yields RPC errors on the
|
|
49
|
+
// verbs, which the panel surfaces as its error state — never a boot failure.
|
|
50
|
+
// All wire calls route through `inspectorWire`: the gateway runtime enforces
|
|
51
|
+
// each verb's declared parameter count EXACTLY (a trailing optional param
|
|
52
|
+
// counts), so the trailing filter arg is always passed explicitly.
|
|
53
|
+
const remote = () => ctx.get('remote.requestInspector');
|
|
54
|
+
const wire = () => inspectorWire(remote());
|
|
55
|
+
const tabFace = () => ({
|
|
56
|
+
...wire(),
|
|
57
|
+
loadThrough: async (sessionId, seq) => {
|
|
58
|
+
const binding = ctx.sessions.binding(sessionId);
|
|
59
|
+
if (binding !== undefined)
|
|
60
|
+
await binding.session.loadThrough(seq);
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
/** Open the inspector tab; a kind hidden by the criterion throws, degrade to a log. */
|
|
64
|
+
const openTab = (turnSeq) => {
|
|
65
|
+
try {
|
|
66
|
+
ctx.sidebarRight.openTab(REQUEST_INSPECTOR_KIND, turnSeq === undefined ? undefined : { params: { turnSeq } });
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
ctx.logger.warn('context-lens: openTab failed (tab type not registered?)', error);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
/** The turn action's resolve-and-open: closingMessageId match, latest-turn fallback. */
|
|
73
|
+
const openForMessage = async (sessionId, messageId) => {
|
|
74
|
+
let turnSeq;
|
|
75
|
+
try {
|
|
76
|
+
const result = await wire().listTurns(sessionId);
|
|
77
|
+
if (result.ok)
|
|
78
|
+
turnSeq = resolveTurnSeq(result.value.turns, messageId);
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
// A dead namespace still opens the tab (its own error state reports it).
|
|
82
|
+
}
|
|
83
|
+
openTab(turnSeq);
|
|
84
|
+
};
|
|
85
|
+
const openLatest = (sessionId) => {
|
|
86
|
+
void openForMessage(sessionId);
|
|
87
|
+
};
|
|
88
|
+
// The OFFICIAL pluginInventory namespace, probed the canvas way: through
|
|
89
|
+
// ctx.get, never injected — a host without it must fail the criterion OPEN.
|
|
90
|
+
const pluginInventory = ctx.get('remote.pluginInventory');
|
|
91
|
+
const visibility = new InspectorTabVisibility(ctx, pluginInventory);
|
|
92
|
+
// The tab type and the two entries register exactly while the current
|
|
93
|
+
// session's criterion passes (hidden means NOT registered; an opened tab of
|
|
94
|
+
// an unregistered kind renders the host's designed tab.unavailable fallback).
|
|
95
|
+
ctx.effect(() => {
|
|
96
|
+
const toggles = [
|
|
97
|
+
new RegistrationToggle(() => ctx.sidebarRightTabs.register(requestInspectorDefinition(t)), () => visibility.show(mainSessionId(ctx.sessions.list.getSnapshot()))),
|
|
98
|
+
new RegistrationToggle(() => ctx.slots.inject('conversation.chat.assistant-actions', () => ctx.slots.register({
|
|
99
|
+
name: 'conversation.chat.assistant-actions',
|
|
100
|
+
id: 'context-lens-turn',
|
|
101
|
+
// After the official actions and the sidechat quote (order 20).
|
|
102
|
+
order: 30,
|
|
103
|
+
locale: NS,
|
|
104
|
+
inject: () => ({ open: openForMessage }),
|
|
105
|
+
}, TurnInspectAction)), () => visibility.show(mainSessionId(ctx.sessions.list.getSnapshot()))),
|
|
106
|
+
new RegistrationToggle(() => ctx.slots.inject('conversation.session.header.utilities', () => ctx.slots.register({
|
|
107
|
+
name: 'conversation.session.header.utilities',
|
|
108
|
+
id: 'context-lens-header',
|
|
109
|
+
// After the official entries and the worktrees badge (order -20).
|
|
110
|
+
order: 40,
|
|
111
|
+
locale: NS,
|
|
112
|
+
inject: () => ({ openLatest }),
|
|
113
|
+
}, HeaderInspectUtility)), () => visibility.show(mainSessionId(ctx.sessions.list.getSnapshot()))),
|
|
114
|
+
];
|
|
115
|
+
for (const toggle of toggles)
|
|
116
|
+
toggle.setReady(true);
|
|
117
|
+
const unsubscribe = visibility.subscribe(() => {
|
|
118
|
+
for (const toggle of toggles)
|
|
119
|
+
toggle.sync();
|
|
120
|
+
});
|
|
121
|
+
return () => {
|
|
122
|
+
unsubscribe();
|
|
123
|
+
for (const toggle of toggles)
|
|
124
|
+
toggle.setReady(false);
|
|
125
|
+
};
|
|
126
|
+
}, 'context-lens: surface visibility');
|
|
127
|
+
// Stage two: the body under the type's id in the keyed pane seat. The body
|
|
128
|
+
// registers unconditionally — an opened tab of a hidden type renders the
|
|
129
|
+
// host's tab.unavailable without ever mounting this component.
|
|
130
|
+
ctx.effect(() => ctx.slots.inject('sidebar.right.pane.tab', () => ctx.slots.register({
|
|
131
|
+
name: 'sidebar.right.pane.tab',
|
|
132
|
+
key: REQUEST_INSPECTOR_TAB_ID,
|
|
133
|
+
locale: NS,
|
|
134
|
+
inject: tabFace,
|
|
135
|
+
}, InspectorPanel)), 'context-lens: tab body');
|
|
136
|
+
return async () => {
|
|
137
|
+
await Promise.all(disposers.map(dispose => dispose()));
|
|
138
|
+
};
|
|
139
|
+
}
|