dsh-codex-subscription 1.9.0 → 1.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -59,18 +59,45 @@ window.__ModuleLoader__.load({
59
59
  const base = prompt.trim() === "" ? translate("imageEditDefault") : prompt.trim();
60
60
  return notes.length === 0 ? base : base + "\n\n" + translate("imageRegionNotes") + "\n" + notes.join("\n");
61
61
  }
62
+ const ORIGINAL_IMAGE_ID_PATTERN = /^img_[0-9a-f]{32}$/u;
63
+ const positiveInteger = (value) => Number.isSafeInteger(value) && value > 0;
64
+ function decodeOriginalImageRef(value) {
65
+ if (value === null || typeof value !== "object" || Array.isArray(value) || typeof value.assetId !== "string" || !ORIGINAL_IMAGE_ID_PATTERN.test(value.assetId) || value.mediaType !== "image/png" || !positiveInteger(value.bytes) || value.bytes > 48 * 1024 * 1024 || !positiveInteger(value.width) || !positiveInteger(value.height) || typeof value.name !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u.test(value.name) || typeof value.sha256 !== "string" || !/^[0-9a-f]{64}$/u.test(value.sha256)) return void 0;
66
+ return {
67
+ assetId: value.assetId,
68
+ mediaType: value.mediaType,
69
+ bytes: value.bytes,
70
+ width: value.width,
71
+ height: value.height,
72
+ name: value.name,
73
+ sha256: value.sha256
74
+ };
75
+ }
76
+ function decodeImagePresentation(value) {
77
+ if (value === null || typeof value !== "object" || Array.isArray(value) || value.kind !== "codex-subscription-image" || value.schemaVersion !== 1) return void 0;
78
+ const original = decodeOriginalImageRef(value.original);
79
+ return original === void 0 ? void 0 : { original };
80
+ }
62
81
  //#endregion
63
82
  //#region src/settings-contract.js
64
83
  const SETTINGS_NAMESPACE = "codex-subscription";
65
84
  const QUICK_QUOTA_MODE_FIELD = "quickQuotaMode";
66
85
  const LEGACY_QUICK_QUOTA_FIELD = "quickQuotaVisible";
67
86
  const QUICK_QUOTA_MODE_PERCENT = "percent";
87
+ const QUICK_QUOTA_MODE_FORECAST = "forecast";
68
88
  const SEARCH_PROVIDER_FIELD = "searchProvider";
89
+ const SEARCH_PROVIDER_AUTO = "auto";
69
90
  const SEARCH_PROVIDER_CODEX = "codex";
91
+ const DEFAULT_SEARCH_PROVIDER = SEARCH_PROVIDER_AUTO;
70
92
  const SPEED_MODE_FIELD = "speedMode";
71
93
  const SPEED_MODE_STANDARD = "standard";
72
94
  const SPEED_MODE_FAST = "fast";
73
95
  const DEFAULT_SPEED_MODE = SPEED_MODE_STANDARD;
96
+ const OUTPUT_VERBOSITY_FIELD = "outputVerbosity";
97
+ const OUTPUT_VERBOSITY_DEFAULT = "default";
98
+ const OUTPUT_VERBOSITY_MEDIUM = "medium";
99
+ const OUTPUT_VERBOSITY_HIGH = "high";
100
+ const DEFAULT_OUTPUT_VERBOSITY = OUTPUT_VERBOSITY_DEFAULT;
74
101
  const CONTEXT_MODE_FIELD = "contextMode";
75
102
  const CONTEXT_MODE_STANDARD = "standard";
76
103
  const CONTEXT_MODE_EXTENDED = "extended";
@@ -98,7 +125,17 @@ window.__ModuleLoader__.load({
98
125
  "gpt-5.5": 272e3,
99
126
  "gpt-5.6": 272e3
100
127
  });
101
- const normalizeSearchProvider = (value) => ["dsh", "codex"].includes(value) ? value : "dsh";
128
+ const normalizeSearchProvider = (value) => [
129
+ "auto",
130
+ "dsh",
131
+ "codex"
132
+ ].includes(value) ? value : DEFAULT_SEARCH_PROVIDER;
133
+ const normalizeOutputVerbosity = (value) => [
134
+ "default",
135
+ "low",
136
+ "medium",
137
+ "high"
138
+ ].includes(value) ? value : DEFAULT_OUTPUT_VERBOSITY;
102
139
  const normalizeSpeedMode = (value) => ["standard", "fast"].includes(value) ? value : DEFAULT_SPEED_MODE;
103
140
  const normalizeContextMode = (value) => [
104
141
  "standard",
@@ -118,7 +155,8 @@ window.__ModuleLoader__.load({
118
155
  const normalizeQuickQuotaMode = (value, legacyVisible = false) => [
119
156
  "off",
120
157
  "percent",
121
- "bar"
158
+ "bar",
159
+ "forecast"
122
160
  ].includes(value) ? value : legacyVisible === true ? QUICK_QUOTA_MODE_PERCENT : "off";
123
161
  const supportsCodexFastMode = (modelId) => typeof modelId === "string" && (/^gpt-5\.(?:5|6)(?:$|-)/u.test(modelId) || modelId === "gpt-5.4");
124
162
  //#endregion
@@ -136,7 +174,8 @@ window.__ModuleLoader__.load({
136
174
  return {
137
175
  remainingPercent: selected.remainingPercent,
138
176
  windowSeconds: selected.windowSeconds,
139
- ...Number.isSafeInteger(selected.resetsAt) ? { resetsAt: selected.resetsAt } : {}
177
+ ...Number.isSafeInteger(selected.resetsAt) ? { resetsAt: selected.resetsAt } : {},
178
+ ...selected.forecast === void 0 ? {} : { forecast: selected.forecast }
140
179
  };
141
180
  }
142
181
  //#endregion
@@ -223,7 +262,9 @@ window.__ModuleLoader__.load({
223
262
  diagnosticsFailed: "无法生成诊断信息。",
224
263
  feedbackOpen: "反馈问题",
225
264
  searchTitle: "搜索来源",
226
- searchScope: "此设置对所有模型和会话生效,不会随当前模型自动切换。",
265
+ searchScope: "自动按当前会话模型分流;手动选择会覆盖所有模型和会话。",
266
+ searchAuto: "自动",
267
+ searchAutoHint: "Codex 模型用订阅搜索,其他模型用 DSH",
227
268
  searchDsh: "DSH 默认",
228
269
  searchDshHint: "所有模型使用 DSH 当前搜索服务",
229
270
  searchCodex: "Codex 订阅",
@@ -285,6 +326,8 @@ window.__ModuleLoader__.load({
285
326
  quickQuotaOff: "关闭",
286
327
  quickQuotaPercent: "百分比",
287
328
  quickQuotaBar: "进度条",
329
+ quickQuotaForecast: "续航预测",
330
+ quickQuotaBeta: "Beta",
288
331
  contextTitle: "上下文窗口",
289
332
  contextStandard: "标准",
290
333
  contextStandardHint: "使用模型目录默认值;官方 Agent 预设会自动管理上下文。",
@@ -296,12 +339,27 @@ window.__ModuleLoader__.load({
296
339
  contextFixed: "固定 {value}",
297
340
  contextMaximum: "范围 128000–{value}",
298
341
  quickQuotaStatus: "Codex 剩余额度 {value}%",
342
+ quickQuotaForecastStatus: "Codex 剩余额度 {value}%,按当前速度预计可用 {duration}",
343
+ quotaForecast: "按当前速度 {symbol}{duration}",
344
+ runwayDaysHours: "{days} 天 {hours} 小时",
345
+ runwayDays: "{days} 天",
346
+ runwayHours: "{hours} 小时",
347
+ runwayMinutes: "{minutes} 分钟",
299
348
  speedTitle: "速度",
300
349
  speedStandard: "标准",
301
350
  speedStandardHint: "标准速度",
302
351
  speedFast: "高速",
303
352
  speedFastHint: "1.5 倍,消耗更多 Credits",
304
- modelMenuAria: "模型、推理等级与速度",
353
+ verbosityTitle: "输出详略",
354
+ verbosityDefault: "模型默认",
355
+ verbosityDefaultHint: "使用官方模型目录推荐值",
356
+ verbosityLow: "简洁",
357
+ verbosityLowHint: "更短、更直接",
358
+ verbosityMedium: "均衡",
359
+ verbosityMediumHint: "兼顾完整性与长度",
360
+ verbosityHigh: "详细",
361
+ verbosityHighHint: "更充分的说明与结构",
362
+ modelMenuAria: "模型、推理等级、速度与输出详略",
305
363
  modelLabel: "模型",
306
364
  effortLabel: "推理等级",
307
365
  providerDefault: "Default",
@@ -318,13 +376,16 @@ window.__ModuleLoader__.load({
318
376
  imageGenerated: "已生成",
319
377
  imageFailed: "生成失败",
320
378
  imageLabel: "生成的图片",
321
- imageOpen: "查看原图",
379
+ imageOpen: "查看图片",
322
380
  imageOpenNamed: "查看 {value}",
323
381
  imageLoading: "正在加载图片…",
324
382
  imageLoadFailed: "图片加载失败,点击重试",
325
383
  imagePreview: "图片预览",
384
+ imagePreviewShort: "预览图",
326
385
  imageClosePreview: "关闭预览",
327
- imageDownload: "下载原图",
386
+ imageDownload: "下载",
387
+ imageDownloadPreparing: "正在准备原图…",
388
+ imageDownloadFailed: "下载失败,重试",
328
389
  imageZoomOut: "缩小",
329
390
  imageZoomIn: "放大",
330
391
  imageFit: "适合窗口",
@@ -366,7 +427,9 @@ window.__ModuleLoader__.load({
366
427
  diagnosticsFailed: "Could not create diagnostics.",
367
428
  feedbackOpen: "Report a problem",
368
429
  searchTitle: "Search source",
369
- searchScope: "Applies to every model and session; it does not switch with the selected model.",
430
+ searchScope: "Auto follows the current session model; an explicit choice overrides every model and session.",
431
+ searchAuto: "Auto",
432
+ searchAutoHint: "Codex models use subscription search; other models use DSH",
370
433
  searchDsh: "DSH default",
371
434
  searchDshHint: "Use DSH's current search service for every model",
372
435
  searchCodex: "Codex subscription",
@@ -428,6 +491,8 @@ window.__ModuleLoader__.load({
428
491
  quickQuotaOff: "Off",
429
492
  quickQuotaPercent: "Percent",
430
493
  quickQuotaBar: "Progress bar",
494
+ quickQuotaForecast: "Runway",
495
+ quickQuotaBeta: "Beta",
431
496
  contextTitle: "Context window",
432
497
  contextStandard: "Standard",
433
498
  contextStandardHint: "Use the model catalog default; official agent presets manage context automatically.",
@@ -439,12 +504,27 @@ window.__ModuleLoader__.load({
439
504
  contextFixed: "Fixed {value}",
440
505
  contextMaximum: "128000–{value}",
441
506
  quickQuotaStatus: "Codex quota: {value}% remaining",
507
+ quickQuotaForecastStatus: "Codex quota: {value}% remaining; about {duration} at the current pace",
508
+ quotaForecast: "At current pace {symbol}{duration}",
509
+ runwayDaysHours: "{days}d {hours}h",
510
+ runwayDays: "{days}d",
511
+ runwayHours: "{hours}h",
512
+ runwayMinutes: "{minutes}m",
442
513
  speedTitle: "Speed",
443
514
  speedStandard: "Standard",
444
515
  speedStandardHint: "Standard speed",
445
516
  speedFast: "Fast",
446
517
  speedFastHint: "1.5x; higher Credits use",
447
- modelMenuAria: "Model, effort, and speed",
518
+ verbosityTitle: "Output detail",
519
+ verbosityDefault: "Model default",
520
+ verbosityDefaultHint: "Use the official model catalog recommendation",
521
+ verbosityLow: "Concise",
522
+ verbosityLowHint: "Shorter and more direct",
523
+ verbosityMedium: "Balanced",
524
+ verbosityMediumHint: "Balance completeness and length",
525
+ verbosityHigh: "Detailed",
526
+ verbosityHighHint: "More explanation and structure",
527
+ modelMenuAria: "Model, effort, speed, and output detail",
448
528
  modelLabel: "Model",
449
529
  effortLabel: "Effort",
450
530
  providerDefault: "Default",
@@ -461,13 +541,16 @@ window.__ModuleLoader__.load({
461
541
  imageGenerated: "Generated",
462
542
  imageFailed: "Generation failed",
463
543
  imageLabel: "Generated image",
464
- imageOpen: "View original",
544
+ imageOpen: "View image",
465
545
  imageOpenNamed: "View {value}",
466
546
  imageLoading: "Loading image…",
467
547
  imageLoadFailed: "Image failed to load. Click to retry",
468
548
  imagePreview: "Image preview",
549
+ imagePreviewShort: "Preview",
469
550
  imageClosePreview: "Close preview",
470
- imageDownload: "Download original",
551
+ imageDownload: "Download",
552
+ imageDownloadPreparing: "Preparing original…",
553
+ imageDownloadFailed: "Download failed. Retry",
471
554
  imageZoomOut: "Zoom out",
472
555
  imageZoomIn: "Zoom in",
473
556
  imageFit: "Fit to window",
@@ -492,10 +575,10 @@ window.__ModuleLoader__.load({
492
575
  .codexSubscriptionNote{font-size:13px;line-height:20px;color:var(--dsw-alias-label-tertiary)}
493
576
  .codexSubscriptionCard{border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-layer-1);padding:14px 16px;display:flex;flex-direction:column;gap:12px}
494
577
  .codexSubscriptionUsageCard{padding:12px 14px;gap:9px}.codexSubscriptionPreferencesCard{padding:12px 14px;gap:10px}.codexSubscriptionPreference{min-height:32px;box-sizing:border-box;display:flex;align-items:center;justify-content:space-between;gap:12px;color:var(--dsw-alias-label-primary);font-size:13px;line-height:20px}.codexSubscriptionPreferenceCopy{display:flex;min-width:0;flex-direction:column;gap:2px}.codexSubscriptionPreferenceLabel{display:flex;align-items:center;gap:6px}
495
- .codexSubscriptionQuotaModes{display:flex;align-items:center;gap:3px;padding:2px;border-radius:9px;background:var(--dsw-alias-bg-module-platform)}.codexSubscriptionQuotaMode{position:relative;display:flex;align-items:center;justify-content:center;min-height:26px;padding:0 9px;border-radius:7px;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px;cursor:pointer}.codexSubscriptionQuotaMode:has(input:checked){background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);box-shadow:0 0 0 1px var(--dsw-alias-border-l3)}.codexSubscriptionQuotaMode:has(input:focus-visible){outline:2px solid var(--dsw-alias-border-l3);outline-offset:1px}.codexSubscriptionQuotaMode:has(input:disabled){cursor:not-allowed;opacity:.5}.codexSubscriptionQuotaMode input{position:absolute;width:1px;height:1px;opacity:0;pointer-events:none}
578
+ .codexSubscriptionQuotaModes{display:flex;align-items:center;gap:3px;padding:2px;border-radius:9px;background:var(--dsw-alias-bg-module-platform)}.codexSubscriptionQuotaMode{position:relative;display:flex;align-items:center;justify-content:center;min-height:26px;padding:0 9px;border-radius:7px;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px;cursor:pointer;white-space:nowrap}.codexSubscriptionQuotaMode small{margin-left:3px;font-size:9px;line-height:1;color:var(--dsw-alias-label-tertiary)}.codexSubscriptionQuotaMode:has(input:checked){background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);box-shadow:0 0 0 1px var(--dsw-alias-border-l3)}.codexSubscriptionQuotaMode:has(input:focus-visible){outline:2px solid var(--dsw-alias-border-l3);outline-offset:1px}.codexSubscriptionQuotaMode:has(input:disabled){cursor:not-allowed;opacity:.5}.codexSubscriptionQuotaMode input{position:absolute;width:1px;height:1px;opacity:0;pointer-events:none}
496
579
  .codexSubscriptionContext{display:flex;flex-direction:column;gap:8px}.codexSubscriptionContextHead{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}.codexSubscriptionContextCopy{display:flex;min-width:0;flex:1;flex-direction:column;gap:2px}.codexSubscriptionContextHint{font-size:11px;line-height:17px;color:var(--dsw-alias-label-tertiary)}.codexSubscriptionContextTrigger{height:32px;min-width:108px;display:inline-flex;align-items:center;justify-content:space-between;gap:10px;padding:0 10px 0 12px;border:0;border-radius:999px;outline:0;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-primary);font:inherit;font-size:12px;cursor:pointer}.codexSubscriptionContextTrigger:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.codexSubscriptionContextTrigger:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-border-l3)}.codexSubscriptionContextTrigger:disabled{color:var(--dsw-alias-label-dimmed);cursor:not-allowed}.codexSubscriptionContextTrigger svg{color:var(--dsw-alias-label-tertiary);transition:transform 120ms var(--ds-ease-in-out)}.codexSubscriptionContextTrigger[aria-expanded=true] svg{transform:rotate(180deg)}.codexSubscriptionContextModels{display:flex;flex-direction:column;border-top:1px solid var(--dsw-alias-border-l2)}.codexSubscriptionContextModel{min-height:42px;display:flex;align-items:center;justify-content:space-between;gap:12px;border-bottom:1px solid var(--dsw-alias-border-l2)}.codexSubscriptionContextModel:last-child{border-bottom:0}.codexSubscriptionContextModelCopy{display:flex;min-width:0;flex-direction:column}.codexSubscriptionContextModelCopy strong{font-size:12px;line-height:18px;font-weight:500}.codexSubscriptionContextModelCopy span{font-size:11px;line-height:16px;color:var(--dsw-alias-label-tertiary)}.codexSubscriptionContextInput{width:116px}
497
580
  .codexSubscriptionSwitch{position:relative;flex:0 0 auto;width:32px;height:18px;padding:0;border:1px solid var(--dsw-alias-border-l3);border-radius:999px;background:var(--dsw-alias-bg-module-platform);cursor:pointer}.codexSubscriptionSwitch:disabled{cursor:not-allowed;opacity:.5}.codexSubscriptionSwitch[aria-checked=true]{background:var(--dsw-alias-label-secondary);border-color:var(--dsw-alias-label-secondary)}.codexSubscriptionSwitchKnob{position:absolute;top:2px;left:2px;width:12px;height:12px;border-radius:50%;background:var(--dsw-alias-bg-layer-1);transition:transform 120ms var(--ds-ease-in-out)}.codexSubscriptionSwitch[aria-checked=true] .codexSubscriptionSwitchKnob{transform:translateX(14px)}
498
- .codexSubscriptionSearch{display:flex;flex-direction:column;gap:7px}.codexSubscriptionSearchChoices{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:6px}.codexSubscriptionSearchChoice{display:grid;grid-template-columns:14px minmax(0,1fr);align-items:center;column-gap:8px;min-width:0;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-primary);padding:9px 10px;text-align:left;cursor:pointer}.codexSubscriptionSearchChoice:has(input:disabled){cursor:not-allowed;opacity:.5}.codexSubscriptionSearchChoice:has(input:checked){border-color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2)}.codexSubscriptionSearchChoice:has(input:focus-visible){outline:2px solid var(--dsw-alias-border-l3);outline-offset:2px}.codexSubscriptionSearchInput{width:14px;height:14px;margin:0;accent-color:var(--dsw-alias-label-primary);cursor:inherit}.codexSubscriptionSearchCopy{display:block;min-width:0;pointer-events:none}.codexSubscriptionSearchCopy strong,.codexSubscriptionSearchCopy span{display:block}.codexSubscriptionSearchCopy strong{font-size:12px;line-height:18px;font-weight:500;color:var(--dsw-alias-label-secondary)}.codexSubscriptionSearchChoice:has(input:checked) strong{color:var(--dsw-alias-label-primary)}.codexSubscriptionSearchCopy span{font-size:11px;line-height:16px;color:var(--dsw-alias-label-tertiary)}.codexSubscriptionDivider{height:1px;background:var(--dsw-alias-border-l2)}
581
+ .codexSubscriptionSearch{display:flex;flex-direction:column;gap:7px}.codexSubscriptionSearchChoices{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:6px}.codexSubscriptionSearchChoice{display:grid;grid-template-columns:14px minmax(0,1fr);align-items:center;column-gap:8px;min-width:0;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-primary);padding:9px 10px;text-align:left;cursor:pointer}.codexSubscriptionSearchChoice:has(input:disabled){cursor:not-allowed;opacity:.5}.codexSubscriptionSearchChoice:has(input:checked){border-color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-2)}.codexSubscriptionSearchChoice:has(input:focus-visible){outline:2px solid var(--dsw-alias-border-l3);outline-offset:2px}.codexSubscriptionSearchInput{width:14px;height:14px;margin:0;accent-color:var(--dsw-alias-label-primary);cursor:inherit}.codexSubscriptionSearchCopy{display:block;min-width:0;pointer-events:none}.codexSubscriptionSearchCopy strong,.codexSubscriptionSearchCopy span{display:block}.codexSubscriptionSearchCopy strong{font-size:12px;line-height:18px;font-weight:500;color:var(--dsw-alias-label-secondary)}.codexSubscriptionSearchChoice:has(input:checked) strong{color:var(--dsw-alias-label-primary)}.codexSubscriptionSearchCopy span{font-size:11px;line-height:16px;color:var(--dsw-alias-label-tertiary)}.codexSubscriptionDivider{height:1px;background:var(--dsw-alias-border-l2)}
499
582
  .codexSubscriptionAccountRow,.codexSubscriptionSectionHead{display:flex;align-items:center;justify-content:space-between;gap:12px}.codexSubscriptionStatus{display:flex;align-items:center;gap:8px;font-size:14px;line-height:22px;font-weight:500}
500
583
  .codexSubscriptionDot{width:8px;height:8px;border-radius:50%;background:var(--dsw-alias-label-dimmed)}.codexSubscriptionDot[data-state=connected]{background:var(--dsw-alias-state-success-primary)}.codexSubscriptionDot[data-state=disconnected]{background:var(--dsw-alias-state-error-primary)}
501
584
  .codexSubscriptionActions{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.codexSubscriptionFlow{display:flex;flex-direction:column;gap:10px;padding:12px 14px;border-radius:10px;background:var(--dsw-alias-bg-module-platform)}
@@ -521,12 +604,13 @@ window.__ModuleLoader__.load({
521
604
  .codexModelSelectMenu{overflow:visible}
522
605
  .codexImageTool{display:flex;flex-direction:column;gap:8px;margin:4px 0;color:var(--dsw-alias-label-primary)}.codexImageToolRow{display:flex;align-items:center;min-height:24px;gap:8px;font-size:13px;line-height:20px}.codexImageToolIcon{display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;color:var(--dsw-alias-label-secondary)}.codexImageToolIcon::before{content:'';width:8px;height:8px;border:1.5px solid currentColor;border-radius:3px}.codexImageTool[data-state=running] .codexImageToolIcon::before{border-radius:50%;border-right-color:transparent;animation:codexImageSpin 800ms linear infinite}.codexImageTool[data-state=error] .codexImageToolIcon::before{border-color:var(--dsw-alias-state-error-primary);background:var(--dsw-alias-state-error-primary)}.codexImageToolTitle{font-weight:500}.codexImageToolState{color:var(--dsw-alias-label-tertiary)}.codexImageToolError{margin:0 0 0 24px;font-size:12px;line-height:18px;color:var(--dsw-alias-state-error-primary)}.codexImageToolGallery{margin-left:24px}.codexGeneratedImageFrame{display:flex;align-items:center;justify-content:center;width:min(240px,100%);height:240px;padding:0;overflow:hidden;border:1px solid var(--dsw-alias-border-l2);border-radius:16px;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-tertiary);cursor:pointer}.codexGeneratedImageFrame img{display:block;width:100%;height:100%;object-fit:cover}.codexGeneratedImageRetry{min-height:36px;padding:0 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);cursor:pointer}.codexGeneratedImageModal{width:min(920px,calc(100vw - 32px));max-height:calc(100vh - 32px)}.codexGeneratedImageModalContent{min-height:0;overflow:hidden}.codexGeneratedImageViewer{display:flex;min-width:0;flex-direction:column;gap:12px}.codexGeneratedImageStage{display:grid;place-items:center;min-height:280px;max-height:calc(100vh - 260px);overflow:auto;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-module-platform)}.codexGeneratedImageStage img{display:block;max-width:100%;max-height:calc(100vh - 280px);object-fit:contain;transform-origin:center;transition:transform 120ms ease}.codexGeneratedImageMeta{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}.codexGeneratedImageToolbar{display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap}.codexGeneratedImageZoom{display:flex;align-items:center;gap:6px}.codexGeneratedImageZoomValue{min-width:44px;color:var(--dsw-alias-label-secondary);font-size:12px;text-align:center}.codexGeneratedImageDownload{display:inline-flex;align-items:center;gap:6px;min-height:32px;box-sizing:border-box;padding:0 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:999px;background:transparent;color:var(--dsw-alias-label-primary);font-size:13px;text-decoration:none}.codexGeneratedImageDownload:hover{background:var(--dsw-alias-interactive-bg-hover)}.codexGeneratedImageGuidance{display:flex;flex-direction:column;gap:2px;padding-top:2px;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}@keyframes codexImageSpin{to{transform:rotate(360deg)}}
523
606
  .codexImageBeta{padding:0 5px;border:1px solid var(--dsw-alias-border-l2);border-radius:999px;color:var(--dsw-alias-label-tertiary);font-size:10px;line-height:16px}
607
+ .codexImageToolGallery{display:flex;align-items:flex-start;flex-direction:column;gap:8px}
524
608
  @container (max-width:560px){.codexSubscriptionCreditRows{grid-template-columns:1fr}}
525
609
  @container (max-width:480px){.codexSubscriptionAccountRow,.codexSubscriptionSectionHead{align-items:flex-start;flex-direction:column}.codexSubscriptionActions{width:100%}.codexSubscriptionSearchChoices{grid-template-columns:1fr}}
526
610
  @media(max-width:640px){.codexSubscriptionCard{padding:14px}}
527
611
  `;
528
612
  const IMAGE_STYLE = String.raw`
529
- .codexGeneratedImageLightbox{position:fixed;inset:0;z-index:1000;display:grid;grid-template-rows:auto minmax(0,1fr) auto;background:var(--dsw-alias-bg-base,#111);color:var(--dsw-alias-label-primary);outline:0}.codexGeneratedImageTopbar{display:flex;align-items:center;justify-content:space-between;gap:20px;min-height:58px;padding:8px 14px;border-bottom:1px solid var(--dsw-alias-border-l2);background:color-mix(in srgb,var(--dsw-alias-bg-base,#111) 92%,transparent);backdrop-filter:blur(18px)}.codexGeneratedImageTopbar>div:first-child{display:flex;min-width:0;flex-direction:column}.codexGeneratedImageTopbar strong{overflow:hidden;font-size:13px;font-weight:600;line-height:20px;text-overflow:ellipsis;white-space:nowrap}.codexGeneratedImageTopbar small{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:17px}.codexGeneratedImageActions{display:flex;align-items:center;gap:6px}.codexGeneratedImageActions button,.codexGeneratedImageActions a{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;gap:5px;height:32px;padding:0 10px;border:1px solid transparent;border-radius:16px;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:12px;text-decoration:none;cursor:pointer}.codexGeneratedImageActions button:hover,.codexGeneratedImageActions a:hover,.codexGeneratedImageActions button.is-active{background:var(--dsw-alias-interactive-bg-hover)}.codexGeneratedImageActions button:focus-visible,.codexGeneratedImageActions a:focus-visible{outline:2px solid var(--dsw-alias-border-l3);outline-offset:1px}.codexGeneratedImageActions button:disabled{opacity:.4;cursor:default}.codexGeneratedImageActions>span{min-width:38px;color:var(--dsw-alias-label-tertiary);font-size:11px;text-align:center}.codexGeneratedImageClose{font-size:20px!important}.codexGeneratedImageCanvas{position:relative;display:grid;place-items:center;min-height:0;overflow:auto;padding:24px;background:var(--dsw-alias-bg-base,#111)}.codexGeneratedImageSurface{position:relative;display:inline-flex;max-width:calc(100vw - 48px);max-height:calc(100vh - 210px);transform-origin:center;transition:transform 120ms ease}.codexGeneratedImageSurface img{display:block;max-width:100%;max-height:calc(100vh - 210px);border-radius:12px;object-fit:contain}.codexGeneratedImageCanvas.is-annotating .codexGeneratedImageSurface{cursor:crosshair}.codexGeneratedImagePin{position:absolute;display:grid;place-items:center;width:24px;height:24px;padding:0;border:2px solid white;border-radius:50%;background:var(--dsw-alias-label-primary);box-shadow:0 1px 4px rgba(0,0,0,.35);color:var(--dsw-alias-bg-base,#111);font:inherit;font-size:11px;font-weight:700;transform:translate(-50%,-50%);cursor:pointer}.codexGeneratedImagePin.is-active{background:var(--dsw-alias-state-business-primary,#3964fe);color:white}.codexGeneratedImageAnnotateHint{position:absolute;bottom:14px;left:50%;padding:6px 10px;border-radius:14px;background:color-mix(in srgb,var(--dsw-alias-bg-layer-3) 90%,transparent);box-shadow:var(--dsw-shadow-lv2);color:var(--dsw-alias-label-secondary);font-size:11px;transform:translateX(-50%)}.codexGeneratedImageEditDock{display:flex;flex-direction:column;gap:8px;padding:10px 14px 14px;border-top:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2,var(--dsw-alias-bg-base))}.codexGeneratedImagePrompt,.codexGeneratedImageAnnotation{display:flex;align-items:center;gap:8px;width:min(820px,100%);margin:0 auto}.codexGeneratedImagePrompt>*:first-child,.codexGeneratedImageAnnotation>*:nth-child(2){min-width:0;flex:1}.codexGeneratedImagePrompt button{flex:none;white-space:nowrap}.codexGeneratedImageAnnotation>span{flex:none;color:var(--dsw-alias-label-secondary);font-size:12px}.codexGeneratedImageAnnotation>button{flex:none;height:28px;padding:0 8px;border:0;border-radius:14px;background:transparent;color:var(--dsw-alias-label-tertiary);font:inherit;font-size:11px;cursor:pointer}.codexGeneratedImageAnnotation>button:hover{background:var(--dsw-alias-interactive-bg-hover)}.codexGeneratedImageEditError{width:min(820px,100%);margin:0 auto;color:var(--dsw-alias-state-error-primary);font-size:11px;line-height:17px}@media(max-width:760px){.codexGeneratedImageTopbar{align-items:flex-start;flex-direction:column}.codexGeneratedImageActions{width:100%;overflow-x:auto}.codexGeneratedImageActions>span{display:none}.codexGeneratedImageCanvas{padding:12px}.codexGeneratedImageSurface{max-width:calc(100vw - 24px);max-height:calc(100vh - 270px)}.codexGeneratedImageSurface img{max-height:calc(100vh - 270px)}.codexGeneratedImagePrompt,.codexGeneratedImageAnnotation{align-items:stretch;flex-direction:column}.codexGeneratedImagePrompt button{width:100%}}
613
+ .codexGeneratedImageLightbox{position:fixed;inset:0;z-index:1000;display:grid;grid-template-rows:auto minmax(0,1fr);background:var(--dsw-alias-bg-base,#111);color:var(--dsw-alias-label-primary);outline:0}.codexGeneratedImageTopbar{display:flex;align-items:center;justify-content:space-between;gap:20px;min-height:58px;padding:8px 14px;border-bottom:1px solid var(--dsw-alias-border-l2);background:color-mix(in srgb,var(--dsw-alias-bg-base,#111) 92%,transparent);backdrop-filter:blur(18px)}.codexGeneratedImageTopbar>div:first-child{display:flex;min-width:0;flex-direction:column}.codexGeneratedImageTopbar strong{overflow:hidden;font-size:13px;font-weight:600;line-height:20px;text-overflow:ellipsis;white-space:nowrap}.codexGeneratedImageTopbar small{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:17px}.codexGeneratedImageActions{display:flex;align-items:center;gap:6px}.codexGeneratedImageActions button,.codexGeneratedImageActions a{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;gap:5px;height:32px;padding:0 10px;border:1px solid transparent;border-radius:16px;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:12px;text-decoration:none;cursor:pointer}.codexGeneratedImageActions button:hover,.codexGeneratedImageActions a:hover,.codexGeneratedImageActions button.is-active{background:var(--dsw-alias-interactive-bg-hover)}.codexGeneratedImageActions button:focus-visible,.codexGeneratedImageActions a:focus-visible{outline:2px solid var(--dsw-alias-border-l3);outline-offset:1px}.codexGeneratedImageActions button:disabled{opacity:.4;cursor:default}.codexGeneratedImageActions>span{min-width:38px;color:var(--dsw-alias-label-tertiary);font-size:11px;text-align:center}.codexGeneratedImageClose{font-size:20px!important}.codexGeneratedImageCanvas{position:relative;display:grid;place-items:center;min-height:0;overflow:auto;padding:24px;background:var(--dsw-alias-bg-base,#111)}.codexGeneratedImageSurface{position:relative;display:inline-flex;max-width:calc(100vw - 48px);max-height:calc(100vh - 150px);transform-origin:center;transition:transform 120ms ease}.codexGeneratedImageSurface img{display:block;max-width:100%;max-height:calc(100vh - 150px);border-radius:12px;object-fit:contain}.codexGeneratedImageCanvas.is-annotating .codexGeneratedImageSurface{cursor:crosshair}.codexGeneratedImagePin{position:absolute;display:grid;place-items:center;width:24px;height:24px;padding:0;border:2px solid white;border-radius:50%;background:var(--dsw-alias-label-primary);box-shadow:0 1px 4px rgba(0,0,0,.35);color:var(--dsw-alias-bg-base,#111);font:inherit;font-size:11px;font-weight:700;transform:translate(-50%,-50%);cursor:pointer}.codexGeneratedImagePin.is-active{background:var(--dsw-alias-state-business-primary,#3964fe);color:white}.codexGeneratedImageAnnotateHint{position:absolute;bottom:14px;left:50%;padding:6px 10px;border-radius:14px;background:color-mix(in srgb,var(--dsw-alias-bg-layer-3) 90%,transparent);box-shadow:var(--dsw-shadow-lv2);color:var(--dsw-alias-label-secondary);font-size:11px;transform:translateX(-50%)}@media(max-width:760px){.codexGeneratedImageTopbar{align-items:flex-start;flex-direction:column}.codexGeneratedImageActions{width:100%;overflow-x:auto}.codexGeneratedImageActions>span{display:none}.codexGeneratedImageCanvas{padding:12px}.codexGeneratedImageSurface{max-width:calc(100vw - 24px);max-height:calc(100vh - 180px)}.codexGeneratedImageSurface img{max-height:calc(100vh - 180px)}}
530
614
  `;
531
615
  const IMAGE_LAYOUT_STYLE = String.raw`
532
616
  .codexGeneratedImageWorkspace{display:grid;min-height:0;grid-template-columns:minmax(0,1fr)}
@@ -566,7 +650,66 @@ window.__ModuleLoader__.load({
566
650
  return cleaned.toLowerCase().endsWith(".png") ? cleaned : `${cleaned}.png`;
567
651
  };
568
652
  const imageByteSize = (bytes) => bytes < 1024 * 1024 ? `${Math.max(.1, bytes / 1024).toLocaleString(void 0, { maximumFractionDigits: 1 })} KB` : `${(bytes / 1024 / 1024).toLocaleString(void 0, { maximumFractionDigits: 1 })} MB`;
569
- function CodexGeneratedImage({ attachment, loadImage, attachForEdit, getImageViewer, t }) {
653
+ const originalRefMatches = (left, right) => left.assetId === right.assetId && left.mediaType === right.mediaType && left.bytes === right.bytes && left.width === right.width && left.height === right.height && left.name === right.name && left.sha256 === right.sha256;
654
+ async function sha256Hex(data) {
655
+ const value = await crypto.subtle.digest("SHA-256", data);
656
+ return [...new Uint8Array(value)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
657
+ }
658
+ function decodeBase64Chunk(value) {
659
+ if (typeof value !== "string" || value.length === 0 || value.length > Math.ceil(4194304 / 3) * 4 + 8) throw new Error("Invalid original image chunk");
660
+ let decoded;
661
+ try {
662
+ decoded = atob(value);
663
+ } catch {
664
+ throw new Error("Invalid original image chunk");
665
+ }
666
+ const bytes = new Uint8Array(decoded.length);
667
+ for (let index = 0; index < decoded.length; index += 1) bytes[index] = decoded.charCodeAt(index);
668
+ return bytes;
669
+ }
670
+ async function readOriginalImage(rpc, sessionId, original) {
671
+ const parts = [];
672
+ let total = 0;
673
+ let done = false;
674
+ while (!done) {
675
+ const chunk = unwrap(await rpc.call(CHANNEL, "image/original/chunk", {
676
+ sessionId,
677
+ assetId: original.assetId,
678
+ offset: total
679
+ }));
680
+ const ref = decodeOriginalImageRef(chunk?.ref);
681
+ if (ref === void 0 || !originalRefMatches(ref, original) || chunk.offset !== total || typeof chunk.done !== "boolean") throw new Error("Original image metadata changed");
682
+ const bytes = decodeBase64Chunk(chunk.encoded);
683
+ if (bytes.byteLength === 0 || total + bytes.byteLength > original.bytes) throw new Error("Original image download is incomplete");
684
+ parts.push(bytes);
685
+ total += bytes.byteLength;
686
+ done = chunk.done;
687
+ }
688
+ if (total !== original.bytes) throw new Error("Original image download is incomplete");
689
+ const data = new Uint8Array(total);
690
+ let offset = 0;
691
+ for (const part of parts) {
692
+ data.set(part, offset);
693
+ offset += part.byteLength;
694
+ }
695
+ if (await sha256Hex(data) !== original.sha256) throw new Error("Original image integrity check failed");
696
+ return data;
697
+ }
698
+ function triggerBlobDownload(data, mediaType, filename) {
699
+ const url = URL.createObjectURL(new Blob([data], { type: mediaType }));
700
+ const anchor = document.createElement("a");
701
+ anchor.href = url;
702
+ anchor.download = filename;
703
+ anchor.rel = "noopener";
704
+ document.body.append(anchor);
705
+ try {
706
+ anchor.click();
707
+ } finally {
708
+ anchor.remove();
709
+ URL.revokeObjectURL(url);
710
+ }
711
+ }
712
+ function CodexGeneratedImage({ attachment, original, rpc, sessionId, loadImage, attachForEdit, getImageViewer, t }) {
570
713
  const [attempt, setAttempt] = (0, react.useState)(0);
571
714
  const [error, setError] = (0, react.useState)(false);
572
715
  const [open, setOpen] = (0, react.useState)(false);
@@ -575,9 +718,9 @@ window.__ModuleLoader__.load({
575
718
  const [annotationMode, setAnnotationMode] = (0, react.useState)(false);
576
719
  const [annotations, setAnnotations] = (0, react.useState)([]);
577
720
  const [selectedAnnotation, setSelectedAnnotation] = (0, react.useState)();
578
- const [prompt, setPrompt] = (0, react.useState)("");
579
721
  const [editBusy, setEditBusy] = (0, react.useState)(false);
580
722
  const [editError, setEditError] = (0, react.useState)(false);
723
+ const [downloadState, setDownloadState] = (0, react.useState)("idle");
581
724
  const triggerRef = (0, react.useRef)(null);
582
725
  const dialogRef = (0, react.useRef)(null);
583
726
  (0, react.useEffect)(() => {
@@ -634,7 +777,8 @@ window.__ModuleLoader__.load({
634
777
  }, [open]);
635
778
  const label = attachment.name ?? t("imageLabel");
636
779
  const downloadName = imageDownloadName(attachment);
637
- const imageMeta = attachment.width + " × " + attachment.height + " · " + imageByteSize(attachment.bytes);
780
+ const previewMeta = attachment.width + " × " + attachment.height + " · " + imageByteSize(attachment.bytes);
781
+ const imageMeta = original === void 0 ? previewMeta : original.width + " × " + original.height + " · " + imageByteSize(original.bytes) + " · " + t("imagePreviewShort") + " " + previewMeta;
638
782
  const addAnnotation = (event) => {
639
783
  if (!annotationMode || event.target.closest(".codexGeneratedImagePin")) return;
640
784
  const bounds = event.currentTarget.getBoundingClientRect();
@@ -653,7 +797,6 @@ window.__ModuleLoader__.load({
653
797
  setEditError(false);
654
798
  try {
655
799
  await attachForEdit(src, downloadName, buildImageEditDraft({
656
- prompt,
657
800
  annotations,
658
801
  translate: t
659
802
  }));
@@ -664,31 +807,50 @@ window.__ModuleLoader__.load({
664
807
  setEditBusy(false);
665
808
  }
666
809
  };
810
+ const downloadOriginal = async () => {
811
+ if (downloadState === "pending" || original === void 0) return;
812
+ setDownloadState("pending");
813
+ try {
814
+ triggerBlobDownload(await readOriginalImage(rpc, sessionId, original), original.mediaType, original.name);
815
+ setDownloadState("idle");
816
+ } catch {
817
+ setDownloadState("failed");
818
+ throw new Error("original image download failed");
819
+ }
820
+ };
667
821
  const openImage = () => {
668
822
  if (src === void 0) return;
669
- if ((getImageViewer?.())?.open?.({
823
+ const viewer = getImageViewer?.();
824
+ const actions = [];
825
+ actions.push({
826
+ id: "continue-editing",
827
+ label: t("imageEdit"),
828
+ pendingLabel: t("imageEditPreparing"),
829
+ errorLabel: t("imageEditFailed"),
830
+ closeOnSuccess: true,
831
+ onInvoke: ({ annotations: nextAnnotations }) => attachForEdit(src, downloadName, buildImageEditDraft({
832
+ annotations: nextAnnotations,
833
+ translate: t
834
+ }))
835
+ });
836
+ if (viewer?.open?.({
670
837
  items: [{
671
838
  id: attachment.attachmentId ?? downloadName,
672
839
  src,
673
840
  name: label,
674
841
  width: attachment.width,
675
842
  height: attachment.height,
676
- bytes: attachment.bytes
843
+ bytes: attachment.bytes,
844
+ download: original === void 0 ? void 0 : {
845
+ pendingLabel: t("imageDownloadPreparing"),
846
+ errorLabel: t("imageDownloadFailed"),
847
+ onInvoke: downloadOriginal
848
+ },
849
+ actions
677
850
  }],
678
851
  opener: triggerRef.current,
679
852
  source: "codex-generated",
680
- annotations: true,
681
- editor: {
682
- label: t("imageEdit"),
683
- busyLabel: t("imageEditPreparing"),
684
- errorLabel: t("imageEditFailed"),
685
- placeholder: t("imageEditPrompt"),
686
- onSubmit: ({ prompt: nextPrompt, annotations: nextAnnotations }) => attachForEdit(src, downloadName, buildImageEditDraft({
687
- prompt: nextPrompt,
688
- annotations: nextAnnotations,
689
- translate: t
690
- }))
691
- }
853
+ annotations: true
692
854
  }) === true) return;
693
855
  setZoom(1);
694
856
  setOpen(true);
@@ -706,151 +868,142 @@ window.__ModuleLoader__.load({
706
868
  "aria-modal": "true",
707
869
  "aria-label": t("imagePreview"),
708
870
  tabIndex: -1,
709
- children: [
710
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
711
- className: "codexGeneratedImageTopbar",
712
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: label }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("small", { children: imageMeta })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
713
- className: "codexGeneratedImageActions",
714
- children: [
715
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
716
- type: "button",
717
- className: annotationMode ? "is-active" : "",
718
- "aria-pressed": annotationMode,
719
- onClick: () => setAnnotationMode((value) => !value),
720
- children: t("imageAnnotate")
721
- }),
722
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
723
- type: "button",
724
- "aria-label": t("imageZoomOut"),
725
- disabled: zoom <= .5,
726
- onClick: () => setZoom((value) => Math.max(.5, value - .25)),
727
- children: "−"
728
- }),
729
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [Math.round(zoom * 100), "%"] }),
730
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
731
- type: "button",
732
- "aria-label": t("imageZoomIn"),
733
- disabled: zoom >= 3,
734
- onClick: () => setZoom((value) => Math.min(3, value + .25)),
735
- children: "+"
736
- }),
737
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
738
- type: "button",
739
- onClick: () => setZoom(1),
740
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFullscreenOutline16, { "aria-hidden": "true" }), t("imageFit")]
741
- }),
742
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("a", {
743
- href: src,
744
- download: downloadName,
745
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconDownloadOutline16, { "aria-hidden": "true" }), t("imageDownload")]
746
- }),
747
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
748
- type: "button",
749
- className: "codexGeneratedImageClose",
750
- "aria-label": t("imageClosePreview"),
751
- onClick: close,
752
- children: "×"
753
- })
754
- ]
755
- })]
756
- }),
757
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
758
- className: "codexGeneratedImageWorkspace " + (annotationMode || annotations.length > 0 ? "has-comments" : ""),
759
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("main", {
760
- className: "codexGeneratedImageCanvas " + (annotationMode ? "is-annotating" : ""),
761
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
762
- className: "codexGeneratedImageSurface",
763
- onClick: addAnnotation,
764
- style: { transform: "scale(" + zoom + ")" },
765
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
766
- src,
767
- alt: label
768
- }), annotations.map((annotation, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
769
- type: "button",
770
- className: "codexGeneratedImagePin " + (annotation.id === selectedAnnotation ? "is-active" : ""),
771
- "aria-label": fill(t("imageAnnotation"), { value: index + 1 }),
772
- style: {
773
- left: annotation.x * 100 + "%",
774
- top: annotation.y * 100 + "%"
775
- },
776
- onClick: (event) => {
777
- event.stopPropagation();
778
- setSelectedAnnotation(annotation.id);
779
- },
780
- children: index + 1
781
- }, annotation.id))]
782
- }), annotationMode ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
783
- className: "codexGeneratedImageAnnotateHint",
784
- children: t("imageAnnotateHint")
785
- }) : null]
786
- }), annotationMode || annotations.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("aside", {
787
- className: "codexGeneratedImageComments",
788
- "aria-label": t("imageAnnotate"),
789
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t("imageAnnotate") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("small", { children: t("imageAnnotateHint") })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
790
- className: "codexGeneratedImageCommentList",
791
- children: annotations.map((annotation, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("article", {
792
- className: annotation.id === selectedAnnotation ? "is-active" : "",
793
- onClick: () => setSelectedAnnotation(annotation.id),
794
- children: [
795
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: index + 1 }),
796
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
797
- value: annotation.note,
798
- rows: 3,
799
- "aria-label": fill(t("imageAnnotation"), { value: index + 1 }),
800
- placeholder: t("imageAnnotationPlaceholder"),
801
- onFocus: () => setSelectedAnnotation(annotation.id),
802
- onChange: (event) => {
803
- const note = event.target.value;
804
- setAnnotations((value) => value.map((item) => item.id === annotation.id ? {
805
- ...item,
806
- note
807
- } : item));
808
- }
809
- }),
810
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
811
- type: "button",
812
- "aria-label": t("imageRemoveAnnotation"),
813
- onClick: (event) => {
814
- event.stopPropagation();
815
- setAnnotations((value) => value.filter((item) => item.id !== annotation.id));
816
- if (selectedAnnotation === annotation.id) setSelectedAnnotation(void 0);
817
- },
818
- children: "×"
819
- })
820
- ]
821
- }, annotation.id))
822
- })]
823
- }) : null]
824
- }),
825
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("footer", {
826
- className: "codexGeneratedImageEditDock",
827
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
828
- className: "codexGeneratedImagePrompt",
829
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
830
- value: prompt,
831
- placeholder: t("imageEditPrompt"),
832
- onChange: (event) => setPrompt(event.target.value),
833
- onKeyDown: (event) => {
834
- if (event.key === "Enter" && !event.shiftKey) {
835
- event.preventDefault();
836
- continueEditing();
837
- }
838
- }
839
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
871
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
872
+ className: "codexGeneratedImageTopbar",
873
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: label }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("small", { children: imageMeta })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
874
+ className: "codexGeneratedImageActions",
875
+ children: [
876
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
877
+ type: "button",
878
+ className: annotationMode ? "is-active" : "",
879
+ "aria-pressed": annotationMode,
880
+ onClick: () => setAnnotationMode((value) => !value),
881
+ children: t("imageAnnotate")
882
+ }),
883
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
884
+ type: "button",
885
+ "aria-label": t("imageZoomOut"),
886
+ disabled: zoom <= .5,
887
+ onClick: () => setZoom((value) => Math.max(.5, value - .25)),
888
+ children: "−"
889
+ }),
890
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [Math.round(zoom * 100), "%"] }),
891
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
892
+ type: "button",
893
+ "aria-label": t("imageZoomIn"),
894
+ disabled: zoom >= 3,
895
+ onClick: () => setZoom((value) => Math.min(3, value + .25)),
896
+ children: "+"
897
+ }),
898
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
899
+ type: "button",
900
+ onClick: () => setZoom(1),
901
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconFullscreenOutline16, { "aria-hidden": "true" }), t("imageFit")]
902
+ }),
903
+ original === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("a", {
904
+ href: src,
905
+ download: downloadName,
906
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconDownloadOutline16, { "aria-hidden": "true" }), t("imageDownload")]
907
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
908
+ type: "button",
909
+ disabled: downloadState === "pending",
910
+ onClick: () => {
911
+ downloadOriginal().catch(() => void 0);
912
+ },
913
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconDownloadOutline16, { "aria-hidden": "true" }), downloadState === "pending" ? t("imageDownloadPreparing") : downloadState === "failed" ? t("imageDownloadFailed") : t("imageDownload")]
914
+ }),
915
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
840
916
  type: "button",
