@goodandready/dsh-opencode-provider 0.5.26 → 0.5.28
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/CHANGELOG.md +12 -0
- package/README.md +3 -3
- package/README.ru.md +3 -3
- package/README.zh.md +3 -3
- package/lib/client.js +59 -0
- package/lib/index.d.ts +15 -0
- package/lib/index.js +94 -17
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.28 — release 5: image policy and preparation timeout
|
|
4
|
+
|
|
5
|
+
- **#73 — image policy**: `maxImages`, `maxRequestImageBytes`, `requestImagePixelBudget` and `requestImageMaxBytes` are configurable in Settings; defaults preserve the previous 50 images / 24 MiB / 1568px / 1 MiB behavior. Count and pixel policies are covered by tests.
|
|
6
|
+
- **#78 — preparation timeout**: `requestPreparationTimeoutMs` (default 60s) bounds image resolution before dispatch, independently of the response-header timeout; caller aborts still win, and timeouts surface as `TIMEOUT`.
|
|
7
|
+
- 136 tests.
|
|
8
|
+
|
|
9
|
+
## 0.5.27 — release 4: catalog freshness
|
|
10
|
+
|
|
11
|
+
- **#86 — new-model badge**: the live catalog now keeps `release_date` (or `created`) metadata; Settings marks models released within the last seven UTC days with a NEW badge and date tooltip. Dates are dropped safely when the gateway omits or malforms them.
|
|
12
|
+
- **#85 — output ceiling**: the default generated-output cap is 65,536 tokens (was 32,768), matching the reference plugin while still respecting each model's own `maxTokens` and explicit caller limits.
|
|
13
|
+
- 133 tests.
|
|
14
|
+
|
|
3
15
|
## 0.5.26 — release 3: quota display and switch notices
|
|
4
16
|
|
|
5
17
|
- **#75 — quota display modes**: new `usageDisplay` setting with `auto`/`always`/`off` (legacy `showSidebarQuota` maps to `always`); `auto` shows the sidebar card when a Go usage report exists. Exposed in Settings and TUI.
|
package/README.md
CHANGED
|
@@ -132,12 +132,12 @@ npm pack
|
|
|
132
132
|
Install the **packed tarball** into a profile:
|
|
133
133
|
|
|
134
134
|
```bash
|
|
135
|
-
dsh plugin --profile web add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.
|
|
135
|
+
dsh plugin --profile web add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.28.tgz
|
|
136
136
|
# DSH Desktop:
|
|
137
|
-
dsh plugin --profile desktop add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.
|
|
137
|
+
dsh plugin --profile desktop add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.28.tgz
|
|
138
138
|
|
|
139
139
|
# Or install the published package:
|
|
140
|
-
dsh plugin --profile web add @goodandready/dsh-opencode-provider@0.5.
|
|
140
|
+
dsh plugin --profile web add @goodandready/dsh-opencode-provider@0.5.28
|
|
141
141
|
```
|
|
142
142
|
|
|
143
143
|
Restart dsh (or DSH Desktop), then pick **OpenCode (plugin)** or **OpenCode Go (plugin)** in the model picker.
|
package/README.ru.md
CHANGED
|
@@ -131,12 +131,12 @@ npm pack
|
|
|
131
131
|
Установка из **упакованного tarball**:
|
|
132
132
|
|
|
133
133
|
```bash
|
|
134
|
-
dsh plugin --profile web add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.
|
|
134
|
+
dsh plugin --profile web add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.28.tgz
|
|
135
135
|
# DSH Desktop:
|
|
136
|
-
dsh plugin --profile desktop add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.
|
|
136
|
+
dsh plugin --profile desktop add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.28.tgz
|
|
137
137
|
|
|
138
138
|
# Либо установка опубликованного пакета из npm:
|
|
139
|
-
dsh plugin --profile web add @goodandready/dsh-opencode-provider@0.5.
|
|
139
|
+
dsh plugin --profile web add @goodandready/dsh-opencode-provider@0.5.28
|
|
140
140
|
```
|
|
141
141
|
|
|
142
142
|
После установки перезапустите dsh (или DSH Desktop) и выберите **OpenCode (plugin)** или **OpenCode Go (plugin)** в списке моделей.
|
package/README.zh.md
CHANGED
|
@@ -126,12 +126,12 @@ npm pack
|
|
|
126
126
|
通过**打包文件 (.tgz)**安装:
|
|
127
127
|
|
|
128
128
|
```bash
|
|
129
|
-
dsh plugin --profile web add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.
|
|
129
|
+
dsh plugin --profile web add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.28.tgz
|
|
130
130
|
# DSH 桌面端:
|
|
131
|
-
dsh plugin --profile desktop add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.
|
|
131
|
+
dsh plugin --profile desktop add /absolute/path/to/goodandready-dsh-opencode-provider-0.5.28.tgz
|
|
132
132
|
|
|
133
133
|
# 或从 npm 安装已发布版本:
|
|
134
|
-
dsh plugin --profile web add @goodandready/dsh-opencode-provider@0.5.
|
|
134
|
+
dsh plugin --profile web add @goodandready/dsh-opencode-provider@0.5.28
|
|
135
135
|
```
|
|
136
136
|
|
|
137
137
|
安装完成后重启 dsh 或 DSH 桌面端,在模型选择器中选择 **OpenCode (plugin)** 或 **OpenCode Go (plugin)** 即可。
|
package/lib/client.js
CHANGED
|
@@ -153,6 +153,8 @@ window.__ModuleLoader__.load({
|
|
|
153
153
|
transportAuto: "Auto",
|
|
154
154
|
free: "FREE",
|
|
155
155
|
vision: "Image",
|
|
156
|
+
newModel: "NEW",
|
|
157
|
+
newModelHint: "Released within the last seven UTC days.",
|
|
156
158
|
sectionFree: "Free models",
|
|
157
159
|
sectionFreeHint: "No charge on any plan.",
|
|
158
160
|
sectionGo: "Go subscription",
|
|
@@ -179,6 +181,11 @@ window.__ModuleLoader__.load({
|
|
|
179
181
|
usageUrl: "Usage endpoint",
|
|
180
182
|
requestTimeoutMs: "Header timeout (ms)",
|
|
181
183
|
streamIdleTimeoutMs: "Stream idle timeout (ms)",
|
|
184
|
+
requestPreparationTimeoutMs: "Preparation timeout (ms)",
|
|
185
|
+
maxImages: "Max images per request",
|
|
186
|
+
maxRequestImageBytes: "Request image budget (bytes)",
|
|
187
|
+
requestImagePixelBudget: "Image pixel budget",
|
|
188
|
+
requestImageMaxBytes: "Per-image max bytes",
|
|
182
189
|
proxyUrl: "Proxy URL",
|
|
183
190
|
proxyUrlHint: "HTTP, HTTPS or SOCKS5 proxy for inference, catalog and usage. Empty disables it. Credentials are not supported.",
|
|
184
191
|
maxOutputTokens: "Output limit (empty = model default, 0 = omit)",
|
|
@@ -379,6 +386,8 @@ window.__ModuleLoader__.load({
|
|
|
379
386
|
transportAuto: "自动",
|
|
380
387
|
free: "免费",
|
|
381
388
|
vision: "图片",
|
|
389
|
+
newModel: "NEW",
|
|
390
|
+
newModelHint: "最近七个 UTC 日内发布。",
|
|
382
391
|
sectionFree: "免费模型",
|
|
383
392
|
sectionFreeHint: "任何计划均不收费。",
|
|
384
393
|
sectionGo: "Go 订阅",
|
|
@@ -405,6 +414,11 @@ window.__ModuleLoader__.load({
|
|
|
405
414
|
usageUrl: "用量端点",
|
|
406
415
|
requestTimeoutMs: "请求头超时(毫秒)",
|
|
407
416
|
streamIdleTimeoutMs: "流空闲超时(毫秒)",
|
|
417
|
+
requestPreparationTimeoutMs: "准备超时(毫秒)",
|
|
418
|
+
maxImages: "每请求最大图片数",
|
|
419
|
+
maxRequestImageBytes: "请求图片预算(字节)",
|
|
420
|
+
requestImagePixelBudget: "图片像素预算",
|
|
421
|
+
requestImageMaxBytes: "单图最大字节数",
|
|
408
422
|
proxyUrl: "代理地址",
|
|
409
423
|
proxyUrlHint: "用于推理、目录和用量的 HTTP、HTTPS 或 SOCKS5 代理。留空则禁用。不支持代理认证。",
|
|
410
424
|
maxOutputTokens: "输出上限(空 = 模型默认,0 = 省略)",
|
|
@@ -1692,6 +1706,11 @@ window.__ModuleLoader__.load({
|
|
|
1692
1706
|
openCodeAuthEntry: state.config.openCodeAuthEntry,
|
|
1693
1707
|
requestTimeoutMs: String(state.config.requestTimeoutMs),
|
|
1694
1708
|
streamIdleTimeoutMs: String(state.config.streamIdleTimeoutMs),
|
|
1709
|
+
requestPreparationTimeoutMs: String(state.config.requestPreparationTimeoutMs),
|
|
1710
|
+
maxImages: String(state.config.maxImages),
|
|
1711
|
+
maxRequestImageBytes: String(state.config.maxRequestImageBytes),
|
|
1712
|
+
requestImagePixelBudget: String(state.config.requestImagePixelBudget),
|
|
1713
|
+
requestImageMaxBytes: String(state.config.requestImageMaxBytes),
|
|
1695
1714
|
proxyUrl: state.config.proxyUrl,
|
|
1696
1715
|
maxOutputTokens: state.config.maxOutputTokens === null ? "" : String(state.config.maxOutputTokens),
|
|
1697
1716
|
contextWindow: state.config.contextWindow === null ? "" : String(state.config.contextWindow),
|
|
@@ -1739,6 +1758,11 @@ window.__ModuleLoader__.load({
|
|
|
1739
1758
|
openCodeAuthEntry: draft.openCodeAuthEntry.trim(),
|
|
1740
1759
|
requestTimeoutMs: numberOrNull(draft.requestTimeoutMs) ?? 3e5,
|
|
1741
1760
|
streamIdleTimeoutMs: numberOrNull(draft.streamIdleTimeoutMs) ?? 3e5,
|
|
1761
|
+
requestPreparationTimeoutMs: numberOrNull(draft.requestPreparationTimeoutMs) ?? 6e4,
|
|
1762
|
+
maxImages: numberOrNull(draft.maxImages) ?? 50,
|
|
1763
|
+
maxRequestImageBytes: numberOrNull(draft.maxRequestImageBytes) ?? 25165824,
|
|
1764
|
+
requestImagePixelBudget: numberOrNull(draft.requestImagePixelBudget) ?? 2458624,
|
|
1765
|
+
requestImageMaxBytes: numberOrNull(draft.requestImageMaxBytes) ?? 1048576,
|
|
1742
1766
|
proxyUrl: draft.proxyUrl.trim(),
|
|
1743
1767
|
maxOutputTokens: numberOrNull(draft.maxOutputTokens),
|
|
1744
1768
|
contextWindow: numberOrNull(draft.contextWindow),
|
|
@@ -2158,6 +2182,11 @@ window.__ModuleLoader__.load({
|
|
|
2158
2182
|
title: t("legendFree"),
|
|
2159
2183
|
children: [" ", t("free")]
|
|
2160
2184
|
}) : null,
|
|
2185
|
+
model.isNew === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2186
|
+
className: "oc-badge oc-badge-ok",
|
|
2187
|
+
title: `${t("newModelHint")} ${model.releaseDate ?? ""}`,
|
|
2188
|
+
children: t("newModel")
|
|
2189
|
+
}) : null,
|
|
2161
2190
|
!model.free && hasPrice ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2162
2191
|
className: "oc-badge",
|
|
2163
2192
|
title: t("legendPrice"),
|
|
@@ -3139,6 +3168,36 @@ window.__ModuleLoader__.load({
|
|
|
3139
3168
|
value: draft.streamIdleTimeoutMs,
|
|
3140
3169
|
onChange: (value) => update({ streamIdleTimeoutMs: value })
|
|
3141
3170
|
}),
|
|
3171
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
3172
|
+
label: t("requestPreparationTimeoutMs"),
|
|
3173
|
+
type: "number",
|
|
3174
|
+
value: draft.requestPreparationTimeoutMs,
|
|
3175
|
+
onChange: (value) => update({ requestPreparationTimeoutMs: value })
|
|
3176
|
+
}),
|
|
3177
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
3178
|
+
label: t("maxImages"),
|
|
3179
|
+
type: "number",
|
|
3180
|
+
value: draft.maxImages,
|
|
3181
|
+
onChange: (value) => update({ maxImages: value })
|
|
3182
|
+
}),
|
|
3183
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
3184
|
+
label: t("maxRequestImageBytes"),
|
|
3185
|
+
type: "number",
|
|
3186
|
+
value: draft.maxRequestImageBytes,
|
|
3187
|
+
onChange: (value) => update({ maxRequestImageBytes: value })
|
|
3188
|
+
}),
|
|
3189
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
3190
|
+
label: t("requestImagePixelBudget"),
|
|
3191
|
+
type: "number",
|
|
3192
|
+
value: draft.requestImagePixelBudget,
|
|
3193
|
+
onChange: (value) => update({ requestImagePixelBudget: value })
|
|
3194
|
+
}),
|
|
3195
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
3196
|
+
label: t("requestImageMaxBytes"),
|
|
3197
|
+
type: "number",
|
|
3198
|
+
value: draft.requestImageMaxBytes,
|
|
3199
|
+
onChange: (value) => update({ requestImageMaxBytes: value })
|
|
3200
|
+
}),
|
|
3142
3201
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
3143
3202
|
label: t("proxyUrl"),
|
|
3144
3203
|
hint: t("proxyUrlHint"),
|
package/lib/index.d.ts
CHANGED
|
@@ -37,6 +37,16 @@ interface Config {
|
|
|
37
37
|
requestTimeoutMs?: number;
|
|
38
38
|
/** Milliseconds a stream may stall; defaults to 300s. */
|
|
39
39
|
streamIdleTimeoutMs?: number;
|
|
40
|
+
/** Milliseconds allowed for image resolution and request preparation. */
|
|
41
|
+
requestPreparationTimeoutMs?: number;
|
|
42
|
+
/** Request-wide image count cap; defaults to 50. */
|
|
43
|
+
maxImages?: number;
|
|
44
|
+
/** Request-wide base64 image budget; defaults to 24 MiB. */
|
|
45
|
+
maxRequestImageBytes?: number;
|
|
46
|
+
/** Total-pixel budget for one normalized image. */
|
|
47
|
+
requestImagePixelBudget?: number;
|
|
48
|
+
/** Raw encoded-byte target for one image before base64 expansion. */
|
|
49
|
+
requestImageMaxBytes?: number;
|
|
40
50
|
/** HTTP/HTTPS/SOCKS5 proxy for inference, catalog and usage requests. */
|
|
41
51
|
proxyUrl?: string;
|
|
42
52
|
/** Visible-model allowlist; an empty list means "show everything". */
|
|
@@ -85,6 +95,11 @@ interface ResolvedConfig {
|
|
|
85
95
|
openCodeAuthEntry: string;
|
|
86
96
|
requestTimeoutMs: number;
|
|
87
97
|
streamIdleTimeoutMs: number;
|
|
98
|
+
requestPreparationTimeoutMs: number;
|
|
99
|
+
maxImages: number;
|
|
100
|
+
maxRequestImageBytes: number;
|
|
101
|
+
requestImagePixelBudget: number;
|
|
102
|
+
requestImageMaxBytes: number;
|
|
88
103
|
proxyUrl: string;
|
|
89
104
|
visibleModels: readonly string[];
|
|
90
105
|
modelVisibility: Readonly<Record<string, boolean>>;
|
package/lib/index.js
CHANGED
|
@@ -81,10 +81,14 @@ const DEFAULT_GO_API_BASE = "https://opencode.ai/inference/go";
|
|
|
81
81
|
const DEFAULT_GO_ZEN_BASE = "https://opencode.ai/zen/go/v1";
|
|
82
82
|
const DEFAULT_USAGE_URL = "https://opencode.ai/zen/go/v1/usage";
|
|
83
83
|
const DEFAULT_AUTH_ENTRY = "opencode-go";
|
|
84
|
-
const DEFAULT_USER_AGENT = "dsh-opencode-provider/0.5.
|
|
84
|
+
const DEFAULT_USER_AGENT = "dsh-opencode-provider/0.5.28";
|
|
85
85
|
const DEFAULT_CONTEXT_WINDOW = 262144;
|
|
86
86
|
const DEFAULT_MAX_TOKENS = 32768;
|
|
87
|
-
const DEFAULT_GENERATE_MAX_TOKENS =
|
|
87
|
+
const DEFAULT_GENERATE_MAX_TOKENS = 65536;
|
|
88
|
+
const DEFAULT_MAX_REQUEST_IMAGE_BYTES = 25165824;
|
|
89
|
+
const DEFAULT_REQUEST_IMAGE_PIXEL_BUDGET = 2458624;
|
|
90
|
+
const DEFAULT_REQUEST_IMAGE_MAX_BYTES = 1048576;
|
|
91
|
+
const DEFAULT_REQUEST_PREPARATION_TIMEOUT_MS = 6e4;
|
|
88
92
|
function markVolatile(schema) {
|
|
89
93
|
const mark = schema.volatile;
|
|
90
94
|
return typeof mark === "function" ? mark.call(schema) : schema;
|
|
@@ -99,6 +103,11 @@ const Config = z.object({
|
|
|
99
103
|
openCodeAuthEntry: markVolatile(z.string().default(DEFAULT_AUTH_ENTRY)),
|
|
100
104
|
requestTimeoutMs: markVolatile(z.number().min(1).max(2147483647).default(3e5)),
|
|
101
105
|
streamIdleTimeoutMs: markVolatile(z.number().min(1).max(2147483647).default(3e5)),
|
|
106
|
+
requestPreparationTimeoutMs: markVolatile(z.number().min(1).max(2147483647).default(DEFAULT_REQUEST_PREPARATION_TIMEOUT_MS)),
|
|
107
|
+
maxImages: markVolatile(z.number().min(1).max(1e4).step(1).default(50)),
|
|
108
|
+
maxRequestImageBytes: markVolatile(z.number().min(1).max(1e9).step(1).default(DEFAULT_MAX_REQUEST_IMAGE_BYTES)),
|
|
109
|
+
requestImagePixelBudget: markVolatile(z.number().min(1).max(1e8).step(1).default(DEFAULT_REQUEST_IMAGE_PIXEL_BUDGET)),
|
|
110
|
+
requestImageMaxBytes: markVolatile(z.number().min(1).max(1e8).step(1).default(DEFAULT_REQUEST_IMAGE_MAX_BYTES)),
|
|
102
111
|
proxyUrl: markVolatile(z.string().default("")),
|
|
103
112
|
visibleModels: markVolatile(z.array(z.string()).default([])),
|
|
104
113
|
modelVisibility: markVolatile(z.dict(z.boolean()).default({})),
|
|
@@ -212,6 +221,11 @@ function resolveConfig(config) {
|
|
|
212
221
|
openCodeAuthEntry: typeof config.openCodeAuthEntry === "string" && config.openCodeAuthEntry !== "" ? config.openCodeAuthEntry : DEFAULT_AUTH_ENTRY,
|
|
213
222
|
requestTimeoutMs: positiveInt(config.requestTimeoutMs, 3e5),
|
|
214
223
|
streamIdleTimeoutMs: positiveInt(config.streamIdleTimeoutMs, 3e5),
|
|
224
|
+
requestPreparationTimeoutMs: positiveInt(config.requestPreparationTimeoutMs, DEFAULT_REQUEST_PREPARATION_TIMEOUT_MS),
|
|
225
|
+
maxImages: positiveInt(config.maxImages, 50),
|
|
226
|
+
maxRequestImageBytes: positiveInt(config.maxRequestImageBytes, DEFAULT_MAX_REQUEST_IMAGE_BYTES),
|
|
227
|
+
requestImagePixelBudget: positiveInt(config.requestImagePixelBudget, DEFAULT_REQUEST_IMAGE_PIXEL_BUDGET),
|
|
228
|
+
requestImageMaxBytes: positiveInt(config.requestImageMaxBytes, DEFAULT_REQUEST_IMAGE_MAX_BYTES),
|
|
215
229
|
proxyUrl: normalizeProxyUrl(config.proxyUrl),
|
|
216
230
|
visibleModels: Array.isArray(config.visibleModels) ? config.visibleModels.filter((id) => typeof id === "string" && id !== "") : [],
|
|
217
231
|
modelVisibility: booleanRecord(config.modelVisibility),
|
|
@@ -1108,6 +1122,24 @@ function modelInGoPlan(id, config) {
|
|
|
1108
1122
|
if (GENERATED_MODELS[id] === void 0) return true;
|
|
1109
1123
|
return effectiveModel(id, config).free || limitsFor(id) !== void 0;
|
|
1110
1124
|
}
|
|
1125
|
+
function releaseDateOf(entry) {
|
|
1126
|
+
const raw = entry.release_date;
|
|
1127
|
+
if (typeof raw === "string" && raw !== "") {
|
|
1128
|
+
const parsed = Date.parse(raw.length === 10 ? `${raw}T00:00:00Z` : raw);
|
|
1129
|
+
if (Number.isFinite(parsed)) return new Date(parsed).toISOString().slice(0, 10);
|
|
1130
|
+
}
|
|
1131
|
+
if (typeof entry.created === "number" && Number.isFinite(entry.created)) return (/* @__PURE__ */ new Date(entry.created * 1e3)).toISOString().slice(0, 10);
|
|
1132
|
+
}
|
|
1133
|
+
/** Seven UTC calendar days, used by the picker's NEW badge. */
|
|
1134
|
+
function isNewRelease(date, now = Date.now()) {
|
|
1135
|
+
if (date === void 0) return false;
|
|
1136
|
+
const parsed = Date.parse(date.length === 10 ? `${date}T00:00:00Z` : date);
|
|
1137
|
+
return Number.isFinite(parsed) && parsed <= now && now - parsed < 6048e5;
|
|
1138
|
+
}
|
|
1139
|
+
/** Release date learned from the live catalog, if the gateway published it. */
|
|
1140
|
+
function catalogReleaseDate(provider, id, config) {
|
|
1141
|
+
return catalogCache.get(`${provider}|${modelsUrlFor(provider, config)}`)?.releaseDates[id];
|
|
1142
|
+
}
|
|
1111
1143
|
async function fetchLiveIds(provider, config, apiKey, fetchImpl) {
|
|
1112
1144
|
const url = modelsUrlFor(provider, config);
|
|
1113
1145
|
const cacheKey = `${provider}|${url}`;
|
|
@@ -1127,9 +1159,16 @@ async function fetchLiveIds(provider, config, apiKey, fetchImpl) {
|
|
|
1127
1159
|
const payload = await response.json();
|
|
1128
1160
|
const ids = Array.isArray(payload.data) ? payload.data.map((entry) => entry.id).filter((id) => typeof id === "string" && id !== "") : Object.keys(payload.models ?? {});
|
|
1129
1161
|
if (ids.length === 0) return void 0;
|
|
1162
|
+
const releaseDates = {};
|
|
1163
|
+
if (Array.isArray(payload.data)) for (const entry of payload.data) {
|
|
1164
|
+
if (typeof entry.id !== "string" || entry.id === "") continue;
|
|
1165
|
+
const date = releaseDateOf(entry);
|
|
1166
|
+
if (date !== void 0) releaseDates[entry.id] = date;
|
|
1167
|
+
}
|
|
1130
1168
|
catalogCache.set(cacheKey, {
|
|
1131
1169
|
at: Date.now(),
|
|
1132
|
-
ids
|
|
1170
|
+
ids,
|
|
1171
|
+
releaseDates
|
|
1133
1172
|
});
|
|
1134
1173
|
return ids;
|
|
1135
1174
|
} catch {
|
|
@@ -1267,16 +1306,21 @@ function classifyStreamError(value, status = 200) {
|
|
|
1267
1306
|
}
|
|
1268
1307
|
// src/images.ts
|
|
1269
1308
|
const MAX_LONG_EDGE = 1568;
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1309
|
+
function limitsOf(policy) {
|
|
1310
|
+
return {
|
|
1311
|
+
maxImages: policy?.maxImages ?? 50,
|
|
1312
|
+
maxRequestImageBytes: policy?.maxRequestImageBytes ?? 25165824,
|
|
1313
|
+
requestImagePixelBudget: policy?.requestImagePixelBudget ?? 2458624,
|
|
1314
|
+
requestImageMaxBytes: policy?.requestImageMaxBytes ?? 1048576
|
|
1315
|
+
};
|
|
1316
|
+
}
|
|
1274
1317
|
/**
|
|
1275
1318
|
* Keep the newest images under one request-wide budget. The oldest images are
|
|
1276
1319
|
* dropped first; the transports render them as an "image omitted" placeholder
|
|
1277
1320
|
* instead of failing the whole request.
|
|
1278
1321
|
*/
|
|
1279
|
-
function selectRequestImages(refs, occurrences) {
|
|
1322
|
+
function selectRequestImages(refs, occurrences, policy) {
|
|
1323
|
+
const limits = limitsOf(policy);
|
|
1280
1324
|
const unique = [];
|
|
1281
1325
|
const seen = /* @__PURE__ */ new Set();
|
|
1282
1326
|
for (const ref of refs) {
|
|
@@ -1289,7 +1333,7 @@ function selectRequestImages(refs, occurrences) {
|
|
|
1289
1333
|
let start = 0;
|
|
1290
1334
|
let totalImages = unique.reduce((sum, ref) => sum + countOf(ref), 0);
|
|
1291
1335
|
let bytes = unique.reduce((sum, ref) => sum + encoded(ref.bytes) * countOf(ref), 0);
|
|
1292
|
-
while (start < unique.length && (totalImages >
|
|
1336
|
+
while (start < unique.length && (totalImages > limits.maxImages || bytes > limits.maxRequestImageBytes)) {
|
|
1293
1337
|
const dropped = unique[start];
|
|
1294
1338
|
if (dropped !== void 0) {
|
|
1295
1339
|
const count = countOf(dropped);
|
|
@@ -1308,20 +1352,23 @@ function selectRequestImages(refs, occurrences) {
|
|
|
1308
1352
|
dropped: unique.slice(0, start)
|
|
1309
1353
|
};
|
|
1310
1354
|
}
|
|
1311
|
-
function targetOf(ref) {
|
|
1355
|
+
function targetOf(ref, policy) {
|
|
1356
|
+
const limits = limitsOf(policy);
|
|
1312
1357
|
const longEdge = Math.max(ref.width, ref.height);
|
|
1313
|
-
const
|
|
1358
|
+
const pixelEdge = Math.max(1, Math.floor(Math.sqrt(limits.requestImagePixelBudget)));
|
|
1359
|
+
const maxEdge = Math.min(MAX_LONG_EDGE, pixelEdge);
|
|
1360
|
+
const scale = longEdge > maxEdge ? maxEdge / longEdge : 1;
|
|
1314
1361
|
return {
|
|
1315
1362
|
width: Math.max(1, Math.round(ref.width * scale)),
|
|
1316
1363
|
height: Math.max(1, Math.round(ref.height * scale)),
|
|
1317
|
-
maxBytes:
|
|
1364
|
+
maxBytes: limits.requestImageMaxBytes
|
|
1318
1365
|
};
|
|
1319
1366
|
}
|
|
1320
1367
|
function hasImages(options) {
|
|
1321
1368
|
return options.messages.some((message) => message.content.some((block) => block.type === "image" && block.offloaded !== true));
|
|
1322
1369
|
}
|
|
1323
1370
|
/** Decode the distinct request images that fit the request-wide budget. */
|
|
1324
|
-
async function resolveImages(options, store, signal) {
|
|
1371
|
+
async function resolveImages(options, store, signal, policy) {
|
|
1325
1372
|
const refs = [];
|
|
1326
1373
|
const counts = /* @__PURE__ */ new Map();
|
|
1327
1374
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -1334,8 +1381,8 @@ async function resolveImages(options, store, signal) {
|
|
|
1334
1381
|
refs.push(ref);
|
|
1335
1382
|
}
|
|
1336
1383
|
const images = /* @__PURE__ */ new Map();
|
|
1337
|
-
for (const ref of selectRequestImages(refs, counts).kept) {
|
|
1338
|
-
const request = await store.readImageRequest(ref, targetOf(ref), signal);
|
|
1384
|
+
for (const ref of selectRequestImages(refs, counts, policy).kept) {
|
|
1385
|
+
const request = await store.readImageRequest(ref, targetOf(ref, policy), signal);
|
|
1339
1386
|
const base64 = Buffer.from(request.data).toString("base64");
|
|
1340
1387
|
images.set(ref.attachmentId, {
|
|
1341
1388
|
mediaType: request.mediaType,
|
|
@@ -3344,7 +3391,25 @@ var OpenCodeAdapter = class extends LlmAdapter {
|
|
|
3344
3391
|
if (hasImages(options)) {
|
|
3345
3392
|
const store = this.deps.resolveAttachments?.();
|
|
3346
3393
|
if (store === void 0) throw new LlmError("llm-opencode: image input requires the durable attachment service", "UNSUPPORTED_CONTENT");
|
|
3347
|
-
|
|
3394
|
+
const preparation = new AbortController();
|
|
3395
|
+
let timedOut = false;
|
|
3396
|
+
const onAbort = () => preparation.abort(options.signal?.reason);
|
|
3397
|
+
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
3398
|
+
const timer = setTimeout(() => {
|
|
3399
|
+
timedOut = true;
|
|
3400
|
+
preparation.abort();
|
|
3401
|
+
}, config.requestPreparationTimeoutMs);
|
|
3402
|
+
timer.unref?.();
|
|
3403
|
+
try {
|
|
3404
|
+
images = await resolveImages(options, store, preparation.signal, config);
|
|
3405
|
+
} catch (error) {
|
|
3406
|
+
if (options.signal?.aborted === true) throw options.signal.reason ?? error;
|
|
3407
|
+
if (timedOut) throw new LlmError(`OpenCode image preparation exceeded ${config.requestPreparationTimeoutMs}ms`, "TIMEOUT", { cause: error });
|
|
3408
|
+
throw error;
|
|
3409
|
+
} finally {
|
|
3410
|
+
clearTimeout(timer);
|
|
3411
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
3412
|
+
}
|
|
3348
3413
|
}
|
|
3349
3414
|
let bodyOptions = options;
|
|
3350
3415
|
if (options.tools !== void 0) {
|
|
@@ -4723,6 +4788,11 @@ function publicConfig(config) {
|
|
|
4723
4788
|
openCodeAuthEntry: config.openCodeAuthEntry,
|
|
4724
4789
|
requestTimeoutMs: config.requestTimeoutMs,
|
|
4725
4790
|
streamIdleTimeoutMs: config.streamIdleTimeoutMs,
|
|
4791
|
+
requestPreparationTimeoutMs: config.requestPreparationTimeoutMs,
|
|
4792
|
+
maxImages: config.maxImages,
|
|
4793
|
+
maxRequestImageBytes: config.maxRequestImageBytes,
|
|
4794
|
+
requestImagePixelBudget: config.requestImagePixelBudget,
|
|
4795
|
+
requestImageMaxBytes: config.requestImageMaxBytes,
|
|
4726
4796
|
proxyUrl: config.proxyUrl,
|
|
4727
4797
|
visibleModels: config.visibleModels,
|
|
4728
4798
|
modelVisibility: config.modelVisibility,
|
|
@@ -4919,6 +4989,11 @@ function configOps(patch) {
|
|
|
4919
4989
|
if ("openCodeAuthEntry" in patch) set("openCodeAuthEntry", patch.openCodeAuthEntry);
|
|
4920
4990
|
if ("requestTimeoutMs" in patch) set("requestTimeoutMs", patch.requestTimeoutMs);
|
|
4921
4991
|
if ("streamIdleTimeoutMs" in patch) set("streamIdleTimeoutMs", patch.streamIdleTimeoutMs);
|
|
4992
|
+
if ("requestPreparationTimeoutMs" in patch) set("requestPreparationTimeoutMs", patch.requestPreparationTimeoutMs);
|
|
4993
|
+
if ("maxImages" in patch) set("maxImages", patch.maxImages);
|
|
4994
|
+
if ("maxRequestImageBytes" in patch) set("maxRequestImageBytes", patch.maxRequestImageBytes);
|
|
4995
|
+
if ("requestImagePixelBudget" in patch) set("requestImagePixelBudget", patch.requestImagePixelBudget);
|
|
4996
|
+
if ("requestImageMaxBytes" in patch) set("requestImageMaxBytes", patch.requestImageMaxBytes);
|
|
4922
4997
|
if ("proxyUrl" in patch) set("proxyUrl", normalizeProxyUrl(typeof patch.proxyUrl === "string" ? patch.proxyUrl : ""));
|
|
4923
4998
|
if ("visibleModels" in patch) set("visibleModels", cleanStringArray(patch.visibleModels));
|
|
4924
4999
|
if ("modelVisibility" in patch) set("modelVisibility", cleanBooleanRecord(patch.modelVisibility));
|
|
@@ -5030,7 +5105,9 @@ async function buildState(ctx, deps) {
|
|
|
5030
5105
|
limits: limitsFor(model.id) ?? null,
|
|
5031
5106
|
input: meta.input,
|
|
5032
5107
|
reasoning: meta.reasoning,
|
|
5033
|
-
visible: modelVisible(model.id, config)
|
|
5108
|
+
visible: modelVisible(model.id, config),
|
|
5109
|
+
...catalogReleaseDate("opencode-go-direct", model.id, config) === void 0 ? {} : { releaseDate: catalogReleaseDate(GO_PROVIDER, model.id, config) },
|
|
5110
|
+
isNew: isNewRelease(catalogReleaseDate(GO_PROVIDER, model.id, config))
|
|
5034
5111
|
};
|
|
5035
5112
|
});
|
|
5036
5113
|
} catch {
|