codex-overleaf-link 2.0.0 → 2.1.1

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.
Files changed (119) hide show
  1. package/README.md +24 -23
  2. package/extension/bootstrap/manifest.template.json +2 -1
  3. package/extension/runtime-manifest.json +12 -6
  4. package/extension/src/background.js +7 -0
  5. package/extension/src/content/composerPanel.js +13 -3
  6. package/extension/src/content/contentRuntime.js +129 -64
  7. package/extension/src/content/contextTray.js +173 -9
  8. package/extension/src/content/markdownText.js +16 -1
  9. package/extension/src/content/mathText.js +233 -0
  10. package/extension/src/content/modelPicker.js +117 -106
  11. package/extension/src/content/modelPickerSupport.js +115 -0
  12. package/extension/src/content/nativeChannel.js +20 -1
  13. package/extension/src/content/panelMaintenance.js +2 -0
  14. package/extension/src/content/projectSettingsCoordinator.js +2 -0
  15. package/extension/src/content/providerSettingsCoordinator.js +413 -0
  16. package/extension/src/content/providerSettingsDialog.js +1006 -0
  17. package/extension/src/content/runTimelineView.js +24 -5
  18. package/extension/src/content/settingsPanel.js +33 -2
  19. package/extension/src/content/writebackOrchestrator.js +3 -1
  20. package/extension/src/page/overleafProjectSnapshot.js +29 -33
  21. package/extension/src/page/snapshotRouter.js +75 -8
  22. package/extension/src/page/writebackRouter.js +20 -33
  23. package/extension/src/pageBridge.js +1 -1
  24. package/extension/src/shared/agentTranscript.js +130 -0
  25. package/extension/src/shared/compatibility.js +3 -2
  26. package/extension/src/shared/failureReasons.js +75 -0
  27. package/extension/src/shared/i18n.js +22 -0
  28. package/extension/src/shared/models.js +1 -0
  29. package/extension/src/shared/providerProfiles.js +283 -0
  30. package/extension/src/shared/sessionState.js +12 -2
  31. package/extension/src/shared/storageDb.js +1 -1
  32. package/extension/styles/panel.css +689 -1
  33. package/extension/vendor/katex/LICENSE +21 -0
  34. package/extension/vendor/katex/VERSION +2 -0
  35. package/extension/vendor/katex/fonts/KaTeX_AMS-Regular.ttf +0 -0
  36. package/extension/vendor/katex/fonts/KaTeX_AMS-Regular.woff +0 -0
  37. package/extension/vendor/katex/fonts/KaTeX_AMS-Regular.woff2 +0 -0
  38. package/extension/vendor/katex/fonts/KaTeX_Caligraphic-Bold.ttf +0 -0
  39. package/extension/vendor/katex/fonts/KaTeX_Caligraphic-Bold.woff +0 -0
  40. package/extension/vendor/katex/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
  41. package/extension/vendor/katex/fonts/KaTeX_Caligraphic-Regular.ttf +0 -0
  42. package/extension/vendor/katex/fonts/KaTeX_Caligraphic-Regular.woff +0 -0
  43. package/extension/vendor/katex/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
  44. package/extension/vendor/katex/fonts/KaTeX_Fraktur-Bold.ttf +0 -0
  45. package/extension/vendor/katex/fonts/KaTeX_Fraktur-Bold.woff +0 -0
  46. package/extension/vendor/katex/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
  47. package/extension/vendor/katex/fonts/KaTeX_Fraktur-Regular.ttf +0 -0
  48. package/extension/vendor/katex/fonts/KaTeX_Fraktur-Regular.woff +0 -0
  49. package/extension/vendor/katex/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
  50. package/extension/vendor/katex/fonts/KaTeX_Main-Bold.ttf +0 -0
  51. package/extension/vendor/katex/fonts/KaTeX_Main-Bold.woff +0 -0
  52. package/extension/vendor/katex/fonts/KaTeX_Main-Bold.woff2 +0 -0
  53. package/extension/vendor/katex/fonts/KaTeX_Main-BoldItalic.ttf +0 -0
  54. package/extension/vendor/katex/fonts/KaTeX_Main-BoldItalic.woff +0 -0
  55. package/extension/vendor/katex/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
  56. package/extension/vendor/katex/fonts/KaTeX_Main-Italic.ttf +0 -0
  57. package/extension/vendor/katex/fonts/KaTeX_Main-Italic.woff +0 -0
  58. package/extension/vendor/katex/fonts/KaTeX_Main-Italic.woff2 +0 -0
  59. package/extension/vendor/katex/fonts/KaTeX_Main-Regular.ttf +0 -0
  60. package/extension/vendor/katex/fonts/KaTeX_Main-Regular.woff +0 -0
  61. package/extension/vendor/katex/fonts/KaTeX_Main-Regular.woff2 +0 -0
  62. package/extension/vendor/katex/fonts/KaTeX_Math-BoldItalic.ttf +0 -0
  63. package/extension/vendor/katex/fonts/KaTeX_Math-BoldItalic.woff +0 -0
  64. package/extension/vendor/katex/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
  65. package/extension/vendor/katex/fonts/KaTeX_Math-Italic.ttf +0 -0
  66. package/extension/vendor/katex/fonts/KaTeX_Math-Italic.woff +0 -0
  67. package/extension/vendor/katex/fonts/KaTeX_Math-Italic.woff2 +0 -0
  68. package/extension/vendor/katex/fonts/KaTeX_SansSerif-Bold.ttf +0 -0
  69. package/extension/vendor/katex/fonts/KaTeX_SansSerif-Bold.woff +0 -0
  70. package/extension/vendor/katex/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
  71. package/extension/vendor/katex/fonts/KaTeX_SansSerif-Italic.ttf +0 -0
  72. package/extension/vendor/katex/fonts/KaTeX_SansSerif-Italic.woff +0 -0
  73. package/extension/vendor/katex/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
  74. package/extension/vendor/katex/fonts/KaTeX_SansSerif-Regular.ttf +0 -0
  75. package/extension/vendor/katex/fonts/KaTeX_SansSerif-Regular.woff +0 -0
  76. package/extension/vendor/katex/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
  77. package/extension/vendor/katex/fonts/KaTeX_Script-Regular.ttf +0 -0
  78. package/extension/vendor/katex/fonts/KaTeX_Script-Regular.woff +0 -0
  79. package/extension/vendor/katex/fonts/KaTeX_Script-Regular.woff2 +0 -0
  80. package/extension/vendor/katex/fonts/KaTeX_Size1-Regular.ttf +0 -0
  81. package/extension/vendor/katex/fonts/KaTeX_Size1-Regular.woff +0 -0
  82. package/extension/vendor/katex/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  83. package/extension/vendor/katex/fonts/KaTeX_Size2-Regular.ttf +0 -0
  84. package/extension/vendor/katex/fonts/KaTeX_Size2-Regular.woff +0 -0
  85. package/extension/vendor/katex/fonts/KaTeX_Size2-Regular.woff2 +0 -0
  86. package/extension/vendor/katex/fonts/KaTeX_Size3-Regular.ttf +0 -0
  87. package/extension/vendor/katex/fonts/KaTeX_Size3-Regular.woff +0 -0
  88. package/extension/vendor/katex/fonts/KaTeX_Size3-Regular.woff2 +0 -0
  89. package/extension/vendor/katex/fonts/KaTeX_Size4-Regular.ttf +0 -0
  90. package/extension/vendor/katex/fonts/KaTeX_Size4-Regular.woff +0 -0
  91. package/extension/vendor/katex/fonts/KaTeX_Size4-Regular.woff2 +0 -0
  92. package/extension/vendor/katex/fonts/KaTeX_Typewriter-Regular.ttf +0 -0
  93. package/extension/vendor/katex/fonts/KaTeX_Typewriter-Regular.woff +0 -0
  94. package/extension/vendor/katex/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
  95. package/extension/vendor/katex/katex.min.css +1 -0
  96. package/extension/vendor/katex/katex.min.js +1 -0
  97. package/native-host/src/anthropicBridgeRequest.js +435 -0
  98. package/native-host/src/anthropicBridgeResponse.js +431 -0
  99. package/native-host/src/anthropicMessagesBridge.js +281 -0
  100. package/native-host/src/chatBridgeRequest.js +274 -0
  101. package/native-host/src/chatBridgeResponse.js +594 -0
  102. package/native-host/src/chatCompletionsBridge.js +485 -0
  103. package/native-host/src/codexCommand.js +25 -0
  104. package/native-host/src/codexPromptAssembly.js +9 -0
  105. package/native-host/src/codexProviderLaunch.js +213 -0
  106. package/native-host/src/codexProviderTest.js +196 -0
  107. package/native-host/src/codexSessionRunner.js +29 -25
  108. package/native-host/src/managedInstall.js +1 -0
  109. package/native-host/src/providerBridgeRoutes.js +11 -0
  110. package/native-host/src/providerModelCatalog.js +107 -0
  111. package/native-host/src/providerProfile.js +454 -0
  112. package/native-host/src/providerReasoning.js +235 -0
  113. package/native-host/src/providerRedaction.js +18 -0
  114. package/native-host/src/providerRuntime.js +365 -0
  115. package/native-host/src/providerStore.js +691 -0
  116. package/native-host/src/readProgressGuard.js +360 -0
  117. package/native-host/src/taskRunnerRuntime.js +39 -3
  118. package/native-host/src/updateArchive.js +1 -0
  119. package/package.json +2 -1
