@tnnevol/dsh-codex-auth 0.1.0-rc.7

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 ADDED
@@ -0,0 +1,1661 @@
1
+ window.__ModuleLoader__.load({
2
+ id: "@tnnevol/dsh-codex-auth",
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ let react = require("react");
8
+ let react_jsx_runtime = require("react/jsx-runtime");
9
+ //#region src/client/CodexCapabilities.tsx
10
+ /** Live optional-capability settings for the Codex Auth plugin. */
11
+ const sectionStyle = {
12
+ display: "flex",
13
+ flexDirection: "column",
14
+ gap: 12,
15
+ borderTop: "1px solid var(--dsw-alias-border-l2)",
16
+ paddingTop: 14
17
+ };
18
+ const headingStyle = {
19
+ margin: 0,
20
+ fontSize: 14,
21
+ lineHeight: "20px",
22
+ fontWeight: 600,
23
+ color: "var(--dsw-alias-label-primary)"
24
+ };
25
+ const bodyStyle$1 = {
26
+ margin: 0,
27
+ fontSize: 12,
28
+ lineHeight: "18px",
29
+ color: "var(--dsw-alias-label-secondary)"
30
+ };
31
+ const fieldsetStyle = {
32
+ display: "flex",
33
+ flexDirection: "column",
34
+ gap: 12,
35
+ margin: 0,
36
+ padding: 0,
37
+ border: 0
38
+ };
39
+ const rowStyle$1 = {
40
+ display: "flex",
41
+ alignItems: "flex-start",
42
+ gap: 9,
43
+ cursor: "pointer"
44
+ };
45
+ const disabledRowStyle = {
46
+ ...rowStyle$1,
47
+ cursor: "not-allowed",
48
+ opacity: .62
49
+ };
50
+ const copyStyle = {
51
+ display: "flex",
52
+ flexDirection: "column",
53
+ gap: 2
54
+ };
55
+ const labelStyle = {
56
+ fontSize: 13,
57
+ lineHeight: "18px",
58
+ fontWeight: 500,
59
+ color: "var(--dsw-alias-label-primary)"
60
+ };
61
+ const actionsStyle = {
62
+ display: "flex",
63
+ alignItems: "center",
64
+ justifyContent: "space-between",
65
+ gap: 10,
66
+ flexWrap: "wrap"
67
+ };
68
+ const buttonsStyle = {
69
+ display: "flex",
70
+ gap: 8
71
+ };
72
+ const buttonStyle$1 = {
73
+ boxSizing: "border-box",
74
+ minHeight: 30,
75
+ padding: "4px 12px",
76
+ border: "1px solid var(--dsw-alias-border-l2)",
77
+ borderRadius: 16,
78
+ background: "var(--dsw-alias-bg-layer-1)",
79
+ color: "var(--dsw-alias-label-primary)",
80
+ font: "inherit",
81
+ fontSize: 12,
82
+ cursor: "pointer"
83
+ };
84
+ const primaryButtonStyle$1 = {
85
+ ...buttonStyle$1,
86
+ border: 0,
87
+ background: "var(--dsw-alias-button-primary-fill)",
88
+ color: "var(--dsw-alias-label-primary-foreground)"
89
+ };
90
+ const errorStyle$1 = {
91
+ ...bodyStyle$1,
92
+ color: "var(--dsw-alias-state-error-primary, #d92d20)"
93
+ };
94
+ const successStyle = {
95
+ ...bodyStyle$1,
96
+ color: "var(--dsw-alias-state-success-primary, #16825d)"
97
+ };
98
+ const UNAVAILABLE_SNAPSHOT = {
99
+ status: "unavailable",
100
+ value: void 0,
101
+ base: void 0,
102
+ user: void 0,
103
+ revision: void 0,
104
+ writable: false,
105
+ mode: "memory"
106
+ };
107
+ /** Render the capability controls with the same Save/Discard contract as DSH settings. */
108
+ function CodexCapabilities({ scope, t }) {
109
+ const subscribe = (0, react.useCallback)((listener) => scope?.subscribe(listener) ?? (() => void 0), [scope]);
110
+ const getSnapshot = (0, react.useCallback)(() => scope?.getSnapshot() ?? UNAVAILABLE_SNAPSHOT, [scope]);
111
+ const snapshot = (0, react.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
112
+ const [draft, setDraft] = (0, react.useState)(snapshot.value);
113
+ const [dirty, setDirty] = (0, react.useState)(false);
114
+ const [busy, setBusy] = (0, react.useState)(false);
115
+ const [feedback, setFeedback] = (0, react.useState)("idle");
116
+ (0, react.useEffect)(() => {
117
+ if (!dirty && !busy) setDraft(snapshot.value);
118
+ }, [
119
+ busy,
120
+ dirty,
121
+ snapshot.revision,
122
+ snapshot.value
123
+ ]);
124
+ const updateImageTool = (enabled) => {
125
+ setDraft((current) => current === void 0 ? current : {
126
+ ...current,
127
+ enableImageTool: enabled
128
+ });
129
+ setDirty(true);
130
+ setFeedback("idle");
131
+ };
132
+ const updateImageUpload = (enabled) => {
133
+ setDraft((current) => current === void 0 ? current : {
134
+ ...current,
135
+ enableImageUpload: enabled
136
+ });
137
+ setDirty(true);
138
+ setFeedback("idle");
139
+ };
140
+ const discard = () => {
141
+ setDraft(scope?.getSnapshot().value);
142
+ setDirty(false);
143
+ setFeedback("idle");
144
+ };
145
+ const save = async () => {
146
+ if (scope === void 0 || draft === void 0 || !snapshot.writable || busy) return;
147
+ setBusy(true);
148
+ setFeedback("idle");
149
+ try {
150
+ await scope.set("enableImageTool", draft.enableImageTool);
151
+ await scope.set("enableImageUpload", draft.enableImageUpload);
152
+ const accepted = scope.getSnapshot().value;
153
+ if (accepted?.enableImageTool !== draft.enableImageTool || accepted?.enableImageUpload !== draft.enableImageUpload) throw new Error("Host returned a different image setting");
154
+ setDraft(accepted);
155
+ setDirty(false);
156
+ setFeedback("saved");
157
+ } catch {
158
+ setDraft(scope.getSnapshot().value);
159
+ setDirty(false);
160
+ setFeedback("error");
161
+ } finally {
162
+ setBusy(false);
163
+ }
164
+ };
165
+ const loading = snapshot.status === "loading";
166
+ const editable = snapshot.status === "ready" && snapshot.writable && !busy;
167
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
168
+ style: sectionStyle,
169
+ "aria-labelledby": "dsh-codex-capabilities-title",
170
+ children: [
171
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
172
+ id: "dsh-codex-capabilities-title",
173
+ style: headingStyle,
174
+ children: t("capabilitiesTitle")
175
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
176
+ style: {
177
+ ...bodyStyle$1,
178
+ marginTop: 3
179
+ },
180
+ children: t("capabilitiesIntro")
181
+ })] }),
182
+ loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
183
+ style: bodyStyle$1,
184
+ role: "status",
185
+ children: t("settingsLoading")
186
+ }) : null,
187
+ snapshot.status === "unavailable" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
188
+ style: errorStyle$1,
189
+ role: "alert",
190
+ children: t("settingsUnavailable")
191
+ }) : null,
192
+ snapshot.status === "ready" && !snapshot.writable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
193
+ style: errorStyle$1,
194
+ role: "alert",
195
+ children: t("settingsReadOnly")
196
+ }) : null,
197
+ draft === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("fieldset", {
198
+ style: fieldsetStyle,
199
+ disabled: !editable,
200
+ children: [
201
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
202
+ style: rowStyle$1,
203
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
204
+ type: "checkbox",
205
+ checked: draft.enableImageTool,
206
+ onChange: (event) => {
207
+ updateImageTool(event.currentTarget.checked);
208
+ }
209
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
210
+ style: copyStyle,
211
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
212
+ style: labelStyle,
213
+ children: t("enableImageRecognition")
214
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
215
+ style: bodyStyle$1,
216
+ children: t("enableImageRecognitionHelp")
217
+ })]
218
+ })]
219
+ }),
220
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
221
+ style: rowStyle$1,
222
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
223
+ type: "checkbox",
224
+ checked: draft.enableImageUpload,
225
+ onChange: (event) => {
226
+ updateImageUpload(event.currentTarget.checked);
227
+ }
228
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
229
+ style: copyStyle,
230
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
231
+ style: labelStyle,
232
+ children: t("enableImageUpload")
233
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
234
+ style: bodyStyle$1,
235
+ children: t("enableImageUploadHelp")
236
+ })]
237
+ })]
238
+ }),
239
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
240
+ style: disabledRowStyle,
241
+ title: t("imageGenerationUnavailableHelp"),
242
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
243
+ type: "checkbox",
244
+ checked: false,
245
+ disabled: true,
246
+ "aria-label": t("enableImageGeneration"),
247
+ readOnly: true
248
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
249
+ style: copyStyle,
250
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
251
+ style: labelStyle,
252
+ children: t("enableImageGeneration")
253
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
254
+ style: bodyStyle$1,
255
+ children: t("imageGenerationUnavailableHelp")
256
+ })]
257
+ })]
258
+ })
259
+ ]
260
+ }),
261
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
262
+ style: actionsStyle,
263
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
264
+ "aria-live": "polite",
265
+ children: [feedback === "saved" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
266
+ style: successStyle,
267
+ children: t("settingsSaved")
268
+ }) : null, feedback === "error" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
269
+ style: errorStyle$1,
270
+ children: t("settingsSaveFailed")
271
+ }) : null]
272
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
273
+ style: buttonsStyle,
274
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
275
+ type: "button",
276
+ style: buttonStyle$1,
277
+ disabled: !dirty || busy,
278
+ onClick: discard,
279
+ children: t("discard")
280
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
281
+ type: "button",
282
+ style: primaryButtonStyle$1,
283
+ disabled: !dirty || !snapshot.writable || busy,
284
+ onClick: () => {
285
+ save();
286
+ },
287
+ children: busy ? t("saving") : t("save")
288
+ })]
289
+ })]
290
+ })
291
+ ]
292
+ });
293
+ }
294
+ //#endregion
295
+ //#region src/auth-paths.ts
296
+ /** Browser-facing paths owned by the standalone Codex authentication plugin. */
297
+ /** Host settings namespace used to dispatch the browser settings card. */
298
+ const CODEX_AUTH_SETTINGS_NAMESPACE = "dsh-codex-auth";
299
+ const CODEX_AUTH_STATUS_PATH = "/plugins/dsh-codex-auth-plugin/auth/status";
300
+ const CODEX_AUTH_LOGIN_PATH = "/plugins/dsh-codex-auth-plugin/auth/login";
301
+ const CODEX_AUTH_LOGOUT_PATH = "/plugins/dsh-codex-auth-plugin/auth/logout";
302
+ const CODEX_USAGE_PATH = "/plugins/dsh-codex-auth-plugin/auth/usage";
303
+ //#endregion
304
+ //#region src/client/CodexAuthCard.tsx
305
+ /** Expandable account card for the DSH Plugins settings section. */
306
+ const cardStyle = {
307
+ overflow: "hidden",
308
+ listStyle: "none",
309
+ border: "1px solid var(--dsw-alias-border-l2)",
310
+ borderRadius: 12,
311
+ background: "var(--dsw-alias-bg-layer-3)",
312
+ transition: "border-color 160ms ease, background 160ms ease"
313
+ };
314
+ const cardOpenStyle = {
315
+ background: "var(--dsw-alias-bg-layer-2)",
316
+ borderColor: "var(--dsw-alias-label-dimmed)"
317
+ };
318
+ const headerStyle = {
319
+ boxSizing: "border-box",
320
+ width: "100%",
321
+ display: "flex",
322
+ alignItems: "center",
323
+ justifyContent: "space-between",
324
+ gap: 12,
325
+ border: 0,
326
+ padding: "14px 16px",
327
+ borderRadius: 12,
328
+ background: "none",
329
+ color: "var(--dsw-alias-label-primary)",
330
+ font: "inherit",
331
+ textAlign: "left",
332
+ cursor: "pointer"
333
+ };
334
+ const headTextStyle = {
335
+ display: "flex",
336
+ minWidth: 0,
337
+ flexDirection: "column",
338
+ gap: 4
339
+ };
340
+ const nameStyle = {
341
+ fontSize: 15,
342
+ lineHeight: "1.4",
343
+ fontWeight: 600
344
+ };
345
+ const descriptionStyle = {
346
+ fontSize: 13,
347
+ lineHeight: "1.5",
348
+ color: "var(--dsw-alias-label-tertiary)"
349
+ };
350
+ const bodyStyle = {
351
+ margin: 0,
352
+ fontSize: 13,
353
+ lineHeight: "20px",
354
+ color: "var(--dsw-alias-label-secondary)"
355
+ };
356
+ const errorStyle = {
357
+ ...bodyStyle,
358
+ color: "var(--dsw-alias-state-error-primary, #d92d20)"
359
+ };
360
+ const cardBodyStyle = {
361
+ display: "flex",
362
+ flexDirection: "column",
363
+ gap: 14,
364
+ borderTop: "1px solid var(--dsw-alias-border-l2)",
365
+ margin: "0 16px",
366
+ padding: "12px 0 8px"
367
+ };
368
+ const rowStyle = {
369
+ display: "flex",
370
+ alignItems: "center",
371
+ justifyContent: "space-between",
372
+ flexWrap: "wrap",
373
+ gap: 12
374
+ };
375
+ const statusStyle = {
376
+ display: "flex",
377
+ alignItems: "center",
378
+ gap: 9,
379
+ fontSize: 14,
380
+ fontWeight: 500,
381
+ color: "var(--dsw-alias-label-primary)"
382
+ };
383
+ const buttonStyle = {
384
+ boxSizing: "border-box",
385
+ minHeight: 34,
386
+ padding: "6px 14px",
387
+ border: "1px solid var(--dsw-alias-border-l2)",
388
+ borderRadius: 18,
389
+ background: "var(--dsw-alias-bg-layer-1)",
390
+ color: "var(--dsw-alias-label-primary)",
391
+ font: "inherit",
392
+ fontSize: 14,
393
+ cursor: "pointer"
394
+ };
395
+ const codeStyle = {
396
+ display: "inline-flex",
397
+ alignItems: "center",
398
+ minHeight: 38,
399
+ padding: "0 14px",
400
+ border: "1px solid var(--dsw-alias-border-l2)",
401
+ borderRadius: 8,
402
+ background: "var(--dsw-alias-bg-layer-1)",
403
+ color: "var(--dsw-alias-label-primary)",
404
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
405
+ fontSize: 18,
406
+ fontWeight: 700,
407
+ letterSpacing: "0.08em"
408
+ };
409
+ const primaryButtonStyle = {
410
+ ...buttonStyle,
411
+ height: 36,
412
+ minHeight: 36,
413
+ padding: "0 14px",
414
+ border: 0,
415
+ background: "var(--dsw-alias-button-primary-fill)",
416
+ color: "var(--dsw-alias-label-primary-foreground)",
417
+ display: "inline-flex",
418
+ alignItems: "center",
419
+ justifyContent: "center",
420
+ lineHeight: "22px"
421
+ };
422
+ const usageStyle = {
423
+ display: "flex",
424
+ flexDirection: "column",
425
+ gap: 10,
426
+ paddingTop: 2
427
+ };
428
+ const usageHeaderStyle = {
429
+ display: "flex",
430
+ alignItems: "center",
431
+ justifyContent: "space-between",
432
+ gap: 12
433
+ };
434
+ const usageTitleStyle = {
435
+ color: "var(--dsw-alias-label-primary)",
436
+ fontSize: 14,
437
+ fontWeight: 600
438
+ };
439
+ const usageWindowStyle = {
440
+ display: "flex",
441
+ flexDirection: "column",
442
+ gap: 5,
443
+ border: "1px solid var(--dsw-alias-border-l2)",
444
+ borderRadius: 12,
445
+ padding: "14px 14px 15px",
446
+ background: "var(--dsw-alias-bg-layer-1)"
447
+ };
448
+ const usageWindowHeaderStyle = {
449
+ display: "flex",
450
+ alignItems: "center",
451
+ justifyContent: "space-between",
452
+ flexWrap: "wrap",
453
+ gap: 16
454
+ };
455
+ const usageWindowDetailsStyle = {
456
+ display: "flex",
457
+ minWidth: 0,
458
+ flexDirection: "column",
459
+ gap: 3
460
+ };
461
+ const usageWindowTitleStyle = {
462
+ color: "var(--dsw-alias-label-primary)",
463
+ fontSize: 14,
464
+ fontWeight: 600
465
+ };
466
+ const usageResetStyle = {
467
+ color: "var(--dsw-alias-label-tertiary)",
468
+ fontSize: 12
469
+ };
470
+ const usageRemainingStyle = {
471
+ display: "flex",
472
+ alignItems: "center",
473
+ justifyContent: "flex-end",
474
+ flex: "1 1 220px",
475
+ minWidth: 200,
476
+ gap: 12
477
+ };
478
+ const usageTrackStyle = {
479
+ overflow: "hidden",
480
+ flex: "1 1 140px",
481
+ width: 192,
482
+ minWidth: 100,
483
+ maxWidth: 192,
484
+ height: 8,
485
+ borderRadius: 999,
486
+ background: "var(--dsw-alias-bg-layer-3, rgba(127, 127, 127, 0.28))"
487
+ };
488
+ const usageFillStyle = {
489
+ height: "100%",
490
+ borderRadius: "inherit",
491
+ background: "var(--dsw-alias-brand-primary)",
492
+ transition: "width 160ms ease"
493
+ };
494
+ const usageRemainingTextStyle = {
495
+ color: "var(--dsw-alias-label-secondary)",
496
+ fontSize: 14,
497
+ whiteSpace: "nowrap"
498
+ };
499
+ function Chevron({ open }) {
500
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
501
+ "aria-hidden": "true",
502
+ style: {
503
+ color: "var(--dsw-alias-label-tertiary)",
504
+ transform: open ? "rotate(180deg)" : "none",
505
+ transition: "transform 160ms ease"
506
+ },
507
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
508
+ width: "14",
509
+ height: "14",
510
+ viewBox: "0 0 14 14",
511
+ fill: "none",
512
+ xmlns: "http://www.w3.org/2000/svg",
513
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
514
+ d: "M2.15 5.5 3 4.65l3.73 3.73a.38.38 0 0 0 .54 0L11 4.65l.85.85-2.73 2.73c-.58.58-.9.9-1.4 1.03a2.1 2.1 0 0 1-.94 0c-.5-.13-.82-.45-1.4-1.03L2.15 5.5Z",
515
+ fill: "currentColor"
516
+ })
517
+ })
518
+ });
519
+ }
520
+ var AccountRequestError = class extends Error {
521
+ code;
522
+ constructor(code, message) {
523
+ super(message);
524
+ this.code = code;
525
+ this.name = "AccountRequestError";
526
+ }
527
+ };
528
+ async function jsonRequest(path, method = "GET", body) {
529
+ const headers = { accept: "application/json" };
530
+ if (body !== void 0) headers["content-type"] = "application/json";
531
+ const response = await fetch(path, {
532
+ method,
533
+ headers,
534
+ ...body === void 0 ? {} : { body: JSON.stringify(body) },
535
+ credentials: "same-origin"
536
+ });
537
+ const value = await response.json().catch(() => void 0);
538
+ if (!response.ok) {
539
+ const code = typeof value === "object" && value !== null && "error" in value && typeof value.error === "string" ? value.error : `HTTP ${response.status}`;
540
+ throw new AccountRequestError(code, code);
541
+ }
542
+ return value;
543
+ }
544
+ function dotStyle(status) {
545
+ return {
546
+ width: 9,
547
+ height: 9,
548
+ borderRadius: "50%",
549
+ flex: "0 0 auto",
550
+ background: status === "signed-in" ? "var(--dsw-alias-state-success-primary, #22a06b)" : status === "error" || status === "remote-web-origin-not-trusted" ? "var(--dsw-alias-state-error-primary, #d92d20)" : status === "signing-in" || status === "loading" ? "var(--dsw-alias-brand-primary, #1677ff)" : "var(--dsw-alias-label-dimmed, #9aa0a6)"
551
+ };
552
+ }
553
+ function percent(value) {
554
+ if (value === void 0 || !Number.isFinite(value)) return "—";
555
+ return `${Math.round(value)}%`;
556
+ }
557
+ function progressWidth(value) {
558
+ if (value === void 0 || !Number.isFinite(value)) return "0%";
559
+ return `${Math.max(0, Math.min(100, value))}%`;
560
+ }
561
+ function weeklyWindow(usage) {
562
+ return [usage.primaryWindow, usage.secondaryWindow].find((window) => window?.limitWindowSeconds === 604800) ?? usage.secondaryWindow;
563
+ }
564
+ function resetLabel(window, t) {
565
+ if (window?.resetAt !== void 0 && Number.isFinite(window.resetAt)) {
566
+ const date = new Intl.DateTimeFormat(void 0, {
567
+ dateStyle: "long",
568
+ timeStyle: "short"
569
+ }).format(/* @__PURE__ */ new Date(window.resetAt * 1e3));
570
+ return `${t("usageResetAt")} ${date}`;
571
+ }
572
+ if (window?.resetAfterSeconds !== void 0 && Number.isFinite(window.resetAfterSeconds)) {
573
+ const minutes = Math.max(1, Math.ceil(window.resetAfterSeconds / 60));
574
+ return `${t("usageResetAfter")}: ${minutes}${t("usageMinutes")}`;
575
+ }
576
+ }
577
+ function UsageWindowView({ label, value, t }) {
578
+ if (value === void 0) return null;
579
+ const reset = resetLabel(value, t);
580
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
581
+ style: usageWindowStyle,
582
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
583
+ style: usageWindowHeaderStyle,
584
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
585
+ style: usageWindowDetailsStyle,
586
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
587
+ style: usageWindowTitleStyle,
588
+ children: label
589
+ }), reset === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
590
+ style: usageResetStyle,
591
+ children: reset
592
+ })]
593
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
594
+ style: usageRemainingStyle,
595
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
596
+ role: "progressbar",
597
+ "aria-label": label,
598
+ "aria-valuemin": 0,
599
+ "aria-valuemax": 100,
600
+ "aria-valuenow": value.remainingPercent,
601
+ style: usageTrackStyle,
602
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { style: {
603
+ ...usageFillStyle,
604
+ width: progressWidth(value.remainingPercent)
605
+ } })
606
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
607
+ style: usageRemainingTextStyle,
608
+ children: [
609
+ t("usageRemaining"),
610
+ " ",
611
+ percent(value.remainingPercent)
612
+ ]
613
+ })]
614
+ })]
615
+ })
616
+ });
617
+ }
618
+ /** Render a standalone login/logout card in the rc.7 keyed Plugin slot. */
619
+ function CodexAuthCard({ t, configScope }) {
620
+ if (t === void 0) throw new Error("Codex auth card requires its translation function");
621
+ const [open, setOpen] = (0, react.useState)(false);
622
+ const [status, setStatus] = (0, react.useState)({ status: "loading" });
623
+ const [busy, setBusy] = (0, react.useState)(false);
624
+ const [challenge, setChallenge] = (0, react.useState)();
625
+ const [usage, setUsage] = (0, react.useState)({ status: "hidden" });
626
+ const refreshUsage = (0, react.useCallback)(async () => {
627
+ if (status.status !== "signed-in") return;
628
+ setUsage((current) => current.status === "ready" ? current : { status: "loading" });
629
+ try {
630
+ setUsage({
631
+ status: "ready",
632
+ usage: await jsonRequest(CODEX_USAGE_PATH)
633
+ });
634
+ } catch {
635
+ setUsage({ status: "error" });
636
+ }
637
+ }, [status.status]);
638
+ const refresh = (0, react.useCallback)(async () => {
639
+ try {
640
+ const next = await jsonRequest(CODEX_AUTH_STATUS_PATH);
641
+ setStatus(next);
642
+ if (next.status !== "signing-in") setChallenge(void 0);
643
+ } catch (error) {
644
+ setStatus(error instanceof AccountRequestError && error.code === "remote-web-origin-not-trusted" ? { status: "remote-web-origin-not-trusted" } : {
645
+ status: "error",
646
+ message: error instanceof Error ? error.message : t("requestFailed")
647
+ });
648
+ }
649
+ }, [t]);
650
+ (0, react.useEffect)(() => {
651
+ refresh();
652
+ }, [refresh]);
653
+ (0, react.useEffect)(() => {
654
+ if (status.status !== "signing-in") return;
655
+ const timer = window.setInterval(() => {
656
+ refresh();
657
+ }, 1e3);
658
+ return () => {
659
+ window.clearInterval(timer);
660
+ };
661
+ }, [refresh, status.status]);
662
+ (0, react.useEffect)(() => {
663
+ if (status.status !== "signed-in") {
664
+ setUsage({ status: "hidden" });
665
+ return;
666
+ }
667
+ refreshUsage();
668
+ const timer = window.setInterval(() => {
669
+ refreshUsage();
670
+ }, 6e4);
671
+ return () => {
672
+ window.clearInterval(timer);
673
+ };
674
+ }, [refreshUsage, status.status]);
675
+ const signIn = async () => {
676
+ const popup = window.open("about:blank", "_blank");
677
+ if (popup === null) {
678
+ setStatus({
679
+ status: "error",
680
+ message: t("popupBlocked")
681
+ });
682
+ return;
683
+ }
684
+ popup.opener = null;
685
+ setBusy(true);
686
+ setStatus({ status: "signing-in" });
687
+ setChallenge(void 0);
688
+ try {
689
+ const next = await jsonRequest(CODEX_AUTH_LOGIN_PATH, "POST");
690
+ setChallenge(next);
691
+ popup.location.replace(next.verificationUri);
692
+ } catch (error) {
693
+ popup.close();
694
+ setChallenge(void 0);
695
+ setStatus(error instanceof AccountRequestError && error.code === "remote-web-origin-not-trusted" ? { status: "remote-web-origin-not-trusted" } : {
696
+ status: "error",
697
+ message: error instanceof Error ? error.message : t("requestFailed")
698
+ });
699
+ } finally {
700
+ setBusy(false);
701
+ }
702
+ };
703
+ const signOut = async () => {
704
+ setBusy(true);
705
+ try {
706
+ await jsonRequest(CODEX_AUTH_LOGOUT_PATH, "POST");
707
+ setStatus({ status: "signed-out" });
708
+ setUsage({ status: "hidden" });
709
+ setChallenge(void 0);
710
+ } catch (error) {
711
+ setStatus({
712
+ status: "error",
713
+ message: error instanceof Error ? error.message : t("requestFailed")
714
+ });
715
+ } finally {
716
+ setBusy(false);
717
+ }
718
+ };
719
+ const label = status.status === "signed-in" ? t("signedIn") : status.status === "loading" ? t("loading") : status.status === "signing-in" ? t("signingIn") : status.status === "remote-web-origin-not-trusted" ? t("remoteOrigin") : status.status === "error" ? t("requestFailed") : t("signedOut");
720
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
721
+ style: {
722
+ ...cardStyle,
723
+ ...open ? cardOpenStyle : {}
724
+ },
725
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
726
+ type: "button",
727
+ style: headerStyle,
728
+ "aria-expanded": open,
729
+ "aria-label": `${t(open ? "collapse" : "expand")}: ${t("title")}`,
730
+ onClick: () => {
731
+ setOpen(!open);
732
+ },
733
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
734
+ style: headTextStyle,
735
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
736
+ style: nameStyle,
737
+ children: t("title")
738
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
739
+ style: descriptionStyle,
740
+ children: t("intro")
741
+ })]
742
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Chevron, { open })]
743
+ }), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
744
+ style: cardBodyStyle,
745
+ children: [
746
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
747
+ style: rowStyle,
748
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
749
+ style: statusStyle,
750
+ role: "status",
751
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
752
+ "aria-hidden": "true",
753
+ style: dotStyle(status.status)
754
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: label })]
755
+ }), status.status === "loading" || status.status === "remote-web-origin-not-trusted" ? null : status.status === "signed-in" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
756
+ type: "button",
757
+ style: buttonStyle,
758
+ disabled: busy,
759
+ onClick: () => {
760
+ signOut();
761
+ },
762
+ children: busy ? t("working") : t("signOut")
763
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
764
+ type: "button",
765
+ style: primaryButtonStyle,
766
+ disabled: busy,
767
+ onClick: () => {
768
+ signIn();
769
+ },
770
+ children: busy ? t("working") : t("signIn")
771
+ })]
772
+ }),
773
+ status.status === "error" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
774
+ style: errorStyle,
775
+ children: status.message
776
+ }) : null,
777
+ status.status === "remote-web-origin-not-trusted" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
778
+ style: errorStyle,
779
+ children: t("remoteOrigin")
780
+ }) : null,
781
+ usage.status !== "hidden" && status.status === "signed-in" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
782
+ style: usageStyle,
783
+ "aria-label": t("usageTitle"),
784
+ children: [
785
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
786
+ style: usageHeaderStyle,
787
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
788
+ style: usageTitleStyle,
789
+ children: t("usageTitle")
790
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
791
+ type: "button",
792
+ style: {
793
+ ...buttonStyle,
794
+ minHeight: 28,
795
+ padding: "3px 10px",
796
+ fontSize: 12
797
+ },
798
+ onClick: () => {
799
+ refreshUsage();
800
+ },
801
+ children: t("refreshUsage")
802
+ })]
803
+ }),
804
+ usage.status === "loading" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
805
+ style: bodyStyle,
806
+ children: t("usageLoading")
807
+ }) : null,
808
+ usage.status === "error" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
809
+ style: errorStyle,
810
+ children: t("usageUnavailable")
811
+ }) : null,
812
+ usage.status === "ready" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageWindowView, {
813
+ label: t("usageWeekly"),
814
+ value: weeklyWindow(usage.usage),
815
+ t
816
+ }), weeklyWindow(usage.usage) === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
817
+ style: bodyStyle,
818
+ children: t("usageNoWindow")
819
+ }) : null] }) : null
820
+ ]
821
+ }) : null,
822
+ status.status === "signing-in" && challenge !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
823
+ style: {
824
+ display: "flex",
825
+ flexDirection: "column",
826
+ gap: 8
827
+ },
828
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
829
+ style: bodyStyle,
830
+ children: t("authorizationCodeHelp")
831
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
832
+ style: {
833
+ display: "flex",
834
+ alignItems: "center",
835
+ flexWrap: "wrap",
836
+ gap: 8
837
+ },
838
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
839
+ "aria-label": t("authorizationCodeLabel"),
840
+ style: codeStyle,
841
+ children: challenge.userCode
842
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
843
+ href: challenge.verificationUri,
844
+ target: "_blank",
845
+ rel: "noreferrer",
846
+ style: {
847
+ ...buttonStyle,
848
+ display: "inline-flex",
849
+ alignItems: "center",
850
+ textDecoration: "none"
851
+ },
852
+ children: t("openAuthorization")
853
+ })]
854
+ })]
855
+ }) : null,
856
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CodexCapabilities, {
857
+ scope: configScope,
858
+ t
859
+ })
860
+ ]
861
+ }) : null]
862
+ });
863
+ }
864
+ //#endregion
865
+ //#region src/settings-contract.ts
866
+ const DEFAULT_CODEX_AUTH_SETTINGS = Object.freeze({
867
+ enableImageTool: false,
868
+ enableImageUpload: false
869
+ });
870
+ function isRecord(value) {
871
+ return typeof value === "object" && value !== null && !Array.isArray(value);
872
+ }
873
+ /** Narrow the settings wire payload before it enters React state. */
874
+ function decodeCodexAuthSettings(value) {
875
+ if (!isRecord(value) || typeof value["enableImageTool"] !== "boolean") return void 0;
876
+ return {
877
+ enableImageTool: value["enableImageTool"],
878
+ enableImageUpload: typeof value["enableImageUpload"] === "boolean" ? value["enableImageUpload"] : DEFAULT_CODEX_AUTH_SETTINGS.enableImageUpload
879
+ };
880
+ }
881
+ //#endregion
882
+ //#region src/client/model-editor-presentation.ts
883
+ /** Keep Codex-specific fields out of the generic pi-ai editor. */
884
+ const CODEX_EDITOR_ATTRIBUTE = "data-dsh-codex-auth-editor";
885
+ const CODEX_ROUTE = "openai-codex";
886
+ const CODEX_PICKER_ATTRIBUTE = "data-dsh-codex-model-picker";
887
+ const SELECT_ALL_ATTRIBUTE = "data-dsh-codex-select-all";
888
+ const ADD_BUTTON_ATTRIBUTE = "data-dsh-codex-add-models";
889
+ const REMOVE_MANUAL_ADD_ATTRIBUTE = "data-dsh-codex-remove-manual-add";
890
+ const PICKER_SELECTION_SYNCED_ATTRIBUTE = "data-dsh-codex-selection-synced";
891
+ const MODEL_ACTION_ATTRIBUTE = "data-dsh-codex-model-action";
892
+ const MODEL_DETAILS_ATTRIBUTE = "data-dsh-codex-model-details";
893
+ const STYLE_ID = "dsh-codex-auth-model-editor";
894
+ const FETCH_MODEL_LABELS = /* @__PURE__ */ new Set([
895
+ "获取可用模型",
896
+ "获取模型",
897
+ "Fetch available models",
898
+ "Fetch models"
899
+ ]);
900
+ const MODEL_ACTION_LABELS = /* @__PURE__ */ new Map([
901
+ ["获取可用模型", {
902
+ action: "fetch",
903
+ replacement: "获取模型"
904
+ }],
905
+ ["获取模型", {
906
+ action: "fetch",
907
+ replacement: "获取模型"
908
+ }],
909
+ ["Fetch available models", {
910
+ action: "fetch",
911
+ replacement: "Fetch models"
912
+ }],
913
+ ["Fetch models", {
914
+ action: "fetch",
915
+ replacement: "Fetch models"
916
+ }],
917
+ ["恢复默认模型", {
918
+ action: "reset",
919
+ replacement: "恢复模型"
920
+ }],
921
+ ["恢复模型", {
922
+ action: "reset",
923
+ replacement: "恢复模型"
924
+ }],
925
+ ["Restore defaults", {
926
+ action: "reset",
927
+ replacement: "Restore models"
928
+ }],
929
+ ["Restore models", {
930
+ action: "reset",
931
+ replacement: "Restore models"
932
+ }]
933
+ ]);
934
+ const PICKER_TITLE_LABELS = /* @__PURE__ */ new Set([
935
+ "选择要添加的模型",
936
+ "模型列表",
937
+ "Select models to add",
938
+ "Model list"
939
+ ]);
940
+ const ADD_MODEL_LABELS = /* @__PURE__ */ new Set([
941
+ "添加所选",
942
+ "确定",
943
+ "Add selected",
944
+ "Confirm"
945
+ ]);
946
+ const MANUAL_ADD_MODEL_LABELS = /* @__PURE__ */ new Set(["添加模型", "Add model"]);
947
+ const REMOVE_MODEL_LABELS = [
948
+ "移除模型",
949
+ "删除模型",
950
+ "Remove model",
951
+ "Delete model"
952
+ ];
953
+ /** Defaults from the installed pi-ai openai-codex catalog (0.82.1). */
954
+ const CODEX_MODEL_DEFAULTS = {
955
+ "gpt-5.3-codex-spark": {
956
+ contextWindow: 128e3,
957
+ maxTokens: 128e3
958
+ },
959
+ "gpt-5.4": {
960
+ contextWindow: 272e3,
961
+ maxTokens: 128e3
962
+ },
963
+ "gpt-5.4-mini": {
964
+ contextWindow: 272e3,
965
+ maxTokens: 128e3
966
+ },
967
+ "gpt-5.5": {
968
+ contextWindow: 272e3,
969
+ maxTokens: 128e3
970
+ },
971
+ "gpt-5.6-luna": {
972
+ contextWindow: 272e3,
973
+ maxTokens: 128e3
974
+ },
975
+ "gpt-5.6-sol": {
976
+ contextWindow: 272e3,
977
+ maxTokens: 128e3
978
+ },
979
+ "gpt-5.6-terra": {
980
+ contextWindow: 272e3,
981
+ maxTokens: 128e3
982
+ }
983
+ };
984
+ const MODEL_ID_LABELS = ["模型 ID", "Model ID"];
985
+ const MODEL_NAME_LABELS = [
986
+ "显示名称",
987
+ "Display name",
988
+ "模型名称",
989
+ "Model name"
990
+ ];
991
+ const MODEL_READONLY_LABELS = [...MODEL_ID_LABELS, ...MODEL_NAME_LABELS];
992
+ const MODEL_CONTEXT_LABELS = ["上下文窗口", "Context window"];
993
+ const MODEL_MAX_TOKENS_LABELS = ["最大输出 token", "Max output tokens"];
994
+ function labelOf(input) {
995
+ return input.getAttribute("aria-label")?.trim() ?? "";
996
+ }
997
+ function startsWithAny(value, prefixes) {
998
+ return prefixes.some((prefix) => value.startsWith(prefix));
999
+ }
1000
+ function rowNumberOf(input) {
1001
+ const match = labelOf(input).match(/(\d+)$/u);
1002
+ if (match === null) return void 0;
1003
+ const value = Number(match[1]);
1004
+ return Number.isInteger(value) && value > 0 ? value : void 0;
1005
+ }
1006
+ function formatCapacity(value) {
1007
+ return value % 1e3 === 0 ? `${value / 1e3}K` : String(value);
1008
+ }
1009
+ function modelIdInputs(editor) {
1010
+ return [...editor.querySelectorAll("input")].filter((input) => startsWithAny(labelOf(input), MODEL_ID_LABELS));
1011
+ }
1012
+ function modelIds(editor) {
1013
+ return new Set(modelIdInputs(editor).map((input) => input.value.trim()).filter((value) => value.length > 0));
1014
+ }
1015
+ function candidateId(input) {
1016
+ const label = input.closest("label");
1017
+ const value = input.nextElementSibling?.textContent?.trim() || label?.querySelector("span")?.textContent?.trim();
1018
+ return value === void 0 || value.length === 0 ? void 0 : value;
1019
+ }
1020
+ /**
1021
+ * The official Models page intentionally shares one pi-ai editor between all
1022
+ * providers. Codex gets its endpoint and credential from OAuth, so those two
1023
+ * generic inputs would be misleading. Mark only the Codex editor and hide the
1024
+ * corresponding field wrappers; the model catalog remains the official editor.
1025
+ */
1026
+ function installCodexModelEditorPresentation() {
1027
+ const style = document.createElement("style");
1028
+ style.id = STYLE_ID;
1029
+ style.textContent = `
1030
+ [${CODEX_EDITOR_ATTRIBUTE}="true"] div:has(> input[aria-label="API 密钥"]),
1031
+ [${CODEX_EDITOR_ATTRIBUTE}="true"] div:has(> input[aria-label="API Key"]),
1032
+ [${CODEX_EDITOR_ATTRIBUTE}="true"] div:has(> input[aria-label="API 地址"]),
1033
+ [${CODEX_EDITOR_ATTRIBUTE}="true"] div:has(> input[aria-label="API URL"]) {
1034
+ display: none !important;
1035
+ }
1036
+
1037
+ [${CODEX_PICKER_ATTRIBUTE}="true"] [${ADD_BUTTON_ATTRIBUTE}="true"] {
1038
+ border: 0 !important;
1039
+ background: var(--dsw-alias-button-primary-fill) !important;
1040
+ color: var(--dsw-alias-label-primary-foreground) !important;
1041
+ }
1042
+
1043
+ [${CODEX_PICKER_ATTRIBUTE}="true"] [${ADD_BUTTON_ATTRIBUTE}="true"]:hover:not(:disabled) {
1044
+ background: var(--dsw-alias-button-primary-hover) !important;
1045
+ }
1046
+
1047
+ [${CODEX_EDITOR_ATTRIBUTE}="true"] [${REMOVE_MANUAL_ADD_ATTRIBUTE}="true"] {
1048
+ display: none !important;
1049
+ }
1050
+
1051
+ [${CODEX_EDITOR_ATTRIBUTE}="true"] [${MODEL_DETAILS_ATTRIBUTE}="true"] > summary {
1052
+ display: none !important;
1053
+ }
1054
+
1055
+ [${CODEX_EDITOR_ATTRIBUTE}="true"] [${MODEL_DETAILS_ATTRIBUTE}="true"] {
1056
+ display: contents !important;
1057
+ border: 0 !important;
1058
+ padding: 0 !important;
1059
+ }
1060
+
1061
+ [${CODEX_EDITOR_ATTRIBUTE}="true"] [${MODEL_DETAILS_ATTRIBUTE}="true"] > :not(summary) {
1062
+ display: contents !important;
1063
+ border: 0 !important;
1064
+ padding: 0 !important;
1065
+ }
1066
+
1067
+ [${CODEX_EDITOR_ATTRIBUTE}="true"] [${MODEL_DETAILS_ATTRIBUTE}="true"] > :not(summary) > section[aria-label="模型目录"],
1068
+ [${CODEX_EDITOR_ATTRIBUTE}="true"] [${MODEL_DETAILS_ATTRIBUTE}="true"] > :not(summary) > section[aria-label="Models"] {
1069
+ border-top: 0 !important;
1070
+ padding-top: 0 !important;
1071
+ }
1072
+
1073
+ [${CODEX_EDITOR_ATTRIBUTE}="true"] [${MODEL_DETAILS_ATTRIBUTE}="true"]:not([open]) > :not(summary) {
1074
+ display: contents !important;
1075
+ }
1076
+
1077
+ [${CODEX_EDITOR_ATTRIBUTE}="true"] [${MODEL_ACTION_ATTRIBUTE}="fetch"],
1078
+ [${CODEX_EDITOR_ATTRIBUTE}="true"] [${MODEL_ACTION_ATTRIBUTE}="reset"] {
1079
+ box-sizing: border-box;
1080
+ display: inline-flex;
1081
+ align-items: center;
1082
+ justify-content: center;
1083
+ height: 32px;
1084
+ padding: 0 12px;
1085
+ border: 1px solid var(--dsw-alias-border-l2);
1086
+ border-radius: 16px;
1087
+ background: var(--dsw-alias-bg-layer-1);
1088
+ color: var(--dsw-alias-label-primary);
1089
+ font: inherit;
1090
+ font-size: 13px;
1091
+ line-height: 20px;
1092
+ cursor: pointer;
1093
+ }
1094
+
1095
+ [${CODEX_EDITOR_ATTRIBUTE}="true"] [${MODEL_ACTION_ATTRIBUTE}="fetch"]:hover:not(:disabled),
1096
+ [${CODEX_EDITOR_ATTRIBUTE}="true"] [${MODEL_ACTION_ATTRIBUTE}="reset"]:hover:not(:disabled) {
1097
+ background: var(--dsw-alias-interactive-bg-hover-solid);
1098
+ }
1099
+
1100
+ [${CODEX_EDITOR_ATTRIBUTE}="true"] [${MODEL_ACTION_ATTRIBUTE}="fetch"]:disabled,
1101
+ [${CODEX_EDITOR_ATTRIBUTE}="true"] [${MODEL_ACTION_ATTRIBUTE}="reset"]:disabled {
1102
+ opacity: 0.4;
1103
+ cursor: default;
1104
+ }
1105
+
1106
+ [${CODEX_EDITOR_ATTRIBUTE}="true"] div:has(> [${MODEL_ACTION_ATTRIBUTE}="fetch"]),
1107
+ [${CODEX_EDITOR_ATTRIBUTE}="true"] div:has(> [${MODEL_ACTION_ATTRIBUTE}="reset"]) {
1108
+ justify-content: flex-start;
1109
+ align-items: center;
1110
+ gap: 15px;
1111
+ }
1112
+
1113
+ [${CODEX_EDITOR_ATTRIBUTE}="true"] div:has(> [${MODEL_ACTION_ATTRIBUTE}="reset"]) > [${MODEL_ACTION_ATTRIBUTE}="reset"],
1114
+ [${CODEX_EDITOR_ATTRIBUTE}="true"] div:not(:has(> [${MODEL_ACTION_ATTRIBUTE}="reset"])):has(> [${MODEL_ACTION_ATTRIBUTE}="fetch"]) > [${MODEL_ACTION_ATTRIBUTE}="fetch"] {
1115
+ margin-left: auto;
1116
+ }
1117
+
1118
+ [${CODEX_PICKER_ATTRIBUTE}="true"] [${SELECT_ALL_ATTRIBUTE}="true"] {
1119
+ display: flex;
1120
+ align-items: center;
1121
+ gap: 8px;
1122
+ padding: 6px 8px;
1123
+ color: var(--dsw-alias-label-primary);
1124
+ cursor: pointer;
1125
+ }
1126
+
1127
+ [${CODEX_PICKER_ATTRIBUTE}="true"] input[type="checkbox"] {
1128
+ flex: none;
1129
+ width: 16px;
1130
+ height: 16px;
1131
+ margin: 3px 0 0;
1132
+ accent-color: var(--dsw-alias-button-primary-fill);
1133
+ cursor: pointer;
1134
+ }
1135
+
1136
+ [${CODEX_PICKER_ATTRIBUTE}="true"] input[type="checkbox"]:focus-visible {
1137
+ outline: 2px solid var(--dsw-alias-border-l4);
1138
+ outline-offset: 2px;
1139
+ }
1140
+
1141
+ [${CODEX_PICKER_ATTRIBUTE}="true"] input[type="checkbox"]:disabled {
1142
+ cursor: default;
1143
+ }
1144
+ `;
1145
+ document.head.appendChild(style);
1146
+ let codexPickerPending = false;
1147
+ const originalPlaceholders = /* @__PURE__ */ new Map();
1148
+ const originalModelNameAttributes = /* @__PURE__ */ new Map();
1149
+ const originalModelActionMarkup = /* @__PURE__ */ new Map();
1150
+ const originalModelDetailsOpen = /* @__PURE__ */ new Map();
1151
+ const originalPickerCopy = /* @__PURE__ */ new Map();
1152
+ let pickerConfirmWorking = false;
1153
+ let pickerConfirmReplay = false;
1154
+ const restoreModelNameInput = (input, original) => {
1155
+ input.readOnly = original.readOnly;
1156
+ if (original.ariaReadOnly === null) input.removeAttribute("aria-readonly");
1157
+ else input.setAttribute("aria-readonly", original.ariaReadOnly);
1158
+ };
1159
+ const markModelNameInputsReadonly = () => {
1160
+ const current = /* @__PURE__ */ new Set();
1161
+ for (const input of document.querySelectorAll("input")) {
1162
+ if (!startsWithAny(labelOf(input), MODEL_READONLY_LABELS)) continue;
1163
+ current.add(input);
1164
+ if (!originalModelNameAttributes.has(input)) originalModelNameAttributes.set(input, {
1165
+ readOnly: input.readOnly,
1166
+ ariaReadOnly: input.getAttribute("aria-readonly")
1167
+ });
1168
+ input.readOnly = true;
1169
+ input.setAttribute("aria-readonly", "true");
1170
+ }
1171
+ for (const [input, original] of originalModelNameAttributes) {
1172
+ if (current.has(input)) continue;
1173
+ restoreModelNameInput(input, original);
1174
+ originalModelNameAttributes.delete(input);
1175
+ }
1176
+ };
1177
+ const markManualAddButtons = (editor) => {
1178
+ for (const button of editor.querySelectorAll("button")) if (MANUAL_ADD_MODEL_LABELS.has(button.textContent?.trim() ?? "")) button.setAttribute(REMOVE_MANUAL_ADD_ATTRIBUTE, "true");
1179
+ else button.removeAttribute(REMOVE_MANUAL_ADD_ATTRIBUTE);
1180
+ };
1181
+ const restoreModelActionButtons = (editor) => {
1182
+ for (const [button, original] of originalModelActionMarkup) {
1183
+ if (!editor.contains(button)) continue;
1184
+ button.innerHTML = original.html;
1185
+ button.removeAttribute(MODEL_ACTION_ATTRIBUTE);
1186
+ originalModelActionMarkup.delete(button);
1187
+ }
1188
+ };
1189
+ const markModelActionButtons = (editor) => {
1190
+ const active = /* @__PURE__ */ new Set();
1191
+ for (const button of editor.querySelectorAll("button")) {
1192
+ const original = originalModelActionMarkup.get(button);
1193
+ const sourceLabel = original?.label ?? button.textContent?.trim() ?? "";
1194
+ const action = MODEL_ACTION_LABELS.get(sourceLabel);
1195
+ if (action === void 0) continue;
1196
+ if (original === void 0) originalModelActionMarkup.set(button, {
1197
+ label: sourceLabel,
1198
+ html: button.innerHTML
1199
+ });
1200
+ active.add(button);
1201
+ button.setAttribute(MODEL_ACTION_ATTRIBUTE, action.action);
1202
+ if (button.textContent !== action.replacement) button.textContent = action.replacement;
1203
+ }
1204
+ for (const [button, original] of originalModelActionMarkup) {
1205
+ if (active.has(button)) continue;
1206
+ if (editor.contains(button)) {
1207
+ button.innerHTML = original.html;
1208
+ button.removeAttribute(MODEL_ACTION_ATTRIBUTE);
1209
+ }
1210
+ originalModelActionMarkup.delete(button);
1211
+ }
1212
+ };
1213
+ const restoreCapacityPlaceholders = (editor) => {
1214
+ for (const [input, placeholder] of originalPlaceholders) {
1215
+ if (!editor.contains(input)) continue;
1216
+ input.placeholder = placeholder;
1217
+ originalPlaceholders.delete(input);
1218
+ }
1219
+ };
1220
+ const markOfficialCapacityPlaceholders = (editor) => {
1221
+ const idsByRow = /* @__PURE__ */ new Map();
1222
+ for (const input of modelIdInputs(editor)) {
1223
+ const row = rowNumberOf(input);
1224
+ const id = input.value.trim();
1225
+ if (row !== void 0 && id.length > 0) idsByRow.set(row, id);
1226
+ }
1227
+ for (const input of editor.querySelectorAll("input")) {
1228
+ const row = rowNumberOf(input);
1229
+ if (row === void 0) continue;
1230
+ const label = labelOf(input);
1231
+ const field = startsWithAny(label, MODEL_CONTEXT_LABELS) ? "contextWindow" : startsWithAny(label, MODEL_MAX_TOKENS_LABELS) ? "maxTokens" : void 0;
1232
+ if (field === void 0) continue;
1233
+ const defaults = CODEX_MODEL_DEFAULTS[idsByRow.get(row) ?? ""];
1234
+ if (defaults === void 0) {
1235
+ if (originalPlaceholders.has(input)) {
1236
+ input.placeholder = originalPlaceholders.get(input) ?? "";
1237
+ originalPlaceholders.delete(input);
1238
+ }
1239
+ continue;
1240
+ }
1241
+ if (!originalPlaceholders.has(input)) originalPlaceholders.set(input, input.placeholder);
1242
+ input.placeholder = formatCapacity(defaults[field]);
1243
+ }
1244
+ };
1245
+ const restoreModelDetails = (editor) => {
1246
+ for (const [details, originalOpen] of originalModelDetailsOpen) {
1247
+ if (!editor.contains(details)) continue;
1248
+ details.open = originalOpen;
1249
+ details.removeAttribute(MODEL_DETAILS_ATTRIBUTE);
1250
+ originalModelDetailsOpen.delete(details);
1251
+ }
1252
+ };
1253
+ const markModelDetails = (editor) => {
1254
+ const details = editor.querySelector("section[aria-label=\"模型目录\"], section[aria-label=\"Models\"]")?.closest("details") ?? [...editor.querySelectorAll("details")].find((candidate) => modelIdInputs(candidate).length > 0);
1255
+ if (details === void 0) {
1256
+ restoreModelDetails(editor);
1257
+ return;
1258
+ }
1259
+ if (!originalModelDetailsOpen.has(details)) originalModelDetailsOpen.set(details, details.open);
1260
+ details.setAttribute(MODEL_DETAILS_ATTRIBUTE, "true");
1261
+ details.open = true;
1262
+ for (const [other, originalOpen] of originalModelDetailsOpen) {
1263
+ if (other === details || !editor.contains(other)) continue;
1264
+ other.open = originalOpen;
1265
+ other.removeAttribute(MODEL_DETAILS_ATTRIBUTE);
1266
+ originalModelDetailsOpen.delete(other);
1267
+ }
1268
+ };
1269
+ const markEditors = () => {
1270
+ const matched = /* @__PURE__ */ new Set();
1271
+ for (const route of document.querySelectorAll("span")) {
1272
+ if (route.textContent?.trim() !== CODEX_ROUTE) continue;
1273
+ const editor = route.parentElement?.parentElement;
1274
+ if (editor === null || editor === void 0) continue;
1275
+ if (!editor.querySelector("input[aria-label=\"API 密钥\"], input[aria-label=\"API Key\"]")) continue;
1276
+ matched.add(editor);
1277
+ if (editor.getAttribute(CODEX_EDITOR_ATTRIBUTE) !== "true") editor.setAttribute(CODEX_EDITOR_ATTRIBUTE, "true");
1278
+ markManualAddButtons(editor);
1279
+ markModelActionButtons(editor);
1280
+ markOfficialCapacityPlaceholders(editor);
1281
+ markModelDetails(editor);
1282
+ }
1283
+ for (const editor of document.querySelectorAll(`[${CODEX_EDITOR_ATTRIBUTE}]`)) if (!matched.has(editor)) {
1284
+ editor.removeAttribute(CODEX_EDITOR_ATTRIBUTE);
1285
+ markManualAddButtons(editor);
1286
+ restoreModelActionButtons(editor);
1287
+ restoreCapacityPlaceholders(editor);
1288
+ restoreModelDetails(editor);
1289
+ }
1290
+ };
1291
+ const pickerIsChinese = (dialog) => {
1292
+ const original = originalPickerCopy.get(dialog);
1293
+ return original?.ariaLabel === "选择要添加的模型" || original?.title === "选择要添加的模型" || dialog.getAttribute("aria-label") === "模型列表";
1294
+ };
1295
+ const isModelPicker = (dialog) => {
1296
+ if (dialog.getAttribute(CODEX_PICKER_ATTRIBUTE) === "true") return true;
1297
+ const label = dialog.getAttribute("aria-label")?.trim();
1298
+ return label !== void 0 && PICKER_TITLE_LABELS.has(label);
1299
+ };
1300
+ const pickerSelectedModelIds = (dialog) => {
1301
+ const candidateList = dialog.querySelector("ul");
1302
+ if (candidateList === null) return /* @__PURE__ */ new Set();
1303
+ return new Set([...candidateList.querySelectorAll("input[type=\"checkbox\"]")].filter((input) => input.checked).map(candidateId).filter((id) => id !== void 0 && id.length > 0));
1304
+ };
1305
+ const removeButtonForModelInput = (input) => {
1306
+ const row = input.parentElement;
1307
+ if (row === null) return void 0;
1308
+ return [...row.querySelectorAll("button")].find((button) => REMOVE_MODEL_LABELS.some((label) => (button.getAttribute("aria-label") ?? "").trim().startsWith(label)));
1309
+ };
1310
+ const uncheckedModelRows = (dialog) => {
1311
+ const editor = document.querySelector(`[${CODEX_EDITOR_ATTRIBUTE}="true"]`);
1312
+ if (editor === null) return [];
1313
+ const selected = pickerSelectedModelIds(dialog);
1314
+ return modelIdInputs(editor).map((input, index) => ({
1315
+ input,
1316
+ index,
1317
+ id: input.value.trim(),
1318
+ remove: removeButtonForModelInput(input)
1319
+ })).filter((row) => !selected.has(row.id) && row.remove !== void 0).sort((left, right) => right.index - left.index).map(({ input, remove }) => ({
1320
+ input,
1321
+ remove
1322
+ }));
1323
+ };
1324
+ const waitForPickerRender = () => new Promise((resolve) => {
1325
+ window.setTimeout(resolve, 0);
1326
+ });
1327
+ /** Remove unchecked rows one at a time so each React draft update is committed before the next one. */
1328
+ const removeUncheckedModelRows = async (dialog) => {
1329
+ let removed = false;
1330
+ for (let attempt = 0; attempt < 128; attempt += 1) {
1331
+ const next = uncheckedModelRows(dialog)[0];
1332
+ if (next === void 0) return removed;
1333
+ if (next.remove.disabled || !next.remove.isConnected) return removed;
1334
+ next.remove.click();
1335
+ removed = true;
1336
+ await waitForPickerRender();
1337
+ }
1338
+ return removed;
1339
+ };
1340
+ const isPickerConfirmButton = (button) => {
1341
+ const dialog = button.closest("[role=\"dialog\"]");
1342
+ if (dialog === null || dialog.getAttribute(CODEX_PICKER_ATTRIBUTE) !== "true") return false;
1343
+ return button.getAttribute(ADD_BUTTON_ATTRIBUTE) === "true" || ADD_MODEL_LABELS.has(button.textContent?.trim() ?? "");
1344
+ };
1345
+ const isCodexFetchButton = (button) => {
1346
+ if (button.getAttribute(MODEL_ACTION_ATTRIBUTE) === "fetch") return true;
1347
+ if (!FETCH_MODEL_LABELS.has(button.textContent?.trim() ?? "")) return false;
1348
+ return button.closest(`[${CODEX_EDITOR_ATTRIBUTE}="true"]`) !== null;
1349
+ };
1350
+ const syncPickerSelection = (dialog, candidateList) => {
1351
+ if (dialog.getAttribute(PICKER_SELECTION_SYNCED_ATTRIBUTE) === "true") return true;
1352
+ const editor = document.querySelector(`[${CODEX_EDITOR_ATTRIBUTE}="true"]`);
1353
+ if (editor === null) return false;
1354
+ const candidates = [...candidateList.querySelectorAll("input[type=\"checkbox\"]")];
1355
+ if (candidates.length === 0) return false;
1356
+ const configured = modelIds(editor);
1357
+ for (const candidate of candidates) {
1358
+ const id = candidateId(candidate);
1359
+ if (id === void 0) return false;
1360
+ const shouldBeChecked = configured.has(id);
1361
+ if (candidate.checked !== shouldBeChecked) candidate.click();
1362
+ }
1363
+ dialog.setAttribute(PICKER_SELECTION_SYNCED_ATTRIBUTE, "true");
1364
+ return true;
1365
+ };
1366
+ const enhancePicker = (dialog) => {
1367
+ if (!isModelPicker(dialog)) return;
1368
+ dialog.setAttribute(CODEX_PICKER_ATTRIBUTE, "true");
1369
+ const original = originalPickerCopy.get(dialog) ?? {
1370
+ ariaLabel: dialog.getAttribute("aria-label"),
1371
+ title: dialog.querySelector("h2")?.textContent ?? null,
1372
+ description: dialog.querySelector("p")?.textContent ?? null
1373
+ };
1374
+ if (!originalPickerCopy.has(dialog)) originalPickerCopy.set(dialog, original);
1375
+ const chinese = pickerIsChinese(dialog);
1376
+ const title = chinese ? "模型列表" : "Model list";
1377
+ const description = chinese ? "请选择需要保留的模型;未勾选的模型将从模型目录移除。" : "Select the models to keep; unchecked models will be removed from the model catalog.";
1378
+ if (dialog.getAttribute("aria-label") !== title) dialog.setAttribute("aria-label", title);
1379
+ const heading = dialog.querySelector("h2");
1380
+ if (heading !== null && heading.textContent !== title) heading.textContent = title;
1381
+ const descriptionNode = dialog.querySelector("p");
1382
+ if (descriptionNode !== null && descriptionNode.textContent !== description) descriptionNode.textContent = description;
1383
+ const candidateList = dialog.querySelector("ul");
1384
+ if (candidateList === null) return;
1385
+ syncPickerSelection(dialog, candidateList);
1386
+ let selectAll = dialog.querySelector(`[${SELECT_ALL_ATTRIBUTE}="true"]`);
1387
+ if (selectAll === null) {
1388
+ const wrapper = document.createElement("label");
1389
+ wrapper.setAttribute(SELECT_ALL_ATTRIBUTE, "true");
1390
+ selectAll = document.createElement("input");
1391
+ selectAll.type = "checkbox";
1392
+ selectAll.setAttribute("aria-label", chinese ? "全选" : "Select all");
1393
+ const label = document.createElement("span");
1394
+ label.textContent = chinese ? "全选" : "Select all";
1395
+ wrapper.append(selectAll, label);
1396
+ candidateList.before(wrapper);
1397
+ selectAll.addEventListener("change", () => {
1398
+ const checked = selectAll?.checked ?? false;
1399
+ for (const candidate of candidateList.querySelectorAll("input[type=\"checkbox\"]")) if (candidate.checked !== checked) candidate.click();
1400
+ window.setTimeout(syncSelectAll, 0);
1401
+ });
1402
+ }
1403
+ const syncSelectAll = () => {
1404
+ if (selectAll === null) return;
1405
+ const candidates = [...candidateList.querySelectorAll("input[type=\"checkbox\"]")];
1406
+ const selected = candidates.filter((candidate) => candidate.checked).length;
1407
+ selectAll.disabled = candidates.length === 0;
1408
+ selectAll.checked = candidates.length > 0 && selected === candidates.length;
1409
+ selectAll.indeterminate = selected > 0 && selected < candidates.length;
1410
+ };
1411
+ if (candidateList.dataset.dshCodexSelectAllBound !== "true") {
1412
+ candidateList.dataset.dshCodexSelectAllBound = "true";
1413
+ candidateList.addEventListener("change", syncSelectAll);
1414
+ }
1415
+ syncSelectAll();
1416
+ const addButton = dialog.querySelector(`[${ADD_BUTTON_ATTRIBUTE}="true"]`) ?? [...dialog.querySelectorAll("button")].find((button) => ADD_MODEL_LABELS.has(button.textContent?.trim() ?? ""));
1417
+ if (addButton !== void 0) {
1418
+ addButton.setAttribute(ADD_BUTTON_ATTRIBUTE, "true");
1419
+ const replacement = chinese ? "确定" : "Confirm";
1420
+ if (addButton.textContent !== replacement) addButton.textContent = replacement;
1421
+ }
1422
+ };
1423
+ const updatePickers = () => {
1424
+ markModelNameInputsReadonly();
1425
+ markEditors();
1426
+ for (const [dialog] of originalPickerCopy) if (!document.contains(dialog)) originalPickerCopy.delete(dialog);
1427
+ const dialogs = [...document.querySelectorAll("[role=\"dialog\"]")];
1428
+ const activePicker = dialogs.find(isModelPicker);
1429
+ if (activePicker !== void 0 && codexPickerPending) {
1430
+ enhancePicker(activePicker);
1431
+ codexPickerPending = false;
1432
+ }
1433
+ for (const dialog of dialogs) if (dialog.getAttribute(CODEX_PICKER_ATTRIBUTE) === "true") enhancePicker(dialog);
1434
+ };
1435
+ const onClick = (event) => {
1436
+ const target = event.target;
1437
+ if (!(target instanceof Element)) return;
1438
+ const button = target.closest("button");
1439
+ if (button !== null && isPickerConfirmButton(button)) {
1440
+ if (pickerConfirmReplay) {
1441
+ pickerConfirmReplay = false;
1442
+ return;
1443
+ }
1444
+ if (pickerConfirmWorking) {
1445
+ event.preventDefault();
1446
+ event.stopPropagation();
1447
+ return;
1448
+ }
1449
+ const dialog = button.closest("[role=\"dialog\"]");
1450
+ if (dialog !== null && uncheckedModelRows(dialog).length > 0) {
1451
+ event.preventDefault();
1452
+ event.stopPropagation();
1453
+ pickerConfirmWorking = true;
1454
+ removeUncheckedModelRows(dialog).then((removed) => {
1455
+ pickerConfirmWorking = false;
1456
+ if (!removed) return;
1457
+ window.setTimeout(() => {
1458
+ const current = dialog.querySelector(`[${ADD_BUTTON_ATTRIBUTE}="true"]`) ?? [...dialog.querySelectorAll("button")].find((candidate) => ADD_MODEL_LABELS.has(candidate.textContent?.trim() ?? ""));
1459
+ if (current === null || current === void 0 || current.disabled) return;
1460
+ current.setAttribute(ADD_BUTTON_ATTRIBUTE, "true");
1461
+ pickerConfirmReplay = true;
1462
+ current.click();
1463
+ }, 0);
1464
+ }, () => {
1465
+ pickerConfirmWorking = false;
1466
+ });
1467
+ return;
1468
+ }
1469
+ }
1470
+ if (button !== null && isCodexFetchButton(button)) {
1471
+ codexPickerPending = true;
1472
+ window.setTimeout(updatePickers, 0);
1473
+ }
1474
+ };
1475
+ markEditors();
1476
+ updatePickers();
1477
+ document.addEventListener("click", onClick, true);
1478
+ const observer = new MutationObserver(updatePickers);
1479
+ observer.observe(document.body, {
1480
+ childList: true,
1481
+ subtree: true
1482
+ });
1483
+ return () => {
1484
+ observer.disconnect();
1485
+ document.removeEventListener("click", onClick, true);
1486
+ document.getElementById(STYLE_ID)?.remove();
1487
+ for (const editor of document.querySelectorAll(`[${CODEX_EDITOR_ATTRIBUTE}]`)) {
1488
+ editor.removeAttribute(CODEX_EDITOR_ATTRIBUTE);
1489
+ restoreModelDetails(editor);
1490
+ }
1491
+ originalModelDetailsOpen.clear();
1492
+ for (const dialog of document.querySelectorAll(`[${CODEX_PICKER_ATTRIBUTE}]`)) {
1493
+ dialog.removeAttribute(CODEX_PICKER_ATTRIBUTE);
1494
+ dialog.removeAttribute(PICKER_SELECTION_SYNCED_ATTRIBUTE);
1495
+ }
1496
+ for (const [dialog, original] of originalPickerCopy) {
1497
+ if (original.ariaLabel === null) dialog.removeAttribute("aria-label");
1498
+ else dialog.setAttribute("aria-label", original.ariaLabel);
1499
+ const heading = dialog.querySelector("h2");
1500
+ if (heading !== null && original.title !== null) heading.textContent = original.title;
1501
+ const description = dialog.querySelector("p");
1502
+ if (description !== null && original.description !== null) description.textContent = original.description;
1503
+ }
1504
+ originalPickerCopy.clear();
1505
+ for (const button of document.querySelectorAll(`[${REMOVE_MANUAL_ADD_ATTRIBUTE}]`)) button.removeAttribute(REMOVE_MANUAL_ADD_ATTRIBUTE);
1506
+ for (const [button, original] of originalModelActionMarkup) {
1507
+ button.innerHTML = original.html;
1508
+ button.removeAttribute(MODEL_ACTION_ATTRIBUTE);
1509
+ }
1510
+ originalModelActionMarkup.clear();
1511
+ for (const [input, placeholder] of originalPlaceholders) input.placeholder = placeholder;
1512
+ originalPlaceholders.clear();
1513
+ for (const [input, original] of originalModelNameAttributes) restoreModelNameInput(input, original);
1514
+ originalModelNameAttributes.clear();
1515
+ };
1516
+ }
1517
+ //#endregion
1518
+ //#region src/client/locales.ts
1519
+ /** Browser copy for the standalone Codex authentication card. */
1520
+ const en = {
1521
+ title: "Codex Auth",
1522
+ intro: "Sign in with your ChatGPT account for Codex-compatible plugins.",
1523
+ expand: "Expand settings",
1524
+ collapse: "Collapse settings",
1525
+ loading: "Loading account…",
1526
+ signedOut: "Not signed in",
1527
+ signingIn: "Waiting for Codex authorization…",
1528
+ signedIn: "Signed in",
1529
+ signIn: "Sign in with ChatGPT",
1530
+ signOut: "Sign out",
1531
+ working: "Working…",
1532
+ popupBlocked: "The browser blocked the sign-in window. Allow pop-ups for this dsh page and retry.",
1533
+ requestFailed: "The Codex account request failed.",
1534
+ remoteOrigin: "This browser origin is not trusted by the DSH Web server.",
1535
+ authorizationCodeHelp: "The plugin generated a one-time authorization code. Enter it on the opened Codex authorization page; no workspace selection is required.",
1536
+ authorizationCodeLabel: "Codex authorization code",
1537
+ openAuthorization: "Open authorization page",
1538
+ usageTitle: "General usage limits",
1539
+ refreshUsage: "Refresh",
1540
+ usageLoading: "Loading usage…",
1541
+ usageUnavailable: "Usage information is temporarily unavailable.",
1542
+ usagePlan: "Plan",
1543
+ usageAvailable: "Available",
1544
+ usageLimitReached: "Limit reached",
1545
+ usageUnlimited: "Unlimited credits",
1546
+ usageBalance: "Credit balance",
1547
+ usagePrimary: "Primary window",
1548
+ usageSecondary: "Usage window",
1549
+ usageWeekly: "Weekly usage limit",
1550
+ usageRemaining: "Remaining",
1551
+ usageResetAt: "Reset time:",
1552
+ usageResetAfter: "Resets in",
1553
+ usageMinutes: " min",
1554
+ usageNoWindow: "No weekly usage information is available.",
1555
+ capabilitiesTitle: "Image capabilities",
1556
+ capabilitiesIntro: "Optional image features for the Codex model route.",
1557
+ enableImageRecognition: "Enable image recognition",
1558
+ enableImageRecognitionHelp: "Adds view_image so image-capable Codex models can inspect approved local images.",
1559
+ enableImageUpload: "Enable image upload",
1560
+ enableImageUploadHelp: "Allow image-capable Codex models to receive images pasted or dropped into the conversation.",
1561
+ enableImageGeneration: "Enable image generation",
1562
+ imageGenerationUnavailableHelp: "Not supported by the Codex provider and DSH rc.7 model adapter.",
1563
+ settingsLoading: "Loading plugin settings…",
1564
+ settingsUnavailable: "Plugin settings are unavailable in this dsh profile.",
1565
+ settingsReadOnly: "This profile exposes plugin settings as read-only.",
1566
+ settingsSaved: "Saved",
1567
+ settingsSaveFailed: "Unable to save settings.",
1568
+ discard: "Discard",
1569
+ save: "Save",
1570
+ saving: "Saving…"
1571
+ };
1572
+ const zh = {
1573
+ title: "Codex Auth",
1574
+ intro: "使用 ChatGPT 账户登录,为 Codex 兼容插件提供认证状态。",
1575
+ expand: "展开设置",
1576
+ collapse: "折叠设置",
1577
+ loading: "正在加载账户信息…",
1578
+ signedOut: "尚未登录",
1579
+ signingIn: "正在等待 Codex 授权…",
1580
+ signedIn: "已登录",
1581
+ signIn: "去登录",
1582
+ signOut: "退出登录",
1583
+ working: "处理中…",
1584
+ popupBlocked: "浏览器阻止了登录窗口。请允许此 dsh 页面弹出窗口后重试。",
1585
+ requestFailed: "Codex 账户请求失败。",
1586
+ remoteOrigin: "当前浏览器来源未被 DSH Web 服务信任。",
1587
+ authorizationCodeHelp: "插件已生成一次性授权码。请在打开的 Codex 授权页面中输入此代码,无需选择工作空间。",
1588
+ authorizationCodeLabel: "Codex 授权码",
1589
+ openAuthorization: "打开授权页面",
1590
+ usageTitle: "通用使用限额",
1591
+ refreshUsage: "刷新",
1592
+ usageLoading: "正在加载用量…",
1593
+ usageUnavailable: "暂时无法获取 Codex 用量信息。",
1594
+ usagePlan: "套餐",
1595
+ usageAvailable: "可用",
1596
+ usageLimitReached: "已达到限制",
1597
+ usageUnlimited: "额度不限量",
1598
+ usageBalance: "额度余额",
1599
+ usagePrimary: "主要窗口",
1600
+ usageSecondary: "用量窗口",
1601
+ usageWeekly: "每周使用限额",
1602
+ usageRemaining: "剩余",
1603
+ usageResetAt: "重置时间:",
1604
+ usageResetAfter: "还剩",
1605
+ usageMinutes: " 分钟",
1606
+ usageNoWindow: "暂无每周用量信息。",
1607
+ capabilitiesTitle: "图片能力",
1608
+ capabilitiesIntro: "配置 Codex 模型路由的可选图片能力。",
1609
+ enableImageRecognition: "启用图片识别",
1610
+ enableImageRecognitionHelp: "增加 view_image 工具,让具备图片输入能力的 Codex 模型读取经过授权的本地图片。",
1611
+ enableImageUpload: "启用图片上传",
1612
+ enableImageUploadHelp: "允许具备图片输入能力的 Codex 模型接收粘贴或拖入对话的图片。",
1613
+ enableImageGeneration: "启用图像生成",
1614
+ imageGenerationUnavailableHelp: "当前 Codex 提供方和 DSH rc.7 模型适配器暂不支持图像输出。",
1615
+ settingsLoading: "正在加载插件设置…",
1616
+ settingsUnavailable: "此 dsh profile 无法使用插件设置。",
1617
+ settingsReadOnly: "此 profile 的插件设置为只读。",
1618
+ settingsSaved: "已保存",
1619
+ settingsSaveFailed: "设置保存失败。",
1620
+ discard: "放弃",
1621
+ save: "保存",
1622
+ saving: "保存中…"
1623
+ };
1624
+ //#endregion
1625
+ //#region src/client/index.tsx
1626
+ const name = "dsh-codex-auth-plugin-client";
1627
+ const inject = [
1628
+ "slots",
1629
+ "locale",
1630
+ "connection",
1631
+ "remote",
1632
+ "settingsScope"
1633
+ ];
1634
+ function apply(ctx) {
1635
+ ctx.effect(() => installCodexModelEditorPresentation(), "dsh-codex-auth-plugin: Codex model editor presentation");
1636
+ const namespace = "settings.dsh-codex-auth";
1637
+ ctx.effect(() => ctx.locale.register(namespace, {
1638
+ zh,
1639
+ en
1640
+ }), "dsh-codex-auth-plugin: locale");
1641
+ const t = ctx.locale.bind(namespace);
1642
+ const configScope = ctx.settingsScope.bind({
1643
+ namespace: CODEX_AUTH_SETTINGS_NAMESPACE,
1644
+ decode: decodeCodexAuthSettings
1645
+ });
1646
+ ctx.slots.inject("settings.plugin.item", () => ctx.slots.register({
1647
+ name: "settings.plugin.item",
1648
+ key: CODEX_AUTH_SETTINGS_NAMESPACE,
1649
+ inject: () => ({
1650
+ t,
1651
+ configScope
1652
+ })
1653
+ }, CodexAuthCard));
1654
+ }
1655
+ //#endregion
1656
+ exports.apply = apply;
1657
+ exports.inject = inject;
1658
+ exports.name = name;
1659
+ return module.exports;
1660
+ }
1661
+ });