@dan-ai-studio/dshopencodego 0.1.76 → 0.1.78

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
@@ -113,7 +113,7 @@ Configuration lives in the profile's `cordis.patch.yml`:
113
113
  some-new-model: openai-responses # the only legal values are anthropic-messages / openai-completions / openai-responses
114
114
  pinnedAddresses: # fixed IP: host -> address (only the gateway host counts)
115
115
  opencode.ai: 104.18.32.47 # IPv4 or IPv6; an invalid or absent address uses the system DNS
116
- requestLogEnabled: true # request-log master switch, default on: off reads byte-for-byte as without the log
116
+ requestLogEnabled: false # request-log master switch, default off: on registers the sidebar entry and the conversation tab; off leaves no entry and reads byte-for-byte as without the log
117
117
  requestLogDebug: false # debug detail: pool events and the request user agent; session ids are always whole
118
118
  requestLogMaxEntries: 200 # most entries to keep, 50 to 500; oldest are dropped first
119
119
  retryPolicy: # optional: this route's retry policy, executed by dsh-llm-retry
@@ -139,7 +139,7 @@ The API key comes from the Harness credential store (reference name `OPENCODE_GO
139
139
  - 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.
140
140
  - 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.
141
141
 
142
- **Request log**: per-request records, on by default; turning them off returns the request path to exactly as without the log. The settings page's "Request log" group holds the master switch, the debug switch (pool events and the request user agent, default off), and the retention count (default 200).
142
+ **Request log**: per-request records, off by default; turning them on registers the sidebar entry and the conversation tab, and turning them off removes those entries and returns the request path to exactly as without the log. The settings page's "Request log" group holds the master switch, the debug switch (pool events and the request user agent, default off), and the retention count (default 200).
143
143
 
144
144
  - **Two entries, one page**:
145
145
  - **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.
@@ -151,8 +151,8 @@ The API key comes from the Harness credential store (reference name `OPENCODE_GO
151
151
  - **The In column is uncached new input**: the gateway reports a prompt as "uncached plus cache hits" and the log records only the new part — a fully prefix-cached turn reads as a tiny input with a 100% hit, which is not a bug. Hover a column head for its definition.
152
152
  - **Content split**: below the body size the detail shows **Text X / Images Y** — images estimated as base64 per deduped attachment (each capped at the route's encoded target), text as the residual, so the two always sum to the total. Image-dominated turns read at a glance.
153
153
  - Session ids are **always stored whole**: an id is a conversation identity, not a credential, and a truncated one reads two conversations as one.
154
- - 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.
155
- - 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.
154
+ - The log is a ring buffer (oldest dropped first) that dies with the process; snapshots are read on open and on manual refresh, plus once a second while any request is running, so a page nobody opens costs no traffic.
155
+ - The credential never enters a record: the recorder takes no headers argument at all, only the allowlisted session id and user agent. A request gets a "Requesting" placeholder row the moment it starts (the status column counts its seconds) and the row is completed in place when it ends. Failed requests are recorded with their error code; a stream the consumer abandoned early is recorded as `ABORTED` instead of vanishing silently.
156
156
 
157
157
  **Images**: an over-budget image is refused explicitly rather than silently downscaled or dropped. The budgets ride one preset `imageBudgetPreset` (three choices, default `dsh`), not three numbers:
158
158
 
package/README.md CHANGED
@@ -112,7 +112,7 @@ DSH 桌面端是 Electron 壳 + 完整的 dsh Web 应用,本插件对它同样
112
112
  some-new-model: openai-responses # 合法值只有 anthropic-messages / openai-completions / openai-responses
113
113
  pinnedAddresses: # 固定 IP:域名 → 地址(只对网关域名生效)
114
114
  opencode.ai: 104.18.32.47 # 可写 IPv4 或 IPv6;不合法或未设置时走系统 DNS
115
- requestLogEnabled: true # 请求日志总开关,默认开启:关闭后请求路径与没有该日志逐字节一致
115
+ requestLogEnabled: false # 请求日志总开关,默认关闭:打开后注册侧边栏入口与会话标签页;关闭后无入口,请求路径与没有该日志逐字节一致
116
116
  requestLogDebug: false # 排错详情:连接池事件与请求 UA;会话 id 始终完整记录
117
117
  requestLogMaxEntries: 200 # 最多保留条数,50 ~ 500,超限丢弃最旧的
118
118
  retryPolicy: # 可选:本路由的重试策略(由 dsh-llm-retry 执行)
@@ -138,7 +138,7 @@ API Key 通过 Harness 凭证库提供(引用名 `OPENCODE_GO_API_KEY`),
138
138
  - 设置页里以**秒**显示与填写(毫秒的数字人读不出量级),存储时换算成毫秒。取值 1 ~ 1800000 毫秒。
139
139
  - 空闲满这个时间之后,连接由连接池**自己回收**,不需要额外的清理机制(实际释放时间比设置值多约 2 秒,那是客户端在服务端给的期限之外自留的一点缓冲)。插件被卸载时,连接池也会**一并关掉**——它是插件的,不该留在机器上。
140
140
 
141
- **请求日志**:默认开启的逐请求记录,关掉它请求路径与没有该日志逐字节一致。设置页"请求日志"分组里有总开关、排错详情开关(连接池事件与请求 UA,默认关闭)、保留条数(默认 200)。
141
+ **请求日志**:默认关闭的逐请求记录,打开它才注册侧边栏入口与会话标签页;关掉它入口一并消失,请求路径与没有该日志逐字节一致。设置页"请求日志"分组里有总开关、排错详情开关(连接池事件与请求 UA,默认关闭)、保留条数(默认 200)。
142
142
 
143
143
  - **两个入口,同一个页面**:
144
144
  - **侧边栏图标 → 主区域整页**:跨会话看全部请求。日志是进程级的数据,放在侧边栏最合理——位置固定、常驻可见,和 DSH 自己的插件管理器同一个范式。
@@ -150,8 +150,8 @@ API Key 通过 Harness 凭证库提供(引用名 `OPENCODE_GO_API_KEY`),
150
150
  - **输入列是未缓存的新增 token**:网关把整段 prompt 拆成"未缓存+缓存命中"两部分上报,输入列只记新增的——同一会话前缀缓存全中时输入很小、缓存命中 100%,不是 bug。悬停列头有口径说明。
151
151
  - **内容拆分**:明细里"请求体"下面跟一行"文本 X /图片 Y"——图片是各去重后附件按 base64 估的占比(单张按路由的编码上限封顶),文本是总数减图片的剩余,两者恒等于请求体总数。图片 dominant 的请求一眼就能看出来。
152
152
  - 会话 id **始终完整记录**(它是会话身份,不是凭据;截断后两个会话会读成一个)。
153
- - 记录的是环形缓冲(超限丢最旧),进程重启即清;只读快照,不轮询,没人打开的页面零流量。
154
- - 凭据永远不进记录:记录器根本不接收请求头参数,只收白名单里的会话 id 与 UA;失败的请求同样记一条(含错误码),中断到没有终端事件的不记(网关没报总数就不编数)。
153
+ - 记录的是环形缓冲(超限丢最旧),进程重启即清;页面平时只在打开与手动刷新时读快照,有请求中时每秒自动刷新一次直到结束,没人打开的页面零流量。
154
+ - 凭据永远不进记录:记录器根本不接收请求头参数,只收白名单里的会话 id 与 UA;请求一开始就有一条"请求中"占位行(状态列读秒),结束时原位补全为完整记录。失败的请求同样记一条(含错误码);消费者提前终止的流记一条 `ABORTED`,不再无声丢失。
155
155
 
156
156
  **图片**:三个字段各管一层,超限的图片会被明确拒绝,而不是静默降质或丢弃:
157
157
 
package/lib/client.js CHANGED
@@ -258,6 +258,9 @@ function parseEntry(value) {
258
258
  ...row["decodeMs"] === void 0 ? {} : { decodeMs: count("decodeMs", row) },
259
259
  ...row["streamMs"] === void 0 ? {} : { streamMs: count("streamMs", row) },
260
260
  ...row["tps"] === void 0 ? {} : { tps: rate("tps", row) },
261
+ // Running rows cross the wire too: a viewer opened mid-request must
262
+ // see them. Older snapshots predate the flag and read as finished.
263
+ ...row["running"] === true ? { running: true } : {},
261
264
  ok: row["ok"] === true,
262
265
  ...typeof row["code"] === "string" ? { code: row["code"] } : {},
263
266
  ...typeof row["poolKey"] === "string" ? { poolKey: row["poolKey"] } : {},
@@ -513,7 +516,7 @@ var en = {
513
516
  imageBudgetPresetDshopencodego: "This plugin (1.44M px / 400KB / 6MB)",
514
517
  imageBudgetPresetPiAi: "pi-ai catalog (2000\xD72000 / 4.5MB / 20MB)",
515
518
  requestLogTitle: "Request log",
516
- requestLogHint: "Per-request records for troubleshooting. On by default; turning it off returns the request path to exactly as without the log.",
519
+ requestLogHint: "Per-request records for troubleshooting. Off by default; turning it on registers the sidebar entry and the conversation tab, and the request path stays exactly as without the log when off.",
517
520
  requestLogPageHint: "One row per request. Click a column head to sort; the table scrolls and the filters stay put.",
518
521
  requestLogEnabled: "Record requests",
519
522
  requestLogDebug: "Debug detail",
@@ -525,6 +528,7 @@ var en = {
525
528
  requestLogOpenSessionFailed: "That session is archived and cannot be opened.",
526
529
  requestLogClose: "Close",
527
530
  requestLogRefresh: "Refresh",
531
+ requestLogRunning: "Requesting",
528
532
  requestLogEmpty: "No requests recorded. Turn the log on, then make a request.",
529
533
  requestLogDisabled: "The request log is off.",
530
534
  requestLogFilterModel: "Model",
@@ -711,7 +715,7 @@ var zh = {
711
715
  imageBudgetPresetDshopencodego: "\u672C\u63D2\u4EF6\uFF08144\u4E07\u50CF\u7D20 / 400KB / 6MB\uFF09",
712
716
  imageBudgetPresetPiAi: "pi-ai \u76EE\u5F55\uFF082000\xD72000 / 4.5MB / 20MB\uFF09",
713
717
  requestLogTitle: "\u8BF7\u6C42\u65E5\u5FD7",
714
- requestLogHint: "\u6392\u67E5\u7528\u7684\u9010\u8BF7\u6C42\u8BB0\u5F55\u3002\u9ED8\u8BA4\u5F00\u542F\uFF1B\u5173\u95ED\u540E\u8BF7\u6C42\u8DEF\u5F84\u4E0E\u6CA1\u6709\u8BE5\u65E5\u5FD7\u9010\u5B57\u8282\u4E00\u81F4\u3002",
718
+ requestLogHint: "\u6392\u67E5\u7528\u7684\u9010\u8BF7\u6C42\u8BB0\u5F55\u3002\u9ED8\u8BA4\u5173\u95ED\uFF1B\u6253\u5F00\u540E\u6CE8\u518C\u4FA7\u8FB9\u680F\u5165\u53E3\u4E0E\u4F1A\u8BDD\u6807\u7B7E\u9875\uFF0C\u5173\u95ED\u540E\u8BF7\u6C42\u8DEF\u5F84\u4E0E\u6CA1\u6709\u8BE5\u65E5\u5FD7\u9010\u5B57\u8282\u4E00\u81F4\u3002",
715
719
  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",
716
720
  requestLogEnabled: "\u8BB0\u5F55\u8BF7\u6C42",
717
721
  requestLogDebug: "\u6392\u9519\u8BE6\u60C5",
@@ -723,6 +727,7 @@ var zh = {
723
727
  requestLogOpenSessionFailed: "\u8BE5\u4F1A\u8BDD\u5DF2\u5F52\u6863\uFF0C\u6253\u4E0D\u5F00\u3002",
724
728
  requestLogClose: "\u5173\u95ED",
725
729
  requestLogRefresh: "\u5237\u65B0",
730
+ requestLogRunning: "\u8BF7\u6C42\u4E2D",
726
731
  requestLogEmpty: "\u6682\u65E0\u8BB0\u5F55\u3002\u5148\u6253\u5F00\u65E5\u5FD7\u5F00\u5173\uFF0C\u518D\u53D1\u8D77\u4E00\u6B21\u8BF7\u6C42\u3002",
727
732
  requestLogDisabled: "\u8BF7\u6C42\u65E5\u5FD7\u672A\u5F00\u542F\u3002",
728
733
  requestLogFilterModel: "\u6A21\u578B",
@@ -1066,7 +1071,7 @@ function settingsOf(value) {
1066
1071
  pinnedAddresses: Object.fromEntries(pinEntries),
1067
1072
  keepAliveTimeoutMs: typeof keepAlive === "number" ? keepAlive : DEFAULT_KEEP_ALIVE_TIMEOUT_MS,
1068
1073
  imageBudgetPreset: isImageBudgetPreset(imageBudgetPreset) ? imageBudgetPreset : "dsh",
1069
- requestLogEnabled: typeof requestLogEnabled === "boolean" ? requestLogEnabled : true,
1074
+ requestLogEnabled: typeof requestLogEnabled === "boolean" ? requestLogEnabled : false,
1070
1075
  requestLogDebug: typeof requestLogDebug === "boolean" ? requestLogDebug : false,
1071
1076
  requestLogMaxEntries: typeof requestLogMaxEntries === "number" ? requestLogMaxEntries : DEFAULT_REQUEST_LOG_MAX_ENTRIES
1072
1077
  };
@@ -1965,7 +1970,7 @@ function Section({ controller, t, getLocale, openRequestLog }) {
1965
1970
  "input",
1966
1971
  {
1967
1972
  type: "checkbox",
1968
- checked: state.settings?.requestLogEnabled ?? true,
1973
+ checked: state.settings?.requestLogEnabled ?? false,
1969
1974
  disabled: !editable || state.saving,
1970
1975
  onChange: (event) => {
1971
1976
  void act(async () => {
@@ -2016,7 +2021,7 @@ function Section({ controller, t, getLocale, openRequestLog }) {
2016
2021
  ),
2017
2022
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: section_default.hint, children: t("requestLogMaxEntriesHint") })
2018
2023
  ] }),
2019
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: section_default.row, children: [
2024
+ state.settings?.requestLogEnabled === true && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: section_default.row, children: [
2020
2025
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: section_default.button, onClick: () => {
2021
2026
  setLogOpenFailed(!(openRequestLog?.() ?? false));
2022
2027
  }, children: t("requestLogOpen") }),
@@ -2140,10 +2145,10 @@ if (!document.querySelector("style[data-plugin-css=" + JSON.stringify(id2) + "]"
2140
2145
  const style = document.createElement("style");
2141
2146
  style.dataset.plugin = "@dan-ai-studio/dshopencodego";
2142
2147
  style.dataset.pluginCss = id2;
2143
- 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_pageLoose .Lx7MOq_stuck{z-index:3;background:var(--dsw-alias-bg-base);position:sticky;top:0}.Lx7MOq_pageLoose .Lx7MOq_table th{top:var(--request-log-stuck,0px)}.Lx7MOq_pageLoose .Lx7MOq_foot{z-index:2;background:var(--dsw-alias-bg-base);position:sticky;bottom:0}.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_sessionLink{color:var(--dsw-alias-link);font:inherit;text-align:left;cursor:pointer;white-space:nowrap;background:0 0;border:none;padding:0}.Lx7MOq_sessionLink:hover{text-decoration:underline}.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)}";
2148
+ 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_pageLoose .Lx7MOq_stuck{z-index:3;background:var(--dsw-alias-bg-base);position:sticky;top:0}.Lx7MOq_pageLoose .Lx7MOq_table th{top:var(--request-log-stuck,0px)}.Lx7MOq_pageLoose .Lx7MOq_foot{z-index:2;background:var(--dsw-alias-bg-base);position:sticky;bottom:0}.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_sessionLink{color:var(--dsw-alias-link);font:inherit;text-align:left;cursor:pointer;white-space:nowrap;background:0 0;border:none;padding:0}.Lx7MOq_sessionLink:hover{text-decoration:underline}.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_rowRunning td:first-child{box-shadow:inset 3px 0 0 var(--dsw-alias-brand-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)}";
2144
2149
  document.head.appendChild(style);
2145
2150
  }
2146
- 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", "sessionLink": "Lx7MOq_sessionLink", "stuck": "Lx7MOq_stuck", "subtitle": "Lx7MOq_subtitle", "summary": "Lx7MOq_summary", "table": "Lx7MOq_table", "tableWrap": "Lx7MOq_tableWrap", "thSortable": "Lx7MOq_thSortable", "title": "Lx7MOq_title", "toolbar": "Lx7MOq_toolbar" };
2151
+ 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", "rowRunning": "Lx7MOq_rowRunning", "rowSlow": "Lx7MOq_rowSlow", "select": "Lx7MOq_select", "sessionLink": "Lx7MOq_sessionLink", "stuck": "Lx7MOq_stuck", "subtitle": "Lx7MOq_subtitle", "summary": "Lx7MOq_summary", "table": "Lx7MOq_table", "tableWrap": "Lx7MOq_tableWrap", "thSortable": "Lx7MOq_thSortable", "title": "Lx7MOq_title", "toolbar": "Lx7MOq_toolbar" };
2147
2152
 
2148
2153
  // src/client/RequestLogPage.tsx
2149
2154
  var import_jsx_runtime2 = require("react/jsx-runtime");
@@ -2183,7 +2188,7 @@ function sortValue(entry, key) {
2183
2188
  case "durationMs":
2184
2189
  return entry.durationMs ?? -1;
2185
2190
  case "status":
2186
- return entry.ok ? `ok-${entry.status ?? ""}` : `fail-${entry.code ?? ""}`;
2191
+ return entry.running === true ? "running" : entry.ok ? `ok-${entry.status ?? ""}` : `fail-${entry.code ?? ""}`;
2187
2192
  }
2188
2193
  }
2189
2194
  var PAGE_SIZE = 50;
@@ -2233,8 +2238,10 @@ function groupKey(entry, groupBy) {
2233
2238
  return entry.session === "" ? "\u2014" : entry.session;
2234
2239
  case "model":
2235
2240
  return entry.model;
2241
+ // A running row is neither a success nor a failure yet: it groups on
2242
+ // its own instead of inflating either outcome.
2236
2243
  case "result":
2237
- return entry.ok ? "ok" : entry.code ?? "fail";
2244
+ return entry.running === true ? "running" : entry.ok ? "ok" : entry.code ?? "fail";
2238
2245
  case "none":
2239
2246
  return void 0;
2240
2247
  }
@@ -2276,6 +2283,7 @@ function RequestLogPage({ readLog, enabled, canOpenSession, openSession, t, getL
2276
2283
  const title = catalog?.byId[id4]?.title?.trim();
2277
2284
  return title === void 0 || title === "" ? { text: id4 === "" ? "\u2014" : id4, titled: false } : { text: title, titled: true };
2278
2285
  }, [catalog]);
2286
+ const resultGroupLabel = (group) => group === "ok" ? t("requestLogResultOk") : group === "fail" ? t("requestLogResultFail") : group === "running" ? t("requestLogRunning") : group;
2279
2287
  const toggleSort = (key) => {
2280
2288
  if (key === sortKey) {
2281
2289
  setSortDir(sortDir === "asc" ? "desc" : "asc");
@@ -2301,6 +2309,16 @@ function RequestLogPage({ readLog, enabled, canOpenSession, openSession, t, getL
2301
2309
  (0, import_react2.useEffect)(() => {
2302
2310
  void load();
2303
2311
  }, [load]);
2312
+ const hasRunning = (snapshot?.entries ?? []).some((entry) => entry.running === true);
2313
+ (0, import_react2.useEffect)(() => {
2314
+ if (!hasRunning) return;
2315
+ const timer = setInterval(() => {
2316
+ void load();
2317
+ }, 1e3);
2318
+ return () => {
2319
+ clearInterval(timer);
2320
+ };
2321
+ }, [hasRunning, load]);
2304
2322
  const models = (0, import_react2.useMemo)(() => {
2305
2323
  const ids = /* @__PURE__ */ new Set();
2306
2324
  for (const entry of snapshot?.entries ?? []) ids.add(entry.model);
@@ -2311,7 +2329,7 @@ function RequestLogPage({ readLog, enabled, canOpenSession, openSession, t, getL
2311
2329
  return (snapshot?.entries ?? []).filter((entry) => {
2312
2330
  if (model !== "all" && entry.model !== model) return false;
2313
2331
  if (result === "ok" && !entry.ok) return false;
2314
- if (result === "fail" && entry.ok) return false;
2332
+ if (result === "fail" && (entry.ok || entry.running === true)) return false;
2315
2333
  if (slowOnly && (entry.durationMs ?? 0) < Math.max(0, slowSeconds) * 1e3) return false;
2316
2334
  if (onlySession && sessionId !== void 0 && entry.session !== sessionId) return false;
2317
2335
  if (query !== "") {
@@ -2334,7 +2352,7 @@ function RequestLogPage({ readLog, enabled, canOpenSession, openSession, t, getL
2334
2352
  return next;
2335
2353
  }, [filtered, sortKey, sortDir]);
2336
2354
  const pageEntries = sorted.slice(safePage * PAGE_SIZE, (safePage + 1) * PAGE_SIZE);
2337
- const failures = filtered.filter((entry) => !entry.ok).length;
2355
+ const failures = filtered.filter((entry) => !entry.ok && entry.running !== true).length;
2338
2356
  const meanLatency = average(filtered.flatMap((entry) => entry.durationMs === void 0 ? [] : [entry.durationMs]));
2339
2357
  const meanFirstToken = average(filtered.flatMap((entry) => entry.firstTokenMs === void 0 ? [] : [entry.firstTokenMs]));
2340
2358
  const meanTps = average(filtered.flatMap((entry) => entry.tps === void 0 ? [] : [entry.tps]));
@@ -2357,10 +2375,10 @@ function RequestLogPage({ readLog, enabled, canOpenSession, openSession, t, getL
2357
2375
  if (group !== void 0 && group !== lastGroup) {
2358
2376
  lastGroup = group;
2359
2377
  out.push(
2360
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("tr", { className: request_log_default.group, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { colSpan: 11, children: groupBy === "session" ? sessionLabel(group).text : group }) }, `group-${group}-${entry.seq}`)
2378
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("tr", { className: request_log_default.group, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { colSpan: 11, children: groupBy === "session" ? sessionLabel(group).text : groupBy === "result" ? resultGroupLabel(group) : group }) }, `group-${group}-${entry.seq}`)
2361
2379
  );
2362
2380
  }
2363
- const state = !entry.ok ? "rowFail" : entry.tps !== void 0 && entry.tps < 5 ? "rowSlow" : "rowOk";
2381
+ const state = entry.running === true ? "rowRunning" : !entry.ok ? "rowFail" : entry.tps !== void 0 && entry.tps < 5 ? "rowSlow" : "rowOk";
2364
2382
  const session = sessionLabel(entry.session);
2365
2383
  out.push(
2366
2384
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
@@ -2398,8 +2416,8 @@ function RequestLogPage({ readLog, enabled, canOpenSession, openSession, t, getL
2398
2416
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { children: percent(cacheRate(entry)) }),
2399
2417
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { children: rate2(entry.tps) }),
2400
2418
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { children: duration(entry.firstTokenMs) }),
2401
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { children: duration(entry.durationMs) }),
2402
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { children: entry.ok ? entry.status === void 0 ? "\u2713" : String(entry.status) : entry.code ?? "\u2717" })
2419
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { children: entry.running === true ? duration(Math.max(0, Date.now() - entry.atMs)) : duration(entry.durationMs) }),
2420
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { children: entry.running === true ? `${t("requestLogRunning")} \xB7 ${duration(Math.max(0, Date.now() - entry.atMs))}` : entry.ok ? entry.status === void 0 ? "\u2713" : String(entry.status) : entry.code ?? "\u2717" })
2403
2421
  ]
2404
2422
  },
2405
2423
  entry.seq
@@ -2962,19 +2980,19 @@ function apply(ctx) {
2962
2980
  scope.effect(() => () => {
2963
2981
  controller.dispose();
2964
2982
  });
2983
+ const isLogEnabled = () => {
2984
+ if (profile === void 0) return true;
2985
+ const value = profile.getSnapshot().value;
2986
+ if (value === null || typeof value !== "object") return false;
2987
+ return value.requestLogEnabled === true;
2988
+ };
2965
2989
  const requestLog = {
2966
2990
  readLog: async () => {
2967
2991
  const result = await scope.remote.opencodeGoRequestLog.readLog();
2968
2992
  if (!result.ok) throw result.error;
2969
2993
  return result.value;
2970
2994
  },
2971
- // Read live: the switch on the settings page can change while the log
2972
- // page sits open, and the page says so rather than pretending.
2973
- enabled: () => {
2974
- const value = profile?.getSnapshot().value;
2975
- if (value === null || typeof value !== "object") return false;
2976
- return value.requestLogEnabled === true;
2977
- },
2995
+ enabled: isLogEnabled,
2978
2996
  // Whether a title click can open its session. The navigation service
2979
2997
  // is a separate composition member; without it titles stay text.
2980
2998
  canOpenSession: () => sessionOpener.current !== void 0,
@@ -2995,15 +3013,34 @@ function apply(ctx) {
2995
3013
  t: (key) => translate(key),
2996
3014
  getLocale: () => scope.locale.getLocale().active
2997
3015
  };
2998
- scope.slots.inject("main", function* () {
2999
- yield scope.slots.register({
3000
- name: "main",
3001
- key: REQUEST_LOG_PANEL,
3002
- locale: NS,
3003
- inject: () => requestLog
3004
- }, RequestLogPage);
3005
- });
3006
- scope.slots.inject("sidebar.panellist", () => scope.slots.register({
3016
+ const gateLogSeat = (slot, register) => {
3017
+ scope.slots.inject(slot, () => {
3018
+ let disposeEntry;
3019
+ const sync = () => {
3020
+ if (isLogEnabled()) {
3021
+ disposeEntry ??= register();
3022
+ } else {
3023
+ disposeEntry?.();
3024
+ disposeEntry = void 0;
3025
+ }
3026
+ };
3027
+ const unsubscribe = profile?.subscribe(sync) ?? (() => {
3028
+ });
3029
+ sync();
3030
+ return () => {
3031
+ unsubscribe();
3032
+ disposeEntry?.();
3033
+ disposeEntry = void 0;
3034
+ };
3035
+ });
3036
+ };
3037
+ gateLogSeat("main", () => scope.slots.register({
3038
+ name: "main",
3039
+ key: REQUEST_LOG_PANEL,
3040
+ locale: NS,
3041
+ inject: () => requestLog
3042
+ }, RequestLogPage));
3043
+ gateLogSeat("sidebar.panellist", () => scope.slots.register({
3007
3044
  name: "sidebar.panellist",
3008
3045
  id: REQUEST_LOG_PANEL,
3009
3046
  // After the Host's own panels, which run from 0.
@@ -3011,7 +3048,7 @@ function apply(ctx) {
3011
3048
  label: () => translate("requestLogTitle"),
3012
3049
  locale: NS
3013
3050
  }, RequestLogPanelIcon));
3014
- scope.slots.inject("conversation.view", () => scope.slots.register({
3051
+ gateLogSeat("conversation.view", () => scope.slots.register({
3015
3052
  name: "conversation.view",
3016
3053
  id: "dshopencodego-request-log",
3017
3054
  // After the Host's own View (the chat itself), which runs at 0.
package/lib/index.js CHANGED
@@ -75876,11 +75876,13 @@ var fields = {
75876
75876
  maxTokens: Schema.union([Schema.const(null), Schema.number().step(1).min(1).max(Number.MAX_SAFE_INTEGER)])
75877
75877
  })])).default({}),
75878
75878
  modelProtocols: Schema.dict(Schema.string()).default({}),
75879
- // On by default: recording costs one call and one boolean per request
75880
- // plus a bounded ring, and buys a per-request trail whenever something
75881
- // looks wrong. Off returns the request path to exactly as without the
75882
- // log, because the recorder is never constructed then.
75883
- requestLogEnabled: Schema.boolean().default(true),
75879
+ // Off by default: the log is a troubleshooting tool, and an unasked
75880
+ // sidebar entry plus a conversation tab is the wrong first impression.
75881
+ // On, recording costs one call and one boolean per request plus a bounded
75882
+ // ring, and buys a per-request trail whenever something looks wrong. Off
75883
+ // returns the request path to exactly as without the log, because the
75884
+ // recorder is never constructed then.
75885
+ requestLogEnabled: Schema.boolean().default(false),
75884
75886
  requestLogDebug: Schema.boolean().default(false),
75885
75887
  requestLogMaxEntries: Schema.number().step(1).min(MIN_REQUEST_LOG_MAX_ENTRIES).max(MAX_REQUEST_LOG_MAX_ENTRIES).default(DEFAULT_REQUEST_LOG_MAX_ENTRIES),
75886
75888
  pinnedAddresses: Schema.dict(Schema.string()).default({}),
@@ -77066,6 +77068,7 @@ var OpencodeGoAdapter = class extends LlmAdapter {
77066
77068
  } finally {
77067
77069
  if (!exhausted) {
77068
77070
  timedOut = timeoutOf(watchdog.signal, "LLM_STREAM_IDLE_TIMEOUT") !== void 0;
77071
+ span?.finish(false, "ABORTED");
77069
77072
  consumer.abort("opencode-go stream consumer stopped");
77070
77073
  try {
77071
77074
  void Promise.resolve(iterator.return(void 0)).catch(() => {
@@ -77319,6 +77322,9 @@ function parseEntry(value) {
77319
77322
  ...row["decodeMs"] === void 0 ? {} : { decodeMs: count("decodeMs", row) },
77320
77323
  ...row["streamMs"] === void 0 ? {} : { streamMs: count("streamMs", row) },
77321
77324
  ...row["tps"] === void 0 ? {} : { tps: rate("tps", row) },
77325
+ // Running rows cross the wire too: a viewer opened mid-request must
77326
+ // see them. Older snapshots predate the flag and read as finished.
77327
+ ...row["running"] === true ? { running: true } : {},
77322
77328
  ok: row["ok"] === true,
77323
77329
  ...typeof row["code"] === "string" ? { code: row["code"] } : {},
77324
77330
  ...typeof row["poolKey"] === "string" ? { poolKey: row["poolKey"] } : {},
@@ -77895,52 +77901,77 @@ var RequestLog = class {
77895
77901
  * Begin one request's span, or nothing when the log is disabled.
77896
77902
  *
77897
77903
  * This is the single gate on the request path: a disabled log costs one
77898
- * method call and one boolean read, and constructs nothing.
77904
+ * method call and one boolean read, and constructs nothing. When enabled,
77905
+ * the running row is appended before the span is returned, so the viewer
77906
+ * sees the request while it runs; `request` and `status` mutate that same
77907
+ * visible row as the wire facts arrive.
77899
77908
  */
77900
77909
  start(detail) {
77901
77910
  if (!this.gates.enabled()) return void 0;
77902
77911
  const debug = this.gates.debug();
77903
77912
  const startedAt = Date.now();
77904
77913
  const session = detail.sessionId ?? "";
77914
+ const record2 = {
77915
+ seq: ++this.seq,
77916
+ atMs: startedAt,
77917
+ model: detail.model,
77918
+ ...detail.protocol === void 0 ? {} : { protocol: detail.protocol },
77919
+ session,
77920
+ method: detail.method ?? "POST",
77921
+ url: detail.url ?? "",
77922
+ running: true,
77923
+ ok: false,
77924
+ shape: { messages: detail.messageCount, hasImage: detail.hasImage, tools: detail.toolCount },
77925
+ ...detail.imageBytes === void 0 ? {} : { imageBytes: detail.imageBytes },
77926
+ ...!debug ? {} : {
77927
+ headers: {
77928
+ ...detail.headers.session === void 0 ? {} : { session: detail.headers.session },
77929
+ ...detail.headers.userAgent === void 0 ? {} : { userAgent: detail.headers.userAgent }
77930
+ }
77931
+ }
77932
+ };
77933
+ this.insert(record2, true);
77905
77934
  let firstTokenAt;
77906
77935
  let lastTokenAt;
77907
- let method;
77908
- let url;
77909
- let requestBytes;
77910
- let status;
77936
+ let reported = false;
77911
77937
  let finished = false;
77912
77938
  return {
77913
77939
  token: () => {
77914
77940
  const now = Date.now();
77915
- if (firstTokenAt === void 0) firstTokenAt = now;
77941
+ if (firstTokenAt === void 0) {
77942
+ firstTokenAt = now;
77943
+ record2.firstTokenMs = Math.max(0, now - startedAt);
77944
+ }
77916
77945
  lastTokenAt = now;
77917
77946
  },
77918
77947
  request: (nextMethod, nextUrl, nextRequestBytes) => {
77919
- if (method === void 0) {
77920
- method = nextMethod;
77921
- url = nextUrl;
77922
- requestBytes = nextRequestBytes;
77948
+ if (!reported) {
77949
+ reported = true;
77950
+ record2.method = nextMethod;
77951
+ record2.url = nextUrl;
77952
+ if (nextRequestBytes !== void 0) record2.requestBytes = nextRequestBytes;
77923
77953
  }
77924
77954
  },
77925
77955
  status: (value) => {
77926
- status = value;
77956
+ record2.status = value;
77927
77957
  },
77928
77958
  finish: (ok, code, usage, costUsd) => {
77929
77959
  if (finished) return;
77930
77960
  finished = true;
77961
+ const seq = record2.seq;
77931
77962
  const endedAt = Date.now();
77932
77963
  const decodeMs = firstTokenAt === void 0 ? void 0 : Math.max(0, endedAt - firstTokenAt);
77933
77964
  const streamMs = firstTokenAt === void 0 || lastTokenAt === void 0 ? void 0 : Math.max(0, lastTokenAt - firstTokenAt);
77934
77965
  const tps = decodeMs === void 0 || decodeMs <= 0 || usage === void 0 || streamMs === void 0 || streamMs < MIN_STREAM_MS_FOR_RATE ? void 0 : usage.outputTokens / (decodeMs / 1e3);
77935
77966
  const entry = {
77936
- seq: ++this.seq,
77967
+ seq,
77937
77968
  atMs: startedAt,
77938
77969
  model: detail.model,
77939
77970
  ...detail.protocol === void 0 ? {} : { protocol: detail.protocol },
77940
77971
  session,
77941
- method: method ?? detail.method ?? "POST",
77942
- url: url ?? detail.url ?? "",
77943
- ...status === void 0 ? {} : { status },
77972
+ method: record2.method,
77973
+ url: record2.url,
77974
+ ...record2.status === void 0 ? {} : { status: record2.status },
77944
77975
  ...usage === void 0 ? {} : {
77945
77976
  inputTokens: usage.inputTokens,
77946
77977
  outputTokens: usage.outputTokens,
@@ -77949,7 +77980,7 @@ var RequestLog = class {
77949
77980
  ...usage.reasoningTokens === void 0 || usage.reasoningTokens <= 0 ? {} : { reasoningTokens: usage.reasoningTokens }
77950
77981
  },
77951
77982
  ...costUsd === void 0 ? {} : { costUsd },
77952
- ...requestBytes === void 0 ? {} : { requestBytes },
77983
+ ...record2.requestBytes === void 0 ? {} : { requestBytes: record2.requestBytes },
77953
77984
  ...detail.imageBytes === void 0 ? {} : { imageBytes: detail.imageBytes },
77954
77985
  durationMs: Math.max(0, endedAt - startedAt),
77955
77986
  ...firstTokenAt === void 0 ? {} : { firstTokenMs: Math.max(0, firstTokenAt - startedAt) },
@@ -77970,12 +78001,17 @@ var RequestLog = class {
77970
78001
  }
77971
78002
  }
77972
78003
  };
77973
- this.append(entry);
78004
+ const at = this.entries.findIndex((candidate) => candidate.seq === seq);
78005
+ if (at === -1) {
78006
+ this.insert(entry, false);
78007
+ } else {
78008
+ this.entries[at] = entry;
78009
+ }
77974
78010
  }
77975
78011
  };
77976
78012
  }
77977
- append(entry) {
77978
- this.totalRecords += 1;
78013
+ insert(entry, count2) {
78014
+ if (count2) this.totalRecords += 1;
77979
78015
  const bound = Math.max(1, Math.floor(this.gates.maxEntries()));
77980
78016
  const next = [entry, ...this.entries].slice(0, bound);
77981
78017
  this.droppedRecords += this.entries.length + 1 - next.length;
@@ -78095,7 +78131,7 @@ function createUpdateChecker(deps) {
78095
78131
  }
78096
78132
 
78097
78133
  // src/update/version.ts
78098
- var PLUGIN_VERSION = true ? "0.1.76" : "0.0.0-dev";
78134
+ var PLUGIN_VERSION = true ? "0.1.78" : "0.0.0-dev";
78099
78135
 
78100
78136
  // src/update/service.ts
78101
78137
  import { TypertRemoteService as TypertRemoteService4 } from "@deepseek-ai/dsh-typert-protocol";
@@ -77,10 +77,10 @@ export interface OpencodeGoConfig {
77
77
  */
78
78
  modelProtocols: Record<string, string>;
79
79
  /**
80
- * Whether the request log records anything at all. On by default: one
81
- * call and one boolean per request, plus a bounded ring. Off returns the
82
- * request path to exactly as without the log — the recorder is never
83
- * constructed.
80
+ * Whether the request log records anything at all. Off by default: one
81
+ * call and one boolean per request, plus a bounded ring, only when on.
82
+ * Off returns the request path to exactly as without the log — the
83
+ * recorder is never constructed, and the log's pages stay unregistered.
84
84
  */
85
85
  requestLogEnabled: boolean;
86
86
  /**
@@ -4,9 +4,10 @@
4
4
  * Everything here is inert unless the profile enables it. The recorder is
5
5
  * constructed only when `requestLogEnabled` reads true, so a disabled log
6
6
  * changes nothing on the request path — not one extra allocation. When
7
- * enabled, one entry per completed or failed request is appended; the oldest
8
- * entries are dropped past the configured bound, so the log can never grow
9
- * with the session.
7
+ * enabled, `start` appends a running placeholder immediately — so a request
8
+ * is visible from the moment it begins, not only after it ends — and
9
+ * `finish` completes that same row in place. The oldest entries are dropped
10
+ * past the configured bound, so the log can never grow with the session.
10
11
  *
11
12
  * Two facts are structural rather than conventional:
12
13
  *
@@ -101,7 +102,16 @@ export interface RequestLogEntry {
101
102
  * misreport a buffered reply as infinitely quick.
102
103
  */
103
104
  readonly tps?: number;
104
- /** Whether the request completed. */
105
+ /**
106
+ * Whether the request is still in flight. Present only while it runs:
107
+ * `finish` removes it. A running row always reads `ok: false` with no
108
+ * `code`, so readers must check this flag before counting a failure.
109
+ */
110
+ readonly running?: boolean;
111
+ /**
112
+ * Whether the request completed successfully. Always false while
113
+ * `running` is true.
114
+ */
105
115
  readonly ok: boolean;
106
116
  /** Failure code (`TIMEOUT`, `TRANSPORT`, …) for a request that did not complete. */
107
117
  readonly code?: string;
@@ -136,7 +146,7 @@ export interface PoolLogEvent {
136
146
  }
137
147
  /** What the viewer reads: the entries plus the counters a rate needs. */
138
148
  export interface RequestLogSnapshot {
139
- /** Newest first, at most the configured bound. */
149
+ /** Newest first, at most the configured bound — finished rows plus running ones. */
140
150
  readonly entries: readonly RequestLogEntry[];
141
151
  /** Recent pool lifecycle, newest first, capped. */
142
152
  readonly poolEvents: readonly PoolLogEvent[];
@@ -161,11 +171,12 @@ export interface RequestLogGates {
161
171
  /**
162
172
  * An in-flight request, handed back only when the log is enabled.
163
173
  *
164
- * Every method is cheap by construction: one `Date.now` read per content
165
- * delta and small integer arithmetic across the whole span. No
166
- * serialization, no hashing, no body handling — the counts ride the usage
167
- * chunk the adapter already holds, and the timings bracket work that
168
- * already happened.
174
+ * `start` already appended its running row, so every method mutates the
175
+ * visible entry in place. Every method stays cheap by construction: one
176
+ * `Date.now` read per content delta, field writes for the wire line, and
177
+ * small integer arithmetic at close. No serialization, no hashing, no body
178
+ * handling — the counts ride the usage chunk the adapter already holds,
179
+ * and the timings bracket work that already happened.
169
180
  */
170
181
  export interface RequestSpan {
171
182
  /**
@@ -188,7 +199,7 @@ export interface RequestSpan {
188
199
  /** Mark the response status line's arrival. */
189
200
  status(status: number): void;
190
201
  /**
191
- * Close the span and append its entry.
202
+ * Close the span and complete its running row in place.
192
203
  * @param ok - whether the request completed.
193
204
  * @param code - failure code for a request that did not complete.
194
205
  * @param usage - provider-reported counts, when the terminal usage arrived.
@@ -254,10 +265,13 @@ export declare class RequestLog {
254
265
  * Begin one request's span, or nothing when the log is disabled.
255
266
  *
256
267
  * This is the single gate on the request path: a disabled log costs one
257
- * method call and one boolean read, and constructs nothing.
268
+ * method call and one boolean read, and constructs nothing. When enabled,
269
+ * the running row is appended before the span is returned, so the viewer
270
+ * sees the request while it runs; `request` and `status` mutate that same
271
+ * visible row as the wire facts arrive.
258
272
  */
259
273
  start(detail: RequestStart): RequestSpan | undefined;
260
- private append;
274
+ private insert;
261
275
  /** Shrink the ring to a new bound immediately; growing backfills nothing. */
262
276
  setMaxEntries(bound: number): void;
263
277
  /** The current reading, newest first. Computed lazily: nothing on the hot path. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dan-ai-studio/dshopencodego",
3
- "version": "0.1.76",
3
+ "version": "0.1.78",
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",