@@ -62,6 +62,21 @@
62
62
  }
63
63
 
64
64
  function buildMarkdownInlineNodes(value) {
65
+ const source = String(value || '');
66
+ const mathText = typeof window !== 'undefined'
67
+ ? window.CodexOverleafMathText
68
+ : null;
69
+ if (!mathText?.buildMathNodes) {
70
+ return buildMarkdownInlineNodesWithoutMath(source);
71
+ }
72
+ return mathText.buildMathNodes(source, {
73
+ document,
74
+ katex: window.katex,
75
+ renderText: buildMarkdownInlineNodesWithoutMath
76
+ });
77
+ }
78
+
79
+ function buildMarkdownInlineNodesWithoutMath(value) {
65
80
  const source = String(value || '');
66
81
  const nodes = [];
67
82
  let index = 0;
@@ -587,7 +602,7 @@
587
602
  }
588
603
 
589
604
  function isMarkdownHeadingLine(line) {
590
- return /^\s*(#{1,6}\s+\S|\*\*[^*]+\*\*:?\s*)$/.test(line);
605
+ return /^\s*(#{1,6}\s+\S.*|\*\*[^*]+\*\*:?\s*)$/.test(line);
591
606
  }
592
607
 
593
608
 
@@ -0,0 +1,233 @@
1
+ (function initCodexOverleafMathText(root) {
2
+ 'use strict';
3
+
4
+ const DELIMITERS = Object.freeze([
5
+ Object.freeze({ open: '$$', close: '$$', display: true, singleDollar: false }),
6
+ Object.freeze({ open: '\\[', close: '\\]', display: true, singleDollar: false }),
7
+ Object.freeze({ open: '\\(', close: '\\)', display: false, singleDollar: false }),
8
+ Object.freeze({ open: '$', close: '$', display: false, singleDollar: true })
9
+ ]);
10
+
11
+ function parseMathSegments(value) {
12
+ const source = String(value || '');
13
+ if (!source) {
14
+ return [];
15
+ }
16
+ const codeRanges = collectInlineCodeRanges(source);
17
+ const segments = [];
18
+ let cursor = 0;
19
+ while (cursor < source.length) {
20
+ const match = findNextMath(source, cursor, codeRanges);
21
+ if (!match) {
22
+ segments.push({ type: 'text', value: source.slice(cursor) });
23
+ break;
24
+ }
25
+ if (match.start > cursor) {
26
+ segments.push({ type: 'text', value: source.slice(cursor, match.start) });
27
+ }
28
+ segments.push({
29
+ type: 'math',
30
+ value: match.value,
31
+ raw: source.slice(match.start, match.end),
32
+ display: match.display
33
+ });
34
+ cursor = match.end;
35
+ }
36
+ return segments;
37
+ }
38
+
39
+ function findNextMath(source, fromIndex, codeRanges) {
40
+ let best = null;
41
+ for (const delimiter of DELIMITERS) {
42
+ const match = findNextDelimitedMath(source, fromIndex, delimiter, codeRanges);
43
+ if (!match) {
44
+ continue;
45
+ }
46
+ if (!best || match.start < best.start
47
+ || (match.start === best.start && delimiter.open.length > best.delimiter.open.length)) {
48
+ best = { ...match, delimiter };
49
+ }
50
+ }
51
+ return best;
52
+ }
53
+
54
+ function findNextDelimitedMath(source, fromIndex, delimiter, codeRanges) {
55
+ let searchFrom = fromIndex;
56
+ while (searchFrom < source.length) {
57
+ const start = source.indexOf(delimiter.open, searchFrom);
58
+ if (start < 0) {
59
+ return null;
60
+ }
61
+ if (!isValidOpening(source, start, delimiter, codeRanges)) {
62
+ searchFrom = start + delimiter.open.length;
63
+ continue;
64
+ }
65
+ const closeStart = findClosingDelimiter(
66
+ source,
67
+ start + delimiter.open.length,
68
+ delimiter,
69
+ codeRanges
70
+ );
71
+ if (closeStart < 0) {
72
+ searchFrom = start + delimiter.open.length;
73
+ continue;
74
+ }
75
+ const value = source.slice(start + delimiter.open.length, closeStart);
76
+ const end = closeStart + delimiter.close.length;
77
+ if (!isValidMathValue(value, delimiter) || overlapsCodeRange(start, end, codeRanges)) {
78
+ searchFrom = start + delimiter.open.length;
79
+ continue;
80
+ }
81
+ return { start, end, value, display: delimiter.display };
82
+ }
83
+ return null;
84
+ }
85
+
86
+ function isValidOpening(source, index, delimiter, codeRanges) {
87
+ if (isEscaped(source, index) || isInsideRange(index, codeRanges)) {
88
+ return false;
89
+ }
90
+ if (!delimiter.singleDollar) {
91
+ return true;
92
+ }
93
+ const previous = source[index - 1] || '';
94
+ const next = source[index + 1] || '';
95
+ return previous !== '$' && next !== '$' && next !== '' && !/\s/.test(next);
96
+ }
97
+
98
+ function findClosingDelimiter(source, fromIndex, delimiter, codeRanges) {
99
+ let searchFrom = fromIndex;
100
+ while (searchFrom < source.length) {
101
+ const index = source.indexOf(delimiter.close, searchFrom);
102
+ if (index < 0) {
103
+ return -1;
104
+ }
105
+ const previous = source[index - 1] || '';
106
+ const next = source[index + delimiter.close.length] || '';
107
+ const invalidSingleDollar = delimiter.singleDollar
108
+ && (previous === '$' || next === '$' || /\s/.test(previous));
109
+ if (!isEscaped(source, index) && !isInsideRange(index, codeRanges) && !invalidSingleDollar) {
110
+ return index;
111
+ }
112
+ searchFrom = index + delimiter.close.length;
113
+ }
114
+ return -1;
115
+ }
116
+
117
+ function isValidMathValue(value, delimiter) {
118
+ const source = String(value || '');
119
+ if (!source.trim()) {
120
+ return false;
121
+ }
122
+ return delimiter.display || !/[\r\n]/.test(source);
123
+ }
124
+
125
+ function collectInlineCodeRanges(source) {
126
+ const ranges = [];
127
+ let index = 0;
128
+ while (index < source.length) {
129
+ if (source[index] !== '`' || isEscaped(source, index)) {
130
+ index++;
131
+ continue;
132
+ }
133
+ let runLength = 1;
134
+ while (source[index + runLength] === '`') {
135
+ runLength++;
136
+ }
137
+ const marker = '`'.repeat(runLength);
138
+ const closeStart = source.indexOf(marker, index + runLength);
139
+ if (closeStart < 0) {
140
+ index += runLength;
141
+ continue;
142
+ }
143
+ ranges.push([index, closeStart + runLength]);
144
+ index = closeStart + runLength;
145
+ }
146
+ return ranges;
147
+ }
148
+
149
+ function isEscaped(source, index) {
150
+ let slashCount = 0;
151
+ for (let cursor = index - 1; cursor >= 0 && source[cursor] === '\\'; cursor--) {
152
+ slashCount++;
153
+ }
154
+ return slashCount % 2 === 1;
155
+ }
156
+
157
+ function isInsideRange(index, ranges) {
158
+ return ranges.some(([start, end]) => index >= start && index < end);
159
+ }
160
+
161
+ function overlapsCodeRange(start, end, ranges) {
162
+ return ranges.some(([rangeStart, rangeEnd]) => rangeStart < end && rangeEnd > start);
163
+ }
164
+
165
+ function buildMathNodes(value, options = {}) {
166
+ const documentRef = options.document || root.document;
167
+ const renderText = typeof options.renderText === 'function'
168
+ ? options.renderText
169
+ : text => [documentRef.createTextNode(text)];
170
+ const nodes = [];
171
+ for (const segment of parseMathSegments(value)) {
172
+ if (segment.type === 'text') {
173
+ if (segment.value) {
174
+ nodes.push(...toNodeArray(renderText(segment.value)));
175
+ }
176
+ continue;
177
+ }
178
+ nodes.push(createMathNode(segment, {
179
+ document: documentRef,
180
+ katex: options.katex || root.katex
181
+ }));
182
+ }
183
+ return nodes;
184
+ }
185
+
186
+ function createMathNode(segment, options = {}) {
187
+ const documentRef = options.document || root.document;
188
+ const node = documentRef.createElement('span');
189
+ node.className = `run-math ${segment.display ? 'run-math--display' : 'run-math--inline'}`;
190
+ node.dataset.mathDisplay = segment.display ? 'block' : 'inline';
191
+ try {
192
+ if (typeof options.katex?.render !== 'function') {
193
+ throw new Error('KaTeX is unavailable');
194
+ }
195
+ options.katex.render(segment.value, node, {
196
+ displayMode: segment.display,
197
+ throwOnError: true,
198
+ trust: false,
199
+ strict: 'warn',
200
+ maxExpand: 200,
201
+ maxSize: 20,
202
+ output: 'htmlAndMathml'
203
+ });
204
+ node.dataset.mathRendered = 'true';
205
+ return node;
206
+ } catch (_error) {
207
+ node.classList.add('run-math--fallback');
208
+ node.dataset.mathRendered = 'false';
209
+ node.textContent = segment.raw;
210
+ return node;
211
+ }
212
+ }
213
+
214
+ function toNodeArray(value) {
215
+ if (Array.isArray(value)) {
216
+ return value;
217
+ }
218
+ if (value === undefined || value === null) {
219
+ return [];
220
+ }
221
+ return [value];
222
+ }
223
+
224
+ const api = {
225
+ buildMathNodes,
226
+ createMathNode,
227
+ parseMathSegments
228
+ };
229
+ root.CodexOverleafMathText = api;
230
+ if (typeof module !== 'undefined' && module.exports) {
231
+ module.exports = api;
232
+ }
233
+ })(typeof window !== 'undefined' ? window : globalThis);
@@ -23,6 +23,14 @@
23
23
  getPanel,
24
24
  getState
25
25
  } = deps;
26
+ const Support = window.CodexOverleafModelPickerSupport;
27
+ const {
28
+ formatCompactModelLabel,
29
+ normalizeModelOptionId,
30
+ normalizeReasoningEffortsForSelect,
31
+ normalizeSpeedTiersForSelect,
32
+ retainSelectedModel
33
+ } = Support;
26
34
 
27
35
  let modelDiscovery = { status: 'fallback', source: 'fallback', fetchedAt: '' };
28
36
 
@@ -78,6 +86,7 @@
78
86
  reasoningSelect.value = choice.dataset.reasoningChoice;
79
87
  } else if (choice.dataset.modelChoice && modelSelect) {
80
88
  modelSelect.value = choice.dataset.modelChoice;
89
+ renderReasoningOptions(getRenderedModelEntries());
81
90
  renderSpeedOptions(getRenderedModelEntries());
82
91
  } else if (choice.dataset.speedChoice && speedSelect) {
83
92
  speedSelect.value = choice.dataset.speedChoice;
@@ -101,9 +110,18 @@
101
110
  const hasDiscoveredModels = response?.ok
102
111
  && Array.isArray(response.result?.models)
103
112
  && response.result.models.length > 0;
113
+ if (!hasDiscoveredModels && !Support.shouldUseBuiltInFallback(response)) {
114
+ applyUnavailableModelOptions(response?.error);
115
+ return;
116
+ }
104
117
  const sourceModels = hasDiscoveredModels ? response.result.models : fallbackModels;
105
- const normalized = modelCatalog.normalizeDiscoveredModels({ models: sourceModels, selectedModel: currentSelectedModel });
106
- renderModelOptions(normalized.models, currentSelectedModel);
118
+ const retainedSelectedModel = retainSelectedModel(sourceModels, currentSelectedModel);
119
+ const normalized = modelCatalog.normalizeDiscoveredModels({ models: sourceModels, selectedModel: retainedSelectedModel });
120
+ if (normalized.usedFallback && response.result?.providerId && response.result.providerId !== 'builtin') {
121
+ applyUnavailableModelOptions({ code: 'provider_model_catalog_invalid', message: 'The active provider returned no usable models.' });
122
+ return;
123
+ }
124
+ renderModelOptions(normalized.models, retainedSelectedModel);
107
125
  modelDiscovery = {
108
126
  status: hasDiscoveredModels && !normalized.usedFallback ? 'discovered' : 'fallback',
109
127
  source: hasDiscoveredModels ? response.result?.source || 'unknown' : 'fallback',
@@ -113,16 +131,43 @@
113
131
  };
114
132
  updateModelDisplay();
115
133
  } catch (error) {
116
- applyFallbackModelOptions(resolveSelectedModel() || selectedModel, error);
134
+ applyUnavailableModelOptions(error);
117
135
  }
