dsh-codex-subscription 2.5.6 → 2.5.8

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
@@ -8,7 +8,7 @@
8
8
 
9
9
  **Use your ChatGPT / Codex subscription directly in DeepSeek Harness**
10
10
 
11
- No OpenAI API key or Codex CLI. Models, search, quota, and image generation stay inside DSH.
11
+ Sign in with ChatGPT Plus / Pro to use GPT-6 and other Codex models, quota, web search, and image generation inside DSH. No OpenAI API key or Codex CLI.
12
12
 
13
13
  [![CI](https://github.com/WSL043/dsh-codex-subscription/actions/workflows/ci.yml/badge.svg)](https://github.com/WSL043/dsh-codex-subscription/actions/workflows/ci.yml)
14
14
  [![npm](https://img.shields.io/npm/v/dsh-codex-subscription?logo=npm&label=npm)](https://www.npmjs.com/package/dsh-codex-subscription)
@@ -106,7 +106,7 @@ This plugin supports the latest DeepSeek Harness release recorded in its package
106
106
  2. Paste this package name, with its version, into the **Package name or address** field:
107
107
 
108
108
  ```text
109
- dsh-codex-subscription@2.5.6
109
+ dsh-codex-subscription@2.5.8
110
110
  ```
111
111
 
112
112
  3. Click **Install** and wait for completion. Follow the page instructions; save your work before restarting if requested.
@@ -139,6 +139,12 @@ dsh --profile headless "Reply with only the word: ok"
139
139
 
140
140
  ## Inside the plugin
141
141
 
142
+ <details>
143
+ <summary><b>Interface demo (animation, click to expand)</b>: models, prompts, images, annotation, sketch board, quota and resets</summary>
144
+ <br>
145
+ <p align="center"><img src="docs/assets/readme-demo-en.webp" width="1100" alt="Demo: sign in with ChatGPT, pick a subscription model, ask, generate an image, view and annotate it, continue on the sketch board, and check quota and resets"></p>
146
+ </details>
147
+
142
148
  ### Everyday controls, right in the composer
143
149
 
144
150
  Choose a model, adjust reasoning, switch speed tiers, and check remaining quota without opening settings for every request.
package/README.md CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  **把 ChatGPT / Codex 订阅直接接入 DeepSeek Harness**
10
10
 
11
- 登录已有订阅,即可在 DSH 中选模型、查额度、搜索和生图。无需 API Key,也不依赖 Codex CLI。
11
+ 用 ChatGPT Plus / Pro 订阅登录,即可在 DSH 中使用 GPT-6 等 Codex 模型、查额度、联网搜索和生图。无需 OpenAI API Key,也不依赖 Codex CLI。
12
12
 
13
13
  [![CI](https://github.com/WSL043/dsh-codex-subscription/actions/workflows/ci.yml/badge.svg)](https://github.com/WSL043/dsh-codex-subscription/actions/workflows/ci.yml)
14
14
  [![npm](https://img.shields.io/npm/v/dsh-codex-subscription?logo=npm&label=npm)](https://www.npmjs.com/package/dsh-codex-subscription)
@@ -106,7 +106,7 @@
106
106
  2. 在 **包名或地址** 输入框中粘贴下面的包名(已带版本号):
107
107
 
108
108
  ```text
109
- dsh-codex-subscription@2.5.6
109
+ dsh-codex-subscription@2.5.8
110
110
  ```
111
111
 
112
112
  3. 点击 **安装**,等待安装完成;按页面提示操作,需要重启时先保存工作。
@@ -139,6 +139,12 @@ dsh --profile headless "只回复:ok"
139
139
 
140
140
  ## 界面一览
141
141
 
142
+ <details>
143
+ <summary><b>界面演示(动图,点击展开)</b>:选模型、提问、出图、标注、草图、额度与重置</summary>
144
+ <br>
145
+ <p align="center"><img src="docs/assets/readme-demo.webp" width="1100" alt="演示:登录 ChatGPT 选订阅模型、提问、用订阅出图、查看与标注、进入草图、查看额度与重置"></p>
146
+ </details>
147
+
142
148
  ### 日常操作,留在输入框
143
149
 
144
150
  选模型、调推理档位、切换速度档位,同时查看剩余额度。无需为每次请求打开设置。
package/lib/client.js CHANGED
@@ -587,6 +587,88 @@ window.__ModuleLoader__.load({
587
587
  ].join("\n");
588
588
  }
589
589
  //#endregion
590
+ //#region src/image-official-tools.js
591
+ const IMAGE_ASPECT_RATIOS = Object.freeze([
592
+ ["square", "1:1"],
593
+ ["portrait", "3:4"],
594
+ ["story", "9:16"],
595
+ ["landscape", "4:3"],
596
+ ["widescreen", "16:9"]
597
+ ]);
598
+ /**
599
+ * Build viewer actions for one image.
600
+ * @param {{ src: string, name: string, t: (key: string) => string,
601
+ * attachForEdit: (src: string, name: string, draft: string, annotations?: unknown[], referenceName?: string, sourceInDraft?: boolean, extraFiles?: File[]) => Promise<unknown>,
602
+ * sourceInDraft?: boolean }} options
603
+ */
604
+ function officialImageTools({ src, name, t, attachForEdit, sourceInDraft = false }) {
605
+ const send = (draft, extraFiles = []) => attachForEdit(src, name, draft, [], void 0, sourceInDraft, extraFiles);
606
+ const base = {
607
+ pendingLabel: t("imageEditPreparing"),
608
+ errorLabel: t("imageEditFailed"),
609
+ closeOnSuccess: true
610
+ };
611
+ return [
612
+ {
613
+ ...base,
614
+ id: "remove-background",
615
+ kind: "tool",
616
+ label: t("imageRemoveBackground"),
617
+ onInvoke: () => send(t("imageRemoveBackgroundPrompt"))
618
+ },
619
+ {
620
+ ...base,
621
+ id: "erase",
622
+ kind: "mask",
623
+ label: t("imageErase"),
624
+ onInvoke: async ({ mask }) => {
625
+ if (!(mask instanceof Blob)) throw new Error("Mark the area to erase first");
626
+ const maskName = `${String(name).replace(/\.[a-z0-9]+$/iu, "")}-erase-mask.png`;
627
+ return send(t("imageErasePrompt"), [new File([mask], maskName, { type: "image/png" })]);
628
+ }
629
+ },
630
+ {
631
+ ...base,
632
+ id: "resize",
633
+ kind: "choice",
634
+ label: t("imageResize"),
635
+ options: IMAGE_ASPECT_RATIOS.map(([id, ratio]) => ({
636
+ value: ratio,
637
+ label: `${t(`imageRatio_${id}`)} ${ratio}`
638
+ })),
639
+ onInvoke: ({ option }) => send(t("imageResizePrompt").replaceAll("{aspectRatio}", option))
640
+ }
641
+ ];
642
+ }
643
+ /** Draw relative erase strokes in one color onto a canvas of the given size. */
644
+ function paintEraseStrokes(context, strokes, width, height, color) {
645
+ context.strokeStyle = color;
646
+ context.fillStyle = color;
647
+ context.lineCap = "round";
648
+ context.lineJoin = "round";
649
+ const scale = Math.min(width, height);
650
+ for (const stroke of strokes) {
651
+ const points = stroke.points;
652
+ if (points.length === 0) continue;
653
+ context.lineWidth = stroke.size * scale;
654
+ context.beginPath();
655
+ if (points.length === 1) {
656
+ context.arc(points[0].x * width, points[0].y * height, stroke.size * scale / 2, 0, Math.PI * 2);
657
+ context.fill();
658
+ continue;
659
+ }
660
+ context.moveTo(points[0].x * width, points[0].y * height);
661
+ for (const point of points.slice(1)) context.lineTo(point.x * width, point.y * height);
662
+ context.stroke();
663
+ }
664
+ }
665
+ /** Codex's erase mask: the marked area white on black, at the image's own resolution. */
666
+ function paintEraseMask(context, strokes, width, height) {
667
+ context.fillStyle = "#000";
668
+ context.fillRect(0, 0, width, height);
669
+ paintEraseStrokes(context, strokes, width, height, "#fff");
670
+ }
671
+ //#endregion
590
672
  //#region src/client-image-previews.jsx
591
673
  function openPreview(props, item, opener, sourceInDraft = false) {
592
674
  const { service, preference, t, attachForEdit } = props;
@@ -595,25 +677,35 @@ window.__ModuleLoader__.load({
595
677
  service.open({
596
678
  items: [{
597
679
  ...item,
598
- actions: settings.imageEditing ? [{
599
- id: "edit",
600
- label: t("imageEdit"),
601
- pendingLabel: t("imageEditPreparing"),
602
- errorLabel: t("imageEditFailed"),
603
- closeOnSuccess: true,
604
- onInvoke: ({ annotations }) => attachForEdit(item.src, item.name, buildImageEditDraft({
605
- annotations,
606
- translate: t,
607
- sourceName: item.name,
608
- referenceName
609
- }), annotations, referenceName, sourceInDraft)
610
- }, ...settings.imageSketch && props.openSketchImage ? [{
611
- id: "sketch",
612
- label: t("imageToSketch"),
613
- pendingLabel: t("imageEditPreparing"),
614
- errorLabel: t("imageEditFailed"),
615
- onInvoke: () => props.openSketchImage(item.src, item.name)
616
- }] : []] : []
680
+ actions: settings.imageEditing ? [
681
+ {
682
+ id: "edit",
683
+ label: t("imageEdit"),
684
+ pendingLabel: t("imageEditPreparing"),
685
+ errorLabel: t("imageEditFailed"),
686
+ closeOnSuccess: true,
687
+ onInvoke: ({ annotations }) => attachForEdit(item.src, item.name, buildImageEditDraft({
688
+ annotations,
689
+ translate: t,
690
+ sourceName: item.name,
691
+ referenceName
692
+ }), annotations, referenceName, sourceInDraft)
693
+ },
694
+ ...officialImageTools({
695
+ src: item.src,
696
+ name: item.name,
697
+ t,
698
+ attachForEdit,
699
+ sourceInDraft
700
+ }),
701
+ ...settings.imageSketch && props.openSketchImage ? [{
702
+ id: "sketch",
703
+ label: t("imageToSketch"),
704
+ pendingLabel: t("imageEditPreparing"),
705
+ errorLabel: t("imageEditFailed"),
706
+ onInvoke: () => props.openSketchImage(item.src, item.name)
707
+ }] : []
708
+ ] : []
617
709
  }],
618
710
  opener,
619
711
  source: sourceInDraft ? "codex-draft" : "codex-message",
@@ -804,7 +896,27 @@ window.__ModuleLoader__.load({
804
896
  d: "M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99"
805
897
  }));
806
898
  }
807
- const ForwardRef$3 = /*#__PURE__*/ react.forwardRef(ArrowPathIcon);
899
+ const ForwardRef$6 = /*#__PURE__*/ react.forwardRef(ArrowPathIcon);
900
+ //#endregion
901
+ //#region node_modules/.pnpm/@heroicons+react@2.2.0_react@18.3.1/node_modules/@heroicons/react/24/outline/esm/ArrowsPointingOutIcon.js
902
+ function ArrowsPointingOutIcon({ title, titleId, ...props }, svgRef) {
903
+ return /*#__PURE__*/ react.createElement("svg", Object.assign({
904
+ xmlns: "http://www.w3.org/2000/svg",
905
+ fill: "none",
906
+ viewBox: "0 0 24 24",
907
+ strokeWidth: 1.5,
908
+ stroke: "currentColor",
909
+ "aria-hidden": "true",
910
+ "data-slot": "icon",
911
+ ref: svgRef,
912
+ "aria-labelledby": titleId
913
+ }, props), title ? /*#__PURE__*/ react.createElement("title", { id: titleId }, title) : null, /*#__PURE__*/ react.createElement("path", {
914
+ strokeLinecap: "round",
915
+ strokeLinejoin: "round",
916
+ d: "M3.75 3.75v4.5m0-4.5h4.5m-4.5 0L9 9M3.75 20.25v-4.5m0 4.5h4.5m-4.5 0L9 15M20.25 3.75h-4.5m4.5 0v4.5m0-4.5L15 9m5.25 11.25h-4.5m4.5 0v-4.5m0 4.5L15 15"
917
+ }));
918
+ }
919
+ const ForwardRef$5 = /*#__PURE__*/ react.forwardRef(ArrowsPointingOutIcon);
808
920
  //#endregion
809
921
  //#region node_modules/.pnpm/@heroicons+react@2.2.0_react@18.3.1/node_modules/@heroicons/react/24/outline/esm/ExclamationCircleIcon.js
810
922
  function ExclamationCircleIcon({ title, titleId, ...props }, svgRef) {
@@ -824,7 +936,27 @@ window.__ModuleLoader__.load({
824
936
  d: "M12 9v3.75m9-.75a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 3.75h.008v.008H12v-.008Z"
825
937
  }));
826
938
  }
827
- const ForwardRef$2 = /*#__PURE__*/ react.forwardRef(ExclamationCircleIcon);
939
+ const ForwardRef$4 = /*#__PURE__*/ react.forwardRef(ExclamationCircleIcon);
940
+ //#endregion
941
+ //#region node_modules/.pnpm/@heroicons+react@2.2.0_react@18.3.1/node_modules/@heroicons/react/24/outline/esm/PaintBrushIcon.js
942
+ function PaintBrushIcon({ title, titleId, ...props }, svgRef) {
943
+ return /*#__PURE__*/ react.createElement("svg", Object.assign({
944
+ xmlns: "http://www.w3.org/2000/svg",
945
+ fill: "none",
946
+ viewBox: "0 0 24 24",
947
+ strokeWidth: 1.5,
948
+ stroke: "currentColor",
949
+ "aria-hidden": "true",
950
+ "data-slot": "icon",
951
+ ref: svgRef,
952
+ "aria-labelledby": titleId
953
+ }, props), title ? /*#__PURE__*/ react.createElement("title", { id: titleId }, title) : null, /*#__PURE__*/ react.createElement("path", {
954
+ strokeLinecap: "round",
955
+ strokeLinejoin: "round",
956
+ d: "M9.53 16.122a3 3 0 0 0-5.78 1.128 2.25 2.25 0 0 1-2.4 2.245 4.5 4.5 0 0 0 8.4-2.245c0-.399-.078-.78-.22-1.128Zm0 0a15.998 15.998 0 0 0 3.388-1.62m-5.043-.025a15.994 15.994 0 0 1 1.622-3.395m3.42 3.42a15.995 15.995 0 0 0 4.764-4.648l3.876-5.814a1.151 1.151 0 0 0-1.597-1.597L14.146 6.32a15.996 15.996 0 0 0-4.649 4.763m3.42 3.42a6.776 6.776 0 0 0-3.42-3.42"
957
+ }));
958
+ }
959
+ const ForwardRef$3 = /*#__PURE__*/ react.forwardRef(PaintBrushIcon);
828
960
  //#endregion
829
961
  //#region node_modules/.pnpm/@heroicons+react@2.2.0_react@18.3.1/node_modules/@heroicons/react/24/outline/esm/PhotoIcon.js
830
962
  function PhotoIcon({ title, titleId, ...props }, svgRef) {
@@ -844,7 +976,27 @@ window.__ModuleLoader__.load({
844
976
  d: "m2.25 15.75 5.159-5.159a2.25 2.25 0 0 1 3.182 0l5.159 5.159m-1.5-1.5 1.409-1.409a2.25 2.25 0 0 1 3.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 0 0 1.5-1.5V6a1.5 1.5 0 0 0-1.5-1.5H3.75A1.5 1.5 0 0 0 2.25 6v12a1.5 1.5 0 0 0 1.5 1.5Zm10.5-11.25h.008v.008h-.008V8.25Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Z"
845
977
  }));
846
978
  }
847
- const ForwardRef$1 = /*#__PURE__*/ react.forwardRef(PhotoIcon);
979
+ const ForwardRef$2 = /*#__PURE__*/ react.forwardRef(PhotoIcon);
980
+ //#endregion
981
+ //#region node_modules/.pnpm/@heroicons+react@2.2.0_react@18.3.1/node_modules/@heroicons/react/24/outline/esm/ScissorsIcon.js
982
+ function ScissorsIcon({ title, titleId, ...props }, svgRef) {
983
+ return /*#__PURE__*/ react.createElement("svg", Object.assign({
984
+ xmlns: "http://www.w3.org/2000/svg",
985
+ fill: "none",
986
+ viewBox: "0 0 24 24",
987
+ strokeWidth: 1.5,
988
+ stroke: "currentColor",
989
+ "aria-hidden": "true",
990
+ "data-slot": "icon",
991
+ ref: svgRef,
992
+ "aria-labelledby": titleId
993
+ }, props), title ? /*#__PURE__*/ react.createElement("title", { id: titleId }, title) : null, /*#__PURE__*/ react.createElement("path", {
994
+ strokeLinecap: "round",
995
+ strokeLinejoin: "round",
996
+ d: "m7.848 8.25 1.536.887M7.848 8.25a3 3 0 1 1-5.196-3 3 3 0 0 1 5.196 3Zm1.536.887a2.165 2.165 0 0 1 1.083 1.839c.005.351.054.695.14 1.024M9.384 9.137l2.077 1.199M7.848 15.75l1.536-.887m-1.536.887a3 3 0 1 1-5.196 3 3 3 0 0 1 5.196-3Zm1.536-.887a2.165 2.165 0 0 0 1.083-1.838c.005-.352.054-.695.14-1.025m-1.223 2.863 2.077-1.199m0-3.328a4.323 4.323 0 0 1 2.068-1.379l5.325-1.628a4.5 4.5 0 0 1 2.48-.044l.803.215-7.794 4.5m-2.882-1.664A4.33 4.33 0 0 0 10.607 12m3.736 0 7.794 4.5-.802.215a4.5 4.5 0 0 1-2.48-.043l-5.326-1.629a4.324 4.324 0 0 1-2.068-1.379M14.343 12l-2.882 1.664"
997
+ }));
998
+ }
999
+ const ForwardRef$1 = /*#__PURE__*/ react.forwardRef(ScissorsIcon);
848
1000
  const ORIGINAL_IMAGE_ID_PATTERN = /^img_[0-9a-f]{32}$/u;
849
1001
  const positiveInteger = (value) => Number.isSafeInteger(value) && value > 0;
850
1002
  function decodeOriginalImageRef(value) {
@@ -1032,32 +1184,41 @@ window.__ModuleLoader__.load({
1032
1184
  errorLabel: t("imageDownloadFailed"),
1033
1185
  onInvoke: downloadOriginal
1034
1186
  },
1035
- actions: !features.imageEditing ? [] : [{
1036
- id: "continue-editing",
1037
- label: t("imageEdit"),
1038
- pendingLabel: t("imageEditPreparing"),
1039
- errorLabel: t("imageEditFailed"),
1040
- closeOnSuccess: true,
1041
- onInvoke: ({ annotations = [] }) => {
1042
- const imageKey = String(attachment.attachmentId ?? "image").replace(/[^a-zA-Z0-9_-]/g, "_");
1043
- const sourceName = annotations.length === 0 ? downloadName : `codex-edit-${imageKey}-source.png`;
1044
- const referenceName = `codex-edit-${imageKey}-annotations.png`;
1045
- return attachForEdit(src, sourceName, buildImageEditDraft({
1046
- annotations,
1047
- translate: t,
1048
- width: attachment.width,
1049
- height: attachment.height,
1050
- sourceName,
1051
- referenceName
1052
- }), annotations, referenceName);
1053
- }
1054
- }, ...features.imageSketch && openSketchImage ? [{
1055
- id: "sketch",
1056
- label: t("imageToSketch"),
1057
- pendingLabel: t("imageEditPreparing"),
1058
- errorLabel: t("imageEditFailed"),
1059
- onInvoke: () => openSketchImage(src, downloadName)
1060
- }] : []]
1187
+ actions: !features.imageEditing ? [] : [
1188
+ {
1189
+ id: "continue-editing",
1190
+ label: t("imageEdit"),
1191
+ pendingLabel: t("imageEditPreparing"),
1192
+ errorLabel: t("imageEditFailed"),
1193
+ closeOnSuccess: true,
1194
+ onInvoke: ({ annotations = [] }) => {
1195
+ const imageKey = String(attachment.attachmentId ?? "image").replace(/[^a-zA-Z0-9_-]/g, "_");
1196
+ const sourceName = annotations.length === 0 ? downloadName : `codex-edit-${imageKey}-source.png`;
1197
+ const referenceName = `codex-edit-${imageKey}-annotations.png`;
1198
+ return attachForEdit(src, sourceName, buildImageEditDraft({
1199
+ annotations,
1200
+ translate: t,
1201
+ width: attachment.width,
1202
+ height: attachment.height,
1203
+ sourceName,
1204
+ referenceName
1205
+ }), annotations, referenceName);
1206
+ }
1207
+ },
1208
+ ...officialImageTools({
1209
+ src,
1210
+ name: downloadName,
1211
+ t,
1212
+ attachForEdit
1213
+ }),
1214
+ ...features.imageSketch && openSketchImage ? [{
1215
+ id: "sketch",
1216
+ label: t("imageToSketch"),
1217
+ pendingLabel: t("imageEditPreparing"),
1218
+ errorLabel: t("imageEditFailed"),
1219
+ onInvoke: () => openSketchImage(src, downloadName)
1220
+ }] : []
1221
+ ]
1061
1222
  }],
1062
1223
  opener: triggerRef.current,
1063
1224
  source: "codex-generated",
@@ -1107,7 +1268,7 @@ window.__ModuleLoader__.load({
1107
1268
  showOutput ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1108
1269
  className: "codexImageToolRow",
1109
1270
  children: [
1110
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(!settled ? ForwardRef$3 : failed ? ForwardRef$2 : ForwardRef$1, {
1271
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(!settled ? ForwardRef$6 : failed ? ForwardRef$4 : ForwardRef$2, {
1111
1272
  className: "codexImageToolIcon",
1112
1273
  "aria-hidden": "true"
1113
1274
  }),
@@ -5525,6 +5686,21 @@ window.__ModuleLoader__.load({
5525
5686
  sketchEditableFile: "可编辑草稿",
5526
5687
  sketchFormatHint: "PNG 为合并图片;可编辑草稿保留原生笔画,可重新导入(32 MB 内)。",
5527
5688
  imageToSketch: "进入草图",
5689
+ imageRemoveBackground: "移除背景",
5690
+ imageRemoveBackgroundPrompt: "移除此图像的背景。保持所有前景主体不变且完整,边缘干净平滑。将背景设为透明。",
5691
+ imageErase: "擦除",
5692
+ imageEraseHint: "在要移除的内容上涂抹",
5693
+ imageErasePrompt: "从第一张图像中移除第二张图像中标记的区域",
5694
+ imageBrushSize: "画笔大小",
5695
+ imageUndo: "撤销",
5696
+ imageSend: "发送",
5697
+ imageResize: "调整大小",
5698
+ imageResizePrompt: "将宽高比设为 {aspectRatio}",
5699
+ imageRatio_square: "正方形",
5700
+ imageRatio_portrait: "竖向",
5701
+ imageRatio_story: "故事版式",
5702
+ imageRatio_landscape: "横向",
5703
+ imageRatio_widescreen: "宽屏",
5528
5704
  sketchPictures: "图片",
5529
5705
  sketchPictureAdd: "添加",
5530
5706
  sketchPictureSelect: "选择图片",
@@ -5781,15 +5957,18 @@ window.__ModuleLoader__.load({
5781
5957
  unlimited: "不限额",
5782
5958
  monthlyCreditLimit: "Credits 月度消费上限",
5783
5959
  resetCredits: "额度重置",
5960
+ resetCreditsHint: "使用重置可恢复短周期额度、每周额度,或两者同时恢复。",
5961
+ resetAvailableTab: "可用",
5962
+ resetFull: "完全重置",
5784
5963
  resetCreditsAvailable: "额度重置(可用 {count} 次)",
5785
5964
  resetCreditDefaultName: "额度重置",
5786
- resetUse: "使用",
5965
+ resetUse: "使用重置",
5787
5966
  resetPreparing: "准备中…",
5788
5967
  resetConfirmTitle: "确认使用额度重置",
5789
5968
  resetWarning: "执行后会消耗 1 次,且无法撤销。",
5790
5969
  resetEarlyWarning: "当前额度未用尽,服务可能不执行重置。",
5791
5970
  resetAcknowledge: "我知道这次操作可能立即消耗 1 次重置",
5792
- resetCreditExpires: "到期:{value}",
5971
+ resetCreditExpires: "有效期至:{value}",
5793
5972
  resetCreditExpiryUnknown: "到期时间未提供",
5794
5973
  resetCreditExpiryLoading: "正在读取到期时间…",
5795
5974
  resetCreditExpiryFailed: "无法读取到期时间",
@@ -6094,6 +6273,21 @@ window.__ModuleLoader__.load({
6094
6273
  sketchEditableFile: "Editable draft",
6095
6274
  sketchFormatHint: "PNG is flattened; editable drafts retain native strokes and can be imported again (up to 32 MB).",
6096
6275
  imageToSketch: "Open in sketch",
6276
+ imageRemoveBackground: "Remove background",
6277
+ imageRemoveBackgroundPrompt: "Remove the background from this image. Keep all foreground subjects unchanged and fully intact, with clean, smooth edges. Make the background transparent.",
6278
+ imageErase: "Erase",
6279
+ imageEraseHint: "Brush over what you want to remove",
6280
+ imageErasePrompt: "Remove the area marked in the second image from the first image",
6281
+ imageBrushSize: "Brush size",
6282
+ imageUndo: "Undo",
6283
+ imageSend: "Send",
6284
+ imageResize: "Resize",
6285
+ imageResizePrompt: "Make the aspect ratio {aspectRatio}",
6286
+ imageRatio_square: "Square",
6287
+ imageRatio_portrait: "Portrait",
6288
+ imageRatio_story: "Story",
6289
+ imageRatio_landscape: "Landscape",
6290
+ imageRatio_widescreen: "Widescreen",
6097
6291
  sketchPictures: "Images",
6098
6292
  sketchPictureAdd: "Add",
6099
6293
  sketchPictureSelect: "Select image",
@@ -6350,15 +6544,18 @@ window.__ModuleLoader__.load({
6350
6544
  unlimited: "Unlimited",
6351
6545
  monthlyCreditLimit: "Monthly Credits spending cap",
6352
6546
  resetCredits: "Quota resets",
6547
+ resetCreditsHint: "A reset restores your short-window limit, weekly limit, or both.",
6548
+ resetAvailableTab: "Available",
6549
+ resetFull: "Full reset",
6353
6550
  resetCreditsAvailable: "Quota resets ({count} available)",
6354
6551
  resetCreditDefaultName: "Quota reset",
6355
- resetUse: "Use",
6552
+ resetUse: "Use reset",
6356
6553
  resetPreparing: "Preparing…",
6357
6554
  resetConfirmTitle: "Confirm quota reset",
6358
6555
  resetWarning: "This consumes one reset and cannot be undone.",
6359
6556
  resetEarlyWarning: "Quota remains. The service may decline the reset.",
6360
6557
  resetAcknowledge: "I understand this may consume one reset now",
6361
- resetCreditExpires: "Expires {value}",
6558
+ resetCreditExpires: "Valid until {value}",
6362
6559
  resetCreditExpiryUnknown: "Expiration time not provided",
6363
6560
  resetCreditExpiryLoading: "Reading expiration…",
6364
6561
  resetCreditExpiryFailed: "Could not read expiration",
@@ -6634,9 +6831,21 @@ window.__ModuleLoader__.load({
6634
6831
  .codexSubscriptionCreditBalance strong{font:600 18px/24px ui-monospace,SFMono-Regular,Consolas,monospace;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}
6635
6832
  .codexSubscriptionCreditRows{display:flex;flex-direction:column;gap:6px}
6636
6833
  .codexSubscriptionResetMeta{display:flex;min-width:0;flex-direction:column;gap:1px}
6637
- .codexSubscriptionResetDisclosure>summary{cursor:pointer;width:fit-content;color:var(--dsw-alias-label-primary);border-radius:6px}
6638
- .codexSubscriptionResetDisclosure>summary:focus-visible{outline:2px solid currentColor;outline-offset:4px}
6639
- .codexSubscriptionResetDisclosure[open]>summary{margin-bottom:10px}
6834
+ .codexSubscriptionResetPanel{display:flex;flex-direction:column;gap:10px;margin-top:6px}
6835
+ .codexSubscriptionResetHead{display:flex;flex-direction:column;gap:2px}
6836
+ .codexSubscriptionResetHead strong{font-size:14px;line-height:20px;font-weight:600;color:var(--dsw-alias-label-primary)}
6837
+ .codexSubscriptionResetHead span{font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary)}
6838
+ .codexSubscriptionResetBox{border:1px solid var(--dsw-alias-border-l2);border-radius:12px;padding:6px 14px 2px}
6839
+ .codexSubscriptionResetTabs{display:flex;gap:8px;padding:6px 0 10px;border-bottom:1px solid var(--dsw-alias-border-l2)}
6840
+ .codexSubscriptionResetTabs span{display:inline-flex;align-items:center;gap:6px;padding:3px 10px;border-radius:8px;background:var(--dsw-alias-bg-module-platform);font-size:12px;line-height:18px;font-weight:600;color:var(--dsw-alias-label-primary)}
6841
+ .codexSubscriptionResetTabs b{font-weight:400;color:var(--dsw-alias-label-secondary)}
6842
+ .codexSubscriptionResetBox .codexSubscriptionResetBalance{gap:0}
6843
+ .codexSubscriptionResetBox .codexSubscriptionResetCard{padding:12px 0;border:0;border-radius:0;background:none}
6844
+ .codexSubscriptionResetBox .codexSubscriptionResetCard+.codexSubscriptionResetCard{border-top:1px solid var(--dsw-alias-border-l2)}
6845
+ .codexSubscriptionResetBox .codexSubscriptionResetCard strong{font-size:13px;line-height:20px;font-weight:600}
6846
+ .codexSubscriptionResetBox .codexSubscriptionResetExpiry{font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary)}
6847
+ .codexSubscriptionResetBox .codexSubscriptionResetUse{min-height:28px;padding:0 12px;border:0!important;border-radius:8px;background:var(--dsw-alias-bg-module-platform);font-size:12px;font-weight:600}
6848
+ .codexSubscriptionResetBox>.codexSubscriptionResetBalance>.codexSubscriptionCreditNote{padding:12px 0}
6640
6849
  .codexSubscriptionResetBalance{display:flex;flex-direction:column;gap:8px}
6641
6850
  .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)}
6642
6851
  .codexSubscriptionResetCard{flex-wrap:wrap}
@@ -6901,6 +7110,9 @@ window.__ModuleLoader__.load({
6901
7110
  .dcsiv-inline-note{position:absolute;bottom:34px;box-sizing:border-box;display:grid;width:min(300px,calc(100vw - 40px));grid-template-columns:24px minmax(0,1fr) 24px;align-items:center;gap:7px;padding:7px 8px;border:1px solid rgba(255,255,255,.12);border-radius:18px;background:rgba(32,33,37,.94);box-shadow:0 14px 38px rgba(0,0,0,.38);color:#fff;pointer-events:auto;backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px)}.dcsiv-annotation[data-x=right] .dcsiv-inline-note{left:-8px}.dcsiv-annotation[data-x=left] .dcsiv-inline-note{right:-8px}.dcsiv-annotation[data-x=center] .dcsiv-inline-note{left:50%;transform:translateX(-50%)}.dcsiv-annotation[data-y=down] .dcsiv-inline-note{top:34px;bottom:auto}.dcsiv-inline-note::after{position:absolute;width:9px;height:9px;background:rgba(32,33,37,.94);content:'';transform:rotate(45deg)}.dcsiv-annotation[data-y=up] .dcsiv-inline-note::after{bottom:-5px}.dcsiv-annotation[data-y=down] .dcsiv-inline-note::after{top:-5px}.dcsiv-annotation[data-x=right] .dcsiv-inline-note::after{left:13px}.dcsiv-annotation[data-x=left] .dcsiv-inline-note::after{right:13px}.dcsiv-annotation[data-x=center] .dcsiv-inline-note::after{left:calc(50% - 4px)}.dcsiv-inline-index{display:grid;place-items:center;width:22px;height:22px;border-radius:50%;background:var(--dsw-alias-state-business-primary,#3964fe);color:#fff;font-size:10px;font-weight:700}.dcsiv-inline-note textarea{box-sizing:border-box;width:100%;min-height:24px;max-height:92px;resize:none;overflow:auto;border:0;outline:0;background:transparent;color:#fff;font:inherit;font-size:12px;line-height:18px}.dcsiv-inline-note textarea::placeholder{color:rgba(255,255,255,.44)}.dcsiv-note-remove{display:grid;place-items:center;width:24px;height:24px;padding:0;border:0;border-radius:50%;background:transparent;color:rgba(255,255,255,.68);cursor:pointer}.dcsiv-note-remove:hover{background:rgba(255,255,255,.1);color:#fff}
6902
7111
  .dcsiv-nav{position:absolute;top:50%;z-index:4;width:42px;height:42px;padding:0;transform:translateY(-50%);background:rgba(48,49,53,.82);box-shadow:0 8px 24px rgba(0,0,0,.28);backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px)}.dcsiv-prev{left:6px}.dcsiv-next{right:6px}.dcsiv-counter{position:absolute;bottom:8px;left:50%;padding:5px 10px;border-radius:999px;background:rgba(38,39,43,.86);color:rgba(255,255,255,.72);font-size:11px;transform:translateX(-50%);backdrop-filter:blur(16px)}
6903
7112
  .dcsiv-hint{display:none}
7113
+ .dcsiv-mask{position:absolute;inset:0;width:100%;height:100%;border-radius:12px;cursor:crosshair;touch-action:none}
7114
+ .dcsiv-tool-icon{width:16px;height:16px;flex:none}.dcsiv-menu{position:fixed;z-index:12;display:flex;flex-direction:column;min-width:150px;padding:6px;border:1px solid rgba(255,255,255,.12);border-radius:12px;background:rgba(32,33,37,.97);box-shadow:0 12px 32px rgba(0,0,0,.4)}.dcsiv-menu button{height:32px;padding:0 10px;border:0;border-radius:8px;background:none;color:rgba(255,255,255,.9);font:inherit;font-size:13px;text-align:left;cursor:pointer}.dcsiv-menu button:hover,.dcsiv-menu button:focus-visible{background:rgba(255,255,255,.1)}
7115
+ .dcsiv-erase-bar{position:absolute;left:50%;top:22px;z-index:7;display:flex;align-items:center;gap:10px;padding:6px 8px 6px 14px;transform:translateX(-50%);border:1px solid rgba(255,255,255,.1);border-radius:999px;background:rgba(38,39,43,.92);color:rgba(255,255,255,.86);font-size:12px;white-space:nowrap}.dcsiv-erase-bar label{display:inline-flex;align-items:center;gap:6px}.dcsiv-erase-bar input{width:90px}.dcsiv-primary{background:#fff!important;color:#111!important}
6904
7116
  .dcsiv-copy-notes{position:absolute;right:20px;bottom:22px;z-index:6;display:inline-flex;align-items:center;gap:6px;height:32px;padding:0 11px;border:1px solid rgba(255,255,255,.1);border-radius:999px;background:rgba(38,39,43,.86);color:rgba(255,255,255,.82);font:inherit;font-size:12px;cursor:pointer;backdrop-filter:blur(16px)}
6905
7117
  .dcsiv-edit-short{display:none}
6906
7118
  @media(max-width:760px){.dcsiv-actions{flex-wrap:wrap;justify-content:center}.dcsiv-edit-short{display:inline;white-space:normal;max-width:min(280px,calc(100vw - 80px))}.dcsiv-edit-action{height:auto;min-height:32px}.dcsiv-close-floating{top:12px;right:12px;width:40px;height:40px}.dcsiv-workspace{padding:60px 12px 68px}.dcsiv-image{max-width:calc(100vw - 24px);max-height:calc(100vh - 136px)}.dcsiv-topbar{bottom:12px;max-width:calc(100vw - 24px)}.dcsiv-button{padding:0 8px}.dcsiv-button span.dcsiv-label{display:none}.dcsiv-inline-note{width:min(260px,calc(100vw - 40px))}.dcsiv-copy-notes{display:none}}
@@ -7084,6 +7296,18 @@ window.__ModuleLoader__.load({
7084
7296
  }
7085
7297
  //#endregion
7086
7298
  //#region src/subscription-image-viewer.jsx
7299
+ const TOOL_ICONS = {
7300
+ "remove-background": ForwardRef$1,
7301
+ erase: ForwardRef$3,
7302
+ resize: ForwardRef$5
7303
+ };
7304
+ const ToolIcon = ({ id }) => {
7305
+ const Icon = TOOL_ICONS[id];
7306
+ return Icon ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
7307
+ className: "dcsiv-tool-icon",
7308
+ "aria-hidden": "true"
7309
+ }) : null;
7310
+ };
7087
7311
  const fill$1 = (value, variables) => Object.entries(variables).reduce((text, [key, replacement]) => text.replaceAll(`{${key}}`, String(replacement)), value);
7088
7312
  const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
7089
7313
  const bytesLabel = (bytes) => bytes === void 0 ? void 0 : bytes < 1024 * 1024 ? `${Math.max(.1, bytes / 1024).toLocaleString(void 0, { maximumFractionDigits: 1 })} KB` : `${(bytes / 1024 / 1024).toLocaleString(void 0, { maximumFractionDigits: 1 })} MB`;
@@ -7094,8 +7318,9 @@ window.__ModuleLoader__.load({
7094
7318
  const noteText = (annotations, t) => annotations.map((annotation, index) => {
7095
7319
  return `${fill$1(t("imageAnnotation"), { value: index + 1 })} (${Math.round(annotation.x * 100)}%, ${Math.round(annotation.y * 100)}%): ${annotation.note.trim()}`;
7096
7320
  }).filter((line) => !line.endsWith(": ")).join("\n");
7097
- function ViewerAction({ action, annotations, item, service, revision, t }) {
7321
+ function ViewerAction({ action, annotations, item, service, revision, t, onStartMask }) {
7098
7322
  const [state, setState] = (0, react.useState)("idle");
7323
+ const [menu, setMenu] = (0, react.useState)();
7099
7324
  const active = (0, react.useRef)(true);
7100
7325
  (0, react.useEffect)(() => {
7101
7326
  active.current = true;
@@ -7103,14 +7328,15 @@ window.__ModuleLoader__.load({
7103
7328
  active.current = false;
7104
7329
  };
7105
7330
  }, []);
7106
- const invoke = async () => {
7331
+ const invoke = async (extra = {}) => {
7107
7332
  if (state === "pending") return;
7108
7333
  setState("pending");
7109
7334
  try {
7110
7335
  await action.onInvoke({
7111
7336
  annotations,
7112
7337
  item,
7113
- src: item.src
7338
+ src: item.src,
7339
+ ...extra
7114
7340
  });
7115
7341
  if (!active.current || service.getSnapshot()?.revision !== revision) return;
7116
7342
  setState("idle");
@@ -7120,6 +7346,62 @@ window.__ModuleLoader__.load({
7120
7346
  }
7121
7347
  };
7122
7348
  const label = state === "pending" ? action.pendingLabel : state === "failed" ? action.errorLabel : action.label;
7349
+ if (action.kind === "mask") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
7350
+ type: "button",
7351
+ className: "dcsiv-button",
7352
+ onClick: () => onStartMask(action),
7353
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolIcon, { id: action.id }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7354
+ className: "dcsiv-label",
7355
+ children: action.label
7356
+ })]
7357
+ });
7358
+ if (action.kind === "tool") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
7359
+ type: "button",
7360
+ className: "dcsiv-button",
7361
+ disabled: state === "pending",
7362
+ onClick: () => {
7363
+ invoke();
7364
+ },
7365
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolIcon, { id: action.id }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7366
+ className: "dcsiv-label",
7367
+ children: label
7368
+ })]
7369
+ });
7370
+ if (action.kind === "choice") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
7371
+ type: "button",
7372
+ className: "dcsiv-button",
7373
+ "aria-haspopup": "menu",
7374
+ "aria-expanded": menu !== void 0,
7375
+ disabled: state === "pending",
7376
+ onClick: (event) => {
7377
+ const box = event.currentTarget.getBoundingClientRect();
7378
+ const host = event.currentTarget.closest(".dcsiv-root");
7379
+ setMenu((value) => value === void 0 && host ? {
7380
+ host,
7381
+ style: {
7382
+ left: box.left,
7383
+ bottom: window.innerHeight - box.top + 8
7384
+ }
7385
+ } : void 0);
7386
+ },
7387
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolIcon, { id: action.id }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7388
+ className: "dcsiv-label",
7389
+ children: label
7390
+ })]
7391
+ }), menu ? (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7392
+ className: "dcsiv-menu",
7393
+ role: "menu",
7394
+ style: menu.style,
7395
+ children: action.options.map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7396
+ type: "button",
7397
+ role: "menuitem",
7398
+ onClick: () => {
7399
+ setMenu(void 0);
7400
+ invoke({ option: option.value });
7401
+ },
7402
+ children: option.label
7403
+ }, option.value))
7404
+ }), menu.host) : null] });
7123
7405
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
7124
7406
  type: "button",
7125
7407
  className: "dcsiv-button dcsiv-edit-action",
@@ -7206,6 +7488,12 @@ window.__ModuleLoader__.load({
7206
7488
  const [selected, setSelected] = (0, react.useState)();
7207
7489
  const [focusNote, setFocusNote] = (0, react.useState)();
7208
7490
  const [copied, setCopied] = (0, react.useState)(false);
7491
+ const [erasing, setErasing] = (0, react.useState)();
7492
+ const [strokes, setStrokes] = (0, react.useState)([]);
7493
+ const [brush, setBrush] = (0, react.useState)(.05);
7494
+ const [sending, setSending] = (0, react.useState)(false);
7495
+ const drawingRef = (0, react.useRef)(false);
7496
+ const maskRef = (0, react.useRef)(null);
7209
7497
  const rootRef = (0, react.useRef)(null);
7210
7498
  annotationsByImageRef.current = annotationsByImage;
7211
7499
  (0, react.useEffect)(() => {
@@ -7298,11 +7586,85 @@ window.__ModuleLoader__.load({
7298
7586
  (0, react.useEffect)(() => {
7299
7587
  setAnnotating(false);
7300
7588
  setSelected(void 0);
7589
+ setErasing(void 0);
7590
+ setStrokes([]);
7301
7591
  }, [
7302
7592
  item?.id,
7303
7593
  item?.src,
7304
7594
  request?.revision
7305
7595
  ]);
7596
+ (0, react.useEffect)(() => {
7597
+ const canvas = maskRef.current;
7598
+ if (!erasing || canvas === null) return;
7599
+ const context = canvas.getContext("2d");
7600
+ context.clearRect(0, 0, canvas.width, canvas.height);
7601
+ paintEraseStrokes(context, strokes, canvas.width, canvas.height, "rgba(255,59,48,.5)");
7602
+ }, [erasing, strokes]);
7603
+ const strokePoint = (event) => {
7604
+ const bounds = maskRef.current?.getBoundingClientRect();
7605
+ if (bounds === void 0) return void 0;
7606
+ return {
7607
+ x: clamp((event.clientX - bounds.left) / bounds.width, 0, 1),
7608
+ y: clamp((event.clientY - bounds.top) / bounds.height, 0, 1)
7609
+ };
7610
+ };
7611
+ const maskHandlers = {
7612
+ onPointerDown: (event) => {
7613
+ const point = strokePoint(event);
7614
+ if (point === void 0) return;
7615
+ event.stopPropagation();
7616
+ event.currentTarget.setPointerCapture?.(event.pointerId);
7617
+ drawingRef.current = true;
7618
+ setStrokes((current) => [...current, {
7619
+ size: brush / transformRef.current.zoom,
7620
+ points: [point]
7621
+ }]);
7622
+ },
7623
+ onPointerMove: (event) => {
7624
+ if (!drawingRef.current) return;
7625
+ const point = strokePoint(event);
7626
+ if (point === void 0) return;
7627
+ setStrokes((current) => current.length === 0 ? current : [...current.slice(0, -1), {
7628
+ ...current.at(-1),
7629
+ points: [...current.at(-1).points, point]
7630
+ }]);
7631
+ },
7632
+ onPointerUp: () => {
7633
+ drawingRef.current = false;
7634
+ },
7635
+ onPointerCancel: () => {
7636
+ drawingRef.current = false;
7637
+ }
7638
+ };
7639
+ const startMask = (action) => {
7640
+ resetGesture();
7641
+ setAnnotating(false);
7642
+ setStrokes([]);
7643
+ setErasing(action);
7644
+ };
7645
+ const sendMask = async () => {
7646
+ const image = imageRef.current;
7647
+ if (!erasing || strokes.length === 0 || image === null || sending) return;
7648
+ setSending(true);
7649
+ try {
7650
+ const canvas = document.createElement("canvas");
7651
+ canvas.width = image.naturalWidth;
7652
+ canvas.height = image.naturalHeight;
7653
+ paintEraseMask(canvas.getContext("2d"), strokes, canvas.width, canvas.height);
7654
+ const mask = await new Promise((resolve, reject) => canvas.toBlob((blob) => blob ? resolve(blob) : reject(/* @__PURE__ */ new Error("Mask")), "image/png"));
7655
+ await erasing.onInvoke({
7656
+ mask,
7657
+ item,
7658
+ src: item.src,
7659
+ annotations: []
7660
+ });
7661
+ setErasing(void 0);
7662
+ setStrokes([]);
7663
+ if (erasing.closeOnSuccess) service.close();
7664
+ } finally {
7665
+ setSending(false);
7666
+ }
7667
+ };
7306
7668
  const addAnnotation = (event) => {
7307
7669
  if (!annotating || event.target.closest(".dcsiv-annotation")) return;
7308
7670
  const bounds = surfaceRef.current?.getBoundingClientRect();
@@ -7434,7 +7796,8 @@ window.__ModuleLoader__.load({
7434
7796
  item,
7435
7797
  service,
7436
7798
  revision: request.revision,
7437
- t
7799
+ t,
7800
+ onStartMask: startMask
7438
7801
  }, `${request.revision}:${index}:${action.id}`))
7439
7802
  ]
7440
7803
  })
@@ -7448,142 +7811,194 @@ window.__ModuleLoader__.load({
7448
7811
  }),
7449
7812
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7450
7813
  className: "dcsiv-workspace",
7451
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("main", {
7452
- ref: stageRef,
7453
- className: "dcsiv-stage",
7454
- "data-dragging": dragging,
7455
- "data-annotating": annotating,
7456
- onClick: (event) => {
7457
- if (event.target === event.currentTarget && !annotating && transform.zoom === 1) service.close();
7458
- },
7459
- ...!annotating ? pointerHandlers : {},
7460
- onDoubleClick: () => {
7461
- if (transform.zoom === 1) actual();
7462
- else fit();
7463
- },
7464
- children: [
7465
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7466
- ref: surfaceRef,
7467
- className: "dcsiv-surface",
7468
- onClick: addAnnotation,
7469
- style: { transform: `translate3d(${transform.x}px,${transform.y}px,0) scale(${transform.zoom})` },
7470
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
7471
- ref: imageRef,
7472
- className: "dcsiv-image",
7473
- src: item.src,
7474
- alt: item.name,
7475
- draggable: "false",
7476
- onLoad: () => {
7477
- measure();
7478
- }
7479
- }, `${request.revision}:${item.id}`)
7480
- }),
7481
- annotations.map((annotation, position) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7482
- className: "dcsiv-annotation",
7483
- "data-x": annotation.x < .38 ? "right" : annotation.x > .62 ? "left" : "center",
7484
- "data-y": annotation.y < .28 ? "down" : "up",
7485
- style: annotationPosition(annotation),
7486
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7487
- type: "button",
7488
- className: "dcsiv-pin",
7489
- "data-active": selected === annotation.id,
7490
- "aria-label": fill$1(t("imageAnnotation"), { value: position + 1 }),
7491
- onClick: (event) => {
7492
- event.stopPropagation();
7493
- const opening = selected !== annotation.id;
7494
- setSelected(opening ? annotation.id : void 0);
7495
- if (opening) setFocusNote(annotation.id);
7496
- },
7497
- children: position + 1
7498
- }), selected === annotation.id ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7499
- className: "dcsiv-inline-note",
7500
- "data-note-id": annotation.id,
7501
- onClick: (event) => event.stopPropagation(),
7502
- children: [
7503
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7504
- className: "dcsiv-inline-index",
7505
- children: position + 1
7506
- }),
7507
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
7508
- value: annotation.note,
7509
- rows: 1,
7510
- "aria-label": fill$1(t("imageAnnotation"), { value: position + 1 }),
7511
- placeholder: t("imageAnnotationPlaceholder"),
7512
- onChange: (event) => {
7513
- const note = event.target.value;
7514
- setAnnotations((current) => current.map((entry) => entry.id === annotation.id ? {
7515
- ...entry,
7516
- note
7517
- } : entry));
7518
- },
7519
- onKeyDown: (event) => {
7520
- if (event.key === "Enter" && !event.shiftKey || event.key === "Escape") {
7521
- event.preventDefault();
7814
+ children: [
7815
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("main", {
7816
+ ref: stageRef,
7817
+ className: "dcsiv-stage",
7818
+ "data-dragging": dragging,
7819
+ "data-annotating": annotating || erasing !== void 0,
7820
+ onClick: (event) => {
7821
+ if (event.target === event.currentTarget && !annotating && !erasing && transform.zoom === 1) service.close();
7822
+ },
7823
+ ...!annotating && !erasing ? pointerHandlers : {},
7824
+ onDoubleClick: () => {
7825
+ if (transform.zoom === 1) actual();
7826
+ else fit();
7827
+ },
7828
+ children: [
7829
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7830
+ ref: surfaceRef,
7831
+ className: "dcsiv-surface",
7832
+ onClick: addAnnotation,
7833
+ style: { transform: `translate3d(${transform.x}px,${transform.y}px,0) scale(${transform.zoom})` },
7834
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
7835
+ ref: imageRef,
7836
+ className: "dcsiv-image",
7837
+ src: item.src,
7838
+ alt: item.name,
7839
+ draggable: "false",
7840
+ onLoad: () => {
7841
+ measure();
7842
+ }
7843
+ }, `${request.revision}:${item.id}`), erasing ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("canvas", {
7844
+ ref: maskRef,
7845
+ className: "dcsiv-mask",
7846
+ width: imageRef.current?.naturalWidth || 1,
7847
+ height: imageRef.current?.naturalHeight || 1,
7848
+ "aria-label": t("imageEraseHint"),
7849
+ ...maskHandlers
7850
+ }) : null]
7851
+ }),
7852
+ annotations.map((annotation, position) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7853
+ className: "dcsiv-annotation",
7854
+ "data-x": annotation.x < .38 ? "right" : annotation.x > .62 ? "left" : "center",
7855
+ "data-y": annotation.y < .28 ? "down" : "up",
7856
+ style: annotationPosition(annotation),
7857
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7858
+ type: "button",
7859
+ className: "dcsiv-pin",
7860
+ "data-active": selected === annotation.id,
7861
+ "aria-label": fill$1(t("imageAnnotation"), { value: position + 1 }),
7862
+ onClick: (event) => {
7863
+ event.stopPropagation();
7864
+ const opening = selected !== annotation.id;
7865
+ setSelected(opening ? annotation.id : void 0);
7866
+ if (opening) setFocusNote(annotation.id);
7867
+ },
7868
+ children: position + 1
7869
+ }), selected === annotation.id ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7870
+ className: "dcsiv-inline-note",
7871
+ "data-note-id": annotation.id,
7872
+ onClick: (event) => event.stopPropagation(),
7873
+ children: [
7874
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7875
+ className: "dcsiv-inline-index",
7876
+ children: position + 1
7877
+ }),
7878
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
7879
+ value: annotation.note,
7880
+ rows: 1,
7881
+ "aria-label": fill$1(t("imageAnnotation"), { value: position + 1 }),
7882
+ placeholder: t("imageAnnotationPlaceholder"),
7883
+ onChange: (event) => {
7884
+ const note = event.target.value;
7885
+ setAnnotations((current) => current.map((entry) => entry.id === annotation.id ? {
7886
+ ...entry,
7887
+ note
7888
+ } : entry));
7889
+ },
7890
+ onKeyDown: (event) => {
7891
+ if (event.key === "Enter" && !event.shiftKey || event.key === "Escape") {
7892
+ event.preventDefault();
7893
+ event.stopPropagation();
7894
+ event.nativeEvent?.stopImmediatePropagation?.();
7895
+ setSelected(void 0);
7896
+ }
7897
+ }
7898
+ }),
7899
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7900
+ type: "button",
7901
+ className: "dcsiv-note-remove",
7902
+ "aria-label": t("imageRemoveAnnotation"),
7903
+ onClick: (event) => {
7522
7904
  event.stopPropagation();
7523
- event.nativeEvent?.stopImmediatePropagation?.();
7905
+ setAnnotations((current) => current.filter((entry) => entry.id !== annotation.id));
7524
7906
  setSelected(void 0);
7525
- }
7526
- }
7527
- }),
7528
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7529
- type: "button",
7530
- className: "dcsiv-note-remove",
7531
- "aria-label": t("imageRemoveAnnotation"),
7532
- onClick: (event) => {
7533
- event.stopPropagation();
7534
- setAnnotations((current) => current.filter((entry) => entry.id !== annotation.id));
7535
- setSelected(void 0);
7536
- },
7537
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconCloseOutline16, {})
7538
- })
7539
- ]
7540
- }) : null]
7541
- }, annotation.id)),
7542
- showCounter ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
7907
+ },
7908
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconCloseOutline16, {})
7909
+ })
7910
+ ]
7911
+ }) : null]
7912
+ }, annotation.id)),
7913
+ showCounter ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
7914
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7915
+ type: "button",
7916
+ className: "dcsiv-button dcsiv-icon-only dcsiv-nav dcsiv-prev",
7917
+ "aria-label": t("imagePrevious"),
7918
+ onClick: (event) => {
7919
+ event.stopPropagation();
7920
+ setIndex((value) => (value - 1 + request.items.length) % request.items.length);
7921
+ },
7922
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconChevronLeftOutline14, {})
7923
+ }),
7924
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7925
+ type: "button",
7926
+ className: "dcsiv-button dcsiv-icon-only dcsiv-nav dcsiv-next",
7927
+ "aria-label": t("imageNext"),
7928
+ onClick: (event) => {
7929
+ event.stopPropagation();
7930
+ setIndex((value) => (value + 1) % request.items.length);
7931
+ },
7932
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconChevronRightOutline14, {})
7933
+ }),
7934
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7935
+ className: "dcsiv-counter",
7936
+ children: [
7937
+ index + 1,
7938
+ " / ",
7939
+ request.items.length
7940
+ ]
7941
+ })
7942
+ ] }) : annotating ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7943
+ className: "dcsiv-hint",
7944
+ children: t("imageAnnotateHint")
7945
+ }) : transform.zoom === 1 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7946
+ className: "dcsiv-hint",
7947
+ children: t("imageZoomHint")
7948
+ }) : null
7949
+ ]
7950
+ }),
7951
+ erasing ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7952
+ className: "dcsiv-erase-bar",
7953
+ role: "toolbar",
7954
+ "aria-label": t("imageErase"),
7955
+ children: [
7956
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("imageEraseHint") }),
7957
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [t("imageBrushSize"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
7958
+ type: "range",
7959
+ min: "0.01",
7960
+ max: "0.15",
7961
+ step: "0.01",
7962
+ value: brush,
7963
+ onChange: (event) => setBrush(Number(event.target.value))
7964
+ })] }),
7543
7965
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7544
7966
  type: "button",
