dsh-neartoken 0.15.4 → 0.16.0

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.md CHANGED
@@ -68,6 +68,8 @@
68
68
  | `defaultContextWindow` | `200000` | 没有任何来源能说明某模型上下文窗口时上报的值 |
69
69
  | `modelOverrides` | `{}` | 按裸模型名逐条修正容量与推理等级,见下 |
70
70
  | `enabledModels` | `[]` | 模型白名单(裸模型名);空数组 = 全部显示。volatile,设置页直接写它 |
71
+ | `imageMaxEdge` | `2048` | 发送到上游前图片的长边上限(像素);更大的图按长边缩到它,见下「图片压缩」 |
72
+ | `imageMaxBytes` | `2097152` | 单张请求图的编码字节上限;超出走编码质量阶梯,必要时把 PNG 转成 JPEG / WebP |
71
73
  | `imageTimeoutMs` | `300000` | 单次生图的预算(毫秒);工具调用截止时间 = 它 + 30 秒,见下文「超时」 |
72
74
  | `imageGeneration` | `{ enabled: false, models: {} }` | 生图开关与「模型 → 分组」映射;volatile,设置页写 |
73
75
 
@@ -83,6 +85,17 @@
83
85
  reasoning: [low, medium, high, max]
84
86
  ```
85
87
 
88
+ ### 图片压缩
89
+
90
+ **图片在离开本机前先被重编码**,不是原图直传:请求前把每张图交给 harness 的附件服务(`attachments.readImageRequest`)按 `imageMaxEdge` / `imageMaxBytes` 生成一份请求版,sharp 缩放 + 质量阶梯重编码(有 alpha 走 WebP、无 alpha 走 JPEG),结果按「附件 + 目标尺寸 + 字节预算」缓存,同一张图重复发不重复编码。已经被上游视觉网格丢掉的分辨率不再上传,也就不再计费。
91
+
92
+ 实测(`ds-harness-remote/docs/images/remote.png`,2559×1639 PNG 951 KB):默认策略下变成 2048 长边 JPEG 219 KB,base64 后 292 KB,约为原图的 23%;把 `imageMaxEdge` 调到 768 且 `imageMaxBytes` 调到 300 KB 时降到 52 KB。
93
+
94
+ 两条边界:
95
+
96
+ - **原图已经在两个上限内时不重编码**(附件服务直接返回原字节),所以小图不会被无谓地转成 JPEG。
97
+ - **附件服务不支持请求版**(`ATTACHMENT_PROJECTION_UNSUPPORTED`,非本地存储后端的默认行为)时**退回原图发送**,请求不会因此失败;其它错误照旧让请求失败,不会静默降级。
98
+
86
99
  ## 模型可见性
87
100
 
88
101
  站点 `/api/pricing` 会列出它知道的所有模型,插件只提供会响应聊天请求的那部分:
@@ -190,7 +203,7 @@ Gemini 系走原生 surface 而不是 openai 兼容层:同模型同提示词
190
203
 
191
204
  ## 已知限制
192
205
 
193
- - 四套 wire(chat / anthropic / genai / responses)各自解析 SSE;**图片按各协议原样发送** —— chat 用 `image_url` part、anthropic 用 `source.base64`、genai 用 `inlineData`、responses 用 `input_image`。**工具结果里的图片也发**(`read_image` 的结果就是一段文本信封 + 一张图):anthropic 把图塞回同一个 `tool_result` 的 content 块数组;genai 在同一 user turn 的 parts 里补 `inlineData`;chat 的 tool 消息只能放字符串、responses 的 `function_call_output` 同理,所以图跟在**整段连续工具结果之后**的一条 user 消息 / 条目里(并行调用时不会把 tool 消息切断)。字节由 `attachments.readImage` 在请求前内联;没有 attachments 服务或读不出来会**让请求失败**,不再静默降级成纯文本(曾经就是这样:贴的截图模型根本收不到)。会话里被 image-offload 标记的图片(`offloaded: true`)不发字节;插件也不替它生成占位文本,所以那一次图片对模型完全不可见。文件块(非图片附件)仍不发;`/model` 弹窗仍是内置平铺列表;箭头键盘导航未接。
206
+ - 四套 wire(chat / anthropic / genai / responses)各自解析 SSE;**图片按各协议发送** —— chat 用 `image_url` part、anthropic 用 `source.base64`、genai 用 `inlineData`、responses 用 `input_image`。发送前先按 `imageMaxEdge` / `imageMaxBytes` 重编码(见「图片压缩」),所以线上字节与媒体类型未必等于附件里的原图。**工具结果里的图片也发**(`read_image` 的结果就是一段文本信封 + 一张图):anthropic 把图塞回同一个 `tool_result` 的 content 块数组;genai 在同一 user turn 的 parts 里补 `inlineData`;chat 的 tool 消息只能放字符串、responses 的 `function_call_output` 同理,所以图跟在**整段连续工具结果之后**的一条 user 消息 / 条目里(并行调用时不会把 tool 消息切断)。字节由 `attachments.readImage` 在请求前内联;没有 attachments 服务或读不出来会**让请求失败**,不再静默降级成纯文本(曾经就是这样:贴的截图模型根本收不到)。会话里被 image-offload 标记的图片(`offloaded: true`)不发字节;插件也不替它生成占位文本,所以那一次图片对模型完全不可见。文件块(非图片附件)仍不发;`/model` 弹窗仍是内置平铺列表;箭头键盘导航未接。
194
207
  - 四套 wire 的 SSE 解析都有「帧样本 → 期望 chunk 序列」回归测试(`src/host/wire-parse.spec.ts`),帧样本取自生产网关的真实响应。网关在内容帧上发 `"usage": null`,只有末帧带真值:任何一处把 `usage` 当对象直接读都会让整条流中途崩掉(曾导致 claude 模型全不可用)。
195
208
  - **每个请求都带会话亲和键**:chat 与 responses 发 `prompt_cache_key`,anthropic 发 `metadata.user_id`,取值都是 DSH 的 `GenerateOptions.sessionId`。站点就是靠这个值挑上游渠道的(New API 的「codex cli trace」规则:`^gpt-.*$` + `/v1/responses`,键取自 body 的 `prompt_cache_key`,取不到就把整条规则跳过),而提示词缓存按**上游账号**存 —— 不带键时每个请求随机换账号,缓存永远不中。实测同一 6k 前缀:不带键连发 4 次 `cached_tokens` 全 0;带键后第 2 次起 5888/6011 ≈ 98%。`/v1/chat/completions` 上目前没有对应规则,chat 线即使带键也只是偶发命中,要彻底解决需要站点加一条匹配该路径的亲和规则;anthropic 线的 `metadata.user_id` 同理(规则只作用于 `/v1/messages`,这一条已经匹配)。
196
209
  - **anthropic 线显式打缓存断点**:Anthropic 只在 `cache_control` 断点处写缓存,没有断点就整段 prompt 按新输入计价(亲和键只保证落在同一上游账号,不产生缓存)。每个请求打 4 个断点:`tools` 末项、`system` 末块、最后两条消息各自末块 —— harness 每轮全量重放历史,尾部双断点保证下一轮几乎全命中,每轮只写新增片段。实测(Claude Max 超惠分组,~6.3k token 前缀):无断点连发两次 `cache_read` 全 0;打点后第 2 次起 `in=4, cr=6316`,三轮增长历史 `cr` 累积到 10811、`in` 恒为个位数。断点也覆盖 `tool_result` 结尾的轮次(DSH 会话的常态形态)。
package/lib/index.js CHANGED
@@ -2136,6 +2136,18 @@ function sessionKeyOf(options) {
2136
2136
  * @module
2137
2137
  */
2138
2138
  /**
2139
+ * Default request-image bounds.
2140
+ *
2141
+ * A model reads a picture through a fixed visual grid, so bytes past roughly
2142
+ * 2048 pixels on the long edge buy no detail while still costing upload time and
2143
+ * base64 in the prompt. 2 MiB is the ceiling the harness's own DeepSeek routes
2144
+ * use for one encoded request image.
2145
+ */
2146
+ const DEFAULT_IMAGE_REQUEST_POLICY = {
2147
+ maxEdge: 2048,
2148
+ maxBytes: 2097152
2149
+ };
2150
+ /**
2139
2151
  * The images one block list carries, in message order.
2140
2152
  *
2141
2153
  * Message content is flat: a tool result keeps the blocks its tool rendered (a
@@ -2154,14 +2166,80 @@ function imagesIn(content, images) {
2154
2166
  return found;
2155
2167
  }
2156
2168
  /**
2169
+ * The request dimensions one stored image is projected onto.
2170
+ *
2171
+ * The long edge is what a visual encoder resamples by, so the short edge follows
2172
+ * it. An image already inside the bound keeps its own dimensions and the store
2173
+ * then decides from the byte ceiling whether it has to encode anything at all.
2174
+ * @param reference - the stored image's intrinsic dimensions.
2175
+ * @param policy - the configured bounds.
2176
+ * @returns integer dimensions no larger than the source, plus the byte ceiling.
2177
+ */
2178
+ function requestTargetOf(reference, policy) {
2179
+ const longEdge = Math.max(reference.width, reference.height);
2180
+ if (longEdge <= policy.maxEdge) return {
2181
+ width: reference.width,
2182
+ height: reference.height,
2183
+ maxBytes: policy.maxBytes
2184
+ };
2185
+ const scale = policy.maxEdge / longEdge;
2186
+ return {
2187
+ width: Math.max(1, Math.floor(reference.width * scale)),
2188
+ height: Math.max(1, Math.floor(reference.height * scale)),
2189
+ maxBytes: policy.maxBytes
2190
+ };
2191
+ }
2192
+ /**
2193
+ * Whether a store refused request-image projection itself.
2194
+ * @param error - value caught from `readImageRequest`.
2195
+ * @returns true for the attachment service's documented unsupported code.
2196
+ */
2197
+ function isProjectionUnsupported(error) {
2198
+ return typeof error === "object" && error !== null && error.code === "ATTACHMENT_PROJECTION_UNSUPPORTED";
2199
+ }
2200
+ /**
2201
+ * Read one image as the bytes this request should carry.
2202
+ *
2203
+ * The request version is preferred: the store downscales and re-encodes it
2204
+ * within the target, so a 4K screenshot does not travel as 8 MiB of base64. A
2205
+ * store without projection still has the normalized original, which is sent
2206
+ * unchanged rather than failing the request.
2207
+ * @param source - the attachment service.
2208
+ * @param reference - the durable reference carried by the message.
2209
+ * @param policy - the configured bounds.
2210
+ * @param signal - the request's cancellation.
2211
+ * @returns the bytes to inline and the media type they are.
2212
+ * @throws {LlmError} when the bytes cannot be read at all.
2213
+ */
2214
+ async function requestBytes(source, reference, policy, signal) {
2215
+ try {
2216
+ if (source.readImageRequest !== void 0) try {
2217
+ const version = await source.readImageRequest(reference, requestTargetOf(reference, policy), signal);
2218
+ return {
2219
+ data: version.data,
2220
+ mediaType: version.mediaType
2221
+ };
2222
+ } catch (error) {
2223
+ if (!isProjectionUnsupported(error)) throw error;
2224
+ }
2225
+ return {
2226
+ data: (await source.readImage(reference, signal)).data,
2227
+ mediaType: reference.mediaType
2228
+ };
2229
+ } catch (error) {
2230
+ throw new LlmError(`cannot read attachment "${reference.attachmentId}" for this request: ${error instanceof Error ? error.message : String(error)}`, "ATTACHMENT_UNREADABLE");
2231
+ }
2232
+ }
2233
+ /**
2157
2234
  * Collect every image a request's messages carry, inlined for the wires.
2158
2235
  * @param attachments - the attachment service, when the composition has one.
2159
2236
  * @param messages - the request's conversation messages.
2160
2237
  * @param signal - the request's cancellation.
2238
+ * @param policy - the request-image bounds every picture is encoded to.
2161
2239
  * @returns attachment id to base64 image; empty when the request carries none.
2162
2240
  * @throws {LlmError} when a message carries an image that cannot be inlined.
2163
2241
  */
2164
- async function inlineImages(attachments, messages, signal) {
2242
+ async function inlineImages(attachments, messages, signal, policy = DEFAULT_IMAGE_REQUEST_POLICY) {
2165
2243
  const references = [];
2166
2244
  for (const message of messages) for (const block of message.content) {
2167
2245
  if (typeof block !== "object" || block === null) continue;
@@ -2176,15 +2254,10 @@ async function inlineImages(attachments, messages, signal) {
2176
2254
  for (const reference of references) {
2177
2255
  const id = String(reference.attachmentId);
2178
2256
  if (images.has(id)) continue;
2179
- let data;
2180
- try {
2181
- data = (await attachments.readImage(reference, signal)).data;
2182
- } catch (error) {
2183
- throw new LlmError(`cannot read attachment "${id}" for this request: ${error instanceof Error ? error.message : String(error)}`, "ATTACHMENT_UNREADABLE");
2184
- }
2257
+ const bytes = await requestBytes(attachments, reference, policy, signal);
2185
2258
  images.set(id, {
2186
- mediaType: reference.mediaType,
2187
- data: Buffer.from(data).toString("base64")
2259
+ mediaType: bytes.mediaType,
2260
+ data: Buffer.from(bytes.data).toString("base64")
2188
2261
  });
2189
2262
  }
2190
2263
  return images;
@@ -4141,7 +4214,7 @@ var NearTokenAdapter = class extends LlmAdapter {
4141
4214
  const { model } = decodeRoute(options.model);
4142
4215
  const groupRef = (await this.deps.routes()).find((entry) => entry.id === options.model)?.groupRef;
4143
4216
  const wire = wireFor(model);
4144
- const images = await inlineImages(this.deps.attachments(), options.messages, options.signal);
4217
+ const images = await inlineImages(this.deps.attachments(), options.messages, options.signal, this.deps.imagePolicy());
4145
4218
  let response;
4146
4219
  try {
4147
4220
  response = await fetch(wire.url(this.deps.baseURL(), model), {
@@ -4402,7 +4475,8 @@ var NearTokenProvider = class {
4402
4475
  baseURL: this.deps.baseURL,
4403
4476
  defaultContextWindow: this.deps.defaultContextWindow,
4404
4477
  modelOverride: this.deps.modelOverride,
4405
- attachments: this.deps.attachments
4478
+ attachments: this.deps.attachments,
4479
+ imagePolicy: this.deps.imagePolicy
4406
4480
  });
4407
4481
  this.routes = this.ctx.llm.registerAdapter([this.deps.provider], adapter);
4408
4482
  this.directory = this.ctx.llm.registerConfigurableProviders([{
@@ -4909,6 +4983,20 @@ const Config = z.object({
4909
4983
  input: z.array(z.union([z.const("text"), z.const("image")]))
4910
4984
  })).default({}).volatile(),
4911
4985
  /**
4986
+ * Longest edge, in pixels, of one image on the wire. A model reads a picture
4987
+ * through a fixed visual grid, so detail past roughly 2048 pixels on the long
4988
+ * edge is billed and uploaded without being seen; a larger image is scaled down
4989
+ * to this bound before it is sent.
4990
+ */
4991
+ imageMaxEdge: z.number().step(1).min(1).default(2048),
4992
+ /**
4993
+ * Encoded-byte ceiling for one request image. An image already inside both
4994
+ * bounds travels unchanged; one that is not is re-encoded down through the
4995
+ * encoder's quality ladder until it fits, converting a screenshot PNG to JPEG
4996
+ * or WebP on the way.
4997
+ */
4998
+ imageMaxBytes: z.number().step(1).min(1024).default(2097152),
4999
+ /**
4912
5000
  * How long one picture may take. The site's image models routinely need
4913
5001
  * 25-60 seconds, and a discounted group measured 114 seconds on a good day, so
4914
5002
  * the default leaves room for that rather than sitting just above the median.
@@ -4961,7 +5049,11 @@ function apply(ctx, config) {
4961
5049
  ...page
4962
5050
  };
4963
5051
  },
4964
- attachments: () => ctx.get("attachments")
5052
+ attachments: () => ctx.get("attachments"),
5053
+ imagePolicy: () => ({
5054
+ maxEdge: config.imageMaxEdge,
5055
+ maxBytes: config.imageMaxBytes
5056
+ })
4965
5057
  });
4966
5058
  const whitelist = new NearTokenWhitelist({
4967
5059
  enabled: () => config.enabledModels.get(),
@@ -1,6 +1,7 @@
1
1
  import { LlmAdapter } from '@deepseek-ai/dsh-llm';
2
2
  import type { GenerateOptions, LlmModelInfo, LlmResolvedModelInfo, StreamChunk } from '@deepseek-ai/dsh-llm';
3
3
  import type { NearTokenRoute } from './pricing.ts';
4
+ import type { ImageRequestPolicy } from './wire-images.ts';
4
5
  import type { ImageSource } from './wire-images.ts';
5
6
  /** Display name of the single provider route this adapter owns. */
6
7
  export declare const NEARTOKEN_PROVIDER_NAME = "NearToken";
@@ -38,6 +39,8 @@ export interface NearTokenDeps {
38
39
  modelOverride: (model: string) => NearTokenModelOverride | undefined;
39
40
  /** The attachment service, which owns the bytes behind a message's images. */
40
41
  attachments: () => ImageSource | undefined;
42
+ /** The bounds every request image is re-encoded to before it is sent. */
43
+ imagePolicy: () => ImageRequestPolicy;
41
44
  }
42
45
  /**
43
46
  * Decode one catalog model id into its routing parts.
@@ -10,7 +10,7 @@
10
10
  */
11
11
  import type { Context } from '@deepseek-ai/cordis';
12
12
  import type { NearTokenModelOverride } from './adapter.ts';
13
- import type { ImageSource } from './wire-images.ts';
13
+ import type { ImageRequestPolicy, ImageSource } from './wire-images.ts';
14
14
  import type { NearTokenRoute } from './pricing.ts';
15
15
  /** External facts the route is built from. */
16
16
  export interface NearTokenProviderDeps {
@@ -34,6 +34,8 @@ export interface NearTokenProviderDeps {
34
34
  modelOverride: (model: string) => NearTokenModelOverride | undefined;
35
35
  /** The attachment service, when the composition provides one. */
36
36
  attachments: () => ImageSource | undefined;
37
+ /** The bounds every request image is re-encoded to before it is sent. */
38
+ imagePolicy: () => ImageRequestPolicy;
37
39
  }
38
40
  /** The NearToken provider route, mounted at most once per process. */
39
41
  export declare class NearTokenProvider {
@@ -8,6 +8,30 @@ export interface WireImage {
8
8
  }
9
9
  /** Attachment id to the inlined image, for one request. */
10
10
  export type WireImages = ReadonlyMap<string, WireImage>;
11
+ /** One durable image reference, as a message block carries it. */
12
+ export interface ImageReference {
13
+ readonly attachmentId: string;
14
+ readonly mediaType: string;
15
+ readonly bytes: number;
16
+ readonly width: number;
17
+ readonly height: number;
18
+ }
19
+ /** Bounds one request image is encoded to before any wire carries it. */
20
+ export interface ImageRequestPolicy {
21
+ /** Longest edge in pixels; a larger image is scaled down to it and never up. */
22
+ readonly maxEdge: number;
23
+ /** Encoded-byte ceiling for one image; the encoder keeps the smallest quality that fits. */
24
+ readonly maxBytes: number;
25
+ }
26
+ /**
27
+ * Default request-image bounds.
28
+ *
29
+ * A model reads a picture through a fixed visual grid, so bytes past roughly
30
+ * 2048 pixels on the long edge buy no detail while still costing upload time and
31
+ * base64 in the prompt. 2 MiB is the ceiling the harness's own DeepSeek routes
32
+ * use for one encoded request image.
33
+ */
34
+ export declare const DEFAULT_IMAGE_REQUEST_POLICY: ImageRequestPolicy;
11
35
  /** The slice of the attachment service this module reads. */
12
36
  export interface ImageSource {
13
37
  /**
@@ -16,14 +40,26 @@ export interface ImageSource {
16
40
  * @param signal - the request's cancellation.
17
41
  * @returns the stored bytes beside their reference.
18
42
  */
19
- readImage(attachment: {
20
- attachmentId: string;
21
- mediaType: string;
22
- bytes: number;
43
+ readImage(attachment: ImageReference, signal?: AbortSignal): Promise<{
44
+ data: Uint8Array;
45
+ }>;
46
+ /**
47
+ * Derive the request version of one stored image: scaled within the target and
48
+ * re-encoded under its byte ceiling. A store that cannot derive one rejects
49
+ * with code `ATTACHMENT_PROJECTION_UNSUPPORTED`, and the caller sends the
50
+ * normalized original instead.
51
+ * @param attachment - the durable reference carried by the message.
52
+ * @param target - integer dimensions and encoded-byte ceiling for this request.
53
+ * @param signal - the request's cancellation.
54
+ * @returns the request bytes and the media type they were encoded as.
55
+ */
56
+ readImageRequest?(attachment: ImageReference, target: {
23
57
  width: number;
24
58
  height: number;
59
+ maxBytes: number;
25
60
  }, signal?: AbortSignal): Promise<{
26
61
  data: Uint8Array;
62
+ mediaType: string;
27
63
  }>;
28
64
  }
29
65
  /**
@@ -42,10 +78,11 @@ export declare function imagesIn(content: readonly unknown[], images: WireImages
42
78
  * @param attachments - the attachment service, when the composition has one.
43
79
  * @param messages - the request's conversation messages.
44
80
  * @param signal - the request's cancellation.
81
+ * @param policy - the request-image bounds every picture is encoded to.
45
82
  * @returns attachment id to base64 image; empty when the request carries none.
46
83
  * @throws {LlmError} when a message carries an image that cannot be inlined.
47
84
  */
48
- export declare function inlineImages(attachments: ImageSource | undefined, messages: readonly RequestMessage[], signal?: AbortSignal): Promise<WireImages>;
85
+ export declare function inlineImages(attachments: ImageSource | undefined, messages: readonly RequestMessage[], signal?: AbortSignal, policy?: ImageRequestPolicy): Promise<WireImages>;
49
86
  /**
50
87
  * Find the inlined bytes behind one message block.
51
88
  * @param block - one content block, of any type.
@@ -25,6 +25,10 @@ export interface Options {
25
25
  enabledModels: Volatile<string[]>;
26
26
  /** Per-model meta overrides the settings page edits beside the whitelist. */
27
27
  modelMeta: Volatile<Record<string, NearTokenModelMeta>>;
28
+ /** Longest edge, in pixels, one request image keeps. */
29
+ imageMaxEdge: number;
30
+ /** Encoded-byte ceiling for one request image. */
31
+ imageMaxBytes: number;
28
32
  /** How long one image generation may take, in milliseconds. */
29
33
  imageTimeoutMs: number;
30
34
  /** The image-generation setting: the tool's switch and one group per enabled model. */
@@ -89,6 +93,20 @@ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
89
93
  maxTokens: z<number, number, "plain">;
90
94
  input: z<("text" | "image")[], ("text" | "image")[], "plain">;
91
95
  }>>, string>>, "volatile-defined">;
96
+ /**
97
+ * Longest edge, in pixels, of one image on the wire. A model reads a picture
98
+ * through a fixed visual grid, so detail past roughly 2048 pixels on the long
99
+ * edge is billed and uploaded without being seen; a larger image is scaled down
100
+ * to this bound before it is sent.
101
+ */
102
+ imageMaxEdge: z<number, number, "defined">;
103
+ /**
104
+ * Encoded-byte ceiling for one request image. An image already inside both
105
+ * bounds travels unchanged; one that is not is re-encoded down through the
106
+ * encoder's quality ladder until it fits, converting a screenshot PNG to JPEG
107
+ * or WebP on the way.
108
+ */
109
+ imageMaxBytes: z<number, number, "defined">;
92
110
  /**
93
111
  * How long one picture may take. The site's image models routinely need
94
112
  * 25-60 seconds, and a discounted group measured 114 seconds on a good day, so
@@ -168,6 +186,20 @@ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
168
186
  maxTokens: z<number, number, "plain">;
169
187
  input: z<("text" | "image")[], ("text" | "image")[], "plain">;
170
188
  }>>, string>>, "volatile-defined">;
189
+ /**
190
+ * Longest edge, in pixels, of one image on the wire. A model reads a picture
191
+ * through a fixed visual grid, so detail past roughly 2048 pixels on the long
192
+ * edge is billed and uploaded without being seen; a larger image is scaled down
193
+ * to this bound before it is sent.
194
+ */
195
+ imageMaxEdge: z<number, number, "defined">;
196
+ /**
197
+ * Encoded-byte ceiling for one request image. An image already inside both
198
+ * bounds travels unchanged; one that is not is re-encoded down through the
199
+ * encoder's quality ladder until it fits, converting a screenshot PNG to JPEG
200
+ * or WebP on the way.
201
+ */
202
+ imageMaxBytes: z<number, number, "defined">;
171
203
  /**
172
204
  * How long one picture may take. The site's image models routinely need
173
205
  * 25-60 seconds, and a discounted group measured 114 seconds on a good day, so
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-neartoken",
3
- "version": "0.15.4",
3
+ "version": "0.16.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },