dsh-codex-subscription 1.14.0 → 1.14.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.en.md CHANGED
@@ -22,12 +22,15 @@ No OpenAI API key or Codex CLI. Models, search, quota, and image generation stay
22
22
  <img src="https://raw.githubusercontent.com/WSL043/dsh-codex-subscription/main/docs/assets/readme-hero-en.webp" width="900" alt="Your Codex subscription inside DSH: models, web search, quota and safe reset, image generation, and Fast mode">
23
23
  </p>
24
24
 
25
- ## What's included in 1.14.0
25
+ ## What's included in 1.14.2
26
26
 
27
- - Built-in image viewer: a bottom toolbar, wheel zoom, drag to pan, on-image notes, copy notes, and continue editing, without another plugin.
27
+ - GPT-6 Astra now supports model-catalog context settings: Standard preserves the catalog default, Extended uses 872K, and Custom is bounded to 128K–872K.
28
28
 
29
- - Fixes generated-image previews that could not open in DSH Web, so generated images can be viewed in the conversation again.
30
- - New and edited images now return the exact original path on the current DSH host in the tool result, so a model or Agent can read or copy the file; preview and download remain session-authorized.
29
+ ### From 1.14.1
30
+
31
+ - The composer shows both 5-hour and weekly quota windows when returned by the service, so neither window is hidden.
32
+ - Image edits validate attachment references, normalize bare SHA-256 digests, and resolve the actual attachment metadata from the current session. Paths produce an actionable error asking the model to obtain the correct reference with `read_image` and retry.
33
+ - Location edits attach the clean source, a numbered annotation image, and coordinate instructions so the model can match each requested change to its location.
31
34
 
32
35
  ## Three-step start
33
36
 
@@ -142,6 +145,10 @@ Restart DSH manually after installation, then:
142
145
 
143
146
  The plugin can follow an existing HTTPS proxy from the process environment or operating-system proxy settings for official OpenAI and ChatGPT requests. It does not provide a proxy, relay, node list, or system-proxy configuration.
144
147
 