841
917
  disabled: editBusy,
842
918
  onClick: () => {
843
919
  continueEditing();
844
920
  },
845
- children: editBusy ? t("imageEditPreparing") : t("imageEdit")
846
- })]
847
- }), editError ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
848
- className: "codexGeneratedImageEditError",
849
- role: "alert",
850
- children: t("imageEditFailed")
921
+ children: editBusy ? t("imageEditPreparing") : editError ? t("imageEditFailed") : t("imageEdit")
922
+ }),
923
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
924
+ type: "button",
925
+ className: "codexGeneratedImageClose",
926
+ "aria-label": t("imageClosePreview"),
927
+ onClick: close,
928
+ children: "×"
929
+ })
930
+ ]
931
+ })]
932
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
933
+ className: "codexGeneratedImageWorkspace " + (annotationMode || annotations.length > 0 ? "has-comments" : ""),
934
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("main", {
935
+ className: "codexGeneratedImageCanvas " + (annotationMode ? "is-annotating" : ""),
936
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
937
+ className: "codexGeneratedImageSurface",
938
+ onClick: addAnnotation,
939
+ style: { transform: "scale(" + zoom + ")" },
940
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
941
+ src,
942
+ alt: label
943
+ }), annotations.map((annotation, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
944
+ type: "button",
945
+ className: "codexGeneratedImagePin " + (annotation.id === selectedAnnotation ? "is-active" : ""),
946
+ "aria-label": fill(t("imageAnnotation"), { value: index + 1 }),
947
+ style: {
948
+ left: annotation.x * 100 + "%",
949
+ top: annotation.y * 100 + "%"
950
+ },
951
+ onClick: (event) => {
952
+ event.stopPropagation();
953
+ setSelectedAnnotation(annotation.id);
954
+ },
955
+ children: index + 1
956
+ }, annotation.id))]
957
+ }), annotationMode ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
958
+ className: "codexGeneratedImageAnnotateHint",
959
+ children: t("imageAnnotateHint")
851
960
  }) : null]
