@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 +1 -0
- package/README.md +1 -0
- package/lib/client.js +16 -0
- package/lib/index.js +20 -1
- package/lib/types/adapter.d.ts +19 -0
- package/lib/types/request-log/store.d.ts +10 -0
- package/package.json +1 -1
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.
|
|
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";
|
package/lib/types/adapter.d.ts
CHANGED
|
@@ -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