@dan-ai-studio/dshopencodego 0.1.72 → 0.1.73

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.en.md CHANGED
@@ -151,6 +151,7 @@ The API key comes from the Harness credential store (reference name `OPENCODE_GO
151
151
  - 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).
152
152
  - **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.
153
153
  - **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.
154
+ - **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.
154
155
  - Session ids are **always stored whole**: an id is a conversation identity, not a credential, and a truncated one reads two conversations as one.
155
156
  - 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.
156
157
  - 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.
package/README.md CHANGED
@@ -150,6 +150,7 @@ API Key 通过 Harness 凭证库提供(引用名 `OPENCODE_GO_API_KEY`),
150
150
  - 会话列显示**会话标题**(标题来自会话目录;拿不到时回退显示 id,悬停可见完整 id)。会话筛选同时匹配标题与 id;按会话分组的组头同样显示标题。**点标题直接打开那个会话**(归档的打不开,会明说)。
151
151
  - **缓存命中**是输入 token 里走缓存的占比(缓存读取/(未缓存输入+缓存读取))。网关没报输入就没有占比——同样不编数。
152
152
  - **输入列是未缓存的新增 token**:网关把整段 prompt 拆成"未缓存+缓存命中"两部分上报,输入列只记新增的——同一会话前缀缓存全中时输入很小、缓存命中 100%,不是 bug。悬停列头有口径说明。
153
+ - **内容拆分**:明细里"请求体"下面跟一行"文本 X /图片 Y"——图片是各去重后附件按 base64 估的占比(单张按路由的编码上限封顶),文本是总数减图片的剩余,两者恒等于请求体总数。图片 dominant 的请求一眼就能看出来。
153
154
  - 会话 id **始终完整记录**(它是会话身份,不是凭据;截断后两个会话会读成一个)。
154
155
  - 记录的是环形缓冲(超限丢最旧),进程重启即清;只读快照,不轮询,没人打开的页面零流量。
155
156
  - 凭据永远不进记录:记录器根本不接收请求头参数,只收白名单里的会话 id 与 UA;失败的请求同样记一条(含错误码),中断到没有终端事件的不记(网关没报总数就不编数)。
package/lib/client.js CHANGED
@@ -252,6 +252,7 @@ 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["imageBytes"] === void 0 ? {} : { imageBytes: count("imageBytes", row) },
255
256
  ...row["durationMs"] === void 0 ? {} : { durationMs: count("durationMs", row) },
256
257
  ...row["firstTokenMs"] === void 0 ? {} : { firstTokenMs: count("firstTokenMs", row) },
257
258
  ...row["decodeMs"] === void 0 ? {} : { decodeMs: count("decodeMs", row) },
@@ -564,6 +565,9 @@ var en = {
564
565
  requestLogDetailProtocol: "Protocol",
565
566
  requestLogDetailUrl: "URL",
566
567
  requestLogDetailRequestBytes: "Request body",
568
+ requestLogDetailContentSplit: "Content split",
569
+ requestLogDetailText: "Text",
570
+ requestLogDetailImages: "Images",
567
571
  requestLogDetailSession: "Session",
568
572
  requestLogDetailPool: "Pool",
569
573
  requestLogDetailError: "Error",
@@ -754,6 +758,9 @@ var zh = {
754
758
  requestLogDetailProtocol: "\u534F\u8BAE",
755
759
  requestLogDetailUrl: "\u5730\u5740",
756
760
  requestLogDetailRequestBytes: "\u8BF7\u6C42\u4F53",
761
+ requestLogDetailContentSplit: "\u5185\u5BB9\u62C6\u5206",
762
+ requestLogDetailText: "\u6587\u672C",
763
+ requestLogDetailImages: "\u56FE\u7247",
757
764
  requestLogDetailSession: "\u4F1A\u8BDD",
758
765
  requestLogDetailPool: "\u8FDE\u63A5\u6C60",
759
766
  requestLogDetailError: "\u9519\u8BEF",
@@ -2140,6 +2147,11 @@ function bytes(value) {
2140
2147
  if (value < 1024 * 1024) return `${(value / 1024).toFixed(1)} KB`;
2141
2148
  return `${(value / (1024 * 1024)).toFixed(2)} MB`;
2142
2149
  }
2150
+ function contentSplit(entry, text2, images) {
2151
+ const imageBytes = entry.imageBytes ?? 0;
2152
+ const textBytes = entry.requestBytes === void 0 ? void 0 : Math.max(0, entry.requestBytes - imageBytes);
2153
+ return `${text2} ${textBytes === void 0 ? "\u2014" : bytes(textBytes)} / ${images} ${bytes(imageBytes)}`;
2154
+ }
2143
2155
  function cacheRate(entry) {
2144
2156
  if (entry.inputTokens === void 0) return void 0;
2145
2157
  const cached = entry.cacheReadTokens ?? 0;
@@ -2352,6 +2364,10 @@ function RequestLogPage({ readLog, enabled, canOpenSession, openSession, t, getL
2352
2364
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: request_log_default.detailLabel, children: t("requestLogDetailRequestBytes") }),
2353
2365
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: request_log_default.detailValue, children: bytes(entry.requestBytes) })
2354
2366
  ] }),
2367
+ entry.imageBytes !== void 0 && entry.imageBytes > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: request_log_default.detailItem, children: [
2368
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: request_log_default.detailLabel, children: t("requestLogDetailContentSplit") }),
2369
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: request_log_default.detailValue, children: contentSplit(entry, t("requestLogDetailText"), t("requestLogDetailImages")) })
2370
+ ] }),
2355
2371
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: request_log_default.detailItem, children: [
2356
2372
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: request_log_default.detailLabel, children: t("requestLogDetailSession") }),
2357
2373
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
package/lib/index.js CHANGED
@@ -76610,6 +76610,21 @@ function bodyBytes(body) {
76610
76610
  if (ArrayBuffer.isView(body)) return body.byteLength;
76611
76611
  return void 0;
76612
76612
  }
76613
+ function estimateImageWireBytes(messages, capBytes) {
76614
+ const seen = /* @__PURE__ */ new Set();
76615
+ let total = 0;
76616
+ const cap = Math.max(0, capBytes);
76617
+ for (const message of messages) {
76618
+ for (const block of message.content) {
76619
+ if (block.type !== "image" || block.offloaded === true) continue;
76620
+ const id = block.attachment.attachmentId;
76621
+ if (seen.has(id)) continue;
76622
+ seen.add(id);
76623
+ total += Math.ceil(Math.min(block.attachment.bytes, cap) / 3) * 4;
76624
+ }
76625
+ }
76626
+ return seen.size === 0 ? void 0 : total;
76627
+ }
76613
76628
  async function* observingTerminalUsage(events, observe) {
76614
76629
  for await (const event of events) {
76615
76630
  if (event.type === "done") observe(event.message.usage);
@@ -76780,6 +76795,7 @@ var OpencodeGoAdapter = class extends LlmAdapter {
76780
76795
  let timedOut = false;
76781
76796
  const containsImage = options.messages.some((message) => contentHasImage2(message.content));
76782
76797
  const headers = providerHeaders(options.sessionId === void 0 ? void 0 : String(options.sessionId));
76798
+ const imageBytes = containsImage ? estimateImageWireBytes(options.messages, config.requestImageMaxBytes) : void 0;
76783
76799
  const span = this.options.requestLog?.start({
76784
76800
  model: options.model,
76785
76801
  sessionId: options.sessionId === void 0 ? void 0 : String(options.sessionId),
@@ -76789,6 +76805,7 @@ var OpencodeGoAdapter = class extends LlmAdapter {
76789
76805
  messageCount: options.messages.length,
76790
76806
  hasImage: containsImage,
76791
76807
  toolCount: options.tools?.length ?? 0,
76808
+ ...imageBytes === void 0 ? {} : { imageBytes },
76792
76809
  headers: {
76793
76810
  ...options.sessionId === void 0 ? {} : { session: String(options.sessionId) },
76794
76811
  ...typeof headers["user-agent"] === "string" ? { userAgent: headers["user-agent"] } : {}
@@ -77145,6 +77162,7 @@ function parseEntry(value) {
77145
77162
  ...row["reasoningTokens"] === void 0 ? {} : { reasoningTokens: count("reasoningTokens", row) },
77146
77163
  ...row["costUsd"] === void 0 ? {} : { costUsd: rate("costUsd", row) },
77147
77164
  ...row["requestBytes"] === void 0 ? {} : { requestBytes: count("requestBytes", row) },
77165
+ ...row["imageBytes"] === void 0 ? {} : { imageBytes: count("imageBytes", row) },
77148
77166
  ...row["durationMs"] === void 0 ? {} : { durationMs: count("durationMs", row) },
77149
77167
  ...row["firstTokenMs"] === void 0 ? {} : { firstTokenMs: count("firstTokenMs", row) },
77150
77168
  ...row["decodeMs"] === void 0 ? {} : { decodeMs: count("decodeMs", row) },
@@ -77781,6 +77799,7 @@ var RequestLog = class {
77781
77799
  },
77782
77800
  ...costUsd === void 0 ? {} : { costUsd },
77783
77801
  ...requestBytes === void 0 ? {} : { requestBytes },
77802
+ ...detail.imageBytes === void 0 ? {} : { imageBytes: detail.imageBytes },
77784
77803
  durationMs: Math.max(0, endedAt - startedAt),
77785
77804
  ...firstTokenAt === void 0 ? {} : { firstTokenMs: Math.max(0, firstTokenAt - startedAt) },
77786
77805
  ...decodeMs === void 0 ? {} : { decodeMs },
@@ -77925,7 +77944,7 @@ function createUpdateChecker(deps) {
77925
77944
  }
77926
77945
 
77927
77946
  // src/update/version.ts
77928
- var PLUGIN_VERSION = true ? "0.1.72" : "0.0.0-dev";
77947
+ var PLUGIN_VERSION = true ? "0.1.73" : "0.0.0-dev";
77929
77948
 
77930
77949
  // src/update/service.ts
77931
77950
  import { TypertRemoteService as TypertRemoteService4 } from "@deepseek-ai/dsh-typert-protocol";
@@ -20,6 +20,25 @@ import type { GoDoc } from './catalog/go-doc.ts';
20
20
  import type { OpencodeGoConfig } from './config.ts';
21
21
  import type { RequestRecorder } from './request-log/store.ts';
22
22
  import type { MeteredUsage } from './usage/meter.ts';
23
+ /**
24
+ * Estimate the image share of one request's wire bytes, from the messages
25
+ * before conversion.
26
+ *
27
+ * Every attached image travels as base64 (or raw bytes on the one protocol
28
+ * that sends them so), so the estimate uses the base64 size — the larger
29
+ * of the two — and can only understate the text residual, never inflate
30
+ * the parts past the whole. Repeated occurrences of one attachment travel
31
+ * once: the conversion dedupes by attachment id, and so does this walk.
32
+ * An offloaded placeholder travels as text, not bytes, and is skipped the
33
+ * same way. Each image is capped at the route's encoded-byte target,
34
+ * mirroring the preparation the conversion applies. Absent when the
35
+ * request carries no image bytes at all, so the common text-only request
36
+ * records nothing extra.
37
+ * @param messages - the request's messages, exactly as the caller passed them.
38
+ * @param capBytes - per-image encoded-byte target, before base64 expansion.
39
+ * @returns estimated image wire bytes, or undefined when there are none.
40
+ */
41
+ export declare function estimateImageWireBytes(messages: GenerateOptions['messages'], capBytes: number): number | undefined;
23
42
  /** Image machinery the adapter reads once per request. */
24
43
  export interface OpencodeGoImageAccess {
25
44
  /** Resolve the durable attachment service at request time. */
@@ -66,6 +66,14 @@ export interface RequestLogEntry {
66
66
  * sensitive: a multi-megabyte turn uploads slowly no matter the model.
67
67
  */
68
68
  readonly requestBytes?: number;
69
+ /**
70
+ * Estimated image share of the wire body, in bytes: unique attached
71
+ * images as base64, each capped at the route's encoded-byte target.
72
+ * The text share is the residual (`requestBytes` minus this), so the two
73
+ * always reconcile with the total. Absent when the request carries no
74
+ * image bytes at all.
75
+ */
76
+ readonly imageBytes?: number;
69
77
  /**
70
78
  * Wall time from the first to the last content token, in milliseconds —
71
79
  * the window the output actually streamed in. Absent when fewer than two
@@ -206,6 +214,8 @@ export interface RequestStart {
206
214
  readonly messageCount: number;
207
215
  readonly hasImage: boolean;
208
216
  readonly toolCount: number;
217
+ /** Estimated image share of the wire body, in bytes; see the entry field. */
218
+ readonly imageBytes?: number;
209
219
  /** Allowlisted request headers; the recorder never sees the credential. */
210
220
  readonly headers: {
211
221
  readonly session?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dan-ai-studio/dshopencodego",
3
- "version": "0.1.72",
3
+ "version": "0.1.73",
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",