118
136
  }
119
137
 
138
+ function applyUnavailableModelOptions(error) {
139
+ const modelSelect = getPanel()?.querySelector('[data-model]');
140
+ if (modelSelect) {
141
+ modelSelect.textContent = '';
142
+ const option = document.createElement('option');
143
+ option.value = '';
144
+ option.textContent = tx('Models unavailable', '模型不可用');
145
+ option.disabled = true;
146
+ modelSelect.append(option);
147
+ modelSelect.value = '';
148
+ modelSelect.disabled = true;
149
+ }
150
+ if (getState()) getState().model = '';
151
+ modelDiscovery = {
152
+ status: 'unavailable',
153
+ source: 'custom-provider',
154
+ fetchedAt: '',
155
+ errorCode: error?.code || '',
156
+ errorMessage: error?.message || (error ? String(error) : '')
157
+ };
158
+ renderReasoningOptions([]);
159
+ renderSpeedOptions([]);
160
+ renderModelConfigChoices();
161
+ updateModelDisplay();
162
+ }
163
+
120
164
  function applyFallbackModelOptions(selectedModel, error) {
121
165
  const modelCatalog = getModelCatalog();
122
166
  const fallbackModels = modelCatalog.FALLBACK_MODELS;
123
167
  const sourceModels = fallbackModels;
124
- const normalized = modelCatalog.normalizeDiscoveredModels({ models: sourceModels, selectedModel });
125
- renderModelOptions(normalized.models, selectedModel);
168
+ const retainedSelectedModel = retainSelectedModel(sourceModels, selectedModel);
169
+ const normalized = modelCatalog.normalizeDiscoveredModels({ models: sourceModels, selectedModel: retainedSelectedModel });
170
+ renderModelOptions(normalized.models, retainedSelectedModel);
126
171
  modelDiscovery = {
127
172
  status: 'fallback',
128
173
  source: 'fallback',
@@ -134,86 +179,7 @@
134
179
  }
135
180
 
136
181
  function getModelCatalog() {
137
- const shared = window.CodexOverleafModels;
138
- if (Array.isArray(shared?.FALLBACK_MODELS) && typeof shared?.normalizeDiscoveredModels === 'function') {
139
- return shared;
140
- }
141
-
142
- return {
143
- FALLBACK_MODELS: buildDomModelCatalogFallback(),
144
- normalizeDiscoveredModels: normalizeDiscoveredModelsFallback
145
- };
146
- }
147
-
148
- function buildDomModelCatalogFallback() {
149
- const modelSelect = getPanel()?.querySelector('[data-model]');
150
- const domModels = Array.from(modelSelect?.options || [])
151
- .map(option => ({
152
- id: normalizeModelOptionId(option.value),
153
- label: option.textContent || option.value
154
- }))
155
- .filter(model => model.id);
156
-
157
- return domModels.length ? domModels : [
158
- { id: 'gpt-5.5', label: 'GPT-5.5' },
159
- { id: 'gpt-5.4', label: 'GPT-5.4' },
160
- { id: 'gpt-5.4-mini', label: 'GPT-5.4 Mini' },
161
- { id: 'gpt-5.3-codex', label: 'GPT-5.3 Codex' },
162
- { id: 'gpt-5.3-codex-spark', label: 'GPT-5.3 Codex Spark' },
163
- { id: 'gpt-5.2', label: 'GPT-5.2' }
164
- ];
165
- }
166
-
167
- function normalizeDiscoveredModelsFallback({ models, selectedModel } = {}) {
168
- const normalized = normalizeModelCatalogEntries(models);
169
- const usedFallback = normalized.length === 0;
170
- const resultModels = usedFallback ? buildDomModelCatalogFallback().map(model => ({ ...model })) : normalized;
171
- const selectedId = normalizeModelOptionId(selectedModel);
172
-
173
- if (selectedId && !resultModels.some(model => model.id === selectedId)) {
174
- resultModels.push({
175
- id: selectedId,
176
- label: `${selectedId} (custom)`,
177
- unverified: true
178
- });
179
- }
180
-
181
- return {
182
- models: resultModels,
183
- usedFallback
184
- };
185
- }
186
-
187
- function normalizeModelCatalogEntries(models) {
188
- if (!Array.isArray(models)) {
189
- return [];
190
- }
191
-
192
- const seen = new Set();
193
- const result = [];
194
-
195
- for (const model of models) {
196
- const id = normalizeModelOptionId(typeof model === 'string' ? model : model?.id);
197
- if (!id || seen.has(id)) {
198
- continue;
199
- }
200
- seen.add(id);
201
- const normalized = {
202
- id,
203
- label: typeof model?.label === 'string' && model.label.length > 0 ? model.label : id,
204
- reasoningEfforts: Array.isArray(model?.reasoningEfforts) ? model.reasoningEfforts.slice() : [],
205
- speedTiers: normalizeSpeedTiersForSelect(model?.speedTiers)
206
- };
207
- if (Object.prototype.hasOwnProperty.call(Object(model), 'defaultReasoningEffort')) {
208
- normalized.defaultReasoningEffort = model.defaultReasoningEffort;
209
- }
210
- if (Object.prototype.hasOwnProperty.call(Object(model), 'defaultSpeedTier')) {
211
- normalized.defaultSpeedTier = model.defaultSpeedTier;
212
- }
213
- result.push(normalized);
214
- }
215
-
216
- return result;
182
+ return Support.getModelCatalog({ getPanel });
217
183
  }
218
184
 
219
185
  function renderModelOptions(models, selectedModel) {
@@ -224,6 +190,7 @@
224
190
 
225
191
  const selectedId = normalizeModelOptionId(selectedModel);
226
192
  modelSelect.textContent = '';
193
+ modelSelect.disabled = false;
227
194
  let renderedSelected = false;
228
195
  let firstModelId = '';
229
196
 
@@ -238,6 +205,9 @@
238
205
  const option = document.createElement('option');
239
206
  option.value = id;
240
207
  option.textContent = model.label;
208
+ option.dataset.reasoningEfforts = normalizeReasoningEffortsForSelect(model.reasoningEfforts).join(',');
209
+ option.dataset.defaultReasoningEffort = model.defaultReasoningEffort || '';
210
+ option.dataset.reasoningPresentation = model.reasoningPresentation || '';
241
211
  option.dataset.speedTiers = normalizeSpeedTiersForSelect(model.speedTiers).join(',');
242
212
  option.dataset.defaultSpeedTier = model.defaultSpeedTier || 'standard';
243
213
  if (model.unverified) {
@@ -253,6 +223,9 @@
253
223
  const option = document.createElement('option');
254
224
  option.value = selectedId;
255
225
  option.textContent = `${selectedId} (custom)`;
226
+ option.dataset.reasoningEfforts = '';
227
+ option.dataset.defaultReasoningEffort = '';
228
+ option.dataset.reasoningPresentation = 'none';
256
229
  option.dataset.speedTiers = 'standard';
257
230
  option.dataset.defaultSpeedTier = 'standard';
258
231
  option.dataset.unverified = 'true';
@@ -265,6 +238,7 @@
265
238
  } else if (firstModelId) {
266
239
  modelSelect.value = firstModelId;
267
240
  }
241
+ renderReasoningOptions(models);
268
242
  renderSpeedOptions(models);
269
243
  renderModelConfigChoices();
270
244
  updateModelDisplay();
@@ -296,6 +270,37 @@
296
270
  renderModelConfigChoices();
297
271
  }
298
272
 
273
+ function renderReasoningOptions(models) {
274
+ const reasoningSelect = getPanel()?.querySelector('[data-reasoning]');
275
+ const modelSelect = getPanel()?.querySelector('[data-model]');
276
+ if (!reasoningSelect || !modelSelect) {
277
+ return;
278
+ }
279
+
280
+ const selectedModel = normalizeModelOptionId(modelSelect.value);
281
+ const model = (Array.isArray(models) ? models : []).find(item => normalizeModelOptionId(item?.id) === selectedModel);
282
+ const presentation = model?.reasoningPresentation || 'effort';
283
+ let efforts = normalizeReasoningEffortsForSelect(model?.reasoningEfforts);
284
+ if (!efforts.length) {
285
+ efforts = presentation === 'none' ? ['none'] : ['low', 'medium', 'high', 'xhigh'];
286
+ }
287
+ const current = reasoningSelect.value || getState()?.reasoningEffort || '';
288
+ const fallback = efforts.includes(model?.defaultReasoningEffort)
289
+ ? model.defaultReasoningEffort
290
+ : efforts.includes('high') ? 'high' : efforts[0];
291
+
292
+ reasoningSelect.textContent = '';
293
+ for (const effort of efforts) {
294
+ const option = document.createElement('option');
295
+ option.value = effort;
296
+ option.textContent = formatReasoningEffortLabel(effort, presentation);
297
+ reasoningSelect.append(option);
298
+ }
299
+ reasoningSelect.value = efforts.includes(current) ? current : fallback;
300
+ reasoningSelect.disabled = presentation === 'none';
301
+ reasoningSelect.dataset.presentation = presentation;
302
+ }
303
+
299
304
  function renderModelConfigChoices() {
300
305
  renderReasoningChoices();
301
306
  renderModelChoices();
@@ -310,10 +315,15 @@
310
315
  return;
311
316
  }
312
317
  list.textContent = '';
318
+ const hidden = reasoningSelect.dataset.presentation === 'none';
319
+ list.hidden = hidden;
320
+ if (list.nextElementSibling?.classList?.contains('codex-model-config-divider')) {
321
+ list.nextElementSibling.hidden = hidden;
322
+ }
313
323
  for (const option of Array.from(reasoningSelect.options || [])) {
314
324
  list.append(createModelConfigChoice({
315
325
  value: option.value,
316
- label: formatReasoningEffortLabel(option.value),
326
+ label: option.textContent || formatReasoningEffortLabel(option.value, reasoningSelect.dataset.presentation),
317
327
  datasetName: 'reasoningChoice'
318
328
  }));
319
329
  }
@@ -394,39 +404,33 @@
394
404
  }
