@dan-ai-studio/dshopencodego 0.1.77 → 0.1.79

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
@@ -144,15 +144,15 @@ The API key comes from the Harness credential store (reference name `OPENCODE_GO
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.
146
146
  - **A "Request log" View inside the conversation**: this session's requests, in place — answering "why was that turn slow" without leaving the chat.
147
- - 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, protocol, URL, body size, redacted headers, request shape, result and usage, reasoning split, cache writes, per-request cost, content span). Filters: model, result, slow requests, session; groups: session, model, result; **click a column head to sort by it**, click again to flip.
147
+ - 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, protocol, URL, body size, **preparation**, redacted headers, request shape, result and usage, reasoning split, cache writes, per-request cost, content span). **Preparation** is the local time from request start to the HTTP send — large means the slowness is local (cold catalog / images / history conversion); small with a large first token means the gateway / model side is slow. Filters: model, result, slow requests, session; groups: session, model, result; **click a column head to sort by it**, click again to flip.
148
148
  - **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. **A burst delivery has no rate either**: when the gateway holds a whole turn and flushes it at the end (common with reasoning models: 8 seconds of thinking invisible on the wire, then a hundred tokens in a few milliseconds), a sub-100ms content span is delivery shape, not generation speed — it shows as — and stays out of the mean.
149
149
  - The session column names the **session title** (from the session catalog; falls back to the id, whose full spelling stays one hover away). The session filter matches titles and ids; session groups are headed the same way. **Clicking a title opens that session** (an archived one refuses, and says so).
150
150
  - **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.
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
@@ -143,15 +143,15 @@ API Key 通过 Harness 凭证库提供(引用名 `OPENCODE_GO_API_KEY`),
143
143
  - **两个入口,同一个页面**:
144
144
  - **侧边栏图标 → 主区域整页**:跨会话看全部请求。日志是进程级的数据,放在侧边栏最合理——位置固定、常驻可见,和 DSH 自己的插件管理器同一个范式。
145
145
  - **会话里的「请求日志」标签页**:就地看**本会话**的请求,回答"刚才那轮为什么慢"不用离开对话。
146
- - 页面是一整页而不是浮层:标题、统计(请求数/失败数/平均耗时/平均首字/平均 tok/s/平均缓存命中/连接复用率)、筛选栏固定在上方,只有表格自己滚动。每行含时间、模型、会话、输入/输出/缓存、**缓存命中**、**tok/s**、首字、耗时与状态;点一行展开 HTTP 明细(方法、协议、地址、请求体大小、脱敏后的请求头、请求构成、结果与用量、推理拆分、缓存写入、单轮花费、内容跨度)。筛选有模型/结果/慢请求/会话,分组有会话/模型/结果;**点列头按该列排序**,再点一次反向。
146
+ - 页面是一整页而不是浮层:标题、统计(请求数/失败数/平均耗时/平均首字/平均 tok/s/平均缓存命中/连接复用率)、筛选栏固定在上方,只有表格自己滚动。每行含时间、模型、会话、输入/输出/缓存、**缓存命中**、**tok/s**、首字、耗时与状态;点一行展开 HTTP 明细(方法、协议、地址、请求体大小、**准备耗时**、脱敏后的请求头、请求构成、结果与用量、推理拆分、缓存写入、单轮花费、内容跨度)。**准备耗时**是从请求开始到 HTTP 发出的本地耗时——它大是本地(目录冷启动/图片/历史转换)慢,它小但首字大是网关/模型侧慢。筛选有模型/结果/慢请求/会话,分组有会话/模型/结果;**点列头按该列排序**,再点一次反向。
147
147
  - **tok/s** 是**首字到结束**这段解码窗口上的输出速率,口径与 Harness 自己的每轮读数一致(含推理 token)。没有 token 的请求没有速率——不编数。**突发推送同样没有速率**:网关把一整轮攒到最后一次性推过来时(推理模型常见:8 秒思考在 wire 上不可见,然后上百个 token 几毫秒内到达),内容跨度不足 100ms,算出来的"每秒几万 token"不是生成速度——显示为 —,也不计入平均。
148
148
  - 会话列显示**会话标题**(标题来自会话目录;拿不到时回退显示 id,悬停可见完整 id)。会话筛选同时匹配标题与 id;按会话分组的组头同样显示标题。**点标题直接打开那个会话**(归档的打不开,会明说)。
149
149
  - **缓存命中**是输入 token 里走缓存的占比(缓存读取/(未缓存输入+缓存读取))。网关没报输入就没有占比——同样不编数。
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
@@ -252,12 +252,16 @@ function parseEntry(value) {
252
252
  ...row["reasoningTokens"] === void 0 ? {} : { reasoningTokens: count("reasoningTokens", row) },
253
253
  ...row["costUsd"] === void 0 ? {} : { costUsd: rate("costUsd", row) },
254
254
  ...row["requestBytes"] === void 0 ? {} : { requestBytes: count("requestBytes", row) },
255
+ ...row["prepMs"] === void 0 ? {} : { prepMs: count("prepMs", row) },
255
256
  ...row["imageBytes"] === void 0 ? {} : { imageBytes: count("imageBytes", row) },
256
257
  ...row["durationMs"] === void 0 ? {} : { durationMs: count("durationMs", row) },
257
258
  ...row["firstTokenMs"] === void 0 ? {} : { firstTokenMs: count("firstTokenMs", row) },
258
259
  ...row["decodeMs"] === void 0 ? {} : { decodeMs: count("decodeMs", row) },
259
260
  ...row["streamMs"] === void 0 ? {} : { streamMs: count("streamMs", row) },
260
261
  ...row["tps"] === void 0 ? {} : { tps: rate("tps", row) },
262
+ // Running rows cross the wire too: a viewer opened mid-request must
263
+ // see them. Older snapshots predate the flag and read as finished.
264
+ ...row["running"] === true ? { running: true } : {},
261
265
  ok: row["ok"] === true,
262
266
  ...typeof row["code"] === "string" ? { code: row["code"] } : {},
263
267
  ...typeof row["poolKey"] === "string" ? { poolKey: row["poolKey"] } : {},
@@ -525,6 +529,7 @@ var en = {
525
529
  requestLogOpenSessionFailed: "That session is archived and cannot be opened.",
526
530
  requestLogClose: "Close",
527
531
  requestLogRefresh: "Refresh",
532
+ requestLogRunning: "Requesting",
528
533
  requestLogEmpty: "No requests recorded. Turn the log on, then make a request.",
529
534
  requestLogDisabled: "The request log is off.",
530
535
  requestLogFilterModel: "Model",
@@ -570,6 +575,7 @@ var en = {
570
575
  requestLogDetailProtocol: "Protocol",
571
576
  requestLogDetailUrl: "URL",
572
577
  requestLogDetailRequestBytes: "Request body",
578
+ requestLogDetailPrep: "Preparation",
573
579
  requestLogDetailContentSplit: "Content split",
574
580
  requestLogDetailText: "Text",
575
581
  requestLogDetailImages: "Images",
@@ -723,6 +729,7 @@ var zh = {
723
729
  requestLogOpenSessionFailed: "\u8BE5\u4F1A\u8BDD\u5DF2\u5F52\u6863\uFF0C\u6253\u4E0D\u5F00\u3002",
724
730
  requestLogClose: "\u5173\u95ED",
725
731
  requestLogRefresh: "\u5237\u65B0",
732
+ requestLogRunning: "\u8BF7\u6C42\u4E2D",
726
733
  requestLogEmpty: "\u6682\u65E0\u8BB0\u5F55\u3002\u5148\u6253\u5F00\u65E5\u5FD7\u5F00\u5173\uFF0C\u518D\u53D1\u8D77\u4E00\u6B21\u8BF7\u6C42\u3002",
727
734
  requestLogDisabled: "\u8BF7\u6C42\u65E5\u5FD7\u672A\u5F00\u542F\u3002",
728
735
  requestLogFilterModel: "\u6A21\u578B",
@@ -768,6 +775,7 @@ var zh = {
768
775
  requestLogDetailProtocol: "\u534F\u8BAE",
769
776
  requestLogDetailUrl: "\u5730\u5740",
770
777
  requestLogDetailRequestBytes: "\u8BF7\u6C42\u4F53",
778
+ requestLogDetailPrep: "\u51C6\u5907\u8017\u65F6",
771
779
  requestLogDetailContentSplit: "\u5185\u5BB9\u62C6\u5206",
772
780
  requestLogDetailText: "\u6587\u672C",
773
781
  requestLogDetailImages: "\u56FE\u7247",
@@ -2140,10 +2148,10 @@ if (!document.querySelector("style[data-plugin-css=" + JSON.stringify(id2) + "]"
2140
2148
  const style = document.createElement("style");
2141
2149
  style.dataset.plugin = "@dan-ai-studio/dshopencodego";
2142
2150
  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)}";
2151
+ 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
2152
  document.head.appendChild(style);
2145
2153
  }
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" };
2154
+ 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
2155
 
2148
2156
  // src/client/RequestLogPage.tsx
2149
2157
  var import_jsx_runtime2 = require("react/jsx-runtime");
@@ -2183,7 +2191,7 @@ function sortValue(entry, key) {
2183
2191
  case "durationMs":
2184
2192
  return entry.durationMs ?? -1;
2185
2193
  case "status":
2186
- return entry.ok ? `ok-${entry.status ?? ""}` : `fail-${entry.code ?? ""}`;
2194
+ return entry.running === true ? "running" : entry.ok ? `ok-${entry.status ?? ""}` : `fail-${entry.code ?? ""}`;
2187
2195
  }
2188
2196
  }
2189
2197
  var PAGE_SIZE = 50;
@@ -2233,8 +2241,10 @@ function groupKey(entry, groupBy) {
2233
2241
  return entry.session === "" ? "\u2014" : entry.session;
2234
2242
  case "model":
2235
2243
  return entry.model;
2244
+ // A running row is neither a success nor a failure yet: it groups on
2245
+ // its own instead of inflating either outcome.
2236
2246
  case "result":
2237
- return entry.ok ? "ok" : entry.code ?? "fail";
2247
+ return entry.running === true ? "running" : entry.ok ? "ok" : entry.code ?? "fail";
2238
2248
  case "none":
2239
2249
  return void 0;
2240
2250
  }
@@ -2276,6 +2286,7 @@ function RequestLogPage({ readLog, enabled, canOpenSession, openSession, t, getL
2276
2286
  const title = catalog?.byId[id4]?.title?.trim();
2277
2287
  return title === void 0 || title === "" ? { text: id4 === "" ? "\u2014" : id4, titled: false } : { text: title, titled: true };
2278
2288
  }, [catalog]);
2289
+ const resultGroupLabel = (group) => group === "ok" ? t("requestLogResultOk") : group === "fail" ? t("requestLogResultFail") : group === "running" ? t("requestLogRunning") : group;
2279
2290
  const toggleSort = (key) => {
2280
2291
  if (key === sortKey) {
2281
2292
  setSortDir(sortDir === "asc" ? "desc" : "asc");
@@ -2301,6 +2312,16 @@ function RequestLogPage({ readLog, enabled, canOpenSession, openSession, t, getL
2301
2312
  (0, import_react2.useEffect)(() => {
2302
2313
  void load();
2303
2314
  }, [load]);
2315
+ const hasRunning = (snapshot?.entries ?? []).some((entry) => entry.running === true);
2316
+ (0, import_react2.useEffect)(() => {
2317
+ if (!hasRunning) return;
2318
+ const timer = setInterval(() => {
2319
+ void load();
2320
+ }, 1e3);
2321
+ return () => {
2322
+ clearInterval(timer);
2323
+ };
2324
+ }, [hasRunning, load]);
2304
2325
  const models = (0, import_react2.useMemo)(() => {
2305
2326
  const ids = /* @__PURE__ */ new Set();
2306
2327
  for (const entry of snapshot?.entries ?? []) ids.add(entry.model);
@@ -2311,7 +2332,7 @@ function RequestLogPage({ readLog, enabled, canOpenSession, openSession, t, getL
2311
2332
  return (snapshot?.entries ?? []).filter((entry) => {
2312
2333
  if (model !== "all" && entry.model !== model) return false;
2313
2334
  if (result === "ok" && !entry.ok) return false;
2314
- if (result === "fail" && entry.ok) return false;
2335
+ if (result === "fail" && (entry.ok || entry.running === true)) return false;
2315
2336
  if (slowOnly && (entry.durationMs ?? 0) < Math.max(0, slowSeconds) * 1e3) return false;
2316
2337
  if (onlySession && sessionId !== void 0 && entry.session !== sessionId) return false;
2317
2338
  if (query !== "") {
@@ -2334,7 +2355,7 @@ function RequestLogPage({ readLog, enabled, canOpenSession, openSession, t, getL
2334
2355
  return next;
2335
2356
  }, [filtered, sortKey, sortDir]);
2336
2357
  const pageEntries = sorted.slice(safePage * PAGE_SIZE, (safePage + 1) * PAGE_SIZE);
2337
- const failures = filtered.filter((entry) => !entry.ok).length;
2358
+ const failures = filtered.filter((entry) => !entry.ok && entry.running !== true).length;
2338
2359
  const meanLatency = average(filtered.flatMap((entry) => entry.durationMs === void 0 ? [] : [entry.durationMs]));
2339
2360
  const meanFirstToken = average(filtered.flatMap((entry) => entry.firstTokenMs === void 0 ? [] : [entry.firstTokenMs]));
2340
2361
  const meanTps = average(filtered.flatMap((entry) => entry.tps === void 0 ? [] : [entry.tps]));
@@ -2357,10 +2378,10 @@ function RequestLogPage({ readLog, enabled, canOpenSession, openSession, t, getL
2357
2378
  if (group !== void 0 && group !== lastGroup) {
2358
2379
  lastGroup = group;
2359
2380
  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}`)
