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

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,9 @@ 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"
869
1315
  ];
870
1316
  function isHighlightKind(value) {
871
1317
  return typeof value === "string" && HIGHLIGHT_KINDS.includes(value);
@@ -1255,7 +1701,7 @@ window.__ModuleLoader__.load({
1255
1701
  gap: 16,
1256
1702
  flexWrap: "wrap"
1257
1703
  };
1258
- const buttonStyle$1 = {
1704
+ const buttonStyle$2 = {
1259
1705
  display: "inline-flex",
1260
1706
  alignItems: "center",
1261
1707
  justifyContent: "center",
@@ -1273,10 +1719,10 @@ window.__ModuleLoader__.load({
1273
1719
  cursor: "pointer"
1274
1720
  };
1275
1721
  const primaryButtonStyle$1 = {
1276
- ...buttonStyle$1,
1277
- borderColor: "var(--dsw-alias-brand-primary)",
1278
- background: "var(--dsw-alias-brand-primary)",
1279
- color: "white"
1722
+ ...buttonStyle$2,
1723
+ borderColor: "var(--dsw-alias-button-primary-fill)",
1724
+ background: "var(--dsw-alias-button-primary-fill)",
1725
+ color: "var(--dsw-alias-label-primary-foreground)"
1280
1726
  };
1281
1727
  const textButtonStyle = {
1282
1728
  border: 0,
@@ -1370,7 +1816,9 @@ window.__ModuleLoader__.load({
1370
1816
  "image-generation": "updateHighlightImageGeneration",
1371
1817
  "oauth-history": "updateHighlightOauthHistory",
1372
1818
  "model-visibility": "updateHighlightModelVisibility",
1373
- "proxy-connection": "updateHighlightProxyConnection"
1819
+ "proxy-connection": "updateHighlightProxyConnection",
1820
+ "models-account": "updateHighlightModelsAccount",
1821
+ "context-budget": "updateHighlightContextBudget"
1374
1822
  };
1375
1823
  const compatibilityTitleKeys = {
1376
1824
  compatible: "compatibilityCompatibleTitle",
@@ -1574,7 +2022,7 @@ window.__ModuleLoader__.load({
1574
2022
  children: compatibilityWarning ? t("updateHeading") : available ? t("newVersionAvailable", { version: snapshot.latestVersion }) : t("updateHeading")
1575
2023
  }), overlay ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1576
2024
  type: "button",
1577
- style: buttonStyle$1,
2025
+ style: buttonStyle$2,
1578
2026
  "aria-label": t("dismissUpdate"),
1579
2027
  onClick: () => {
1580
2028
  if (noticeKey !== void 0) updater.dismiss(noticeKey);
@@ -1663,7 +2111,7 @@ window.__ModuleLoader__.load({
1663
2111
  children: agentPrompt
1664
2112
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1665
2113
  type: "button",
1666
- style: buttonStyle$1,
2114
+ style: buttonStyle$2,
1667
2115
  onClick: () => {
1668
2116
  copy();
1669
2117
  },
@@ -1726,7 +2174,7 @@ window.__ModuleLoader__.load({
1726
2174
  },
1727
2175
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1728
2176
  type: "button",
1729
- style: buttonStyle$1,
2177
+ style: buttonStyle$2,
1730
2178
  disabled: snapshot.status === "checking",
1731
2179
  onClick: () => {
1732
2180
  updater.refresh(true);
@@ -1754,8 +2202,6 @@ window.__ModuleLoader__.load({
1754
2202
  //#endregion
1755
2203
  //#region src/client/OpenAICodexSettings.tsx
1756
2204
  /** 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
2205
  const pageStyle = {
1760
2206
  display: "flex",
1761
2207
  flexDirection: "column",
@@ -1811,7 +2257,7 @@ window.__ModuleLoader__.load({
1811
2257
  fontWeight: 500,
1812
2258
  color: "var(--dsw-alias-label-primary)"
1813
2259
  };
1814
- const buttonStyle = {
2260
+ const buttonStyle$1 = {
1815
2261
  boxSizing: "border-box",
1816
2262
  minHeight: 34,
1817
2263
  padding: "6px 14px",
@@ -1824,10 +2270,10 @@ window.__ModuleLoader__.load({
1824
2270
  cursor: "pointer"
1825
2271
  };
1826
2272
  const primaryButtonStyle = {
1827
- ...buttonStyle,
1828
- borderColor: "var(--dsw-alias-brand-primary)",
1829
- background: "var(--dsw-alias-brand-primary)",
1830
- color: "white"
2273
+ ...buttonStyle$1,
2274
+ borderColor: "var(--dsw-alias-button-primary-fill)",
2275
+ background: "var(--dsw-alias-button-primary-fill)",
2276
+ color: "var(--dsw-alias-label-primary-foreground)"
1831
2277
  };
1832
2278
  const errorStyle$1 = {
1833
2279
  ...bodyStyle,
@@ -1931,15 +2377,16 @@ window.__ModuleLoader__.load({
1931
2377
  ]
1932
2378
  });
1933
2379
  }
1934
- function UsageLimits({ usage, quotaError, t }) {
2380
+ /** Quota rows shared by Models and Plugin settings. */
2381
+ function UsageLimits({ usage, quotaError, t, heading = true }) {
1935
2382
  const hasData = usage.rateLimits.length > 0 || usage.credits !== void 0 || usage.individualLimit !== void 0;
1936
2383
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1937
2384
  style: quotaListStyle,
1938
2385
  children: [
1939
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
2386
+ heading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
1940
2387
  style: quotaTitleStyle,
1941
2388
  children: t("usageLimits")
1942
- }),
2389
+ }) : null,
1943
2390
  usage.rateLimits.map((limit) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1944
2391
  style: quotaGroupStyle,
1945
2392
  children: limit.windows.map((window) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(QuotaBar, {
@@ -1973,6 +2420,7 @@ window.__ModuleLoader__.load({
1973
2420
  ]
1974
2421
  });
1975
2422
  }
2423
+ /** Account indicator colors shared by the compact row and expanded controls. */
1976
2424
  function dotStyle(status) {
1977
2425
  return {
1978
2426
  width: 9,
@@ -1982,133 +2430,157 @@ window.__ModuleLoader__.load({
1982
2430
  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
2431
  };
1984
2432
  }
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 }
2433
+ /** Non-sensitive account state label for either settings presentation. */
2434
+ function accountStatusLabel(status, t) {
2435
+ return t({
2436
+ "signed-in": "signedIn",
2437
+ loading: "loadingAccount",
2438
+ "signing-in": "signingIn",
2439
+ "reauth-required": "reauthRequired",
2440
+ "remote-web-origin-not-trusted": "remoteOriginTitle",
2441
+ error: "requestFailed",
2442
+ "signed-out": "signedOut"
2443
+ }[status]);
2444
+ }
2445
+ /** Shared OAuth actions; Models uses shorter, task-oriented labels. */
2446
+ function AccountActions({ t, store, snapshot, compact = false }) {
2447
+ const { status, busy } = snapshot;
2448
+ if (status.status === "loading" || status.status === "remote-web-origin-not-trusted") return null;
2449
+ if (status.status === "signed-in") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2450
+ type: "button",
2451
+ style: buttonStyle$1,
2452
+ disabled: busy,
2453
+ onClick: () => {
2454
+ store.signOut();
2455
+ },
2456
+ children: busy ? t("working") : t("logout")
2457
+ });
2458
+ if (status.status === "signing-in") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2459
+ style: rowStyle,
2460
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2461
+ type: "button",
2462
+ style: buttonStyle$1,
2463
+ disabled: busy,
2464
+ onClick: () => {
2465
+ store.signIn();
2466
+ },
2467
+ children: busy ? t("working") : t(compact ? "continueAuthorization" : "reopenAuthorization")
2468
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2469
+ type: "button",
2470
+ style: buttonStyle$1,
2471
+ disabled: busy,
2472
+ onClick: () => {
2473
+ store.cancel();
2474
+ },
2475
+ children: t("cancelSignIn")
2476
+ })]
2477
+ });
2478
+ const action = status.status === "error" || status.status === "reauth-required" ? t(compact ? "reauthorize" : "loginAgain") : t(compact ? "authorize" : "login");
2479
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2480
+ type: "button",
2481
+ style: primaryButtonStyle,
2482
+ disabled: busy,
2483
+ onClick: () => {
2484
+ store.signIn();
2485
+ },
2486
+ children: busy ? t("working") : action
1999
2487
  });
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
2488
  }
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);
2489
+ /** Recovery links, errors and trusted-origin guidance in either account entry. */
2490
+ function AccountFeedback({ t, snapshot }) {
2491
+ const { status, loginUrl } = snapshot;
2012
2492
  const [copied, setCopied] = (0, react.useState)(false);
2013
2493
  const [copyFailed, setCopyFailed] = (0, react.useState)(false);
2014
- const [loginUrl, setLoginUrl] = (0, react.useState)();
2015
- const mounted = (0, react.useRef)(true);
2016
2494
  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
2495
  const copyTrustedOriginCommand = async () => {
2083
2496
  setCopyFailed(false);
2084
2497
  try {
2085
2498
  if (navigator.clipboard?.writeText === void 0) throw new Error("clipboard unavailable");
2086
2499
  await navigator.clipboard.writeText(trustedOriginCommand);
2087
- if (mounted.current) setCopied(true);
2500
+ setCopied(true);
2088
2501
  } 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);
2502
+ setCopyFailed(true);
2104
2503
  }
2105
2504
  };
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");
2505
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
2506
+ loginUrl === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2507
+ style: {
2508
+ display: "flex",
2509
+ flexDirection: "column",
2510
+ alignItems: "flex-start",
2511
+ gap: 10
2512
+ },
2513
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2514
+ style: bodyStyle,
2515
+ children: t("authorizationHelp")
2516
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
2517
+ href: loginUrl,
2518
+ target: "_blank",
2519
+ rel: "noopener noreferrer",
2520
+ style: {
2521
+ ...primaryButtonStyle,
2522
+ display: "inline-flex",
2523
+ alignItems: "center",
2524
+ textDecoration: "none"
2525
+ },
2526
+ children: t("openLoginInBrowser")
2527
+ })]
2528
+ }),
2529
+ status.status === "error" || status.status === "reauth-required" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2530
+ style: errorStyle$1,
2531
+ children: status.message
2532
+ }) : null,
2533
+ status.status === "remote-web-origin-not-trusted" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2534
+ style: {
2535
+ display: "flex",
2536
+ flexDirection: "column",
2537
+ gap: 10
2538
+ },
2539
+ children: [
2540
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2541
+ style: errorStyle$1,
2542
+ children: t("remoteOriginDescription")
2543
+ }),
2544
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2545
+ style: bodyStyle,
2546
+ children: t("remoteOriginCommandHelp")
2547
+ }),
2548
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
2549
+ style: commandStyle,
2550
+ children: trustedOriginCommand
2551
+ }),
2552
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2553
+ style: rowStyle,
2554
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2555
+ type: "button",
2556
+ style: buttonStyle$1,
2557
+ onClick: () => {
2558
+ copyTrustedOriginCommand();
2559
+ },
2560
+ children: copied ? t("remoteOriginCopied") : t("remoteOriginCopy")
2561
+ }), copyFailed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2562
+ style: errorStyle$1,
2563
+ children: t("remoteOriginCopyFailed")
2564
+ }) : null]
2565
+ })
2566
+ ]
2567
+ }) : null
2568
+ ] });
2569
+ }
2570
+ /** OpenAI Codex account status and OAuth actions. */
2571
+ function OpenAICodexSettings({ t, configScope, updater, account, embedded = false, accountOnly = false }) {
2572
+ if (t === void 0) throw new Error("OpenAI Codex settings requires its translation function");
2573
+ const [localAccount] = (0, react.useState)(() => new OpenAICodexAccountStore());
2574
+ const store = account ?? localAccount;
2575
+ const snapshot = (0, react.useSyncExternalStore)(store.subscribe, store.getSnapshot);
2576
+ const { status } = snapshot;
2577
+ const titleId = (0, react.useId)();
2578
+ const label = accountStatusLabel(status.status, t);
2107
2579
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
2108
2580
  style: embedded ? embeddedPageStyle : pageStyle,
2109
- ...embedded ? { "aria-label": t("title") } : { "aria-labelledby": "openai-codex-settings-title" },
2581
+ ...embedded ? { "aria-label": t("title") } : { "aria-labelledby": titleId },
2110
2582
  children: [embedded ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
2111
- id: "openai-codex-settings-title",
2583
+ id: titleId,
2112
2584
  style: titleStyle,
2113
2585
  children: t("title")
2114
2586
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
@@ -2120,7 +2592,7 @@ window.__ModuleLoader__.load({
2120
2592
  })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2121
2593
  style: embedded ? embeddedCardStyle : cardStyle$1,
2122
2594
  children: [
2123
- updater === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OpenAICodexUpdateSettings, {
2595
+ accountOnly || updater === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OpenAICodexUpdateSettings, {
2124
2596
  t,
2125
2597
  updater
2126
2598
  }),
@@ -2137,92 +2609,25 @@ window.__ModuleLoader__.load({
2137
2609
  "aria-hidden": "true",
2138
2610
  style: dotStyle(status.status)
2139
2611
  }), /* @__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")
2612
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountActions, {
2613
+ t,
2614
+ store,
2615
+ snapshot
2156
2616
  })]
2157
2617
  }),
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
- })]
2618
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountFeedback, {
2619
+ t,
2620
+ snapshot
2180
2621
  }),
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
2622
  status.status === "signed-in" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageLimits, {
2221
2623
  usage: status.usage,
2222
2624
  ...status.quotaError === void 0 ? {} : { quotaError: status.quotaError },
2223
2625
  t
2224
2626
  }) : null,
2225
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OpenAICodexConfiguration, {
2627
+ accountOnly ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
2628
+ style: bodyStyle,
2629
+ children: t("modelsAccountHelp")
2630
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OpenAICodexConfiguration, {
2226
2631
  t,
2227
2632
  ...configScope === void 0 ? {} : { scope: configScope }
2228
2633
  })
@@ -2300,7 +2705,7 @@ window.__ModuleLoader__.load({
2300
2705
  });
2301
2706
  }
2302
2707
  /** Render account management as one expandable Plugin configuration card. */
2303
- function OpenAICodexPluginCard({ t, configScope, updater }) {
2708
+ function OpenAICodexPluginCard({ t, configScope, updater, account }) {
2304
2709
  if (t === void 0) throw new Error("OpenAI Codex plugin card requires its translation function");
2305
2710
  const [open, setOpen] = (0, react.useState)(false);
2306
2711
  const title = t("title");
@@ -2331,6 +2736,7 @@ window.__ModuleLoader__.load({
2331
2736
  style: cardBodyStyle,
2332
2737
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OpenAICodexSettings, {
2333
2738
  t,
2739
+ ...account === void 0 ? {} : { account },
2334
2740
  ...updater === void 0 ? {} : { updater },
2335
2741
  embedded: true,
2336
2742
  ...configScope === void 0 ? {} : { configScope }
@@ -2761,8 +3167,24 @@ window.__ModuleLoader__.load({
2761
3167
  /** English copy for the OpenAI Codex Plugin configuration card. */
2762
3168
  const en = {
2763
3169
  title: "Codex Connect",
3170
+ modelsProviderName: "Openai-Codex",
3171
+ modelsProviderSupport: "Powered by the Codex Connect plugin.",
2764
3172
  intro: "Use your ChatGPT subscription in dsh, with optional GPT Image generation and no API key.",
2765
3173
  accountHeading: "ChatGPT account",
3174
+ manageAccount: "Manage",
3175
+ authorize: "Authorize",
3176
+ continueAuthorization: "Continue authorization",
3177
+ reauthorize: "Reauthorize",
3178
+ viewQuota: "View quota",
3179
+ hideQuota: "Hide quota",
3180
+ moreSettings: "More settings",
3181
+ moreSettingsTitle: "Codex Connect settings",
3182
+ closeSettings: "Close",
3183
+ settingsSaveHint: "Save applies changes to both settings entries. Closing discards unsaved changes.",
3184
+ reopenAuthorization: "Reopen authorization",
3185
+ cancelSignIn: "Cancel sign-in",
3186
+ authorizationHelp: "You can reopen the authorization page or cancel and start again. Cancelling does not sign out an existing account.",
3187
+ modelsAccountHelp: "Proxy, model visibility, search and image options remain in Settings → Plugins → Codex Connect, or open them here:",
2766
3188
  expand: "Expand settings",
2767
3189
  collapse: "Collapse settings",
2768
3190
  loadingAccount: "Loading account…",
@@ -2810,6 +3232,20 @@ window.__ModuleLoader__.load({
2810
3232
  modelCatalogIntro: "Choose which Codex models appear in model selectors. Existing conversations can continue using a hidden model.",
2811
3233
  modelCatalogLoading: "Loading available models…",
2812
3234
  modelCatalogFailed: "Available models could not be loaded.",
3235
+ modelContext: "Context",
3236
+ contextDefault: "Catalog default",
3237
+ contextCustom: "Custom",
3238
+ contextAdjust: "Adjust",
3239
+ contextHide: "Hide adjustment",
3240
+ contextTokens: "Context length",
3241
+ contextReset: "Restore default",
3242
+ contextSlider: "Adjust context budget",
3243
+ contextMaximum: "Configuration limit",
3244
+ contextLimitSource: "Source: official Codex configuration catalog (2026-08-28)",
3245
+ contextLimitFallback: "No higher configuration limit confirmed; capped at the installed catalog default.",
3246
+ 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.",
3247
+ 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.",
3248
+ contextInvalid: "Enter a whole-number budget within the model’s configuration range, or choose Restore default.",
2813
3249
  capabilitiesHeading: "Optional capabilities",
2814
3250
  capabilitiesIntro: "Choose which extra Codex capabilities this dsh profile may register.",
2815
3251
  networkHeading: "Network connection",
@@ -2945,6 +3381,8 @@ window.__ModuleLoader__.load({
2945
3381
  updateHighlightOauthHistory: "Added safer session-history migration and a sign-in fallback when the account needs to reconnect.",
2946
3382
  updateHighlightModelVisibility: "You can now choose which Codex models appear in model selectors.",
2947
3383
  updateHighlightProxyConnection: "Added opt-in Codex proxy detection with explicit confirmation and a visible direct-connection fallback.",
3384
+ updateHighlightModelsAccount: "Added an Openai-Codex account card in Models settings for authorization, quota, and shared plugin settings.",
3385
+ updateHighlightContextBudget: "Added bounded per-model context budget controls while preserving each catalog default until you save an override.",
2948
3386
  viewTechnicalDetails: "View technical details",
2949
3387
  hideTechnicalDetails: "Hide technical details",
2950
3388
  technicalDetailsHeading: "Technical changes",
@@ -2970,8 +3408,24 @@ window.__ModuleLoader__.load({
2970
3408
  /** Chinese copy for the OpenAI Codex Plugin configuration card. */
2971
3409
  const zh = {
2972
3410
  title: "Codex Connect",
3411
+ modelsProviderName: "Openai-Codex",
3412
+ modelsProviderSupport: "由 Codex Connect 插件提供支持。",
2973
3413
  intro: "使用 ChatGPT 订阅在 dsh 中调用模型,并可使用 GPT Image 生成图片,无需 API Key。",
2974
3414
  accountHeading: "ChatGPT 账户",
3415
+ manageAccount: "管理",
3416
+ authorize: "授权",
3417
+ continueAuthorization: "继续授权",
3418
+ reauthorize: "重新授权",
3419
+ viewQuota: "查看额度",
3420
+ hideQuota: "收起额度",
3421
+ moreSettings: "更多设置",
3422
+ moreSettingsTitle: "Codex Connect 设置",
3423
+ closeSettings: "关闭",
3424
+ settingsSaveHint: "保存后两处设置同步生效;关闭将放弃未保存的修改。",
3425
+ reopenAuthorization: "重新打开授权",
3426
+ cancelSignIn: "取消登录",
3427
+ authorizationHelp: "可以重新打开授权页,或取消后重试。取消登录不会退出已有账户。",
3428
+ modelsAccountHelp: "代理、模型显示、搜索和图片选项仍可在“设置 → 插件 → Codex Connect”中管理,也可直接打开:",
2975
3429
  expand: "展开设置",
2976
3430
  collapse: "折叠设置",
2977
3431
  loadingAccount: "正在加载账户信息…",
@@ -3019,6 +3473,20 @@ window.__ModuleLoader__.load({
3019
3473
  modelCatalogIntro: "选择要在模型选择器中显示的 Codex 模型;隐藏模型后,已有会话仍可继续使用。",
3020
3474
  modelCatalogLoading: "正在加载可用模型…",
3021
3475
  modelCatalogFailed: "无法加载可用模型。",
3476
+ modelContext: "上下文",
3477
+ contextDefault: "目录默认",
3478
+ contextCustom: "自定义",
3479
+ contextAdjust: "调整",
3480
+ contextHide: "收起调整",
3481
+ contextTokens: "上下文长度",
3482
+ contextReset: "恢复默认",
3483
+ contextSlider: "调整上下文预算",
3484
+ contextMaximum: "配置上限",
3485
+ contextLimitSource: "依据:Codex 官方配置目录(2026-08-28)",
3486
+ contextLimitFallback: "尚未确认更高配置上限,暂以已安装目录的默认值为上限。",
3487
+ contextAboveDefault: "已超过目录默认值:请求可能消耗更多额度,或被当前账号、通道拒绝。本插件未对该范围进行容量实测。",
3488
+ contextWarning: "调整的是本地上下文预算,不会扩大服务端容量;设置过高可能导致请求失败。隐藏模型会保留其预算,修改在保存后生效。",
3489
+ contextInvalid: "请输入模型配置范围内的整数预算,或点击“恢复默认”。",
3022
3490
  capabilitiesHeading: "可选能力",
3023
3491
  capabilitiesIntro: "选择允许此 dsh profile 注册哪些额外的 Codex 能力。",
3024
3492
  networkHeading: "网络连接",
@@ -3154,6 +3622,8 @@ window.__ModuleLoader__.load({
3154
3622
  updateHighlightOauthHistory: "增加更安全的会话历史迁移,并在账号需要重新登录时提供备用登录路径。",
3155
3623
  updateHighlightModelVisibility: "现在可以选择哪些 Codex 模型显示在模型选择菜单中。",
3156
3624
  updateHighlightProxyConnection: "增加可选的 Codex 代理检测,启用前需要明确确认,并始终保留直连回退入口。",
3625
+ updateHighlightModelsAccount: "在模型设置中增加 Openai-Codex 账户卡,用于授权、查看额度和打开共享插件设置。",
3626
+ updateHighlightContextBudget: "增加有范围限制的逐模型上下文预算设置;保存覆盖值前继续使用各模型的目录默认值。",
3157
3627
  viewTechnicalDetails: "查看完整技术说明",
3158
3628
  hideTechnicalDetails: "收起技术说明",
3159
3629
  technicalDetailsHeading: "技术变更",
@@ -3270,7 +3740,7 @@ window.__ModuleLoader__.load({
3270
3740
  //#region src/client/CodexImageGallery.tsx
3271
3741
  /** Self-contained result-image gallery for the tool-call view.
3272
3742
  *
3273
- * DSH 0.1.1-rc.2 keeps the attachment package's React atoms behind its
3743
+ * DSH keeps the attachment package's React atoms behind its
3274
3744
  * conversation slots. A tool-call view cannot render those slots, so this
3275
3745
  * gallery owns only the durable-image presentation it needs and receives all
3276
3746
  * stateful work through props.
@@ -3441,7 +3911,6 @@ window.__ModuleLoader__.load({
3441
3911
  open: true,
3442
3912
  onClose,
3443
3913
  title: labels.dialog,
3444
- closeLabel: labels.close,
3445
3914
  headless: true,
3446
3915
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
3447
3916
  ref: lightbox,
@@ -4340,7 +4809,232 @@ window.__ModuleLoader__.load({
4340
4809
  }
4341
4810
  //#endregion
4342
4811
  //#region src/version.ts
4343
- const CODEX_CONNECT_VERSION = "0.1.0-alpha.4.21";
4812
+ const CODEX_CONNECT_VERSION = "0.1.0-alpha.4.22";
4813
+ //#endregion
4814
+ //#region src/client/OpenAICodexModelsCard.tsx
4815
+ /** Compact Models account entry with quota disclosure and shared configuration. */
4816
+ const buttonStyle = {
4817
+ padding: "6px 14px",
4818
+ border: "1px solid var(--dsw-alias-border-l2)",
4819
+ borderRadius: 999,
4820
+ background: "transparent",
4821
+ color: "inherit",
4822
+ font: "inherit",
4823
+ fontSize: 14,
4824
+ cursor: "pointer"
4825
+ };
4826
+ const secondaryStyle = {
4827
+ fontSize: 12,
4828
+ lineHeight: "18px",
4829
+ color: "var(--dsw-alias-label-secondary)"
4830
+ };
4831
+ /** Native modality contains keyboard focus and restores it to More settings on close. */
4832
+ function ConfigurationDialog({ t, configScope, onClose }) {
4833
+ const dialog = (0, react.useRef)(null);
4834
+ const titleId = (0, react.useId)();
4835
+ (0, react.useEffect)(() => {
4836
+ const element = dialog.current;
4837
+ element?.showModal();
4838
+ return () => {
4839
+ element?.close();
4840
+ };
4841
+ }, []);
4842
+ const close = () => {
4843
+ dialog.current?.close();
4844
+ onClose();
4845
+ };
4846
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dialog", {
4847
+ ref: dialog,
4848
+ "aria-labelledby": titleId,
4849
+ onCancel: (event) => {
4850
+ event.preventDefault();
4851
+ close();
4852
+ },
4853
+ onKeyDown: (event) => {
4854
+ if (event.key !== "Escape") return;
4855
+ event.stopPropagation();
4856
+ event.preventDefault();
4857
+ close();
4858
+ },
4859
+ style: {
4860
+ boxSizing: "border-box",
4861
+ width: "min(720px, calc(100vw - 32px))",
4862
+ maxHeight: "calc(100dvh - 32px)",
4863
+ overflowY: "auto",
4864
+ padding: 20,
4865
+ border: "1px solid var(--dsw-alias-border-l2)",
4866
+ borderRadius: 12,
4867
+ background: "var(--dsw-alias-bg-layer-1, white)",
4868
+ color: "var(--dsw-alias-label-primary)",
4869
+ margin: "auto"
4870
+ },
4871
+ children: [
4872
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4873
+ style: {
4874
+ display: "flex",
4875
+ alignItems: "center",
4876
+ justifyContent: "space-between",
4877
+ gap: 12
4878
+ },
4879
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
4880
+ id: titleId,
4881
+ style: {
4882
+ margin: 0,
4883
+ fontSize: 18
4884
+ },
4885
+ children: t("moreSettingsTitle")
4886
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4887
+ type: "button",
4888
+ style: buttonStyle,
4889
+ onClick: close,
4890
+ children: t("closeSettings")
4891
+ })]
4892
+ }),
4893
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
4894
+ style: secondaryStyle,
4895
+ children: t("settingsSaveHint")
4896
+ }),
4897
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OpenAICodexConfiguration, {
4898
+ t,
4899
+ ...configScope === void 0 ? {} : { scope: configScope }
4900
+ })
4901
+ ]
4902
+ });
4903
+ }
4904
+ function OpenAICodexModelsCard({ t, account, configScope }) {
4905
+ const [expanded, setExpanded] = (0, react.useState)(false);
4906
+ const [settingsOpen, setSettingsOpen] = (0, react.useState)(false);
4907
+ const detailsId = (0, react.useId)();
4908
+ const snapshot = (0, react.useSyncExternalStore)(account.subscribe, account.getSnapshot);
4909
+ const { status } = snapshot;
4910
+ const label = accountStatusLabel(status.status, t);
4911
+ (0, react.useEffect)(() => {
4912
+ if (status.status !== "signed-in") setExpanded(false);
4913
+ }, [status.status]);
4914
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4915
+ style: {
4916
+ border: "1px solid var(--dsw-alias-border-l2)",
4917
+ borderRadius: 12,
4918
+ padding: "12px 14px",
4919
+ color: "var(--dsw-alias-label-primary)"
4920
+ },
4921
+ children: [
4922
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4923
+ style: {
4924
+ display: "flex",
4925
+ alignItems: "center",
4926
+ flexWrap: "wrap",
4927
+ gap: 10
4928
+ },
4929
+ children: [
4930
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4931
+ style: {
4932
+ fontSize: 14,
4933
+ lineHeight: "22px",
4934
+ fontWeight: 500
4935
+ },
4936
+ children: t("modelsProviderName")
4937
+ }),
4938
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4939
+ "aria-hidden": "true",
4940
+ style: {
4941
+ ...dotStyle(status.status),
4942
+ width: 8,
4943
+ height: 8
4944
+ }
4945
+ }),
4946
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4947
+ role: "status",
4948
+ style: {
4949
+ ...secondaryStyle,
4950
+ flex: 1
4951
+ },
4952
+ children: label
4953
+ }),
4954
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4955
+ style: {
4956
+ display: "flex",
4957
+ flexWrap: "wrap",
4958
+ gap: 8,
4959
+ marginLeft: "auto"
4960
+ },
4961
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountActions, {
4962
+ t,
4963
+ store: account,
4964
+ snapshot,
4965
+ compact: true
4966
+ }), status.status === "signed-in" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4967
+ type: "button",
4968
+ "aria-expanded": expanded,
4969
+ "aria-controls": detailsId,
4970
+ onClick: () => {
4971
+ setExpanded(!expanded);
4972
+ },
4973
+ style: buttonStyle,
4974
+ children: t(expanded ? "hideQuota" : "viewQuota")
4975
+ })]
4976
+ })
4977
+ ]
4978
+ }),
4979
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4980
+ style: {
4981
+ ...secondaryStyle,
4982
+ marginTop: 4
4983
+ },
4984
+ children: t("modelsProviderSupport")
4985
+ }),
4986
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountFeedback, {
4987
+ t,
4988
+ snapshot
4989
+ }),
4990
+ expanded && status.status === "signed-in" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4991
+ id: detailsId,
4992
+ style: {
4993
+ borderTop: "1px solid var(--dsw-alias-border-l2)",
4994
+ marginTop: 12,
4995
+ paddingTop: 12
4996
+ },
4997
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageLimits, {
4998
+ t,
4999
+ usage: status.usage,
5000
+ heading: false,
5001
+ ...status.quotaError === void 0 ? {} : { quotaError: status.quotaError }
5002
+ })
5003
+ }),
5004
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5005
+ style: {
5006
+ ...secondaryStyle,
5007
+ marginTop: 12
5008
+ },
5009
+ children: [
5010
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("modelsAccountHelp") }),
5011
+ " ",
5012
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5013
+ type: "button",
5014
+ onClick: () => {
5015
+ setSettingsOpen(true);
5016
+ },
5017
+ style: {
5018
+ ...buttonStyle,
5019
+ padding: 0,
5020
+ border: 0,
5021
+ fontSize: "inherit",
5022
+ textDecoration: "underline"
5023
+ },
5024
+ children: t("moreSettings")
5025
+ })
5026
+ ]
5027
+ }),
5028
+ settingsOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConfigurationDialog, {
5029
+ t,
5030
+ ...configScope === void 0 ? {} : { configScope },
5031
+ onClose: () => {
5032
+ setSettingsOpen(false);
5033
+ }
5034
+ })
5035
+ ]
5036
+ });
5037
+ }
4344
5038
  //#endregion