852
- })
853
- ]
961
+ }), annotationMode || annotations.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("aside", {
962
+ className: "codexGeneratedImageComments",
963
+ "aria-label": t("imageAnnotate"),
964
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t("imageAnnotate") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("small", { children: t("imageAnnotateHint") })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
965
+ className: "codexGeneratedImageCommentList",
966
+ children: annotations.map((annotation, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("article", {
967
+ className: annotation.id === selectedAnnotation ? "is-active" : "",
968
+ onClick: () => setSelectedAnnotation(annotation.id),
969
+ children: [
970
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: index + 1 }),
971
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
972
+ value: annotation.note,
973
+ rows: 3,
974
+ "aria-label": fill(t("imageAnnotation"), { value: index + 1 }),
975
+ placeholder: t("imageAnnotationPlaceholder"),
976
+ onFocus: () => setSelectedAnnotation(annotation.id),
977
+ onChange: (event) => {
978
+ const note = event.target.value;
979
+ setAnnotations((value) => value.map((item) => item.id === annotation.id ? {
980
+ ...item,
981
+ note
982
+ } : item));
983
+ },
984
+ onKeyDown: (event) => {
985
+ if (event.key === "Enter" && !event.shiftKey || event.key === "Escape") {
986
+ event.preventDefault();
987
+ event.stopPropagation();
988
+ setSelectedAnnotation(void 0);
989
+ }
990
+ }
991
+ }),
992
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
993
+ type: "button",
994
+ "aria-label": t("imageRemoveAnnotation"),
995
+ onClick: (event) => {
996
+ event.stopPropagation();
997
+ setAnnotations((value) => value.filter((item) => item.id !== annotation.id));
998
+ if (selectedAnnotation === annotation.id) setSelectedAnnotation(void 0);
999
+ },
1000
+ children: "×"
1001
+ })
1002
+ ]
1003
+ }, annotation.id))
1004
+ })]
1005
+ }) : null]
1006
+ })]
854
1007
  }), document.body);