7545
- className: "dcsiv-button dcsiv-icon-only dcsiv-nav dcsiv-prev",
7546
- "aria-label": t("imagePrevious"),
7547
- onClick: (event) => {
7548
- event.stopPropagation();
7549
- setIndex((value) => (value - 1 + request.items.length) % request.items.length);
7550
- },
7551
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconChevronLeftOutline14, {})
7967
+ className: "dcsiv-button",
7968
+ disabled: strokes.length === 0 || sending,
7969
+ onClick: () => setStrokes((current) => current.slice(0, -1)),
7970
+ children: t("imageUndo")
7552
7971
  }),
7553
7972
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7554
7973
  type: "button",
7555
- className: "dcsiv-button dcsiv-icon-only dcsiv-nav dcsiv-next",
7556
- "aria-label": t("imageNext"),
7557
- onClick: (event) => {
7558
- event.stopPropagation();
7559
- setIndex((value) => (value + 1) % request.items.length);
7974
+ className: "dcsiv-button",
7975
+ disabled: sending,
7976
+ onClick: () => {
7977
+ setErasing(void 0);
7978
+ setStrokes([]);
7560
7979
  },
7561
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconChevronRightOutline14, {})
7980
+ children: t("cancel")
7562
7981
  }),
7563
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7564
- className: "dcsiv-counter",
7565
- children: [
7566
- index + 1,
7567
- " / ",
7568
- request.items.length
7569
- ]
7982
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7983
+ type: "button",
7984
+ className: "dcsiv-button dcsiv-primary",
7985
+ disabled: strokes.length === 0 || sending,
7986
+ onClick: () => {
7987
+ sendMask();
7988
+ },
7989
+ children: sending ? t("imageEditPreparing") : t("imageSend")
7570
7990
  })
