dsh-codex-connect 0.1.0-alpha.4.21 → 0.1.0-alpha.4.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -36,10 +36,35 @@ window.__ModuleLoader__.load({
36
36
  function isValidOpenAICodexProxyUrl(value) {
37
37
  return normalizeOpenAICodexProxyUrl(value) !== void 0;
38
38
  }
39
+ /**
40
+ * Whether a value is a bounded per-model context-window override map. Keys
41
+ * are nonempty, unpadded model ids; values are positive safe integers or null
42
+ * to restore that model's catalog default. The Host also checks catalog
43
+ * membership and the model-specific configuration ceiling.
44
+ */
45
+ function isValidOpenAICodexContextWindowOverrides(value) {
46
+ if (!isRecord$2(value)) return false;
47
+ const entries = Object.entries(value);
48
+ if (entries.length > 256) return false;
49
+ return entries.every(([modelId, window]) => modelId.length > 0 && modelId.trim() === modelId && (window === null || typeof window === "number" && Number.isSafeInteger(window) && window > 0));
50
+ }
51
+ /** Preserve per-model null masks until the Host has merged its settings layers. */
52
+ function parseOpenAICodexContextWindowOverrides(value) {
53
+ if (value === void 0 || value === null) return void 0;
54
+ if (!isValidOpenAICodexContextWindowOverrides(value)) throw new TypeError("OpenAI Codex contextWindowOverrides must contain at most 256 nonempty model ids with positive safe-integer token budgets or null resets");
55
+ return { ...value };
56
+ }
57
+ /** Resolve merged settings; whole-map or per-model null masks use catalog defaults. */
58
+ function resolveOpenAICodexContextWindowOverrides(value) {
59
+ const overrides = parseOpenAICodexContextWindowOverrides(value);
60
+ if (overrides === void 0) return void 0;
61
+ return Object.fromEntries(Object.entries(overrides).filter((entry) => entry[1] !== null));
62
+ }
39
63
  Object.freeze({
40
64
  models: void 0,
41
65
  enableProxy: false,
42
66
  proxyUrl: DEFAULT_OPENAI_CODEX_PROXY_URL,
67
+ contextWindowOverrides: void 0,
43
68
  enableSearch: false,
44
69
  enableImageTool: false,
45
70
  enableImageGeneration: false,
@@ -57,6 +82,7 @@ window.__ModuleLoader__.load({
57
82
  const models = value["models"];
58
83
  const enableProxy = value["enableProxy"];
59
84
  const proxyUrl = value["proxyUrl"];
85
+ const contextWindowOverrides = value["contextWindowOverrides"];
60
86
  const enableSearch = value["enableSearch"];
61
87
  const enableImageTool = value["enableImageTool"];
62
88
  const enableImageGeneration = value["enableImageGeneration"];
@@ -67,16 +93,19 @@ window.__ModuleLoader__.load({
67
93
  if (models !== void 0 && (!Array.isArray(models) || models.some((model) => typeof model !== "string"))) return void 0;
68
94
  if (enableProxy !== void 0 && typeof enableProxy !== "boolean") return void 0;
69
95
  if (proxyUrl !== void 0 && (typeof proxyUrl !== "string" || !isValidOpenAICodexProxyUrl(proxyUrl))) return void 0;
96
+ if (contextWindowOverrides !== void 0 && contextWindowOverrides !== null && !isValidOpenAICodexContextWindowOverrides(contextWindowOverrides)) return void 0;
70
97
  if (typeof enableSearch !== "boolean" || typeof enableImageTool !== "boolean") return void 0;
71
98
  if (enableImageGeneration !== void 0 && typeof enableImageGeneration !== "boolean") return void 0;
72
99
  if (typeof searchModel !== "string" || searchModel.trim().length === 0) return void 0;
73
100
  if (searchMode !== "cached" && searchMode !== "indexed" && searchMode !== "live") return void 0;
74
101
  if (searchContextSize !== "low" && searchContextSize !== "medium" && searchContextSize !== "high") return void 0;
75
102
  if (typeof searchMaxOutputTokens !== "number" || !Number.isInteger(searchMaxOutputTokens) || searchMaxOutputTokens < 1) return void 0;
103
+ const overrides = resolveOpenAICodexContextWindowOverrides(contextWindowOverrides);
76
104
  return {
77
105
  models: models === void 0 ? void 0 : [...new Set(models)],
78
106
  enableProxy: enableProxy ?? false,
79
107
  proxyUrl: proxyUrl === void 0 ? DEFAULT_OPENAI_CODEX_PROXY_URL : normalizeOpenAICodexProxyUrl(proxyUrl),
108
+ contextWindowOverrides: overrides === void 0 ? void 0 : Object.freeze(overrides),
80
109
  enableSearch,
81
110
  enableImageTool,
82
111
  enableImageGeneration: enableImageGeneration ?? false,
@@ -95,11 +124,202 @@ window.__ModuleLoader__.load({
95
124
  const OPENAI_CODEX_AUTH_LOGIN_PATH = "/plugins/dsh-openai-codex/auth/login";
96
125
  /** Plugin-owned logout endpoint consumed by its browser half. */
97
126
  const OPENAI_CODEX_AUTH_LOGOUT_PATH = "/plugins/dsh-openai-codex/auth/logout";
127
+ /** Cancel only the pending authorization; never delete a stored credential. */
128
+ const OPENAI_CODEX_AUTH_CANCEL_PATH = "/plugins/dsh-openai-codex/auth/cancel";
129
+ //#endregion
130
+ //#region src/client/account-store.ts
131
+ var AccountRequestError = class extends Error {};
132
+ async function request(path, method = "GET", signal) {
133
+ const response = await fetch(path, {
134
+ method,
135
+ headers: { accept: "application/json" },
136
+ credentials: "same-origin",
137
+ ...signal === void 0 ? {} : { signal }
138
+ });
139
+ const value = await response.json().catch(() => void 0);
140
+ if (!response.ok) throw new AccountRequestError(typeof value === "object" && value !== null && "error" in value && typeof value.error === "string" ? value.error : `HTTP ${response.status}`);
141
+ return value;
142
+ }
143
+ /** One account state per browser-plugin instance; subscribers share requests and timers. */
144
+ var OpenAICodexAccountStore = class {
145
+ snapshot = {
146
+ status: { status: "loading" },
147
+ busy: false
148
+ };
149
+ listeners = /* @__PURE__ */ new Set();
150
+ controller;
151
+ timer;
152
+ disposed = false;
153
+ popup = null;
154
+ getSnapshot = () => this.snapshot;
155
+ subscribe = (listener) => {
156
+ if (this.disposed) return () => {};
157
+ this.listeners.add(listener);
158
+ if (this.listeners.size === 1) this.refresh();
159
+ return () => {
160
+ this.listeners.delete(listener);
161
+ if (this.listeners.size === 0) this.stopPolling();
162
+ };
163
+ };
164
+ publish(snapshot) {
165
+ if (this.disposed) return;
166
+ this.snapshot = snapshot;
167
+ for (const listener of this.listeners) listener();
168
+ }
169
+ failure(error) {
170
+ return error instanceof AccountRequestError && error.message === "remote-web-origin-not-trusted" ? { status: "remote-web-origin-not-trusted" } : {
171
+ status: "error",
172
+ message: error instanceof Error ? error.message : "Account request failed"
173
+ };
174
+ }
175
+ stopPolling() {
176
+ clearTimeout(this.timer);
177
+ this.timer = void 0;
178
+ this.controller?.abort();
179
+ this.controller = void 0;
180
+ }
181
+ schedule() {
182
+ clearTimeout(this.timer);
183
+ const interval = this.snapshot.status.status === "signing-in" ? 1e3 : this.snapshot.status.status === "signed-in" ? 6e4 : void 0;
184
+ if (!this.disposed && this.listeners.size > 0 && interval !== void 0) this.timer = setTimeout(() => {
185
+ this.refresh();
186
+ }, interval);
187
+ }
188
+ /** Refresh only while observed, without overlapping status reads or OAuth mutations. */
189
+ async refresh() {
190
+ if (this.disposed || this.snapshot.busy || this.controller !== void 0 || this.listeners.size === 0) return;
191
+ const controller = new AbortController();
192
+ this.controller = controller;
193
+ try {
194
+ const status = await request(OPENAI_CODEX_AUTH_STATUS_PATH, "GET", controller.signal);
195
+ if (!controller.signal.aborted) this.publish({
196
+ status,
197
+ busy: false,
198
+ ...status.status === "signing-in" && this.snapshot.loginUrl !== void 0 ? { loginUrl: this.snapshot.loginUrl } : {}
199
+ });
200
+ } catch (error) {
201
+ if (!controller.signal.aborted) this.publish({
202
+ status: this.failure(error),
203
+ busy: false
204
+ });
205
+ } finally {
206
+ if (this.controller === controller) {
207
+ this.controller = void 0;
208
+ this.schedule();
209
+ }
210
+ }
211
+ }
212
+ /** Start or reopen the server-owned authorization from a user click, retaining popup permission. */
213
+ async signIn() {
214
+ if (this.disposed || this.snapshot.busy) return;
215
+ this.stopPolling();
216
+ const popup = window.open("about:blank", "_blank");
217
+ this.popup = popup;
218
+ if (popup !== null) popup.opener = null;
219
+ this.publish({
220
+ status: { status: "signing-in" },
221
+ busy: true
222
+ });
223
+ try {
224
+ const challenge = await request(OPENAI_CODEX_AUTH_LOGIN_PATH, "POST");
225
+ if (this.disposed) {
226
+ popup?.close();
227
+ return;
228
+ }
229
+ if (popup !== null) popup.location.replace(challenge.url);
230
+ this.publish({
231
+ status: { status: "signing-in" },
232
+ busy: false,
233
+ loginUrl: challenge.url
234
+ });
235
+ } catch (error) {
236
+ popup?.close();
237
+ this.publish({
238
+ status: this.failure(error),
239
+ busy: false
240
+ });
241
+ if (error instanceof AccountRequestError && error.message === "OpenAI Codex sign-in cancelled") await this.refresh();
242
+ } finally {
243
+ if (this.popup === popup) this.popup = null;
244
+ this.schedule();
245
+ }
246
+ }
247
+ /** Cancel only the pending authorization, preserving an already signed-in account. */
248
+ async cancel() {
249
+ if (this.disposed || this.snapshot.busy) return;
250
+ this.stopPolling();
251
+ this.publish({
252
+ status: this.snapshot.status,
253
+ busy: true
254
+ });
255
+ try {
256
+ const status = await request(OPENAI_CODEX_AUTH_CANCEL_PATH, "POST");
257
+ this.publish({
258
+ status,
259
+ busy: false
260
+ });
261
+ } catch (error) {
262
+ this.publish({
263
+ status: this.failure(error),
264
+ busy: false
265
+ });
266
+ } finally {
267
+ this.schedule();
268
+ }
269
+ }
270
+ /** Sign out once for all mounted account views and invalidate older status reads. */
271
+ async signOut() {
272
+ if (this.disposed || this.snapshot.busy) return;
273
+ this.stopPolling();
274
+ this.publish({
275
+ status: this.snapshot.status,
276
+ busy: true
277
+ });
278
+ try {
279
+ await request(OPENAI_CODEX_AUTH_LOGOUT_PATH, "POST");
280
+ this.publish({
281
+ status: { status: "signed-out" },
282
+ busy: false
283
+ });
284
+ } catch (error) {
285
+ this.publish({
286
+ status: this.failure(error),
287
+ busy: false
288
+ });
289
+ }
290
+ }
291
+ /** Stop local observation on plugin unload; do not log out the server account. */
292
+ dispose() {
293
+ this.disposed = true;
294
+ this.stopPolling();
295
+ this.popup?.close();
296
+ this.popup = null;
297
+ this.listeners.clear();
298
+ this.snapshot = {
299
+ status: { status: "loading" },
300
+ busy: false
301
+ };
302
+ }
303
+ };
98
304
  //#endregion
99
305
  //#region src/model-contract.ts
100
306
  /** Node-free model catalog contract shared by the Host route and browser card. */
101
307
  /** Same-origin endpoint exposing the complete Codex model catalog. */
102
308
  const OPENAI_CODEX_MODEL_CATALOG_PATH = "/plugins/dsh-codex-connect/models";
309
+ /** Versioned official-client override policy, not a measured endpoint capacity. */
310
+ const OPENAI_CODEX_CONTEXT_LIMIT_SOURCE = "https://github.com/openai/codex/blob/7625343977154efed8c0dadba956374992a1580b/codex-rs/models-manager/models.json";
311
+ Object.freeze({
312
+ "gpt-5.6-sol": 872e3,
313
+ "gpt-5.6-terra": 872e3,
314
+ "gpt-5.6-luna": 872e3,
315
+ "gpt-5.4": 1e6,
316
+ "gpt-5.5": 272e3,
317
+ "gpt-5.4-mini": 272e3
318
+ });
319
+ /** Whether a proposed local token budget fits the model's configuration range. */
320
+ function isValidOpenAICodexContextBudget(value, maximum) {
321
+ return Number.isSafeInteger(value) && value > 0 && value <= maximum;
322
+ }
103
323
  /** Validate the model catalog before it enters React state. */
104
324
  function decodeOpenAICodexModelCatalog(value) {
105
325
  if (!Array.isArray(value)) return void 0;
@@ -110,11 +330,18 @@ window.__ModuleLoader__.load({
110
330
  const record = entry;
111
331
  const id = record["id"];
112
332
  const name = record["name"];
333
+ const contextWindow = record["contextWindow"];
334
+ const maxContextWindow = record["maxContextWindow"];
335
+ const contextLimitSource = record["contextLimitSource"];
113
336
  if (typeof id !== "string" || id.length === 0 || typeof name !== "string" || name.length === 0 || ids.has(id)) return void 0;
337
+ if (typeof contextWindow !== "number" || typeof maxContextWindow !== "number" || !isValidOpenAICodexContextBudget(maxContextWindow, Number.MAX_SAFE_INTEGER) || !isValidOpenAICodexContextBudget(contextWindow, maxContextWindow) || contextLimitSource !== "codex-catalog" && contextLimitSource !== "catalog-default") return void 0;
114
338
  ids.add(id);
115
339
  catalog.push({
116
340
  id,
117
- name
341
+ name,
342
+ contextWindow,
343
+ maxContextWindow,
344
+ contextLimitSource
118
345
  });
119
346
  }
120
347
  return catalog;
@@ -158,8 +385,8 @@ window.__ModuleLoader__.load({
158
385
  border: 0
159
386
  };
160
387
  const modelListStyle = {
161
- display: "grid",
162
- gridTemplateColumns: "repeat(auto-fit, minmax(210px, 1fr))",
388
+ display: "flex",
389
+ flexDirection: "column",
163
390
  gap: 10
164
391
  };
165
392
  const modelRowStyle = {
@@ -226,7 +453,7 @@ window.__ModuleLoader__.load({
226
453
  display: "flex",
227
454
  gap: 8
228
455
  };
229
- const buttonStyle$2 = {
456
+ const buttonStyle$3 = {
230
457
  boxSizing: "border-box",
231
458
  minHeight: 34,
232
459
  padding: "6px 14px",
@@ -239,10 +466,10 @@ window.__ModuleLoader__.load({
239
466
  cursor: "pointer"
240
467
  };
241
468
  const primaryButtonStyle$2 = {
242
- ...buttonStyle$2,
243
- borderColor: "var(--dsw-alias-brand-primary)",
244
- background: "var(--dsw-alias-brand-primary)",
245
- color: "white"
469
+ ...buttonStyle$3,
470
+ borderColor: "var(--dsw-alias-button-primary-fill)",
471
+ background: "var(--dsw-alias-button-primary-fill)",
472
+ color: "var(--dsw-alias-label-primary-foreground)"
246
473
  };
247
474
  const errorStyle$2 = {
248
475
  ...bodyStyle$2,
@@ -263,6 +490,7 @@ window.__ModuleLoader__.load({
263
490
  };
264
491
  const CONFIG_FIELDS = [
265
492
  "models",
493
+ "contextWindowOverrides",
266
494
  "enableProxy",
267
495
  "proxyUrl",
268
496
  "enableSearch",
@@ -274,6 +502,11 @@ window.__ModuleLoader__.load({
274
502
  "searchMaxOutputTokens"
275
503
  ];
276
504
  function sameField(field, left, right) {
505
+ if (field === "contextWindowOverrides") {
506
+ const leftMap = left;
507
+ const rightMap = right;
508
+ return Object.keys(leftMap ?? {}).length === Object.keys(rightMap ?? {}).length && Object.entries(leftMap ?? {}).every(([id, value]) => rightMap?.[id] === value);
509
+ }
277
510
  if (field !== "models") return left === right;
278
511
  if (left === void 0 || right === void 0) return left === right;
279
512
  return Array.isArray(left) && Array.isArray(right) && left.length === right.length && left.every((model, index) => model === right[index]);
@@ -292,6 +525,7 @@ window.__ModuleLoader__.load({
292
525
  const [feedback, setFeedback] = (0, react.useState)("idle");
293
526
  const [modelCatalog, setModelCatalog] = (0, react.useState)();
294
527
  const [modelCatalogError, setModelCatalogError] = (0, react.useState)(false);
528
+ const [expandedModels, setExpandedModels] = (0, react.useState)({});
295
529
  const [proxyDetection, setProxyDetection] = (0, react.useState)({ status: "idle" });
296
530
  const [manualProxy, setManualProxy] = (0, react.useState)(false);
297
531
  const [manualProbe, setManualProbe] = (0, react.useState)();
@@ -403,7 +637,11 @@ window.__ModuleLoader__.load({
403
637
  const validModel = draft !== void 0 && draft.searchModel.trim().length > 0;
404
638
  const validTokens = draft !== void 0 && Number.isInteger(draft.searchMaxOutputTokens) && draft.searchMaxOutputTokens > 0;
405
639
  const validProxy = draft !== void 0 && isValidOpenAICodexProxyUrl(draft.proxyUrl);
406
- const valid = validModel && validTokens && validProxy;
640
+ const validContexts = draft !== void 0 && isValidOpenAICodexContextWindowOverrides(draft.contextWindowOverrides ?? {}) && Object.entries(draft.contextWindowOverrides ?? {}).every(([id, budget]) => {
641
+ const model = modelCatalog?.find((entry) => entry.id === id);
642
+ return model !== void 0 && isValidOpenAICodexContextBudget(budget, model.maxContextWindow);
643
+ });
644
+ const valid = validModel && validTokens && validProxy && validContexts;
407
645
  const save = async () => {
408
646
  if (scope === void 0 || draft === void 0 || !snapshot.writable || !valid) return;
409
647
  const desired = {
@@ -416,7 +654,11 @@ window.__ModuleLoader__.load({
416
654
  for (const field of CONFIG_FIELDS) {
417
655
  const accepted = scope.getSnapshot().value;
418
656
  if (accepted !== void 0 && sameField(field, accepted[field], desired[field])) continue;
419
- await scope.set(field, desired[field]);
657
+ const value = field === "contextWindowOverrides" ? {
658
+ ...Object.fromEntries((modelCatalog ?? []).map((model) => [model.id, null])),
659
+ ...desired.contextWindowOverrides
660
+ } : desired[field];
661
+ await scope.set(field, value);
420
662
  const committed = scope.getSnapshot().value;
421
663
  if (committed === void 0 || !sameField(field, committed[field], desired[field])) throw new Error(`Host refused ${field}`);
422
664
  }
@@ -485,28 +727,230 @@ window.__ModuleLoader__.load({
485
727
  "aria-label": t("modelCatalog"),
486
728
  children: modelCatalog.map((model) => {
487
729
  const selected = draft.models === void 0 || draft.models.includes(model.id);
488
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
489
- style: modelRowStyle,
490
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
491
- type: "checkbox",
492
- checked: selected,
493
- onChange: (event) => {
494
- const visible = new Set(draft.models ?? modelCatalog.map((entry) => entry.id));
495
- if (event.currentTarget.checked) visible.add(model.id);
496
- else visible.delete(model.id);
497
- update("models", modelCatalog.filter((entry) => visible.has(entry.id)).map((entry) => entry.id));
498
- }
499
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: model.name }), model.name === model.id ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
500
- style: modelIdStyle,
730
+ const budget = draft.contextWindowOverrides?.[model.id];
731
+ const invalidBudget = budget !== void 0 && !isValidOpenAICodexContextBudget(budget, model.maxContextWindow);
732
+ const effectiveBudget = budget ?? model.contextWindow;
733
+ const changeBudget = (value) => {
734
+ update("contextWindowOverrides", {
735
+ ...draft.contextWindowOverrides,
736
+ [model.id]: value
737
+ });
738
+ };
739
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
740
+ role: "group",
741
+ "aria-label": model.name,
742
+ style: {
743
+ minWidth: 0,
744
+ padding: "10px 0",
745
+ borderBottom: "1px solid var(--dsw-alias-border-l2)"
746
+ },
747
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
748
+ style: {
749
+ display: "flex",
750
+ alignItems: "center",
751
+ justifyContent: "space-between",
752
+ flexWrap: "wrap",
753
+ gap: 8
754
+ },
755
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
756
+ style: {
757
+ ...modelRowStyle,
758
+ minWidth: 0,
759
+ overflowWrap: "anywhere"
760
+ },
761
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
762
+ type: "checkbox",
763
+ checked: selected,
764
+ onChange: (event) => {
765
+ const visible = new Set(draft.models ?? modelCatalog.map((entry) => entry.id));
766
+ if (event.currentTarget.checked) visible.add(model.id);
767
+ else visible.delete(model.id);
768
+ update("models", modelCatalog.filter((entry) => visible.has(entry.id)).map((entry) => entry.id));
769
+ }
770
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: model.name }), model.name === model.id ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
771
+ style: modelIdStyle,
772
+ children: [
773
+ " (",
774
+ model.id,
775
+ ")"
776
+ ]
777
+ })] })]
778
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
779
+ style: {
780
+ ...buttonsStyle,
781
+ alignItems: "center",
782
+ flexWrap: "wrap"
783
+ },
784
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
785
+ style: bodyStyle$2,
786
+ children: [
787
+ t("modelContext"),
788
+ ": ",
789
+ invalidBudget ? t("contextCustom") : `${effectiveBudget.toLocaleString()} tokens${budget === void 0 ? ` · ${t("contextDefault")}` : ""}`
790
+ ]
791
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
792
+ type: "button",
793
+ style: buttonStyle$3,
794
+ "aria-expanded": expandedModels[model.id] === true,
795
+ onClick: () => {
796
+ setExpandedModels((current) => ({
797
+ ...current,
798
+ [model.id]: !current[model.id]
799
+ }));
800
+ },
801
+ children: expandedModels[model.id] === true ? t("contextHide") : t("contextAdjust")
802
+ })]
803
+ })]
804
+ }), expandedModels[model.id] === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
805
+ role: "group",
806
+ "aria-label": t("contextTokens"),
807
+ style: {
808
+ display: "flex",
809
+ flexDirection: "column",
810
+ gap: 8,
811
+ marginTop: 12
812
+ },
501
813
  children: [
502
- " (",
503
- model.id,
504
- ")"
814
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
815
+ style: {
816
+ display: "flex",
817
+ alignItems: "center",
818
+ justifyContent: "space-between",
819
+ gap: 12
820
+ },
821
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
822
+ style: labelStyle,
823
+ children: t("contextTokens")
824
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
825
+ type: "number",
826
+ min: 1,
827
+ step: 1,
828
+ max: model.maxContextWindow,
829
+ style: {
830
+ ...controlStyle,
831
+ width: 112,
832
+ flexShrink: 0,
833
+ textAlign: "right",
834
+ fontVariantNumeric: "tabular-nums"
835
+ },
836
+ value: Number.isNaN(effectiveBudget) ? "" : effectiveBudget,
837
+ "aria-invalid": invalidBudget,
838
+ onChange: (event) => {
839
+ changeBudget(event.currentTarget.valueAsNumber);
840
+ }
841
+ })]
842
+ }),
843
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
844
+ style: {
845
+ ...bodyStyle$2,
846
+ display: "flex",
847
+ alignItems: "center",
848
+ justifyContent: "space-between",
849
+ flexWrap: "wrap",
850
+ gap: 8
851
+ },
852
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
853
+ style: {
854
+ display: "inline-flex",
855
+ alignItems: "center",
856
+ gap: 6
857
+ },
858
+ children: [
859
+ model.contextLimitSource === "codex-catalog" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
860
+ href: OPENAI_CODEX_CONTEXT_LIMIT_SOURCE,
861
+ target: "_blank",
862
+ rel: "noopener noreferrer",
863
+ title: t("contextLimitSource"),
864
+ style: {
865
+ color: "inherit",
866
+ textDecorationStyle: "dotted",
867
+ textUnderlineOffset: 3
868
+ },
869
+ children: t("contextMaximum")
870
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
871
+ title: t("contextLimitFallback"),
872
+ children: t("contextMaximum")
873
+ }),
874
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
875
+ style: {
876
+ padding: "1px 6px",
877
+ borderRadius: 4,
878
+ background: "var(--dsw-alias-bg-layer-2, var(--dsw-alias-bg-layer-1))",
879
+ fontVariantNumeric: "tabular-nums"
880
+ },
881
+ children: model.maxContextWindow
882
+ }),
883
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "tokens" })
884
+ ]
885
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
886
+ type: "button",
887
+ title: `${t("contextDefault")}: ${model.contextWindow.toLocaleString()} tokens`,
888
+ style: {
889
+ ...bodyStyle$2,
890
+ padding: "2px 0",
891
+ border: 0,
892
+ background: "transparent",
893
+ font: "inherit",
894
+ fontSize: 12,
895
+ cursor: "pointer",
896
+ textDecoration: "underline",
897
+ textUnderlineOffset: 3
898
+ },
899
+ onClick: () => {
900
+ const overrides = { ...draft.contextWindowOverrides };
901
+ delete overrides[model.id];
902
+ update("contextWindowOverrides", overrides);
903
+ },
904
+ children: t("contextReset")
905
+ })]
906
+ }),
907
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
908
+ type: "range",
909
+ min: 1,
910
+ max: model.maxContextWindow,
911
+ step: 1,
912
+ "aria-label": t("contextSlider"),
913
+ "aria-valuetext": invalidBudget ? t("contextInvalid") : `${effectiveBudget.toLocaleString()} tokens`,
914
+ style: {
915
+ width: "100%",
916
+ height: 20,
917
+ margin: 0,
918
+ accentColor: "var(--dsw-alias-label-secondary)"
919
+ },
920
+ value: invalidBudget ? model.contextWindow : effectiveBudget,
921
+ onChange: (event) => {
922
+ changeBudget(event.currentTarget.valueAsNumber);
923
+ }
924
+ }),
925
+ budget !== void 0 && budget > model.contextWindow && !invalidBudget ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
926
+ style: bodyStyle$2,
927
+ role: "status",
928
+ children: t("contextAboveDefault")
929
+ }) : null,
930
+ invalidBudget ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
931
+ style: errorStyle$2,
932
+ role: "alert",
933
+ children: [
934
+ t("contextInvalid"),
935
+ " (1–",
936
+ model.maxContextWindow.toLocaleString(),
937
+ ")"
938
+ ]
939
+ }) : null
505
940
  ]