2381
+ /* @__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
2382
  );
2362
2383
  }
2363
- const state = !entry.ok ? "rowFail" : entry.tps !== void 0 && entry.tps < 5 ? "rowSlow" : "rowOk";
2384
+ const state = entry.running === true ? "rowRunning" : !entry.ok ? "rowFail" : entry.tps !== void 0 && entry.tps < 5 ? "rowSlow" : "rowOk";
2364
2385
  const session = sessionLabel(entry.session);
2365
2386
  out.push(
2366
2387
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
@@ -2398,8 +2419,8 @@ function RequestLogPage({ readLog, enabled, canOpenSession, openSession, t, getL
2398
2419
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { children: percent(cacheRate(entry)) }),
2399
2420
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { children: rate2(entry.tps) }),
2400
2421
  /* @__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" })
2422
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { children: entry.running === true ? duration(Math.max(0, Date.now() - entry.atMs)) : duration(entry.durationMs) }),
2423
+ /* @__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
2424
  ]
2404
2425
  },
2405
2426
  entry.seq
@@ -2428,6 +2449,10 @@ function RequestLogPage({ readLog, enabled, canOpenSession, openSession, t, getL
2428
2449
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: request_log_default.detailLabel, children: t("requestLogDetailContentSplit") }),
2429
2450
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: request_log_default.detailValue, children: contentSplit(entry, t("requestLogDetailText"), t("requestLogDetailImages")) })
2430
2451
  ] }),
2452
+ entry.prepMs !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: request_log_default.detailItem, children: [
2453
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: request_log_default.detailLabel, children: t("requestLogDetailPrep") }),
2454
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: request_log_default.detailValue, children: duration(entry.prepMs) })
2455
+ ] }),
2431
2456
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: request_log_default.detailItem, children: [
2432
2457
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: request_log_default.detailLabel, children: t("requestLogDetailSession") }),
2433
2458
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
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(() => {
@@ -77315,12 +77316,16 @@ function parseEntry(value) {
77315
77316
  ...row["reasoningTokens"] === void 0 ? {} : { reasoningTokens: count("reasoningTokens", row) },
77316
77317
  ...row["costUsd"] === void 0 ? {} : { costUsd: rate("costUsd", row) },
77317
77318
  ...row["requestBytes"] === void 0 ? {} : { requestBytes: count("requestBytes", row) },
77319
+ ...row["prepMs"] === void 0 ? {} : { prepMs: count("prepMs", row) },
77318
77320
  ...row["imageBytes"] === void 0 ? {} : { imageBytes: count("imageBytes", row) },
77319
77321
  ...row["durationMs"] === void 0 ? {} : { durationMs: count("durationMs", row) },
77320
77322
  ...row["firstTokenMs"] === void 0 ? {} : { firstTokenMs: count("firstTokenMs", row) },
77321
77323
  ...row["decodeMs"] === void 0 ? {} : { decodeMs: count("decodeMs", row) },
77322
77324
  ...row["streamMs"] === void 0 ? {} : { streamMs: count("streamMs", row) },
77323
77325
  ...row["tps"] === void 0 ? {} : { tps: rate("tps", row) },
77326
+ // Running rows cross the wire too: a viewer opened mid-request must
77327
+ // see them. Older snapshots predate the flag and read as finished.
77328
+ ...row["running"] === true ? { running: true } : {},
77324
77329
  ok: row["ok"] === true,
77325
77330
  ...typeof row["code"] === "string" ? { code: row["code"] } : {},
77326
77331
  ...typeof row["poolKey"] === "string" ? { poolKey: row["poolKey"] } : {},
@@ -77897,52 +77902,79 @@ var RequestLog = class {
77897
77902
  * Begin one request's span, or nothing when the log is disabled.
77898
77903
  *
77899
77904
  * This is the single gate on the request path: a disabled log costs one
77900
- * method call and one boolean read, and constructs nothing.
77905
+ * method call and one boolean read, and constructs nothing. When enabled,
77906
+ * the running row is appended before the span is returned, so the viewer
77907
+ * sees the request while it runs; `request` and `status` mutate that same
77908
+ * visible row as the wire facts arrive.
77901
77909
  */