855
1008
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
856
1009
  ref: triggerRef,
@@ -865,13 +1018,14 @@ window.__ModuleLoader__.load({
865
1018
  })
866
1019
  }), lightbox] });
867
1020
  }
868
- function CodexImageToolRow({ block, loadImage, attachForEdit, getImageViewer, t }) {
1021
+ function CodexImageToolRow({ block, sessionId, rpc, loadImage, attachForEdit, getImageViewer, t }) {
869
1022
  const settled = block?.kind === "tool-result";
870
1023
  const image = settled ? block.content.find((item) => item?.type === "image" && item.attachment !== void 0) : void 0;
871
1024
  const failed = settled && block.isError === true;
872
1025
  const state = !settled ? "running" : failed ? "error" : "done";
873
1026
  const status = !settled ? t("imageGenerating") : failed ? t("imageFailed") : t("imageGenerated");
874
1027
  const error = failed ? block.content.find((item) => item?.type === "text" && typeof item.text === "string")?.text : void 0;
1028
+ const original = decodeImagePresentation(block?.meta)?.original;
875
1029
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
876
1030
  className: "codexImageTool",
877
1031
  "data-state": state,
@@ -901,6 +1055,9 @@ window.__ModuleLoader__.load({
901
1055
  className: "codexImageToolGallery",
902
1056
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CodexGeneratedImage, {
903
1057
  attachment: image.attachment,
1058
+ original,
1059
+ rpc,
1060
+ sessionId,
904
1061
  loadImage,
905
1062
  attachForEdit,
906
1063
  getImageViewer,
@@ -922,6 +1079,7 @@ window.__ModuleLoader__.load({
922
1079
  let failedPatch;
923
1080
  let generation = 0;
924
1081
  let contextModels = [];
1082
+ let verbosityModels = [];
925
1083
  const nativeSnapshot = () => scope.getSnapshot();
926
1084
  const read = () => {
927
1085
  const native = nativeSnapshot();
@@ -931,10 +1089,12 @@ window.__ModuleLoader__.load({
931
1089
  quickQuotaMode: normalizeQuickQuotaMode(current.value?.[QUICK_QUOTA_MODE_FIELD], current.value?.[LEGACY_QUICK_QUOTA_FIELD]),
932
1090
  searchProvider: normalizeSearchProvider(current.value?.[SEARCH_PROVIDER_FIELD]),
933
1091
  speedMode: normalizeSpeedMode(current.value?.[SPEED_MODE_FIELD]),
1092
+ outputVerbosity: normalizeOutputVerbosity(current.value?.[OUTPUT_VERBOSITY_FIELD]),
934
1093
  contextMode: normalizeContextMode(current.value?.[CONTEXT_MODE_FIELD]),
935
1094
  customContextWindow: normalizeCustomContextWindow(current.value?.[CUSTOM_CONTEXT_WINDOW_FIELD]),
936
1095
  customContextWindows: Object.fromEntries(Object.entries(CUSTOM_CONTEXT_MODEL_FIELDS).map(([modelKey, field]) => [modelKey, normalizeCustomContextWindow(current.value?.[field] ?? CUSTOM_CONTEXT_MODEL_DEFAULTS[modelKey], CUSTOM_CONTEXT_MODEL_CAPS[modelKey])])),
937
1096
  contextModels,
1097
+ verbosityModels,
938
1098
  writable: !updating && current.status === "ready" && current.writable === true,
939
1099
  error
940
1100
  });
@@ -952,6 +1112,7 @@ window.__ModuleLoader__.load({
952
1112
  });
953
1113
  const acceptFallback = (value) => {
954
1114
  contextModels = Array.isArray(value?.contextModels) ? value.contextModels : [];
1115
+ verbosityModels = Array.isArray(value?.verbosityModels) ? value.verbosityModels : [];
955
1116
  fallbackStatus = "ready";
956
1117
  fallback = {
957
1118
  status: "ready",
@@ -959,6 +1120,7 @@ window.__ModuleLoader__.load({
959
1120
  [QUICK_QUOTA_MODE_FIELD]: normalizeQuickQuotaMode(value?.[QUICK_QUOTA_MODE_FIELD], value?.[LEGACY_QUICK_QUOTA_FIELD]),
960
1121
  [SEARCH_PROVIDER_FIELD]: normalizeSearchProvider(value?.[SEARCH_PROVIDER_FIELD]),
961
1122
  [SPEED_MODE_FIELD]: normalizeSpeedMode(value?.[SPEED_MODE_FIELD]),
1123
+ [OUTPUT_VERBOSITY_FIELD]: normalizeOutputVerbosity(value?.[OUTPUT_VERBOSITY_FIELD]),
962
1124
  [CONTEXT_MODE_FIELD]: normalizeContextMode(value?.[CONTEXT_MODE_FIELD]),
963
1125
  [CUSTOM_CONTEXT_WINDOW_FIELD]: normalizeCustomContextWindow(value?.[CUSTOM_CONTEXT_WINDOW_FIELD]),
964
1126
  ...Object.fromEntries(Object.entries(CUSTOM_CONTEXT_MODEL_FIELDS).map(([modelKey, field]) => [field, normalizeCustomContextWindow(value?.[field] ?? CUSTOM_CONTEXT_MODEL_DEFAULTS[modelKey], CUSTOM_CONTEXT_MODEL_CAPS[modelKey])]))
@@ -977,8 +1139,10 @@ window.__ModuleLoader__.load({
977
1139
  try {
978
1140
  const value = unwrap(await rpc.call(CHANNEL, "preferences/status", {}));
979
1141
  if (current !== generation) return;
980
- if (nativeSnapshot().status === "ready") contextModels = Array.isArray(value?.contextModels) ? value.contextModels : [];
981
- else acceptFallback(value);
1142
+ if (nativeSnapshot().status === "ready") {
1143
+ contextModels = Array.isArray(value?.contextModels) ? value.contextModels : [];
1144
+ verbosityModels = Array.isArray(value?.verbosityModels) ? value.verbosityModels : [];
1145
+ } else acceptFallback(value);
982
1146
  publish();
983
1147
  } catch {
984
1148
  if (current !== generation || nativeSnapshot().status === "ready") return;
@@ -1034,6 +1198,18 @@ window.__ModuleLoader__.load({
1034
1198
  }
1035
1199
  const usePreferenceSnapshot = (preference) => (0, react.useSyncExternalStore)(preference.subscribe, preference.getSnapshot);
1036
1200
  const notifyQuickQuota = () => window.dispatchEvent(new Event(QUICK_QUOTA_REFRESH_EVENT));
1201
+ const formatRunway = (seconds, t) => {
1202
+ if (!Number.isFinite(seconds) || seconds <= 0) return void 0;
1203
+ const minutes = Math.max(1, Math.round(seconds / 60));
1204
+ const days = Math.floor(minutes / 1440);
1205
+ const hours = Math.floor(minutes % 1440 / 60);
1206
+ if (days > 0) return hours > 0 ? fill(t("runwayDaysHours"), {
1207
+ days,
1208
+ hours
1209
+ }) : fill(t("runwayDays"), { days });
1210
+ if (hours > 0) return fill(t("runwayHours"), { hours });
1211
+ return fill(t("runwayMinutes"), { minutes });
1212
+ };
1037
1213
  function useQuickQuota(rpc, enabled, model) {
1038
1214
  const [quota, setQuota] = (0, react.useState)();
1039
1215
  (0, react.useEffect)(() => {
@@ -1109,7 +1285,12 @@ window.__ModuleLoader__.load({
1109
1285
  children: [
1110
1286
  choice("off", t("quickQuotaOff")),
1111
1287
  choice(QUICK_QUOTA_MODE_PERCENT, t("quickQuotaPercent")),
1112
- choice("bar", t("quickQuotaBar"))
1288
+ choice("bar", t("quickQuotaBar")),
1289
+ choice(QUICK_QUOTA_MODE_FORECAST, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1290
+ t("quickQuotaForecast"),
1291
+ " ",
1292
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("small", { children: t("quickQuotaBeta") })
1293
+ ] }))
1113
1294
  ]
1114
1295
  })]
1115
1296
  });
@@ -1145,7 +1326,11 @@ window.__ModuleLoader__.load({
1145
1326
  className: "codexSubscriptionSearchChoices",
1146
1327
  role: "radiogroup",
1147
1328
  "aria-label": t("searchTitle"),
1148
- children: [choice("dsh", t("searchDsh"), t("searchDshHint")), choice(SEARCH_PROVIDER_CODEX, t("searchCodex"), t("searchCodexHint"))]
1329
+ children: [
1330
+ choice(SEARCH_PROVIDER_AUTO, t("searchAuto"), t("searchAutoHint")),
1331
+ choice("dsh", t("searchDsh"), t("searchDshHint")),
1332
+ choice(SEARCH_PROVIDER_CODEX, t("searchCodex"), t("searchCodexHint"))
1333
+ ]
1149
1334
  })]
1150
1335
  });
1151
1336
  }
@@ -1310,11 +1495,16 @@ window.__ModuleLoader__.load({
1310
1495
  const current = (0, react.useSyncExternalStore)((listener) => directory.subscribe(listener), () => directory.getSnapshot()).current;
1311
1496
  const codex = current?.provider === "openai-codex";
1312
1497
  const quotaEnabled = preferenceSnapshot.status === "ready" && preferenceSnapshot.quickQuotaMode !== "off" && codex;
1498
+ const forecastMode = preferenceSnapshot.quickQuotaMode === QUICK_QUOTA_MODE_FORECAST;
1313
1499
  const quota = useQuickQuota(rpc, quotaEnabled, current?.model);
1314
1500
  if (!quotaEnabled || quota === void 0) return null;
1315
1501
  const value = Math.round(Number(quota.remainingPercent) * 10) / 10;
1316
1502
  const display = percent(value);
1317
- const label = fill(t("quickQuotaStatus"), { value: display });
1503
+ const duration = quota.forecast?.status === "ready" ? formatRunway(quota.forecast.runwaySeconds, t) : void 0;
1504
+ const label = duration === void 0 ? fill(t("quickQuotaStatus"), { value: display }) : fill(t("quickQuotaForecastStatus"), {
1505
+ value: display,
1506
+ duration
1507
+ });
1318
1508
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1319
1509
  className: "codexComposerQuota",
1320
1510
  role: "status",
@@ -1325,7 +1515,7 @@ window.__ModuleLoader__.load({
1325
1515
  max: 100,
1326
1516
  value,
1327
1517
  "aria-hidden": "true"
1328
- }) : `${display}%`
1518
+ }) : forecastMode && duration !== void 0 ? `${display}% · ${quota.forecast.survivesReset ? "≥" : "≈"}${duration}` : `${display}%`
1329
1519
  });
1330
1520
  }
1331
1521
  function CodexModelSelect({ locked, available, directory, load, select, preference, t }) {
@@ -1363,6 +1553,31 @@ window.__ModuleLoader__.load({
1363
1553
  const speedSupported = state.current?.provider === "openai-codex" && supportsCodexFastMode(state.current?.model);
1364
1554
  const speedWritable = preferenceSnapshot.status === "ready" && preferenceSnapshot.writable === true;
1365
1555
  const fast = speedSupported && preferenceSnapshot.speedMode === "fast";
1556
+ const verbositySupported = state.current?.provider === "openai-codex" && preferenceSnapshot.verbosityModels.includes(state.current?.model);
1557
+ const verbosityWritable = preferenceSnapshot.status === "ready" && preferenceSnapshot.writable === true;
1558
+ const verbosityItems = [
1559
+ {
1560
+ id: OUTPUT_VERBOSITY_DEFAULT,
1561
+ label: t("verbosityDefault"),
1562
+ description: t("verbosityDefaultHint")
1563
+ },
1564
+ {
1565
+ id: "low",
1566
+ label: t("verbosityLow"),
1567
+ description: t("verbosityLowHint")
1568
+ },
1569
+ {
1570
+ id: OUTPUT_VERBOSITY_MEDIUM,
1571
+ label: t("verbosityMedium"),
1572
+ description: t("verbosityMediumHint")
1573
+ },
1574
+ {
1575
+ id: OUTPUT_VERBOSITY_HIGH,
1576
+ label: t("verbosityHigh"),
1577
+ description: t("verbosityHighHint")
1578
+ }
1579
+ ];
1580
+ const verbosityLabel = verbosityItems.find((item) => item.id === preferenceSnapshot.outputVerbosity)?.label ?? t("verbosityDefault");
1366
1581
  const busy = state.status === "selecting";
1367
1582
  (0, react.useEffect)(() => {
1368
1583
  if (available) load();
@@ -1380,7 +1595,12 @@ window.__ModuleLoader__.load({
1380
1595
  }, [open]);
1381
1596
  (0, react.useEffect)(() => {
1382
1597
  if (!speedSupported && pane === "speed") setPane("root");
1383
- }, [pane, speedSupported]);
1598
+ if (!verbositySupported && pane === "verbosity") setPane("root");
1599
+ }, [
1600
+ pane,
1601
+ speedSupported,
1602
+ verbositySupported
1603
+ ]);
1384
1604
  if (!available) return null;
1385
1605
  const close = (restoreFocus = false) => {
1386
1606
  setOpen(false);
@@ -1413,6 +1633,10 @@ window.__ModuleLoader__.load({
1413
1633
  close(true);
1414
1634
  preference.set({ [SPEED_MODE_FIELD]: speedMode });
1415
1635
  };
1636
+ const chooseVerbosity = (outputVerbosity) => {
1637
+ close(true);
1638
+ preference.set({ [OUTPUT_VERBOSITY_FIELD]: outputVerbosity });
1639
+ };
1416
1640
  const option = ({ key, label, description, selected, disabled, onClick }) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1417
1641
  type: "button",
1418
1642
  role: "menuitemradio",
@@ -1545,6 +1769,19 @@ window.__ModuleLoader__.load({
1545
1769
  onClick: () => chooseSpeed(SPEED_MODE_FAST)
1546
1770
  })]
1547
1771
  });
1772
+ else if (pane === "verbosity") submenu = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1773
+ className: "codexModelSelectSubmenu",
1774
+ role: "menu",
1775
+ "aria-label": t("verbosityTitle"),
1776
+ children: verbosityItems.map((item) => option({
1777
+ key: item.id,
1778
+ label: item.label,
1779
+ description: item.description,
1780
+ selected: preferenceSnapshot.outputVerbosity === item.id,
1781
+ disabled: !verbosityWritable,
1782
+ onClick: () => chooseVerbosity(item.id)
1783
+ }))
1784
+ });
1548
1785
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1549
1786
  className: "codexModelSelect",
1550
1787
  ref: rootRef,
@@ -1590,6 +1827,7 @@ window.__ModuleLoader__.load({
1590
1827
  cell("model", t("modelLabel"), modelLabel),
1591
1828
  reasoning === void 0 ? null : cell("effort", t("effortLabel"), effortLabel),
1592
1829
  speedSupported && cell("speed", t("speedTitle"), t(fast ? "speedFast" : "speedStandard")),
1830
+ verbositySupported && cell("verbosity", t("verbosityTitle"), verbosityLabel),
1593
1831
  submenu
1594
1832
  ]
1595
1833
  }) : null]
@@ -2183,7 +2421,10 @@ window.__ModuleLoader__.load({
2183
2421
  }),
2184
2422
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2185
2423
  className: "codexSubscriptionLimitMeta",
2186
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: windowLabel(window.windowSeconds, t) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ResetTime, {
2424
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: window.forecast?.status === "ready" ? fill(t("quotaForecast"), {
2425
+ symbol: window.forecast.survivesReset ? "≥" : "≈",
2426
+ duration: formatRunway(window.forecast.runwaySeconds, t)
2427
+ }) : windowLabel(window.windowSeconds, t) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ResetTime, {
2187
2428
  resetsAt: window.resetsAt,
2188
2429
  t
2189
2430
  })]
@@ -2339,43 +2580,50 @@ window.__ModuleLoader__.load({
2339
2580
  t
2340
2581
  })
2341
2582
  }, CodexSection));