506
- })] })]
941
+ }) : null]
507
942
  }, model.id);
508
943
  })
509
944
  }),
945
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
946
+ style: bodyStyle$2,
947
+ children: t("contextWarning")
948
+ }),
949
+ !validContexts ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
950
+ style: errorStyle$2,
951
+ role: "alert",
952
+ children: t("contextInvalid")
953
+ }) : null,
510
954
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
511
955
  style: { paddingTop: 4 },
512
956
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
@@ -530,7 +974,7 @@ window.__ModuleLoader__.load({
530
974
  children: [
531
975
  !draft.enableProxy ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
532
976
  type: "button",
533
- style: buttonStyle$2,
977
+ style: buttonStyle$3,
534
978
  onClick: () => {
535
979
  detectProxy();
536
980
  },
@@ -539,7 +983,7 @@ window.__ModuleLoader__.load({
539
983
  }) : null,
540
984
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
541
985
  type: "button",
542
- style: buttonStyle$2,
986
+ style: buttonStyle$3,
543
987
  onClick: () => {
544
988
  setManualProxy(true);
545
989
  },
@@ -547,7 +991,7 @@ window.__ModuleLoader__.load({
547
991
  }),
548
992
  draft.enableProxy ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
549
993
  type: "button",
550
- style: buttonStyle$2,
994
+ style: buttonStyle$3,
551
995
  onClick: () => {
552
996
  update("enableProxy", false);
553
997
  },
@@ -579,7 +1023,7 @@ window.__ModuleLoader__.load({
579
1023
  }, candidate.proxyUrl)),
580
1024
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
581
1025
  type: "button",
582
- style: buttonStyle$2,
1026
+ style: buttonStyle$3,
583
1027
  onClick: () => {
584
1028
  setProxyDetection({ status: "idle" });
585
1029
  },
@@ -613,7 +1057,7 @@ window.__ModuleLoader__.load({
613
1057
  style: buttonsStyle,
614
1058
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
615
1059
  type: "button",
616
- style: buttonStyle$2,
1060
+ style: buttonStyle$3,
617
1061
  disabled: !isValidOpenAICodexProxyUrl(draft.proxyUrl) || manualProbeBusy,
618
1062
  onClick: () => {
619
1063
  testManualProxy();
@@ -837,7 +1281,7 @@ window.__ModuleLoader__.load({
837
1281
  style: buttonsStyle,
838
1282
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
839
1283
  type: "button",
840
- style: buttonStyle$2,
1284
+ style: buttonStyle$3,
841
1285
  disabled: !dirty || busy,
842
1286
  onClick: discard,
843
1287
  children: t("discard")
@@ -865,7 +1309,10 @@ window.__ModuleLoader__.load({
865
1309
  "image-generation",
866
1310
  "oauth-history",
867
1311
  "model-visibility",
868
- "proxy-connection"
1312
+ "proxy-connection",
1313
+ "models-account",
1314
+ "context-budget",
1315
+ "auto-review-probe"
869
1316
  ];
870
1317
  function isHighlightKind(value) {
871
1318
  return typeof value === "string" && HIGHLIGHT_KINDS.includes(value);
@@ -1255,7 +1702,7 @@ window.__ModuleLoader__.load({
1255
1702
  gap: 16,
1256
1703
  flexWrap: "wrap"
1257
1704
  };
1258
- const buttonStyle$1 = {
1705
+ const buttonStyle$2 = {
1259
1706
  display: "inline-flex",
1260
1707
  alignItems: "center",
1261
1708
  justifyContent: "center",
@@ -1273,10 +1720,10 @@ window.__ModuleLoader__.load({
1273
1720
  cursor: "pointer"
1274
1721
  };
1275
1722
  const primaryButtonStyle$1 = {
1276
- ...buttonStyle$1,
1277
- borderColor: "var(--dsw-alias-brand-primary)",
1278
- background: "var(--dsw-alias-brand-primary)",
1279
- color: "white"
1723
+ ...buttonStyle$2,
1724
+ borderColor: "var(--dsw-alias-button-primary-fill)",
1725
+ background: "var(--dsw-alias-button-primary-fill)",
1726
+ color: "var(--dsw-alias-label-primary-foreground)"
1280
1727
  };
1281
1728
  const textButtonStyle = {
1282
1729
  border: 0,
@@ -1370,7 +1817,10 @@ window.__ModuleLoader__.load({
1370
1817
  "image-generation": "updateHighlightImageGeneration",
1371
1818
  "oauth-history": "updateHighlightOauthHistory",
1372
1819
  "model-visibility": "updateHighlightModelVisibility",
1373
- "proxy-connection": "updateHighlightProxyConnection"
1820
+ "proxy-connection": "updateHighlightProxyConnection",
1821
+ "models-account": "updateHighlightModelsAccount",
1822
+ "context-budget": "updateHighlightContextBudget",
1823
+ "auto-review-probe": "updateHighlightAutoReviewProbe"
1374
1824
  };
1375
1825
  const compatibilityTitleKeys = {
1376
1826
  compatible: "compatibilityCompatibleTitle",
@@ -1574,7 +2024,7 @@ window.__ModuleLoader__.load({
1574
2024
  children: compatibilityWarning ? t("updateHeading") : available ? t("newVersionAvailable", { version: snapshot.latestVersion }) : t("updateHeading")
1575
2025
  }), overlay ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1576
2026
  type: "button",
1577
- style: buttonStyle$1,
2027
+ style: buttonStyle$2,
1578
2028
  "aria-label": t("dismissUpdate"),
1579
2029
  onClick: () => {
1580
2030
  if (noticeKey !== void 0) updater.dismiss(noticeKey);
@@ -1663,7 +2113,7 @@ window.__ModuleLoader__.load({
1663
2113
  children: agentPrompt
1664
2114
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1665
2115
  type: "button",
1666
- style: buttonStyle$1,
2116
+ style: buttonStyle$2,
1667
2117
  onClick: () => {
1668
2118
  copy();
1669
2119
  },
@@ -1726,7 +2176,7 @@ window.__ModuleLoader__.load({
1726
2176
  },
1727
2177
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1728
2178
  type: "button",
1729
- style: buttonStyle$1,
2179
+ style: buttonStyle$2,
1730
2180
  disabled: snapshot.status === "checking",
1731
2181
  onClick: () => {
1732
2182
  updater.refresh(true);
@@ -1754,8 +2204,6 @@ window.__ModuleLoader__.load({
1754
2204
  //#endregion
1755
2205
  //#region src/client/OpenAICodexSettings.tsx
1756
2206
  /** Plugin-owned OpenAI Codex account controls used inside Plugin configuration. */
1757
- const POLL_INTERVAL_MS = 1e3;
1758
- const USAGE_POLL_INTERVAL_MS$1 = 6e4;
1759
2207
  const pageStyle = {
1760
2208
  display: "flex",
1761
2209
  flexDirection: "column",
@@ -1811,7 +2259,7 @@ window.__ModuleLoader__.load({
1811
2259
  fontWeight: 500,
1812
2260
  color: "var(--dsw-alias-label-primary)"
1813
2261
  };
1814
- const buttonStyle = {
2262
+ const buttonStyle$1 = {
1815
2263
  boxSizing: "border-box",
1816
2264
  minHeight: 34,
1817
2265
  padding: "6px 14px",
@@ -1824,10 +2272,10 @@ window.__ModuleLoader__.load({
1824
2272
  cursor: "pointer"
1825
2273
  };
1826
2274
  const primaryButtonStyle = {
1827
- ...buttonStyle,
1828
- borderColor: "var(--dsw-alias-brand-primary)",
1829
- background: "var(--dsw-alias-brand-primary)",
1830
- color: "white"
2275
+ ...buttonStyle$1,
2276
+ borderColor: "var(--dsw-alias-button-primary-fill)",
2277
+ background: "var(--dsw-alias-button-primary-fill)",
2278
+ color: "var(--dsw-alias-label-primary-foreground)"
1831
2279
  };
1832
2280
  const errorStyle$1 = {
1833
2281
  ...bodyStyle,
@@ -1931,15 +2379,16 @@ window.__ModuleLoader__.load({
1931
2379
  ]
1932
2380
  });
1933
2381
  }
1934
- function UsageLimits({ usage, quotaError, t }) {
2382
+ /** Quota rows shared by Models and Plugin settings. */
2383
+ function UsageLimits({ usage, quotaError, t, heading = true }) {
1935
2384
  const hasData = usage.rateLimits.length > 0 || usage.credits !== void 0 || usage.individualLimit !== void 0;
1936
2385
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1937
2386
  style: quotaListStyle,
1938
2387
  children: [
1939
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
2388
+ heading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
1940
2389
  style: quotaTitleStyle,
1941
2390
  children: t("usageLimits")
1942
- }),
2391
+ }) : null,
1943
2392
  usage.rateLimits.map((limit) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1944
2393
  style: quotaGroupStyle,
1945
2394
  children: limit.windows.map((window) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(QuotaBar, {
@@ -1973,6 +2422,7 @@ window.__ModuleLoader__.load({
1973
2422
  ]
1974
2423
  });
1975
2424
  }
2425
+ /** Account indicator colors shared by the compact row and expanded controls. */
1976
2426
  function dotStyle(status) {
1977
2427
  return {
1978
2428
  width: 9,
@@ -1982,133 +2432,157 @@ window.__ModuleLoader__.load({
1982
2432
  background: status === "signed-in" ? "var(--dsw-alias-state-success-primary, #22a06b)" : status === "error" || status === "reauth-required" || 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)"
1983
2433
  };
1984
2434
  }
1985
- var AccountRequestError = class extends Error {
1986
- code;
1987
- constructor(code, message) {
1988
- super(message);
1989
- this.code = code;
1990
- this.name = "AccountRequestError";
1991
- }
1992
- };
1993
- async function jsonRequest(path, method = "GET", signal) {
1994
- const response = await fetch(path, {
1995
- method,
1996
- headers: { accept: "application/json" },
1997
- credentials: "same-origin",
1998
- ...signal === void 0 ? {} : { signal }
2435
+ /** Non-sensitive account state label for either settings presentation. */
2436
+ function accountStatusLabel(status, t) {
2437
+ return t({
2438
+ "signed-in": "signedIn",
2439
+ loading: "loadingAccount",
2440
+ "signing-in": "signingIn",
2441
+ "reauth-required": "reauthRequired",
2442
+ "remote-web-origin-not-trusted": "remoteOriginTitle",
2443
+ error: "requestFailed",
2444
+ "signed-out": "signedOut"
2445
+ }[status]);
2446
+ }
2447
+ /** Shared OAuth actions; Models uses shorter, task-oriented labels. */
2448
+ function AccountActions({ t, store, snapshot, compact = false }) {
2449
+ const { status, busy } = snapshot;
2450
+ if (status.status === "loading" || status.status === "remote-web-origin-not-trusted") return null;
2451
+ if (status.status === "signed-in") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2452
+ type: "button",
2453
+ style: buttonStyle$1,
2454
+ disabled: busy,
2455
+ onClick: () => {
2456
+ store.signOut();
2457
+ },
2458
+ children: busy ? t("working") : t("logout")
2459
+ });
2460
+ if (status.status === "signing-in") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2461
+ style: rowStyle,
2462
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2463
+ type: "button",
2464
+ style: buttonStyle$1,
2465
+ disabled: busy,
2466
+ onClick: () => {
2467
+ store.signIn();
2468
+ },
2469
+ children: busy ? t("working") : t(compact ? "continueAuthorization" : "reopenAuthorization")
2470
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2471
+ type: "button",
2472
+ style: buttonStyle$1,
2473
+ disabled: busy,
2474
+ onClick: () => {
2475
+ store.cancel();
2476
+ },
2477
+ children: t("cancelSignIn")
2478
+ })]
2479
+ });
2480
+ const action = status.status === "error" || status.status === "reauth-required" ? t(compact ? "reauthorize" : "loginAgain") : t(compact ? "authorize" : "login");
2481
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2482
+ type: "button",
2483
+ style: primaryButtonStyle,
2484
+ disabled: busy,
2485
+ onClick: () => {
2486
+ store.signIn();
2487
+ },
2488
+ children: busy ? t("working") : action
1999
2489
  });
2000
- const value = await response.json().catch(() => void 0);
2001
- if (!response.ok) {
2002
- const code = typeof value === "object" && value !== null && "error" in value && typeof value.error === "string" ? value.error : `HTTP ${response.status}`;
2003
- throw new AccountRequestError(code, code);
2004
- }
2005
- return value;
2006
2490
  }
2007
- /** OpenAI Codex account status and OAuth actions. */
2008
- function OpenAICodexSettings({ t, configScope, updater, embedded = false }) {
2009
- if (t === void 0) throw new Error("OpenAI Codex settings requires its translation function");
2010
- const [status, setStatus] = (0, react.useState)({ status: "loading" });
2011
- const [busy, setBusy] = (0, react.useState)(false);
2491
+ /** Recovery links, errors and trusted-origin guidance in either account entry. */
2492
+ function AccountFeedback({ t, snapshot }) {
2493
+ const { status, loginUrl } = snapshot;
2012
2494
  const [copied, setCopied] = (0, react.useState)(false);
2013
2495
  const [copyFailed, setCopyFailed] = (0, react.useState)(false);
2014
- const [loginUrl, setLoginUrl] = (0, react.useState)();
2015
- const mounted = (0, react.useRef)(true);
2016
2496
  const trustedOriginCommand = `dsh plugin --profile web exec dsh-codex-connect trust-origin ${window.location.origin}`;
2017
- (0, react.useEffect)(() => {
2018
- mounted.current = true;
2019
- return () => {
2020
- mounted.current = false;
2021
- };
2022
- }, []);
2023
- const refresh = (0, react.useCallback)(async (signal) => {
2024
- try {
2025
- const nextStatus = await jsonRequest(OPENAI_CODEX_AUTH_STATUS_PATH, "GET", signal);
2026
- if (mounted.current && signal?.aborted !== true) setStatus(nextStatus);
2027
- } catch (error) {
2028
- if (mounted.current && signal?.aborted !== true) setStatus(error instanceof AccountRequestError && error.code === "remote-web-origin-not-trusted" ? { status: "remote-web-origin-not-trusted" } : {
2029
- status: "error",
2030
- message: error instanceof Error ? error.message : t("requestFailed")
2031
- });
2032
- }
2033
- }, [t]);
2034
- (0, react.useEffect)(() => {
2035
- const controller = new AbortController();
2036
- refresh(controller.signal);
2037
- return () => {
2038
- controller.abort();
2039
- };
2040
- }, [refresh]);
2041
- (0, react.useEffect)(() => {
2042
- const interval = status.status === "signing-in" ? POLL_INTERVAL_MS : status.status === "signed-in" ? USAGE_POLL_INTERVAL_MS$1 : void 0;
2043
- if (interval === void 0) return;
2044
- const controller = new AbortController();
2045
- const timer = window.setInterval(() => {
2046
- refresh(controller.signal);
2047
- }, interval);
2048
- return () => {
2049
- window.clearInterval(timer);
2050
- controller.abort();
2051
- };
2052
- }, [refresh, status.status]);
2053
- (0, react.useEffect)(() => {
2054
- if (status.status !== "signing-in") setLoginUrl(void 0);
2055
- }, [status.status]);
2056
- const signIn = async () => {
2057
- const popup = window.open("about:blank", "_blank");
2058
- if (popup !== null) popup.opener = null;
2059
- setBusy(true);
2060
- setStatus({ status: "signing-in" });
2061
- try {
2062
- const challenge = await jsonRequest(OPENAI_CODEX_AUTH_LOGIN_PATH, "POST");
2063
- if (!mounted.current) {
2064
- popup?.close();
2065
- return;
2066
- }
2067
- if (popup !== null) {
2068
- setLoginUrl(void 0);
2069
- popup.location.replace(challenge.url);
2070
- } else setLoginUrl(challenge.url);
2071
- } catch (error) {
2072
- popup?.close();
2073
- setLoginUrl(void 0);
2074
- if (mounted.current) setStatus(error instanceof AccountRequestError && error.code === "remote-web-origin-not-trusted" ? { status: "remote-web-origin-not-trusted" } : {
2075
- status: "error",
2076
- message: error instanceof Error ? error.message : t("requestFailed")
2077
- });
2078
- } finally {
2079
- if (mounted.current) setBusy(false);
2080
- }
2081
- };
2082
2497
  const copyTrustedOriginCommand = async () => {
2083
2498
  setCopyFailed(false);
2084
2499
  try {
2085
2500
  if (navigator.clipboard?.writeText === void 0) throw new Error("clipboard unavailable");
2086
2501
  await navigator.clipboard.writeText(trustedOriginCommand);
2087
- if (mounted.current) setCopied(true);
2502
+ setCopied(true);
2088
2503
  } catch {
2089
- if (mounted.current) setCopyFailed(true);
2090
- }
2091
- };
2092
- const signOut = async () => {
2093
- setBusy(true);
2094
- try {
2095
- await jsonRequest(OPENAI_CODEX_AUTH_LOGOUT_PATH, "POST");
2096
- if (mounted.current) setStatus({ status: "signed-out" });
2097
- } catch (error) {
2098
- if (mounted.current) setStatus({
2099
- status: "error",
2100
- message: error instanceof Error ? error.message : t("requestFailed")
2101
- });
2102
- } finally {
2103
- if (mounted.current) setBusy(false);
2504
+ setCopyFailed(true);
2104
2505
  }
2105
2506
  };
2106
- const label = status.status === "signed-in" ? t("signedIn") : status.status === "loading" ? t("loadingAccount") : status.status === "signing-in" ? t("signingIn") : status.status === "reauth-required" ? t("reauthRequired") : status.status === "remote-web-origin-not-trusted" ? t("remoteOriginTitle") : status.status === "error" ? t("requestFailed") : t("signedOut");
2507
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
2508
+ loginUrl === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2509
+ style: {
2510
+ display: "flex",
2511
+ flexDirection: "column",
2512
+ alignItems: "flex-start",
2513
+ gap: 10
2514
+ },
2515
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2516
+ style: bodyStyle,
2517
+ children: t("authorizationHelp")
2518
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
2519
+ href: loginUrl,
2520
+ target: "_blank",
2521
+ rel: "noopener noreferrer",
2522
+ style: {
2523
+ ...primaryButtonStyle,
2524
+ display: "inline-flex",
2525
+ alignItems: "center",
2526
+ textDecoration: "none"
2527
+ },
2528
+ children: t("openLoginInBrowser")
2529
+ })]
2530
+ }),
2531
+ status.status === "error" || status.status === "reauth-required" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2532
+ style: errorStyle$1,
2533
+ children: status.message
2534
+ }) : null,
2535
+ status.status === "remote-web-origin-not-trusted" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2536
+ style: {
2537
+ display: "flex",
2538
+ flexDirection: "column",
2539
+ gap: 10
2540
+ },
2541
+ children: [
2542
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2543
+ style: errorStyle$1,
2544
+ children: t("remoteOriginDescription")
2545
+ }),
2546
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2547
+ style: bodyStyle,
2548
+ children: t("remoteOriginCommandHelp")
2549
+ }),
2550
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
2551
+ style: commandStyle,
2552
+ children: trustedOriginCommand
2553
+ }),
2554
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2555
+ style: rowStyle,
2556
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2557
+ type: "button",
2558
+ style: buttonStyle$1,
2559
+ onClick: () => {
2560
+ copyTrustedOriginCommand();
2561
+ },
2562
+ children: copied ? t("remoteOriginCopied") : t("remoteOriginCopy")
2563
+ }), copyFailed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2564
+ style: errorStyle$1,
2565
+ children: t("remoteOriginCopyFailed")
2566
+ }) : null]
2567
+ })
2568
+ ]
2569
+ }) : null
2570
+ ] });
2571
+ }
2572
+ /** OpenAI Codex account status and OAuth actions. */
2573
+ function OpenAICodexSettings({ t, configScope, updater, account, embedded = false, accountOnly = false }) {
2574
+ if (t === void 0) throw new Error("OpenAI Codex settings requires its translation function");
2575
+ const [localAccount] = (0, react.useState)(() => new OpenAICodexAccountStore());
2576
+ const store = account ?? localAccount;
2577
+ const snapshot = (0, react.useSyncExternalStore)(store.subscribe, store.getSnapshot);
2578
+ const { status } = snapshot;
2579
+ const titleId = (0, react.useId)();
2580
+ const label = accountStatusLabel(status.status, t);
2107
2581
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2108
2582
  style: embedded ? embeddedPageStyle : pageStyle,
2109
- ...embedded ? { "aria-label": t("title") } : { "aria-labelledby": "openai-codex-settings-title" },
2583
+ ...embedded ? { "aria-label": t("title") } : { "aria-labelledby": titleId },
2110
2584
  children: [embedded ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
2111
- id: "openai-codex-settings-title",
2585
+ id: titleId,
2112
2586
  style: titleStyle,
2113
2587
  children: t("title")
2114
2588
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
@@ -2120,7 +2594,7 @@ window.__ModuleLoader__.load({
2120
2594
  })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2121
2595
  style: embedded ? embeddedCardStyle : cardStyle$1,
2122
2596
  children: [
2123
- updater === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OpenAICodexUpdateSettings, {
2597
+ accountOnly || updater === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OpenAICodexUpdateSettings, {
2124
2598
  t,
2125
2599
  updater
2126
2600
  }),
@@ -2137,92 +2611,25 @@ window.__ModuleLoader__.load({
2137
2611
  "aria-hidden": "true",
2138
2612
  style: dotStyle(status.status)
2139
2613
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: label })]
2140
- }), status.status === "loading" || status.status === "remote-web-origin-not-trusted" ? null : status.status === "signed-in" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2141
- type: "button",
2142
- style: buttonStyle,
2143
- disabled: busy,
2144
- onClick: () => {
2145
- signOut();
2146
- },
2147
- children: busy ? t("working") : t("logout")
2148
- }) : status.status === "signing-in" && loginUrl !== void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2149
- type: "button",
2150
- style: primaryButtonStyle,
2151
- disabled: busy,
2152
- onClick: () => {
2153
- signIn();
2154
- },
2155
- children: busy ? t("working") : status.status === "error" || status.status === "reauth-required" ? t("loginAgain") : t("login")
2614
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountActions, {
2615
+ t,
2616
+ store,
2617
+ snapshot
2156
2618
  })]