7571
- ] }) : annotating ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7572
- className: "dcsiv-hint",
7573
- children: t("imageAnnotateHint")
7574
- }) : transform.zoom === 1 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7575
- className: "dcsiv-hint",
7576
- children: t("imageZoomHint")
7577
- }) : null
7578
- ]
7579
- }), annotations.some((annotation) => annotation.note.trim() !== "") ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
7580
- type: "button",
7581
- className: "dcsiv-copy-notes",
7582
- onClick: () => {
7583
- copyNotes();
7584
- },
7585
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconCopyOutline16, {}), copied ? t("imageCopied") : t("imageCopyNotes")]
7586
- }) : null]
7991
+ ]
7992
+ }) : null,
7993
+ annotations.some((annotation) => annotation.note.trim() !== "") ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
7994
+ type: "button",
7995
+ className: "dcsiv-copy-notes",
7996
+ onClick: () => {
7997
+ copyNotes();
7998
+ },
7999
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconCopyOutline16, {}), copied ? t("imageCopied") : t("imageCopyNotes")]
8000
+ }) : null
8001
+ ]
7587
8002
  })
7588
8003
  ]
7589
8004
  });
@@ -7604,6 +8019,12 @@ window.__ModuleLoader__.load({
7604
8019
  pendingLabel: typeof action.pendingLabel === "string" && action.pendingLabel !== "" ? action.pendingLabel : action.label,
7605
8020
  errorLabel: typeof action.errorLabel === "string" && action.errorLabel !== "" ? action.errorLabel : action.label,
7606
8021
  closeOnSuccess: action.closeOnSuccess === true,
8022
+ kind: [
8023
+ "tool",
8024
+ "mask",
8025
+ "choice"
8026
+ ].includes(action.kind) ? action.kind : "edit",
8027
+ options: Array.isArray(action.options) ? action.options.filter((option) => typeof option?.value === "string" && typeof option?.label === "string") : [],
7607
8028
  onInvoke: action.onInvoke
7608
8029
  }];