148
+ ### GPT-6 Astra context
149
+
150
+ When the official model catalog exposes GPT-6 Astra, Standard preserves the catalog window, Extended uses 872000 tokens, and Custom accepts 128000–872000 tokens (initially 272000). This limit follows the [official Codex model catalog](https://github.com/openai/codex/blob/6af345407d9c2a568da9d01b6c4b81a9e61495c0/codex-rs/models-manager/models.json#L33-L34), not the API model's total context capacity. These settings only adjust DSH's local context budget; they do not grant model access or guarantee an account's server-side capacity. Actual availability remains subject to the service.
151
+
145
152
  ### Composer quota
146
153
 
147
154
  <p align="center">
@@ -149,8 +156,8 @@ The plugin can follow an existing HTTPS proxy from the process environment or op
149
156
  </p>
150
157
 
151
158
  Choose Off, Percent, Progress bar, or Beta Runway in Settings. The compact display appears only for a selected Codex model. Runway is opt-in and estimates pace only from official remaining-percentage observations. It needs at least three samples; sustained high use usually produces a range in 5–10 minutes, while low use takes longer or reports a stable state. Non-sensitive observations from the last 24 hours are kept locally so calibration can continue after restart; a quota reset, account switch, or disabling the feature starts a new calibration period.
152
- Standard Codex uses the lowest remaining window returned by the service; Spark uses its independent quota. The plugin does not hard-code a
153
- “5-hour + weekly” layout or invent Credits and spending caps that the service did not return.
159
+ The composer shows each returned quota window with its duration. When Plus returns both five-hour and weekly limits, both are visible. Spark keeps its independent quota. Accounts with only a weekly window still show only that window; the plugin does not invent a
160
+ five-hour limit, Credits, or spending caps that the service did not return.
154
161
 
155
162
  ### Safe quota reset
156
163
 
@@ -162,11 +169,11 @@ clicks are single-flight, and an uncertain network result is never retried autom
162
169
 
163
170
  ### Image generation and editing (Beta)
164
171
 
165
- A basic viewer derived from `dsh-image-viewer` is now built in, with no extra installation required. If `dsh-image-viewer` is installed, its unified preview still takes priority; otherwise, or if it declines, the built-in viewer opens. You can zoom, pan, fit, add region notes, and download the image. The standard **Download** action retrieves the permission- and integrity-checked exact original by default; only legacy sessions without an exact original fall back to the conversation preview.
172
+ A basic viewer derived from `dsh-image-viewer` is now built in, with no extra installation required. Plugin-generated image cards use the built-in viewer to keep annotation and continue-editing actions available. You can zoom, pan, fit, add region notes, and download the image. The standard **Download** action retrieves the permission- and integrity-checked exact original by default; only legacy sessions without an exact original fall back to the conversation preview.
166
173
 
167
174
  New and edited images return the exact original path on the current DSH host in the tool result, so a model or Agent can read or copy the file. The path is on the host running DSH, not a browser download link; original downloads remain session-authorized. Uninstalling the plugin does not delete generated originals.
168
175
 
169
- **Continue editing in composer** attaches exactly the image you opened and writes the region notes into the draft without sending it. Press **Enter** to save and collapse a region note; use **Shift+Enter** for a new line. Notes remain available when the same image is reopened during the current DSH page session.
176
+ **Continue editing in composer** does not send automatically. With annotations, it attaches the clean source and a numbered location-reference image, and includes matching numbers, coordinates, notes, and instructions to exclude the markers from the result. Without annotations, it attaches only the opened image. Every marker needs a note; reference preparation failures stop the handoff. Press **Enter** to save and collapse a region note; use **Shift+Enter** for a new line. Notes remain available when the same image is reopened during the current DSH page session.
170
177
 
171
178
  A new image request does not silently include earlier images. GPT Image 2 can take longer than a normal text turn, and detailed text, exact composition, or repeated-character consistency may still need another pass.
172
179
 
package/README.md CHANGED
@@ -23,12 +23,15 @@
23
23
  <img src="https://raw.githubusercontent.com/WSL043/dsh-codex-subscription/main/docs/assets/readme-hero.webp" width="900" alt="Codex 订阅直接用在 DSH:订阅模型、联网搜索、额度与安全重置、图片生成和高速模式">
24
24
  </p>
25
25
 
26
- ## 1.14.0 重点变化
26
+ ## 1.14.2 重点变化
27
27
 
28
- - 内置图片查看器:无需额外安装插件,即可使用底部工具栏、滚轮缩放、拖动、图上备注、复制备注和继续编辑。
28
+ - GPT-6 Astra 支持模型目录上下文设置:标准模式保留目录默认值,扩展模式使用 872K,自定义模式限制为 128K–872K。
29
29
 
30
- - 修复 DSH Web 中生成图片预览无法打开的问题,生成图片可以继续在会话中查看。
31
- - 新生成或编辑的图片会在工具结果中返回当前 DSH 主机上的原图路径,方便模型或 Agent 读取或复制;预览和下载仍按会话权限处理。
30
+ ### 1.14.1 更新回顾
31
+
32
+ - 输入框同时显示服务端返回的 5 小时与每周额度,避免其中一个额度窗口被隐藏。
33
+ - 图片编辑会检查附件引用,自动补全裸 SHA-256,并从当前会话取回真实附件元数据;误填路径时提示模型通过 `read_image` 获取正确引用并重试。
34
+ - 部位修改会同时附上干净原图、编号标注图和坐标说明,让模型能够对应每条修改要求。
32
35
 
33
36
  ## 三步开始
34
37
 
@@ -143,14 +146,18 @@ dsh --profile web --dump-config
143
146
 
144
147
  对于 OpenAI 与 ChatGPT 官方请求,插件可以沿用进程环境或操作系统中已有的 HTTPS 代理。插件本身不提供代理、转发服务、节点列表,也不会修改系统代理设置。
145
148
 
149
+ ### GPT-6 Astra 上下文
150
+
151
+ 当官方模型目录提供 GPT-6 Astra 时,标准模式保留目录默认窗口;扩展模式使用 872000 Token,自定义模式可设置 128000–872000 Token(初始值为 272000)。该上限依据 [Codex 官方模型目录](https://github.com/openai/codex/blob/6af345407d9c2a568da9d01b6c4b81a9e61495c0/codex-rs/models-manager/models.json#L33-L34),不是 API 模型的总上下文容量。这些设置只调整 DSH 的本地上下文预算,不授予模型访问权限,也不保证账号的服务端容量;实际可用性以服务端为准。
152
+
146
153
  ### 输入框额度
147
154
 
148
155
  <p align="center">
149
156
  <img src="https://raw.githubusercontent.com/WSL043/dsh-codex-subscription/main/docs/assets/composer-quota.png" width="800" alt="中文 DSH 输入框内的 Codex 剩余额度进度条">
150
157
  </p>
151
158
 
152
- 可在设置中选择关闭、百分比、进度条或 Beta 续航预测;紧凑额度只在选择 Codex 模型时显示。续航预测仅在用户主动选择后,根据官方剩余百分比估算当前消耗速度。它至少需要 3 个样本;持续高消耗时通常 5–10 分钟即可给出范围,消耗较低时会延长观察或显示稳定。最近 24 小时的无敏感信息观测会保存在本机,重启后可以继续校准;额度重置、账号切换或关闭功能会开启新的校准周期。普通 Codex 使用服务端返回窗口中剩余最少的一项,
153
- Spark 使用独立额度。插件不会写死“5 小时 + 每周”,也不会虚构服务端没有返回的 Credits 或消费上限。
159
+ 可在设置中选择关闭、百分比、进度条或 Beta 续航预测;紧凑额度只在选择 Codex 模型时显示。续航预测仅在用户主动选择后,根据官方剩余百分比估算当前消耗速度。它至少需要 3 个样本;持续高消耗时通常 5–10 分钟即可给出范围,消耗较低时会延长观察或显示稳定。最近 24 小时的无敏感信息观测会保存在本机,重启后可以继续校准;额度重置、账号切换或关闭功能会开启新的校准周期。输入框分别显示服务端返回的各额度窗口,并标明窗口时长:Plus 返回 5 小时和每周额度时,两项都会显示。
160
+ Spark 使用独立额度。只返回每周额度的账号仍只显示每周,不会虚构 5 小时窗口、Credits 或消费上限。
154
161
 
155
162
  ### 安全使用额度重置
156
163
 
@@ -160,11 +167,11 @@ ChatGPT 返回可用重置卡时,设置页会把每张卡分别显示为紧凑
160
167
 
161
168
  ### 图片生成与编辑(Beta)
162
169
 
163
- 订阅插件已内置基于 `dsh-image-viewer` 的基础查看器,无需额外安装。若已安装 `dsh-image-viewer`,仍优先使用它的统一预览;没有该服务或其拒绝打开时,自动使用内置查看器。你可以缩放、拖动、适合窗口、添加区域备注并下载图片。标准“下载”默认获取经过权限与完整性校验的精确原图;旧会话没有精确原图时才下载会话预览图。
170
+ 订阅插件已内置基于 `dsh-image-viewer` 的基础查看器,无需额外安装。插件生成图片的工具卡片使用内置查看器,确保标注和继续编辑功能可用。你可以缩放、拖动、适合窗口、添加区域备注并下载图片。标准“下载”默认获取经过权限与完整性校验的精确原图;旧会话没有精确原图时才下载会话预览图。
164
171
 
165
172
  新生成或编辑的图片会在工具结果中返回当前 DSH 主机上的原图路径,模型或 Agent 可以读取或复制该文件。该路径位于运行 DSH 的主机,并非浏览器下载链接;原图下载仍按会话授权。卸载插件不会删除已生成的原图。
166
173
 
167
- 点击“在输入框中继续编辑”只附上当前打开的图片,并把部位标注写入草稿,不会自动发送。按 Enter 保存并收起备注,Shift+Enter 换行;在当前 DSH 页面重新打开同一张图片时,备注仍会保留。
174
+ 点击“在输入框中继续编辑”不会自动发送。有标注时会附上干净源图和带编号标记的定位参考图,草稿包含对应编号、坐标、备注,以及不得把标记绘入成品的说明;没有标注时只附上当前图片。每个标记必须填写备注,参考图准备失败时会中止回填。按 Enter 保存并收起备注,Shift+Enter 换行;在当前 DSH 页面重新打开同一张图片时,备注仍会保留。
168
175
 
169
176
  新的图片请求不会静默带入历史图片。GPT Image 2 可能比文本回复耗时更长,复杂文字、精确构图和连续角色一致性也可能需要再次调整。
170
177
 
package/lib/client.js CHANGED
@@ -49,14 +49,193 @@ window.__ModuleLoader__.load({
49
49
  const ForwardRef = /*#__PURE__*/ react.forwardRef(BoltIcon);
50
50
  //#endregion
51
51
  //#region src/image-edit.js
52
- function buildImageEditDraft({ prompt = "", annotations = [], translate }) {
52
+ const isRecord = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
53
+ const validCoordinate = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 1;
54
+ const cleanNote = (value) => typeof value === "string" ? value.trim() : "";
55
+ const coordinateError = (number) => {
56
+ const error = /* @__PURE__ */ new Error(`Annotation ${number} must have finite x and y coordinates between 0 and 1`);
57
+ error.code = "ANNOTATION_INVALID";
58
+ return error;
59
+ };
60
+ /**
61
+ * Validate the annotation contract shared by the draft builder and the
62
+ * reference-image renderer. Annotation numbers are their array positions so
63
+ * that they stay aligned with the pins shown to the user.
64
+ */
65
+ function normalizeImageEditAnnotations(annotations, { requireNotes = false } = {}) {
66
+ if (!Array.isArray(annotations)) throw new TypeError("annotations must be an array");
67
+ return annotations.map((annotation, index) => {
68
+ const number = index + 1;
69
+ const note = cleanNote(annotation?.note);
70
+ if (requireNotes && note === "") {
71
+ const error = /* @__PURE__ */ new Error(`Annotation ${number} is missing a note; describe what should change`);
72
+ error.code = "ANNOTATION_INVALID";
73
+ throw error;
74
+ }
75
+ if (!isRecord(annotation) || !validCoordinate(annotation.x) || !validCoordinate(annotation.y)) throw coordinateError(number);
76
+ return {
77
+ number,
78
+ x: annotation.x,
79
+ y: annotation.y,
80
+ note
81
+ };
82
+ });
83
+ }
84
+ const formatPercent = (value) => {
85
+ return `${Number((value * 100).toFixed(2))}%`;
86
+ };
87
+ const formatPixel = (value) => {
88
+ const rounded = Number(value.toFixed(2));
89
+ return String(rounded);
90
+ };
91
+ const withNames = (value, sourceName, referenceName) => String(value).replaceAll("{sourceName}", sourceName).replaceAll("{referenceName}", referenceName);
92
+ const positiveImageDimension = (value) => Number.isSafeInteger(value) && value > 0;
93
+ function buildImageEditDraft({ prompt = "", annotations = [], translate, width, height, sourceName = "source.png", referenceName = "annotated-reference.png" }) {
53
94
  if (typeof translate !== "function") throw new TypeError("translate must be a function");
54
- const notes = annotations.map((annotation, index) => ({
55
- number: index + 1,
56
- note: typeof annotation?.note === "string" ? annotation.note.trim() : ""
57
- })).filter((annotation) => annotation.note !== "").map((annotation) => String(annotation.number) + ". " + annotation.note);
58
- const base = prompt.trim() === "" ? translate("imageEditDefault") : prompt.trim();
59
- return notes.length === 0 ? base : base + "\n\n" + translate("imageRegionNotes") + "\n" + notes.join("\n");
95
+ const base = typeof prompt === "string" && prompt.trim() !== "" ? prompt.trim() : translate("imageEditDefault");
96
+ if (!Array.isArray(annotations)) throw new TypeError("annotations must be an array");
97
+ if (annotations.length === 0) return base;
98
+ const hasWidth = width !== void 0;
99
+ if (hasWidth !== (height !== void 0) || hasWidth && (!positiveImageDimension(width) || !positiveImageDimension(height))) throw new Error("width and height must be positive integers when provided");
100
+ const normalized = normalizeImageEditAnnotations(annotations, { requireNotes: true });
101
+ const source = typeof sourceName === "string" && sourceName.trim() !== "" ? sourceName.trim() : "source.png";
102
+ const reference = typeof referenceName === "string" && referenceName.trim() !== "" ? referenceName.trim() : "annotated-reference.png";
103
+ const guide = withNames(translate("imageEditReferenceGuide"), source, reference);
104
+ const location = translate("imageEditLocation");
105
+ const notes = normalized.map(({ number, x, y, note }) => {
106
+ const pixels = hasWidth ? ` (pixel x=${formatPixel(x * Math.max(0, width - 1))} of ${width}, y=${formatPixel(y * Math.max(0, height - 1))} of ${height})` : "";
107
+ return `${number}. ${location}: x=${formatPercent(x)} (normalized ${x}), y=${formatPercent(y)} (normalized ${y})${pixels}; ${note}`;
108
+ });
109
+ return [
110
+ base,
111
+ "",
112
+ guide,
113
+ "",
114
+ translate("imageRegionNotes"),
115
+ ...notes
116
+ ].join("\n");
117
+ }
118
+ //#endregion
119
+ //#region src/image-edit-reference.js
120
+ const TWO_PI = Math.PI * 2;
121
+ const PIN_FILL = "#e11d48";
122
+ const PIN_OUTER_STROKE = "#111827";
123
+ const PIN_INNER_STROKE = "#ffffff";
124
+ const finiteDimension = (value) => Number.isSafeInteger(value) && value > 0;
125
+ const clamp$1 = (value, min, max) => Math.min(max, Math.max(min, value));
126
+ const clampCenter = (value, size, edge) => {
127
+ const margin = Math.min(edge, size / 2);
128
+ return clamp$1(value, margin, size - margin);
129
+ };
130
+ function getBitmapFactory(options) {
131
+ const factory = options?.createImageBitmap ?? options?.bitmapFactory ?? globalThis.createImageBitmap;
132
+ if (typeof factory !== "function") throw new Error("createImageBitmap is not available");
133
+ return factory;
134
+ }
135
+ function getCanvasFactory(options) {
136
+ const injected = options?.createCanvas ?? options?.canvasFactory;
137
+ if (typeof injected === "function") return injected;
138
+ const document = globalThis.document;
139
+ if (document !== void 0 && typeof document.createElement === "function") return (width, height) => {
140
+ const canvas = document.createElement("canvas");
141
+ canvas.width = width;
142
+ canvas.height = height;
143
+ return canvas;
144
+ };
145
+ throw new Error("A canvas factory is not available");
146
+ }
147
+ function drawPin(context, number, x, y, width, height) {
148
+ const label = String(number);
149
+ const fontSize = Math.max(12, Math.min(40, Math.round(Math.min(width, height) * .03)));
150
+ const radius = Math.max(12, fontSize * .8, fontSize * (.3 * label.length + .35));
151
+ const outerStroke = Math.max(2, Math.min(4, radius * .25));
152
+ const innerStroke = Math.max(1, Math.min(2, radius * .13));
153
+ const edge = radius + outerStroke + 2;
154
+ const targetX = x * Math.max(0, width - 1);
155
+ const targetY = y * Math.max(0, height - 1);
156
+ const centerX = clampCenter(targetX, width, edge);
157
+ const centerY = clampCenter(targetY, height, edge);
158
+ context.save?.();
159
+ if ((centerX !== targetX || centerY !== targetY) && typeof context.moveTo === "function" && typeof context.lineTo === "function") {
160
+ context.beginPath();
161
+ context.moveTo(targetX, targetY);
162
+ context.lineTo(centerX, centerY);
163
+ context.lineWidth = outerStroke * 2;
164
+ context.strokeStyle = PIN_OUTER_STROKE;
165
+ context.stroke();
166
+ context.beginPath();
167
+ context.moveTo(targetX, targetY);
168
+ context.lineTo(centerX, centerY);
169
+ context.lineWidth = innerStroke;
170
+ context.strokeStyle = PIN_INNER_STROKE;
171
+ context.stroke();
172
+ }
173
+ context.beginPath();
174
+ context.arc(centerX, centerY, radius, 0, TWO_PI);
175
+ context.fillStyle = PIN_FILL;
176
+ context.fill();
177
+ context.lineWidth = outerStroke;
178
+ context.strokeStyle = PIN_OUTER_STROKE;
179
+ context.stroke();
180
+ context.lineWidth = innerStroke;
181
+ context.strokeStyle = PIN_INNER_STROKE;
182
+ context.stroke();
183
+ context.font = `700 ${fontSize}px sans-serif`;
184
+ context.fillStyle = PIN_INNER_STROKE;
185
+ context.textAlign = "center";
186
+ context.textBaseline = "middle";
187
+ context.fillText(label, centerX, centerY);
188
+ context.restore?.();
189
+ }
190
+ function encodePng(canvas) {
191
+ if (typeof canvas?.toBlob !== "function") throw new Error("Canvas PNG encoding is not available");
192
+ return new Promise((resolve, reject) => {
193
+ let settled = false;
194
+ const finish = (callback, value) => {
195
+ if (settled) return;
196
+ settled = true;
197
+ callback(value);
198
+ };
199
+ try {
200
+ canvas.toBlob((value) => {
201
+ if (value === null || value === void 0) finish(reject, /* @__PURE__ */ new Error("Canvas failed to encode the annotation reference as PNG"));
202
+ else finish(resolve, value);
203
+ }, "image/png");
204
+ } catch (error) {
205
+ finish(reject, error);
206
+ }
207
+ });
208
+ }
209
+ /**
210
+ * Create the second image sent with an annotated edit. It contains the clean
211
+ * source plus numbered pins, with no note text. `options` is intentionally
212
+ * injectable so the rendering path can be exercised without a browser:
213
+ * `{ createImageBitmap, createCanvas }`.
214
+ */
215
+ async function createAnnotatedImageReference(blob, annotations, options = {}) {
216
+ const normalized = normalizeImageEditAnnotations(annotations);
217
+ const createImageBitmap = getBitmapFactory(options);
218
+ const createCanvas = getCanvasFactory(options);
219
+ const bitmap = await createImageBitmap(blob);
220
+ try {
221
+ const width = bitmap?.width;
222
+ const height = bitmap?.height;
223
+ if (!finiteDimension(width) || !finiteDimension(height)) throw new Error("The source image has invalid dimensions");
224
+ if (Math.min(width, height) < 64) throw new Error("The source image is too small for readable annotation pins");
225
+ const canvas = await createCanvas(width, height);
226
+ if (canvas === null || canvas === void 0) throw new Error("Canvas factory returned no canvas");
227
+ canvas.width = width;
228
+ canvas.height = height;
229
+ const context = canvas.getContext?.("2d");
230
+ if (context === null || context === void 0) throw new Error("Canvas 2D context is not available");
231
+ context.clearRect?.(0, 0, width, height);
232
+ context.drawImage?.(bitmap, 0, 0, width, height);
233
+ if (typeof context.drawImage !== "function") throw new Error("Canvas 2D context cannot draw the source image");
234
+ for (const annotation of normalized) drawPin(context, annotation.number, annotation.x, annotation.y, width, height);
235
+ return await encodePng(canvas);
236
+ } finally {
237
+ if (typeof bitmap?.close === "function") bitmap.close();
238
+ }
60
239
  }
61
240
  const ORIGINAL_IMAGE_ID_PATTERN = /^img_[0-9a-f]{32}$/u;
62
241
  const positiveInteger = (value) => Number.isSafeInteger(value) && value > 0;
@@ -723,9 +902,8 @@ window.__ModuleLoader__.load({
723
902
  /**
724
903
  * Local image viewer state for subscription-generated images.
725
904
  *
726
- * This deliberately stays private to the subscription client. An installed
727
- * native image viewer can take the request first, while this service provides
728
- * the same baseline experience when that optional plugin is absent.
905
+ * This stays private to subscription image cards, which need annotation and
906
+ * edit actions that a host's generic native viewer may not implement.
729
907
  */
730
908
  var SubscriptionImageViewerService = class {
731
909
  #listeners = /* @__PURE__ */ new Set();
@@ -809,19 +987,22 @@ window.__ModuleLoader__.load({
809
987
  "gpt-5.4": "customContextGpt54",
810
988
  "gpt-5.4-mini": "customContextGpt54Mini",
811
989
  "gpt-5.5": "customContextGpt55",
812
- "gpt-5.6": "customContextGpt56"
990
+ "gpt-5.6": "customContextGpt56",
991
+ "gpt-6-astra": "customContextGpt6Astra"
813
992
  });
814
993
  const CUSTOM_CONTEXT_MODEL_CAPS = Object.freeze({
815
994
  "gpt-5.4": 1e6,
816
995
  "gpt-5.4-mini": 4e5,
817
996
  "gpt-5.5": 1e6,
818
- "gpt-5.6": 1e6
997
+ "gpt-5.6": 1e6,
998
+ "gpt-6-astra": 872e3
819
999
  });
820
1000
  const CUSTOM_CONTEXT_MODEL_DEFAULTS = Object.freeze({
821
1001
  "gpt-5.4": 272e3,
822
1002
  "gpt-5.4-mini": 272e3,
823
1003
  "gpt-5.5": 272e3,
824
- "gpt-5.6": 272e3
1004
+ "gpt-5.6": 272e3,
1005
+ "gpt-6-astra": 272e3
825
1006
  });
826
1007
  const normalizeSearchProvider = (value) => [
827
1008
  "auto",
@@ -865,16 +1046,13 @@ window.__ModuleLoader__.load({
865
1046
  if (/\bspark\b/u.test(normalized(model))) return /\bspark\b/u.test(normalized(`${limit?.id ?? ""} ${limit?.name ?? ""}`));
866
1047
  return limit?.id === "codex";
867
1048
  };
868
- function selectModelQuota(usage, model) {
869
- const windows = Array.isArray(usage?.rateLimits) ? usage.rateLimits.filter((limit) => limitMatchesModel(limit, model) && Array.isArray(limit.windows)).flatMap((limit) => limit.windows).filter(isDisplayableWindow) : [];
870
- if (windows.length === 0) return void 0;
871
- const selected = windows.reduce((lowest, candidate) => candidate.remainingPercent < lowest.remainingPercent ? candidate : lowest);
872
- return {
1049
+ function selectModelQuotaWindows(usage, model) {
1050
+ return (Array.isArray(usage?.rateLimits) ? usage.rateLimits.filter((limit) => limitMatchesModel(limit, model) && Array.isArray(limit.windows)).flatMap((limit) => limit.windows).filter(isDisplayableWindow) : []).map((selected) => ({
873
1051
  remainingPercent: selected.remainingPercent,
874
1052
  windowSeconds: selected.windowSeconds,
875
1053
  ...Number.isSafeInteger(selected.resetsAt) ? { resetsAt: selected.resetsAt } : {},
876
1054
  ...selected.forecast === void 0 ? {} : { forecast: selected.forecast }
877
- };
1055
+ })).sort((a, b) => a.windowSeconds - b.windowSeconds);
878
1056
  }
879
1057
  //#endregion
880
1058
  //#region src/login-progress.js
@@ -1078,6 +1256,8 @@ window.__ModuleLoader__.load({
1078
1256
  const QUICK_QUOTA_REFRESH_EVENT = "dsh-codex-subscription:refresh-quick-quota";
1079
1257
  const QUICK_QUOTA_REFRESH_MS = 6e4;
1080
1258
  const zh = {
1259
+ imageEditLocation: "位置",
1260
+ imageEditReferenceGuide: "本次编辑的干净源图为「{sourceName}」,编号定位参考图为「{referenceName}」。坐标以图片左上角为原点,x 向右、y 向下,百分比相对于整张图片。请查看这两张图片,将它们同时作为编辑工具的参考图,并在工具提示词中完整保留下方编号、位置和修改要求。只修改源图中对应位置的内容;定位参考图上的编号、圆点和引线仅用于定位,不得绘入最终结果。若无法读取两张图片或确定位置,请说明问题,不要猜测或忽略标注。",
1081
1261
  nav: "Codex 订阅",
1082
1262
  title: "Codex 订阅",
1083
1263
  connected: "已登录",
@@ -1184,7 +1364,7 @@ window.__ModuleLoader__.load({
1184
1364
  contextStandard: "标准",
1185
1365
  contextStandardHint: "使用模型目录默认值;官方 Agent 预设会自动管理上下文。",
1186
1366
  contextExtended: "扩展",
1187
- contextExtendedHint: "按模型使用 400K 或 1M;超过 272K 后可能消耗更多额度。",
1367
+ contextExtendedHint: "按模型使用已审核的扩展预算(Astra:872K);实际可用性由服务端决定。",
1188
1368
  contextCustom: "自定义",
1189
1369
  contextCustomHint: "输入完整 Token 数值;较低数值会让官方 Agent 预设更早压缩上下文。",
1190
1370
  contextTokens: "Token 上限",
@@ -1267,10 +1447,12 @@ window.__ModuleLoader__.load({
1267
1447
  imageRegionNotes: "部位修改:",
1268
1448
  imageEdit: "在输入框中继续编辑",
1269
1449
  imageEditPreparing: "正在添加到输入框…",
1270
- imageEditFailed: "无法把图片添加到输入框。",
1450
+ imageEditFailed: "回填失败:请填写每个标记的备注,并确认输入框可接收图片后重试。",
1271
1451
  imageRemoveAnnotation: "删除标注"
1272
1452
  };
1273
1453
  const en = {
1454
+ imageEditLocation: "Location",
1455
+ imageEditReferenceGuide: "The clean source for this edit is \"{sourceName}\"; \"{referenceName}\" is the numbered location reference. Coordinates start at the top-left, x increases rightward and y downward; percentages refer to the whole image. Inspect both images and pass both as references to the image-editing tool. Preserve every number, position and requested change below in the tool prompt. Edit the corresponding content in the clean source; numbers, dots and leader lines on the location reference are guidance only and must not appear in the final result. If either image cannot be read or a location is unclear, explain the problem instead of guessing or ignoring annotations.",
1274
1456
  nav: "Codex",
1275
1457
  title: "Codex subscription",
1276
1458
  connected: "Signed in",
@@ -1377,7 +1559,7 @@ window.__ModuleLoader__.load({
1377
1559
  contextStandard: "Standard",
1378
1560
  contextStandardHint: "Use the model catalog default; official agent presets manage context automatically.",
1379
1561
  contextExtended: "Extended",
1380
- contextExtendedHint: "Uses 400K or 1M by model; usage above 272K may consume more quota.",
1562
+ contextExtendedHint: "Uses each model's audited extended budget (Astra: 872K); availability depends on the service.",
1381
1563
  contextCustom: "Custom",
1382
1564
  contextCustomHint: "Enter the full token count; lower values make official agent presets compact sooner.",
1383
1565
  contextTokens: "Token limit",
@@ -1460,7 +1642,7 @@ window.__ModuleLoader__.load({
1460
1642
  imageRegionNotes: "Region changes:",
1461
1643
  imageEdit: "Continue editing in composer",
1462
1644
  imageEditPreparing: "Adding to composer…",
1463
- imageEditFailed: "Could not add the image to the composer.",
1645
+ imageEditFailed: "Handoff failed. Add a note to every marker and ensure the composer accepts images, then retry.",
1464
1646
  imageRemoveAnnotation: "Remove note"
1465
1647
  };
1466
1648
  const STYLE = `
@@ -1497,7 +1679,7 @@ window.__ModuleLoader__.load({
1497
1679
  .codexSubscriptionCreditRows{display:flex;flex-direction:column;gap:6px}.codexSubscriptionResetMeta{display:flex;min-width:0;flex-direction:column;gap:1px}.codexSubscriptionResetBalance{display:flex;flex-direction:column;gap:8px}.codexSubscriptionResetCard{display:flex;align-items:center;justify-content:space-between;gap:10px;min-width:0;padding:9px 10px;border:1px solid var(--dsw-alias-border-l2);border-radius:9px;background:var(--dsw-alias-bg-module-platform)}.codexSubscriptionResetCard .codexSubscriptionResetMeta{flex:1}.codexSubscriptionResetCard strong{overflow:hidden;font-size:12px;line-height:18px;font-weight:500;text-overflow:ellipsis;white-space:nowrap}.codexSubscriptionResetCard .codexSubscriptionActions{flex:0 0 auto}.codexSubscriptionResetCard .codexSubscriptionResetUse{min-height:28px;padding:0 10px}.codexSubscriptionResetBalance .codexSubscriptionActions{justify-content:flex-start}.codexSubscriptionResetFlow{display:flex;flex-direction:column;gap:10px;border-top:1px solid var(--dsw-alias-border-l2);padding-top:10px}.codexSubscriptionResetFlow h4{margin:0;font-size:13px;line-height:20px;font-weight:500}.codexSubscriptionResetWarning{font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary)}.codexSubscriptionResetExpiry{font-size:11px;line-height:17px;color:var(--dsw-alias-label-tertiary)}.codexSubscriptionResetCheck{display:flex;align-items:flex-start;gap:8px;padding:9px 10px;border-radius:8px;background:var(--dsw-alias-bg-module-platform);font-size:12px;line-height:18px;color:var(--dsw-alias-label-primary);cursor:pointer}.codexSubscriptionResetCheck input{margin:3px 0 0;accent-color:var(--dsw-alias-label-primary)}.codexSubscriptionResetFinal{border-color:var(--dsw-alias-state-error-primary)!important;color:var(--dsw-alias-state-error-primary)!important}.codexSubscriptionResetResult{font-size:12px;line-height:18px;color:var(--dsw-alias-state-success-primary)}
1498
1680
  .codexSubscriptionResetUse:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.codexSubscriptionResetUse:focus-visible{outline:2px solid var(--dsw-alias-border-l3);outline-offset:1px}
1499
1681
  .codexSubscriptionSpendLimit{display:flex;flex-direction:column;gap:8px}.codexSubscriptionSpendTop{display:flex;align-items:baseline;justify-content:space-between;gap:12px}.codexSubscriptionSpendTop strong{font:600 16px/22px ui-monospace,SFMono-Regular,Consolas,monospace;font-variant-numeric:tabular-nums}.codexSubscriptionSpendLimit progress{width:100%;height:6px;border:0;border-radius:999px;overflow:hidden;background:var(--dsw-alias-border-l3);accent-color:var(--dsw-alias-brand-primary,#3964fe);-webkit-appearance:none;appearance:none}.codexSubscriptionSpendLimit progress::-webkit-progress-bar{background:var(--dsw-alias-border-l3);border-radius:999px}.codexSubscriptionSpendLimit progress::-webkit-progress-value{background:var(--dsw-alias-brand-primary,#3964fe);border-radius:999px}.codexSubscriptionSpendLimit progress::-moz-progress-bar{background:var(--dsw-alias-brand-primary,#3964fe);border-radius:999px}
1500
- .codexComposerQuota{display:inline-flex;align-items:center;flex:0 0 auto;height:28px;box-sizing:border-box;padding:0;color:var(--dsw-alias-label-secondary);font-family:inherit;font-size:12px;line-height:20px;font-weight:500;font-variant-numeric:tabular-nums;white-space:nowrap;user-select:none}.codexComposerQuotaBar{display:block;width:40px;height:4px;border:0;border-radius:999px;overflow:hidden;background:var(--dsw-alias-border-l3);accent-color:var(--dsw-alias-label-secondary);-webkit-appearance:none;appearance:none}.codexComposerQuotaBar::-webkit-progress-bar{background:var(--dsw-alias-border-l3);border-radius:999px}.codexComposerQuotaBar::-webkit-progress-value{background:var(--dsw-alias-label-secondary);border-radius:999px}.codexComposerQuotaBar::-moz-progress-bar{background:var(--dsw-alias-label-secondary);border-radius:999px}
1682
+ .codexComposerQuotaWindows{display:inline-flex;align-items:center;gap:10px;flex-wrap:wrap}.codexComposerQuota{display:inline-flex;align-items:center;gap:5px;flex:0 0 auto;height:28px;box-sizing:border-box;padding:0;color:var(--dsw-alias-label-secondary);font-family:inherit;font-size:12px;line-height:20px;font-weight:500;font-variant-numeric:tabular-nums;white-space:nowrap;user-select:none}.codexComposerQuotaBar{display:block;width:40px;height:4px;border:0;border-radius:999px;overflow:hidden;background:var(--dsw-alias-border-l3);accent-color:var(--dsw-alias-label-secondary);-webkit-appearance:none;appearance:none}.codexComposerQuotaBar::-webkit-progress-bar{background:var(--dsw-alias-border-l3);border-radius:999px}.codexComposerQuotaBar::-webkit-progress-value{background:var(--dsw-alias-label-secondary);border-radius:999px}.codexComposerQuotaBar::-moz-progress-bar{background:var(--dsw-alias-label-secondary);border-radius:999px}
1501
1683
  .codexModelSelect{position:relative;min-width:0}.codexModelSelectTrigger{display:flex;align-items:center;gap:4px;min-width:0;max-width:min(360px,45cqw);height:28px;padding:0 4px 0 8px;border:0;border-radius:24px;outline:0;background:transparent;color:var(--dsw-alias-label-secondary);font-size:13px;font-weight:500;line-height:20px;cursor:pointer}.codexModelSelectTrigger:hover:not(:disabled),.codexModelSelectTrigger[aria-expanded=true]{background:var(--dsw-alias-interactive-bg-hover)}.codexModelSelectTrigger:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-border-l3)}.codexModelSelectTrigger:disabled{color:var(--dsw-alias-label-dimmed);cursor:default}.codexModelSelectBolt{display:block;flex:none;width:14px;height:14px;color:var(--dsw-alias-label-primary)}.codexModelSelectLabel{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.codexModelSelectEffort{flex:none;color:var(--dsw-alias-label-caption)}.codexModelSelectChevron{flex:none;color:var(--dsw-alias-label-caption);transition:transform 120ms}.codexModelSelectTrigger[aria-expanded=true] .codexModelSelectChevron{transform:rotate(180deg)}
1502
1684
  .codexModelSelectMenu,.codexModelSelectSubmenu{position:absolute;z-index:30;box-sizing:border-box;width:max-content;min-width:min(240px,calc(100vw - 32px));max-width:min(420px,calc(100vw - 32px));max-height:min(360px,calc(100vh - 96px));padding:4px;border:1px solid var(--dsw-alias-border-inverted);border-radius:12px;background:var(--dsw-specific-menu);box-shadow:var(--dsw-shadow-lv3);color:var(--dsw-alias-label-primary);overflow:hidden}.codexModelSelectMenu{right:0;bottom:calc(100% + 8px)}.codexModelSelectSubmenu{right:calc(100% + 8px);bottom:0;min-width:min(230px,calc(100vw - 32px))}.codexModelSelectCell{display:flex;align-items:center;gap:8px;width:100%;min-width:100%;height:40px;box-sizing:border-box;padding:0 10px;border:0;border-radius:10px;background:transparent;color:inherit;font-size:14px;line-height:22px;text-align:left;cursor:pointer}.codexModelSelectCell:hover,.codexModelSelectCell:focus-visible,.codexModelSelectCell[data-open=true]{background:var(--dsw-alias-interactive-bg-hover);outline:0}.codexModelSelectCell:disabled{color:var(--dsw-alias-label-dimmed);cursor:default}.codexModelSelectCellLabel{flex:none;white-space:nowrap}.codexModelSelectCellValue{flex:auto;min-width:0;overflow:hidden;color:var(--dsw-alias-label-tertiary);text-align:right;text-overflow:ellipsis;white-space:nowrap}.codexModelSelectCellChevron{flex:none;color:var(--dsw-alias-label-tertiary)}.codexModelSelectGroups{min-height:0;max-height:352px;overflow-y:auto}.codexModelSelectGroup+.codexModelSelectGroup{margin-top:4px}.codexModelSelectGroupTitle{position:sticky;top:0;z-index:1;padding:5px 8px 3px;background:var(--dsw-specific-menu);color:var(--dsw-alias-label-tertiary);font-size:12px;font-weight:500;line-height:18px}.codexModelSelectOption{display:flex;align-items:center;gap:8px;width:100%;min-width:100%;min-height:38px;box-sizing:border-box;padding:6px 8px;border:0;border-radius:10px;outline:0;background:transparent;color:inherit;text-align:left;cursor:pointer}.codexModelSelectOption:hover:not(:disabled),.codexModelSelectOption:focus-visible{background:var(--dsw-alias-interactive-bg-hover)}.codexModelSelectOption:disabled{color:var(--dsw-alias-label-dimmed);cursor:default}.codexModelSelectOptionCopy{display:flex;flex:1;min-width:0;flex-direction:column}.codexModelSelectOptionName{overflow:hidden;color:inherit;font-size:14px;font-weight:500;line-height:20px;text-overflow:ellipsis;white-space:nowrap}.codexModelSelectOptionDescription{overflow:hidden;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px;text-overflow:ellipsis;white-space:nowrap}.codexModelSelectCheck{display:grid;place-items:center;flex:0 0 18px;color:var(--dsw-alias-label-primary)}.codexModelSelectStatus,.codexModelSelectEmpty{padding:10px;color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:20px}.codexModelSelectError,.codexModelSelectWarning{display:flex;align-items:flex-start;justify-content:space-between;gap:8px;margin-bottom:4px;padding:7px 8px;border-radius:8px;background:var(--dsw-alias-interactive-bg-hover-danger);color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:18px}.codexModelSelectWarning{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-state-warn-label)}.codexModelSelectRetry{flex:none;padding:0;border:0;background:transparent;color:inherit;font:inherit;font-weight:600;cursor:pointer}
1503
1685
  .codexModelSelectMenu{overflow:visible}
@@ -1649,18 +1831,27 @@ window.__ModuleLoader__.load({
1649
1831
  pendingLabel: t("imageEditPreparing"),
1650
1832
  errorLabel: t("imageEditFailed"),
1651
1833
  closeOnSuccess: true,
1652
- onInvoke: ({ annotations }) => attachForEdit(src, downloadName, buildImageEditDraft({
1653
- annotations,
1654
- translate: t
1655
- }))
1834
+ onInvoke: ({ annotations = [] }) => {
1835
+ const imageKey = String(attachment.attachmentId ?? "image").replace(/[^a-zA-Z0-9_-]/g, "_");
1836
+ const sourceName = annotations.length === 0 ? downloadName : `codex-edit-${imageKey}-source.png`;
1837
+ const referenceName = `codex-edit-${imageKey}-annotations.png`;
1838
+ return attachForEdit(src, sourceName, buildImageEditDraft({
1839
+ annotations,
1840
+ translate: t,
1841
+ width: attachment.width,
1842
+ height: attachment.height,
1843
+ sourceName,
1844
+ referenceName
1845
+ }), annotations, referenceName);
1846
+ }
1656
1847
  }]
1657
1848
  }],
1658
1849
  opener: triggerRef.current,
1659
1850
  source: "codex-generated",
1660
1851
  annotations: true
1661
1852
  };
1662
- if ((getImageViewer?.())?.open?.(request) === true) return;
1663
- getInternalImageViewer?.()?.open?.(request);
1853
+ if (getInternalImageViewer?.()?.open?.(request) === true) return;
1854
+ (getImageViewer?.())?.open?.(request);
1664
1855
  };
1665
1856
  if (error) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1666
1857
  type: "button",
@@ -1780,7 +1971,7 @@ window.__ModuleLoader__.load({
1780
1971
  return;
1781
1972
  }
1782
1973
  const usage = unwrap(await rpc.call(CHANNEL, "usage", { force: false }));
1783
- if (live) setQuota(selectModelQuota(usage, model));
1974
+ if (live) setQuota(selectModelQuotaWindows(usage, model));
1784
1975
  } catch {
1785
1976
  if (live) setQuota(void 0);
1786
1977
  } finally {
@@ -2053,8 +2244,19 @@ window.__ModuleLoader__.load({
2053
2244
  const codex = current?.provider === "openai-codex";
2054
2245
  const quotaEnabled = preferenceSnapshot.status === "ready" && preferenceSnapshot.quickQuotaMode !== "off" && codex;
2055
2246
  const forecastMode = preferenceSnapshot.quickQuotaMode === QUICK_QUOTA_MODE_FORECAST;
2056
- const quota = useQuickQuota(rpc, quotaEnabled, current?.model);
2057
- if (!quotaEnabled || quota === void 0) return null;
2247
+ const quotas = useQuickQuota(rpc, quotaEnabled, current?.model);
2248
+ if (!quotaEnabled || quotas === void 0 || quotas.length === 0) return null;
2249
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2250
+ className: "codexComposerQuotaWindows",
2251
+ children: quotas.map((quota, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CodexComposerQuotaWindow, {
2252
+ quota,
2253
+ mode: preferenceSnapshot.quickQuotaMode,
2254
+ forecastMode,
2255
+ t
2256
+ }, `${quota.windowSeconds}-${index}`))
2257
+ });
2258
+ }
2259
+ function CodexComposerQuotaWindow({ quota, mode, forecastMode, t }) {
2058
2260
  const value = Math.round(Number(quota.remainingPercent) * 10) / 10;
2059
2261
  const display = percent(value);
2060
2262
  const forecast = forecastMode ? quota.forecast : void 0;
@@ -2063,17 +2265,20 @@ window.__ModuleLoader__.load({
2063
2265
  value: display,
2064
2266
  duration
2065
2267
  });
2066
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2268
+ const content = mode === "bar" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("progress", {
2269
+ className: "codexComposerQuotaBar",
2270
+ max: 100,
2271
+ value,
2272
+ "aria-hidden": "true"
2273
+ }) : forecast?.status === "calibrating" ? `${display}% · ${t("quickQuotaForecastCalibrating")}` : forecast?.status === "idle" ? `${display}% · ${t("quickQuotaForecastIdle")}` : forecast?.status === "ready" && forecast.survivesReset ? `${display}% · ${t("quickQuotaForecastUntilReset")}` : forecastMode && duration !== void 0 ? `${display}% · ≈${duration}` : `${display}%`;
2274
+ const durationLabel = windowLabel(quota.windowSeconds, t);
2275
+ const accessibleLabel = `${durationLabel}: ${label}`;
2276
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2067
2277
  className: "codexComposerQuota",
2068
2278
  role: "status",
2069
- "aria-label": label,
2070
- title: label,
2071
- children: preferenceSnapshot.quickQuotaMode === "bar" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("progress", {
2072
- className: "codexComposerQuotaBar",
2073
- max: 100,
2074
- value,
2075
- "aria-hidden": "true"
2076
- }) : forecast?.status === "calibrating" ? `${display}% · ${t("quickQuotaForecastCalibrating")}` : forecast?.status === "idle" ? `${display}% · ${t("quickQuotaForecastIdle")}` : forecast?.status === "ready" && forecast.survivesReset ? `${display}% · ${t("quickQuotaForecastUntilReset")}` : forecastMode && duration !== void 0 ? `${display}% · ≈${duration}` : `${display}%`
2279
+ "aria-label": accessibleLabel,
2280
+ title: accessibleLabel,
2281
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: durationLabel }), content]
2077
2282
  });
2078
2283
  }
2079
2284
  function CodexModelSelect({ locked, available, directory, load, select, preference, t }) {
@@ -3358,13 +3563,18 @@ window.__ModuleLoader__.load({
3358
3563
  }
3359
3564
  },
3360
3565
  getInternalImageViewer: () => imageViewer,
3361
- attachForEdit: async (src, filename, draft) => {
3566
+ attachForEdit: async (src, filename, draft, annotations = [], referenceName) => {
3362
3567
  const actx = sessions.scope(sessionId);
3363
3568
  if (actx === void 0 || typeof conversation.createDraftImages !== "function" || conversation.input?.for === void 0) throw new Error("This DSH version does not provide the image composer bridge");
3364
3569
  const response = await fetch(src);
3365
3570
  if (!response.ok) throw new Error("Could not read generated image");
3366
3571
  const blob = await response.blob();
3367
- const created = conversation.createDraftImages([new File([blob], filename, { type: blob.type || "image/png" })]);
3572
+ const files = [new File([blob], filename, { type: blob.type || "image/png" })];
3573
+ if (annotations.length > 0) {
3574
+ const reference = await createAnnotatedImageReference(blob, annotations);
3575
+ files.push(new File([reference], referenceName, { type: "image/png" }));
3576
+ }
3577
+ const created = conversation.createDraftImages(files);
3368
3578
  const input = conversation.input.for(actx);
3369
3579
  if (!input.addImages(created.map((item) => item.id))) {
3370
3580
  conversation.releaseDraftImages(created);
package/lib/index.js CHANGED
@@ -1123,19 +1123,22 @@ const CUSTOM_CONTEXT_MODEL_FIELDS = Object.freeze({
1123
1123
  "gpt-5.4": "customContextGpt54",
1124
1124
  "gpt-5.4-mini": "customContextGpt54Mini",
1125
1125
  "gpt-5.5": "customContextGpt55",
1126
- "gpt-5.6": "customContextGpt56"
1126
+ "gpt-5.6": "customContextGpt56",
1127
+ "gpt-6-astra": "customContextGpt6Astra"
1127
1128
  });
1128
1129
  const CUSTOM_CONTEXT_MODEL_CAPS = Object.freeze({
1129
1130
  "gpt-5.4": 1e6,
1130
1131
  "gpt-5.4-mini": 4e5,
1131
1132
  "gpt-5.5": 1e6,
1132
- "gpt-5.6": 1e6
1133
+ "gpt-5.6": 1e6,
1134
+ "gpt-6-astra": 872e3
1133
1135
  });
1134
1136
  const CUSTOM_CONTEXT_MODEL_DEFAULTS = Object.freeze({
1135
1137
  "gpt-5.4": 272e3,
1136
1138
  "gpt-5.4-mini": 272e3,
1137
1139
  "gpt-5.5": 272e3,
1138
- "gpt-5.6": 272e3
1140
+ "gpt-5.6": 272e3,
1141
+ "gpt-6-astra": 272e3
1139
1142
  });
1140
1143
  const normalizeOutputVerbosity = (value) => [
1141
1144
  "default",
@@ -1213,7 +1216,8 @@ const EXTENDED_CONTEXT_WINDOWS = Object.freeze({
1213
1216
  "gpt-5.5": 1e6,
1214
1217
  "gpt-5.6-luna": 1e6,
1215
1218
  "gpt-5.6-sol": 1e6,
1216
- "gpt-5.6-terra": 1e6
1219
+ "gpt-5.6-terra": 1e6,
1220
+ "gpt-6-astra": CUSTOM_CONTEXT_MODEL_CAPS["gpt-6-astra"]
1217
1221
  });
1218
1222
  function openaiCodexSubscriptionProvider({ resolveSpeedMode = () => void 0, resolveOutputVerbosity = () => OUTPUT_VERBOSITY_DEFAULT, resolveContextMode = () => void 0, resolveCustomContextWindow = () => void 0, catalog, runNetwork = (_area, operation) => operation() } = {}) {
1219
1223
  const provider = createOpenAICodexProvider();
@@ -1265,10 +1269,13 @@ function openaiCodexSubscriptionProvider({ resolveSpeedMode = () => void 0, reso
1265
1269
  const maximum = EXTENDED_CONTEXT_WINDOWS[model.id];
1266
1270
  const mode = resolveContextMode();
1267
1271
  if (maximum === void 0 || !["extended", "custom"].includes(mode)) return model;
1268
- if (mode === "extended") return {
1269
- ...model,
1270
- contextWindow: Math.max(model.contextWindow, maximum)
1271
- };
1272
+ if (mode === "extended") {
1273
+ const contextWindow = model.id === "gpt-6-astra" ? maximum : Math.max(model.contextWindow, maximum);
1274
+ return {
1275
+ ...model,
1276
+ contextWindow
1277
+ };
1278
+ }
1272
1279
  const requested = normalizeCustomContextWindow(resolveCustomContextWindow(customContextModelKey(model.id)), maximum);
1273
1280
  return {
1274
1281
  ...model,
@@ -1303,7 +1310,7 @@ function openaiCodexSubscriptionProvider({ resolveSpeedMode = () => void 0, reso
1303
1310
  }
1304
1311
  //#endregion
1305
1312
  //#region src/version.js
1306
- const PACKAGE_VERSION = "1.14.0";
1313
+ const PACKAGE_VERSION = "1.14.2";
1307
1314
  const USER_AGENT = `dsh-codex-subscription/${PACKAGE_VERSION}`;
1308
1315
  //#endregion
1309
1316
  //#region src/model-catalog.js
@@ -1637,6 +1644,10 @@ const PNG_SIGNATURE = Buffer.from([
1637
1644
  26,
1638
1645
  10
1639
1646
  ]);
1647
+ const FULL_ATTACHMENT_ID = /^sha256:[0-9a-f]{64}$/u;
1648
+ const BARE_ATTACHMENT_DIGEST = /^[0-9a-f]{64}$/iu;
1649
+ const PATH_LIKE_ATTACHMENT_ID = /[\\/]/u;
1650
+ const FILE_NAME_ATTACHMENT_ID = /\.[A-Za-z0-9]{1,16}$/u;
1640
1651
  const record$2 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
1641
1652
  const nonEmpty = (value) => typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
1642
1653
  function normalizeImageOptions(args) {
@@ -1706,22 +1717,63 @@ function decodeCodexPng(value, maximumBytes) {
1706
1717
  return new Uint8Array(data);
1707
1718
  }
1708
1719
  function imageReference(value) {
1720
+ const originalDimensions = record$2(value.originalDimensions) ? {
1721
+ width: value.originalDimensions.width,
1722
+ height: value.originalDimensions.height
1723
+ } : void 0;
1709
1724
  return {
1710
1725
  attachmentId: value.attachmentId,
1711
1726
  mediaType: value.mediaType,
1712
1727
  bytes: value.bytes,
1713
1728
  width: value.width,
1714
1729
  height: value.height,
1715
- ...value.name === void 0 ? {} : { name: value.name }
1730
+ ...value.name === void 0 ? {} : { name: value.name },
1731
+ ...originalDimensions === void 0 ? {} : { originalDimensions }
1716
1732
  };
1717
1733
  }
1734
+ function normalizeAttachmentId(value) {
1735
+ if (typeof value !== "string") return void 0;
1736
+ if (FULL_ATTACHMENT_ID.test(value)) return value;
1737
+ const bare = BARE_ATTACHMENT_DIGEST.exec(value);
1738
+ return bare === null ? void 0 : `sha256:${bare[0].toLowerCase()}`;
1739
+ }
1740
+ function invalidAttachmentId(value) {
1741
+ const attachmentId = value?.attachmentId;
1742
+ if (typeof attachmentId === "string" && (PATH_LIKE_ATTACHMENT_ID.test(attachmentId) || FILE_NAME_ATTACHMENT_ID.test(attachmentId))) throw new Error("referenceImages attachmentId is a file path or filename; call read_image on that file and retry with its complete sha256:<64 lowercase hex> attachment reference. Do not omit referenceImages or fall back to new image generation.");
1743
+ throw new Error("referenceImages attachmentId must be sha256:<64 lowercase hex> copied from an image block or read_image result. Do not omit referenceImages or fall back to new image generation.");
1744
+ }
1718
1745
  function referenceOf(value, attachments) {
1719
- if (!record$2(value) || typeof value.attachmentId !== "string" || value.attachmentId.length === 0 || value.attachmentId.length > 256 || !attachments.imageLimits.mediaTypes.includes(value.mediaType) || !Number.isSafeInteger(value.bytes) || value.bytes <= 0 || !Number.isSafeInteger(value.width) || value.width <= 0 || !Number.isSafeInteger(value.height) || value.height <= 0 || value.name !== void 0 && (typeof value.name !== "string" || value.name.length > 256)) throw new Error("referenceImages contains an invalid image reference");
1720
- return imageReference(value);
1746
+ const attachmentId = normalizeAttachmentId(value?.attachmentId);
1747
+ if (attachmentId === void 0) invalidAttachmentId(value);
1748
+ if (!record$2(value) || !attachments.imageLimits.mediaTypes.includes(value.mediaType) || !Number.isSafeInteger(value.bytes) || value.bytes <= 0 || !Number.isSafeInteger(value.width) || value.width <= 0 || !Number.isSafeInteger(value.height) || value.height <= 0 || value.name !== void 0 && (typeof value.name !== "string" || value.name.length > 256) || value.originalDimensions !== void 0 && (!record$2(value.originalDimensions) || !Number.isSafeInteger(value.originalDimensions.width) || value.originalDimensions.width <= 0 || !Number.isSafeInteger(value.originalDimensions.height) || value.originalDimensions.height <= 0)) throw new Error("referenceImages contains an invalid image reference");
1749
+ return imageReference({
1750
+ ...value,
1751
+ attachmentId
1752
+ });
1753
+ }
1754
+ function sessionImageReferences(messages) {
1755
+ const references = /* @__PURE__ */ new Map();
1756
+ const visit = (content) => {
1757
+ if (!Array.isArray(content)) return;
1758
+ for (const block of content) if (block?.type === "image" && record$2(block.attachment)) {
1759
+ const id = normalizeAttachmentId(block.attachment.attachmentId);
1760
+ if (id !== void 0) references.set(id, block.attachment);
1761
+ } else if (block?.type === "tool-result") visit(block.content);
1762
+ };
1763
+ for (const message of messages ?? []) visit(message?.content);
1764
+ return references;
1721
1765
  }
1722
- async function editImages(values, attachments, signal) {
1766
+ async function editImages(values, attachments, signal, messages) {
1723
1767
  if (!Array.isArray(values) || values.length === 0 || values.length > MAX_REFERENCE_IMAGES) throw new Error(`referenceImages must contain between 1 and ${MAX_REFERENCE_IMAGES} images`);
1724
- const references = values.map((value) => referenceOf(value, attachments));
1768
+ const available = messages === void 0 ? void 0 : sessionImageReferences(messages);
1769
+ const references = values.map((value) => {
1770
+ const id = normalizeAttachmentId(value?.attachmentId);
1771
+ if (id === void 0) invalidAttachmentId(value);
1772
+ if (available === void 0) return referenceOf(value, attachments);
1773
+ const selected = available.get(id);
1774
+ if (selected === void 0) throw new Error("The selected image attachment cannot be found in the current session. Call read_image on the intended image and retry with its returned reference. Do not omit referenceImages or substitute another image.");
1775
+ return referenceOf(selected, attachments);
1776
+ });
1725
1777
  if (new Set(references.map((value) => value.attachmentId)).size !== references.length) throw new Error("referenceImages must not contain duplicates");
1726
1778
  const images = [];
1727
1779
  let totalBytes = 0;
@@ -1774,7 +1826,21 @@ function imageOutputSchema() {
1774
1826
  type: "integer",
1775
1827
  required: true
1776
1828
  },
1777
- name: { type: "string" }
1829
+ name: { type: "string" },
1830
+ originalDimensions: {
1831
+ type: "object",
1832
+ additionalProperties: false,
1833
+ properties: {
1834
+ width: {
1835
+ type: "integer",
1836
+ required: true
1837
+ },
1838
+ height: {
1839
+ type: "integer",
1840
+ required: true
1841
+ }
1842
+ }
1843
+ }
1778
1844
  }
1779
1845
  },
1780
1846
  original: {
@@ -1841,7 +1907,7 @@ function createCodexImageTool(options) {
1841
1907
  const attachments = options.attachments;
1842
1908
  return defineTool({
1843
1909
  name: CODEX_IMAGE_TOOL_NAME,
1844
- description: "Create a new image or explicitly edit selected prior images using the signed-in Codex subscription. Omit referenceImages for a completely new image. Include only the exact prior image references the user asked to edit; never assume every image in the conversation is a reference.",
1910
+ description: "Create a new image or explicitly edit selected prior images using the signed-in Codex subscription. Omit referenceImages only for a completely new image. For an edit, copy each complete image reference from the session image block or read_image result, including attachmentId in the exact form sha256:<64 lowercase hex>; never use a workspace path, absolute path, or filename, and do not omit the references to turn an edit into text-to-image generation. A bare 64-character hex digest is accepted and normalized to sha256:<64 lowercase hex>. If a reference is unavailable, call read_image and retry with its returned reference. Include only the exact prior image references the user asked to edit; never assume every image in the conversation is a reference. For annotation-guided edits, include both the named clean source and its numbered location reference, and preserve the numbered coordinates and requested changes in the prompt. The location-reference markers are guidance only and must not appear in the result. If those references cannot be identified, do not substitute unrelated images or silently ignore the annotations.",
1845
1911
  parameters: {
1846
1912
  prompt: {
1847
1913
  type: "string",
@@ -1873,14 +1939,15 @@ function createCodexImageTool(options) {
1873
1939
  },
1874
1940
  referenceImages: {
1875
1941
  type: "array",
1876
- description: "Optional explicit references to 1-5 prior images to edit. Omit for a new image.",
1942
+ description: "Optional explicit references to 1-5 prior images to edit. Copy each complete reference from the session image block or read_image result. Omit only for a new image; an invalid reference must be fixed and retried rather than omitted.",
1877
1943
  items: {
1878
1944
  type: "object",
1879
1945
  additionalProperties: false,
1880
1946
  properties: {
1881
1947
  attachmentId: {
1882
1948
  type: "string",
1883
- required: true
1949
+ required: true,
1950
+ description: "Copy the complete attachmentId from the image block or read_image result: sha256:<64 lowercase hex>. Never pass a workspace path, absolute path, or filename. A bare 64-character hex digest is accepted and normalized to sha256:<64 lowercase hex>."
1884
1951
  },
1885
1952
  mediaType: {
1886
1953
  type: "string",
@@ -1898,7 +1965,21 @@ function createCodexImageTool(options) {
1898
1965
  type: "integer",
1899
1966
  required: true
1900
1967
  },
1901
- name: { type: "string" }
1968
+ name: { type: "string" },
1969
+ originalDimensions: {
1970
+ type: "object",
1971
+ additionalProperties: false,
1972
+ properties: {
1973
+ width: {
1974
+ type: "integer",
1975
+ required: true
1976
+ },
1977
+ height: {
1978
+ type: "integer",
1979
+ required: true
1980
+ }
1981
+ }
1982
+ }
1902
1983
  }
1903
1984
  }
1904
1985
  }
@@ -1926,7 +2007,8 @@ function createCodexImageTool(options) {
1926
2007
  if (!attachments.imageLimits.mediaTypes.includes("image/png")) throw new Error("This DSH installation does not accept PNG image attachments");
1927
2008
  const maximumBytes = Math.min(attachments.imageLimits.maxImageBytes, attachments.imageLimits.maxMessageImageBytes);
1928
2009
  const editing = args.referenceImages !== void 0;
1929
- const images = editing ? await editImages(args.referenceImages, attachments, exec.signal) : void 0;
2010
+ const sessionMessages = editing && typeof options.getSessionMessages === "function" ? await options.getSessionMessages(exec.agent?.id) : void 0;
2011
+ const images = editing ? await editImages(args.referenceImages, attachments, exec.signal, sessionMessages ?? (options.getSessionMessages ? [] : void 0)) : void 0;
1930
2012
  let response;
1931
2013
  try {
1932
2014
  response = await fetchImage(editing ? CODEX_IMAGE_EDIT_URL : CODEX_IMAGE_GENERATION_URL, {
@@ -3304,6 +3386,7 @@ function apply(ctx) {
3304
3386
  getAuth: resolveAuth,
3305
3387
  readCredential: (options) => store.read(PROVIDER, options),
3306
3388
  attachments: ctx.attachments,
3389
+ getSessionMessages: (sessionId) => ctx.get?.("sessions")?.get?.(sessionId)?.deriveMessages?.() ?? [],
3307
3390
  originalImages,
3308
3391
  fetch: (input, init) => network.fetch("image", input, init)
3309
3392
  }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-codex-subscription",
3
- "version": "1.14.0",
3
+ "version": "1.14.2",
4
4
  "description": "Use ChatGPT and Codex subscriptions in DeepSeek Harness with OAuth, quota, safe resets, web search, images, and Fast mode",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",