2157
2619
  }),
2158
- loginUrl === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2159
- style: {
2160
- display: "flex",
2161
- flexDirection: "column",
2162
- alignItems: "flex-start",
2163
- gap: 10
2164
- },
2165
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2166
- style: bodyStyle,
2167
- children: t("popupBlockedFallback")
2168
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
2169
- href: loginUrl,
2170
- target: "_blank",
2171
- rel: "noopener noreferrer",
2172
- style: {
2173
- ...primaryButtonStyle,
2174
- display: "inline-flex",
2175
- alignItems: "center",
2176
- textDecoration: "none"
2177
- },
2178
- children: t("openLoginInBrowser")
2179
- })]
2620
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountFeedback, {
2621
+ t,
2622
+ snapshot
2180
2623
  }),
2181
- status.status === "error" || status.status === "reauth-required" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2182
- style: errorStyle$1,
2183
- children: status.message
2184
- }) : null,
2185
- status.status === "remote-web-origin-not-trusted" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2186
- style: {
2187
- display: "flex",
2188
- flexDirection: "column",
2189
- gap: 10
2190
- },
2191
- children: [
2192
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2193
- style: errorStyle$1,
2194
- children: t("remoteOriginDescription")
2195
- }),
2196
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2197
- style: bodyStyle,
2198
- children: t("remoteOriginCommandHelp")
2199
- }),
2200
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
2201
- style: commandStyle,
2202
- children: trustedOriginCommand
2203
- }),
2204
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2205
- style: rowStyle,
2206
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2207
- type: "button",
2208
- style: buttonStyle,
2209
- onClick: () => {
2210
- copyTrustedOriginCommand();
2211
- },
2212
- children: copied ? t("remoteOriginCopied") : t("remoteOriginCopy")
2213
- }), copyFailed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2214
- style: errorStyle$1,
2215
- children: t("remoteOriginCopyFailed")
2216
- }) : null]
2217
- })
2218
- ]
2219
- }) : null,
2220
2624
  status.status === "signed-in" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageLimits, {
2221
2625
  usage: status.usage,
2222
2626
  ...status.quotaError === void 0 ? {} : { quotaError: status.quotaError },
2223
2627
  t
2224
2628
  }) : null,
