@dan-ai-studio/dshopencodego 0.1.61 → 0.1.63
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 +12 -1
- package/README.md +11 -1
- package/lib/client.js +51 -1
- package/lib/index.js +94 -13
- package/lib/types/config.d.ts +10 -1
- package/lib/types/network/keep-alive.d.ts +52 -0
- package/lib/types/network/pinning.d.ts +2 -1
- package/package.json +2 -1
package/README.en.md
CHANGED
|
@@ -97,6 +97,10 @@ Configuration lives in the profile's `cordis.patch.yml`:
|
|
|
97
97
|
# or https://opencode.ai/inference/v1 (Console main endpoint, pay-as-you-go/free models)
|
|
98
98
|
refreshMinutes: 60 # live catalog TTL, 1 ~ 10080 (7 days)
|
|
99
99
|
streamIdleTimeoutMs: 300000 # stream idle timeout: longest silence between events (ms)
|
|
100
|
+
keepAliveTimeoutMs: 600000 # connection kept idle between requests (ms, 1 ~ 1800000)
|
|
101
|
+
# the timer restarts on every answer, so a connection in
|
|
102
|
+
# frequent use is never dropped; a long pause in a
|
|
103
|
+
# conversation costs one handshake (about 200ms)
|
|
100
104
|
maxRequestImageBytes: 6291456 # whole-request image payload cap (after base64, bytes)
|
|
101
105
|
requestImagePixelBudget: 1440000 # total-pixel budget for one image
|
|
102
106
|
requestImageMaxBytes: 400000 # encoded-byte target for one image (before base64)
|
|
@@ -121,12 +125,19 @@ Configuration lives in the profile's `cordis.patch.yml`:
|
|
|
121
125
|
jitterRatio: 0.1 # default 0.1, range 0 ~ 1
|
|
122
126
|
```
|
|
123
127
|
|
|
124
|
-
**Validated on save**: those constraints are checked when the profile is **written**, not at the first request — `baseURL` must be http(s) with no query or fragment; `refreshMinutes` ranges from 1 to 10080; every `modelLimits` field may be `null`, meaning "select the catalog value explicitly" (`modelLimits.<id>: null` sends both fields back); `modelProtocols` accepts only those three values, and a wrong one makes that model appear as *configuration missing* (switch off, not selectable) rather than falling back to a guess; `retryPolicy` is a union (`normal` reads `maxRetries`/`retryableCodes`/`backoff`, `always` reads only `backoff`) and unknown keys are rejected.
|
|
128
|
+
**Validated on save**: those constraints are checked when the profile is **written**, not at the first request — `baseURL` must be http(s) with no query or fragment; `refreshMinutes` ranges from 1 to 10080; `keepAliveTimeoutMs` ranges from 1 to 1800000; every `modelLimits` field may be `null`, meaning "select the catalog value explicitly" (`modelLimits.<id>: null` sends both fields back); `modelProtocols` accepts only those three values, and a wrong one makes that model appear as *configuration missing* (switch off, not selectable) rather than falling back to a guess; `retryPolicy` is a union (`normal` reads `maxRetries`/`retryableCodes`/`backoff`, `always` reads only `backoff`) and unknown keys are rejected.
|
|
125
129
|
|
|
126
130
|
The API key comes from the Harness credential store (reference name `OPENCODE_GO_API_KEY`), or from `export OPENCODE_GO_API_KEY=...`.
|
|
127
131
|
|
|
128
132
|
**Stream timeout**: `streamIdleTimeoutMs` bounds the **longest silence between two stream events**, not the total request duration — a long-thinking model that keeps producing events is never cut off. Default 5 minutes; the ceiling is `2147483647` (about 24.8 days). Exceeding it raises `TIMEOUT`, which is retryable.
|
|
129
133
|
|
|
134
|
+
**Connection kept**: `keepAliveTimeoutMs` decides **how long a connection to the gateway is held between two requests**. The timer **restarts on every answer**, so a connection in frequent use is never dropped; it bounds only the silence between two requests. Default 10 minutes.
|
|
135
|
+
|
|
136
|
+
- The reason is practical: once a connection is dropped, the next request pays a TCP and a TLS handshake again — **about 200 milliseconds** from here to the gateway — and that cost lands on the **first token of the next turn**. A pause in a conversation (reading the answer, thinking, typing) usually runs 5 to 30 seconds, which is longer than the HTTP client's own 4-second default, so **almost every turn paid for a handshake it did not need**.
|
|
137
|
+
- This only affects deployments that **pin an address** (`pinnedAddresses`): with no pin the request goes through the system's own fetch, and this plugin adds nothing to it.
|
|
138
|
+
- The settings page shows and accepts the value in **seconds** (a number of milliseconds is not readable at a glance) and stores it in milliseconds. Range: 1 to 1800000 milliseconds.
|
|
139
|
+
- Once a connection has been idle that long, the pool **reclaims it** on its own — no separate mechanism is needed. The actual release comes a couple of seconds later than the setting, which is the client's own small margin beyond the deadline the server states. When the plugin is unloaded, the pool is **closed with it**: the pool belongs to the plugin and should not be left behind on the machine.
|
|
140
|
+
|
|
130
141
|
**Images**: three fields, one layer each. An image outside a bound is refused explicitly rather than silently downscaled or dropped:
|
|
131
142
|
|
|
132
143
|
| Field | What it bounds | Default |
|
package/README.md
CHANGED
|
@@ -97,6 +97,9 @@ DSH 桌面端是 Electron 壳 + 完整的 dsh Web 应用,本插件对它同样
|
|
|
97
97
|
# 也可指定为 https://opencode.ai/inference/v1(Console 主端点,含按量/免费模型)
|
|
98
98
|
refreshMinutes: 60 # 目录缓存时长,1 ~ 10080(7 天)
|
|
99
99
|
streamIdleTimeoutMs: 300000 # 流空闲超时:两次流事件之间的最长静默(毫秒)
|
|
100
|
+
keepAliveTimeoutMs: 600000 # 连接空闲保留:两次请求之间连接留多久(毫秒,1 ~ 1800000)
|
|
101
|
+
# 每次收到回答后重新计时,所以常用时连接不会被断开;
|
|
102
|
+
# 对话中每次停顿较久都要重新握手一次(约 200ms)
|
|
100
103
|
maxRequestImageBytes: 6291456 # 整个请求的图片载荷上限(base64 之后,字节)
|
|
101
104
|
requestImagePixelBudget: 1440000 # 单张图的总像素预算
|
|
102
105
|
requestImageMaxBytes: 400000 # 单张图编码前的字节目标(base64 之前)
|
|
@@ -121,12 +124,19 @@ DSH 桌面端是 Electron 壳 + 完整的 dsh Web 应用,本插件对它同样
|
|
|
121
124
|
jitterRatio: 0.1 # 默认 0.1,取值 0 ~ 1
|
|
122
125
|
```
|
|
123
126
|
|
|
124
|
-
**保存即校验**:上面这些约束在**写入 profile 时**就检查,不会拖到第一次请求——`baseURL` 必须是 http(s) 且不带 query/fragment;`refreshMinutes` 取值 1 ~ 10080;`modelLimits` 的每个字段都可以写 `null`,语义是"显式选回目录值"(`modelLimits.<模型>: null` 表示两个字段一起回退);`modelProtocols` 只接受那三个合法值,写错会让该模型以"配置缺失"出现(开关关闭、不可选),而不是回退到猜测;`retryPolicy` 是联合类型(`normal` 读 `maxRetries`/`retryableCodes`/`backoff`,`always` 只读 `backoff`),未知字段会被拒绝。
|
|
127
|
+
**保存即校验**:上面这些约束在**写入 profile 时**就检查,不会拖到第一次请求——`baseURL` 必须是 http(s) 且不带 query/fragment;`refreshMinutes` 取值 1 ~ 10080;`keepAliveTimeoutMs` 取值 1 ~ 1800000;`modelLimits` 的每个字段都可以写 `null`,语义是"显式选回目录值"(`modelLimits.<模型>: null` 表示两个字段一起回退);`modelProtocols` 只接受那三个合法值,写错会让该模型以"配置缺失"出现(开关关闭、不可选),而不是回退到猜测;`retryPolicy` 是联合类型(`normal` 读 `maxRetries`/`retryableCodes`/`backoff`,`always` 只读 `backoff`),未知字段会被拒绝。
|
|
125
128
|
|
|
126
129
|
API Key 通过 Harness 凭证库提供(引用名 `OPENCODE_GO_API_KEY`),也可以直接 `export OPENCODE_GO_API_KEY=...`。
|
|
127
130
|
|
|
128
131
|
**流超时**:`streamIdleTimeoutMs` 约束的是**两次流事件之间的最长静默**,不是请求总时长——长思考模型只要持续产出事件就不会被误判。默认 5 分钟,上限为 `2147483647`(约 24.8 天)。超时抛出 `TIMEOUT`(可重试类)。
|
|
129
132
|
|
|
133
|
+
**连接保留**:`keepAliveTimeoutMs` 决定**与网关的连接在两次请求之间保留多久**。计时器**每次收到回答后重新开始**,所以常用时连接不会被断开,它约束的只是两次请求之间的静默。默认 10 分钟。
|
|
134
|
+
|
|
135
|
+
- 背后的原因很实际:连接一旦被断开,下一次请求要重新做一次 TCP 与 TLS 握手,**到网关这一侧约 200 毫秒**,而这笔开销正好落在**下一轮的第一个 token 上**。对话里的停顿(读答案、思考、再打字)通常在 5 ~ 30 秒,正好跨过 HTTP 客户端默认的 4 秒,于是**几乎每一轮都在白付一次握手**。
|
|
136
|
+
- 这条只影响**固定了地址**(`pinnedAddresses`)的部署:没有固定地址时走的是系统自己的 fetch,本插件不加任何东西。
|
|
137
|
+
- 设置页里以**秒**显示与填写(毫秒的数字人读不出量级),存储时换算成毫秒。取值 1 ~ 1800000 毫秒。
|
|
138
|
+
- 空闲满这个时间之后,连接由连接池**自己回收**,不需要额外的清理机制(实际释放时间比设置值多约 2 秒,那是客户端在服务端给的期限之外自留的一点缓冲)。插件被卸载时,连接池也会**一并关掉**——它是插件的,不该留在机器上。
|
|
139
|
+
|
|
130
140
|
**图片**:三个字段各管一层,超限的图片会被明确拒绝,而不是静默降质或丢弃:
|
|
131
141
|
|
|
132
142
|
| 字段 | 约束对象 | 默认 |
|
package/lib/client.js
CHANGED
|
@@ -386,6 +386,8 @@ var en = {
|
|
|
386
386
|
catalogRefreshing: "Showing the last known catalog; a refresh is running in the background.",
|
|
387
387
|
refreshLabel: "Catalog refresh (minutes)",
|
|
388
388
|
refreshHint: "How long requests and the model picker reuse one listing.",
|
|
389
|
+
keepAliveLabel: "Connection kept idle (seconds)",
|
|
390
|
+
keepAliveHint: "How long a connection to the gateway is held between requests. Longer pauses in a conversation cost one handshake each; the timer restarts on every answer.",
|
|
389
391
|
writeRejected: "The settings write was rejected (the namespace was reloaded) \u2014 try again",
|
|
390
392
|
writeFailed: "The settings write failed",
|
|
391
393
|
filterPlaceholder: "Filter by name or id",
|
|
@@ -496,6 +498,8 @@ var zh = {
|
|
|
496
498
|
catalogRefreshing: "\u663E\u793A\u4E0A\u6B21\u5DF2\u77E5\u7684\u76EE\u5F55\uFF0C\u540E\u53F0\u6B63\u5728\u5237\u65B0\u3002",
|
|
497
499
|
refreshLabel: "\u76EE\u5F55\u5237\u65B0\uFF08\u5206\u949F\uFF09",
|
|
498
500
|
refreshHint: "\u8BF7\u6C42\u4E0E\u6A21\u578B\u9009\u62E9\u5668\u590D\u7528\u540C\u4E00\u4EFD\u5217\u8868\u7684\u65F6\u957F\u3002",
|
|
501
|
+
keepAliveLabel: "\u8FDE\u63A5\u7A7A\u95F2\u4FDD\u7559\uFF08\u79D2\uFF09",
|
|
502
|
+
keepAliveHint: "\u4E0E\u7F51\u5173\u7684\u8FDE\u63A5\u5728\u4E24\u6B21\u8BF7\u6C42\u4E4B\u95F4\u4FDD\u7559\u591A\u4E45\u3002\u5BF9\u8BDD\u4E2D\u6BCF\u6B21\u505C\u987F\u8F83\u4E45\u90FD\u8981\u91CD\u65B0\u63E1\u624B\u4E00\u6B21\uFF1B\u6BCF\u6B21\u6536\u5230\u56DE\u7B54\u540E\u8BA1\u65F6\u90FD\u4F1A\u91CD\u65B0\u5F00\u59CB\u3002",
|
|
499
503
|
writeRejected: "\u8BBE\u7F6E\u5199\u5165\u88AB\u62D2\u7EDD\uFF08\u5DF2\u91CD\u65B0\u8F7D\u5165\uFF09\uFF0C\u8BF7\u91CD\u8BD5",
|
|
500
504
|
writeFailed: "\u8BBE\u7F6E\u5199\u5165\u5931\u8D25",
|
|
501
505
|
filterPlaceholder: "\u6309\u540D\u79F0\u6216 ID \u7B5B\u9009",
|
|
@@ -578,6 +582,12 @@ var REPOSITORY = "dan-ai-studio/dshopencodego";
|
|
|
578
582
|
var REGISTRY_LATEST_URL = `https://registry.npmjs.org/${encodeURIComponent(PACKAGE_NAME)}/latest`;
|
|
579
583
|
var CHECK_CACHE_MS = 6 * 60 * 60 * 1e3;
|
|
580
584
|
|
|
585
|
+
// src/network/keep-alive.ts
|
|
586
|
+
var DEFAULT_KEEP_ALIVE_TIMEOUT_SECONDS = 600;
|
|
587
|
+
var MAX_KEEP_ALIVE_TIMEOUT_SECONDS = 1800;
|
|
588
|
+
var DEFAULT_KEEP_ALIVE_TIMEOUT_MS = DEFAULT_KEEP_ALIVE_TIMEOUT_SECONDS * 1e3;
|
|
589
|
+
var MAX_KEEP_ALIVE_TIMEOUT_MS = MAX_KEEP_ALIVE_TIMEOUT_SECONDS * 1e3;
|
|
590
|
+
|
|
581
591
|
// src/go-limits.ts
|
|
582
592
|
var SEED_TRANSCRIBED = "2026-09-25";
|
|
583
593
|
function monthlyRequestsRank(quota) {
|
|
@@ -738,10 +748,12 @@ function settingsOf(value) {
|
|
|
738
748
|
const entries = Object.entries(visibility).filter(([, enabled]) => typeof enabled === "boolean");
|
|
739
749
|
const pins = record["pinnedAddresses"];
|
|
740
750
|
const pinEntries = pins !== null && typeof pins === "object" ? Object.entries(pins).filter(([, address]) => typeof address === "string") : [];
|
|
751
|
+
const keepAlive = record["keepAliveTimeoutMs"];
|
|
741
752
|
return {
|
|
742
753
|
modelVisibility: Object.fromEntries(entries),
|
|
743
754
|
refreshMinutes: minutes,
|
|
744
|
-
pinnedAddresses: Object.fromEntries(pinEntries)
|
|
755
|
+
pinnedAddresses: Object.fromEntries(pinEntries),
|
|
756
|
+
keepAliveTimeoutMs: typeof keepAlive === "number" ? keepAlive : DEFAULT_KEEP_ALIVE_TIMEOUT_MS
|
|
745
757
|
};
|
|
746
758
|
}
|
|
747
759
|
var SectionController = class _SectionController {
|
|
@@ -1122,6 +1134,25 @@ var SectionController = class _SectionController {
|
|
|
1122
1134
|
() => this.set({ settings: { ...current, refreshMinutes: minutes } })
|
|
1123
1135
|
);
|
|
1124
1136
|
}
|
|
1137
|
+
/**
|
|
1138
|
+
* Set how long an idle gateway connection is kept, clamped to the schema's
|
|
1139
|
+
* own bounds.
|
|
1140
|
+
*
|
|
1141
|
+
* The value is in seconds here and stored in milliseconds, because a
|
|
1142
|
+
* person reading a number of seconds can tell whether it is right and a
|
|
1143
|
+
* person reading a number of milliseconds cannot.
|
|
1144
|
+
*/
|
|
1145
|
+
async setKeepAliveTimeoutSeconds(seconds) {
|
|
1146
|
+
const scope = this.services.scope;
|
|
1147
|
+
const current = this.state.settings;
|
|
1148
|
+
if (scope === void 0 || current === void 0 || !this.state.writable) return;
|
|
1149
|
+
if (!Number.isSafeInteger(seconds) || seconds < 1 || seconds > MAX_KEEP_ALIVE_TIMEOUT_SECONDS) return;
|
|
1150
|
+
const milliseconds = seconds * 1e3;
|
|
1151
|
+
await this.write(
|
|
1152
|
+
() => scope.set("keepAliveTimeoutMs", milliseconds),
|
|
1153
|
+
() => this.set({ settings: { ...current, keepAliveTimeoutMs: milliseconds } })
|
|
1154
|
+
);
|
|
1155
|
+
}
|
|
1125
1156
|
async write(operation, onAccepted) {
|
|
1126
1157
|
this.set({ saving: true });
|
|
1127
1158
|
try {
|
|
@@ -1500,6 +1531,25 @@ function Section({ controller, t, getLocale }) {
|
|
|
1500
1531
|
),
|
|
1501
1532
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: section_default.hint, children: t("refreshHint") })
|
|
1502
1533
|
] }),
|
|
1534
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: section_default.row, children: [
|
|
1535
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { className: section_default.hint, htmlFor: "dshopencodego-keepalive", children: t("keepAliveLabel") }),
|
|
1536
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1537
|
+
"input",
|
|
1538
|
+
{
|
|
1539
|
+
id: "dshopencodego-keepalive",
|
|
1540
|
+
className: section_default.number,
|
|
1541
|
+
type: "number",
|
|
1542
|
+
min: 1,
|
|
1543
|
+
max: MAX_KEEP_ALIVE_TIMEOUT_SECONDS,
|
|
1544
|
+
disabled: !editable || state.saving,
|
|
1545
|
+
value: state.settings === void 0 ? "" : Math.round(state.settings.keepAliveTimeoutMs / 1e3),
|
|
1546
|
+
onChange: (event) => {
|
|
1547
|
+
void controller.setKeepAliveTimeoutSeconds(Number(event.target.value));
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
),
|
|
1551
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: section_default.hint, children: t("keepAliveHint") })
|
|
1552
|
+
] }),
|
|
1503
1553
|
state.writeFailure !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: section_default.warn, children: state.writeFailure.kind === "rejected" ? t("writeRejected") : state.writeFailure.message.length === 0 ? t("writeFailed") : `${t("writeFailed")}: ${state.writeFailure.message}` }),
|
|
1504
1554
|
reading !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: section_default.row, children: [
|
|
1505
1555
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
package/lib/index.js
CHANGED
|
@@ -53107,6 +53107,7 @@ var require_client_h2 = __commonJS({
|
|
|
53107
53107
|
var kGoAwayReplayAttempts = /* @__PURE__ */ Symbol("goaway replay attempts");
|
|
53108
53108
|
var kRefusedStreamRetry = /* @__PURE__ */ Symbol("refused stream retry");
|
|
53109
53109
|
var MAX_GOAWAY_REPLAY_ATTEMPTS = 1;
|
|
53110
|
+
var MIN_PROBE_TIMEOUT = 1e3;
|
|
53110
53111
|
var extractBody2;
|
|
53111
53112
|
var http2;
|
|
53112
53113
|
try {
|
|
@@ -53267,6 +53268,9 @@ var require_client_h2 = __commonJS({
|
|
|
53267
53268
|
// Armed while the peer advertises MAX_CONCURRENT_STREAMS = 0 and we have
|
|
53268
53269
|
// work that cannot start. See setNoStreamsTimeout.
|
|
53269
53270
|
noStreamsTimeout: null,
|
|
53271
|
+
// Armed while a PING checks whether the peer is still there after a
|
|
53272
|
+
// stream timed out. See probeHttp2Session.
|
|
53273
|
+
probeTimeout: null,
|
|
53270
53274
|
// Sockets start out ref'd. Session ref/unref proxies to the socket, so a
|
|
53271
53275
|
// single cached flag lets us skip redundant uv ref/unref calls, provided
|
|
53272
53276
|
// every ref/unref of the session or its socket goes through
|
|
@@ -53336,6 +53340,9 @@ var require_client_h2 = __commonJS({
|
|
|
53336
53340
|
if (session[kRemoteSettings] === false && client[kRunning] > 0) {
|
|
53337
53341
|
return true;
|
|
53338
53342
|
}
|
|
53343
|
+
if (session[kHTTP2SessionState].probeTimeout != null) {
|
|
53344
|
+
return true;
|
|
53345
|
+
}
|
|
53339
53346
|
if (client[kRunning] >= client[kMaxConcurrentStreams]) {
|
|
53340
53347
|
return true;
|
|
53341
53348
|
}
|
|
@@ -53420,6 +53427,41 @@ var require_client_h2 = __commonJS({
|
|
|
53420
53427
|
session[kError] = err;
|
|
53421
53428
|
resetHttp2Session(session, err);
|
|
53422
53429
|
}
|
|
53430
|
+
function probeHttp2Session(session, timeout) {
|
|
53431
|
+
const state2 = session[kHTTP2SessionState];
|
|
53432
|
+
if (state2.probeTimeout != null || session.closed || session.destroyed) {
|
|
53433
|
+
return;
|
|
53434
|
+
}
|
|
53435
|
+
try {
|
|
53436
|
+
session.ping(onHttp2ProbeAck.bind(session));
|
|
53437
|
+
} catch {
|
|
53438
|
+
return;
|
|
53439
|
+
}
|
|
53440
|
+
state2.probeTimeout = setTimeout(onHttp2ProbeTimeout, Math.max(timeout, MIN_PROBE_TIMEOUT), session).unref();
|
|
53441
|
+
}
|
|
53442
|
+
function clearHttp2ProbeTimeout(session) {
|
|
53443
|
+
const state2 = session[kHTTP2SessionState];
|
|
53444
|
+
if (state2?.probeTimeout != null) {
|
|
53445
|
+
clearTimeout(state2.probeTimeout);
|
|
53446
|
+
state2.probeTimeout = null;
|
|
53447
|
+
}
|
|
53448
|
+
}
|
|
53449
|
+
function onHttp2ProbeAck() {
|
|
53450
|
+
if (this[kHTTP2SessionState].probeTimeout == null) {
|
|
53451
|
+
return;
|
|
53452
|
+
}
|
|
53453
|
+
clearHttp2ProbeTimeout(this);
|
|
53454
|
+
this[kClient][kResume]();
|
|
53455
|
+
}
|
|
53456
|
+
function onHttp2ProbeTimeout(session) {
|
|
53457
|
+
session[kHTTP2SessionState].probeTimeout = null;
|
|
53458
|
+
if (session.closed || session.destroyed) {
|
|
53459
|
+
return;
|
|
53460
|
+
}
|
|
53461
|
+
const err = new SocketError("HTTP/2: connection unresponsive", util2.getSocketInfo(session[kSocket]));
|
|
53462
|
+
session[kError] = err;
|
|
53463
|
+
resetHttp2Session(session, err);
|
|
53464
|
+
}
|
|
53423
53465
|
function clearHttp2IdleTimeout(session) {
|
|
53424
53466
|
const state2 = session[kHTTP2SessionState];
|
|
53425
53467
|
if (state2?.idleTimeout != null) {
|
|
@@ -53554,6 +53596,7 @@ var require_client_h2 = __commonJS({
|
|
|
53554
53596
|
}
|
|
53555
53597
|
clearHttp2IdleTimeout(this);
|
|
53556
53598
|
clearNoStreamsTimeout(this);
|
|
53599
|
+
clearHttp2ProbeTimeout(this);
|
|
53557
53600
|
if (!this.closed && !this.destroyed) {
|
|
53558
53601
|
this.close();
|
|
53559
53602
|
}
|
|
@@ -53571,6 +53614,7 @@ var require_client_h2 = __commonJS({
|
|
|
53571
53614
|
}
|
|
53572
53615
|
clearHttp2IdleTimeout(this);
|
|
53573
53616
|
clearNoStreamsTimeout(this);
|
|
53617
|
+
clearHttp2ProbeTimeout(this);
|
|
53574
53618
|
if (state2.ping.interval != null) {
|
|
53575
53619
|
clearInterval(state2.ping.interval);
|
|
53576
53620
|
state2.ping.interval = null;
|
|
@@ -54157,7 +54201,9 @@ var require_client_h2 = __commonJS({
|
|
|
54157
54201
|
return;
|
|
54158
54202
|
}
|
|
54159
54203
|
stream5.off("timeout", onTimeout);
|
|
54160
|
-
const
|
|
54204
|
+
const { responseReceived, session, bodyTimeout, headersTimeout } = state2;
|
|
54205
|
+
const err = responseReceived ? new BodyTimeoutError(`HTTP/2: "stream timeout after ${bodyTimeout}"`) : new HeadersTimeoutError(`HTTP/2: "headers timeout after ${headersTimeout}"`);
|
|
54206
|
+
probeHttp2Session(session, responseReceived ? bodyTimeout : headersTimeout);
|
|
54161
54207
|
state2.abort(err);
|
|
54162
54208
|
}
|
|
54163
54209
|
function onTrailers(trailers) {
|
|
@@ -55031,6 +55077,7 @@ var require_fixed_queue = __commonJS({
|
|
|
55031
55077
|
var require_pool_base = __commonJS({
|
|
55032
55078
|
"node_modules/undici/lib/dispatcher/pool-base.js"(exports, module) {
|
|
55033
55079
|
"use strict";
|
|
55080
|
+
var { AsyncResource } = __require("node:async_hooks");
|
|
55034
55081
|
var { PoolStats } = require_stats();
|
|
55035
55082
|
var DispatcherBase = require_dispatcher_base();
|
|
55036
55083
|
var FixedQueue = require_fixed_queue();
|
|
@@ -55052,6 +55099,16 @@ var require_pool_base = __commonJS({
|
|
|
55052
55099
|
var kRemoveClient = /* @__PURE__ */ Symbol("remove client");
|
|
55053
55100
|
var kRetireClient = /* @__PURE__ */ Symbol("retire client");
|
|
55054
55101
|
var kRetiring = /* @__PURE__ */ Symbol("retiring clients");
|
|
55102
|
+
var QueuedRequest = class extends AsyncResource {
|
|
55103
|
+
constructor(opts, handler) {
|
|
55104
|
+
super("UNDICI_POOL_QUEUED_REQUEST");
|
|
55105
|
+
this.opts = opts;
|
|
55106
|
+
this.handler = handler;
|
|
55107
|
+
}
|
|
55108
|
+
dispatch(dispatcher) {
|
|
55109
|
+
return this.runInAsyncScope(dispatcher.dispatch, dispatcher, this.opts, this.handler);
|
|
55110
|
+
}
|
|
55111
|
+
};
|
|
55055
55112
|
function closeClients(pool) {
|
|
55056
55113
|
const closeAll = [];
|
|
55057
55114
|
for (let i = 0; i < pool[kClients].length; i++) {
|
|
@@ -55086,7 +55143,7 @@ var require_pool_base = __commonJS({
|
|
|
55086
55143
|
break;
|
|
55087
55144
|
}
|
|
55088
55145
|
this[kQueued]--;
|
|
55089
|
-
needDrain = !
|
|
55146
|
+
needDrain = !item.dispatch(client);
|
|
55090
55147
|
}
|
|
55091
55148
|
client[kNeedDrain] = needDrain;
|
|
55092
55149
|
if (needDrain) {
|
|
@@ -55118,7 +55175,7 @@ var require_pool_base = __commonJS({
|
|
|
55118
55175
|
}
|
|
55119
55176
|
const item = queue.shift();
|
|
55120
55177
|
this[kQueued]--;
|
|
55121
|
-
if (!
|
|
55178
|
+
if (!item.dispatch(dispatcher)) {
|
|
55122
55179
|
dispatcher[kNeedDrain] = true;
|
|
55123
55180
|
this[kOnClientBusy](dispatcher);
|
|
55124
55181
|
hasDispatcher = this[kHasDispatcher]();
|
|
@@ -55215,7 +55272,7 @@ var require_pool_base = __commonJS({
|
|
|
55215
55272
|
const dispatcher = this[kGetDispatcher]();
|
|
55216
55273
|
if (!dispatcher) {
|
|
55217
55274
|
this[kNeedDrain] = true;
|
|
55218
|
-
this[kQueue].push(
|
|
55275
|
+
this[kQueue].push(new QueuedRequest(opts, handler));
|
|
55219
55276
|
this[kQueued]++;
|
|
55220
55277
|
} else if (!dispatcher.dispatch(opts, handler)) {
|
|
55221
55278
|
dispatcher[kNeedDrain] = true;
|
|
@@ -73803,7 +73860,7 @@ var ENDPOINT_PROTOCOLS = {
|
|
|
73803
73860
|
"/responses": "openai-responses"
|
|
73804
73861
|
};
|
|
73805
73862
|
function cell(value) {
|
|
73806
|
-
return (value ?? "").replace(/[*`]/g, "").trim();
|
|
73863
|
+
return (value ?? "").replace(/<small>.*?<\/small>/gi, " ").replace(/~~.*?~~/g, " ").replace(/<[^>]*>/g, " ").replace(/[*`]/g, "").replace(/\s+/g, " ").trim();
|
|
73807
73864
|
}
|
|
73808
73865
|
function baseName(name2) {
|
|
73809
73866
|
return name2.replace(/\s*\(.*\)\s*$/, "").trim();
|
|
@@ -73827,7 +73884,8 @@ function protocolOfEndpoint(endpoint) {
|
|
|
73827
73884
|
return void 0;
|
|
73828
73885
|
}
|
|
73829
73886
|
function tableRows(lines, required) {
|
|
73830
|
-
const
|
|
73887
|
+
const want = required.toLowerCase();
|
|
73888
|
+
const start = lines.findIndex((line) => line.trimStart().startsWith("|") && line.toLowerCase().includes(want));
|
|
73831
73889
|
if (start < 0) return [];
|
|
73832
73890
|
const rows = [];
|
|
73833
73891
|
for (let index = start + 2; index < lines.length; index++) {
|
|
@@ -75658,6 +75716,12 @@ defineMethod("transform", [
|
|
|
75658
75716
|
"preserve"
|
|
75659
75717
|
], ({ inner }, isInner) => inner.toString(isInner));
|
|
75660
75718
|
|
|
75719
|
+
// src/network/keep-alive.ts
|
|
75720
|
+
var DEFAULT_KEEP_ALIVE_TIMEOUT_SECONDS = 600;
|
|
75721
|
+
var MAX_KEEP_ALIVE_TIMEOUT_SECONDS = 1800;
|
|
75722
|
+
var DEFAULT_KEEP_ALIVE_TIMEOUT_MS = DEFAULT_KEEP_ALIVE_TIMEOUT_SECONDS * 1e3;
|
|
75723
|
+
var MAX_KEEP_ALIVE_TIMEOUT_MS = MAX_KEEP_ALIVE_TIMEOUT_SECONDS * 1e3;
|
|
75724
|
+
|
|
75661
75725
|
// src/config.ts
|
|
75662
75726
|
var DEFAULT_API_KEY_ENV = "OPENCODE_GO_API_KEY";
|
|
75663
75727
|
var DEFAULT_REFRESH_MINUTES = 60;
|
|
@@ -75672,6 +75736,12 @@ var fields = {
|
|
|
75672
75736
|
baseURL: Schema.string().default(DEFAULT_BASE_URL),
|
|
75673
75737
|
refreshMinutes: Schema.number().step(1).min(1).max(7 * 24 * 60).default(DEFAULT_REFRESH_MINUTES),
|
|
75674
75738
|
streamIdleTimeoutMs: Schema.number().min(Number.MIN_VALUE).max(MAX_TIMER_DELAY_MS).default(DEFAULT_STREAM_IDLE_TIMEOUT_MS),
|
|
75739
|
+
// The ceiling is the plugin's own, and it is well under the point where a
|
|
75740
|
+
// Node timer overflows: a value above 2^31-1 does not become a longer
|
|
75741
|
+
// timer, it becomes ONE MILLISECOND, which would drop the connection at
|
|
75742
|
+
// once and say so only in a warning on stderr. The bound here is what
|
|
75743
|
+
// keeps that unreachable.
|
|
75744
|
+
keepAliveTimeoutMs: Schema.number().step(1).min(1).max(MAX_KEEP_ALIVE_TIMEOUT_MS).default(DEFAULT_KEEP_ALIVE_TIMEOUT_MS),
|
|
75675
75745
|
maxRequestImageBytes: Schema.number().step(1).min(1).default(DEFAULT_MAX_REQUEST_IMAGE_BYTES),
|
|
75676
75746
|
requestImagePixelBudget: Schema.number().step(1).min(1).default(DEFAULT_REQUEST_IMAGE_PIXEL_BUDGET),
|
|
75677
75747
|
requestImageMaxBytes: Schema.number().step(1).min(1).default(DEFAULT_REQUEST_IMAGE_MAX_BYTES),
|
|
@@ -77269,14 +77339,23 @@ function pinnedLookup(address) {
|
|
|
77269
77339
|
};
|
|
77270
77340
|
}
|
|
77271
77341
|
var current;
|
|
77272
|
-
function dispatcherFor(host, address) {
|
|
77273
|
-
const key = `${host}@${address}`;
|
|
77342
|
+
function dispatcherFor(host, address, keepAliveTimeoutMs) {
|
|
77343
|
+
const key = `${host}@${address}@${String(keepAliveTimeoutMs)}`;
|
|
77274
77344
|
if (current?.key === key) return current.agent;
|
|
77275
77345
|
void current?.agent.close();
|
|
77276
|
-
const agent = new import_undici.Agent({
|
|
77346
|
+
const agent = new import_undici.Agent({
|
|
77347
|
+
connect: { lookup: pinnedLookup(address) },
|
|
77348
|
+
keepAliveTimeout: keepAliveTimeoutMs,
|
|
77349
|
+
keepAliveMaxTimeout: keepAliveTimeoutMs
|
|
77350
|
+
});
|
|
77277
77351
|
current = { key, agent };
|
|
77278
77352
|
return agent;
|
|
77279
77353
|
}
|
|
77354
|
+
async function closeDispatcher() {
|
|
77355
|
+
const agent = current?.agent;
|
|
77356
|
+
current = void 0;
|
|
77357
|
+
if (agent !== void 0) await agent.close();
|
|
77358
|
+
}
|
|
77280
77359
|
var SPENT_ADDRESS_CODES = /* @__PURE__ */ new Set([
|
|
77281
77360
|
// The socket never came up.
|
|
77282
77361
|
"ECONNREFUSED",
|
|
@@ -77342,7 +77421,7 @@ async function edgeRefused(response) {
|
|
|
77342
77421
|
return false;
|
|
77343
77422
|
}
|
|
77344
77423
|
}
|
|
77345
|
-
function gatewayFetch(pin) {
|
|
77424
|
+
function gatewayFetch(pin, keepAliveTimeoutMs) {
|
|
77346
77425
|
const address = pin.address;
|
|
77347
77426
|
if (address === void 0) return globalThis.fetch;
|
|
77348
77427
|
return async (input, init) => {
|
|
@@ -77350,7 +77429,7 @@ function gatewayFetch(pin) {
|
|
|
77350
77429
|
if (host !== pin.host) return globalThis.fetch(input, init);
|
|
77351
77430
|
if (isPinSpent(pin)) return globalThis.fetch(input, init);
|
|
77352
77431
|
try {
|
|
77353
|
-
const dispatcher = dispatcherFor(pin.host, address);
|
|
77432
|
+
const dispatcher = dispatcherFor(pin.host, address, keepAliveTimeoutMs);
|
|
77354
77433
|
const response = await dispatchingFetch(input, {
|
|
77355
77434
|
...init,
|
|
77356
77435
|
dispatcher
|
|
@@ -77483,7 +77562,7 @@ function createUpdateChecker(deps) {
|
|
|
77483
77562
|
}
|
|
77484
77563
|
|
|
77485
77564
|
// src/update/version.ts
|
|
77486
|
-
var PLUGIN_VERSION = true ? "0.1.
|
|
77565
|
+
var PLUGIN_VERSION = true ? "0.1.63" : "0.0.0-dev";
|
|
77487
77566
|
|
|
77488
77567
|
// src/update/service.ts
|
|
77489
77568
|
import { TypertRemoteService as TypertRemoteService3 } from "@deepseek-ai/dsh-typert-protocol";
|
|
@@ -77654,7 +77733,8 @@ function apply(ctx, raw) {
|
|
|
77654
77733
|
const meter = new UsageMeter();
|
|
77655
77734
|
const cacheDir = resolveCacheDir(ctx);
|
|
77656
77735
|
const gatewayRequest = () => gatewayFetch(
|
|
77657
|
-
resolveGatewayPin(current2().pinnedAddresses, current2().baseURL)
|
|
77736
|
+
resolveGatewayPin(current2().pinnedAddresses, current2().baseURL),
|
|
77737
|
+
current2().keepAliveTimeoutMs
|
|
77658
77738
|
);
|
|
77659
77739
|
const adapter = new OpencodeGoAdapter({
|
|
77660
77740
|
config: current2,
|
|
@@ -77766,6 +77846,7 @@ function apply(ctx, raw) {
|
|
|
77766
77846
|
ctx.effect(() => () => {
|
|
77767
77847
|
registration?.();
|
|
77768
77848
|
undiscover();
|
|
77849
|
+
return closeDispatcher();
|
|
77769
77850
|
});
|
|
77770
77851
|
ctx.on("internal/config", function(_raw, next) {
|
|
77771
77852
|
const value = next();
|
package/lib/types/config.d.ts
CHANGED
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import type { RetryPolicyConfig } from '@deepseek-ai/dsh-llm';
|
|
15
15
|
import z from '@deepseek-ai/schemastery';
|
|
16
|
+
import { DEFAULT_KEEP_ALIVE_TIMEOUT_MS, DEFAULT_KEEP_ALIVE_TIMEOUT_SECONDS, MAX_KEEP_ALIVE_TIMEOUT_MS, MAX_KEEP_ALIVE_TIMEOUT_SECONDS, TIMER_CEILING_MS } from './network/keep-alive.ts';
|
|
17
|
+
export { DEFAULT_KEEP_ALIVE_TIMEOUT_MS, DEFAULT_KEEP_ALIVE_TIMEOUT_SECONDS, MAX_KEEP_ALIVE_TIMEOUT_MS, MAX_KEEP_ALIVE_TIMEOUT_SECONDS, TIMER_CEILING_MS, };
|
|
16
18
|
/** Credential reference resolving the OpenCode Go API key. */
|
|
17
19
|
export declare const DEFAULT_API_KEY_ENV = "OPENCODE_GO_API_KEY";
|
|
18
20
|
/** Requests and pickers share this cache lifetime; explicit discovery revalidates immediately. */
|
|
@@ -50,6 +52,14 @@ export interface OpencodeGoConfig {
|
|
|
50
52
|
refreshMinutes: number;
|
|
51
53
|
/** Largest idle gap between stream events before the request fails. */
|
|
52
54
|
streamIdleTimeoutMs: number;
|
|
55
|
+
/**
|
|
56
|
+
* Idle time a gateway connection is kept between requests, in milliseconds.
|
|
57
|
+
*
|
|
58
|
+
* The timer is reset by every response, so a connection that is used
|
|
59
|
+
* often is never dropped; this bounds only the silence between two
|
|
60
|
+
* requests.
|
|
61
|
+
*/
|
|
62
|
+
keepAliveTimeoutMs: number;
|
|
53
63
|
/** Request-level bound on base64-encoded image payload, in bytes. */
|
|
54
64
|
maxRequestImageBytes: number;
|
|
55
65
|
/** Total-pixel budget for one request image. */
|
|
@@ -106,4 +116,3 @@ export declare function readConfig(config: LiveConfig): OpencodeGoConfig;
|
|
|
106
116
|
* @returns the normalized base URL without trailing slashes.
|
|
107
117
|
*/
|
|
108
118
|
export declare function assertBaseURL(raw: string): string;
|
|
109
|
-
export {};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How long a gateway connection is kept between requests.
|
|
3
|
+
*
|
|
4
|
+
* These two numbers are declared apart from `config.ts` on purpose. The
|
|
5
|
+
* configuration module imports the Harness' own packages — a schema library
|
|
6
|
+
* and the retry policy type — and the settings page must not drag those into
|
|
7
|
+
* the browser bundle: the page is shipped to a page, and a package that
|
|
8
|
+
* reaches for `node:module` cannot be bundled for one.
|
|
9
|
+
*
|
|
10
|
+
* @module @dan-ai-studio/dshopencodego/network/keep-alive
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Default idle time a gateway connection is kept between requests, in seconds.
|
|
14
|
+
*
|
|
15
|
+
* Undici's own default is four seconds, and a conversation pauses for longer
|
|
16
|
+
* than that all the time: a person reads the answer, thinks, and types the
|
|
17
|
+
* next question. A connection dropped during such a pause has to be built
|
|
18
|
+
* again — a TCP and a TLS handshake, roughly a fifth of a second from here
|
|
19
|
+
* to the gateway — and that cost lands on the first token of the next turn.
|
|
20
|
+
*
|
|
21
|
+
* The timer is reset by every response, so this bounds only the silence
|
|
22
|
+
* between two requests: a connection that is used often is never dropped.
|
|
23
|
+
*
|
|
24
|
+
* The unit is seconds because that is what the settings page shows and
|
|
25
|
+
* accepts; a number of milliseconds is not readable at a glance. The
|
|
26
|
+
* millisecond value below is derived from it, never written separately, so
|
|
27
|
+
* the two can never disagree.
|
|
28
|
+
*/
|
|
29
|
+
export declare const DEFAULT_KEEP_ALIVE_TIMEOUT_SECONDS = 600;
|
|
30
|
+
/**
|
|
31
|
+
* Largest idle time a connection may be kept, in seconds.
|
|
32
|
+
*
|
|
33
|
+
* A connection held for hours belongs to a machine that never talks to the
|
|
34
|
+
* gateway, and a socket that outlives the server's own idea of the address
|
|
35
|
+
* it is talking to is a socket that fails on first use rather than on the
|
|
36
|
+
* one that mattered. Half an hour is longer than any conversation and short
|
|
37
|
+
* enough that the address is re-checked often.
|
|
38
|
+
*/
|
|
39
|
+
export declare const MAX_KEEP_ALIVE_TIMEOUT_SECONDS = 1800;
|
|
40
|
+
/**
|
|
41
|
+
* The ceiling Node's timers can hold, in milliseconds.
|
|
42
|
+
*
|
|
43
|
+
* A value above this does not become a longer timer: it overflows and
|
|
44
|
+
* becomes **one millisecond**, so the connection would be dropped at once —
|
|
45
|
+
* and silently, because the warning goes to stderr and nothing else. This
|
|
46
|
+
* is the reason the maximum below is far smaller than the ceiling.
|
|
47
|
+
*/
|
|
48
|
+
export declare const TIMER_CEILING_MS = 2147483647;
|
|
49
|
+
/** Default idle time a gateway connection is kept between requests, in milliseconds. */
|
|
50
|
+
export declare const DEFAULT_KEEP_ALIVE_TIMEOUT_MS: number;
|
|
51
|
+
/** Largest idle time a connection may be kept, in milliseconds. */
|
|
52
|
+
export declare const MAX_KEEP_ALIVE_TIMEOUT_MS: number;
|
|
@@ -100,6 +100,7 @@ export declare function resetSpentAddresses(): void;
|
|
|
100
100
|
* by the address the system would have chosen — so the request is sent
|
|
101
101
|
* again, that way, and the answer is the one the caller gets.
|
|
102
102
|
* @param pin - the gateway's name and its pinned address.
|
|
103
|
+
* @param keepAliveTimeoutMs - how long an idle connection is kept.
|
|
103
104
|
* @returns the fetch to use.
|
|
104
105
|
*/
|
|
105
|
-
export declare function gatewayFetch(pin: GatewayPin): typeof fetch;
|
|
106
|
+
export declare function gatewayFetch(pin: GatewayPin, keepAliveTimeoutMs: number): typeof fetch;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dan-ai-studio/dshopencodego",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.63",
|
|
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",
|
|
@@ -145,6 +145,7 @@
|
|
|
145
145
|
"react": "^18.3.1",
|
|
146
146
|
"react-dom": "^18.3.1",
|
|
147
147
|
"typescript": "^7.0.2",
|
|
148
|
+
"undici": "~8.12.3",
|
|
148
149
|
"vitest": "^5.0.5"
|
|
149
150
|
},
|
|
150
151
|
"keywords": [
|