dsh-codex-subscription 1.14.0 → 1.14.1
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 +8 -9
- package/README.md +8 -9
- package/lib/client.js +246 -39
- package/lib/index.js +88 -12
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -22,12 +22,11 @@ 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.
|
|
25
|
+
## What's included in 1.14.1
|
|
26
26
|
|
|
27
|
-
-
|
|
28
|
-
|
|
29
|
-
-
|
|
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.
|
|
27
|
+
- The composer shows both 5-hour and weekly quota windows when returned by the service, so neither window is hidden.
|
|
28
|
+
- 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.
|
|
29
|
+
- 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
30
|
|
|
32
31
|
## Three-step start
|
|
33
32
|
|
|
@@ -149,8 +148,8 @@ The plugin can follow an existing HTTPS proxy from the process environment or op
|
|
|
149
148
|
</p>
|
|
150
149
|
|
|
151
150
|
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
|
-
|
|
153
|
-
|
|
151
|
+
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
|
|
152
|
+
five-hour limit, Credits, or spending caps that the service did not return.
|
|
154
153
|
|
|
155
154
|
### Safe quota reset
|
|
156
155
|
|
|
@@ -162,11 +161,11 @@ clicks are single-flight, and an uncertain network result is never retried autom
|
|
|
162
161
|
|
|
163
162
|
### Image generation and editing (Beta)
|
|
164
163
|
|
|
165
|
-
A basic viewer derived from `dsh-image-viewer` is now built in, with no extra installation required.
|
|
164
|
+
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
165
|
|
|
167
166
|
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
167
|
|
|
169
|
-
**Continue editing in composer** attaches
|
|
168
|
+
**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
169
|
|
|
171
170
|
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
171
|
|
package/README.md
CHANGED
|
@@ -23,12 +23,11 @@
|
|
|
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.
|
|
26
|
+
## 1.14.1 重点变化
|
|
27
27
|
|
|
28
|
-
-
|
|
29
|
-
|
|
30
|
-
-
|
|
31
|
-
- 新生成或编辑的图片会在工具结果中返回当前 DSH 主机上的原图路径,方便模型或 Agent 读取或复制;预览和下载仍按会话权限处理。
|
|
28
|
+
- 输入框同时显示服务端返回的 5 小时与每周额度,避免其中一个额度窗口被隐藏。
|
|
29
|
+
- 图片编辑会检查附件引用,自动补全裸 SHA-256,并从当前会话取回真实附件元数据;误填路径时提示模型通过 `read_image` 获取正确引用并重试。
|
|
30
|
+
- 部位修改会同时附上干净原图、编号标注图和坐标说明,让模型能够对应每条修改要求。
|
|
32
31
|
|
|
33
32
|
## 三步开始
|
|
34
33
|
|
|
@@ -149,8 +148,8 @@ dsh --profile web --dump-config
|
|
|
149
148
|
<img src="https://raw.githubusercontent.com/WSL043/dsh-codex-subscription/main/docs/assets/composer-quota.png" width="800" alt="中文 DSH 输入框内的 Codex 剩余额度进度条">
|
|
150
149
|
</p>
|
|
151
150
|
|
|
152
|
-
可在设置中选择关闭、百分比、进度条或 Beta 续航预测;紧凑额度只在选择 Codex 模型时显示。续航预测仅在用户主动选择后,根据官方剩余百分比估算当前消耗速度。它至少需要 3 个样本;持续高消耗时通常 5–10 分钟即可给出范围,消耗较低时会延长观察或显示稳定。最近 24
|
|
153
|
-
Spark
|
|
151
|
+
可在设置中选择关闭、百分比、进度条或 Beta 续航预测;紧凑额度只在选择 Codex 模型时显示。续航预测仅在用户主动选择后,根据官方剩余百分比估算当前消耗速度。它至少需要 3 个样本;持续高消耗时通常 5–10 分钟即可给出范围,消耗较低时会延长观察或显示稳定。最近 24 小时的无敏感信息观测会保存在本机,重启后可以继续校准;额度重置、账号切换或关闭功能会开启新的校准周期。输入框分别显示服务端返回的各额度窗口,并标明窗口时长:Plus 返回 5 小时和每周额度时,两项都会显示。
|
|
152
|
+
Spark 使用独立额度。只返回每周额度的账号仍只显示每周,不会虚构 5 小时窗口、Credits 或消费上限。
|
|
154
153
|
|
|
155
154
|
### 安全使用额度重置
|
|
156
155
|
|
|
@@ -160,11 +159,11 @@ ChatGPT 返回可用重置卡时,设置页会把每张卡分别显示为紧凑
|
|
|
160
159
|
|
|
161
160
|
### 图片生成与编辑(Beta)
|
|
162
161
|
|
|
163
|
-
订阅插件已内置基于 `dsh-image-viewer`
|
|
162
|
+
订阅插件已内置基于 `dsh-image-viewer` 的基础查看器,无需额外安装。插件生成图片的工具卡片使用内置查看器,确保标注和继续编辑功能可用。你可以缩放、拖动、适合窗口、添加区域备注并下载图片。标准“下载”默认获取经过权限与完整性校验的精确原图;旧会话没有精确原图时才下载会话预览图。
|
|
164
163
|
|
|
165
164
|
新生成或编辑的图片会在工具结果中返回当前 DSH 主机上的原图路径,模型或 Agent 可以读取或复制该文件。该路径位于运行 DSH 的主机,并非浏览器下载链接;原图下载仍按会话授权。卸载插件不会删除已生成的原图。
|
|
166
165
|
|
|
167
|
-
|
|
166
|
+
点击“在输入框中继续编辑”不会自动发送。有标注时会附上干净源图和带编号标记的定位参考图,草稿包含对应编号、坐标、备注,以及不得把标记绘入成品的说明;没有标注时只附上当前图片。每个标记必须填写备注,参考图准备失败时会中止回填。按 Enter 保存并收起备注,Shift+Enter 换行;在当前 DSH 页面重新打开同一张图片时,备注仍会保留。
|
|
168
167
|
|
|
169
168
|
新的图片请求不会静默带入历史图片。GPT Image 2 可能比文本回复耗时更长,复杂文字、精确构图和连续角色一致性也可能需要再次调整。
|
|
170
169
|
|
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
|
-
|
|
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
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
|
727
|
-
*
|
|
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();
|
|
@@ -865,16 +1043,13 @@ window.__ModuleLoader__.load({
|
|
|
865
1043
|
if (/\bspark\b/u.test(normalized(model))) return /\bspark\b/u.test(normalized(`${limit?.id ?? ""} ${limit?.name ?? ""}`));
|
|
866
1044
|
return limit?.id === "codex";
|
|
867
1045
|
};
|
|
868
|
-
function
|
|
869
|
-
|
|
870
|
-
if (windows.length === 0) return void 0;
|
|
871
|
-
const selected = windows.reduce((lowest, candidate) => candidate.remainingPercent < lowest.remainingPercent ? candidate : lowest);
|
|
872
|
-
return {
|
|
1046
|
+
function selectModelQuotaWindows(usage, model) {
|
|
1047
|
+
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
1048
|
remainingPercent: selected.remainingPercent,
|
|
874
1049
|
windowSeconds: selected.windowSeconds,
|
|
875
1050
|
...Number.isSafeInteger(selected.resetsAt) ? { resetsAt: selected.resetsAt } : {},
|
|
876
1051
|
...selected.forecast === void 0 ? {} : { forecast: selected.forecast }
|
|
877
|
-
};
|
|
1052
|
+
})).sort((a, b) => a.windowSeconds - b.windowSeconds);
|
|
878
1053
|
}
|
|
879
1054
|
//#endregion
|
|
880
1055
|
//#region src/login-progress.js
|
|
@@ -1078,6 +1253,8 @@ window.__ModuleLoader__.load({
|
|
|
1078
1253
|
const QUICK_QUOTA_REFRESH_EVENT = "dsh-codex-subscription:refresh-quick-quota";
|
|
1079
1254
|
const QUICK_QUOTA_REFRESH_MS = 6e4;
|
|
1080
1255
|
const zh = {
|
|
1256
|
+
imageEditLocation: "位置",
|
|
1257
|
+
imageEditReferenceGuide: "本次编辑的干净源图为「{sourceName}」,编号定位参考图为「{referenceName}」。坐标以图片左上角为原点,x 向右、y 向下,百分比相对于整张图片。请查看这两张图片,将它们同时作为编辑工具的参考图,并在工具提示词中完整保留下方编号、位置和修改要求。只修改源图中对应位置的内容;定位参考图上的编号、圆点和引线仅用于定位,不得绘入最终结果。若无法读取两张图片或确定位置,请说明问题,不要猜测或忽略标注。",
|
|
1081
1258
|
nav: "Codex 订阅",
|
|
1082
1259
|
title: "Codex 订阅",
|
|
1083
1260
|
connected: "已登录",
|
|
@@ -1267,10 +1444,12 @@ window.__ModuleLoader__.load({
|
|
|
1267
1444
|
imageRegionNotes: "部位修改:",
|
|
1268
1445
|
imageEdit: "在输入框中继续编辑",
|
|
1269
1446
|
imageEditPreparing: "正在添加到输入框…",
|
|
1270
|
-
imageEditFailed: "
|
|
1447
|
+
imageEditFailed: "回填失败:请填写每个标记的备注,并确认输入框可接收图片后重试。",
|
|
1271
1448
|
imageRemoveAnnotation: "删除标注"
|
|
1272
1449
|
};
|
|
1273
1450
|
const en = {
|
|
1451
|
+
imageEditLocation: "Location",
|
|
1452
|
+
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
1453
|
nav: "Codex",
|
|
1275
1454
|
title: "Codex subscription",
|
|
1276
1455
|
connected: "Signed in",
|
|
@@ -1460,7 +1639,7 @@ window.__ModuleLoader__.load({
|
|
|
1460
1639
|
imageRegionNotes: "Region changes:",
|
|
1461
1640
|
imageEdit: "Continue editing in composer",
|
|
1462
1641
|
imageEditPreparing: "Adding to composer…",
|
|
1463
|
-
imageEditFailed: "
|
|
1642
|
+
imageEditFailed: "Handoff failed. Add a note to every marker and ensure the composer accepts images, then retry.",
|
|
1464
1643
|
imageRemoveAnnotation: "Remove note"
|
|
1465
1644
|
};
|
|
1466
1645
|
const STYLE = `
|
|
@@ -1497,7 +1676,7 @@ window.__ModuleLoader__.load({
|
|
|
1497
1676
|
.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
1677
|
.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
1678
|
.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}
|
|
1679
|
+
.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
1680
|
.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
1681
|
.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
1682
|
.codexModelSelectMenu{overflow:visible}
|
|
@@ -1649,18 +1828,27 @@ window.__ModuleLoader__.load({
|
|
|
1649
1828
|
pendingLabel: t("imageEditPreparing"),
|
|
1650
1829
|
errorLabel: t("imageEditFailed"),
|
|
1651
1830
|
closeOnSuccess: true,
|
|
1652
|
-
onInvoke: ({ annotations }) =>
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1831
|
+
onInvoke: ({ annotations = [] }) => {
|
|
1832
|
+
const imageKey = String(attachment.attachmentId ?? "image").replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
1833
|
+
const sourceName = annotations.length === 0 ? downloadName : `codex-edit-${imageKey}-source.png`;
|
|
1834
|
+
const referenceName = `codex-edit-${imageKey}-annotations.png`;
|
|
1835
|
+
return attachForEdit(src, sourceName, buildImageEditDraft({
|
|
1836
|
+
annotations,
|
|
1837
|
+
translate: t,
|
|
1838
|
+
width: attachment.width,
|
|
1839
|
+
height: attachment.height,
|
|
1840
|
+
sourceName,
|
|
1841
|
+
referenceName
|
|
1842
|
+
}), annotations, referenceName);
|
|
1843
|
+
}
|
|
1656
1844
|
}]
|
|
1657
1845
|
}],
|
|
1658
1846
|
opener: triggerRef.current,
|
|
1659
1847
|
source: "codex-generated",
|
|
1660
1848
|
annotations: true
|
|
1661
1849
|
};
|
|
1662
|
-
if (
|
|
1663
|
-
|
|
1850
|
+
if (getInternalImageViewer?.()?.open?.(request) === true) return;
|
|
1851
|
+
(getImageViewer?.())?.open?.(request);
|
|
1664
1852
|
};
|
|
1665
1853
|
if (error) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
1666
1854
|
type: "button",
|
|
@@ -1780,7 +1968,7 @@ window.__ModuleLoader__.load({
|
|
|
1780
1968
|
return;
|
|
1781
1969
|
}
|
|
1782
1970
|
const usage = unwrap(await rpc.call(CHANNEL, "usage", { force: false }));
|
|
1783
|
-
if (live) setQuota(
|
|
1971
|
+
if (live) setQuota(selectModelQuotaWindows(usage, model));
|
|
1784
1972
|
} catch {
|
|
1785
1973
|
if (live) setQuota(void 0);
|
|
1786
1974
|
} finally {
|
|
@@ -2053,8 +2241,19 @@ window.__ModuleLoader__.load({
|
|
|
2053
2241
|
const codex = current?.provider === "openai-codex";
|
|
2054
2242
|
const quotaEnabled = preferenceSnapshot.status === "ready" && preferenceSnapshot.quickQuotaMode !== "off" && codex;
|
|
2055
2243
|
const forecastMode = preferenceSnapshot.quickQuotaMode === QUICK_QUOTA_MODE_FORECAST;
|
|
2056
|
-
const
|
|
2057
|
-
if (!quotaEnabled ||
|
|
2244
|
+
const quotas = useQuickQuota(rpc, quotaEnabled, current?.model);
|
|
2245
|
+
if (!quotaEnabled || quotas === void 0 || quotas.length === 0) return null;
|
|
2246
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2247
|
+
className: "codexComposerQuotaWindows",
|
|
2248
|
+
children: quotas.map((quota, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CodexComposerQuotaWindow, {
|
|
2249
|
+
quota,
|
|
2250
|
+
mode: preferenceSnapshot.quickQuotaMode,
|
|
2251
|
+
forecastMode,
|
|
2252
|
+
t
|
|
2253
|
+
}, `${quota.windowSeconds}-${index}`))
|
|
2254
|
+
});
|
|
2255
|
+
}
|
|
2256
|
+
function CodexComposerQuotaWindow({ quota, mode, forecastMode, t }) {
|
|
2058
2257
|
const value = Math.round(Number(quota.remainingPercent) * 10) / 10;
|
|
2059
2258
|
const display = percent(value);
|
|
2060
2259
|
const forecast = forecastMode ? quota.forecast : void 0;
|
|
@@ -2063,17 +2262,20 @@ window.__ModuleLoader__.load({
|
|
|
2063
2262
|
value: display,
|
|
2064
2263
|
duration
|
|
2065
2264
|
});
|
|
2066
|
-
|
|
2265
|
+
const content = mode === "bar" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("progress", {
|
|
2266
|
+
className: "codexComposerQuotaBar",
|
|
2267
|
+
max: 100,
|
|
2268
|
+
value,
|
|
2269
|
+
"aria-hidden": "true"
|
|
2270
|
+
}) : 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}%`;
|
|
2271
|
+
const durationLabel = windowLabel(quota.windowSeconds, t);
|
|
2272
|
+
const accessibleLabel = `${durationLabel}: ${label}`;
|
|
2273
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2067
2274
|
className: "codexComposerQuota",
|
|
2068
2275
|
role: "status",
|
|
2069
|
-
"aria-label":
|
|
2070
|
-
title:
|
|
2071
|
-
children:
|
|
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}%`
|
|
2276
|
+
"aria-label": accessibleLabel,
|
|
2277
|
+
title: accessibleLabel,
|
|
2278
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: durationLabel }), content]
|
|
2077
2279
|
});
|
|
2078
2280
|
}
|
|
2079
2281
|
function CodexModelSelect({ locked, available, directory, load, select, preference, t }) {
|
|
@@ -3358,13 +3560,18 @@ window.__ModuleLoader__.load({
|
|
|
3358
3560
|
}
|
|
3359
3561
|
},
|
|
3360
3562
|
getInternalImageViewer: () => imageViewer,
|
|
3361
|
-
attachForEdit: async (src, filename, draft) => {
|
|
3563
|
+
attachForEdit: async (src, filename, draft, annotations = [], referenceName) => {
|
|
3362
3564
|
const actx = sessions.scope(sessionId);
|
|
3363
3565
|
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
3566
|
const response = await fetch(src);
|
|
3365
3567
|
if (!response.ok) throw new Error("Could not read generated image");
|
|
3366
3568
|
const blob = await response.blob();
|
|
3367
|
-
const
|
|
3569
|
+
const files = [new File([blob], filename, { type: blob.type || "image/png" })];
|
|
3570
|
+
if (annotations.length > 0) {
|
|
3571
|
+
const reference = await createAnnotatedImageReference(blob, annotations);
|
|
3572
|
+
files.push(new File([reference], referenceName, { type: "image/png" }));
|
|
3573
|
+
}
|
|
3574
|
+
const created = conversation.createDraftImages(files);
|
|
3368
3575
|
const input = conversation.input.for(actx);
|
|
3369
3576
|
if (!input.addImages(created.map((item) => item.id))) {
|
|
3370
3577
|
conversation.releaseDraftImages(created);
|
package/lib/index.js
CHANGED
|
@@ -1303,7 +1303,7 @@ function openaiCodexSubscriptionProvider({ resolveSpeedMode = () => void 0, reso
|
|
|
1303
1303
|
}
|
|
1304
1304
|
//#endregion
|
|
1305
1305
|
//#region src/version.js
|
|
1306
|
-
const PACKAGE_VERSION = "1.14.
|
|
1306
|
+
const PACKAGE_VERSION = "1.14.1";
|
|
1307
1307
|
const USER_AGENT = `dsh-codex-subscription/${PACKAGE_VERSION}`;
|
|
1308
1308
|
//#endregion
|
|
1309
1309
|
//#region src/model-catalog.js
|
|
@@ -1637,6 +1637,10 @@ const PNG_SIGNATURE = Buffer.from([
|
|
|
1637
1637
|
26,
|
|
1638
1638
|
10
|
|
1639
1639
|
]);
|
|
1640
|
+
const FULL_ATTACHMENT_ID = /^sha256:[0-9a-f]{64}$/u;
|
|
1641
|
+
const BARE_ATTACHMENT_DIGEST = /^[0-9a-f]{64}$/iu;
|
|
1642
|
+
const PATH_LIKE_ATTACHMENT_ID = /[\\/]/u;
|
|
1643
|
+
const FILE_NAME_ATTACHMENT_ID = /\.[A-Za-z0-9]{1,16}$/u;
|
|
1640
1644
|
const record$2 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1641
1645
|
const nonEmpty = (value) => typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
|
|
1642
1646
|
function normalizeImageOptions(args) {
|
|
@@ -1706,22 +1710,63 @@ function decodeCodexPng(value, maximumBytes) {
|
|
|
1706
1710
|
return new Uint8Array(data);
|
|
1707
1711
|
}
|
|
1708
1712
|
function imageReference(value) {
|
|
1713
|
+
const originalDimensions = record$2(value.originalDimensions) ? {
|
|
1714
|
+
width: value.originalDimensions.width,
|
|
1715
|
+
height: value.originalDimensions.height
|
|
1716
|
+
} : void 0;
|
|
1709
1717
|
return {
|
|
1710
1718
|
attachmentId: value.attachmentId,
|
|
1711
1719
|
mediaType: value.mediaType,
|
|
1712
1720
|
bytes: value.bytes,
|
|
1713
1721
|
width: value.width,
|
|
1714
1722
|
height: value.height,
|
|
1715
|
-
...value.name === void 0 ? {} : { name: value.name }
|
|
1723
|
+
...value.name === void 0 ? {} : { name: value.name },
|
|
1724
|
+
...originalDimensions === void 0 ? {} : { originalDimensions }
|
|
1716
1725
|
};
|
|
1717
1726
|
}
|
|
1727
|
+
function normalizeAttachmentId(value) {
|
|
1728
|
+
if (typeof value !== "string") return void 0;
|
|
1729
|
+
if (FULL_ATTACHMENT_ID.test(value)) return value;
|
|
1730
|
+
const bare = BARE_ATTACHMENT_DIGEST.exec(value);
|
|
1731
|
+
return bare === null ? void 0 : `sha256:${bare[0].toLowerCase()}`;
|
|
1732
|
+
}
|
|
1733
|
+
function invalidAttachmentId(value) {
|
|
1734
|
+
const attachmentId = value?.attachmentId;
|
|
1735
|
+
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.");
|
|
1736
|
+
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.");
|
|
1737
|
+
}
|
|
1718
1738
|
function referenceOf(value, attachments) {
|
|
1719
|
-
|
|
1720
|
-
|
|
1739
|
+
const attachmentId = normalizeAttachmentId(value?.attachmentId);
|
|
1740
|
+
if (attachmentId === void 0) invalidAttachmentId(value);
|
|
1741
|
+
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");
|
|
1742
|
+
return imageReference({
|
|
1743
|
+
...value,
|
|
1744
|
+
attachmentId
|
|
1745
|
+
});
|
|
1721
1746
|
}
|
|
1722
|
-
|
|
1747
|
+
function sessionImageReferences(messages) {
|
|
1748
|
+
const references = /* @__PURE__ */ new Map();
|
|
1749
|
+
const visit = (content) => {
|
|
1750
|
+
if (!Array.isArray(content)) return;
|
|
1751
|
+
for (const block of content) if (block?.type === "image" && record$2(block.attachment)) {
|
|
1752
|
+
const id = normalizeAttachmentId(block.attachment.attachmentId);
|
|
1753
|
+
if (id !== void 0) references.set(id, block.attachment);
|
|
1754
|
+
} else if (block?.type === "tool-result") visit(block.content);
|
|
1755
|
+
};
|
|
1756
|
+
for (const message of messages ?? []) visit(message?.content);
|
|
1757
|
+
return references;
|
|
1758
|
+
}
|
|
1759
|
+
async function editImages(values, attachments, signal, messages) {
|
|
1723
1760
|
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
|
|
1761
|
+
const available = messages === void 0 ? void 0 : sessionImageReferences(messages);
|
|
1762
|
+
const references = values.map((value) => {
|
|
1763
|
+
const id = normalizeAttachmentId(value?.attachmentId);
|
|
1764
|
+
if (id === void 0) invalidAttachmentId(value);
|
|
1765
|
+
if (available === void 0) return referenceOf(value, attachments);
|
|
1766
|
+
const selected = available.get(id);
|
|
1767
|
+
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.");
|
|
1768
|
+
return referenceOf(selected, attachments);
|
|
1769
|
+
});
|
|
1725
1770
|
if (new Set(references.map((value) => value.attachmentId)).size !== references.length) throw new Error("referenceImages must not contain duplicates");
|
|
1726
1771
|
const images = [];
|
|
1727
1772
|
let totalBytes = 0;
|
|
@@ -1774,7 +1819,21 @@ function imageOutputSchema() {
|
|
|
1774
1819
|
type: "integer",
|
|
1775
1820
|
required: true
|
|
1776
1821
|
},
|
|
1777
|
-
name: { type: "string" }
|
|
1822
|
+
name: { type: "string" },
|
|
1823
|
+
originalDimensions: {
|
|
1824
|
+
type: "object",
|
|
1825
|
+
additionalProperties: false,
|
|
1826
|
+
properties: {
|
|
1827
|
+
width: {
|
|
1828
|
+
type: "integer",
|
|
1829
|
+
required: true
|
|
1830
|
+
},
|
|
1831
|
+
height: {
|
|
1832
|
+
type: "integer",
|
|
1833
|
+
required: true
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1778
1837
|
}
|
|
1779
1838
|
},
|
|
1780
1839
|
original: {
|
|
@@ -1841,7 +1900,7 @@ function createCodexImageTool(options) {
|
|
|
1841
1900
|
const attachments = options.attachments;
|
|
1842
1901
|
return defineTool({
|
|
1843
1902
|
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.",
|
|
1903
|
+
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
1904
|
parameters: {
|
|
1846
1905
|
prompt: {
|
|
1847
1906
|
type: "string",
|
|
@@ -1873,14 +1932,15 @@ function createCodexImageTool(options) {
|
|
|
1873
1932
|
},
|
|
1874
1933
|
referenceImages: {
|
|
1875
1934
|
type: "array",
|
|
1876
|
-
description: "Optional explicit references to 1-5 prior images to edit. Omit for a new image.",
|
|
1935
|
+
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
1936
|
items: {
|
|
1878
1937
|
type: "object",
|
|
1879
1938
|
additionalProperties: false,
|
|
1880
1939
|
properties: {
|
|
1881
1940
|
attachmentId: {
|
|
1882
1941
|
type: "string",
|
|
1883
|
-
required: true
|
|
1942
|
+
required: true,
|
|
1943
|
+
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
1944
|
},
|
|
1885
1945
|
mediaType: {
|
|
1886
1946
|
type: "string",
|
|
@@ -1898,7 +1958,21 @@ function createCodexImageTool(options) {
|
|
|
1898
1958
|
type: "integer",
|
|
1899
1959
|
required: true
|
|
1900
1960
|
},
|
|
1901
|
-
name: { type: "string" }
|
|
1961
|
+
name: { type: "string" },
|
|
1962
|
+
originalDimensions: {
|
|
1963
|
+
type: "object",
|
|
1964
|
+
additionalProperties: false,
|
|
1965
|
+
properties: {
|
|
1966
|
+
width: {
|
|
1967
|
+
type: "integer",
|
|
1968
|
+
required: true
|
|
1969
|
+
},
|
|
1970
|
+
height: {
|
|
1971
|
+
type: "integer",
|
|
1972
|
+
required: true
|
|
1973
|
+
}
|
|
1974
|
+
}
|
|
1975
|
+
}
|
|
1902
1976
|
}
|
|
1903
1977
|
}
|
|
1904
1978
|
}
|
|
@@ -1926,7 +2000,8 @@ function createCodexImageTool(options) {
|
|
|
1926
2000
|
if (!attachments.imageLimits.mediaTypes.includes("image/png")) throw new Error("This DSH installation does not accept PNG image attachments");
|
|
1927
2001
|
const maximumBytes = Math.min(attachments.imageLimits.maxImageBytes, attachments.imageLimits.maxMessageImageBytes);
|
|
1928
2002
|
const editing = args.referenceImages !== void 0;
|
|
1929
|
-
const
|
|
2003
|
+
const sessionMessages = editing && typeof options.getSessionMessages === "function" ? await options.getSessionMessages(exec.agent?.id) : void 0;
|
|
2004
|
+
const images = editing ? await editImages(args.referenceImages, attachments, exec.signal, sessionMessages ?? (options.getSessionMessages ? [] : void 0)) : void 0;
|
|
1930
2005
|
let response;
|
|
1931
2006
|
try {
|
|
1932
2007
|
response = await fetchImage(editing ? CODEX_IMAGE_EDIT_URL : CODEX_IMAGE_GENERATION_URL, {
|
|
@@ -3304,6 +3379,7 @@ function apply(ctx) {
|
|
|
3304
3379
|
getAuth: resolveAuth,
|
|
3305
3380
|
readCredential: (options) => store.read(PROVIDER, options),
|
|
3306
3381
|
attachments: ctx.attachments,
|
|
3382
|
+
getSessionMessages: (sessionId) => ctx.get?.("sessions")?.get?.(sessionId)?.deriveMessages?.() ?? [],
|
|
3307
3383
|
originalImages,
|
|
3308
3384
|
fetch: (input, init) => network.fetch("image", input, init)
|
|
3309
3385
|
}));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-codex-subscription",
|
|
3
|
-
"version": "1.14.
|
|
3
|
+
"version": "1.14.1",
|
|
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",
|