395
405
  }
396
406
 
397
- function normalizeSpeedTiersForSelect(speedTiers) {
398
- const tiers = Array.isArray(speedTiers)
399
- ? speedTiers.map(tier => normalizeModelOptionId(tier)).filter(Boolean)
400
- : ['standard'];
401
- return tiers.includes('standard') ? tiers : ['standard', ...tiers];
402
- }
403
-
404
407
  function formatSpeedTierLabel(tier) {
405
408
  return tier === 'fast' ? tx('Fast', '快速') : tx('Standard', '标准');
406
409
  }
407
410
 
408
- function formatReasoningEffortLabel(effort) {
411
+ function formatReasoningEffortLabel(effort, presentation = '') {
412
+ if (presentation === 'toggle') {
413
+ return effort === 'none' ? tx('Off', '关闭') : tx('On', '开启');
414
+ }
415
+ if (presentation === 'deepseek') {
416
+ if (effort === 'none') return tx('Off', '关闭');
417
+ if (effort === 'xhigh') return 'Max';
418
+ }
409
419
  const labels = getLocale() === 'zh'
410
- ? { low: '低', medium: '中', high: '高', xhigh: '超高' }
411
- : { low: 'Low', medium: 'Medium', high: 'High', xhigh: 'XHigh' };
420
+ ? { none: '关闭', minimal: '最小', low: '低', medium: '中', high: '高', xhigh: '超高' }
421
+ : { none: 'Off', minimal: 'Minimal', low: 'Low', medium: 'Medium', high: 'High', xhigh: 'XHigh' };
412
422
  return labels[effort] || effort || '';
413
423
  }
414
424
 
415
- function formatCompactModelLabel(label) {
416
- return String(label || '').replace(/^gpt[-\s]*/i, '');
417
- }
418
-
419
425
  function resolveSelectedModel() {
420
426
  return getPanel()?.querySelector('[data-model]')?.value || getState()?.model || '';
421
427
  }
422
428
 
423
- function normalizeModelOptionId(id) {
424
- return typeof id === 'string' ? id.trim() : '';
425
- }
426
-
427
429
  function getModelDiscoverySourceLabel() {
428
430
  if (modelDiscovery.errorCode || modelDiscovery.errorMessage) {
429
- return `${tr('modelSourceFailed')} (${tr('modelSourceFallback')})`;
431
+ return modelDiscovery.status === 'fallback'
432
+ ? `${tr('modelSourceFailed')} (${tr('modelSourceFallback')})`
433
+ : tr('modelSourceFailed');
430
434
  }
431
435
  if (modelDiscovery.source === 'fallback') {
432
436
  return tr('modelSourceFallback');
@@ -454,7 +458,12 @@
454
458
  });
455
459
  modelDisplay.title = sourceTitle;
456
460
  if (reasoningDisplay) {
457
- reasoningDisplay.textContent = formatReasoningEffortLabel(getPanel()?.querySelector('[data-reasoning]')?.value || getState()?.reasoningEffort || '');
461
+ const reasoningSelect = getPanel()?.querySelector('[data-reasoning]');
462
+ const presentation = reasoningSelect?.dataset.presentation || '';
463
+ reasoningDisplay.hidden = presentation === 'none';
464
+ reasoningDisplay.textContent = presentation === 'none'
465
+ ? ''
466
+ : formatReasoningEffortLabel(reasoningSelect?.value || getState()?.reasoningEffort || '', presentation);
458
467
  }
459
468
  if (speedIndicator) {
460
469
  speedIndicator.hidden = readSelectedSpeedInput() !== 'fast';
@@ -475,8 +484,10 @@
475
484
  handleModelConfigChoiceClick,
476
485
  loadModelOptions,
477
486
  applyFallbackModelOptions,
487
+ applyUnavailableModelOptions,
478
488
  getModelCatalog,
479
489
  renderModelOptions,
490
+ renderReasoningOptions,
480
491
  renderSpeedOptions,
481
492
  renderModelConfigChoices,
482
493
  resolveSelectedModel,