7609
8030
  }) : [];
@@ -8531,7 +8952,7 @@ window.__ModuleLoader__.load({
8531
8952
  }
8532
8953
  //#endregion
8533
8954
  //#region src/version.js
8534
- const PACKAGE_VERSION = "2.5.6";
8955
+ const PACKAGE_VERSION = "2.5.8";
8535
8956
  //#endregion
8536
8957
  //#region src/client-recovery.js
8537
8958
  async function recoveryCall(rpc, endpoint, payload = {}, timeoutMs = 1e4) {
@@ -11247,7 +11668,10 @@ window.__ModuleLoader__.load({
11247
11668
  children: date === void 0 ? t("resetCreditExpiryUnknown") : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("time", {
11248
11669
  dateTime: date.toISOString(),
11249
11670
  title: date.toLocaleString(),
11250
- children: fill(t("resetCreditExpires"), { value: date.toLocaleString() })
11671
+ children: fill(t("resetCreditExpires"), { value: date.toLocaleDateString(void 0, {
11672
+ month: "long",
11673
+ day: "numeric"
11674
+ }) })
11251
11675
  })
11252
11676
  });
11253
11677
  }
@@ -11349,7 +11773,7 @@ window.__ModuleLoader__.load({
11349
11773
  children: [
11350
11774
  challenge === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
11351
11775
  className: "codexSubscriptionResetMeta",
11352
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: credit.name ?? t("resetCreditDefaultName") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ResetCreditExpiry, {
11776
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: credit.name === void 0 ? t("resetCreditDefaultName") : /^full reset$/iu.test(credit.name) ? t("resetFull") : credit.name }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ResetCreditExpiry, {
11353
11777
  expiresAt: credit.expiresAt,
11354
11778
  t
11355
11779
  })]
@@ -11626,17 +12050,27 @@ window.__ModuleLoader__.load({
11626
12050
  cloud: visibleUsage.credits.approxCloudMessages ? `${count(visibleUsage.credits.approxCloudMessages[0])}–${count(visibleUsage.credits.approxCloudMessages[1])}` : t("unavailable")
11627
12051
  })
11628
12052
  }) : null,
