dsh-codex-community 0.0.1 → 0.0.3

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.
@@ -7,6 +7,7 @@ window.__ModuleLoader__.load({
7
7
  const h = React.createElement
8
8
 
9
9
  const CHANNEL = "/dsh-codex"
10
+ const SESSION_CHANNEL = "/dsh-codex-session"
10
11
  const NS = "settings.codex"
11
12
  const CODEX_PROVIDER = "dsh-codex"
12
13
  const CODEX_SETTINGS_NS = "dsh-codex"
@@ -27,6 +28,7 @@ window.__ModuleLoader__.load({
27
28
  unavailable: "当前环境没有可用的 Codex 登录流程。",
28
29
  writableNo: "当前凭据存储不可写。",
29
30
  refresh: "刷新",
31
+ refreshing: "刷新中…",
30
32
  signIn: "登录",
31
33
  signInWithChatGPT: "使用 ChatGPT 登录",
32
34
  signInAgain: "重新登录",
@@ -47,20 +49,36 @@ window.__ModuleLoader__.load({
47
49
  logoutFailed: "退出登录失败,请重试。",
48
50
  responseFailed: "提交登录信息失败,请重试。",
49
51
  requestFailed: "暂时无法连接本机授权服务。",
52
+ locale: "zh-CN",
50
53
  quotaTitle: "使用额度",
51
54
  quotaProduct: "Codex",
52
- quotaUnknown: "尚无可验证的额度观测。",
53
- quotaUnknownHelp: "完成一次 Codex 请求后,此处会更新可验证状态;插件不会调用未公开的账户额度接口。",
55
+ quotaLoading: "正在读取账户额度…",
56
+ quotaLoadFailed: "暂时无法刷新账户额度。",
57
+ quotaStaleHelp: "继续显示上一次验证成功的数据。",
58
+ quotaUnknown: "尚无可验证的额度数据。",
59
+ quotaUnknownHelp: "登录后会在每次进入此页面和手动刷新时读取;读取失败时保留最近的安全观测。",
60
+ quotaFiveHour: "5 小时额度",
61
+ quotaWeekly: "每周额度",
62
+ quotaMinuteWindow: "分钟额度",
63
+ quotaRemaining: "剩余",
64
+ quotaResetsAt: "重置时间",
65
+ quotaUpdatedAt: "数据更新时间",
66
+ quotaResetSoon: "即将重置",
67
+ quotaResetPrefix: "",
68
+ quotaResetSuffix: "后重置",
69
+ quotaResetMinute: "分钟",
70
+ quotaResetHour: "小时",
71
+ quotaResetDay: "天",
54
72
  quotaRecentSuccess: "最近一次 Codex 请求成功",
55
73
  quotaSuccessCaution: "这只表示最近一次请求成功,不代表账户剩余额度。",
56
74
  quotaExhausted: "最近观测到 Codex 账户额度已用尽。",
57
75
  quotaObservedAt: "观测时间",
58
76
  quotaResetAt: "观测到的重置时间",
59
- quotaResetIn: "预计还有约",
60
- quotaMinutes: "分钟",
61
77
  quotaNoReset: "未获得通过校验的重置时间;状态会在有限时间后自动回到未知。",
62
- modelsTitle: "模型选择器中显示的模型",
63
- modelsDescription: "选择要在模型选择器中显示的 Codex 模型。隐藏后,已有会话中保存的精确模型仍可继续使用。",
78
+ modelsTitle: "当前安装的 Codex 模型",
79
+ modelsDescription: "选择在模型选择器中显示的 Codex 模型。名称、上下文和最大输出来自当前安装的 provider catalog;隐藏模型不影响已有会话。",
80
+ modelsContextWindow: "上下文",
81
+ modelsMaxOutput: "最大输出",
64
82
  modelsLoading: "正在读取 Codex 模型目录…",
65
83
  modelsUnavailable: "当前环境没有可用的 Codex 模型管理接口。",
66
84
  modelsReadOnly: "当前设置存储为只读,无法修改启用模型。",
@@ -69,11 +87,24 @@ window.__ModuleLoader__.load({
69
87
  modelsSaving: "正在保存…",
70
88
  modelsSaved: "模型启用设置已保存。",
71
89
  modelsAtLeastOne: "至少需要启用一个 Codex 模型。",
72
- modelsAllEnabledFollow: "已启用全部当前目录模型;当前覆盖不含额外模型字段,保存后会清除覆盖并自动启用未来新增的目录模型。",
73
- modelsAllEnabledPreserve: "已启用全部当前目录模型;为保留现有模型参数,保存后会固定当前模型列表,未来新增的目录模型不会自动启用。",
90
+ modelsAllEnabledFollow: "已启用全部当前目录模型;保存后将清除列表覆盖,未来新增模型会自动显示。",
91
+ modelsAllEnabledPreserve: "已启用全部当前目录模型;为保留其他模型参数,保存后会固定当前列表,未来新增模型不会自动显示。",
92
+ modelsShowMore: "显示未选择的 {count} 个模型",
93
+ modelsCollapse: "收起未选择的模型",
94
+ modelsSummary: "已选择 {selected}/{total} · 当前显示 {visible}/{total}",
74
95
  modelsRetry: "重新读取",
75
96
  modelsLoadFailed: "读取 Codex 模型目录失败。",
76
97
  modelsSaveFailed: "保存模型启用设置失败。",
98
+ fastEnable: "启用 Fast(1.5×)",
99
+ fastDisable: "关闭 Fast,恢复默认速度",
100
+ fastUnsupported: "当前模型不支持 Fast(1.5×)",
101
+ fastLoading: "正在读取 Fast 状态",
102
+ fastUnavailable: "暂时无法读取 Fast 状态,点击重试",
103
+ legacyReasoningRepair: "修复并设为新会话默认模型",
104
+ legacyReasoningRepairing: "正在修复…",
105
+ legacyReasoningRepaired: "已修复并设为新会话默认模型",
106
+ legacyReasoningRepairRetry: "重试修复并设为新会话默认模型",
107
+ legacyReasoningRepairHelp: "旧会话保存了当前不支持的 Off/Minimal。点击后会切换到此模型当前默认档位;与模型选择器相同,此操作也会把该模型保存为未来新会话的默认模型。",
77
108
  }
78
109
  const en = {
79
110
  nav: "OpenAI Codex",
@@ -88,6 +119,7 @@ window.__ModuleLoader__.load({
88
119
  unavailable: "No Codex sign-in flow is available in this environment.",
89
120
  writableNo: "The credential store is currently read-only.",
90
121
  refresh: "Refresh",
122
+ refreshing: "Refreshing…",
91
123
  signIn: "Sign in",
92
124
  signInWithChatGPT: "Sign in with ChatGPT",
93
125
  signInAgain: "Sign in again",
@@ -108,20 +140,36 @@ window.__ModuleLoader__.load({
108
140
  logoutFailed: "Could not sign out. Please try again.",
109
141
  responseFailed: "Could not submit the sign-in response. Please try again.",
110
142
  requestFailed: "The local authorization service is temporarily unavailable.",
143
+ locale: "en-US",
111
144
  quotaTitle: "Usage",
112
145
  quotaProduct: "Codex",
113
- quotaUnknown: "No verifiable quota observation yet.",
114
- quotaUnknownHelp: "This area updates after a Codex request; the plugin does not call undocumented account-quota endpoints.",
146
+ quotaLoading: "Reading account usage…",
147
+ quotaLoadFailed: "Account usage could not be refreshed.",
148
+ quotaStaleHelp: "The last successfully verified data remains visible.",
149
+ quotaUnknown: "No verifiable usage data is available yet.",
150
+ quotaUnknownHelp: "After sign-in, usage is read whenever this page opens and when you refresh it; the latest safe observation remains available if the read fails.",
151
+ quotaFiveHour: "5-hour limit",
152
+ quotaWeekly: "Weekly limit",
153
+ quotaMinuteWindow: "minute limit",
154
+ quotaRemaining: "Remaining",
155
+ quotaResetsAt: "Resets",
156
+ quotaUpdatedAt: "Data updated",
157
+ quotaResetSoon: "Resets soon",
158
+ quotaResetPrefix: "Resets in",
159
+ quotaResetSuffix: "",
160
+ quotaResetMinute: "minute",
161
+ quotaResetHour: "hour",
162
+ quotaResetDay: "day",
115
163
  quotaRecentSuccess: "The latest Codex request succeeded",
116
164
  quotaSuccessCaution: "This only describes the latest request; it does not represent remaining account quota.",
117
165
  quotaExhausted: "Codex account quota was exhausted in the latest observation.",
118
166
  quotaObservedAt: "Observed at",
119
167
  quotaResetAt: "Observed reset time",
120
- quotaResetIn: "Approximately",
121
- quotaMinutes: "minutes remaining",
122
168
  quotaNoReset: "No reset time passed validation; this state automatically returns to unknown after a bounded interval.",
123
- modelsTitle: "Models shown in the model selector",
124
- modelsDescription: "Choose which Codex models appear in the model selector. Hidden models remain available to existing conversations that saved an exact model.",
169
+ modelsTitle: "Codex models in this installation",
170
+ modelsDescription: "Choose which Codex models appear in the model selector. Names, context windows, and maximum output values come from the installed provider catalog; hidden models do not affect existing conversations.",
171
+ modelsContextWindow: "Context",
172
+ modelsMaxOutput: "Max output",
125
173
  modelsLoading: "Reading the Codex model catalog…",
126
174
  modelsUnavailable: "Codex model management is unavailable in this environment.",
127
175
  modelsReadOnly: "The settings store is read-only, so enabled models cannot be changed.",
@@ -130,13 +178,43 @@ window.__ModuleLoader__.load({
130
178
  modelsSaving: "Saving…",
131
179
  modelsSaved: "Enabled models saved.",
132
180
  modelsAtLeastOne: "At least one Codex model must remain enabled.",
133
- modelsAllEnabledFollow: "All current catalog models are enabled. Because the override has no additional model fields, saving will clear it and automatically enable future catalog additions.",
134
- modelsAllEnabledPreserve: "All current catalog models are enabled. To preserve existing model parameters, saving will keep the current model list; future catalog additions will not be enabled automatically.",
181
+ modelsAllEnabledFollow: "All current catalog models are enabled. Saving clears the list override, so future models appear automatically.",
182
+ modelsAllEnabledPreserve: "All current catalog models are enabled. To preserve other model settings, saving fixes the current list and future models will not appear automatically.",
183
+ modelsShowMore: "Show {count} unselected models",
184
+ modelsCollapse: "Hide unselected models",
185
+ modelsSummary: "Selected {selected}/{total} · Showing {visible}/{total}",
135
186
  modelsRetry: "Reload",
136
187
  modelsLoadFailed: "Could not read the Codex model catalog.",
137
188
  modelsSaveFailed: "Could not save the enabled models.",
189
+ fastEnable: "Enable Fast (1.5×)",
190
+ fastDisable: "Turn off Fast and restore standard speed",
191
+ fastUnsupported: "The current model does not support Fast (1.5×)",
192
+ fastLoading: "Reading Fast status",
193
+ fastUnavailable: "Fast status is temporarily unavailable; click to retry",
194
+ legacyReasoningRepair: "Repair and set as future default",
195
+ legacyReasoningRepairing: "Repairing…",
196
+ legacyReasoningRepaired: "Repaired and set as future default",
197
+ legacyReasoningRepairRetry: "Retry repair and set as future default",
198
+ legacyReasoningRepairHelp: "This conversation saved an unsupported Off/Minimal level. Click to switch to this model's current default. Like using the model selector, this also saves it as the default model for future conversations.",
138
199
  }
139
200
 
201
+ const NAV_ICON_MARKER = "data-dsh-codex-community-nav-icon"
202
+ const NAV_ICON_STYLE_SELECTOR = 'style[data-plugin-nav-icon="dsh-codex-community"]'
203
+ const NAV_ICON_STATE_KEY = Symbol.for("dsh-codex-community.settings-nav-icon.v1")
204
+ const NAV_ICON_LABELS = new Set([zh.nav.trim(), en.nav.trim()])
205
+ const SETTINGS_DIALOG_SELECTOR = '[role="dialog"][aria-modal="true"]'
206
+ const SETTINGS_NAV_SELECTOR = `${SETTINGS_DIALOG_SELECTOR} > nav`
207
+ const SETTINGS_NAV_BUTTON_SELECTOR = `${SETTINGS_NAV_SELECTOR} button`
208
+ // settings.section has no icon slot in Harness 0.1.1-rc.2. This original code-brackets glyph is applied only to the unique Codex nav entry.
209
+ const CODE_ICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
210
+ <path d="M5.25 3.25 1.5 8l3.75 4.75M10.75 3.25 14.5 8l-3.75 4.75M9.25 2.5l-2.5 11" fill="none" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
211
+ </svg>`
212
+ const CODE_ICON_MASK = `data:image/svg+xml,${encodeURIComponent(CODE_ICON_SVG).replace(/[!'()*]/gu, (character) => `%${character.charCodeAt(0).toString(16).toUpperCase()}`)}`
213
+ const NAV_ICON_CSS = [
214
+ `[${NAV_ICON_MARKER}]>svg:first-child{display:none!important}`,
215
+ `[${NAV_ICON_MARKER}]::before{content:"";width:16px;height:16px;flex:none;background:currentColor;-webkit-mask:url("${CODE_ICON_MASK}") center/contain no-repeat;mask:url("${CODE_ICON_MASK}") center/contain no-repeat}`,
216
+ ].join("")
217
+
140
218
  function createAuthorizationClient(connection) {
141
219
  const call = async (endpoint, payload, signal) => {
142
220
  const result = await connection.rpc.call(CHANNEL, endpoint, payload, signal)
@@ -164,9 +242,199 @@ window.__ModuleLoader__.load({
164
242
  }),
165
243
  cancel: (attemptId) => call("cancel", attemptId === undefined ? {} : { attemptId }),
166
244
  logout: () => call("logout", {}),
245
+ usage: (signal) => call("usage", {}, signal),
167
246
  })
168
247
  }
169
248
 
249
+ function createSessionPreferenceClient(connection) {
250
+ const call = async (endpoint, payload, signal) => {
251
+ const result = await connection.rpc.call(SESSION_CHANNEL, endpoint, payload, signal)
252
+ if (!result.ok) {
253
+ const error = new Error(result.error.message)
254
+ error.code = result.error.code
255
+ throw error
256
+ }
257
+ return result.value
258
+ }
259
+ return Object.freeze({
260
+ forSession(sessionId) {
261
+ return Object.freeze({
262
+ get: (signal) => call("get", { sessionId }, signal),
263
+ setFast: (fast, signal) => call("set-fast", { sessionId, fast }, signal),
264
+ })
265
+ },
266
+ })
267
+ }
268
+
269
+ function supportsFastModel(modelId) {
270
+ return modelId === "gpt-5.4"
271
+ || modelId === "gpt-5.5"
272
+ || modelId === "gpt-5.6-luna"
273
+ || modelId === "gpt-5.6-sol"
274
+ || modelId === "gpt-5.6-terra"
275
+ }
276
+
277
+ function CodexFastToggle({
278
+ session,
279
+ useModelDirectory,
280
+ preferenceClient,
281
+ selectModel,
282
+ t,
283
+ }) {
284
+ const current = useModelDirectory((state) => state.current ?? null)
285
+ const [preference, setPreference] = React.useState({ status: "loading", fast: false })
286
+ const [repair, setRepair] = React.useState({ key: null, status: "idle" })
287
+ const mountedRef = React.useRef(false)
288
+ const generationRef = React.useRef(0)
289
+ const controllerRef = React.useRef(null)
290
+ const repairAttemptRef = React.useRef(null)
291
+ const isCodex = current?.provider === CODEX_PROVIDER
292
+ const supported = isCodex && supportsFastModel(current?.model)
293
+
294
+ const refreshPreference = React.useCallback(async () => {
295
+ if (!mountedRef.current || !isCodex) return
296
+ const generation = ++generationRef.current
297
+ controllerRef.current?.abort()
298
+ const controller = new AbortController()
299
+ controllerRef.current = controller
300
+ setPreference((value) => ({ status: "loading", fast: value.fast }))
301
+ try {
302
+ const value = await preferenceClient.get(controller.signal)
303
+ if (!mountedRef.current || generation !== generationRef.current || controller.signal.aborted) return
304
+ setPreference({ status: "ready", fast: value?.fast === true })
305
+ } catch {
306
+ if (!mountedRef.current || generation !== generationRef.current || controller.signal.aborted) return
307
+ setPreference((value) => ({ status: "error", fast: value.fast }))
308
+ } finally {
309
+ if (controllerRef.current === controller) controllerRef.current = null
310
+ }
311
+ }, [isCodex, preferenceClient])
312
+
313
+ React.useEffect(() => {
314
+ mountedRef.current = true
315
+ if (isCodex) void refreshPreference()
316
+ return () => {
317
+ mountedRef.current = false
318
+ generationRef.current += 1
319
+ controllerRef.current?.abort()
320
+ controllerRef.current = null
321
+ }
322
+ }, [isCodex, preferenceClient, refreshPreference])
323
+
324
+ const legacySelection = isCodex
325
+ && (current?.reasoningEffort === "off" || current?.reasoningEffort === "minimal")
326
+ const legacyKey = legacySelection ? `${current.model}:${current.reasoningEffort}` : null
327
+ const repairStatus = repair.key === legacyKey ? repair.status : "idle"
328
+
329
+ const repairLegacySelection = async () => {
330
+ if (
331
+ !mountedRef.current
332
+ || !legacySelection
333
+ || typeof selectModel !== "function"
334
+ || session?.removed === true
335
+ || repairStatus === "saving"
336
+ || repairStatus === "done"
337
+ || repairAttemptRef.current === legacyKey
338
+ ) return
339
+ const key = legacyKey
340
+ repairAttemptRef.current = key
341
+ setRepair({ key, status: "saving" })
342
+ try {
343
+ await selectModel({
344
+ provider: CODEX_PROVIDER,
345
+ model: current.model,
346
+ })
347
+ if (mountedRef.current) setRepair({ key, status: "done" })
348
+ } catch {
349
+ if (repairAttemptRef.current === key) repairAttemptRef.current = null
350
+ if (mountedRef.current) setRepair({ key, status: "error" })
351
+ }
352
+ }
353
+
354
+ const toggleFast = async () => {
355
+ if (!mountedRef.current || !supported || session?.removed === true) return
356
+ if (preference.status === "error") {
357
+ void refreshPreference()
358
+ return
359
+ }
360
+ if (preference.status !== "ready") return
361
+ const generation = ++generationRef.current
362
+ controllerRef.current?.abort()
363
+ const controller = new AbortController()
364
+ controllerRef.current = controller
365
+ const nextFast = !preference.fast
366
+ setPreference({ status: "saving", fast: preference.fast })
367
+ try {
368
+ const value = await preferenceClient.setFast(nextFast, controller.signal)
369
+ if (!mountedRef.current || generation !== generationRef.current || controller.signal.aborted) return
370
+ setPreference({ status: "ready", fast: value?.fast === true })
371
+ } catch {
372
+ if (!mountedRef.current || generation !== generationRef.current || controller.signal.aborted) return
373
+ setPreference({ status: "error", fast: preference.fast })
374
+ } finally {
375
+ if (controllerRef.current === controller) controllerRef.current = null
376
+ }
377
+ }
378
+
379
+ if (!isCodex) return null
380
+ const label = !supported
381
+ ? t("fastUnsupported")
382
+ : preference.status === "loading" || preference.status === "saving"
383
+ ? t("fastLoading")
384
+ : preference.status === "error"
385
+ ? t("fastUnavailable")
386
+ : preference.fast
387
+ ? t("fastDisable")
388
+ : t("fastEnable")
389
+ const disabled = !supported
390
+ || session?.removed === true
391
+ || preference.status === "loading"
392
+ || preference.status === "saving"
393
+
394
+ const fastButton = h("button", {
395
+ type: "button",
396
+ className: "dshCodexFastToggle",
397
+ "data-fast": preference.fast ? "true" : undefined,
398
+ "aria-label": label,
399
+ "aria-pressed": preference.fast,
400
+ "aria-busy": preference.status === "loading" || preference.status === "saving" ? "true" : undefined,
401
+ title: label,
402
+ disabled,
403
+ onClick: () => void toggleFast(),
404
+ }, h("svg", {
405
+ viewBox: "0 0 24 24",
406
+ width: 16,
407
+ height: 16,
408
+ fill: "none",
409
+ stroke: "currentColor",
410
+ strokeWidth: 2,
411
+ strokeLinecap: "round",
412
+ strokeLinejoin: "round",
413
+ "aria-hidden": "true",
414
+ }, h("path", { d: "M13 2 4.5 13h7L11 22l8.5-11h-7L13 2Z" })))
415
+
416
+ if (!legacySelection || typeof selectModel !== "function") return fastButton
417
+ const repairLabel = repairStatus === "saving"
418
+ ? t("legacyReasoningRepairing")
419
+ : repairStatus === "done"
420
+ ? t("legacyReasoningRepaired")
421
+ : repairStatus === "error"
422
+ ? t("legacyReasoningRepairRetry")
423
+ : t("legacyReasoningRepair")
424
+ return h(React.Fragment, null,
425
+ h("button", {
426
+ type: "button",
427
+ className: "dshCodexLegacyRepair",
428
+ "data-status": repairStatus,
429
+ "aria-label": repairLabel,
430
+ "aria-busy": repairStatus === "saving" ? "true" : undefined,
431
+ title: t("legacyReasoningRepairHelp"),
432
+ disabled: session?.removed === true || repairStatus === "saving" || repairStatus === "done",
433
+ onClick: () => void repairLegacySelection(),
434
+ }, repairLabel),
435
+ fastButton)
436
+ }
437
+
170
438
  function resultValue(response) {
171
439
  if (response?.result?.ok === true) return response.result.value
172
440
  const message = response?.result?.error?.message
@@ -184,7 +452,9 @@ window.__ModuleLoader__.load({
184
452
  return { status: "unknown" }
185
453
  }
186
454
  if (value.resetAt === undefined) return { status: value.status, observedAt: value.observedAt }
187
- if (!Number.isSafeInteger(value.resetAt) || value.resetAt <= now) return { status: "unknown" }
455
+ if (!Number.isSafeInteger(value.resetAt) || value.resetAt <= now || !Number.isFinite(new Date(value.resetAt).getTime())) {
456
+ return { status: "unknown" }
457
+ }
188
458
  return {
189
459
  status: value.status,
190
460
  observedAt: value.observedAt,
@@ -193,6 +463,168 @@ window.__ModuleLoader__.load({
193
463
  }
194
464
  }
195
465
 
466
+ function safeAccountUsage(value) {
467
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return undefined
468
+ if (!Number.isSafeInteger(value.observedAt) || value.observedAt < 0) return undefined
469
+ if (!Array.isArray(value.rateLimits) || value.rateLimits.length > 16) return undefined
470
+ const rateLimits = []
471
+ for (const candidate of value.rateLimits) {
472
+ if (typeof candidate !== "object" || candidate === null || Array.isArray(candidate)) continue
473
+ if (typeof candidate.limitId !== "string" || candidate.limitId.length < 1 || candidate.limitId.length > 128) continue
474
+ const primary = safeUsageWindow(candidate.primary)
475
+ const secondary = safeUsageWindow(candidate.secondary)
476
+ if (primary === undefined && secondary === undefined) continue
477
+ rateLimits.push({
478
+ limitId: candidate.limitId,
479
+ ...(typeof candidate.limitName === "string" && candidate.limitName.length > 0 && candidate.limitName.length <= 256
480
+ ? { limitName: candidate.limitName }
481
+ : {}),
482
+ ...(primary === undefined ? {} : { primary }),
483
+ ...(secondary === undefined ? {} : { secondary }),
484
+ })
485
+ }
486
+ if (rateLimits.length === 0) return undefined
487
+ return { observedAt: value.observedAt, rateLimits }
488
+ }
489
+
490
+ function safeUsageWindow(value) {
491
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return undefined
492
+ if (typeof value.usedPercent !== "number" || !Number.isFinite(value.usedPercent) || value.usedPercent < 0 || value.usedPercent > 100) return undefined
493
+ if (!Number.isSafeInteger(value.windowDurationMins) || value.windowDurationMins < 1 || value.windowDurationMins > 525_600) return undefined
494
+ if (!Number.isSafeInteger(value.resetsAt) || value.resetsAt < 0 || !Number.isFinite(new Date(value.resetsAt).getTime())) return undefined
495
+ return {
496
+ usedPercent: value.usedPercent,
497
+ windowDurationMins: value.windowDurationMins,
498
+ resetsAt: value.resetsAt,
499
+ }
500
+ }
501
+
502
+ function percentText(value) {
503
+ return String(Math.round(value * 10) / 10)
504
+ }
505
+
506
+ function usageWindowLabel(window, t) {
507
+ if (window.windowDurationMins === 300) return t("quotaFiveHour")
508
+ if (window.windowDurationMins === 10_080) return t("quotaWeekly")
509
+ return `${window.windowDurationMins} ${t("quotaMinuteWindow")}`
510
+ }
511
+
512
+ function formatResetDistance(resetsAt, t, now = Date.now()) {
513
+ if (!Number.isSafeInteger(resetsAt) || !Number.isSafeInteger(now) || resetsAt < 0 || now < 0) {
514
+ return t("quotaResetSoon")
515
+ }
516
+ const remaining = resetsAt - now
517
+ if (remaining <= 60_000) return t("quotaResetSoon")
518
+ let count
519
+ let unit
520
+ if (remaining < 60 * 60_000) {
521
+ count = Math.ceil(remaining / 60_000)
522
+ unit = t("quotaResetMinute")
523
+ } else if (remaining < 24 * 60 * 60_000) {
524
+ count = Math.ceil(remaining / (60 * 60_000))
525
+ unit = t("quotaResetHour")
526
+ } else {
527
+ count = Math.ceil(remaining / (24 * 60 * 60_000))
528
+ unit = t("quotaResetDay")
529
+ }
530
+ if (t("locale").toLowerCase().startsWith("zh")) {
531
+ return `${count} ${unit}${t("quotaResetSuffix")}`
532
+ }
533
+ return `${t("quotaResetPrefix")} ${count} ${unit}${count === 1 ? "" : "s"}`
534
+ }
535
+
536
+ function renderResetTime(resetsAt, t, { showExact = false, now = Date.now() } = {}) {
537
+ const exact = new Date(resetsAt).toLocaleString(t("locale"))
538
+ const relative = formatResetDistance(resetsAt, t, now)
539
+ const separator = t("locale").toLowerCase().startsWith("zh") ? ":" : ": "
540
+ const exactLabel = `${t("quotaResetsAt")}${separator}${exact}`
541
+ return h("time", {
542
+ dateTime: new Date(resetsAt).toISOString(),
543
+ title: exactLabel,
544
+ "aria-label": showExact ? exactLabel : `${relative} · ${exactLabel}`,
545
+ }, showExact ? exactLabel : relative)
546
+ }
547
+
548
+ function refreshIcon() {
549
+ return h("svg", {
550
+ className: "dshCodexRefreshIcon",
551
+ viewBox: "0 0 16 16",
552
+ fill: "none",
553
+ "aria-hidden": "true",
554
+ focusable: "false",
555
+ },
556
+ h("path", {
557
+ d: "M13.25 5.75A5.5 5.5 0 1 0 13.1 10.6",
558
+ stroke: "currentColor",
559
+ strokeWidth: "1.4",
560
+ strokeLinecap: "round",
561
+ }),
562
+ h("path", {
563
+ d: "M10.25 5.75h3v-3",
564
+ stroke: "currentColor",
565
+ strokeWidth: "1.4",
566
+ strokeLinecap: "round",
567
+ strokeLinejoin: "round",
568
+ }))
569
+ }
570
+
571
+ function renderObservedQuota(quota, t) {
572
+ if (quota.status === "recent-success") {
573
+ return h(React.Fragment, null,
574
+ h("p", { className: "dshCodexQuotaState" }, `${t("quotaRecentSuccess")} · ${new Date(quota.observedAt).toLocaleString(t("locale"))}`),
575
+ h("p", { className: "dshCodexMuted" }, t("quotaSuccessCaution")))
576
+ }
577
+ if (quota.status === "exhausted") {
578
+ return h(React.Fragment, null,
579
+ h("p", { className: "dshCodexQuotaState dshCodexQuotaExhausted" }, t("quotaExhausted")),
580
+ h("p", { className: "dshCodexMuted" }, `${t("quotaObservedAt")} · ${new Date(quota.observedAt).toLocaleString(t("locale"))}`),
581
+ quota.resetAt === undefined
582
+ ? h("p", { className: "dshCodexMuted" }, t("quotaNoReset"))
583
+ : h("p", { className: "dshCodexMuted" },
584
+ `${t("quotaResetAt")} · `,
585
+ renderResetTime(quota.resetAt, t)))
586
+ }
587
+ return h(React.Fragment, null,
588
+ h("p", { className: "dshCodexQuotaState dshCodexQuotaUnknown" }, t("quotaUnknown")),
589
+ h("p", { className: "dshCodexMuted" }, t("quotaUnknownHelp")))
590
+ }
591
+
592
+ function renderAccountUsage(usage, t) {
593
+ const now = Date.now()
594
+ return h(React.Fragment, null,
595
+ ...usage.rateLimits.map((limit) => {
596
+ const windows = [limit.primary, limit.secondary].filter((window) => window !== undefined)
597
+ const limitLabel = limit.limitId === "codex"
598
+ ? t("quotaProduct")
599
+ : limit.limitName ?? limit.limitId
600
+ return h("section", { key: limit.limitId, className: "dshCodexQuotaGroup" },
601
+ h("p", { className: "dshCodexQuotaProduct" }, limitLabel),
602
+ h("div", { className: "dshCodexQuotaWindows" },
603
+ ...windows.map((window) => {
604
+ const remaining = Math.max(0, Math.min(100, 100 - window.usedPercent))
605
+ const remainingText = percentText(remaining)
606
+ const label = usageWindowLabel(window, t)
607
+ const resetDistance = window.resetsAt - now
608
+ const showExactReset = window.windowDurationMins === 300
609
+ || (window.windowDurationMins === 10_080
610
+ && resetDistance > 0
611
+ && resetDistance < 24 * 60 * 60_000)
612
+ return h("div", { key: `${limit.limitId}-${window.windowDurationMins}`, className: "dshCodexQuotaWindow" },
613
+ h("div", { className: "dshCodexQuotaWindowHeader" },
614
+ h("span", null, label),
615
+ h("strong", null, `${t("quotaRemaining")} ${remainingText}%`)),
616
+ h("progress", {
617
+ max: 100,
618
+ value: remaining,
619
+ "aria-label": `${limitLabel} · ${label} · ${t("quotaRemaining")} ${remainingText}%`,
620
+ }),
621
+ h("div", { className: "dshCodexQuotaMeta" },
622
+ renderResetTime(window.resetsAt, t, { showExact: showExactReset, now })))
623
+ })))
624
+ }),
625
+ h("p", { className: "dshCodexMuted dshCodexQuotaUpdated" }, `${t("quotaUpdatedAt")}${t("locale").toLowerCase().startsWith("zh") ? ":" : ": "}${new Date(usage.observedAt).toLocaleString(t("locale"))}`))
626
+ }
627
+
196
628
  function objectAtPath(root, path) {
197
629
  let current = root
198
630
  for (const key of path) {
@@ -217,11 +649,28 @@ window.__ModuleLoader__.load({
217
649
  rows.push({
218
650
  id: candidate.id,
219
651
  ...(typeof candidate.name === "string" && candidate.name.length > 0 ? { name: candidate.name } : {}),
652
+ ...(Number.isSafeInteger(candidate.contextWindow) && candidate.contextWindow > 0
653
+ ? { contextWindow: candidate.contextWindow }
654
+ : {}),
655
+ ...(Number.isSafeInteger(candidate.maxTokens) && candidate.maxTokens > 0
656
+ ? { maxTokens: candidate.maxTokens }
657
+ : {}),
220
658
  })
221
659
  }
222
660
  return rows
223
661
  }
224
662
 
663
+ function formatModelTokenCount(value, locale) {
664
+ const divisor = value >= 1_000_000 ? 1_000_000 : value >= 1_000 ? 1_000 : 1
665
+ const suffix = divisor === 1_000_000 ? "M" : divisor === 1_000 ? "K" : ""
666
+ const compact = value / divisor
667
+ try {
668
+ return `${compact.toLocaleString(locale, { maximumFractionDigits: 1 })}${suffix}`
669
+ } catch {
670
+ return `${Math.round(compact * 10) / 10}${suffix}`
671
+ }
672
+ }
673
+
225
674
  function modelSavePlan(snapshot, selectedIds) {
226
675
  const selected = new Set(selectedIds)
227
676
  const modelIds = snapshot.models.map((model) => model.id)
@@ -340,6 +789,7 @@ window.__ModuleLoader__.load({
340
789
 
341
790
  function AuthorizationSettings({ client, t }) {
342
791
  const [snapshot, setSnapshot] = React.useState({ status: "loading" })
792
+ const [usageSnapshot, setUsageSnapshot] = React.useState({ status: "idle" })
343
793
  const [attempt, setAttempt] = React.useState(null)
344
794
  const [notices, setNotices] = React.useState([])
345
795
  const [prompt, setPrompt] = React.useState(null)
@@ -368,6 +818,29 @@ window.__ModuleLoader__.load({
368
818
  const value = await client.describe(controller.signal)
369
819
  if (!mountedRef.current || generation !== refreshGenerationRef.current || controller.signal.aborted) return
370
820
  setSnapshot({ status: "ready", value })
821
+ const credentialState = value?.credential?.state
822
+ const signedIn = credentialState === "signed-in"
823
+ || (credentialState === undefined && value?.credential?.configured === true)
824
+ if (!signedIn || typeof client.usage !== "function") {
825
+ setUsageSnapshot({ status: "idle" })
826
+ return
827
+ }
828
+ setUsageSnapshot((current) => ({
829
+ status: "loading",
830
+ ...(current.value === undefined ? {} : { value: current.value }),
831
+ }))
832
+ try {
833
+ const usage = safeAccountUsage(await client.usage(controller.signal))
834
+ if (usage === undefined) throw new Error("invalid account usage")
835
+ if (!mountedRef.current || generation !== refreshGenerationRef.current || controller.signal.aborted) return
836
+ setUsageSnapshot({ status: "ready", value: usage })
837
+ } catch {
838
+ if (!mountedRef.current || generation !== refreshGenerationRef.current || controller.signal.aborted) return
839
+ setUsageSnapshot((current) => ({
840
+ status: "error",
841
+ ...(current.value === undefined ? {} : { value: current.value }),
842
+ }))
843
+ }
371
844
  } catch {
372
845
  if (!mountedRef.current || generation !== refreshGenerationRef.current || controller.signal.aborted) return
373
846
  setSnapshot({ status: "error" })
@@ -381,9 +854,6 @@ window.__ModuleLoader__.load({
381
854
  React.useEffect(() => {
382
855
  mountedRef.current = true
383
856
  void refresh()
384
- const timer = typeof window.setInterval === "function"
385
- ? window.setInterval(() => void refresh(), 60_000)
386
- : undefined
387
857
  return () => {
388
858
  mountedRef.current = false
389
859
  refreshGenerationRef.current += 1
@@ -396,7 +866,6 @@ window.__ModuleLoader__.load({
396
866
  attemptRef.current = null
397
867
  current?.controller.abort()
398
868
  if (current !== null && !current.done) void client.cancel(current.id).catch(() => undefined)
399
- if (timer !== undefined) window.clearInterval(timer)
400
869
  }
401
870
  }, [client])
402
871
 
@@ -646,22 +1115,22 @@ window.__ModuleLoader__.load({
646
1115
  onClick: () => void logout(),
647
1116
  }, loggingOut ? t("signingOut") : t("signOut")) : null)
648
1117
 
1118
+ const liveUsage = usageSnapshot.value
649
1119
  let quotaBody
650
- if (quota.status === "recent-success") {
1120
+ if (liveUsage !== undefined) {
651
1121
  quotaBody = h(React.Fragment, null,
652
- h("p", { className: "dshCodexQuotaState" }, `${t("quotaRecentSuccess")} · ${new Date(quota.observedAt).toLocaleString()}`),
653
- h("p", { className: "dshCodexMuted" }, t("quotaSuccessCaution")))
654
- } else if (quota.status === "exhausted") {
1122
+ renderAccountUsage(liveUsage, t),
1123
+ usageSnapshot.status === "error"
1124
+ ? h("p", { role: "status", className: "dshCodexError" }, `${t("quotaLoadFailed")} ${t("quotaStaleHelp")}`)
1125
+ : null)
1126
+ } else if (usageSnapshot.status === "loading") {
1127
+ quotaBody = h("p", { className: "dshCodexMuted", "aria-busy": "true" }, t("quotaLoading"))
1128
+ } else if (usageSnapshot.status === "error") {
655
1129
  quotaBody = h(React.Fragment, null,
656
- h("p", { className: "dshCodexQuotaState dshCodexQuotaExhausted" }, t("quotaExhausted")),
657
- h("p", { className: "dshCodexMuted" }, `${t("quotaObservedAt")} · ${new Date(quota.observedAt).toLocaleString()}`),
658
- quota.resetAt === undefined
659
- ? h("p", { className: "dshCodexMuted" }, t("quotaNoReset"))
660
- : h("p", { className: "dshCodexMuted" }, `${t("quotaResetAt")} · ${new Date(quota.resetAt).toLocaleString()} · ${t("quotaResetIn")} ${quota.remainingMinutes} ${t("quotaMinutes")}`))
1130
+ h("p", { role: "status", className: "dshCodexError" }, t("quotaLoadFailed")),
1131
+ renderObservedQuota(quota, t))
661
1132
  } else {
662
- quotaBody = h(React.Fragment, null,
663
- h("p", { className: "dshCodexQuotaState dshCodexQuotaUnknown" }, t("quotaUnknown")),
664
- h("p", { className: "dshCodexMuted" }, t("quotaUnknownHelp")))
1133
+ quotaBody = renderObservedQuota(quota, t)
665
1134
  }
666
1135
 
667
1136
  return h("section", {
@@ -733,6 +1202,7 @@ window.__ModuleLoader__.load({
733
1202
  h("section", {
734
1203
  className: "dshCodexQuota",
735
1204
  "data-quota-status": quota.status,
1205
+ "aria-busy": usageSnapshot.status === "loading" ? "true" : undefined,
736
1206
  "aria-live": "polite",
737
1207
  "aria-labelledby": "dsh-codex-quota-title",
738
1208
  },
@@ -741,10 +1211,16 @@ window.__ModuleLoader__.load({
741
1211
  h("button", {
742
1212
  type: "button",
743
1213
  className: "dshCodexButton dshCodexRefreshButton",
744
- disabled: actionBusy,
1214
+ "data-loading": usageSnapshot.status === "loading" ? "true" : undefined,
1215
+ "aria-busy": usageSnapshot.status === "loading" ? "true" : undefined,
1216
+ disabled: actionBusy || usageSnapshot.status === "loading",
745
1217
  onClick: refresh,
746
- }, t("refresh"))),
747
- h("p", { className: "dshCodexQuotaProduct" }, t("quotaProduct")),
1218
+ },
1219
+ refreshIcon(),
1220
+ h("span", null, usageSnapshot.status === "loading" ? t("refreshing") : t("refresh")))),
1221
+ liveUsage === undefined
1222
+ ? h("p", { className: "dshCodexQuotaProduct" }, t("quotaProduct"))
1223
+ : null,
748
1224
  quotaBody))
749
1225
  }
750
1226
 
@@ -761,13 +1237,17 @@ window.__ModuleLoader__.load({
761
1237
  const [saving, setSaving] = React.useState(false)
762
1238
  const [saved, setSaved] = React.useState(false)
763
1239
  const [failure, setFailure] = React.useState(null)
1240
+ const [expanded, setExpanded] = React.useState(false)
764
1241
  const generationRef = React.useRef(0)
765
1242
  const mountedRef = React.useRef(false)
1243
+ const modelToggleRef = React.useRef(null)
1244
+ const focusModelToggleRef = React.useRef(false)
766
1245
 
767
1246
  const refresh = React.useCallback(async () => {
768
1247
  const generation = ++generationRef.current
769
1248
  setSaved(false)
770
1249
  setFailure(null)
1250
+ setExpanded(false)
771
1251
  setSnapshot({ kind: "loading" })
772
1252
  try {
773
1253
  const next = await client.load()
@@ -792,6 +1272,12 @@ window.__ModuleLoader__.load({
792
1272
  }
793
1273
  }, [client, refresh])
794
1274
 
1275
+ React.useEffect(() => {
1276
+ if (!focusModelToggleRef.current) return
1277
+ focusModelToggleRef.current = false
1278
+ if (typeof modelToggleRef.current?.focus === "function") modelToggleRef.current.focus()
1279
+ }, [expanded, selectedIds])
1280
+
795
1281
  const toggle = (id) => {
796
1282
  const selected = selectedIds.includes(id)
797
1283
  if (selected && selectedIds.length === 1) {
@@ -800,6 +1286,7 @@ window.__ModuleLoader__.load({
800
1286
  }
801
1287
  setSaved(false)
802
1288
  setFailure(null)
1289
+ if (selected && !expanded) focusModelToggleRef.current = true
803
1290
  setSelectedIds(selected
804
1291
  ? selectedIds.filter((candidate) => candidate !== id)
805
1292
  : [...selectedIds, id])
@@ -832,6 +1319,7 @@ window.__ModuleLoader__.load({
832
1319
  }
833
1320
 
834
1321
  let content
1322
+ let modelSummary = null
835
1323
  if (snapshot.kind === "loading") {
836
1324
  content = h("p", { className: "dshCodexMuted", "aria-busy": "true" }, t("modelsLoading"))
837
1325
  } else if (snapshot.kind === "unavailable") {
@@ -851,26 +1339,68 @@ window.__ModuleLoader__.load({
851
1339
  const original = new Set(snapshot.selectedIds)
852
1340
  const dirty = selected.size !== original.size || [...selected].some((id) => !original.has(id))
853
1341
  const plan = modelSavePlan(snapshot, selectedIds)
854
- const modelItems = snapshot.models.map((model) => h("li", { key: model.id },
855
- h("label", {
856
- className: "dshCodexModelOption",
857
- "data-selected": selected.has(model.id) ? "true" : undefined,
858
- },
859
- h("input", {
860
- type: "checkbox",
861
- checked: selected.has(model.id),
862
- disabled: saving || !snapshot.writable,
863
- onChange: () => toggle(model.id),
864
- }),
865
- h("span", null,
866
- h("strong", null, model.name ?? model.id),
867
- model.name === undefined || model.name === model.id ? null : h("code", null, model.id)))))
1342
+ const selectedModels = snapshot.models.filter((model) => selected.has(model.id))
1343
+ const unselectedModels = snapshot.models.filter((model) => !selected.has(model.id))
1344
+ const hiddenModelCount = unselectedModels.length
1345
+ const visibleModels = expanded
1346
+ ? [...selectedModels, ...unselectedModels]
1347
+ : selectedModels
1348
+ modelSummary = t("modelsSummary")
1349
+ .replace("{selected}", String(selected.size))
1350
+ .replaceAll("{total}", String(snapshot.models.length))
1351
+ .replace("{visible}", String(visibleModels.length))
1352
+ const modelItems = visibleModels.map((model) => {
1353
+ const metadata = [
1354
+ ...(model.contextWindow === undefined
1355
+ ? []
1356
+ : [`${t("modelsContextWindow")} ${formatModelTokenCount(model.contextWindow, t("locale"))}`]),
1357
+ ...(model.maxTokens === undefined
1358
+ ? []
1359
+ : [`${t("modelsMaxOutput")} ${formatModelTokenCount(model.maxTokens, t("locale"))}`]),
1360
+ ]
1361
+ return h("li", { key: model.id },
1362
+ h("label", {
1363
+ className: "dshCodexModelOption",
1364
+ "data-selected": selected.has(model.id) ? "true" : undefined,
1365
+ },
1366
+ h("input", {
1367
+ type: "checkbox",
1368
+ checked: selected.has(model.id),
1369
+ disabled: saving || !snapshot.writable,
1370
+ onChange: () => toggle(model.id),
1371
+ }),
1372
+ h("span", { className: "dshCodexModelCopy" },
1373
+ h("span", { className: "dshCodexModelHeading" },
1374
+ h("strong", null, model.name ?? model.id),
1375
+ model.name === undefined || model.name === model.id
1376
+ ? null
1377
+ : h("code", { className: "dshCodexModelId" }, model.id)),
1378
+ metadata.length === 0
1379
+ ? null
1380
+ : h("span", { className: "dshCodexModelCapabilities" },
1381
+ ...metadata.map((label) => h("span", {
1382
+ key: label,
1383
+ className: "dshCodexModelBadge",
1384
+ }, label))))))
1385
+ })
868
1386
  content = h(React.Fragment, null,
869
1387
  snapshot.writable ? null : h("p", { className: "dshCodexMuted" }, t("modelsReadOnly")),
870
1388
  h("ul", {
1389
+ id: "dsh-codex-model-list",
871
1390
  className: "dshCodexModelList",
872
1391
  "aria-labelledby": "dsh-codex-models-title",
873
1392
  }, ...modelItems),
1393
+ hiddenModelCount === 0 ? null : h("button", {
1394
+ type: "button",
1395
+ className: "dshCodexButton dshCodexModelToggle",
1396
+ "aria-controls": "dsh-codex-model-list",
1397
+ "aria-expanded": expanded ? "true" : "false",
1398
+ ref: modelToggleRef,
1399
+ disabled: saving,
1400
+ onClick: () => setExpanded((current) => !current),
1401
+ }, expanded
1402
+ ? t("modelsCollapse")
1403
+ : t("modelsShowMore").replace("{count}", String(hiddenModelCount))),
874
1404
  plan.allCatalogSelected ? h("p", { className: "dshCodexMuted" }, t(plan.unsetOverride
875
1405
  ? "modelsAllEnabledFollow"
876
1406
  : "modelsAllEnabledPreserve")) : null,
@@ -894,7 +1424,11 @@ window.__ModuleLoader__.load({
894
1424
  return h("section", { className: "dshCodexPanel dshCodexCard dshCodexModelPanel" },
895
1425
  h("header", { className: "dshCodexHeader" },
896
1426
  h("div", null,
897
- h("h3", { id: "dsh-codex-models-title" }, t("modelsTitle")),
1427
+ h("div", { className: "dshCodexModelTitleRow" },
1428
+ h("h3", { id: "dsh-codex-models-title" }, t("modelsTitle")),
1429
+ modelSummary === null
1430
+ ? null
1431
+ : h("span", { className: "dshCodexModelSummary" }, modelSummary)),
898
1432
  h("p", { className: "dshCodexWarning" }, t("modelsDescription")))),
899
1433
  content)
900
1434
  }
@@ -912,9 +1446,11 @@ window.__ModuleLoader__.load({
912
1446
 
913
1447
  function apply(ctx) {
914
1448
  ctx.effect(installStyle, "dsh-codex: client styles")
1449
+ ctx.effect(installSettingsNavIcon, "dsh-codex: settings nav icon")
915
1450
  ctx.effect(() => ctx.locale.register(NS, { zh, en }), "dsh-codex: client dictionaries")
916
1451
  const t = ctx.locale.bind(NS)
917
1452
  const client = createAuthorizationClient(ctx.connection)
1453
+ const sessionPreferenceClient = createSessionPreferenceClient(ctx.connection)
918
1454
  const modelClient = createModelEnablementClient(ctx.connection, ctx.settingsScope?.describe?.())
919
1455
  ctx.slots.inject("settings.section", () => ctx.slots.register({
920
1456
  name: "settings.section",
@@ -924,6 +1460,115 @@ window.__ModuleLoader__.load({
924
1460
  locale: NS,
925
1461
  inject: () => ({ client, modelClient, t }),
926
1462
  }, CodexSettings))
1463
+ ctx.inject?.(["slots", "modelDirectories"], (scope) => {
1464
+ scope.slots.inject("conversation.input.right", () => scope.slots.register({
1465
+ name: "conversation.input.right",
1466
+ id: "dsh-codex-fast",
1467
+ order: 100,
1468
+ locale: NS,
1469
+ inject: (sessionId) => {
1470
+ const directory = scope.modelDirectories.directoryFor(sessionId)
1471
+ return {
1472
+ preferenceClient: sessionPreferenceClient.forSession(sessionId),
1473
+ selectModel: (selection) => directory.select(selection),
1474
+ t,
1475
+ hooks: {
1476
+ modelDirectory: directory.store,
1477
+ },
1478
+ }
1479
+ },
1480
+ }, CodexFastToggle))
1481
+ })
1482
+ }
1483
+
1484
+ function installSettingsNavIcon() {
1485
+ if (typeof document === "undefined" || typeof window === "undefined") return undefined
1486
+ const Observer = typeof window.MutationObserver === "function"
1487
+ ? window.MutationObserver
1488
+ : typeof MutationObserver === "function" ? MutationObserver : undefined
1489
+ if (!document.body || typeof document.querySelectorAll !== "function" || Observer === undefined) return undefined
1490
+
1491
+ const isInsideSettingsNav = (target) => {
1492
+ const element = target?.nodeType === 1 ? target : target?.parentElement
1493
+ return typeof element?.closest === "function" && element.closest(SETTINGS_NAV_SELECTOR) !== null
1494
+ }
1495
+ const matchesOrContains = (node, selector) => (
1496
+ (typeof node?.matches === "function" && node.matches(selector))
1497
+ || (typeof node?.querySelector === "function" && node.querySelector(selector) !== null)
1498
+ )
1499
+ const mutationTouchesSettingsNav = (records) => Array.from(records ?? []).some((record) => {
1500
+ if (isInsideSettingsNav(record.target)) return true
1501
+ return [...Array.from(record.addedNodes ?? []), ...Array.from(record.removedNodes ?? [])]
1502
+ .some((node) => matchesOrContains(node, SETTINGS_DIALOG_SELECTOR) || matchesOrContains(node, SETTINGS_NAV_SELECTOR))
1503
+ })
1504
+ const enqueueMicrotask = typeof window.queueMicrotask === "function"
1505
+ ? (callback) => window.queueMicrotask(callback)
1506
+ : (callback) => Promise.resolve().then(callback)
1507
+
1508
+ let state = window[NAV_ICON_STATE_KEY]
1509
+ if (state === undefined) {
1510
+ let style = document.querySelector(NAV_ICON_STYLE_SELECTOR)
1511
+ if (style === null) {
1512
+ style = document.createElement("style")
1513
+ style.dataset.pluginNavIcon = "dsh-codex-community"
1514
+ document.head.appendChild(style)
1515
+ }
1516
+ style.dataset.plugin = "dsh-codex-community"
1517
+ const clearMarkers = () => {
1518
+ for (const button of document.querySelectorAll(`[${NAV_ICON_MARKER}]`)) button.removeAttribute(NAV_ICON_MARKER)
1519
+ }
1520
+ state = {
1521
+ active: true,
1522
+ refs: 0,
1523
+ scheduled: false,
1524
+ style,
1525
+ clearMarkers,
1526
+ scheduleSync() {
1527
+ if (!state.active || state.scheduled) return
1528
+ state.scheduled = true
1529
+ enqueueMicrotask(() => {
1530
+ state.scheduled = false
1531
+ state.sync()
1532
+ })
1533
+ },
1534
+ sync() {
1535
+ if (!state.active) return
1536
+ clearMarkers()
1537
+ const candidates = []
1538
+ for (const button of document.querySelectorAll(SETTINGS_NAV_BUTTON_SELECTOR)) {
1539
+ const icon = button.firstElementChild
1540
+ const labelElement = icon?.nextElementSibling
1541
+ if (icon?.localName !== "svg" || labelElement?.localName !== "span") continue
1542
+ const label = String(labelElement.textContent ?? "").replace(/\s+/gu, " ").trim()
1543
+ if (NAV_ICON_LABELS.has(label)) candidates.push(button)
1544
+ }
1545
+ if (candidates.length === 1) candidates[0].setAttribute(NAV_ICON_MARKER, "")
1546
+ },
1547
+ }
1548
+ state.observer = new Observer((records) => {
1549
+ if (mutationTouchesSettingsNav(records)) state.scheduleSync()
1550
+ })
1551
+ state.observer.observe(document.body, { childList: true, subtree: true, characterData: true })
1552
+ window[NAV_ICON_STATE_KEY] = state
1553
+ }
1554
+
1555
+ state.refs += 1
1556
+ state.style.textContent = NAV_ICON_CSS
1557
+ state.sync()
1558
+ let active = true
1559
+ return () => {
1560
+ if (!active) return
1561
+ active = false
1562
+ if (window[NAV_ICON_STATE_KEY] !== state) return
1563
+ state.refs -= 1
1564
+ if (state.refs > 0) return
1565
+ state.active = false
1566
+ state.observer.disconnect()
1567
+ state.clearMarkers()
1568
+ if (typeof state.style.remove === "function") state.style.remove()
1569
+ else state.style.parentNode?.removeChild(state.style)
1570
+ delete window[NAV_ICON_STATE_KEY]
1571
+ }
927
1572
  }
928
1573
 
929
1574
  function installStyle() {
@@ -936,16 +1581,15 @@ window.__ModuleLoader__.load({
936
1581
  document.head.appendChild(style)
937
1582
  }
938
1583
  style.textContent = [
939
- "[role=dialog]:has(.dshCodexPage){width:min(1180px,calc(100vw - 48px))}",
940
- ".dshCodexPage{box-sizing:border-box;display:flex;width:100%;max-width:1120px;min-width:0;flex-direction:column;gap:28px;padding:6px 0 40px;color:var(--dsw-alias-label-primary)}",
1584
+ ".dshCodexPage{box-sizing:border-box;display:flex;width:100%;min-width:0;flex-direction:column;gap:12px;padding:4px 0 20px;color:var(--dsw-alias-label-primary);font:inherit}",
941
1585
  ".dshCodexPage *{box-sizing:border-box}",
942
1586
  ".dshCodexHero{display:flex;min-width:0;flex-direction:column;gap:8px}",
943
1587
  ".dshCodexHero h2,.dshCodexHero p{margin:0}",
944
1588
  ".dshCodexHero h2{font-size:16px;font-weight:500;line-height:24px}",
945
1589
  ".dshCodexHero p{max-width:760px;color:var(--dsw-alias-label-tertiary);font-size:14px;line-height:22px}",
946
- ".dshCodexPanel{display:flex;width:100%;min-width:0;max-width:none;color:var(--dsw-alias-label-primary);flex-direction:column;gap:18px}",
1590
+ ".dshCodexPanel{display:flex;width:100%;min-width:0;color:var(--dsw-alias-label-primary);flex-direction:column;gap:12px}",
947
1591
  ".dshCodexPanel p,.dshCodexPanel label{overflow-wrap:anywhere;word-break:break-word}",
948
- ".dshCodexCard{border:1px solid var(--dsw-alias-border-l2);border-radius:18px;background:var(--dsw-alias-bg-module-platform,var(--dsw-alias-bg-layer-3));padding:clamp(24px,3vw,34px)}",
1592
+ ".dshCodexCard{border:1px solid var(--dsw-alias-border-l2);border-radius:18px;background:var(--dsw-alias-bg-module-platform,var(--dsw-alias-bg-layer-3));padding:18px}",
949
1593
  ".dshCodexHeader{display:flex;align-items:flex-start;justify-content:space-between;gap:16px}",
950
1594
  ".dshCodexHeader>div{min-width:0}",
951
1595
  ".dshCodexHeader h3,.dshCodexHeader p,.dshCodexNotices p,.dshCodexQuota p{margin:0}",
@@ -957,7 +1601,7 @@ window.__ModuleLoader__.load({
957
1601
  ".dshCodexStatus[data-state=active] .dshCodexStatusDot{background:var(--dsw-alias-state-success-primary)}",
958
1602
  ".dshCodexStatus[data-state=invalid] .dshCodexStatusDot{background:var(--dsw-alias-state-error-primary)}",
959
1603
  ".dshCodexStatus[data-state=busy] .dshCodexStatusDot{background:var(--dsw-alias-state-business-primary)}",
960
- ".dshCodexQuota{display:flex;min-width:0;flex-direction:column;gap:10px;border-top:1px solid var(--dsw-alias-border-l2);padding-top:24px}",
1604
+ ".dshCodexQuota{display:flex;min-width:0;flex-direction:column;gap:10px;border-top:1px solid var(--dsw-alias-border-l2);padding-top:16px}",
961
1605
  ".dshCodexQuotaHeader{display:flex;min-width:0;align-items:center;justify-content:space-between;gap:12px}",
962
1606
  ".dshCodexQuota h3{margin:0;font-size:16px;font-weight:500;line-height:24px}",
963
1607
  ".dshCodexQuotaProduct{font-size:14px;font-weight:500;line-height:22px}",
@@ -966,28 +1610,61 @@ window.__ModuleLoader__.load({
966
1610
  ".dshCodexQuotaUnknown::before{background:var(--dsw-alias-label-tertiary)}",
967
1611
  ".dshCodexQuotaExhausted::before{background:var(--dsw-alias-state-error-primary)}",
968
1612
  ".dshCodexQuotaExhausted{color:var(--dsw-alias-state-error-primary)}",
1613
+ ".dshCodexQuotaGroup{display:flex;min-width:0;flex-direction:column;gap:8px}",
1614
+ ".dshCodexQuotaGroup+.dshCodexQuotaGroup{border-top:1px solid var(--dsw-alias-border-l2);padding-top:14px}",
1615
+ ".dshCodexQuotaWindows{display:grid;min-width:0;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}",
1616
+ ".dshCodexQuotaWindow{display:flex;min-width:0;flex-direction:column;gap:8px;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-layer-1);padding:11px 12px}",
1617
+ ".dshCodexQuotaWindowHeader,.dshCodexQuotaMeta{display:flex;min-width:0;align-items:center;justify-content:space-between;gap:12px}",
1618
+ ".dshCodexQuotaWindowHeader{font-size:14px;line-height:22px}",
1619
+ ".dshCodexQuotaWindowHeader strong{font-weight:500;color:var(--dsw-alias-label-secondary)}",
1620
+ ".dshCodexQuotaMeta{justify-content:flex-end;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}",
1621
+ ".dshCodexQuotaMeta time{cursor:help}",
1622
+ ".dshCodexQuotaWindow progress{display:block;width:100%;height:8px;overflow:hidden;border:0;border-radius:999px;appearance:none;-webkit-appearance:none;background:var(--dsw-alias-bg-layer-1)}",
1623
+ ".dshCodexQuotaWindow progress::-webkit-progress-bar{border-radius:999px;background:var(--dsw-alias-bg-layer-1)}",
1624
+ ".dshCodexQuotaWindow progress::-webkit-progress-value{border-radius:999px;background:var(--dsw-alias-state-business-primary)}",
1625
+ ".dshCodexQuotaWindow progress::-moz-progress-bar{border-radius:999px;background:var(--dsw-alias-state-business-primary)}",
969
1626
  ".dshCodexMuted{color:var(--dsw-alias-label-tertiary);font-size:14px;line-height:22px}",
1627
+ ".dshCodexQuota .dshCodexQuotaUpdated{margin-top:2px;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}",
970
1628
  ".dshCodexWarning{max-width:820px;color:var(--dsw-alias-label-secondary);font-size:14px;line-height:22px}",
971
1629
  ".dshCodexActions{display:flex;min-width:0;flex-wrap:wrap;gap:8px}",
972
1630
  ".dshCodexAuthActions{margin-left:auto;justify-content:flex-end}",
973
1631
  ".dshCodexAuthActions .dshCodexButton{height:36px;min-height:36px;border-radius:18px;padding:0 14px;font-size:14px;line-height:22px}",
974
- ".dshCodexQuotaHeader .dshCodexRefreshButton{min-height:28px;border:none;border-radius:14px;background:transparent;color:var(--dsw-alias-label-tertiary);padding:0 10px;font-size:12px;line-height:18px}",
1632
+ ".dshCodexQuotaHeader .dshCodexRefreshButton{display:inline-flex;min-height:30px;align-items:center;gap:6px;border:1px solid var(--dsw-alias-border-l2);border-radius:15px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);padding:0 10px;font-size:12px;line-height:18px;white-space:nowrap}",
1633
+ ".dshCodexRefreshIcon{width:14px;height:14px;flex:none}",
1634
+ ".dshCodexRefreshButton[data-loading=true] .dshCodexRefreshIcon{animation:dshCodexRefreshSpin .8s linear infinite}",
1635
+ "@keyframes dshCodexRefreshSpin{to{transform:rotate(360deg)}}",
975
1636
  ".dshCodexButton{box-sizing:border-box;max-width:100%;white-space:normal;overflow-wrap:anywhere;word-break:break-word;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font:inherit;cursor:pointer;padding:8px 14px;transition:background-color .15s ease,border-color .15s ease}",
976
1637
  ".dshCodexButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,var(--dsw-alias-bg-layer-2))}",
977
1638
  ".dshCodexButton:focus-visible,.dshCodexModelOption:has(input:focus-visible){outline:2px solid var(--dsw-alias-brand-primary,var(--dsw-alias-border-l3));outline-offset:2px}",
978
1639
  ".dshCodexButton:disabled{cursor:not-allowed;opacity:.5}",
1640
+ ".dshCodexFastToggle{box-sizing:border-box;display:inline-flex;width:28px;height:28px;flex:none;align-items:center;justify-content:center;border:0;border-radius:999px;background:transparent;color:var(--dsw-alias-label-tertiary);font:inherit;cursor:pointer;padding:0;transition:background-color .15s ease,color .15s ease}",
1641
+ ".dshCodexFastToggle:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,var(--dsw-alias-bg-layer-2));color:var(--dsw-alias-label-primary)}",
1642
+ ".dshCodexFastToggle[data-fast=true]{background:var(--dsw-alias-state-business-secondary,var(--dsw-alias-bg-layer-2));color:var(--dsw-alias-state-business-primary)}",
1643
+ ".dshCodexFastToggle:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,var(--dsw-alias-border-l3));outline-offset:2px}",
1644
+ ".dshCodexFastToggle:disabled{cursor:not-allowed;opacity:.45}",
1645
+ ".dshCodexLegacyRepair{box-sizing:border-box;display:inline-flex;min-width:0;height:28px;flex:none;align-items:center;border:1px solid var(--dsw-alias-border-l2);border-radius:14px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-state-warning-primary,var(--dsw-alias-label-secondary));font:inherit;font-size:12px;line-height:18px;cursor:pointer;padding:0 10px;white-space:nowrap}",
1646
+ ".dshCodexLegacyRepair:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,var(--dsw-alias-bg-layer-2))}",
1647
+ ".dshCodexLegacyRepair:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,var(--dsw-alias-border-l3));outline-offset:2px}",
1648
+ ".dshCodexLegacyRepair:disabled{cursor:not-allowed;opacity:.55}",
979
1649
  ".dshCodexPrimary{border-color:var(--dsw-alias-button-primary-fill,var(--dsw-alias-state-business-primary));background:var(--dsw-alias-button-primary-fill,var(--dsw-alias-state-business-primary));color:var(--dsw-alias-label-primary-foreground,#fff)}",
980
1650
  ".dshCodexPrimary:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover,var(--dsw-alias-state-business-primary))}",
981
- ".dshCodexModelList{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));column-gap:36px;row-gap:8px;margin:0;padding:0;list-style:none}",
982
- ".dshCodexModelOption{display:flex;min-width:0;align-items:flex-start;gap:12px;border-radius:10px;padding:10px 8px;cursor:pointer;transition:background-color .15s ease}",
1651
+ ".dshCodexModelList{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;margin:0;padding:0;list-style:none}",
1652
+ ".dshCodexModelTitleRow{display:flex;min-width:0;flex-wrap:wrap;align-items:baseline;justify-content:space-between;gap:4px 12px}",
1653
+ ".dshCodexModelSummary{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px;white-space:nowrap}",
1654
+ ".dshCodexModelOption{display:flex;min-width:0;height:100%;align-items:flex-start;gap:10px;border:1px solid var(--dsw-alias-border-l2);border-radius:11px;background:var(--dsw-alias-bg-layer-1);padding:9px 10px;cursor:pointer;transition:background-color .15s ease,border-color .15s ease}",
983
1655
  ".dshCodexModelOption:hover{background:var(--dsw-alias-interactive-bg-hover,var(--dsw-alias-bg-layer-2))}",
1656
+ ".dshCodexModelOption[data-selected=true]{border-color:var(--dsw-alias-state-business-primary)}",
984
1657
  ".dshCodexModelOption:has(input:disabled){cursor:not-allowed;opacity:.65}",
985
1658
  ".dshCodexModelOption:has(input:disabled):hover{background:transparent}",
986
1659
  ".dshCodexModelOption input{width:18px;height:18px;flex:none;margin:2px 0 0;accent-color:var(--dsw-alias-state-business-primary)}",
987
- ".dshCodexModelOption span{display:flex;min-width:0;flex-wrap:wrap;align-items:baseline;column-gap:8px;row-gap:2px}",
1660
+ ".dshCodexModelCopy{display:flex;min-width:0;flex:1;flex-direction:column;gap:5px}",
1661
+ ".dshCodexModelHeading{display:flex;min-width:0;flex-wrap:wrap;align-items:baseline;justify-content:space-between;gap:1px 8px}",
988
1662
  ".dshCodexModelOption strong{overflow-wrap:anywhere;font-size:14px;font-weight:500;line-height:22px}",
989
- ".dshCodexModelOption code{overflow-wrap:anywhere;color:var(--dsw-alias-label-tertiary);font-family:inherit;font-size:12px;line-height:18px}",
990
- ".dshCodexModelActions{justify-content:flex-end;border-top:1px solid var(--dsw-alias-border-l2);padding-top:20px}",
1663
+ ".dshCodexModelId{overflow-wrap:anywhere;color:var(--dsw-alias-label-tertiary);font-family:inherit;font-size:12px;line-height:18px}",
1664
+ ".dshCodexModelCapabilities{display:flex;min-width:0;flex-wrap:wrap;gap:6px}",
1665
+ ".dshCodexModelBadge{display:inline-flex;align-items:center;border-radius:999px;background:var(--dsw-alias-bg-layer-2,var(--dsw-alias-bg-module-platform));color:var(--dsw-alias-label-secondary);font-family:inherit;font-size:12px;line-height:18px;padding:1px 7px}",
1666
+ ".dshCodexModelToggle{align-self:center;border-radius:999px;padding:6px 12px;font-size:12px;line-height:18px}",
1667
+ ".dshCodexModelActions{justify-content:flex-end;border-top:1px solid var(--dsw-alias-border-l2);padding-top:16px}",
991
1668
  ".dshCodexNotices{display:flex;min-width:0;flex-direction:column;gap:10px;margin:0;padding:0;list-style:none}",
992
1669
  ".dshCodexNotices li,.dshCodexPrompt{box-sizing:border-box;min-width:0;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-layer-1);padding:14px 16px}",
993
1670
  ".dshCodexNotices a{display:inline-block;max-width:100%;margin-top:8px;color:var(--dsw-alias-state-business-primary)}",
@@ -998,9 +1675,10 @@ window.__ModuleLoader__.load({
998
1675
  ".dshCodexPrompt input,.dshCodexPrompt select{box-sizing:border-box;width:100%;min-width:0;height:38px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font:inherit;padding:0 10px}",
999
1676
  ".dshCodexError{margin:0;color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:18px}",
1000
1677
  ".dshCodexSuccess{margin:0;color:var(--dsw-alias-state-success-primary);font-size:12px;line-height:18px}",
1001
- "@media(max-width:760px){.dshCodexCard{padding:24px}.dshCodexModelList{grid-template-columns:1fr}.dshCodexModelActions{justify-content:flex-start}}",
1678
+ "@media(prefers-reduced-motion:reduce){.dshCodexRefreshButton[data-loading=true] .dshCodexRefreshIcon{animation:none}}",
1679
+ "@media(max-width:760px){.dshCodexQuotaWindows,.dshCodexModelList{grid-template-columns:1fr}.dshCodexModelActions{justify-content:flex-start}}",
1002
1680
  "@media(max-width:640px){.dshCodexAuthToolbar,.dshCodexHeader{align-items:stretch;flex-direction:column}.dshCodexAuthActions{margin-left:0;justify-content:flex-start}}",
1003
- "@media(max-width:480px){[role=dialog]:has(.dshCodexPage){flex-direction:column}[role=dialog]:has(.dshCodexPage)>nav{box-sizing:border-box;width:100%;height:auto;flex:none;gap:0;overflow:hidden;padding:10px 12px 8px}[role=dialog]:has(.dshCodexPage)>nav>div:first-child{display:none}[role=dialog]:has(.dshCodexPage)>nav>div:last-child{display:flex;width:100%;flex-direction:row;gap:4px;overflow-x:auto;padding-bottom:2px}[role=dialog]:has(.dshCodexPage)>nav>div:last-child>*{flex:none}[role=dialog]:has(.dshCodexPage)>:not(nav){width:100%;min-width:0;min-height:0;overflow:hidden;flex:1 1 0%}.dshCodexPage{gap:20px;padding:0 0 28px}.dshCodexCard{border-radius:14px;padding:18px}.dshCodexActions{align-items:stretch;flex-direction:column}.dshCodexButton{width:100%}}",
1681
+ "@media(max-width:480px){.dshCodexPage{gap:10px;padding:0 0 16px}.dshCodexCard{border-radius:14px;padding:16px}.dshCodexActions{align-items:stretch;flex-direction:column}.dshCodexButton{width:100%}.dshCodexQuotaHeader .dshCodexRefreshButton{width:auto}.dshCodexQuotaWindowHeader{gap:8px}}",
1004
1682
  ].join("")
1005
1683
  const references = style[STYLE_REF_KEY]
1006
1684
  style[STYLE_REF_KEY] = Number.isSafeInteger(references) && references > 0
@@ -1024,13 +1702,17 @@ window.__ModuleLoader__.load({
1024
1702
  exports.AuthorizationSettings = AuthorizationSettings
1025
1703
  exports.CHANNEL = CHANNEL
1026
1704
  exports.CODEX_PROVIDER = CODEX_PROVIDER
1705
+ exports.CodexFastToggle = CodexFastToggle
1027
1706
  exports.CodexSettings = CodexSettings
1028
1707
  exports.ModelEnablementSettings = ModelEnablementSettings
1029
1708
  exports.NS = NS
1030
1709
  exports.apply = apply
1031
1710
  exports.createAuthorizationClient = createAuthorizationClient
1032
1711
  exports.createModelEnablementClient = createModelEnablementClient
1712
+ exports.createSessionPreferenceClient = createSessionPreferenceClient
1713
+ exports.formatResetDistance = formatResetDistance
1033
1714
  exports.inject = inject
1715
+ exports.installSettingsNavIcon = installSettingsNavIcon
1034
1716
  exports.safeQuotaSnapshot = safeQuotaSnapshot
1035
1717
  return module.exports
1036
1718
  },