@dan-ai-studio/dshopencodego 0.1.77 → 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
@@ -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
@@ -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"] } : {},
@@ -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",
@@ -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",
@@ -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
package/lib/index.js CHANGED
@@ -77068,6 +77068,7 @@ var OpencodeGoAdapter = class extends LlmAdapter {
77068
77068
  } finally {
77069
77069
  if (!exhausted) {
77070
77070
  timedOut = timeoutOf(watchdog.signal, "LLM_STREAM_IDLE_TIMEOUT") !== void 0;
77071
+ span?.finish(false, "ABORTED");
77071
77072
  consumer.abort("opencode-go stream consumer stopped");
77072
77073
  try {
77073
77074
  void Promise.resolve(iterator.return(void 0)).catch(() => {
@@ -77321,6 +77322,9 @@ function parseEntry(value) {
77321
77322
  ...row["decodeMs"] === void 0 ? {} : { decodeMs: count("decodeMs", row) },
77322
77323
  ...row["streamMs"] === void 0 ? {} : { streamMs: count("streamMs", row) },
77323
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 } : {},
77324
77328
  ok: row["ok"] === true,
77325
77329
  ...typeof row["code"] === "string" ? { code: row["code"] } : {},
77326
77330
  ...typeof row["poolKey"] === "string" ? { poolKey: row["poolKey"] } : {},
@@ -77897,52 +77901,77 @@ var RequestLog = class {
77897
77901
  * Begin one request's span, or nothing when the log is disabled.
77898
77902
  *
77899
77903
  * This is the single gate on the request path: a disabled log costs one
77900
- * 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.
77901
77908
  */
77902
77909
  start(detail) {
77903
77910
  if (!this.gates.enabled()) return void 0;
77904
77911
  const debug = this.gates.debug();
77905
77912
  const startedAt = Date.now();
77906
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);
77907
77934
  let firstTokenAt;
77908
77935
  let lastTokenAt;
77909
- let method;
77910
- let url;
77911
- let requestBytes;
77912
- let status;
77936
+ let reported = false;
77913
77937
  let finished = false;
77914
77938
  return {
77915
77939
  token: () => {
77916
77940
  const now = Date.now();
77917
- if (firstTokenAt === void 0) firstTokenAt = now;
77941
+ if (firstTokenAt === void 0) {
77942
+ firstTokenAt = now;
77943
+ record2.firstTokenMs = Math.max(0, now - startedAt);
77944
+ }
77918
77945
  lastTokenAt = now;
77919
77946
  },
77920
77947
  request: (nextMethod, nextUrl, nextRequestBytes) => {
77921
- if (method === void 0) {
77922
- method = nextMethod;
77923
- url = nextUrl;
77924
- requestBytes = nextRequestBytes;
77948
+ if (!reported) {
77949
+ reported = true;
77950
+ record2.method = nextMethod;
77951
+ record2.url = nextUrl;
77952
+ if (nextRequestBytes !== void 0) record2.requestBytes = nextRequestBytes;
77925
77953
  }
77926
77954
  },
77927
77955
  status: (value) => {
77928
- status = value;
77956
+ record2.status = value;
77929
77957
  },
77930
77958
  finish: (ok, code, usage, costUsd) => {
77931
77959
  if (finished) return;
77932
77960
  finished = true;
77961
+ const seq = record2.seq;
77933
77962
  const endedAt = Date.now();
77934
77963
  const decodeMs = firstTokenAt === void 0 ? void 0 : Math.max(0, endedAt - firstTokenAt);
77935
77964
  const streamMs = firstTokenAt === void 0 || lastTokenAt === void 0 ? void 0 : Math.max(0, lastTokenAt - firstTokenAt);
77936
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);
77937
77966
  const entry = {
77938
- seq: ++this.seq,
77967
+ seq,
77939
77968
  atMs: startedAt,
77940
77969
  model: detail.model,
77941
77970
  ...detail.protocol === void 0 ? {} : { protocol: detail.protocol },
77942
77971
  session,
77943
- method: method ?? detail.method ?? "POST",
77944
- url: url ?? detail.url ?? "",
77945
- ...status === void 0 ? {} : { status },
77972
+ method: record2.method,
77973
+ url: record2.url,
77974
+ ...record2.status === void 0 ? {} : { status: record2.status },
77946
77975
  ...usage === void 0 ? {} : {
77947
77976
  inputTokens: usage.inputTokens,
77948
77977
  outputTokens: usage.outputTokens,
@@ -77951,7 +77980,7 @@ var RequestLog = class {
77951
77980
  ...usage.reasoningTokens === void 0 || usage.reasoningTokens <= 0 ? {} : { reasoningTokens: usage.reasoningTokens }
77952
77981
  },
77953
77982
  ...costUsd === void 0 ? {} : { costUsd },
77954
- ...requestBytes === void 0 ? {} : { requestBytes },
77983
+ ...record2.requestBytes === void 0 ? {} : { requestBytes: record2.requestBytes },
77955
77984
  ...detail.imageBytes === void 0 ? {} : { imageBytes: detail.imageBytes },
77956
77985
  durationMs: Math.max(0, endedAt - startedAt),
77957
77986
  ...firstTokenAt === void 0 ? {} : { firstTokenMs: Math.max(0, firstTokenAt - startedAt) },
@@ -77972,12 +78001,17 @@ var RequestLog = class {
77972
78001
  }
77973
78002
  }
77974
78003
  };
77975
- 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
+ }
77976
78010
  }
77977
78011
  };
77978
78012
  }
77979
- append(entry) {
77980
- this.totalRecords += 1;
78013
+ insert(entry, count2) {
78014
+ if (count2) this.totalRecords += 1;
77981
78015
  const bound = Math.max(1, Math.floor(this.gates.maxEntries()));
77982
78016
  const next = [entry, ...this.entries].slice(0, bound);
77983
78017
  this.droppedRecords += this.entries.length + 1 - next.length;
@@ -78097,7 +78131,7 @@ function createUpdateChecker(deps) {
78097
78131
  }
78098
78132
 
78099
78133
  // src/update/version.ts
78100
- var PLUGIN_VERSION = true ? "0.1.77" : "0.0.0-dev";
78134
+ var PLUGIN_VERSION = true ? "0.1.78" : "0.0.0-dev";
78101
78135
 
78102
78136
  // src/update/service.ts
78103
78137
  import { TypertRemoteService as TypertRemoteService4 } from "@deepseek-ai/dsh-typert-protocol";
@@ -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.77",
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",