2342
- ctx.slots.inject("conversation.input.right", () => ctx.slots.register({
2343
- name: "conversation.input.right",
2344
- id: "codex-subscription-quota",
2345
- order: 15,
2346
- locale: NS,
2347
- inject: (sessionId) => ({
2348
- preference,
2349
- rpc: connection.rpc,
2350
- t,
2351
- directory: ctx.modelDirectories.directoryFor(sessionId).store
2352
- })
2353
- }, CodexComposerQuota));
2354
2583
  const sessions = ctx.get("sessions");
2355
- ctx.slots.inject("conversation.input.model", () => ctx.slots.register({
2356
- name: "conversation.input.model",
2357
- priority: -10,
2358
- locale: NS,
2359
- inject: (sessionId) => {
2360
- const directory = ctx.modelDirectories.directoryFor(sessionId);
2361
- const available = sessions.subagentAddress(sessionId) === void 0;
2362
- return {
2363
- available,
2364
- directory: directory.store,
2365
- load: () => {
2366
- if (available) directory.load();
2367
- },
2368
- select: (selection) => available ? directory.select(selection).then(() => true, () => false) : Promise.resolve(false),
2369
- preference
2370
- };
2371
- }
2372
- }, CodexModelSelect));
2584
+ const installDirectorySlots = (scope) => {
2585
+ const modelDirectories = scope.get("modelDirectories");
2586
+ scope.slots.inject("conversation.input.right", () => scope.slots.register({
2587
+ name: "conversation.input.right",
2588
+ id: "codex-subscription-quota",
2589
+ order: 15,
2590
+ locale: NS,
2591
+ inject: (sessionId) => ({
2592
+ preference,
2593
+ rpc: connection.rpc,
2594
+ t,
2595
+ directory: modelDirectories.directoryFor(sessionId).store
2596
+ })
2597
+ }, CodexComposerQuota));
2598
+ scope.slots.inject("conversation.input.model", () => scope.slots.register({
2599
+ name: "conversation.input.model",
2600
+ priority: -10,
2601
+ locale: NS,
2602
+ inject: (sessionId) => {
2603
+ const directory = modelDirectories.directoryFor(sessionId);
2604
+ const available = sessions.subagentAddress(sessionId) === void 0;
2605
+ return {
2606
+ available,
2607
+ directory: directory.store,
2608
+ load: () => {
2609
+ if (available) directory.load();
2610
+ },
2611
+ select: (selection) => available ? directory.select(selection).then(() => true, () => false) : Promise.resolve(false),
2612
+ preference
2613
+ };
2614
+ }
2615
+ }, CodexModelSelect));
2616
+ };
2617
+ if (ctx.get("remote.session") === void 0) installDirectorySlots(ctx);
2618
+ else ctx.inject(["remote.session"], installDirectorySlots);
2373
2619
  const conversation = ctx.get("conversation");
2374
2620
  ctx.slots.inject("tool.call.toolview", () => ctx.slots.register({
2375
2621
  name: "tool.call.toolview",
2376
2622
  key: "codex_image_generate",
2377
2623
  locale: NS,
2378
2624
  inject: (sessionId) => ({
2625
+ sessionId,
2626
+ rpc: connection.rpc,
2379
2627
  t,
2380
2628
  loadImage: (attachment) => conversation.resolveImage(sessionId, attachment),
2381
2629
  getImageViewer: () => {