2225
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OpenAICodexConfiguration, {
2629
+ accountOnly ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2630
+ style: bodyStyle,
2631
+ children: t("modelsAccountHelp")
2632
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OpenAICodexConfiguration, {
2226
2633
  t,
2227
2634
  ...configScope === void 0 ? {} : { scope: configScope }
2228
2635
  })
@@ -2300,7 +2707,7 @@ window.__ModuleLoader__.load({
2300
2707
  });
2301
2708
  }
2302
2709
  /** Render account management as one expandable Plugin configuration card. */
2303
- function OpenAICodexPluginCard({ t, configScope, updater }) {
2710
+ function OpenAICodexPluginCard({ t, configScope, updater, account }) {
2304
2711
  if (t === void 0) throw new Error("OpenAI Codex plugin card requires its translation function");
2305
2712
  const [open, setOpen] = (0, react.useState)(false);
2306
2713
  const title = t("title");
@@ -2331,6 +2738,7 @@ window.__ModuleLoader__.load({
2331
2738
  style: cardBodyStyle,
2332
2739
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OpenAICodexSettings, {
2333
2740
  t,
2741
+ ...account === void 0 ? {} : { account },
2334
2742
  ...updater === void 0 ? {} : { updater },
2335
2743
  embedded: true,
2336
2744
  ...configScope === void 0 ? {} : { configScope }
@@ -2761,8 +3169,24 @@ window.__ModuleLoader__.load({
2761
3169
  /** English copy for the OpenAI Codex Plugin configuration card. */
2762
3170
  const en = {
2763
3171
  title: "Codex Connect",
3172
+ modelsProviderName: "Openai-Codex",
3173
+ modelsProviderSupport: "Powered by the Codex Connect plugin.",
2764
3174
  intro: "Use your ChatGPT subscription in dsh, with optional GPT Image generation and no API key.",
2765
3175
  accountHeading: "ChatGPT account",
3176
+ manageAccount: "Manage",
3177
+ authorize: "Authorize",
3178
+ continueAuthorization: "Continue authorization",
3179
+ reauthorize: "Reauthorize",
3180
+ viewQuota: "View quota",
3181
+ hideQuota: "Hide quota",
3182
+ moreSettings: "More settings",
3183
+ moreSettingsTitle: "Codex Connect settings",
3184
+ closeSettings: "Close",
3185
+ settingsSaveHint: "Save applies changes to both settings entries. Closing discards unsaved changes.",
3186
+ reopenAuthorization: "Reopen authorization",
3187
+ cancelSignIn: "Cancel sign-in",
3188
+ authorizationHelp: "You can reopen the authorization page or cancel and start again. Cancelling does not sign out an existing account.",
3189
+ modelsAccountHelp: "Proxy, model visibility, search and image options remain in Settings → Plugins → Codex Connect, or open them here:",
2766
3190
  expand: "Expand settings",
2767
3191
  collapse: "Collapse settings",
2768
3192
  loadingAccount: "Loading account…",
@@ -2810,6 +3234,20 @@ window.__ModuleLoader__.load({
2810
3234
  modelCatalogIntro: "Choose which Codex models appear in model selectors. Existing conversations can continue using a hidden model.",
2811
3235
  modelCatalogLoading: "Loading available models…",
2812
3236
  modelCatalogFailed: "Available models could not be loaded.",
3237
+ modelContext: "Context",
3238
+ contextDefault: "Catalog default",
3239
+ contextCustom: "Custom",
3240
+ contextAdjust: "Adjust",
3241
+ contextHide: "Hide adjustment",
3242
+ contextTokens: "Context length",
3243
+ contextReset: "Restore default",
3244
+ contextSlider: "Adjust context budget",
3245
+ contextMaximum: "Configuration limit",
3246
+ contextLimitSource: "Source: official Codex configuration catalog (2026-08-28)",
3247
+ contextLimitFallback: "No higher configuration limit confirmed; capped at the installed catalog default.",
3248
+ contextAboveDefault: "Above the catalog default: larger requests may consume more quota or be rejected by your account or route. This plugin has not capacity-tested this range.",
3249
+ contextWarning: "Context overrides change client budgeting, not server capacity. An excessive value can cause requests to fail. Hiding a model keeps its budget. Changes apply after Save.",
3250
+ contextInvalid: "Enter a whole-number budget within the model’s configuration range, or choose Restore default.",
2813
3251
  capabilitiesHeading: "Optional capabilities",
2814
3252
  capabilitiesIntro: "Choose which extra Codex capabilities this dsh profile may register.",
2815
3253
  networkHeading: "Network connection",
@@ -2945,6 +3383,9 @@ window.__ModuleLoader__.load({
2945
3383
  updateHighlightOauthHistory: "Added safer session-history migration and a sign-in fallback when the account needs to reconnect.",
2946
3384
  updateHighlightModelVisibility: "You can now choose which Codex models appear in model selectors.",
2947
3385
  updateHighlightProxyConnection: "Added opt-in Codex proxy detection with explicit confirmation and a visible direct-connection fallback.",
3386
+ updateHighlightModelsAccount: "Added an Openai-Codex account card in Models settings for authorization, quota, and shared plugin settings.",
3387
+ updateHighlightContextBudget: "Added bounded per-model context budget controls while preserving each catalog default until you save an override.",
3388
+ updateHighlightAutoReviewProbe: "Added an explicit, synthetic probe for checking whether the hidden Codex approval reviewer is available without enabling it.",
2948
3389
  viewTechnicalDetails: "View technical details",
2949
3390
  hideTechnicalDetails: "Hide technical details",
2950
3391
  technicalDetailsHeading: "Technical changes",
@@ -2970,8 +3411,24 @@ window.__ModuleLoader__.load({
2970
3411
  /** Chinese copy for the OpenAI Codex Plugin configuration card. */
2971
3412
  const zh = {
2972
3413
  title: "Codex Connect",
3414
+ modelsProviderName: "Openai-Codex",
3415
+ modelsProviderSupport: "由 Codex Connect 插件提供支持。",
2973
3416
  intro: "使用 ChatGPT 订阅在 dsh 中调用模型,并可使用 GPT Image 生成图片,无需 API Key。",
2974
3417
  accountHeading: "ChatGPT 账户",
3418
+ manageAccount: "管理",
3419
+ authorize: "授权",
3420
+ continueAuthorization: "继续授权",
3421
+ reauthorize: "重新授权",
3422
+ viewQuota: "查看额度",
3423
+ hideQuota: "收起额度",
3424
+ moreSettings: "更多设置",
3425
+ moreSettingsTitle: "Codex Connect 设置",
3426
+ closeSettings: "关闭",
3427
+ settingsSaveHint: "保存后两处设置同步生效;关闭将放弃未保存的修改。",
3428
+ reopenAuthorization: "重新打开授权",
3429
+ cancelSignIn: "取消登录",
3430
+ authorizationHelp: "可以重新打开授权页,或取消后重试。取消登录不会退出已有账户。",
3431
+ modelsAccountHelp: "代理、模型显示、搜索和图片选项仍可在“设置 → 插件 → Codex Connect”中管理,也可直接打开:",
2975
3432
  expand: "展开设置",
2976
3433
  collapse: "折叠设置",
2977
3434
  loadingAccount: "正在加载账户信息…",
@@ -3019,6 +3476,20 @@ window.__ModuleLoader__.load({
3019
3476
  modelCatalogIntro: "选择要在模型选择器中显示的 Codex 模型;隐藏模型后,已有会话仍可继续使用。",
3020
3477
  modelCatalogLoading: "正在加载可用模型…",
3021
3478
  modelCatalogFailed: "无法加载可用模型。",
3479
+ modelContext: "上下文",
3480
+ contextDefault: "目录默认",
3481
+ contextCustom: "自定义",
3482
+ contextAdjust: "调整",
3483
+ contextHide: "收起调整",
3484
+ contextTokens: "上下文长度",
3485
+ contextReset: "恢复默认",
3486
+ contextSlider: "调整上下文预算",
3487
+ contextMaximum: "配置上限",
3488
+ contextLimitSource: "依据:Codex 官方配置目录(2026-08-28)",
3489
+ contextLimitFallback: "尚未确认更高配置上限,暂以已安装目录的默认值为上限。",
3490
+ contextAboveDefault: "已超过目录默认值:请求可能消耗更多额度,或被当前账号、通道拒绝。本插件未对该范围进行容量实测。",
3491
+ contextWarning: "调整的是本地上下文预算,不会扩大服务端容量;设置过高可能导致请求失败。隐藏模型会保留其预算,修改在保存后生效。",
3492
+ contextInvalid: "请输入模型配置范围内的整数预算,或点击“恢复默认”。",
3022
3493
  capabilitiesHeading: "可选能力",
3023
3494
  capabilitiesIntro: "选择允许此 dsh profile 注册哪些额外的 Codex 能力。",
3024
3495
  networkHeading: "网络连接",
@@ -3154,6 +3625,9 @@ window.__ModuleLoader__.load({
3154
3625
  updateHighlightOauthHistory: "增加更安全的会话历史迁移,并在账号需要重新登录时提供备用登录路径。",
3155
3626
  updateHighlightModelVisibility: "现在可以选择哪些 Codex 模型显示在模型选择菜单中。",
3156
3627
  updateHighlightProxyConnection: "增加可选的 Codex 代理检测,启用前需要明确确认,并始终保留直连回退入口。",
3628
+ updateHighlightModelsAccount: "在模型设置中增加 Openai-Codex 账户卡,用于授权、查看额度和打开共享插件设置。",
3629
+ updateHighlightContextBudget: "增加有范围限制的逐模型上下文预算设置;保存覆盖值前继续使用各模型的目录默认值。",
3630
+ updateHighlightAutoReviewProbe: "增加显式的合成探针,用于检查隐藏的 Codex 审批 reviewer 是否可用,但不会启用该能力。",
3157
3631
  viewTechnicalDetails: "查看完整技术说明",
3158
3632
  hideTechnicalDetails: "收起技术说明",
3159
3633
  technicalDetailsHeading: "技术变更",
@@ -3270,7 +3744,7 @@ window.__ModuleLoader__.load({
3270
3744
  //#region src/client/CodexImageGallery.tsx
3271
3745
  /** Self-contained result-image gallery for the tool-call view.
3272
3746
  *
3273
- * DSH 0.1.1-rc.2 keeps the attachment package's React atoms behind its
3747
+ * DSH keeps the attachment package's React atoms behind its
3274
3748
  * conversation slots. A tool-call view cannot render those slots, so this
3275
3749
  * gallery owns only the durable-image presentation it needs and receives all
3276
3750
  * stateful work through props.
@@ -3441,7 +3915,6 @@ window.__ModuleLoader__.load({
3441
3915
  open: true,
3442
3916
  onClose,
3443
3917
  title: labels.dialog,
3444
- closeLabel: labels.close,
3445
3918
  headless: true,
3446
3919
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3447
3920
  ref: lightbox,
@@ -4340,7 +4813,232 @@ window.__ModuleLoader__.load({
4340
4813
  }
4341
4814
  //#endregion
4342
4815
  //#region src/version.ts
4343
- const CODEX_CONNECT_VERSION = "0.1.0-alpha.4.21";
4816
+ const CODEX_CONNECT_VERSION = "0.1.0-alpha.4.23";
4817
+ //#endregion
4818
+ //#region src/client/OpenAICodexModelsCard.tsx
4819
+ /** Compact Models account entry with quota disclosure and shared configuration. */
4820
+ const buttonStyle = {
4821
+ padding: "6px 14px",
4822
+ border: "1px solid var(--dsw-alias-border-l2)",
4823
+ borderRadius: 999,
4824
+ background: "transparent",
4825
+ color: "inherit",
4826
+ font: "inherit",
4827
+ fontSize: 14,
4828
+ cursor: "pointer"
4829
+ };
4830
+ const secondaryStyle = {
4831
+ fontSize: 12,
4832
+ lineHeight: "18px",
4833
+ color: "var(--dsw-alias-label-secondary)"
4834
+ };
4835
+ /** Native modality contains keyboard focus and restores it to More settings on close. */
4836
+ function ConfigurationDialog({ t, configScope, onClose }) {
4837
+ const dialog = (0, react.useRef)(null);
4838
+ const titleId = (0, react.useId)();
4839
+ (0, react.useEffect)(() => {
4840
+ const element = dialog.current;
4841
+ element?.showModal();
4842
+ return () => {
4843
+ element?.close();
4844
+ };
4845
+ }, []);
4846
+ const close = () => {
4847
+ dialog.current?.close();
4848
+ onClose();
4849
+ };
4850
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dialog", {
4851
+ ref: dialog,
4852
+ "aria-labelledby": titleId,
4853
+ onCancel: (event) => {
4854
+ event.preventDefault();
4855
+ close();
4856
+ },
4857
+ onKeyDown: (event) => {
4858
+ if (event.key !== "Escape") return;
4859
+ event.stopPropagation();
4860
+ event.preventDefault();
4861
+ close();
4862
+ },
4863
+ style: {
4864
+ boxSizing: "border-box",
4865
+ width: "min(720px, calc(100vw - 32px))",
4866
+ maxHeight: "calc(100dvh - 32px)",
4867
+ overflowY: "auto",
4868
+ padding: 20,
4869
+ border: "1px solid var(--dsw-alias-border-l2)",
4870
+ borderRadius: 12,
4871
+ background: "var(--dsw-alias-bg-layer-1, white)",
4872
+ color: "var(--dsw-alias-label-primary)",
4873
+ margin: "auto"
4874
+ },
4875
+ children: [
4876
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4877
+ style: {
4878
+ display: "flex",
4879
+ alignItems: "center",
4880
+ justifyContent: "space-between",
4881
+ gap: 12
4882
+ },
4883
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
4884
+ id: titleId,
4885
+ style: {
4886
+ margin: 0,
4887
+ fontSize: 18
4888
+ },
4889
+ children: t("moreSettingsTitle")
4890
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4891
+ type: "button",
4892
+ style: buttonStyle,
4893
+ onClick: close,
4894
+ children: t("closeSettings")
4895
+ })]
4896
+ }),
4897
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4898
+ style: secondaryStyle,
4899
+ children: t("settingsSaveHint")
4900
+ }),
4901
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OpenAICodexConfiguration, {
4902
+ t,
4903
+ ...configScope === void 0 ? {} : { scope: configScope }
4904
+ })
4905
+ ]
4906
+ });
4907
+ }
4908
+ function OpenAICodexModelsCard({ t, account, configScope }) {
4909
+ const [expanded, setExpanded] = (0, react.useState)(false);
4910
+ const [settingsOpen, setSettingsOpen] = (0, react.useState)(false);
4911
+ const detailsId = (0, react.useId)();
4912
+ const snapshot = (0, react.useSyncExternalStore)(account.subscribe, account.getSnapshot);
4913
+ const { status } = snapshot;
4914
+ const label = accountStatusLabel(status.status, t);
4915
+ (0, react.useEffect)(() => {
4916
+ if (status.status !== "signed-in") setExpanded(false);
4917
+ }, [status.status]);
4918
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4919
+ style: {
4920
+ border: "1px solid var(--dsw-alias-border-l2)",
4921
+ borderRadius: 12,
4922
+ padding: "12px 14px",
4923
+ color: "var(--dsw-alias-label-primary)"
4924
+ },
4925
+ children: [
4926
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4927
+ style: {
4928
+ display: "flex",
4929
+ alignItems: "center",
4930
+ flexWrap: "wrap",
4931
+ gap: 10
4932
+ },
4933
+ children: [
4934
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4935
+ style: {
4936
+ fontSize: 14,
4937
+ lineHeight: "22px",
4938
+ fontWeight: 500
4939
+ },
4940
+ children: t("modelsProviderName")
4941
+ }),
4942
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4943
+ "aria-hidden": "true",
4944
+ style: {
4945
+ ...dotStyle(status.status),
4946
+ width: 8,
4947
+ height: 8
4948
+ }
4949
+ }),
4950
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4951
+ role: "status",
4952
+ style: {
4953
+ ...secondaryStyle,
4954
+ flex: 1
4955
+ },
4956
+ children: label
4957
+ }),
4958
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4959
+ style: {
4960
+ display: "flex",
4961
+ flexWrap: "wrap",
4962
+ gap: 8,
4963
+ marginLeft: "auto"
4964
+ },
4965
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountActions, {
4966
+ t,
4967
+ store: account,
4968
+ snapshot,
4969
+ compact: true
4970
+ }), status.status === "signed-in" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4971
+ type: "button",
4972
+ "aria-expanded": expanded,
4973
+ "aria-controls": detailsId,
4974
+ onClick: () => {
4975
+ setExpanded(!expanded);
4976
+ },
4977
+ style: buttonStyle,
4978
+ children: t(expanded ? "hideQuota" : "viewQuota")
4979
+ })]
4980
+ })
4981
+ ]
4982
+ }),
4983
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4984
+ style: {
4985
+ ...secondaryStyle,
4986
+ marginTop: 4
4987
+ },
4988
+ children: t("modelsProviderSupport")
4989
+ }),
4990
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountFeedback, {
4991
+ t,
4992
+ snapshot
4993
+ }),
4994
+ expanded && status.status === "signed-in" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4995
+ id: detailsId,
4996
+ style: {
4997
+ borderTop: "1px solid var(--dsw-alias-border-l2)",
4998
+ marginTop: 12,
4999
+ paddingTop: 12
5000
+ },
5001
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageLimits, {
5002
+ t,
5003
+ usage: status.usage,
5004
+ heading: false,
5005
+ ...status.quotaError === void 0 ? {} : { quotaError: status.quotaError }
5006
+ })
5007
+ }),
5008
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5009
+ style: {
5010
+ ...secondaryStyle,
5011
+ marginTop: 12
5012
+ },
5013
+ children: [
5014
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("modelsAccountHelp") }),
5015
+ " ",
5016
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5017
+ type: "button",
5018
+ onClick: () => {
5019
+ setSettingsOpen(true);
5020
+ },
5021
+ style: {
5022
+ ...buttonStyle,
5023
+ padding: 0,
5024
+ border: 0,
5025
+ fontSize: "inherit",
5026
+ textDecoration: "underline"
5027
+ },
5028
+ children: t("moreSettings")
5029
+ })
5030
+ ]
5031
+ }),
5032
+ settingsOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConfigurationDialog, {
5033
+ t,
5034
+ ...configScope === void 0 ? {} : { configScope },
5035
+ onClose: () => {
5036
+ setSettingsOpen(false);
5037
+ }
5038
+ })
5039
+ ]
5040
+ });
5041
+ }
4344
5042
  //#endregion