11629
- visibleUsage?.resetCredits?.availableCount > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
11630
- className: "codexSubscriptionCreditBalance codexSubscriptionResetDisclosure",
11631
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("summary", { children: fill(t("resetCreditsAvailable"), { count: visibleUsage.resetCredits.availableCount }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ResetCreditList, {
11632
- rpc,
11633
- t,
11634
- count: visibleUsage.resetCredits.availableCount,
11635
- nextExpiresAt: visibleUsage.resetCredits.nextExpiresAt,
11636
- initialCredits: visibleUsage.resetCredits.credits,
11637
- refreshKey: `${resetKey}:${usageRefreshGeneration}`,
11638
- hasExhaustedQuota: exhausted,
11639
- onConsumed: () => load(true)
12053
+ visibleUsage?.resetCredits?.availableCount > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
12054
+ className: "codexSubscriptionResetPanel",
12055
+ "aria-label": t("resetCredits"),
12056
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
12057
+ className: "codexSubscriptionResetHead",
12058
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t("resetCredits") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("resetCreditsHint") })]
12059
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
12060
+ className: "codexSubscriptionResetBox",
12061
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
12062
+ className: "codexSubscriptionResetTabs",
12063
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [t("resetAvailableTab"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: visibleUsage.resetCredits.availableCount })] })
12064
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ResetCreditList, {
12065
+ rpc,
12066
+ t,
12067
+ count: visibleUsage.resetCredits.availableCount,
12068
+ nextExpiresAt: visibleUsage.resetCredits.nextExpiresAt,
12069
+ initialCredits: visibleUsage.resetCredits.credits,
12070
+ refreshKey: `${resetKey}:${usageRefreshGeneration}`,
12071
+ hasExhaustedQuota: exhausted,
12072
+ onConsumed: () => load(true)
12073
+ })]
11640
12074
  })]
