@dan-ai-studio/dshopencodego 0.1.65 → 0.1.66

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.en.md CHANGED
@@ -116,7 +116,7 @@ Configuration lives in the profile's `cordis.patch.yml`:
116
116
  pinnedAddresses: # fixed IP: host -> address (only the gateway host counts)
117
117
  opencode.ai: 104.18.32.47 # IPv4 or IPv6; an invalid or absent address uses the system DNS
118
118
  requestLogEnabled: false # request-log master switch, default off: off reads byte-for-byte as without the log
119
- requestLogDebug: false # debug detail: pool events and full session ids; otherwise sessions keep 8 chars
119
+ requestLogDebug: false # debug detail: pool events and the request user agent; session ids are always whole
120
120
  requestLogMaxEntries: 200 # most entries to keep, 50 to 500; oldest are dropped first
121
121
  retryPolicy: # optional: this route's retry policy, executed by dsh-llm-retry
122
122
  mode: normal # or always (retry every failure; maxRetries is ignored)
@@ -141,13 +141,15 @@ The API key comes from the Harness credential store (reference name `OPENCODE_GO
141
141
  - The settings page shows and accepts the value in **seconds** (a number of milliseconds is not readable at a glance) and stores it in milliseconds. Range: 1 to 1800000 milliseconds.
142
142
  - Once a connection has been idle that long, the pool **reclaims it** on its own — no separate mechanism is needed. The actual release comes a couple of seconds later than the setting, which is the client's own small margin beyond the deadline the server states. When the plugin is unloaded, the pool is **closed with it**: the pool belongs to the plugin and should not be left behind on the machine.
143
143
 
144
- **Request log**: per-request records, off by default; enabling them changes nothing about what requests do. The settings page's "Request log" group holds the master switch, the debug switch (pool events and full session ids), and the retention count (default 200).
144
+ **Request log**: per-request records, off by default; enabling them changes nothing about what requests do. The settings page's "Request log" group holds the master switch, the debug switch (pool events and the request user agent), and the retention count (default 200).
145
145
 
146
146
  - **Two entries, one page**:
147
147
  - **A sidebar icon → a full page in the main column**: every request across sessions. The log is a process-wide thing, so the sidebar is where it belongs — a fixed, always-visible spot, and the same pattern the Harness's own plugin manager uses.
148
148
  - **A "Request log" View inside the conversation**: this session's requests, in place — answering "why was that turn slow" without leaving the chat.
149
- - It is a page, not a floating card: the head, the summary (requests / failures / mean latency / mean first token / mean tok/s / connection reuse) and the filters stay put while the table scrolls. Each row carries time, model, session, input/output/cache, **tok/s**, first token, latency and status; clicking one expands its HTTP detail (method, URL, redacted headers, request shape, result and usage). Filters: model, result, slow requests, session; groups: session, model, result.
149
+ - It is a page, not a floating card: the head, the summary (requests / failures / mean latency / mean first token / mean tok/s / mean cache hit / connection reuse) and the filters stay put while the table scrolls. Each row carries time, model, session, input/output/cache, **cache hit**, **tok/s**, first token, latency and status; clicking one expands its HTTP detail (method, URL, redacted headers, request shape, result and usage). Filters: model, result, slow requests, session; groups: session, model, result; **click a column head to sort by it**, click again to flip.
150
150
  - **tok/s** is the output rate over the decode window (first token to the end), on the same scale as the Harness's own per-turn reading (reasoning tokens included). A request that produced no token has no rate — none is invented.
151
+ - **Cache hit** is the share of input tokens served from cache (cache reads over uncached input plus cache reads). No reported input means no share — none is invented either.
152
+ - Session ids are **always stored whole**: an id is a conversation identity, not a credential, and a truncated one reads two conversations as one.
151
153
  - The log is a ring buffer (oldest dropped first) that dies with the process; snapshots are read on demand, never polled, so a page nobody opens costs no traffic.
152
154
  - The credential never enters a record: the recorder takes no headers argument at all, only the allowlisted session id and user agent. Failed requests are recorded with their error code; a request cut off before any terminal event is not recorded — no total reported, none invented.
153
155
 
package/README.md CHANGED
@@ -115,7 +115,7 @@ DSH 桌面端是 Electron 壳 + 完整的 dsh Web 应用,本插件对它同样
115
115
  pinnedAddresses: # 固定 IP:域名 → 地址(只对网关域名生效)
116
116
  opencode.ai: 104.18.32.47 # 可写 IPv4 或 IPv6;不合法或未设置时走系统 DNS
117
117
  requestLogEnabled: false # 请求日志总开关,默认关闭:关闭时请求路径与没有该日志逐字节一致
118
- requestLogDebug: false # 排错详情:连接池事件与完整会话 id;不开启则会话只保留前 8 位
118
+ requestLogDebug: false # 排错详情:连接池事件与请求 UA;会话 id 始终完整记录
119
119
  requestLogMaxEntries: 200 # 最多保留条数,50 ~ 500,超限丢弃最旧的
120
120
  retryPolicy: # 可选:本路由的重试策略(由 dsh-llm-retry 执行)
121
121
  mode: normal # 也可用 always(对所有失败重试,maxRetries 被忽略)
@@ -140,13 +140,15 @@ API Key 通过 Harness 凭证库提供(引用名 `OPENCODE_GO_API_KEY`),
140
140
  - 设置页里以**秒**显示与填写(毫秒的数字人读不出量级),存储时换算成毫秒。取值 1 ~ 1800000 毫秒。
141
141
  - 空闲满这个时间之后,连接由连接池**自己回收**,不需要额外的清理机制(实际释放时间比设置值多约 2 秒,那是客户端在服务端给的期限之外自留的一点缓冲)。插件被卸载时,连接池也会**一并关掉**——它是插件的,不该留在机器上。
142
142
 
143
- **请求日志**:默认关闭的逐请求记录,打开它不改变请求的任何行为。设置页"请求日志"分组里有总开关、排错详情开关(连接池事件与完整会话 id)、保留条数(默认 200)。
143
+ **请求日志**:默认关闭的逐请求记录,打开它不改变请求的任何行为。设置页"请求日志"分组里有总开关、排错详情开关(连接池事件与请求 UA)、保留条数(默认 200)。
144
144
 
145
145
  - **两个入口,同一个页面**:
146
146
  - **侧边栏图标 → 主区域整页**:跨会话看全部请求。日志是进程级的数据,放在侧边栏最合理——位置固定、常驻可见,和 DSH 自己的插件管理器同一个范式。
147
147
  - **会话里的「请求日志」标签页**:就地看**本会话**的请求,回答"刚才那轮为什么慢"不用离开对话。
148
- - 页面是一整页而不是浮层:标题、统计(请求数/失败数/平均耗时/平均首字/平均 tok/s/连接复用率)、筛选栏固定在上方,只有表格自己滚动。每行含时间、模型、会话、输入/输出/缓存、**tok/s**、首字、耗时与状态;点一行展开 HTTP 明细(方法、地址、脱敏后的请求头、请求构成、结果与用量)。筛选有模型/结果/慢请求/会话,分组有会话/模型/结果。
148
+ - 页面是一整页而不是浮层:标题、统计(请求数/失败数/平均耗时/平均首字/平均 tok/s/平均缓存命中/连接复用率)、筛选栏固定在上方,只有表格自己滚动。每行含时间、模型、会话、输入/输出/缓存、**缓存命中**、**tok/s**、首字、耗时与状态;点一行展开 HTTP 明细(方法、地址、脱敏后的请求头、请求构成、结果与用量)。筛选有模型/结果/慢请求/会话,分组有会话/模型/结果;**点列头按该列排序**,再点一次反向。
149
149
  - **tok/s** 是**首字到结束**这段解码窗口上的输出速率,口径与 Harness 自己的每轮读数一致(含推理 token)。没有 token 的请求没有速率——不编数。
150
+ - **缓存命中**是输入 token 里走缓存的占比(缓存读取/(未缓存输入+缓存读取))。网关没报输入就没有占比——同样不编数。
151
+ - 会话 id **始终完整记录**(它是会话身份,不是凭据;截断后两个会话会读成一个)。
150
152
  - 记录的是环形缓冲(超限丢最旧),进程重启即清;只读快照,不轮询,没人打开的页面零流量。
151
153
  - 凭据永远不进记录:记录器根本不接收请求头参数,只收白名单里的会话 id 与 UA;失败的请求同样记一条(含错误码),中断到没有终端事件的不记(网关没报总数就不编数)。
152
154
 
package/lib/client.js CHANGED
@@ -499,13 +499,14 @@ var en = {
499
499
  keepAliveHint: "How long a connection to the gateway is held between requests. Longer pauses in a conversation cost one handshake each; the timer restarts on every answer.",
500
500
  requestLogTitle: "Request log",
501
501
  requestLogHint: "Per-request records for troubleshooting. Off by default; enabling it does not change what requests do.",
502
- requestLogPageHint: "One row per request. The table scrolls; the filters stay put.",
502
+ requestLogPageHint: "One row per request. Click a column head to sort; the table scrolls and the filters stay put.",
503
503
  requestLogEnabled: "Record requests",
504
504
  requestLogDebug: "Debug detail",
505
- requestLogDebugHint: "Pool events and full session ids. Otherwise sessions are truncated to 8 characters.",
505
+ requestLogDebugHint: "Pool events and the request user agent.",
506
506
  requestLogMaxEntries: "Keep at most (entries)",
507
507
  requestLogMaxEntriesHint: "50 to 500; oldest entries are dropped first. Default 200.",
508
508
  requestLogOpen: "Open request log",
509
+ requestLogOpenFailed: "The request-log page could not be opened. Use the sidebar entry.",
509
510
  requestLogClose: "Close",
510
511
  requestLogRefresh: "Refresh",
511
512
  requestLogEmpty: "No requests recorded. Turn the log on, then make a request.",
@@ -537,6 +538,7 @@ var en = {
537
538
  requestLogColIn: "In",
538
539
  requestLogColOut: "Out",
539
540
  requestLogColCache: "Cached",
541
+ requestLogColCacheRate: "Cache hit",
540
542
  requestLogColLatency: "Latency",
541
543
  requestLogColTps: "tok/s",
542
544
  requestLogColFirstToken: "First token",
@@ -561,6 +563,7 @@ var en = {
561
563
  requestLogStatLatency: "Mean latency",
562
564
  requestLogStatFirstToken: "Mean first token",
563
565
  requestLogStatTps: "Mean tok/s",
566
+ requestLogStatCacheRate: "Mean cache hit",
564
567
  writeRejected: "The settings write was rejected (the namespace was reloaded) \u2014 try again",
565
568
  writeFailed: "The settings write failed",
566
569
  filterPlaceholder: "Filter by name or id",
@@ -675,13 +678,14 @@ var zh = {
675
678
  keepAliveHint: "\u4E0E\u7F51\u5173\u7684\u8FDE\u63A5\u5728\u4E24\u6B21\u8BF7\u6C42\u4E4B\u95F4\u4FDD\u7559\u591A\u4E45\u3002\u5BF9\u8BDD\u4E2D\u6BCF\u6B21\u505C\u987F\u8F83\u4E45\u90FD\u8981\u91CD\u65B0\u63E1\u624B\u4E00\u6B21\uFF1B\u6BCF\u6B21\u6536\u5230\u56DE\u7B54\u540E\u8BA1\u65F6\u90FD\u4F1A\u91CD\u65B0\u5F00\u59CB\u3002",
676
679
  requestLogTitle: "\u8BF7\u6C42\u65E5\u5FD7",
677
680
  requestLogHint: "\u6392\u67E5\u7528\u7684\u9010\u8BF7\u6C42\u8BB0\u5F55\u3002\u9ED8\u8BA4\u5173\u95ED\uFF1B\u6253\u5F00\u5B83\u4E0D\u4F1A\u6539\u53D8\u8BF7\u6C42\u672C\u8EAB\u7684\u884C\u4E3A\u3002",
678
- requestLogPageHint: "\u6BCF\u4E2A\u8BF7\u6C42\u4E00\u884C\u3002\u8868\u683C\u6EDA\u52A8\uFF0C\u7B5B\u9009\u680F\u56FA\u5B9A\u5728\u4E0A\u65B9\u3002",
681
+ requestLogPageHint: "\u6BCF\u4E2A\u8BF7\u6C42\u4E00\u884C\u3002\u70B9\u5217\u5934\u53EF\u6392\u5E8F\uFF1B\u8868\u683C\u6EDA\u52A8\uFF0C\u7B5B\u9009\u680F\u56FA\u5B9A\u5728\u4E0A\u65B9\u3002",
679
682
  requestLogEnabled: "\u8BB0\u5F55\u8BF7\u6C42",
680
683
  requestLogDebug: "\u6392\u9519\u8BE6\u60C5",
681
- requestLogDebugHint: "\u8FDE\u63A5\u6C60\u4E8B\u4EF6\u4E0E\u5B8C\u6574\u4F1A\u8BDD id\u3002\u4E0D\u5F00\u5219\u4F1A\u8BDD\u53EA\u4FDD\u7559\u524D 8 \u4F4D\u3002",
684
+ requestLogDebugHint: "\u8FDE\u63A5\u6C60\u4E8B\u4EF6\u4E0E\u8BF7\u6C42 UA\u3002",
682
685
  requestLogMaxEntries: "\u6700\u591A\u4FDD\u7559\uFF08\u6761\uFF09",
683
686
  requestLogMaxEntriesHint: "50\uFF5E500 \u6761\uFF0C\u8D85\u9650\u4E22\u5F03\u6700\u65E7\u7684\u3002\u9ED8\u8BA4 200 \u6761\u3002",
684
687
  requestLogOpen: "\u6253\u5F00\u8BF7\u6C42\u65E5\u5FD7",
688
+ requestLogOpenFailed: "\u6253\u4E0D\u5F00\u8BF7\u6C42\u65E5\u5FD7\u9875\u9762\uFF0C\u8BF7\u4ECE\u4FA7\u8FB9\u680F\u7684\u5165\u53E3\u6253\u5F00\u3002",
685
689
  requestLogClose: "\u5173\u95ED",
686
690
  requestLogRefresh: "\u5237\u65B0",
687
691
  requestLogEmpty: "\u6682\u65E0\u8BB0\u5F55\u3002\u5148\u6253\u5F00\u65E5\u5FD7\u5F00\u5173\uFF0C\u518D\u53D1\u8D77\u4E00\u6B21\u8BF7\u6C42\u3002",
@@ -713,6 +717,7 @@ var zh = {
713
717
  requestLogColIn: "\u8F93\u5165",
714
718
  requestLogColOut: "\u8F93\u51FA",
715
719
  requestLogColCache: "\u7F13\u5B58",
720
+ requestLogColCacheRate: "\u7F13\u5B58\u547D\u4E2D",
716
721
  requestLogColLatency: "\u8017\u65F6",
717
722
  requestLogColTps: "tok/s",
718
723
  requestLogColFirstToken: "\u9996\u5B57",
@@ -737,6 +742,7 @@ var zh = {
737
742
  requestLogStatLatency: "\u5E73\u5747\u8017\u65F6",
738
743
  requestLogStatFirstToken: "\u5E73\u5747\u9996\u5B57",
739
744
  requestLogStatTps: "\u5E73\u5747 tok/s",
745
+ requestLogStatCacheRate: "\u5E73\u5747\u7F13\u5B58\u547D\u4E2D",
740
746
  writeRejected: "\u8BBE\u7F6E\u5199\u5165\u88AB\u62D2\u7EDD\uFF08\u5DF2\u91CD\u65B0\u8F7D\u5165\uFF09\uFF0C\u8BF7\u91CD\u8BD5",
741
747
  writeFailed: "\u8BBE\u7F6E\u5199\u5165\u5931\u8D25",
742
748
  filterPlaceholder: "\u6309\u540D\u79F0\u6216 ID \u7B5B\u9009",
@@ -1623,6 +1629,7 @@ function Section({ controller, t, getLocale, openRequestLog }) {
1623
1629
  const [keyDraft, setKeyDraft] = (0, import_react.useState)("");
1624
1630
  const [pinDraft, setPinDraft] = (0, import_react.useState)("");
1625
1631
  const [busy, setBusy] = (0, import_react.useState)(false);
1632
+ const [logOpenFailed, setLogOpenFailed] = (0, import_react.useState)(false);
1626
1633
  const act = (0, import_react.useCallback)(async (operation) => {
1627
1634
  setBusy(true);
1628
1635
  try {
@@ -1911,9 +1918,12 @@ function Section({ controller, t, getLocale, openRequestLog }) {
1911
1918
  ),
1912
1919
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: section_default.hint, children: t("requestLogMaxEntriesHint") })
1913
1920
  ] }),
1914
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: section_default.row, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: section_default.button, onClick: () => {
1915
- openRequestLog?.();
1916
- }, children: t("requestLogOpen") }) }),
1921
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: section_default.row, children: [
1922
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: section_default.button, onClick: () => {
1923
+ setLogOpenFailed(!(openRequestLog?.() ?? false));
1924
+ }, children: t("requestLogOpen") }),
1925
+ logOpenFailed && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: section_default.warn, children: t("requestLogOpenFailed") })
1926
+ ] }),
1917
1927
  state.writeFailure !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: section_default.warn, children: state.writeFailure.kind === "rejected" ? t("writeRejected") : state.writeFailure.message.length === 0 ? t("writeFailed") : `${t("writeFailed")}: ${state.writeFailure.message}` }),
1918
1928
  reading !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: section_default.row, children: [
1919
1929
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -2032,13 +2042,52 @@ if (!document.querySelector("style[data-plugin-css=" + JSON.stringify(id2) + "]"
2032
2042
  const style = document.createElement("style");
2033
2043
  style.dataset.plugin = "@dan-ai-studio/dshopencodego";
2034
2044
  style.dataset.pluginCss = id2;
2035
- style.textContent = ".Lx7MOq_page{box-sizing:border-box;background:var(--dsw-alias-bg-base);height:100%;min-height:0;color:var(--dsw-alias-label-primary);flex-direction:column;display:flex;overflow:hidden}.Lx7MOq_head{flex-wrap:wrap;flex:none;align-items:center;gap:12px;padding:20px clamp(16px,3vw,32px) 12px;display:flex}.Lx7MOq_headText{min-width:0}.Lx7MOq_title{margin:0;font-size:20px;font-weight:500;line-height:28px}.Lx7MOq_subtitle{color:var(--dsw-alias-label-secondary);margin:4px 0 0;font-size:13px;line-height:20px}.Lx7MOq_headActions{flex:none;align-items:center;gap:8px;margin-left:auto;display:flex}.Lx7MOq_button{border:1px solid var(--dsw-alias-border-l2);border-radius:var(--dsw-radius-sm);background:var(--dsw-alias-bg-layer-1);height:28px;color:var(--dsw-alias-label-primary);font:inherit;cursor:pointer;align-items:center;gap:6px;padding:0 12px;font-size:13px;display:inline-flex}.Lx7MOq_button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.Lx7MOq_button:disabled{cursor:default;opacity:.5}.Lx7MOq_summary{flex-wrap:wrap;flex:none;gap:8px;padding:0 clamp(16px,3vw,32px) 12px;display:flex}.Lx7MOq_card{border:1px solid var(--dsw-alias-border-l2);border-radius:var(--dsw-radius-md);background:var(--dsw-alias-bg-layer-1);flex-direction:column;gap:2px;min-width:104px;padding:8px 12px;display:flex}.Lx7MOq_cardLabel{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:16px}.Lx7MOq_cardValue{font-variant-numeric:tabular-nums;font-size:16px;font-weight:500;line-height:22px}.Lx7MOq_cardError .Lx7MOq_cardValue{color:var(--dsw-alias-state-error-primary)}.Lx7MOq_cardWarn .Lx7MOq_cardValue{color:var(--dsw-alias-state-warn-primary)}.Lx7MOq_toolbar{flex-wrap:wrap;flex:none;align-items:center;gap:8px 12px;padding:0 clamp(16px,3vw,32px) 12px;display:flex}.Lx7MOq_field{color:var(--dsw-alias-label-secondary);align-items:center;gap:6px;font-size:13px;line-height:20px;display:inline-flex}.Lx7MOq_select,.Lx7MOq_input{border:1px solid var(--dsw-alias-border-l2);border-radius:var(--dsw-radius-sm);background:var(--dsw-alias-bg-layer-1);height:28px;color:var(--dsw-alias-label-primary);font:inherit;padding:0 8px;font-size:13px}.Lx7MOq_input{width:160px}.Lx7MOq_inputNumber{width:72px}.Lx7MOq_checkbox{accent-color:var(--dsw-alias-brand-primary)}.Lx7MOq_tableWrap{border:1px solid var(--dsw-alias-border-l2);border-radius:var(--dsw-radius-md);background:var(--dsw-alias-bg-layer-1);flex:1;min-height:0;margin:0 clamp(16px,3vw,32px);overflow:auto}.Lx7MOq_table{border-collapse:separate;border-spacing:0;width:100%;min-width:940px;font-size:13px;line-height:20px}.Lx7MOq_table th{z-index:1;border-bottom:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-tertiary);text-align:left;white-space:nowrap;padding:8px 10px;font-size:12px;font-weight:500;position:sticky;top:0}.Lx7MOq_table td{border-bottom:1px solid var(--dsw-alias-border-l1);white-space:nowrap;font-variant-numeric:tabular-nums;padding:6px 10px}.Lx7MOq_table tbody tr:last-child td{border-bottom:none}.Lx7MOq_row{cursor:pointer}.Lx7MOq_row:hover td{background:var(--dsw-alias-interactive-bg-hover)}.Lx7MOq_rowOk td:first-child{box-shadow:inset 3px 0 0 var(--dsw-alias-border-l3)}.Lx7MOq_rowSlow td:first-child{box-shadow:inset 3px 0 0 var(--dsw-alias-state-warn-primary)}.Lx7MOq_rowFail td:first-child{box-shadow:inset 3px 0 0 var(--dsw-alias-state-error-primary)}.Lx7MOq_rowFail td:last-child,.Lx7MOq_rowSlow td:last-child{color:var(--dsw-alias-state-error-primary)}.Lx7MOq_group td{border-bottom:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);padding-top:12px;font-weight:500}.Lx7MOq_detail td{border-bottom:1px solid var(--dsw-alias-border-l1);white-space:normal;background:var(--dsw-alias-bg-layer-2);padding:12px 10px 16px}.Lx7MOq_detailGrid{grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:6px 24px;display:grid}.Lx7MOq_detailItem{gap:8px;min-width:0;font-size:13px;line-height:20px;display:flex}.Lx7MOq_detailLabel{color:var(--dsw-alias-label-tertiary);flex:none}.Lx7MOq_detailValue{overflow-wrap:anywhere;min-width:0;color:var(--dsw-alias-label-primary)}.Lx7MOq_mono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px}.Lx7MOq_empty{text-align:center;color:var(--dsw-alias-label-tertiary);padding:48px 16px}.Lx7MOq_foot{color:var(--dsw-alias-label-tertiary);flex-wrap:wrap;flex:none;align-items:center;gap:12px;padding:12px clamp(16px,3vw,32px) 20px;font-size:12px;display:flex}.Lx7MOq_pager{align-items:center;gap:8px;margin-left:auto;display:flex}.Lx7MOq_notice{color:var(--dsw-alias-label-secondary);padding:0 clamp(16px,3vw,32px) 12px;font-size:13px;line-height:20px}.Lx7MOq_noticeError{color:var(--dsw-alias-state-error-primary)}";
2045
+ style.textContent = ".Lx7MOq_page{box-sizing:border-box;background:var(--dsw-alias-bg-base);height:100%;min-height:0;color:var(--dsw-alias-label-primary);flex-direction:column;display:flex}.Lx7MOq_pageLoose{height:auto}.Lx7MOq_pageLoose .Lx7MOq_tableWrap{overflow:visible}.Lx7MOq_head{flex-wrap:wrap;flex:none;align-items:center;gap:12px;padding:20px clamp(16px,3vw,32px) 12px;display:flex}.Lx7MOq_headText{min-width:0}.Lx7MOq_title{margin:0;font-size:20px;font-weight:500;line-height:28px}.Lx7MOq_subtitle{color:var(--dsw-alias-label-secondary);margin:4px 0 0;font-size:13px;line-height:20px}.Lx7MOq_headActions{flex:none;align-items:center;gap:8px;margin-left:auto;display:flex}.Lx7MOq_button{border:1px solid var(--dsw-alias-border-l2);border-radius:var(--dsw-radius-sm);background:var(--dsw-alias-bg-layer-1);height:28px;color:var(--dsw-alias-label-primary);font:inherit;cursor:pointer;align-items:center;gap:6px;padding:0 12px;font-size:13px;display:inline-flex}.Lx7MOq_button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.Lx7MOq_button:disabled{cursor:default;opacity:.5}.Lx7MOq_summary{flex-wrap:wrap;flex:none;gap:8px;padding:0 clamp(16px,3vw,32px) 12px;display:flex}.Lx7MOq_card{border:1px solid var(--dsw-alias-border-l2);border-radius:var(--dsw-radius-md);background:var(--dsw-alias-bg-layer-1);flex-direction:column;gap:2px;min-width:104px;padding:8px 12px;display:flex}.Lx7MOq_cardLabel{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:16px}.Lx7MOq_cardValue{font-variant-numeric:tabular-nums;font-size:16px;font-weight:500;line-height:22px}.Lx7MOq_cardError .Lx7MOq_cardValue{color:var(--dsw-alias-state-error-primary)}.Lx7MOq_cardWarn .Lx7MOq_cardValue{color:var(--dsw-alias-state-warn-primary)}.Lx7MOq_toolbar{flex-wrap:wrap;flex:none;align-items:center;gap:8px 12px;padding:0 clamp(16px,3vw,32px) 12px;display:flex}.Lx7MOq_field{color:var(--dsw-alias-label-secondary);align-items:center;gap:6px;font-size:13px;line-height:20px;display:inline-flex}.Lx7MOq_select,.Lx7MOq_input{border:1px solid var(--dsw-alias-border-l2);border-radius:var(--dsw-radius-sm);background:var(--dsw-alias-bg-layer-1);height:28px;color:var(--dsw-alias-label-primary);font:inherit;padding:0 8px;font-size:13px}.Lx7MOq_input{width:160px}.Lx7MOq_inputNumber{width:72px}.Lx7MOq_checkbox{accent-color:var(--dsw-alias-brand-primary)}.Lx7MOq_tableWrap{border:1px solid var(--dsw-alias-border-l2);border-radius:var(--dsw-radius-md);background:var(--dsw-alias-bg-layer-1);flex:1;min-height:0;margin:0 clamp(16px,3vw,32px);overflow:auto}.Lx7MOq_table{border-collapse:separate;border-spacing:0;width:100%;min-width:940px;font-size:13px;line-height:20px}.Lx7MOq_table th{z-index:1;border-bottom:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-tertiary);text-align:left;white-space:nowrap;padding:8px 10px;font-size:12px;font-weight:500;position:sticky;top:0}.Lx7MOq_thSortable{cursor:pointer;user-select:none}.Lx7MOq_thSortable:hover{color:var(--dsw-alias-label-primary)}.Lx7MOq_table td{border-bottom:1px solid var(--dsw-alias-border-l1);white-space:nowrap;font-variant-numeric:tabular-nums;padding:6px 10px}.Lx7MOq_table tbody tr:last-child td{border-bottom:none}.Lx7MOq_row{cursor:pointer}.Lx7MOq_row:hover td{background:var(--dsw-alias-interactive-bg-hover)}.Lx7MOq_rowOk td:first-child{box-shadow:inset 3px 0 0 var(--dsw-alias-border-l3)}.Lx7MOq_rowSlow td:first-child{box-shadow:inset 3px 0 0 var(--dsw-alias-state-warn-primary)}.Lx7MOq_rowFail td:first-child{box-shadow:inset 3px 0 0 var(--dsw-alias-state-error-primary)}.Lx7MOq_rowFail td:last-child,.Lx7MOq_rowSlow td:last-child{color:var(--dsw-alias-state-error-primary)}.Lx7MOq_group td{border-bottom:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);padding-top:12px;font-weight:500}.Lx7MOq_detail td{border-bottom:1px solid var(--dsw-alias-border-l1);white-space:normal;background:var(--dsw-alias-bg-layer-2);padding:12px 10px 16px}.Lx7MOq_detailGrid{grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:6px 24px;display:grid}.Lx7MOq_detailItem{gap:8px;min-width:0;font-size:13px;line-height:20px;display:flex}.Lx7MOq_detailLabel{color:var(--dsw-alias-label-tertiary);flex:none}.Lx7MOq_detailValue{overflow-wrap:anywhere;min-width:0;color:var(--dsw-alias-label-primary)}.Lx7MOq_mono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px}.Lx7MOq_empty{text-align:center;color:var(--dsw-alias-label-tertiary);padding:48px 16px}.Lx7MOq_foot{color:var(--dsw-alias-label-tertiary);flex-wrap:wrap;flex:none;align-items:center;gap:12px;padding:12px clamp(16px,3vw,32px) 20px;font-size:12px;display:flex}.Lx7MOq_pager{align-items:center;gap:8px;margin-left:auto;display:flex}.Lx7MOq_notice{color:var(--dsw-alias-label-secondary);padding:0 clamp(16px,3vw,32px) 12px;font-size:13px;line-height:20px}.Lx7MOq_noticeError{color:var(--dsw-alias-state-error-primary)}";
2036
2046
  document.head.appendChild(style);
2037
2047
  }
2038
- var request_log_default = { "button": "Lx7MOq_button", "card": "Lx7MOq_card", "cardError": "Lx7MOq_cardError", "cardLabel": "Lx7MOq_cardLabel", "cardValue": "Lx7MOq_cardValue", "cardWarn": "Lx7MOq_cardWarn", "checkbox": "Lx7MOq_checkbox", "detail": "Lx7MOq_detail", "detailGrid": "Lx7MOq_detailGrid", "detailItem": "Lx7MOq_detailItem", "detailLabel": "Lx7MOq_detailLabel", "detailValue": "Lx7MOq_detailValue", "empty": "Lx7MOq_empty", "field": "Lx7MOq_field", "foot": "Lx7MOq_foot", "group": "Lx7MOq_group", "head": "Lx7MOq_head", "headActions": "Lx7MOq_headActions", "headText": "Lx7MOq_headText", "input": "Lx7MOq_input", "inputNumber": "Lx7MOq_inputNumber", "mono": "Lx7MOq_mono", "notice": "Lx7MOq_notice", "noticeError": "Lx7MOq_noticeError", "page": "Lx7MOq_page", "pager": "Lx7MOq_pager", "row": "Lx7MOq_row", "rowFail": "Lx7MOq_rowFail", "rowOk": "Lx7MOq_rowOk", "rowSlow": "Lx7MOq_rowSlow", "select": "Lx7MOq_select", "subtitle": "Lx7MOq_subtitle", "summary": "Lx7MOq_summary", "table": "Lx7MOq_table", "tableWrap": "Lx7MOq_tableWrap", "title": "Lx7MOq_title", "toolbar": "Lx7MOq_toolbar" };
2048
+ var request_log_default = { "button": "Lx7MOq_button", "card": "Lx7MOq_card", "cardError": "Lx7MOq_cardError", "cardLabel": "Lx7MOq_cardLabel", "cardValue": "Lx7MOq_cardValue", "cardWarn": "Lx7MOq_cardWarn", "checkbox": "Lx7MOq_checkbox", "detail": "Lx7MOq_detail", "detailGrid": "Lx7MOq_detailGrid", "detailItem": "Lx7MOq_detailItem", "detailLabel": "Lx7MOq_detailLabel", "detailValue": "Lx7MOq_detailValue", "empty": "Lx7MOq_empty", "field": "Lx7MOq_field", "foot": "Lx7MOq_foot", "group": "Lx7MOq_group", "head": "Lx7MOq_head", "headActions": "Lx7MOq_headActions", "headText": "Lx7MOq_headText", "input": "Lx7MOq_input", "inputNumber": "Lx7MOq_inputNumber", "mono": "Lx7MOq_mono", "notice": "Lx7MOq_notice", "noticeError": "Lx7MOq_noticeError", "page": "Lx7MOq_page", "pageLoose": "Lx7MOq_pageLoose", "pager": "Lx7MOq_pager", "row": "Lx7MOq_row", "rowFail": "Lx7MOq_rowFail", "rowOk": "Lx7MOq_rowOk", "rowSlow": "Lx7MOq_rowSlow", "select": "Lx7MOq_select", "subtitle": "Lx7MOq_subtitle", "summary": "Lx7MOq_summary", "table": "Lx7MOq_table", "tableWrap": "Lx7MOq_tableWrap", "thSortable": "Lx7MOq_thSortable", "title": "Lx7MOq_title", "toolbar": "Lx7MOq_toolbar" };
2039
2049
 
2040
2050
  // src/client/RequestLogPage.tsx
2041
2051
  var import_jsx_runtime2 = require("react/jsx-runtime");
2052
+ var SORT_DEFAULT_DIR = {
2053
+ atMs: "desc",
2054
+ model: "asc",
2055
+ session: "asc",
2056
+ inputTokens: "desc",
2057
+ outputTokens: "desc",
2058
+ cacheReadTokens: "desc",
2059
+ cacheRate: "desc",
2060
+ tps: "desc",
2061
+ firstTokenMs: "desc",
2062
+ durationMs: "desc",
2063
+ status: "asc"
2064
+ };
2065
+ function sortValue(entry, key) {
2066
+ switch (key) {
2067
+ case "atMs":
2068
+ return entry.atMs;
2069
+ case "model":
2070
+ return entry.model;
2071
+ case "session":
2072
+ return entry.session;
2073
+ case "inputTokens":
2074
+ return entry.inputTokens ?? -1;
2075
+ case "outputTokens":
2076
+ return entry.outputTokens ?? -1;
2077
+ case "cacheReadTokens":
2078
+ return entry.cacheReadTokens ?? -1;
2079
+ case "cacheRate":
2080
+ return cacheRate(entry) ?? -1;
2081
+ case "tps":
2082
+ return entry.tps ?? -1;
2083
+ case "firstTokenMs":
2084
+ return entry.firstTokenMs ?? -1;
2085
+ case "durationMs":
2086
+ return entry.durationMs ?? -1;
2087
+ case "status":
2088
+ return entry.ok ? `ok-${entry.status ?? ""}` : `fail-${entry.code ?? ""}`;
2089
+ }
2090
+ }
2042
2091
  var PAGE_SIZE = 50;
2043
2092
  function count2(value) {
2044
2093
  return value === void 0 ? "\u2014" : value.toLocaleString();
@@ -2050,6 +2099,15 @@ function duration(value) {
2050
2099
  function rate2(value) {
2051
2100
  return value === void 0 ? "\u2014" : value.toFixed(1);
2052
2101
  }
2102
+ function cacheRate(entry) {
2103
+ if (entry.inputTokens === void 0) return void 0;
2104
+ const cached = entry.cacheReadTokens ?? 0;
2105
+ const total = entry.inputTokens + cached;
2106
+ return total > 0 ? cached / total : void 0;
2107
+ }
2108
+ function percent(value) {
2109
+ return value === void 0 ? "\u2014" : `${(value * 100).toFixed(0)}%`;
2110
+ }
2053
2111
  function average(values) {
2054
2112
  if (values.length === 0) return void 0;
2055
2113
  let sum = 0;
@@ -2080,8 +2138,19 @@ function RequestLogPage({ readLog, enabled, t, getLocale, sessionId }) {
2080
2138
  const [groupBy, setGroupBy] = (0, import_react2.useState)("none");
2081
2139
  const [page, setPage] = (0, import_react2.useState)(0);
2082
2140
  const [expanded, setExpanded] = (0, import_react2.useState)(null);
2083
- const sessionPrefix = sessionId === void 0 ? "" : sessionId.slice(0, 8);
2084
- const [onlySession, setOnlySession] = (0, import_react2.useState)(sessionPrefix !== "");
2141
+ const [sortKey, setSortKey] = (0, import_react2.useState)("atMs");
2142
+ const [sortDir, setSortDir] = (0, import_react2.useState)("desc");
2143
+ const [onlySession, setOnlySession] = (0, import_react2.useState)(sessionId !== void 0);
2144
+ const toggleSort = (key) => {
2145
+ if (key === sortKey) {
2146
+ setSortDir(sortDir === "asc" ? "desc" : "asc");
2147
+ } else {
2148
+ setSortKey(key);
2149
+ setSortDir(SORT_DEFAULT_DIR[key]);
2150
+ }
2151
+ setPage(0);
2152
+ setExpanded(null);
2153
+ };
2085
2154
  const load = (0, import_react2.useCallback)(async () => {
2086
2155
  setRefreshing(true);
2087
2156
  try {
@@ -2109,19 +2178,39 @@ function RequestLogPage({ readLog, enabled, t, getLocale, sessionId }) {
2109
2178
  if (result === "ok" && !entry.ok) return false;
2110
2179
  if (result === "fail" && entry.ok) return false;
2111
2180
  if (slowOnly && (entry.durationMs ?? 0) < Math.max(0, slowSeconds) * 1e3) return false;
2112
- if (onlySession && sessionPrefix !== "" && !entry.session.startsWith(sessionPrefix)) return false;
2181
+ if (onlySession && sessionId !== void 0 && entry.session !== sessionId) return false;
2113
2182
  if (query !== "" && !entry.session.includes(query)) return false;
2114
2183
  return true;
2115
2184
  });
2116
- }, [snapshot, model, result, slowOnly, slowSeconds, sessionQuery, onlySession, sessionPrefix]);
2185
+ }, [snapshot, model, result, slowOnly, slowSeconds, sessionQuery, onlySession, sessionId]);
2117
2186
  const pages = Math.max(1, Math.ceil(filtered.length / PAGE_SIZE));
2118
2187
  const safePage = Math.min(page, pages - 1);
2119
- const pageEntries = filtered.slice(safePage * PAGE_SIZE, (safePage + 1) * PAGE_SIZE);
2188
+ const sorted = (0, import_react2.useMemo)(() => {
2189
+ const next = [...filtered];
2190
+ next.sort((a, b) => {
2191
+ const first = sortValue(a, sortKey);
2192
+ const second = sortValue(b, sortKey);
2193
+ const compared = typeof first === "number" && typeof second === "number" ? first - second : String(first) < String(second) ? -1 : String(first) > String(second) ? 1 : 0;
2194
+ return sortDir === "asc" ? compared : -compared;
2195
+ });
2196
+ return next;
2197
+ }, [filtered, sortKey, sortDir]);
2198
+ const pageEntries = sorted.slice(safePage * PAGE_SIZE, (safePage + 1) * PAGE_SIZE);
2120
2199
  const failures = filtered.filter((entry) => !entry.ok).length;
2121
2200
  const meanLatency = average(filtered.flatMap((entry) => entry.durationMs === void 0 ? [] : [entry.durationMs]));
2122
2201
  const meanFirstToken = average(filtered.flatMap((entry) => entry.firstTokenMs === void 0 ? [] : [entry.firstTokenMs]));
2123
2202
  const meanTps = average(filtered.flatMap((entry) => entry.tps === void 0 ? [] : [entry.tps]));
2203
+ const meanCacheRate = average(filtered.flatMap((entry) => {
2204
+ const value = cacheRate(entry);
2205
+ return value === void 0 ? [] : [value];
2206
+ }));
2124
2207
  const reuse = snapshot !== void 0 && snapshot.totalRecords > 0 ? Math.max(0, Math.min(1, 1 - snapshot.newConnections / snapshot.totalRecords)) : void 0;
2208
+ const head = (key, label) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("th", { className: request_log_default.thSortable, onClick: () => {
2209
+ toggleSort(key);
2210
+ }, children: [
2211
+ label,
2212
+ sortKey === key ? sortDir === "asc" ? " \u25B2" : " \u25BC" : ""
2213
+ ] });
2125
2214
  const rows = () => {
2126
2215
  const out = [];
2127
2216
  let lastGroup;
@@ -2130,7 +2219,7 @@ function RequestLogPage({ readLog, enabled, t, getLocale, sessionId }) {
2130
2219
  if (group !== void 0 && group !== lastGroup) {
2131
2220
  lastGroup = group;
2132
2221
  out.push(
2133
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("tr", { className: request_log_default.group, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { colSpan: 10, children: group }) }, `group-${group}-${entry.seq}`)
2222
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("tr", { className: request_log_default.group, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { colSpan: 11, children: group }) }, `group-${group}-${entry.seq}`)
2134
2223
  );
2135
2224
  }
2136
2225
  const state = !entry.ok ? "rowFail" : entry.tps !== void 0 && entry.tps < 5 ? "rowSlow" : "rowOk";
@@ -2149,6 +2238,7 @@ function RequestLogPage({ readLog, enabled, t, getLocale, sessionId }) {
2149
2238
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { children: count2(entry.inputTokens) }),
2150
2239
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { children: count2(entry.outputTokens) }),
2151
2240
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { children: count2(entry.cacheReadTokens) }),
2241
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { children: percent(cacheRate(entry)) }),
2152
2242
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { children: rate2(entry.tps) }),
2153
2243
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { children: duration(entry.firstTokenMs) }),
2154
2244
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { children: duration(entry.durationMs) }),
@@ -2160,7 +2250,7 @@ function RequestLogPage({ readLog, enabled, t, getLocale, sessionId }) {
2160
2250
  );
2161
2251
  if (expanded === entry.seq) {
2162
2252
  out.push(
2163
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("tr", { className: request_log_default.detail, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { colSpan: 10, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: request_log_default.detailGrid, children: [
2253
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("tr", { className: request_log_default.detail, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { colSpan: 11, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: request_log_default.detailGrid, children: [
2164
2254
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: request_log_default.detailItem, children: [
2165
2255
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: request_log_default.detailLabel, children: t("requestLogDetailMethod") }),
2166
2256
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: `${request_log_default.detailValue} ${request_log_default.mono}`, children: entry.method })
@@ -2193,6 +2283,10 @@ function RequestLogPage({ readLog, enabled, t, getLocale, sessionId }) {
2193
2283
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: request_log_default.detailLabel, children: t("requestLogDetailTokens") }),
2194
2284
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: request_log_default.detailValue, children: `${count2(entry.inputTokens)} / ${count2(entry.outputTokens)} / ${count2(entry.cacheReadTokens)}` })
2195
2285
  ] }),
2286
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: request_log_default.detailItem, children: [
2287
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: request_log_default.detailLabel, children: t("requestLogColCacheRate") }),
2288
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: request_log_default.detailValue, children: percent(cacheRate(entry)) })
2289
+ ] }),
2196
2290
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: request_log_default.detailItem, children: [
2197
2291
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: request_log_default.detailLabel, children: t("requestLogColTps") }),
2198
2292
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: request_log_default.detailValue, children: rate2(entry.tps) })
@@ -2207,7 +2301,7 @@ function RequestLogPage({ readLog, enabled, t, getLocale, sessionId }) {
2207
2301
  }
2208
2302
  return out;
2209
2303
  };
2210
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: request_log_default.page, children: [
2304
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: sessionId === void 0 ? request_log_default.page : `${request_log_default.page} ${request_log_default.pageLoose}`, children: [
2211
2305
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: request_log_default.head, children: [
2212
2306
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: request_log_default.headText, children: [
2213
2307
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h1", { className: request_log_default.title, children: t("requestLogTitle") }),
@@ -2243,6 +2337,10 @@ function RequestLogPage({ readLog, enabled, t, getLocale, sessionId }) {
2243
2337
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: request_log_default.cardLabel, children: t("requestLogStatTps") }),
2244
2338
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: request_log_default.cardValue, children: rate2(meanTps) })
2245
2339
  ] }),
2340
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: request_log_default.card, children: [
2341
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: request_log_default.cardLabel, children: t("requestLogStatCacheRate") }),
2342
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: request_log_default.cardValue, children: percent(meanCacheRate) })
2343
+ ] }),
2246
2344
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: request_log_default.card, children: [
2247
2345
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: request_log_default.cardLabel, children: t("requestLogReuse") }),
2248
2346
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: request_log_default.cardValue, children: reuse === void 0 ? "\u2014" : `${(reuse * 100).toFixed(0)}%` })
@@ -2319,7 +2417,7 @@ function RequestLogPage({ readLog, enabled, t, getLocale, sessionId }) {
2319
2417
  ] }),
2320
2418
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: request_log_default.field, children: [
2321
2419
  t("requestLogFilterSession"),
2322
- sessionPrefix !== "" && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
2420
+ sessionId !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
2323
2421
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2324
2422
  "input",
2325
2423
  {
@@ -2371,16 +2469,17 @@ function RequestLogPage({ readLog, enabled, t, getLocale, sessionId }) {
2371
2469
  ] }),
2372
2470
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: request_log_default.tableWrap, children: filtered.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: request_log_default.empty, children: t("requestLogEmpty") }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("table", { className: request_log_default.table, children: [
2373
2471
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("tr", { children: [
2374
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("th", { children: t("requestLogColTime") }),
2375
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("th", { children: t("requestLogColModel") }),
2376
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("th", { children: t("requestLogColSession") }),
2377
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("th", { children: t("requestLogColIn") }),
2378
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("th", { children: t("requestLogColOut") }),
2379
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("th", { children: t("requestLogColCache") }),
2380
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("th", { children: t("requestLogColTps") }),
2381
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("th", { children: t("requestLogColFirstToken") }),
2382
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("th", { children: t("requestLogColLatency") }),
2383
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("th", { children: t("requestLogColStatus") })
2472
+ head("atMs", t("requestLogColTime")),
2473
+ head("model", t("requestLogColModel")),
2474
+ head("session", t("requestLogColSession")),
2475
+ head("inputTokens", t("requestLogColIn")),
2476
+ head("outputTokens", t("requestLogColOut")),
2477
+ head("cacheReadTokens", t("requestLogColCache")),
2478
+ head("cacheRate", t("requestLogColCacheRate")),
2479
+ head("tps", t("requestLogColTps")),
2480
+ head("firstTokenMs", t("requestLogColFirstToken")),
2481
+ head("durationMs", t("requestLogColLatency")),
2482
+ head("status", t("requestLogColStatus"))
2384
2483
  ] }) }),
2385
2484
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("tbody", { children: rows() })
2386
2485
  ] }) }),
@@ -2602,6 +2701,13 @@ var REQUEST_LOG_PANEL = "dshopencodego-request-log";
2602
2701
  var inject = ["slots", "locale", "remote"];
2603
2702
  function apply(ctx) {
2604
2703
  ctx.effect(() => ctx.locale.register(NS, { zh, en }), "dshopencodego: copy dictionaries");
2704
+ const layout = { current: void 0 };
2705
+ ctx.inject(["layout"], (layoutScope) => {
2706
+ layout.current = layoutScope.layout;
2707
+ return () => {
2708
+ layout.current = void 0;
2709
+ };
2710
+ });
2605
2711
  const mounted = ctx.remote.$mount({
2606
2712
  package: usageRemote.package,
2607
2713
  descriptors: [
@@ -2696,18 +2802,18 @@ function apply(ctx) {
2696
2802
  t: (key) => translate(key),
2697
2803
  getLocale: () => scope.locale.getLocale().active,
2698
2804
  // The settings page opens the log in the main column, beside this
2699
- // page rather than over it. A composition without the layout service
2700
- // has no main column to select, so the button does nothing there.
2805
+ // page rather than over it. It reports whether it worked: a button
2806
+ // that silently does nothing is worse than one that says why.
2701
2807
  openRequestLog: () => {
2702
- try {
2703
- ctx.layout.selectPanel(REQUEST_LOG_PANEL);
2704
- } catch {
2705
- }
2808
+ const service = layout.current;
2809
+ if (service === void 0) return false;
2810
+ service.selectPanel(REQUEST_LOG_PANEL);
2811
+ return true;
2706
2812
  }
2707
2813
  })
2708
2814
  }, Section));
2709
2815
  });
2710
- ctx.inject(["modelDirectories", "remote.opencodeGoUsage"], (scope) => {
2816
+ ctx.inject(["modelDirectories", "remote.opencodeGoUsage", "remote.session"], (scope) => {
2711
2817
  const translate = scope.locale.bind(NS);
2712
2818
  scope.slots.inject("conversation.input.right", () => scope.slots.register({
2713
2819
  name: "conversation.input.right",
package/lib/index.js CHANGED
@@ -77665,7 +77665,6 @@ var OpencodeGoRequestLogService = class extends TypertRemoteService3 {
77665
77665
  };
77666
77666
 
77667
77667
  // src/request-log/store.ts
77668
- var SESSION_PREFIX_LENGTH = 8;
77669
77668
  var MAX_POOL_EVENTS = 20;
77670
77669
  var RequestLog = class {
77671
77670
  gates;
@@ -77706,8 +77705,7 @@ var RequestLog = class {
77706
77705
  if (!this.gates.enabled()) return void 0;
77707
77706
  const debug = this.gates.debug();
77708
77707
  const startedAt = Date.now();
77709
- const full = detail.sessionId ?? "";
77710
- const session = debug || full.length === 0 ? full : full.slice(0, SESSION_PREFIX_LENGTH);
77708
+ const session = detail.sessionId ?? "";
77711
77709
  let firstTokenAt;
77712
77710
  let method;
77713
77711
  let url;
@@ -77888,7 +77886,7 @@ function createUpdateChecker(deps) {
77888
77886
  }
77889
77887
 
77890
77888
  // src/update/version.ts
77891
- var PLUGIN_VERSION = true ? "0.1.65" : "0.0.0-dev";
77889
+ var PLUGIN_VERSION = true ? "0.1.66" : "0.0.0-dev";
77892
77890
 
77893
77891
  // src/update/service.ts
77894
77892
  import { TypertRemoteService as TypertRemoteService4 } from "@deepseek-ai/dsh-typert-protocol";
@@ -82,9 +82,9 @@ export interface OpencodeGoConfig {
82
82
  */
83
83
  requestLogEnabled: boolean;
84
84
  /**
85
- * Whether the request log also records pool lifecycle and full session
86
- * identity. Debug only: the common log carries a truncated session and no
87
- * pool events, which is enough to follow one conversation.
85
+ * Whether the request log also records pool lifecycle and request headers.
86
+ * Debug only: the common log carries no pool events and no header detail,
87
+ * which is enough to follow one conversation.
88
88
  */
89
89
  requestLogDebug: boolean;
90
90
  /**
@@ -13,14 +13,12 @@
13
13
  * - Authorization never enters a record. The recorder takes no headers
14
14
  * argument at all; the only header-derived value it stores is the session
15
15
  * id it is handed, already separated from the credential by the caller.
16
- * - The session id is stored truncated (first 8 characters) unless debug
17
- * mode is on at record time. A truncated id groups one conversation's
18
- * requests without writing the full identity to the log.
16
+ * - The session id is stored whole. It is a conversation identity, not a
17
+ * secret, and a truncated one cannot group a conversation's requests —
18
+ * two conversations sharing an eight-character prefix would read as one.
19
19
  *
20
20
  * @module @dan-ai-studio/dshopencodego/request-log/store
21
21
  */
22
- /** How many session-id characters the common (non-debug) log keeps. */
23
- export declare const SESSION_PREFIX_LENGTH = 8;
24
22
  /** One recorded request. Counts are provider-reported; timings are Host-measured. */
25
23
  export interface RequestLogEntry {
26
24
  /** Monotonic sequence within this process; gaps mean nothing was recorded. */
@@ -29,7 +27,7 @@ export interface RequestLogEntry {
29
27
  readonly atMs: number;
30
28
  /** The model id the request was made with. */
31
29
  readonly model: string;
32
- /** The session id, full in debug mode, truncated to {@link SESSION_PREFIX_LENGTH} otherwise. */
30
+ /** The session id the request belonged to, whole. */
33
31
  readonly session: string;
34
32
  /** HTTP method used. */
35
33
  readonly method: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dan-ai-studio/dshopencodego",
3
- "version": "0.1.65",
3
+ "version": "0.1.66",
4
4
  "description": "OpenCode Go provider for DeepSeek Harness: live gateway catalog, per-conversation session header, usage",
5
5
  "type": "module",
6
6
  "license": "MIT",