77902
77910
  start(detail) {
77903
77911
  if (!this.gates.enabled()) return void 0;
77904
77912
  const debug = this.gates.debug();
77905
77913
  const startedAt = Date.now();
77906
77914
  const session = detail.sessionId ?? "";
77915
+ const record2 = {
77916
+ seq: ++this.seq,
77917
+ atMs: startedAt,
77918
+ model: detail.model,
77919
+ ...detail.protocol === void 0 ? {} : { protocol: detail.protocol },
77920
+ session,
77921
+ method: detail.method ?? "POST",
77922
+ url: detail.url ?? "",
77923
+ running: true,
77924
+ ok: false,
77925
+ shape: { messages: detail.messageCount, hasImage: detail.hasImage, tools: detail.toolCount },
77926
+ ...detail.imageBytes === void 0 ? {} : { imageBytes: detail.imageBytes },
77927
+ ...!debug ? {} : {
77928
+ headers: {
77929
+ ...detail.headers.session === void 0 ? {} : { session: detail.headers.session },
77930
+ ...detail.headers.userAgent === void 0 ? {} : { userAgent: detail.headers.userAgent }
77931
+ }
77932
+ }
77933
+ };
77934
+ this.insert(record2, true);
77907
77935
  let firstTokenAt;
77908
77936
  let lastTokenAt;
77909
- let method;
77910
- let url;
77911
- let requestBytes;
77912
- let status;
77937
+ let reported = false;
77913
77938
  let finished = false;
77914
77939
  return {
77915
77940
  token: () => {
77916
77941
  const now = Date.now();
77917
- if (firstTokenAt === void 0) firstTokenAt = now;
77942
+ if (firstTokenAt === void 0) {
77943
+ firstTokenAt = now;
77944
+ record2.firstTokenMs = Math.max(0, now - startedAt);
77945
+ }
77918
77946
  lastTokenAt = now;
77919
77947
  },
77920
77948
  request: (nextMethod, nextUrl, nextRequestBytes) => {
77921
- if (method === void 0) {
77922
- method = nextMethod;
77923
- url = nextUrl;
77924
- requestBytes = nextRequestBytes;
77949
+ if (!reported) {
77950
+ reported = true;
77951
+ record2.method = nextMethod;
77952
+ record2.url = nextUrl;
77953
+ if (nextRequestBytes !== void 0) record2.requestBytes = nextRequestBytes;
77954
+ record2.prepMs = Math.max(0, Date.now() - startedAt);
77925
77955
  }
77926
77956
  },
77927
77957
  status: (value) => {
77928
- status = value;
77958
+ record2.status = value;
77929
77959
  },
77930
77960
  finish: (ok, code, usage, costUsd) => {
77931
77961
  if (finished) return;
77932
77962
  finished = true;
77963
+ const seq = record2.seq;
77933
77964
  const endedAt = Date.now();
77934
77965
  const decodeMs = firstTokenAt === void 0 ? void 0 : Math.max(0, endedAt - firstTokenAt);
77935
77966
  const streamMs = firstTokenAt === void 0 || lastTokenAt === void 0 ? void 0 : Math.max(0, lastTokenAt - firstTokenAt);
77936
77967
  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
77968
  const entry = {
77938
- seq: ++this.seq,
77969
+ seq,
77939
77970
  atMs: startedAt,
77940
77971
  model: detail.model,
77941
77972
  ...detail.protocol === void 0 ? {} : { protocol: detail.protocol },
77942
77973
  session,
77943
- method: method ?? detail.method ?? "POST",
77944
- url: url ?? detail.url ?? "",
77945
- ...status === void 0 ? {} : { status },
77974
+ method: record2.method,
77975
+ url: record2.url,
77976
+ ...record2.status === void 0 ? {} : { status: record2.status },
77977
+ ...record2.prepMs === void 0 ? {} : { prepMs: record2.prepMs },
77946
77978
  ...usage === void 0 ? {} : {
77947
77979
  inputTokens: usage.inputTokens,
77948
77980
  outputTokens: usage.outputTokens,
@@ -77951,7 +77983,7 @@ var RequestLog = class {
77951
77983
  ...usage.reasoningTokens === void 0 || usage.reasoningTokens <= 0 ? {} : { reasoningTokens: usage.reasoningTokens }
77952
77984
  },
77953
77985
  ...costUsd === void 0 ? {} : { costUsd },
77954
- ...requestBytes === void 0 ? {} : { requestBytes },
77986
+ ...record2.requestBytes === void 0 ? {} : { requestBytes: record2.requestBytes },
77955
77987
  ...detail.imageBytes === void 0 ? {} : { imageBytes: detail.imageBytes },
77956
77988
  durationMs: Math.max(0, endedAt - startedAt),
77957
77989
  ...firstTokenAt === void 0 ? {} : { firstTokenMs: Math.max(0, firstTokenAt - startedAt) },
@@ -77972,12 +78004,17 @@ var RequestLog = class {
77972
78004
  }
77973
78005
  }
77974
78006
  };
77975
- this.append(entry);
78007
+ const at = this.entries.findIndex((candidate) => candidate.seq === seq);
78008
+ if (at === -1) {
78009
+ this.insert(entry, false);
78010
+ } else {
78011
+ this.entries[at] = entry;
78012
+ }
77976
78013
  }
77977
78014
  };
77978
78015
  }
77979
- append(entry) {
77980
- this.totalRecords += 1;
78016
+ insert(entry, count2) {
78017
+ if (count2) this.totalRecords += 1;
77981
78018
  const bound = Math.max(1, Math.floor(this.gates.maxEntries()));
77982
78019
  const next = [entry, ...this.entries].slice(0, bound);
77983
78020
  this.droppedRecords += this.entries.length + 1 - next.length;
@@ -78097,7 +78134,7 @@ function createUpdateChecker(deps) {
78097
78134
  }
78098
78135
 
78099
78136
  // src/update/version.ts
78100
- var PLUGIN_VERSION = true ? "0.1.77" : "0.0.0-dev";
78137
+ var PLUGIN_VERSION = true ? "0.1.79" : "0.0.0-dev";
78101
78138
 
78102
78139
  // src/update/service.ts
78103
78140
  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
  *
@@ -81,6 +82,13 @@ export interface RequestLogEntry {
81
82
  * whole reply still counts in the totals above.
82
83
  */
83
84
  readonly streamMs?: number;
85
+ /**
86
+ * Wall time from request start to the moment the fetch actually went out,
87
+ * in milliseconds — the local preparation (catalog, conversion, images).
88
+ * The rest of the first-token wait belongs to the wire and the gateway.
89
+ * Absent when the request never reached the wire (rejected up front).
90
+ */
91
+ readonly prepMs?: number;
84
92
  /** Wall time from request start to terminal event, in milliseconds. */
85
93
  readonly durationMs?: number;
86
94
  /** Wall time from request start to the first token delta, in milliseconds. */
@@ -101,7 +109,16 @@ export interface RequestLogEntry {
101
109
  * misreport a buffered reply as infinitely quick.
102
110
  */
103
111
  readonly tps?: number;
104
- /** Whether the request completed. */
112
+ /**
113
+ * Whether the request is still in flight. Present only while it runs:
114
+ * `finish` removes it. A running row always reads `ok: false` with no
115
+ * `code`, so readers must check this flag before counting a failure.
116
+ */
117
+ readonly running?: boolean;
118
+ /**
119
+ * Whether the request completed successfully. Always false while
120
+ * `running` is true.
121
+ */
105
122
  readonly ok: boolean;
106
123
  /** Failure code (`TIMEOUT`, `TRANSPORT`, …) for a request that did not complete. */
107
124
  readonly code?: string;
@@ -136,7 +153,7 @@ export interface PoolLogEvent {
136
153
  }
137
154
  /** What the viewer reads: the entries plus the counters a rate needs. */
138
155
  export interface RequestLogSnapshot {
139
- /** Newest first, at most the configured bound. */
156
+ /** Newest first, at most the configured bound — finished rows plus running ones. */
140
157
  readonly entries: readonly RequestLogEntry[];
141
158
  /** Recent pool lifecycle, newest first, capped. */
142
159
  readonly poolEvents: readonly PoolLogEvent[];
@@ -161,11 +178,12 @@ export interface RequestLogGates {
161
178
  /**
162
179
  * An in-flight request, handed back only when the log is enabled.
163
180
  *
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.
181
+ * `start` already appended its running row, so every method mutates the
182
+ * visible entry in place. Every method stays cheap by construction: one
183
+ * `Date.now` read per content delta, field writes for the wire line, and
184
+ * small integer arithmetic at close. No serialization, no hashing, no body
185
+ * handling — the counts ride the usage chunk the adapter already holds,
186
+ * and the timings bracket work that already happened.
169
187
  */
170
188
  export interface RequestSpan {
171
189
  /**
@@ -188,7 +206,7 @@ export interface RequestSpan {
188
206
  /** Mark the response status line's arrival. */
189
207
  status(status: number): void;
190
208
  /**
191
- * Close the span and append its entry.
209
+ * Close the span and complete its running row in place.
192
210
  * @param ok - whether the request completed.
193
211
  * @param code - failure code for a request that did not complete.
194
212
  * @param usage - provider-reported counts, when the terminal usage arrived.
@@ -254,10 +272,13 @@ export declare class RequestLog {
254
272
  * Begin one request's span, or nothing when the log is disabled.
255
273
  *
256
274
  * This is the single gate on the request path: a disabled log costs one
257
- * method call and one boolean read, and constructs nothing.
275
+ * method call and one boolean read, and constructs nothing. When enabled,
276
+ * the running row is appended before the span is returned, so the viewer
277
+ * sees the request while it runs; `request` and `status` mutate that same
278
+ * visible row as the wire facts arrive.
258
279
  */
259
280
  start(detail: RequestStart): RequestSpan | undefined;
260
- private append;
281
+ private insert;
261
282
  /** Shrink the ring to a new bound immediately; growing backfills nothing. */
262
283
  setMaxEntries(bound: number): void;
263
284
  /** 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.79",
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",