@dan-ai-studio/dshopencodego 0.1.81 → 0.1.83

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
@@ -152,7 +152,7 @@ The API key comes from the Harness credential store (reference name `OPENCODE_GO
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
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.
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 — including failures before the first byte (unresolvable models, missing credentials), whose rows carry the reason but no usage. 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
@@ -151,7 +151,7 @@ API Key 通过 Harness 凭证库提供(引用名 `OPENCODE_GO_API_KEY`),
151
151
  - **内容拆分**:明细里"请求体"下面跟一行"文本 X /图片 Y"——图片是各去重后附件按 base64 估的占比(单张按路由的编码上限封顶),文本是总数减图片的剩余,两者恒等于请求体总数。图片 dominant 的请求一眼就能看出来。
152
152
  - 会话 id **始终完整记录**(它是会话身份,不是凭据;截断后两个会话会读成一个)。
153
153
  - 记录的是环形缓冲(超限丢最旧),进程重启即清;页面平时只在打开与手动刷新时读快照,有请求中时每秒自动刷新一次直到结束,没人打开的页面零流量。
154
- - 凭据永远不进记录:记录器根本不接收请求头参数,只收白名单里的会话 id 与 UA;请求一开始就有一条"请求中"占位行(状态列读秒),结束时原位补全为完整记录。失败的请求同样记一条(含错误码);消费者提前终止的流记一条 `ABORTED`,不再无声丢失。
154
+ - 凭据永远不进记录:记录器根本不接收请求头参数,只收白名单里的会话 id 与 UA;请求一开始就有一条"请求中"占位行(状态列读秒),结束时原位补全为完整记录。失败的请求同样记一条(含错误码)——包括目录解析失败(未知模型)与凭证缺失这类"第一个字节之前"的失败,行里没有用量只有原因;消费者提前终止的流记一条 `ABORTED`,不再无声丢失。
155
155
 
156
156
  **图片**:三个字段各管一层,超限的图片会被明确拒绝,而不是静默降质或丢弃:
157
157
 
package/lib/client.js CHANGED
@@ -566,6 +566,10 @@ var en = {
566
566
  requestLogColLatency: "Latency",
567
567
  requestLogColTps: "tok/s",
568
568
  requestLogColPrep: "Prep",
569
+ requestLogColWait: "Wait",
570
+ requestLogTimelineWait: "Waiting for first token",
571
+ requestLogTimelineStream: "Streaming",
572
+ requestLogTimelineBurst: "Delivered in one burst",
569
573
  requestLogColStream: "Content span",
570
574
  requestLogColFirstToken: "First token",
571
575
  requestLogColStatus: "Status",
@@ -577,8 +581,6 @@ var en = {
577
581
  requestLogDetailUrl: "URL",
578
582
  requestLogDetailRequestBytes: "Request body",
579
583
  requestLogDetailPrep: "Preparation",
580
- requestLogTimelineWait: "Waiting for first token",
581
- requestLogTimelineStream: "Streaming",
582
584
  requestLogDetailContentSplit: "Content split",
583
585
  requestLogDetailText: "Text",
584
586
  requestLogDetailImages: "Images",
@@ -769,6 +771,10 @@ var zh = {
769
771
  requestLogColLatency: "\u8017\u65F6",
770
772
  requestLogColTps: "tok/s",
771
773
  requestLogColPrep: "\u51C6\u5907",
774
+ requestLogColWait: "\u7B49\u5F85",
775
+ requestLogTimelineWait: "\u7B49\u5F85\u9996\u5B57",
776
+ requestLogTimelineStream: "\u8F93\u51FA\u4E2D",
777
+ requestLogTimelineBurst: "\u6574\u8F6E\u4E00\u6B21\u9001\u8FBE",
772
778
  requestLogColStream: "\u5185\u5BB9\u8DE8\u5EA6",
773
779
  requestLogColFirstToken: "\u9996\u5B57",
774
780
  requestLogColStatus: "\u72B6\u6001",
@@ -780,8 +786,6 @@ var zh = {
780
786
  requestLogDetailUrl: "\u5730\u5740",
781
787
  requestLogDetailRequestBytes: "\u8BF7\u6C42\u4F53",
782
788
  requestLogDetailPrep: "\u51C6\u5907\u8017\u65F6",
783
- requestLogTimelineWait: "\u7B49\u5F85\u9996\u5B57",
784
- requestLogTimelineStream: "\u8F93\u51FA\u4E2D",
785
789
  requestLogDetailContentSplit: "\u5185\u5BB9\u62C6\u5206",
786
790
  requestLogDetailText: "\u6587\u672C",
787
791
  requestLogDetailImages: "\u56FE\u7247",
@@ -2148,16 +2152,19 @@ function Section({ controller, t, getLocale, openRequestLog }) {
2148
2152
  var import_react2 = require("react");
2149
2153
  var import_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
2150
2154
 
2155
+ // src/request-log/store.ts
2156
+ var MIN_STREAM_MS_FOR_RATE = 100;
2157
+
2151
2158
  // plugin-css:/home/runner/work/dshopencodego/dshopencodego/src/client/request-log.module.css
2152
2159
  var id2 = "@dan-ai-studio/dshopencodego/request-log.module.css";
2153
2160
  if (!document.querySelector("style[data-plugin-css=" + JSON.stringify(id2) + "]")) {
2154
2161
  const style = document.createElement("style");
2155
2162
  style.dataset.plugin = "@dan-ai-studio/dshopencodego";
2156
2163
  style.dataset.pluginCss = id2;
2157
- 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_sessionText{text-overflow:ellipsis;vertical-align:bottom;max-width:340px;display:inline-block;overflow:hidden}.Lx7MOq_modelText{text-overflow:ellipsis;vertical-align:bottom;max-width:220px;display:inline-block;overflow:hidden}.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_timeline{flex-direction:column;grid-column:1/-1;gap:6px;padding:4px 0 8px;display:flex}.Lx7MOq_timelineTrack{background:var(--dsw-alias-bg-layer-1);border-radius:999px;height:10px;position:relative;overflow:hidden}.Lx7MOq_timelineSeg{min-width:2px;position:absolute;top:0;bottom:0}.Lx7MOq_timelinePrep{background:var(--dsw-alias-brand-primary)}.Lx7MOq_timelineWait{background:var(--dsw-alias-state-warn-primary)}.Lx7MOq_timelineStream{background:var(--dsw-alias-brand-primary);opacity:.45}.Lx7MOq_timelineTick{background:var(--dsw-alias-label-primary);width:2px;margin-left:-2px;position:absolute;top:-2px;bottom:-2px;left:100%}.Lx7MOq_timelineLegend{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;flex-wrap:wrap;gap:4px 16px;font-size:12px;display:flex}.Lx7MOq_timelineItem{white-space:nowrap;align-items:center;gap:6px;display:inline-flex}.Lx7MOq_timelineSwatch{border-radius:2px;width:8px;height:8px}.Lx7MOq_timelineTickSwatch{background:var(--dsw-alias-label-primary)}.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)}";
2164
+ 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_sessionText{text-overflow:ellipsis;vertical-align:bottom;max-width:340px;display:inline-block;overflow:hidden}.Lx7MOq_modelText{text-overflow:ellipsis;vertical-align:bottom;max-width:220px;display:inline-block;overflow:hidden}.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_timeline{flex-direction:column;grid-column:1/-1;gap:6px;padding:4px 0 8px;display:flex}.Lx7MOq_timelineTrackWrap{padding-bottom:18px;position:relative}.Lx7MOq_timelineTrack{background:var(--dsw-alias-bg-layer-1);border-radius:999px;height:10px;position:relative;overflow:hidden}.Lx7MOq_timelineMidTick{background:var(--dsw-alias-label-tertiary);opacity:.7;width:1px;position:absolute;top:0;bottom:0;left:50%}.Lx7MOq_timelineMid{color:var(--dsw-alias-label-tertiary);white-space:nowrap;font-variant-numeric:tabular-nums;font-size:11px;line-height:16px;position:absolute;top:100%;transform:translate(-50%)}.Lx7MOq_timelineSeg{min-width:2px;position:absolute;top:0;bottom:0}.Lx7MOq_timelinePrep{background:var(--dsw-alias-brand-primary)}.Lx7MOq_timelineWait{background:var(--dsw-alias-state-warn-primary)}.Lx7MOq_timelineStream{background:var(--dsw-alias-brand-primary);opacity:.45}.Lx7MOq_timelineTick{background:var(--dsw-alias-label-primary);width:2px;margin-left:-2px;position:absolute;top:-2px;bottom:-2px;left:100%}.Lx7MOq_timelineLegend{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;flex-wrap:wrap;gap:4px 16px;font-size:12px;display:flex}.Lx7MOq_timelineItem{white-space:nowrap;align-items:center;gap:6px;display:inline-flex}.Lx7MOq_timelineSwatch{border-radius:2px;width:8px;height:8px}.Lx7MOq_timelineTickSwatch{background:var(--dsw-alias-label-primary)}.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)}";
2158
2165
  document.head.appendChild(style);
2159
2166
  }
2160
- 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", "modelText": "Lx7MOq_modelText", "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", "sessionText": "Lx7MOq_sessionText", "stuck": "Lx7MOq_stuck", "subtitle": "Lx7MOq_subtitle", "summary": "Lx7MOq_summary", "table": "Lx7MOq_table", "tableWrap": "Lx7MOq_tableWrap", "thSortable": "Lx7MOq_thSortable", "timeline": "Lx7MOq_timeline", "timelineItem": "Lx7MOq_timelineItem", "timelineLegend": "Lx7MOq_timelineLegend", "timelinePrep": "Lx7MOq_timelinePrep", "timelineSeg": "Lx7MOq_timelineSeg", "timelineStream": "Lx7MOq_timelineStream", "timelineSwatch": "Lx7MOq_timelineSwatch", "timelineTick": "Lx7MOq_timelineTick", "timelineTickSwatch": "Lx7MOq_timelineTickSwatch", "timelineTrack": "Lx7MOq_timelineTrack", "timelineWait": "Lx7MOq_timelineWait", "title": "Lx7MOq_title", "toolbar": "Lx7MOq_toolbar" };
2167
+ 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", "modelText": "Lx7MOq_modelText", "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", "sessionText": "Lx7MOq_sessionText", "stuck": "Lx7MOq_stuck", "subtitle": "Lx7MOq_subtitle", "summary": "Lx7MOq_summary", "table": "Lx7MOq_table", "tableWrap": "Lx7MOq_tableWrap", "thSortable": "Lx7MOq_thSortable", "timeline": "Lx7MOq_timeline", "timelineItem": "Lx7MOq_timelineItem", "timelineLegend": "Lx7MOq_timelineLegend", "timelineMid": "Lx7MOq_timelineMid", "timelineMidTick": "Lx7MOq_timelineMidTick", "timelinePrep": "Lx7MOq_timelinePrep", "timelineSeg": "Lx7MOq_timelineSeg", "timelineStream": "Lx7MOq_timelineStream", "timelineSwatch": "Lx7MOq_timelineSwatch", "timelineTick": "Lx7MOq_timelineTick", "timelineTickSwatch": "Lx7MOq_timelineTickSwatch", "timelineTrack": "Lx7MOq_timelineTrack", "timelineTrackWrap": "Lx7MOq_timelineTrackWrap", "timelineWait": "Lx7MOq_timelineWait", "title": "Lx7MOq_title", "toolbar": "Lx7MOq_toolbar" };
2161
2168
 
2162
2169
  // src/client/RequestLogPage.tsx
2163
2170
  var import_jsx_runtime2 = require("react/jsx-runtime");
@@ -2171,6 +2178,7 @@ var SORT_DEFAULT_DIR = {
2171
2178
  cacheRate: "desc",
2172
2179
  tps: "desc",
2173
2180
  prepMs: "desc",
2181
+ waitMs: "desc",
2174
2182
  firstTokenMs: "desc",
2175
2183
  durationMs: "desc",
2176
2184
  status: "asc"
@@ -2195,6 +2203,8 @@ function sortValue(entry, key) {
2195
2203
  return entry.tps ?? -1;
2196
2204
  case "prepMs":
2197
2205
  return entry.prepMs ?? -1;
2206
+ case "waitMs":
2207
+ return waitMs(entry) ?? -1;
2198
2208
  case "firstTokenMs":
2199
2209
  return entry.firstTokenMs ?? -1;
2200
2210
  case "durationMs":
@@ -2236,7 +2246,8 @@ function cacheRate(entry) {
2236
2246
  return total > 0 ? cached / total : void 0;
2237
2247
  }
2238
2248
  function timelineOf(entry, t) {
2239
- const end = entry.durationMs ?? (entry.running === true ? Math.max(0, Date.now() - entry.atMs) : void 0);
2249
+ const running = entry.running === true;
2250
+ const end = entry.durationMs ?? (running ? Math.max(0, Date.now() - entry.atMs) : void 0);
2240
2251
  if (end === void 0 || end <= 0) return void 0;
2241
2252
  const prep = entry.prepMs;
2242
2253
  const first = entry.firstTokenMs;
@@ -2261,11 +2272,19 @@ function timelineOf(entry, t) {
2261
2272
  }
2262
2273
  }
2263
2274
  if (slices.length === 0) return void 0;
2264
- return { slices, end };
2275
+ return {
2276
+ slices,
2277
+ end,
2278
+ burst: !running && first !== void 0 && (entry.streamMs === void 0 || entry.streamMs < MIN_STREAM_MS_FOR_RATE)
2279
+ };
2265
2280
  }
2266
2281
  function percent(value) {
2267
2282
  return value === void 0 ? "\u2014" : `${(value * 100).toFixed(0)}%`;
2268
2283
  }
2284
+ function waitMs(entry) {
2285
+ if (entry.firstTokenMs === void 0) return void 0;
2286
+ return Math.max(0, entry.firstTokenMs - (entry.prepMs ?? 0));
2287
+ }
2269
2288
  function average(values) {
2270
2289
  if (values.length === 0) return void 0;
2271
2290
  let sum = 0;
@@ -2354,7 +2373,7 @@ function RequestLogPage({ readLog, enabled, canOpenSession, openSession, t, getL
2354
2373
  if (!hasRunning) return;
2355
2374
  const timer = setInterval(() => {
2356
2375
  void load();
2357
- }, 1e3);
2376
+ }, 2e3);
2358
2377
  return () => {
2359
2378
  clearInterval(timer);
2360
2379
  };
@@ -2415,7 +2434,7 @@ function RequestLogPage({ readLog, enabled, canOpenSession, openSession, t, getL
2415
2434
  if (group !== void 0 && group !== lastGroup) {
2416
2435
  lastGroup = group;
2417
2436
  out.push(
2418
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("tr", { className: request_log_default.group, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { colSpan: 12, children: groupBy === "session" ? sessionLabel(group).text : groupBy === "result" ? resultGroupLabel(group) : group }) }, `group-${group}-${entry.seq}`)
2437
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("tr", { className: request_log_default.group, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { colSpan: 13, children: groupBy === "session" ? sessionLabel(group).text : groupBy === "result" ? resultGroupLabel(group) : group }) }, `group-${group}-${entry.seq}`)
2419
2438
  );
2420
2439
  }
2421
2440
  const state = entry.running === true ? "rowRunning" : !entry.ok ? "rowFail" : entry.tps !== void 0 && entry.tps < 5 ? "rowSlow" : "rowOk";
@@ -2456,6 +2475,7 @@ function RequestLogPage({ readLog, enabled, canOpenSession, openSession, t, getL
2456
2475
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { children: percent(cacheRate(entry)) }),
2457
2476
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { children: rate2(entry.tps) }),
2458
2477
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { children: duration(entry.prepMs) }),
2478
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { children: duration(waitMs(entry)) }),
2459
2479
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { children: duration(entry.firstTokenMs) }),
2460
2480
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { children: entry.running === true ? duration(Math.max(0, Date.now() - entry.atMs)) : duration(entry.durationMs) }),
2461
2481
  /* @__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" })
@@ -2466,28 +2486,38 @@ function RequestLogPage({ readLog, enabled, canOpenSession, openSession, t, getL
2466
2486
  );
2467
2487
  if (expanded === entry.seq) {
2468
2488
  out.push(
2469
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("tr", { className: request_log_default.detail, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { colSpan: 12, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: request_log_default.detailGrid, children: [
2489
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("tr", { className: request_log_default.detail, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { colSpan: 13, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: request_log_default.detailGrid, children: [
2470
2490
  (() => {
2471
2491
  const timeline = timelineOf(entry, t);
2472
- return timeline === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: request_log_default.timeline, children: [
2473
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: request_log_default.timelineTrack, children: [
2474
- timeline.slices.map((slice) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2475
- "div",
2476
- {
2477
- className: `${request_log_default.timelineSeg} ${request_log_default[`timeline${slice.kind.charAt(0).toUpperCase()}${slice.kind.slice(1)}`]}`,
2478
- style: {
2479
- left: `${(slice.from / timeline.end * 100).toFixed(2)}%`,
2480
- width: `${Math.max(0, (slice.to - slice.from) / timeline.end * 100).toFixed(2)}%`
2492
+ if (timeline === void 0) return null;
2493
+ const kindClass = (kind) => kind === "prep" ? request_log_default.timelinePrep : kind === "wait" ? request_log_default.timelineWait : request_log_default.timelineStream;
2494
+ const spoken = [
2495
+ ...timeline.slices.map((slice) => `${slice.label} ${duration(slice.to - slice.from)}`),
2496
+ `${t("requestLogColLatency")} ${duration(timeline.end)}`
2497
+ ].join(", ");
2498
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: request_log_default.timeline, role: "img", "aria-label": spoken, children: [
2499
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: request_log_default.timelineTrackWrap, children: [
2500
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: request_log_default.timelineTrack, children: [
2501
+ timeline.slices.map((slice) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2502
+ "div",
2503
+ {
2504
+ className: `${request_log_default.timelineSeg} ${kindClass(slice.kind)}`,
2505
+ style: {
2506
+ left: `${(slice.from / timeline.end * 100).toFixed(2)}%`,
2507
+ width: `${Math.max(0, (slice.to - slice.from) / timeline.end * 100).toFixed(2)}%`
2508
+ },
2509
+ title: `${slice.label} ${duration(slice.to - slice.from)}`
2481
2510
  },
2482
- title: `${slice.label} ${duration(slice.to - slice.from)}`
2483
- },
2484
- slice.kind
2485
- )),
2486
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: request_log_default.timelineTick, title: duration(timeline.end) })
2511
+ slice.kind
2512
+ )),
2513
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: request_log_default.timelineMidTick }),
2514
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: request_log_default.timelineTick, title: duration(timeline.end) })
2515
+ ] }),
2516
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: request_log_default.timelineMid, title: duration(timeline.end / 2), children: duration(timeline.end / 2) })
2487
2517
  ] }),
2488
2518
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: request_log_default.timelineLegend, children: [
2489
2519
  timeline.slices.map((slice) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: request_log_default.timelineItem, children: [
2490
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: `${request_log_default.timelineSwatch} ${request_log_default[`timeline${slice.kind.charAt(0).toUpperCase()}${slice.kind.slice(1)}`]}` }),
2520
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: `${request_log_default.timelineSwatch} ${kindClass(slice.kind)}` }),
2491
2521
  slice.label,
2492
2522
  " ",
2493
2523
  duration(slice.to - slice.from)
@@ -2497,7 +2527,8 @@ function RequestLogPage({ readLog, enabled, canOpenSession, openSession, t, getL
2497
2527
  t("requestLogColLatency"),
2498
2528
  " ",
2499
2529
  duration(timeline.end)
2500
- ] })
2530
+ ] }),
2531
+ timeline.burst && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: request_log_default.timelineItem, children: t("requestLogTimelineBurst") })
2501
2532
  ] })
2502
2533
  ] });
2503
2534
  })(),
@@ -2771,6 +2802,7 @@ function RequestLogPage({ readLog, enabled, canOpenSession, openSession, t, getL
2771
2802
  head("cacheRate", t("requestLogColCacheRate")),
2772
2803
  head("tps", t("requestLogColTps")),
2773
2804
  head("prepMs", t("requestLogColPrep")),
2805
+ head("waitMs", t("requestLogColWait")),
2774
2806
  head("firstTokenMs", t("requestLogColFirstToken")),
2775
2807
  head("durationMs", t("requestLogColLatency")),
2776
2808
  head("status", t("requestLogColStatus"))
package/lib/index.js CHANGED
@@ -76907,30 +76907,113 @@ var OpencodeGoAdapter = class extends LlmAdapter {
76907
76907
  "UNSUPPORTED_REASONING_EFFORT"
76908
76908
  );
76909
76909
  }
76910
+ /**
76911
+ * Open the request's log row.
76912
+ *
76913
+ * Called before any wait the request may hit — the catalog included — so
76914
+ * a cold catalog reads as preparation instead of invisible time, and a
76915
+ * failure before the first byte still leaves a row behind. Everything
76916
+ * here is already known or pure: the protocol arrives later, through
76917
+ * `identify`, once the catalog has answered.
76918
+ */
76919
+ startSpan(options, config, protocol) {
76920
+ const containsImage = options.messages.some((message) => contentHasImage2(message.content));
76921
+ const headers = providerHeaders(options.sessionId === void 0 ? void 0 : String(options.sessionId));
76922
+ const imageBudget = IMAGE_BUDGETS[config.imageBudgetPreset];
76923
+ const imageBytes = containsImage ? estimateImageWireBytes(options.messages, imageBudget.maxBytes) : void 0;
76924
+ return this.options.requestLog?.start({
76925
+ model: options.model,
76926
+ sessionId: options.sessionId === void 0 ? void 0 : String(options.sessionId),
76927
+ ...protocol === void 0 ? {} : { protocol },
76928
+ method: "POST",
76929
+ url: config.baseURL,
76930
+ messageCount: options.messages.length,
76931
+ hasImage: containsImage,
76932
+ toolCount: options.tools?.length ?? 0,
76933
+ ...imageBytes === void 0 ? {} : { imageBytes },
76934
+ headers: {
76935
+ ...options.sessionId === void 0 ? {} : { session: String(options.sessionId) },
76936
+ ...typeof headers["user-agent"] === "string" ? { userAgent: headers["user-agent"] } : {}
76937
+ }
76938
+ });
76939
+ }
76940
+ /**
76941
+ * Record a failure that happened before any request was built.
76942
+ *
76943
+ * Preparation (unlike dispatch) knows no session and no messages, so the
76944
+ * row reads as unbuilt rather than empty — but it still names the model
76945
+ * and the code, instead of leaving the caller with an error the log never
76946
+ * heard of.
76947
+ */
76948
+ logUnstartedFailure(config, model, error) {
76949
+ this.options.requestLog?.start({
76950
+ model,
76951
+ sessionId: void 0,
76952
+ method: "POST",
76953
+ url: config.baseURL,
76954
+ messageCount: 0,
76955
+ hasImage: false,
76956
+ toolCount: 0,
76957
+ headers: {}
76958
+ })?.finish(false, error instanceof LlmError9 ? error.code : void 0);
76959
+ }
76910
76960
  async prepareCall(_provider, model, _signal) {
76911
76961
  const config = this.options.config();
76912
76962
  assertStreamIdleTimeoutMs(config.streamIdleTimeoutMs);
76913
- const snapshot2 = await this.catalogOf(config).forModel(model);
76963
+ let snapshot2;
76964
+ try {
76965
+ snapshot2 = await this.catalogOf(config).forModel(model);
76966
+ } catch (error) {
76967
+ this.logUnstartedFailure(config, model, error);
76968
+ throw error;
76969
+ }
76914
76970
  const facts = snapshot2.facts.get(model);
76915
- if (facts === void 0) throw new LlmError9(`opencode-go has no model "${model}"`, "UNKNOWN_MODEL");
76971
+ if (facts === void 0) {
76972
+ const error = new LlmError9(`opencode-go has no model "${model}"`, "UNKNOWN_MODEL");
76973
+ this.logUnstartedFailure(config, model, error);
76974
+ throw error;
76975
+ }
76916
76976
  const prepared = this.modelInfo(withModelLimit(toPiModel(facts, config.baseURL), config.modelLimits));
76917
76977
  return {
76918
76978
  model: prepared,
76919
- stream: (options) => this.streamWithSnapshot(options, config, snapshot2)
76979
+ stream: (options) => {
76980
+ const span = this.startSpan(options, config, facts.api);
76981
+ return this.streamWithSnapshot(options, config, snapshot2, span);
76982
+ }
76920
76983
  };
76921
76984
  }
76922
76985
  async *stream(options) {
76923
76986
  const config = this.options.config();
76924
76987
  assertStreamIdleTimeoutMs(config.streamIdleTimeoutMs);
76925
- const snapshot2 = await this.catalogOf(config).forModel(options.model);
76926
- yield* this.streamWithSnapshot(options, config, snapshot2);
76988
+ const span = this.startSpan(options, config);
76989
+ let snapshot2;
76990
+ try {
76991
+ snapshot2 = await this.catalogOf(config).forModel(options.model);
76992
+ } catch (error) {
76993
+ span?.finish(false, error instanceof LlmError9 ? error.code : void 0);
76994
+ throw error;
76995
+ }
76996
+ yield* this.streamWithSnapshot(options, config, snapshot2, span);
76927
76997
  }
76928
- async *streamWithSnapshot(options, config, snapshot2) {
76998
+ async *streamWithSnapshot(options, config, snapshot2, outerSpan) {
76929
76999
  var _stack = [];
76930
77000
  try {
76931
- assertStreamIdleTimeoutMs(config.streamIdleTimeoutMs);
77001
+ const span = outerSpan ?? this.startSpan(options, config);
77002
+ const failSpan = (error) => {
77003
+ span?.finish(false, error instanceof LlmError9 ? error.code : void 0);
77004
+ throw error;
77005
+ };
77006
+ try {
77007
+ assertStreamIdleTimeoutMs(config.streamIdleTimeoutMs);
77008
+ } catch (error) {
77009
+ failSpan(error);
77010
+ }
76932
77011
  const facts = snapshot2.facts.get(options.model);
76933
- if (facts === void 0) throw new LlmError9(`opencode-go has no model "${options.model}"`, "UNKNOWN_MODEL");
77012
+ if (facts === void 0) {
77013
+ const error = new LlmError9(`opencode-go has no model "${options.model}"`, "UNKNOWN_MODEL");
77014
+ span?.finish(false, error.code);
77015
+ throw error;
77016
+ }
76934
77017
  const model = withModelLimit(toPiModel(facts, config.baseURL), config.modelLimits);
76935
77018
  const rates2 = usableRates(facts.cost);
76936
77019
  const stop = stopSequencesFor(model, options.stop);
@@ -76938,32 +77021,24 @@ var OpencodeGoAdapter = class extends LlmAdapter {
76938
77021
  const maxTokens = options.maxTokens === void 0 || ceiling === null || ceiling === void 0 ? options.maxTokens : Math.min(options.maxTokens, ceiling);
76939
77022
  const apiKey = await this.options.resolveApiKey();
76940
77023
  if (apiKey === void 0 || apiKey.length === 0) {
76941
- throw new LlmError9("dshopencodego: no credential resolved for the opencode-go route", "MISSING_CREDENTIAL");
77024
+ const error = new LlmError9("dshopencodego: no credential resolved for the opencode-go route", "MISSING_CREDENTIAL");
77025
+ span?.finish(false, error.code);
77026
+ throw error;
77027
+ }
77028
+ let reasoning;
77029
+ try {
77030
+ reasoning = this.resolveReasoningLevel(model, options.reasoningEffort);
77031
+ } catch (error) {
77032
+ failSpan(error);
76942
77033
  }
76943
- const reasoning = this.resolveReasoningLevel(model, options.reasoningEffort);
76944
77034
  const consumer = new AbortController();
76945
77035
  const upstream = options.signal === void 0 ? consumer.signal : AbortSignal.any([options.signal, consumer.signal]);
76946
77036
  const watchdog = __using(_stack, idleWatchdog(upstream, config.streamIdleTimeoutMs, "LLM_STREAM_IDLE_TIMEOUT"));
76947
77037
  let timedOut = false;
77038
+ span?.identify(facts.api);
76948
77039
  const containsImage = options.messages.some((message) => contentHasImage2(message.content));
76949
77040
  const headers = providerHeaders(options.sessionId === void 0 ? void 0 : String(options.sessionId));
76950
77041
  const imageBudget = IMAGE_BUDGETS[config.imageBudgetPreset];
76951
- const imageBytes = containsImage ? estimateImageWireBytes(options.messages, imageBudget.maxBytes) : void 0;
76952
- const span = this.options.requestLog?.start({
76953
- model: options.model,
76954
- sessionId: options.sessionId === void 0 ? void 0 : String(options.sessionId),
76955
- protocol: facts.api,
76956
- method: "POST",
76957
- url: config.baseURL,
76958
- messageCount: options.messages.length,
76959
- hasImage: containsImage,
76960
- toolCount: options.tools?.length ?? 0,
76961
- ...imageBytes === void 0 ? {} : { imageBytes },
76962
- headers: {
76963
- ...options.sessionId === void 0 ? {} : { session: String(options.sessionId) },
76964
- ...typeof headers["user-agent"] === "string" ? { userAgent: headers["user-agent"] } : {}
76965
- }
76966
- });
76967
77042
  const fetchFactory = this.options.fetch;
76968
77043
  const requestFetch = span === void 0 || fetchFactory === void 0 ? fetchFactory : () => {
76969
77044
  const base = fetchFactory();
@@ -77937,6 +78012,9 @@ var RequestLog = class {
77937
78012
  let reported = false;
77938
78013
  let finished = false;
77939
78014
  return {
78015
+ identify: (protocol) => {
78016
+ if (record2.protocol === void 0) record2.protocol = protocol;
78017
+ },
77940
78018
  token: () => {
77941
78019
  const now = Date.now();
77942
78020
  if (firstTokenAt === void 0) {
@@ -77969,7 +78047,10 @@ var RequestLog = class {
77969
78047
  seq,
77970
78048
  atMs: startedAt,
77971
78049
  model: detail.model,
77972
- ...detail.protocol === void 0 ? {} : { protocol: detail.protocol },
78050
+ // The protocol may have arrived after the row opened, through
78051
+ // `identify` — the running row and the completion read the same
78052
+ // field, so they never disagree.
78053
+ ...record2.protocol === void 0 ? {} : { protocol: record2.protocol },
77973
78054
  session,
77974
78055
  method: record2.method,
77975
78056
  url: record2.url,
@@ -78134,7 +78215,7 @@ function createUpdateChecker(deps) {
78134
78215
  }
78135
78216
 
78136
78217
  // src/update/version.ts
78137
- var PLUGIN_VERSION = true ? "0.1.81" : "0.0.0-dev";
78218
+ var PLUGIN_VERSION = true ? "0.1.83" : "0.0.0-dev";
78138
78219
 
78139
78220
  // src/update/service.ts
78140
78221
  import { TypertRemoteService as TypertRemoteService4 } from "@deepseek-ai/dsh-typert-protocol";
@@ -156,6 +156,25 @@ export declare class OpencodeGoAdapter extends LlmAdapter {
156
156
  providerRetryPolicy(_provider: string): ResolvedRetryPolicy | undefined;
157
157
  /** Validate an explicit effort against the model's own levels, without clamping. */
158
158
  private resolveReasoningLevel;
159
+ /**
160
+ * Open the request's log row.
161
+ *
162
+ * Called before any wait the request may hit — the catalog included — so
163
+ * a cold catalog reads as preparation instead of invisible time, and a
164
+ * failure before the first byte still leaves a row behind. Everything
165
+ * here is already known or pure: the protocol arrives later, through
166
+ * `identify`, once the catalog has answered.
167
+ */
168
+ private startSpan;
169
+ /**
170
+ * Record a failure that happened before any request was built.
171
+ *
172
+ * Preparation (unlike dispatch) knows no session and no messages, so the
173
+ * row reads as unbuilt rather than empty — but it still names the model
174
+ * and the code, instead of leaving the caller with an error the log never
175
+ * heard of.
176
+ */
177
+ private logUnstartedFailure;
159
178
  prepareCall(_provider: string, model: string, _signal?: AbortSignal): Promise<PreparedAdapterCall>;
160
179
  stream(options: GenerateOptions): AsyncIterable<StreamChunk>;
161
180
  private streamWithSnapshot;
@@ -83,10 +83,12 @@ export interface RequestLogEntry {
83
83
  */
84
84
  readonly streamMs?: number;
85
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).
86
+ * Wall time from request start to the moment the fetch went out,
87
+ * in milliseconds — the whole local path, catalog wait included: the row
88
+ * opens before the catalog is consulted, so a cold catalog reads as
89
+ * preparation rather than invisible time. The rest of the first-token
90
+ * wait belongs to the wire and the gateway. Absent when the request never
91
+ * reached the wire (rejected up front).
90
92
  */
91
93
  readonly prepMs?: number;
92
94
  /** Wall time from request start to terminal event, in milliseconds. */
@@ -175,6 +177,19 @@ export interface RequestLogGates {
175
177
  /** How many entries to keep; the ring trims to it on every append. */
176
178
  readonly maxEntries: () => number;
177
179
  }
180
+ /**
181
+ * Minimum content span for a meaningful rate, in milliseconds.
182
+ *
183
+ * A rate needs a window, and a sub-100ms arrival spread is delivery shape,
184
+ * not generation speed: wall-clock timers resolve to a millisecond, one
185
+ * network flush fans its events out across several event-loop turns, and no
186
+ * served LLM sustains thousands of tokens per second. Below this line the
187
+ * tokens arrived in one burst — the reply's speed on the wire is unknown,
188
+ * so no rate is reported rather than a fabricated one. The viewer reuses
189
+ * the same line to name that shape instead of leaving the missing output
190
+ * slice unexplained.
191
+ */
192
+ export declare const MIN_STREAM_MS_FOR_RATE = 100;
178
193
  /**
179
194
  * An in-flight request, handed back only when the log is enabled.
180
195
  *
@@ -205,6 +220,14 @@ export interface RequestSpan {
205
220
  request(method: string, url: string, requestBytes?: number): void;
206
221
  /** Mark the response status line's arrival. */
207
222
  status(status: number): void;
223
+ /**
224
+ * Fill in the wire protocol once the catalog has answered.
225
+ *
226
+ * The row opens before the catalog wait (so the wait itself is recorded),
227
+ * but the protocol is only known after it. Called at most once, before
228
+ * `finish`; a second call keeps the first.
229
+ */
230
+ identify(protocol: WireProtocol): void;
208
231
  /**
209
232
  * Close the span and complete its running row in place.
210
233
  * @param ok - whether the request completed.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dan-ai-studio/dshopencodego",
3
- "version": "0.1.81",
3
+ "version": "0.1.83",
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",