4345
5039
  //#region src/client/index.tsx
4346
5040
  /** Stable browser-plugin name. */
@@ -4358,6 +5052,10 @@ window.__ModuleLoader__.load({
4358
5052
  function apply(ctx) {
4359
5053
  const namespace = "settings.openai-codex";
4360
5054
  const updater = new OpenAICodexUpdateStore(CODEX_CONNECT_VERSION);
5055
+ const account = new OpenAICodexAccountStore();
5056
+ ctx.effect(() => () => {
5057
+ account.dispose();
5058
+ }, "dsh-codex-connect: account observation");
4361
5059
  ctx.effect(() => {
4362
5060
  updater.refresh();
4363
5061
  return () => {
@@ -4379,9 +5077,20 @@ window.__ModuleLoader__.load({
4379
5077
  inject: () => ({
4380
5078
  t,
4381
5079
  configScope,
4382
- updater
5080
+ updater,
5081
+ account
4383
5082
  })
4384
5083
  }, OpenAICodexPluginCard));
5084
+ ctx.slots.inject("settings.models.footer", () => ctx.slots.register({
5085
+ name: "settings.models.footer",
5086
+ id: "dsh-codex-connect-account",
5087
+ order: 100,
5088
+ inject: () => ({
5089
+ t,
5090
+ account,
5091
+ configScope
5092
+ })
5093
+ }, OpenAICodexModelsCard));
4385
5094
  ctx.slots.inject("shell.overlay", () => ctx.slots.register({
4386
5095
  name: "shell.overlay",
4387
5096
  id: "dsh-codex-connect-update",