4345
5043
  //#region src/client/index.tsx
4346
5044
  /** Stable browser-plugin name. */
@@ -4351,6 +5049,7 @@ window.__ModuleLoader__.load({
4351
5049
  "locale",
4352
5050
  "connection",
4353
5051
  "remote",
5052
+ "remote.session",
4354
5053
  "settingsScope",
4355
5054
  "sessions"
4356
5055
  ];
@@ -4358,6 +5057,10 @@ window.__ModuleLoader__.load({
4358
5057
  function apply(ctx) {
4359
5058
  const namespace = "settings.openai-codex";
4360
5059
  const updater = new OpenAICodexUpdateStore(CODEX_CONNECT_VERSION);
5060
+ const account = new OpenAICodexAccountStore();
5061
+ ctx.effect(() => () => {
5062
+ account.dispose();
5063
+ }, "dsh-codex-connect: account observation");
4361
5064
  ctx.effect(() => {
4362
5065
  updater.refresh();
4363
5066
  return () => {
@@ -4379,9 +5082,20 @@ window.__ModuleLoader__.load({
4379
5082
  inject: () => ({
4380
5083
  t,
4381
5084
  configScope,
4382
- updater
5085
+ updater,
5086
+ account
4383
5087
  })
4384
5088
  }, OpenAICodexPluginCard));
5089
+ ctx.slots.inject("settings.models.footer", () => ctx.slots.register({
5090
+ name: "settings.models.footer",
5091
+ id: "dsh-codex-connect-account",
5092
+ order: 100,
5093
+ inject: () => ({
5094
+ t,
5095
+ account,
5096
+ configScope
5097
+ })
5098
+ }, OpenAICodexModelsCard));
4385
5099
  ctx.slots.inject("shell.overlay", () => ctx.slots.register({
4386
5100
  name: "shell.overlay",
4387
5101
  id: "dsh-codex-connect-update",