11641
12075
  }) : null,
11642
12076
  visibleUsage?.individualLimit ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -12130,7 +12564,8 @@ window.__ModuleLoader__.load({
12130
12564
  imageViewer.close();
12131
12565
  liveOpen("sketch", document.activeElement, new File([blob], name || "image.png", { type: blob.type || "image/png" }));
12132
12566
  };
12133
- const attachForEdit = (sessionId) => async (src, filename, draft, annotations = [], referenceName, sourceInDraft = false) => {
12567
+ const attachForEdit = (sessionId) => async (src, filename, draft, annotations = [], referenceName, sourceInDraft = false, extraFiles) => {
12568
+ const tool = Array.isArray(extraFiles);
12134
12569
  if (!preference.getSnapshot().imageEditing) throw new Error("Image editing is disabled");
12135
12570
  return withComposerSession(sessions, sessionId, async (actx) => {
12136
12571
  if (conversation.input?.for === void 0) throw new Error("This DSH version does not provide the image composer bridge");
@@ -12142,14 +12577,15 @@ window.__ModuleLoader__.load({
12142
12577
  const reference = await createAnnotatedImageReference(blob, annotations);
12143
12578
  files.push(new File([reference], referenceName, { type: "image/png" }));
12144
12579
  }
12580
+ if (tool) files.push(...extraFiles);
12145
12581
  lifetime.signal.throwIfAborted();
12146
12582
  const input = conversation.input.for(actx);
12147
12583
  if (!preference.getSnapshot().imageEditing) throw new Error("Image editing is disabled");
12148
12584
  if (files.length) attachImageFiles(conversation, input, files, sessionId);
12149
12585
  openComposerSession(sessions, ctx.get("uiWorkspace"), sessionId);
12150
- if (sourceInDraft && !annotations.length) return;
12586
+ if (sourceInDraft && !annotations.length && !tool) return;
12151
12587
  if (!input.state.getSnapshot().draft.trim()) input.setDraft(draft);
12152
- else if (annotations.length) if (!input.state.getSnapshot().occurrences?.length) appendImagePrompt(input, draft);
12588
+ else if (annotations.length || tool) if (!input.state.getSnapshot().occurrences?.length) appendImagePrompt(input, draft);
12153
12589
  else input.notify("info", draft);
12154
12590
  });
12155
12591
  };
package/lib/index.js CHANGED
@@ -2049,7 +2049,7 @@ function inheritedOriginalImageRef(session, assetId) {
2049
2049
  }
2050
2050
  //#endregion
2051
2051
  //#region src/version.js
2052
- const PACKAGE_VERSION = "2.5.6";
2052
+ const PACKAGE_VERSION = "2.5.8";
2053
2053
  const USER_AGENT = `dsh-codex-subscription/${PACKAGE_VERSION}`;
2054
2054
  //#endregion
2055
2055
  //#region src/codex-images.js
@@ -3590,6 +3590,8 @@ function withInputImageDetail(payload, detail) {
3590
3590
  }
3591
3591
  const DSH_REVIEW_POLICY_PREFIX = "REVIEW_POLICY\nYou are the final authorization reviewer";
3592
3592
  const REVIEW_MODEL_SESSION = "session";
3593
+ const REVIEW_RETRIES = 2;
3594
+ const TRANSIENT_REVIEW_FAILURE = /stream ended (?:before|without)|\b(?:network|connection|socket)\b|\bECONN[A-Z]+\b|other side closed|premature close|\bterminated\b|WebSocket closed (?:1006|1011|1012|1013)/iu;
3593
3595
  function systemText(context) {
3594
3596
  if (typeof context?.systemPrompt === "string") return context.systemPrompt;
3595
3597
  const first = Array.isArray(context?.messages) ? context.messages[0] : void 0;
@@ -3683,7 +3685,8 @@ function openaiCodexSubscriptionProvider({ resolveSpeedMode = () => void 0, reso
3683
3685
  const review = {
3684
3686
  requests: 0,
3685
3687
  routed: 0,
3686
- sessionModel: 0
3688
+ sessionModel: 0,
3689
+ retries: 0
3687
3690
  };
3688
3691
  const reviewRoute = (model, context) => {
3689
3692
  if (resolveReviewModel() !== "official" || !isDshAutoReview(context)) return void 0;
@@ -3710,6 +3713,34 @@ function openaiCodexSubscriptionProvider({ resolveSpeedMode = () => void 0, reso
3710
3713
  }
3711
3714
  };
3712
3715
  };
3716
+ const reviewIterable = (create) => {
3717
+ let replay;
3718
+ const run = async function* () {
3719
+ for (let attempt = 0;; attempt += 1) {
3720
+ const events = [];
3721
+ let failure;
3722
+ let thrown;
3723
+ try {
3724
+ for await (const event of create()) {
3725
+ events.push(event);
3726
+ if (event?.type === "error") failure = event.error?.errorMessage;
3727
+ }
3728
+ } catch (error) {
3729
+ thrown = error;
3730
+ failure = error?.message;
3731
+ }
3732
+ if (failure !== void 0 && attempt < REVIEW_RETRIES && TRANSIENT_REVIEW_FAILURE.test(failure)) {
3733
+ review.retries += 1;
3734
+ continue;
3735
+ }
3736
+ if (thrown !== void 0 && events.length === 0) throw thrown;
3737
+ yield* events;
3738
+ if (thrown !== void 0) throw thrown;
3739
+ return;
3740
+ }
3741
+ };
3742
+ return { [Symbol.asyncIterator]: () => replay ??= run() };
3743
+ };
3713
3744
  const networkIterable = (factory, options, direct = false) => {
3714
3745
  let iterator;
3715
3746
  let prepared;
@@ -3739,6 +3770,11 @@ function openaiCodexSubscriptionProvider({ resolveSpeedMode = () => void 0, reso
3739
3770
  throw: (error) => iterator ? step("throw", error) : Promise.reject(error)
3740
3771
  };
3741
3772
  };
3773
+ const routed = (method, model, context, options) => {
3774
+ const route = reviewRoute(model, context);
3775
+ const create = () => route === void 0 ? networkIterable((prepared) => provider[method](model, context, prepared), withPreferences(model, options)) : networkIterable((prepared) => provider[method](model, context, prepared), withReview(route, options), true);
3776
+ return isDshAutoReview(context) ? reviewIterable(create) : create();
3777
+ };
3742
3778
  return Object.freeze({
3743
3779
  ...provider,
3744
3780
  auth: Object.freeze({
@@ -3746,14 +3782,8 @@ function openaiCodexSubscriptionProvider({ resolveSpeedMode = () => void 0, reso
3746
3782
  apiKey: requestToken
3747
3783
  }),
3748
3784
  getModels,
3749
- stream: (model, context, options) => {
3750
- const route = reviewRoute(model, context);
3751
- return route === void 0 ? networkIterable((prepared) => provider.stream(model, context, prepared), withPreferences(model, options)) : networkIterable((prepared) => provider.stream(model, context, prepared), withReview(route, options), true);
3752
- },
3753
- streamSimple: (model, context, options) => {
3754
- const route = reviewRoute(model, context);
3755
- return route === void 0 ? networkIterable((prepared) => provider.streamSimple(model, context, prepared), withPreferences(model, options)) : networkIterable((prepared) => provider.streamSimple(model, context, prepared), withReview(route, options), true);
3756
- },
3785
+ stream: (model, context, options) => routed("stream", model, context, options),
3786
+ streamSimple: (model, context, options) => routed("streamSimple", model, context, options),
3757
3787
  reviewCounters: () => ({ ...review })
3758
3788
  });
3759
3789
  }
@@ -4759,7 +4789,8 @@ async function createSubscriptionDiagnostics({ auth, preferences, login = { phas
4759
4789
  () => typeof review?.reviewCounters === "function" ? safeCounters({ snapshot: () => review.reviewCounters() }, [
4760
4790
  "requests",
4761
4791
  "routed",
4762
- "sessionModel"
4792
+ "sessionModel",
4793
+ "retries"
4763
4794
  ]) : void 0
4764
4795
  ],
4765
4796
  [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-codex-subscription",
3
- "version": "2.5.6",
3
+ "version": "2.5.8",
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",