codex-overleaf-link 2.4.0 → 2.5.0

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 (327) hide show
  1. package/README.md +272 -311
  2. package/README.zh-CN.md +287 -326
  3. package/extension/bootstrap/background.js +47 -10
  4. package/extension/bootstrap/manifest.template.json +9 -4
  5. package/extension/bootstrap/popup.html +10 -16
  6. package/extension/bootstrap/update.js +25 -2
  7. package/extension/runtime-manifest.json +3 -1
  8. package/extension/src/background.js +28 -3
  9. package/extension/src/backgroundSharedSessions.js +380 -0
  10. package/extension/src/backgroundUpdateCoordinator.js +442 -379
  11. package/extension/src/content/activeTurnControl.js +8 -0
  12. package/extension/src/content/assetTransferBroker.js +186 -12
  13. package/extension/src/content/changeHistoryView.js +175 -0
  14. package/extension/src/content/composerAttachments.js +24 -22
  15. package/extension/src/content/composerPanel.js +23 -8
  16. package/extension/src/content/contentRuntime.js +572 -571
  17. package/extension/src/content/diagnosticsController.js +1 -2
  18. package/extension/src/content/diagnosticsExport.js +222 -0
  19. package/extension/src/content/diagnosticsPanel.js +48 -19
  20. package/extension/src/content/diffReviewPanel.js +8 -5
  21. package/extension/src/content/generated/content.bundle.js +8974 -1781
  22. package/extension/src/content/generated/content.bundle.meta.json +22 -3
  23. package/extension/src/content/legacyGlobalRegistry.js +19 -1
  24. package/extension/src/content/localSkillsPanel.js +42 -7
  25. package/extension/src/content/markdownDomRenderer.js +9 -4
  26. package/extension/src/content/modelPicker.js +1 -1
  27. package/extension/src/content/pageBridgeClient.js +18 -3
  28. package/extension/src/content/panelMaintenance.js +51 -120
  29. package/extension/src/content/panelRenderer.js +4 -0
  30. package/extension/src/content/pendingInputView.js +109 -76
  31. package/extension/src/content/providerSettingsCoordinator.js +17 -6
  32. package/extension/src/content/providerSettingsDialog.js +661 -108
  33. package/extension/src/content/recentProjects.js +364 -356
  34. package/extension/src/content/runActivitySummary.js +631 -0
  35. package/extension/src/content/runController.js +3 -1
  36. package/extension/src/content/runFailureNotice.js +14 -1
  37. package/extension/src/content/runGuidanceController.js +4 -3
  38. package/extension/src/content/runPresence.js +306 -0
  39. package/extension/src/content/runQueueScheduler.js +2 -1
  40. package/extension/src/content/runResultActions.js +123 -3
  41. package/extension/src/content/runTimelineView.js +194 -164
  42. package/extension/src/content/scopedPersistenceCoordinator.js +2 -1
  43. package/extension/src/content/scopedPersistencePanelState.js +45 -2
  44. package/extension/src/content/selectionContextView.js +300 -0
  45. package/extension/src/content/sessionPersistence.js +10 -2
  46. package/extension/src/content/settingsPanel.js +93 -54
  47. package/extension/src/content/settingsWorkbench.js +374 -0
  48. package/extension/src/content/sharedSessionViewSync.js +72 -0
  49. package/extension/src/content/subagentActivityView.js +195 -0
  50. package/extension/src/content/trackedChangeCaptureController.js +7 -3
  51. package/extension/src/content/updateNotice.js +121 -50
  52. package/extension/src/content/writebackController.js +257 -0
  53. package/extension/src/content/writebackOrchestrator.js +146 -141
  54. package/extension/src/content/writebackPlan.js +231 -0
  55. package/extension/src/content/writebackRecovery.js +122 -0
  56. package/extension/src/content/writingStyleSettings.js +576 -0
  57. package/extension/src/content/writtenChangesView.js +152 -0
  58. package/extension/src/page/binaryAssetUploader.js +97 -19
  59. package/extension/src/page/overleafProjectSnapshot.js +1 -0
  60. package/extension/src/page/referenceProjects.js +148 -0
  61. package/extension/src/page/saveState.js +58 -3
  62. package/extension/src/page/snapshotRouter.js +44 -13
  63. package/extension/src/page/textCoordinates.js +102 -0
  64. package/extension/src/page/textFileCreator.js +350 -59
  65. package/extension/src/page/trackedChangeCapture.js +42 -56
  66. package/extension/src/page/trackedChangeOwnership.js +198 -0
  67. package/extension/src/page/trackedChangeReplay.js +164 -0
  68. package/extension/src/page/trackedChangesLifecycle.js +139 -150
  69. package/extension/src/page/treeOperations.js +36 -29
  70. package/extension/src/page/writebackReceiptJournal.js +112 -0
  71. package/extension/src/page/writebackRouter.js +176 -136
  72. package/extension/src/pageBridge.js +59 -101
  73. package/extension/src/popup.js +203 -59
  74. package/extension/src/shared/agentTranscript.js +18 -4
  75. package/extension/src/shared/auditRecords.js +71 -0
  76. package/extension/src/shared/changeHistoryModel.js +94 -0
  77. package/extension/src/shared/compatibility.js +1 -1
  78. package/extension/src/shared/compileAdapter.js +42 -0
  79. package/extension/src/shared/failureReasons.js +20 -0
  80. package/extension/src/shared/globalPreferences.js +1 -1
  81. package/extension/src/shared/i18n.js +187 -65
  82. package/extension/src/shared/managedUpdateProjection.js +6 -3
  83. package/extension/src/shared/pageRpcContract.js +7 -1
  84. package/extension/src/shared/providerProfiles.js +109 -0
  85. package/extension/src/shared/runActivityModel.js +361 -0
  86. package/extension/src/shared/runExecutionSnapshot.js +8 -2
  87. package/extension/src/shared/runExecutionSnapshotCodec.js +16 -4
  88. package/extension/src/shared/scopedPersistenceBrowserAdapter.js +6 -3
  89. package/extension/src/shared/selectionContext.js +28 -0
  90. package/extension/src/shared/sessionState.js +97 -6
  91. package/extension/src/shared/sharedSessionBridge.js +288 -0
  92. package/extension/src/shared/storageDb.js +23 -55
  93. package/extension/src/shared/storageMigration.js +3 -0
  94. package/extension/src/shared/storageRunActions.js +54 -4
  95. package/extension/src/shared/storageValuePruning.js +40 -0
  96. package/extension/src/shared/subagentActivityModel.js +174 -0
  97. package/extension/src/shared/undoOperations.js +87 -0
  98. package/extension/src/shared/updateConsent.js +37 -8
  99. package/extension/src/shared/writebackIntent.js +62 -0
  100. package/extension/src/shared/writebackSettlement.js +29 -10
  101. package/extension/src/shared/writingStyle.js +33 -0
  102. package/extension/styles/panel.css +2136 -744
  103. package/extension/styles/popup.css +110 -0
  104. package/native-host/src/anthropicBridgeRequest.js +2 -1
  105. package/native-host/src/codexModelRecovery.js +107 -0
  106. package/native-host/src/codexPromptAssembly.js +2 -0
  107. package/native-host/src/codexProviderLaunch.js +3 -3
  108. package/native-host/src/codexRuntimeIdentity.js +1 -0
  109. package/native-host/src/codexSessionRunner.js +146 -177
  110. package/native-host/src/codexSessionTiming.js +71 -0
  111. package/native-host/src/managedInstall.js +5 -4
  112. package/native-host/src/nativeEnvironment.js +70 -0
  113. package/native-host/src/nativeQuotas.js +15 -8
  114. package/native-host/src/overleafWorkspaceRules.js +16 -0
  115. package/native-host/src/processStopBarrier.js +80 -0
  116. package/native-host/src/providerModelCatalog.js +4 -3
  117. package/native-host/src/providerProfile.js +13 -1
  118. package/native-host/src/providerReasoning.js +9 -5
  119. package/native-host/src/providerStore.js +4 -0
  120. package/native-host/src/readProgressGuard.js +84 -17
  121. package/native-host/src/releaseTransport.js +14 -5
  122. package/native-host/src/selectionScope.js +58 -0
  123. package/native-host/src/skills/parallel-subagents/SKILL.md +31 -3
  124. package/native-host/src/skills/writing-style-builder/SKILL.md +146 -0
  125. package/native-host/src/subagentBroker.js +102 -98
  126. package/native-host/src/subagentTelemetry.js +316 -0
  127. package/native-host/src/subagentWorkspace.js +103 -0
  128. package/native-host/src/subagentWorkspacePath.js +31 -0
  129. package/native-host/src/taskRunnerRuntime.js +55 -5
  130. package/native-host/src/turnAttachments.js +113 -0
  131. package/native-host/src/updateManager.js +122 -12
  132. package/native-host/src/updateNetworkControl.js +75 -0
  133. package/native-host/src/updateTrust.js +21 -2
  134. package/native-host/src/vendor/pdfjs/LICENSE +177 -0
  135. package/native-host/src/vendor/pdfjs/SOURCE.txt +3 -0
  136. package/native-host/src/vendor/pdfjs/cmaps/78-EUC-H.bcmap +0 -0
  137. package/native-host/src/vendor/pdfjs/cmaps/78-EUC-V.bcmap +0 -0
  138. package/native-host/src/vendor/pdfjs/cmaps/78-H.bcmap +0 -0
  139. package/native-host/src/vendor/pdfjs/cmaps/78-RKSJ-H.bcmap +0 -0
  140. package/native-host/src/vendor/pdfjs/cmaps/78-RKSJ-V.bcmap +0 -0
  141. package/native-host/src/vendor/pdfjs/cmaps/78-V.bcmap +0 -0
  142. package/native-host/src/vendor/pdfjs/cmaps/78ms-RKSJ-H.bcmap +0 -0
  143. package/native-host/src/vendor/pdfjs/cmaps/78ms-RKSJ-V.bcmap +0 -0
  144. package/native-host/src/vendor/pdfjs/cmaps/83pv-RKSJ-H.bcmap +0 -0
  145. package/native-host/src/vendor/pdfjs/cmaps/90ms-RKSJ-H.bcmap +0 -0
  146. package/native-host/src/vendor/pdfjs/cmaps/90ms-RKSJ-V.bcmap +0 -0
  147. package/native-host/src/vendor/pdfjs/cmaps/90msp-RKSJ-H.bcmap +0 -0
  148. package/native-host/src/vendor/pdfjs/cmaps/90msp-RKSJ-V.bcmap +0 -0
  149. package/native-host/src/vendor/pdfjs/cmaps/90pv-RKSJ-H.bcmap +0 -0
  150. package/native-host/src/vendor/pdfjs/cmaps/90pv-RKSJ-V.bcmap +0 -0
  151. package/native-host/src/vendor/pdfjs/cmaps/Add-H.bcmap +0 -0
  152. package/native-host/src/vendor/pdfjs/cmaps/Add-RKSJ-H.bcmap +0 -0
  153. package/native-host/src/vendor/pdfjs/cmaps/Add-RKSJ-V.bcmap +0 -0
  154. package/native-host/src/vendor/pdfjs/cmaps/Add-V.bcmap +0 -0
  155. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-0.bcmap +0 -0
  156. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-1.bcmap +0 -0
  157. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-2.bcmap +0 -0
  158. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-3.bcmap +0 -0
  159. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-4.bcmap +0 -0
  160. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-5.bcmap +0 -0
  161. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-6.bcmap +0 -0
  162. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-UCS2.bcmap +0 -0
  163. package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-0.bcmap +0 -0
  164. package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-1.bcmap +0 -0
  165. package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-2.bcmap +0 -0
  166. package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-3.bcmap +0 -0
  167. package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-4.bcmap +0 -0
  168. package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-5.bcmap +0 -0
  169. package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-UCS2.bcmap +0 -0
  170. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-0.bcmap +0 -0
  171. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-1.bcmap +0 -0
  172. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-2.bcmap +0 -0
  173. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-3.bcmap +0 -0
  174. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-4.bcmap +0 -0
  175. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-5.bcmap +0 -0
  176. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-6.bcmap +0 -0
  177. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-UCS2.bcmap +0 -0
  178. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Korea1-0.bcmap +0 -0
  179. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Korea1-1.bcmap +0 -0
  180. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Korea1-2.bcmap +0 -0
  181. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Korea1-UCS2.bcmap +0 -0
  182. package/native-host/src/vendor/pdfjs/cmaps/B5-H.bcmap +0 -0
  183. package/native-host/src/vendor/pdfjs/cmaps/B5-V.bcmap +0 -0
  184. package/native-host/src/vendor/pdfjs/cmaps/B5pc-H.bcmap +0 -0
  185. package/native-host/src/vendor/pdfjs/cmaps/B5pc-V.bcmap +0 -0
  186. package/native-host/src/vendor/pdfjs/cmaps/CNS-EUC-H.bcmap +0 -0
  187. package/native-host/src/vendor/pdfjs/cmaps/CNS-EUC-V.bcmap +0 -0
  188. package/native-host/src/vendor/pdfjs/cmaps/CNS1-H.bcmap +0 -0
  189. package/native-host/src/vendor/pdfjs/cmaps/CNS1-V.bcmap +0 -0
  190. package/native-host/src/vendor/pdfjs/cmaps/CNS2-H.bcmap +0 -0
  191. package/native-host/src/vendor/pdfjs/cmaps/CNS2-V.bcmap +3 -0
  192. package/native-host/src/vendor/pdfjs/cmaps/ETHK-B5-H.bcmap +0 -0
  193. package/native-host/src/vendor/pdfjs/cmaps/ETHK-B5-V.bcmap +0 -0
  194. package/native-host/src/vendor/pdfjs/cmaps/ETen-B5-H.bcmap +0 -0
  195. package/native-host/src/vendor/pdfjs/cmaps/ETen-B5-V.bcmap +0 -0
  196. package/native-host/src/vendor/pdfjs/cmaps/ETenms-B5-H.bcmap +3 -0
  197. package/native-host/src/vendor/pdfjs/cmaps/ETenms-B5-V.bcmap +0 -0
  198. package/native-host/src/vendor/pdfjs/cmaps/EUC-H.bcmap +0 -0
  199. package/native-host/src/vendor/pdfjs/cmaps/EUC-V.bcmap +0 -0
  200. package/native-host/src/vendor/pdfjs/cmaps/Ext-H.bcmap +0 -0
  201. package/native-host/src/vendor/pdfjs/cmaps/Ext-RKSJ-H.bcmap +0 -0
  202. package/native-host/src/vendor/pdfjs/cmaps/Ext-RKSJ-V.bcmap +0 -0
  203. package/native-host/src/vendor/pdfjs/cmaps/Ext-V.bcmap +0 -0
  204. package/native-host/src/vendor/pdfjs/cmaps/GB-EUC-H.bcmap +0 -0
  205. package/native-host/src/vendor/pdfjs/cmaps/GB-EUC-V.bcmap +0 -0
  206. package/native-host/src/vendor/pdfjs/cmaps/GB-H.bcmap +4 -0
  207. package/native-host/src/vendor/pdfjs/cmaps/GB-V.bcmap +0 -0
  208. package/native-host/src/vendor/pdfjs/cmaps/GBK-EUC-H.bcmap +0 -0
  209. package/native-host/src/vendor/pdfjs/cmaps/GBK-EUC-V.bcmap +0 -0
  210. package/native-host/src/vendor/pdfjs/cmaps/GBK2K-H.bcmap +0 -0
  211. package/native-host/src/vendor/pdfjs/cmaps/GBK2K-V.bcmap +0 -0
  212. package/native-host/src/vendor/pdfjs/cmaps/GBKp-EUC-H.bcmap +0 -0
  213. package/native-host/src/vendor/pdfjs/cmaps/GBKp-EUC-V.bcmap +0 -0
  214. package/native-host/src/vendor/pdfjs/cmaps/GBT-EUC-H.bcmap +0 -0
  215. package/native-host/src/vendor/pdfjs/cmaps/GBT-EUC-V.bcmap +0 -0
  216. package/native-host/src/vendor/pdfjs/cmaps/GBT-H.bcmap +0 -0
  217. package/native-host/src/vendor/pdfjs/cmaps/GBT-V.bcmap +0 -0
  218. package/native-host/src/vendor/pdfjs/cmaps/GBTpc-EUC-H.bcmap +0 -0
  219. package/native-host/src/vendor/pdfjs/cmaps/GBTpc-EUC-V.bcmap +0 -0
  220. package/native-host/src/vendor/pdfjs/cmaps/GBpc-EUC-H.bcmap +0 -0
  221. package/native-host/src/vendor/pdfjs/cmaps/GBpc-EUC-V.bcmap +0 -0
  222. package/native-host/src/vendor/pdfjs/cmaps/H.bcmap +0 -0
  223. package/native-host/src/vendor/pdfjs/cmaps/HKdla-B5-H.bcmap +0 -0
  224. package/native-host/src/vendor/pdfjs/cmaps/HKdla-B5-V.bcmap +0 -0
  225. package/native-host/src/vendor/pdfjs/cmaps/HKdlb-B5-H.bcmap +0 -0
  226. package/native-host/src/vendor/pdfjs/cmaps/HKdlb-B5-V.bcmap +0 -0
  227. package/native-host/src/vendor/pdfjs/cmaps/HKgccs-B5-H.bcmap +0 -0
  228. package/native-host/src/vendor/pdfjs/cmaps/HKgccs-B5-V.bcmap +0 -0
  229. package/native-host/src/vendor/pdfjs/cmaps/HKm314-B5-H.bcmap +0 -0
  230. package/native-host/src/vendor/pdfjs/cmaps/HKm314-B5-V.bcmap +0 -0
  231. package/native-host/src/vendor/pdfjs/cmaps/HKm471-B5-H.bcmap +0 -0
  232. package/native-host/src/vendor/pdfjs/cmaps/HKm471-B5-V.bcmap +0 -0
  233. package/native-host/src/vendor/pdfjs/cmaps/HKscs-B5-H.bcmap +0 -0
  234. package/native-host/src/vendor/pdfjs/cmaps/HKscs-B5-V.bcmap +0 -0
  235. package/native-host/src/vendor/pdfjs/cmaps/Hankaku.bcmap +0 -0
  236. package/native-host/src/vendor/pdfjs/cmaps/Hiragana.bcmap +0 -0
  237. package/native-host/src/vendor/pdfjs/cmaps/KSC-EUC-H.bcmap +0 -0
  238. package/native-host/src/vendor/pdfjs/cmaps/KSC-EUC-V.bcmap +0 -0
  239. package/native-host/src/vendor/pdfjs/cmaps/KSC-H.bcmap +0 -0
  240. package/native-host/src/vendor/pdfjs/cmaps/KSC-Johab-H.bcmap +0 -0
  241. package/native-host/src/vendor/pdfjs/cmaps/KSC-Johab-V.bcmap +0 -0
  242. package/native-host/src/vendor/pdfjs/cmaps/KSC-V.bcmap +0 -0
  243. package/native-host/src/vendor/pdfjs/cmaps/KSCms-UHC-H.bcmap +0 -0
  244. package/native-host/src/vendor/pdfjs/cmaps/KSCms-UHC-HW-H.bcmap +0 -0
  245. package/native-host/src/vendor/pdfjs/cmaps/KSCms-UHC-HW-V.bcmap +0 -0
  246. package/native-host/src/vendor/pdfjs/cmaps/KSCms-UHC-V.bcmap +0 -0
  247. package/native-host/src/vendor/pdfjs/cmaps/KSCpc-EUC-H.bcmap +0 -0
  248. package/native-host/src/vendor/pdfjs/cmaps/KSCpc-EUC-V.bcmap +0 -0
  249. package/native-host/src/vendor/pdfjs/cmaps/Katakana.bcmap +0 -0
  250. package/native-host/src/vendor/pdfjs/cmaps/LICENSE +36 -0
  251. package/native-host/src/vendor/pdfjs/cmaps/NWP-H.bcmap +0 -0
  252. package/native-host/src/vendor/pdfjs/cmaps/NWP-V.bcmap +0 -0
  253. package/native-host/src/vendor/pdfjs/cmaps/RKSJ-H.bcmap +0 -0
  254. package/native-host/src/vendor/pdfjs/cmaps/RKSJ-V.bcmap +0 -0
  255. package/native-host/src/vendor/pdfjs/cmaps/Roman.bcmap +0 -0
  256. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UCS2-H.bcmap +0 -0
  257. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UCS2-V.bcmap +0 -0
  258. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF16-H.bcmap +0 -0
  259. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF16-V.bcmap +0 -0
  260. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF32-H.bcmap +0 -0
  261. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF32-V.bcmap +0 -0
  262. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF8-H.bcmap +0 -0
  263. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF8-V.bcmap +0 -0
  264. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UCS2-H.bcmap +0 -0
  265. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UCS2-V.bcmap +0 -0
  266. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF16-H.bcmap +0 -0
  267. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF16-V.bcmap +0 -0
  268. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF32-H.bcmap +0 -0
  269. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF32-V.bcmap +0 -0
  270. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF8-H.bcmap +0 -0
  271. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF8-V.bcmap +0 -0
  272. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UCS2-H.bcmap +0 -0
  273. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UCS2-HW-H.bcmap +0 -0
  274. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UCS2-HW-V.bcmap +0 -0
  275. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UCS2-V.bcmap +0 -0
  276. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF16-H.bcmap +0 -0
  277. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF16-V.bcmap +0 -0
  278. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF32-H.bcmap +0 -0
  279. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF32-V.bcmap +0 -0
  280. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF8-H.bcmap +0 -0
  281. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF8-V.bcmap +0 -0
  282. package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF16-H.bcmap +0 -0
  283. package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF16-V.bcmap +0 -0
  284. package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF32-H.bcmap +0 -0
  285. package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF32-V.bcmap +0 -0
  286. package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF8-H.bcmap +0 -0
  287. package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF8-V.bcmap +0 -0
  288. package/native-host/src/vendor/pdfjs/cmaps/UniJISPro-UCS2-HW-V.bcmap +0 -0
  289. package/native-host/src/vendor/pdfjs/cmaps/UniJISPro-UCS2-V.bcmap +0 -0
  290. package/native-host/src/vendor/pdfjs/cmaps/UniJISPro-UTF8-V.bcmap +0 -0
  291. package/native-host/src/vendor/pdfjs/cmaps/UniJISX0213-UTF32-H.bcmap +0 -0
  292. package/native-host/src/vendor/pdfjs/cmaps/UniJISX0213-UTF32-V.bcmap +0 -0
  293. package/native-host/src/vendor/pdfjs/cmaps/UniJISX02132004-UTF32-H.bcmap +0 -0
  294. package/native-host/src/vendor/pdfjs/cmaps/UniJISX02132004-UTF32-V.bcmap +0 -0
  295. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UCS2-H.bcmap +0 -0
  296. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UCS2-V.bcmap +0 -0
  297. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF16-H.bcmap +0 -0
  298. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF16-V.bcmap +0 -0
  299. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF32-H.bcmap +0 -0
  300. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF32-V.bcmap +0 -0
  301. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF8-H.bcmap +0 -0
  302. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF8-V.bcmap +0 -0
  303. package/native-host/src/vendor/pdfjs/cmaps/V.bcmap +0 -0
  304. package/native-host/src/vendor/pdfjs/cmaps/WP-Symbol.bcmap +0 -0
  305. package/native-host/src/vendor/pdfjs/pdf.mjs +26740 -0
  306. package/native-host/src/vendor/pdfjs/pdf.worker.mjs +62201 -0
  307. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitDingbats.pfb +0 -0
  308. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitFixed.pfb +0 -0
  309. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitFixedBold.pfb +0 -0
  310. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitFixedBoldItalic.pfb +0 -0
  311. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitFixedItalic.pfb +0 -0
  312. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitSerif.pfb +0 -0
  313. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitSerifBold.pfb +0 -0
  314. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitSerifBoldItalic.pfb +0 -0
  315. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitSerifItalic.pfb +0 -0
  316. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitSymbol.pfb +0 -0
  317. package/native-host/src/vendor/pdfjs/standard_fonts/LICENSE_FOXIT +27 -0
  318. package/native-host/src/vendor/pdfjs/standard_fonts/LICENSE_LIBERATION +102 -0
  319. package/native-host/src/vendor/pdfjs/standard_fonts/LiberationSans-Bold.ttf +0 -0
  320. package/native-host/src/vendor/pdfjs/standard_fonts/LiberationSans-BoldItalic.ttf +0 -0
  321. package/native-host/src/vendor/pdfjs/standard_fonts/LiberationSans-Italic.ttf +0 -0
  322. package/native-host/src/vendor/pdfjs/standard_fonts/LiberationSans-Regular.ttf +0 -0
  323. package/native-host/src/writingStyleEvidence.js +102 -0
  324. package/native-host/src/writingStylePdfWorker.js +35 -0
  325. package/native-host/src/writingStyleProfile.js +165 -0
  326. package/native-host/src/writingStyleRuntime.js +447 -0
  327. package/package.json +1 -1
@@ -4,6 +4,44 @@
4
4
  // Official OpenAI blossom mark used by the Codex VS Code extension.
5
5
  const CODEX_BLOSSOM_PATH = 'M13.798 23.976a5.7 5.7 0 0 1-2.26-.456 6.1 6.1 0 0 1-1.903-1.27 5.7 5.7 0 0 1-1.88.311 5.75 5.75 0 0 1-2.95-.79 6.2 6.2 0 0 1-2.188-2.159q-.81-1.366-.809-3.045 0-.695.19-1.51a6.4 6.4 0 0 1-1.475-2.038A5.95 5.95 0 0 1 0 10.573Q0 9.278.547 8.08q.547-1.2 1.523-2.062a5.5 5.5 0 0 1 2.307-1.223A5.7 5.7 0 0 1 5.472 2.35 6.1 6.1 0 0 1 7.565.623 5.8 5.8 0 0 1 10.206 0q1.19 0 2.26.456a6.1 6.1 0 0 1 1.903 1.27 5.7 5.7 0 0 1 1.88-.311q1.594 0 2.95.79a6 6 0 0 1 2.165 2.159q.832 1.366.832 3.045 0 .695-.19 1.51a6.3 6.3 0 0 1 1.475 2.062q.523 1.15.523 2.422a5.9 5.9 0 0 1-.547 2.493q-.547 1.2-1.546 2.086a5.4 5.4 0 0 1-2.284 1.199 5.56 5.56 0 0 1-1.118 2.445 5.9 5.9 0 0 1-2.07 1.727 5.8 5.8 0 0 1-2.64.623m-5.876-2.997q1.19 0 2.07-.504l4.472-2.589a.53.53 0 0 0 .238-.455v-2.062L8.945 18.7a.96.96 0 0 1-1.047 0l-4.496-2.613a.7.7 0 0 1-.024.168v.287q0 1.224.571 2.254a4.24 4.24 0 0 0 1.642 1.583q1.047.6 2.331.599m.238-3.908a.6.6 0 0 0 .262.072q.118 0 .238-.072l1.784-1.031-5.734-3.357q-.522-.312-.523-.935V6.545a4.3 4.3 0 0 0-1.903 1.63 4.25 4.25 0 0 0-.714 2.398q0 1.176.595 2.254.594 1.08 1.546 1.63zm5.638 5.323q1.26 0 2.284-.576a4.3 4.3 0 0 0 1.618-1.582q.595-1.008.595-2.254v-5.179a.47.47 0 0 0-.238-.431l-1.808-1.055v6.689q0 .624-.524.935l-4.496 2.613a4.3 4.3 0 0 0 2.57.84m.904-8.776v-3.26l-2.688-1.535-2.712 1.535v3.26l2.712 1.535zM7.756 5.97q0-.623.523-.935l4.496-2.613a4.3 4.3 0 0 0-2.569-.84q-1.26 0-2.284.576A4.3 4.3 0 0 0 6.304 3.74q-.57 1.008-.57 2.254v5.155q0 .287.237.455l1.785 1.055zM19.84 17.43a4.16 4.16 0 0 0 1.88-1.63 4.33 4.33 0 0 0 .713-2.397q0-1.176-.595-2.254-.594-1.08-1.546-1.63l-4.449-2.59q-.143-.096-.261-.072a.46.46 0 0 0-.238.072L13.56 7.936l5.758 3.38a.9.9 0 0 1 .38.384q.143.216.143.528zM15.059 5.25q.524-.335 1.047 0l4.52 2.662V7.48q0-1.15-.57-2.181A4.14 4.14 0 0 0 18.46 3.62q-1.023-.623-2.379-.623-1.19 0-2.07.503L9.54 6.09a.53.53 0 0 0-.238.455v2.062z';
6
6
 
7
+ // Anthropic mark: https://github.com/simple-icons/simple-icons/blob/develop/icons/anthropic.svg
8
+ // Upstream SVG blob: c917480d32596242d674985ee622d9e7dd51f81a
9
+ const ANTHROPIC_LOGO_PATH = 'M17.3041 3.541h-3.6718l6.696 16.918H24Zm-10.6082 0L0 20.459h3.7442l1.3693-3.5527h7.0052l1.3693 3.5528h3.7442L10.5363 3.5409Zm-.3712 10.2232 2.2914-5.9456 2.2914 5.9456Z';
10
+
11
+ /*!
12
+ * Provider SVG marks adapted from LobeHub Icons.
13
+ * kimi: https://github.com/lobehub/lobe-icons/blob/master/packages/static-svg/icons/kimi.svg (blob 1915850ee50ea636cc34b37aa11ba7f1ae0a26a2)
14
+ * glm: https://github.com/lobehub/lobe-icons/blob/master/packages/static-svg/icons/zhipu.svg (blob f7b12528e1ac82ef8b8b58825164bc88fa8b9e4b)
15
+ * deepseek: https://github.com/lobehub/lobe-icons/blob/master/packages/static-svg/icons/deepseek.svg (blob dc224e43a4d68070ca6eed494476c8ddd900bf80)
16
+ *
17
+ * MIT License
18
+ *
19
+ * Copyright (c) 2023 LobeHub
20
+ *
21
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
22
+ * of this software and associated documentation files (the "Software"), to deal
23
+ * in the Software without restriction, including without limitation the rights
24
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
25
+ * copies of the Software, and to permit persons to whom the Software is
26
+ * furnished to do so, subject to the following conditions:
27
+ *
28
+ * The above copyright notice and this permission notice shall be included in all
29
+ * copies or substantial portions of the Software.
30
+ *
31
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
32
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
33
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
34
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
35
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
36
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
37
+ * SOFTWARE.
38
+ */
39
+ const PROVIDER_VENDOR_ICON_PATHS = Object.freeze({
40
+ "kimi": "M21.846 0a1.923 1.923 0 110 3.846H20.15a.226.226 0 01-.227-.226V1.923C19.923.861 20.784 0 21.846 0z M11.065 11.199l7.257-7.2c.137-.136.06-.41-.116-.41H14.3a.164.164 0 00-.117.051l-7.82 7.756c-.122.12-.302.013-.302-.179V3.82c0-.127-.083-.23-.185-.23H3.186c-.103 0-.186.103-.186.23V19.77c0 .128.083.23.186.23h2.69c.103 0 .186-.102.186-.23v-3.25c0-.069.025-.135.069-.178l2.424-2.406a.158.158 0 01.205-.023l6.484 4.772a7.677 7.677 0 003.453 1.283c.108.012.2-.095.2-.23v-3.06c0-.117-.07-.212-.164-.227a5.028 5.028 0 01-2.027-.807l-5.613-4.064c-.117-.078-.132-.279-.028-.381z",
41
+ "glm": "M11.991 23.503a.24.24 0 00-.244.248.24.24 0 00.244.249.24.24 0 00.245-.249.24.24 0 00-.22-.247l-.025-.001zM9.671 5.365a1.697 1.697 0 011.099 2.132l-.071.172-.016.04-.018.054c-.07.16-.104.32-.104.498-.035.71.47 1.279 1.186 1.314h.366c1.309.053 2.338 1.173 2.286 2.523-.052 1.332-1.152 2.38-2.478 2.327h-.174c-.715.018-1.274.64-1.239 1.368 0 .124.018.23.053.337.209.373.54.658.96.8.75.23 1.517-.125 1.9-.782l.018-.035c.402-.64 1.17-.96 1.92-.711.854.284 1.378 1.226 1.099 2.167a1.661 1.661 0 01-2.077 1.102 1.711 1.711 0 01-.907-.711l-.017-.035c-.2-.323-.463-.58-.851-.711l-.056-.018a1.646 1.646 0 00-1.954.746 1.66 1.66 0 01-1.065.764 1.677 1.677 0 01-1.989-1.279c-.209-.906.332-1.83 1.257-2.043a1.51 1.51 0 01.296-.035h.018c.68-.071 1.151-.622 1.116-1.333a1.307 1.307 0 00-.227-.693 2.515 2.515 0 01-.366-1.403 2.39 2.39 0 01.366-1.208c.14-.195.21-.444.227-.693.018-.71-.506-1.261-1.186-1.332l-.07-.018a1.43 1.43 0 01-.299-.07l-.05-.019a1.7 1.7 0 01-1.047-2.114 1.68 1.68 0 012.094-1.101zm-5.575 10.11c.26-.264.639-.367.994-.27.355.096.633.379.728.74.095.362-.007.748-.267 1.013-.402.41-1.053.41-1.455 0a1.062 1.062 0 010-1.482zm14.845-.294c.359-.09.738.024.992.297.254.274.344.665.237 1.025-.107.36-.396.634-.756.718-.551.128-1.1-.22-1.23-.781a1.05 1.05 0 01.757-1.26zm-.064-4.39c.314.32.49.753.49 1.206 0 .452-.176.886-.49 1.206-.315.32-.74.5-1.185.5-.444 0-.87-.18-1.184-.5a1.727 1.727 0 010-2.412 1.654 1.654 0 012.369 0zm-11.243.163c.364.484.447 1.128.218 1.691a1.665 1.665 0 01-2.188.923c-.855-.36-1.26-1.358-.907-2.228a1.68 1.68 0 011.33-1.038c.593-.08 1.183.169 1.547.652zm11.545-4.221c.368 0 .708.2.892.524.184.324.184.724 0 1.048a1.026 1.026 0 01-.892.524c-.568 0-1.03-.47-1.03-1.048 0-.579.462-1.048 1.03-1.048zm-14.358 0c.368 0 .707.2.891.524.184.324.184.724 0 1.048a1.026 1.026 0 01-.891.524c-.569 0-1.03-.47-1.03-1.048 0-.579.461-1.048 1.03-1.048zm10.031-1.475c.925 0 1.675.764 1.675 1.706s-.75 1.705-1.675 1.705-1.674-.763-1.674-1.705c0-.942.75-1.706 1.674-1.706zm-2.626-.684c.362-.082.653-.356.761-.718a1.062 1.062 0 00-.238-1.028 1.017 1.017 0 00-.996-.294c-.547.14-.881.7-.752 1.257.13.558.675.907 1.225.783zm0 16.876c.359-.087.644-.36.75-.72a1.062 1.062 0 00-.237-1.019 1.018 1.018 0 00-.985-.301 1.037 1.037 0 00-.762.717c-.108.361-.017.754.239 1.028.245.263.606.377.953.305l.043-.01zM17.19 3.5a.631.631 0 00.628-.64c0-.355-.279-.64-.628-.64a.631.631 0 00-.628.64c0 .355.28.64.628.64zm-10.38 0a.631.631 0 00.628-.64c0-.355-.28-.64-.628-.64a.631.631 0 00-.628.64c0 .355.279.64.628.64zm-5.182 7.852a.631.631 0 00-.628.64c0 .354.28.639.628.639a.63.63 0 00.627-.606l.001-.034a.62.62 0 00-.628-.64zm5.182 9.13a.631.631 0 00-.628.64c0 .355.279.64.628.64a.631.631 0 00.628-.64c0-.355-.28-.64-.628-.64zm10.38.018a.631.631 0 00-.628.64c0 .355.28.64.628.64a.631.631 0 00.628-.64c0-.355-.279-.64-.628-.64zm5.182-9.148a.631.631 0 00-.628.64c0 .354.279.639.628.639a.631.631 0 00.628-.64c0-.355-.28-.64-.628-.64zm-.384-4.992a.24.24 0 00.244-.249.24.24 0 00-.244-.249.24.24 0 00-.244.249c0 .142.122.249.244.249zM11.991.497a.24.24 0 00.245-.248A.24.24 0 0011.99 0a.24.24 0 00-.244.249c0 .133.108.236.223.247l.021.001zM2.011 6.36a.24.24 0 00.245-.249.24.24 0 00-.244-.249.24.24 0 00-.244.249.24.24 0 00.244.249zm0 11.263a.24.24 0 00-.243.248.24.24 0 00.244.249.24.24 0 00.244-.249.252.252 0 00-.244-.248zm19.995-.018a.24.24 0 00-.245.248.24.24 0 00.245.25.24.24 0 00.244-.25.252.252 0 00-.244-.248z",
42
+ "deepseek": "M23.748 4.482c-.254-.124-.364.113-.512.234-.051.039-.094.09-.137.136-.372.397-.806.657-1.373.626-.829-.046-1.537.214-2.163.848-.133-.782-.575-1.248-1.247-1.548-.352-.156-.708-.311-.955-.65-.172-.241-.219-.51-.305-.774-.055-.16-.11-.323-.293-.35-.2-.031-.278.136-.356.276-.313.572-.434 1.202-.422 1.84.027 1.436.633 2.58 1.838 3.393.137.093.172.187.129.323-.082.28-.18.552-.266.833-.055.179-.137.217-.329.14a5.526 5.526 0 01-1.736-1.18c-.857-.828-1.631-1.742-2.597-2.458a11.365 11.365 0 00-.689-.471c-.985-.957.13-1.743.388-1.836.27-.098.093-.432-.779-.428-.872.004-1.67.295-2.687.684a3.055 3.055 0 01-.465.137 9.597 9.597 0 00-2.883-.102c-1.885.21-3.39 1.102-4.497 2.623C.082 8.606-.231 10.684.152 12.85c.403 2.284 1.569 4.175 3.36 5.653 1.858 1.533 3.997 2.284 6.438 2.14 1.482-.085 3.133-.284 4.994-1.86.47.234.962.327 1.78.397.63.059 1.236-.03 1.705-.128.735-.156.684-.837.419-.961-2.155-1.004-1.682-.595-2.113-.926 1.096-1.296 2.746-2.642 3.392-7.003.05-.347.007-.565 0-.845-.004-.17.035-.237.23-.256a4.173 4.173 0 001.545-.475c1.396-.763 1.96-2.015 2.093-3.517.02-.23-.004-.467-.247-.588zM11.581 18c-2.089-1.642-3.102-2.183-3.52-2.16-.392.024-.321.471-.235.763.09.288.207.486.371.739.114.167.192.416-.113.603-.673.416-1.842-.14-1.897-.167-1.361-.802-2.5-1.86-3.301-3.307-.774-1.393-1.224-2.887-1.298-4.482-.02-.386.093-.522.477-.592a4.696 4.696 0 011.529-.039c2.132.312 3.946 1.265 5.468 2.774.868.86 1.525 1.887 2.202 2.891.72 1.066 1.494 2.082 2.48 2.914.348.292.625.514.891.677-.802.09-2.14.11-3.054-.614zm1-6.44a.306.306 0 01.415-.287.302.302 0 01.2.288.306.306 0 01-.31.307.303.303 0 01-.304-.308zm3.11 1.596c-.2.081-.399.151-.59.16a1.245 1.245 0 01-.798-.254c-.274-.23-.47-.358-.552-.758a1.73 1.73 0 01.016-.588c.07-.327-.008-.537-.239-.727-.187-.156-.426-.199-.688-.199a.559.559 0 01-.254-.078c-.11-.054-.2-.19-.114-.358.028-.054.16-.186.192-.21.356-.202.767-.136 1.146.016.352.144.618.408 1.001.782.391.451.462.576.685.914.176.265.336.537.445.848.067.195-.019.354-.25.452z"
43
+ });
44
+
7
45
  function create(options = {}) {
8
46
  const Profiles = options.ProviderProfiles;
9
47
  if (!Profiles) {
@@ -23,11 +61,20 @@
23
61
  verifications: {},
24
62
  pendingCatalog: null,
25
63
  root: null,
26
- returnFocus: null
64
+ returnFocus: null,
65
+ providerFilter: '',
66
+ modelFilter: '',
67
+ templatePicker: false,
68
+ previousProviderId: 'builtin',
69
+ modelEditor: null,
70
+ verificationStale: false,
71
+ embedded: false,
72
+ embeddedContainer: null,
73
+ confirmation: null
27
74
  };
28
75
  ensureRoot(instance);
29
76
  return {
30
- open: catalog => open(instance, catalog),
77
+ open: (catalog, presentation) => open(instance, catalog, presentation),
31
78
  close: () => requestClose(instance),
32
79
  setCatalog: (catalog, selectedId) => setCatalog(instance, catalog, selectedId),
33
80
  offerCatalog: (catalog, selectedId) => offerCatalog(instance, catalog, selectedId),
@@ -36,7 +83,7 @@
36
83
  setVerification: verification => setVerification(instance, verification),
37
84
  setVerificationFailure: failure => setVerificationFailure(instance, failure),
38
85
  setTestProgress: progress => setTestProgress(instance, progress),
39
- hasUnsavedChanges: () => instance.dirty,
86
+ hasUnsavedChanges: () => instance.dirty || Boolean(instance.modelEditor || instance.confirmation),
40
87
  isOpen: () => Boolean(instance.root && !instance.root.hidden),
41
88
  destroy: () => destroy(instance),
42
89
  _instance: instance
@@ -48,7 +95,7 @@
48
95
  return instance.root;
49
96
  }
50
97
  const root = instance.document.createElement('dialog');
51
- root.className = 'codex-provider-dialog-root';
98
+ root.className = 'codex-provider-dialog-root codex-provider-v2';
52
99
  root.setAttribute('aria-labelledby', 'codex-provider-dialog-title');
53
100
  root.hidden = true;
54
101
  root.innerHTML = `
@@ -62,6 +109,7 @@
62
109
  </div>
63
110
  <p data-provider-dialog-subtitle></p>
64
111
  </div>
112
+ <button type="button" class="codex-provider-inline-button" data-provider-action="reload" hidden></button>
65
113
  <button type="button" class="codex-provider-dialog-close" data-provider-action="close" aria-label="Close">×</button>
66
114
  </header>
67
115
  <div class="codex-provider-dialog-body">
@@ -74,6 +122,23 @@
74
122
  </footer>
75
123
  </section>
76
124
  `;
125
+ const modelDialog = instance.document.createElement('dialog');
126
+ modelDialog.className = 'codex-provider-model-editor';
127
+ modelDialog.setAttribute('data-provider-model-editor', '');
128
+ modelDialog.setAttribute('aria-labelledby', 'codex-provider-model-title');
129
+ modelDialog.addEventListener('cancel', event => {
130
+ event.preventDefault(); event.stopPropagation(); closeModelEditor(instance);
131
+ });
132
+ root.appendChild(modelDialog);
133
+ const confirmation = instance.document.createElement('dialog');
134
+ confirmation.className = 'codex-provider-confirmation';
135
+ confirmation.setAttribute('data-provider-confirmation', '');
136
+ confirmation.setAttribute('aria-labelledby', 'codex-provider-confirm-title');
137
+ confirmation.setAttribute('aria-describedby', 'codex-provider-confirm-message');
138
+ confirmation.addEventListener('cancel', event => {
139
+ event.preventDefault(); event.stopPropagation(); finishProviderConfirmation(instance, false);
140
+ });
141
+ root.appendChild(confirmation);
77
142
  instance.document.documentElement.appendChild(root);
78
143
  instance.root = root;
79
144
  root.addEventListener('click', event => handleClick(instance, event));
@@ -86,22 +151,38 @@
86
151
  return root;
87
152
  }
88
153
 
89
- function open(instance, catalog) {
154
+ function open(instance, catalog, presentation = {}) {
155
+ const host = presentation.container || null;
156
+ const parent = host || instance.document.documentElement;
157
+ if (instance.root.parentElement !== parent) {
158
+ instance.root.close?.();
159
+ parent.appendChild(instance.root);
160
+ }
161
+ instance.embedded = Boolean(host);
162
+ instance.embeddedContainer = host;
163
+ instance.root.dataset.embedded = String(instance.embedded);
164
+ instance.root.setAttribute('role', host ? 'region' : 'dialog');
90
165
  instance.returnFocus = instance.document.activeElement;
91
166
  instance.root.hidden = false;
92
167
  syncTheme(instance);
93
- setCatalog(instance, catalog || instance.catalog);
94
- if (!instance.root.open) instance.root.showModal?.();
168
+ if (!presentation.preserveDraft) setCatalog(instance, catalog || instance.catalog);
169
+ if (!instance.root.open) {
170
+ if (host) instance.root.show?.();
171
+ else instance.root.showModal?.();
172
+ }
173
+ if (presentation.preserveDraft) return;
95
174
  queueMicrotask(() => {
96
- if (!instance.root || instance.root.hidden) return;
175
+ if (!instance.root || instance.root.hidden || instance.root.closest('[hidden]')) return;
97
176
  const target = instance.root.querySelector('[data-provider-row][aria-current="true"]')
98
177
  || instance.root.querySelector('input, button, select, textarea');
99
178
  target?.focus?.();
100
179
  });
101
180
  }
102
181
 
103
- function requestClose(instance) {
104
- if (instance.dirty && !instance.document.defaultView.confirm(instance.tx(
182
+ async function requestClose(instance) {
183
+ if (instance.confirmation) { finishProviderConfirmation(instance, false); return false; }
184
+ if (instance.modelEditor) { closeModelEditor(instance); return false; }
185
+ if (instance.dirty && !await confirmProviderAction(instance, instance.tx(
105
186
  'Discard unsaved provider changes?',
106
187
  '放弃尚未保存的模型服务配置吗?'
107
188
  ))) {
@@ -111,6 +192,9 @@
111
192
  instance.callbacks.onCancelTest?.();
112
193
  instance.busy = '';
113
194
  }
195
+ const secretInput = instance.root.querySelector('[data-provider-field="apiKey"]');
196
+ if (secretInput) secretInput.value = '';
197
+ instance.secretAction = 'unchanged';
114
198
  instance.root.close?.();
115
199
  instance.root.hidden = true;
116
200
  instance.busy = '';
@@ -119,6 +203,7 @@
119
203
  }
120
204
 
121
205
  function setCatalog(instance, catalog, selectedId) {
206
+ finishProviderConfirmation(instance, false, false);
122
207
  instance.catalog = instance.Profiles.normalizeCatalog(catalog || {});
123
208
  const nextId = selectedId || (
124
209
  instance.catalog.providers.some(provider => provider.id === instance.selectedId)
@@ -131,13 +216,17 @@
131
216
  delete instance.root.dataset.dirty;
132
217
  instance.secretAction = 'unchanged';
133
218
  instance.verifications = {};
219
+ instance.verificationStale = false;
220
+ instance.templatePicker = false;
221
+ instance.modelFilter = '';
222
+ closeModelEditor(instance, false);
134
223
  instance.pendingCatalog = null;
135
224
  setStatus(instance, { tone: '', title: '' });
136
225
  render(instance);
137
226
  }
138
227
 
139
228
  function offerCatalog(instance, catalog, selectedId) {
140
- if (!instance.dirty) {
229
+ if (!instance.dirty && !instance.modelEditor && !instance.confirmation) {
141
230
  setCatalog(instance, catalog, selectedId);
142
231
  return true;
143
232
  }
@@ -156,14 +245,19 @@
156
245
 
157
246
  function render(instance) {
158
247
  const tx = instance.tx;
159
- instance.root.querySelector('[data-provider-dialog-title]').textContent = tx('Model API providers', '模型 API 服务');
248
+ const reload = instance.root.querySelector('[data-provider-action="reload"]');
249
+ if (reload) {
250
+ reload.hidden = !instance.embedded;
251
+ reload.textContent = tx('Refresh', '刷新');
252
+ }
253
+ instance.root.querySelector('[data-provider-dialog-title]').textContent = tx('Models & providers', '模型与服务');
160
254
  instance.root.querySelector('[data-provider-dialog-experimental]').textContent = tx(
161
255
  'Third-party experimental',
162
256
  '第三方实验性'
163
257
  );
164
258
  instance.root.querySelector('[data-provider-dialog-subtitle]').textContent = tx(
165
- "Configure experimental third-party integrations used by this project's future Codex runs. Compatibility varies by provider and gateway.",
166
- '配置当前项目后续 Codex 任务使用的实验性第三方集成;兼容性取决于具体模型服务与网关。'
259
+ 'Connect a provider, configure its models, and choose it for this project.',
260
+ '连接模型服务,管理模型,并选择当前项目使用的连接。'
167
261
  );
168
262
  if (instance.callbacks.hasCurrentProject?.() === false) instance.root.querySelector('[data-provider-dialog-subtitle]').textContent = tx(
169
263
  'Manage shared model API profiles. Open a project to choose which provider its future turns use.',
@@ -175,40 +269,32 @@
175
269
  }
176
270
 
177
271
  function renderProviderList(instance) {
178
- const tx = instance.tx;
179
- const list = instance.root.querySelector('[data-provider-list]');
180
- const rows = instance.catalog.providers.map(provider => {
181
- const selected = provider.id === instance.selectedId;
182
- const active = provider.id === getCurrentProjectProviderId(instance);
183
- const diagnosticCount = Object.values(provider.modelDiagnostics || {}).filter(item => item.status === 'tested').length;
184
- const status = active
185
- ? tx('Current project', '当前项目')
186
- : diagnosticCount
187
- ? tx(`Tested ${diagnosticCount}/${Math.max(1, provider.models.length)}`, `已测试 ${diagnosticCount}/${Math.max(1, provider.models.length)}`)
188
- : provider.kind === 'custom'
189
- ? tx('Untested', '未测试')
190
- : '';
191
- return `
192
- <button type="button" class="codex-provider-row" data-provider-row="${escapeAttr(provider.id)}" aria-current="${selected ? 'true' : 'false'}">
193
- <span class="codex-provider-row-main" title="${escapeAttr(provider.name)}">${escapeHtml(provider.name)}</span>
194
- ${status ? `<span class="codex-provider-row-status" data-tone="${active ? 'active' : 'muted'}">${escapeHtml(status)}</span>` : ''}
195
- </button>
196
- `;
197
- });
198
- if (instance.selectedId === '__new__') {
199
- rows.push(`
200
- <button type="button" class="codex-provider-row" data-provider-row="__new__" aria-current="true">
201
- <span class="codex-provider-row-main">${escapeHtml(tx('New provider', '新模型服务'))}</span>
202
- <span class="codex-provider-row-status" data-tone="muted">${escapeHtml(tx('Draft', '草稿'))}</span>
203
- </button>
204
- `);
205
- }
206
- rows.push(`<button type="button" class="codex-provider-add" data-provider-action="add">+ ${escapeHtml(tx('Add provider', '添加模型服务'))}</button>`);
207
- list.innerHTML = rows.join('');
272
+ const tx = instance.tx, list = instance.root.querySelector('[data-provider-list]');
273
+ list.innerHTML = '<label class="codex-provider-search"><span class="codex-provider-sr-only">'
274
+ + escapeHtml(tx('Search providers', '搜索服务')) + '</span><input type="search" data-provider-search value="'
275
+ + escapeAttr(instance.providerFilter) + '" placeholder="' + escapeAttr(tx('Search providers', '搜索服务')) + '"></label>'
276
+ + '<div class="codex-provider-nav-items">' + instance.catalog.providers.map(provider => {
277
+ const active = provider.id === getCurrentProjectProviderId(instance);
278
+ const subtitle = active ? tx('Current project', '当前项目') : provider.kind === 'builtin'
279
+ ? tx('Local authentication', '本地身份验证')
280
+ : tx(provider.models.length + ' models', provider.models.length + ' 个模型');
281
+ const searchText = (provider.name + ' ' + (provider.baseUrl || '')).toLowerCase();
282
+ return '<button type="button" class="codex-provider-row" data-provider-row="' + escapeAttr(provider.id)
283
+ + '" data-provider-search-text="' + escapeAttr(searchText)
284
+ + '" aria-current="' + String(provider.id === instance.selectedId) + '"'
285
+ + (searchText.includes(instance.providerFilter.toLowerCase()) ? '' : ' hidden') + '>'
286
+ + '<span class="codex-provider-nav-mark" aria-hidden="true">'
287
+ + (provider.kind === 'builtin' ? renderProviderTemplateIcon({ id: 'openai-compatible' }) : escapeHtml(provider.name.slice(0, 1).toUpperCase())) + '</span>'
288
+ + '<span class="codex-provider-nav-copy"><span class="codex-provider-row-main" title="' + escapeAttr(provider.name) + '">' + escapeHtml(provider.name)
289
+ + '</span><span class="codex-provider-row-status" data-tone="' + (active ? 'active' : 'muted') + '">'
290
+ + escapeHtml(subtitle) + '</span></span></button>';
291
+ }).join('') + '</div><button type="button" class="codex-provider-add" data-provider-action="add">+ '
292
+ + escapeHtml(tx('Add provider', '添加模型服务')) + '</button>';
208
293
  }
209
294
 
210
295
  function renderDetail(instance) {
211
296
  const detail = instance.root.querySelector('[data-provider-detail]');
297
+ if (instance.templatePicker) { renderTemplatePicker(instance, detail); return; }
212
298
  const provider = getSelectedProvider(instance);
213
299
  if (!provider || provider.kind === 'builtin') {
214
300
  renderBuiltinDetail(instance, detail);
@@ -216,11 +302,6 @@
216
302
  }
217
303
  const tx = instance.tx;
218
304
  const draft = instance.draft || provider;
219
- const defaultModel = (draft.models || []).find(model => model.id === draft.defaultModelId) || {};
220
- const additionalModels = (draft.models || [])
221
- .map(model => model.id)
222
- .filter(id => id !== draft.defaultModelId)
223
- .join('\n');
224
305
  const active = provider.id && provider.id === getCurrentProjectProviderId(instance);
225
306
  const acceptedHost = getEndpointHost(draft.baseUrl);
226
307
  const secretSavedAt = formatSecretSavedAt(instance, provider.secretUpdatedAt);
@@ -246,11 +327,13 @@
246
327
  <span>${escapeHtml(tx('Base URL', '基础 URL'))}</span>
247
328
  <input type="url" data-provider-field="baseUrl" value="${escapeAttr(draft.baseUrl || '')}" placeholder="https://provider.example/v1" spellcheck="false">
248
329
  <small>${escapeHtml(tx('HTTPS is required except for localhost.', '除 localhost 外必须使用 HTTPS。'))}</small>
330
+ <small data-provider-connection-hint hidden></small>
249
331
  </label>
250
332
  <label class="codex-provider-field codex-provider-field--wide">
251
333
  <span>${escapeHtml(tx('API key', 'API 密钥'))}</span>
252
334
  <div class="codex-provider-secret-row">
253
335
  <input type="password" data-provider-field="apiKey" autocomplete="new-password" placeholder="${escapeAttr(provider.hasSecret ? tx('Configured; enter a new key to replace it', '已配置;输入新密钥即可替换') : tx('Optional for local no-auth endpoints', '本地无鉴权端点可留空'))}">
336
+ <button type="button" class="codex-provider-inline-button" data-provider-action="toggle-secret">${escapeHtml(tx('Show', '显示'))}</button>
254
337
  ${provider.hasSecret ? `<button type="button" class="codex-provider-inline-button" data-provider-action="clear-secret">${escapeHtml(tx('Clear', '清除'))}</button>` : ''}
255
338
  </div>
256
339
  <small data-provider-secret-note>${provider.hasSecret
@@ -259,16 +342,8 @@
259
342
  : tx('Stored by the Native Host. The plaintext is never returned to the Extension.', '由 Native Host 保管。扩展不会读回密钥明文。'))}`
260
343
  : escapeHtml(tx('No API key is currently stored. Local no-auth endpoints may leave this empty.', '当前未存储 API 密钥。本地无鉴权端点可以留空。'))}</small>
261
344
  </label>
262
- <label class="codex-provider-field codex-provider-field--wide">
263
- <span>${escapeHtml(tx('Default model', '默认模型'))}</span>
264
- <input type="text" data-provider-field="defaultModelId" value="${escapeAttr(draft.defaultModelId || '')}" placeholder="model-id" spellcheck="false">
265
- </label>
266
- <label class="codex-provider-field codex-provider-field--wide">
267
- <span>${escapeHtml(tx('Additional models', '其他模型'))}</span>
268
- <textarea data-provider-field="additionalModels" rows="3" placeholder="one-model-id-per-line" spellcheck="false">${escapeHtml(additionalModels)}</textarea>
269
- <small>${escapeHtml(tx('Enter one model ID per line.', '每行填写一个模型 ID。'))}</small>
270
- </label>
271
345
  </div>
346
+ <section class="codex-provider-model-section" data-provider-model-section></section>
272
347
  <details class="codex-provider-advanced">
273
348
  <summary>${escapeHtml(tx('Advanced compatibility', '高级兼容设置'))}</summary>
274
349
  <div class="codex-provider-form-grid">
@@ -281,15 +356,6 @@
281
356
  <option value="anthropic" ${draft.wireApiPreference === 'anthropic' ? 'selected' : ''}>Anthropic Messages</option>
282
357
  </select>
283
358
  </label>
284
- <label class="codex-provider-field">
285
- <span>${escapeHtml(tx('Upstream response mode', '上游响应模式'))}</span>
286
- <select data-provider-field="upstreamResponseMode">
287
- <option value="auto" ${defaultModel.upstreamResponseMode === 'auto' || !defaultModel.upstreamResponseMode ? 'selected' : ''}>${escapeHtml(tx('Auto (test streaming, then buffered)', '自动(先测流式,再测缓冲)'))}</option>
288
- <option value="streaming" ${defaultModel.upstreamResponseMode === 'streaming' ? 'selected' : ''}>${escapeHtml(tx('Streaming', '流式'))}</option>
289
- <option value="buffered" ${defaultModel.upstreamResponseMode === 'buffered' ? 'selected' : ''}>${escapeHtml(tx('Buffered', '缓冲'))}</option>
290
- </select>
291
- <small>${escapeHtml(tx('Applied to the default model. Buffered keeps Codex streaming locally while the provider returns one complete response.', '应用于默认模型。缓冲模式仍向 Codex 本地流式输出,但等待服务商返回完整响应。'))}</small>
292
- </label>
293
359
  <label class="codex-provider-field">
294
360
  <span>${escapeHtml(tx('Request timeout', '请求超时'))}</span>
295
361
  <select data-provider-field="requestTimeoutMs">
@@ -311,7 +377,7 @@
311
377
  <input type="text" data-provider-field="apiKeyHeaderName" value="${escapeAttr(draft.apiKeyHeaderName || '')}" placeholder="X-API-Key" spellcheck="false">
312
378
  </label>
313
379
  <label class="codex-provider-field">
314
- <span>${escapeHtml(tx('Context window', '上下文窗口'))}</span>
380
+ <span>${escapeHtml(tx('Default context for new models', '新模型的默认上下文'))}</span>
315
381
  <input type="number" data-provider-field="contextWindow" min="8192" max="4000000" step="1024" value="${Number(draft.contextWindow) || 262144}">
316
382
  </label>
317
383
  <label class="codex-provider-field">
@@ -450,6 +516,14 @@
450
516
  <span class="codex-provider-test-state" data-provider-test-state>${escapeHtml(formatVerification(instance, provider, draft.defaultModelId))}</span>
451
517
  </div>
452
518
  `;
519
+ const protocol = detail.querySelector('[data-provider-field="wireApiPreference"]')?.closest('label');
520
+ const connection = detail.querySelector('.codex-provider-form-grid');
521
+ if (protocol && connection) { protocol.classList.add('codex-provider-field--wide'); connection.prepend(protocol); }
522
+ const name = detail.querySelector('[data-provider-field="name"]')?.closest('label');
523
+ const heading = detail.querySelector('.codex-provider-detail-titleline h3');
524
+ if (name && heading) { name.classList.add('codex-provider-name-field'); heading.replaceWith(name); }
525
+ updatePresetConnectionHint(instance);
526
+ renderModelList(instance);
453
527
  }
454
528
 
455
529
  function renderBuiltinDetail(instance, detail) {
@@ -484,8 +558,8 @@
484
558
  const actions = instance.root.querySelector('[data-provider-footer-actions]');
485
559
  const provider = getSelectedProvider(instance);
486
560
  const tx = instance.tx;
487
- if (!provider || provider.kind === 'builtin') {
488
- actions.innerHTML = `<button type="button" class="codex-provider-secondary-button" data-provider-action="close">${escapeHtml(tx('Close', '关闭'))}</button>`;
561
+ if (instance.templatePicker || !provider || provider.kind === 'builtin') {
562
+ actions.innerHTML = instance.embedded ? '' : `<button type="button" class="codex-provider-secondary-button" data-provider-action="close">${escapeHtml(tx('Close', '关闭'))}</button>`;
489
563
  return;
490
564
  }
491
565
  const active = provider.id && provider.id === getCurrentProjectProviderId(instance);
@@ -506,7 +580,7 @@
506
580
  ${provider.id ? `<button type="button" class="codex-provider-danger-button" data-provider-action="delete">${escapeHtml(tx('Delete', '删除'))}</button>` : ''}
507
581
  ${instance.pendingCatalog ? `<button type="button" class="codex-provider-secondary-button" data-provider-action="reload-external">${escapeHtml(tx('Reload latest', '加载最新配置'))}</button>` : ''}
508
582
  <span class="codex-provider-footer-spacer"></span>
509
- <button type="button" class="codex-provider-secondary-button" data-provider-action="close">${escapeHtml(tx('Cancel', '取消'))}</button>
583
+ ${!instance.embedded || instance.dirty ? `<button type="button" class="codex-provider-secondary-button" data-provider-action="close">${escapeHtml(instance.embedded ? tx('Discard changes', '放弃更改') : tx('Cancel', '取消'))}</button>` : ''}
510
584
  ${actionState.showSave ? `<button type="button" class="codex-provider-secondary-button" data-provider-action="save"${saveDisabled}>${escapeHtml(tx('Save', '保存'))}</button>` : ''}
511
585
  ${actionState.showSaveAndUse ? `<button type="button" class="codex-provider-primary-button" data-provider-action="save-use"${saveDisabled}>${escapeHtml(tx('Save and use for this project', '保存并用于当前项目'))}</button>` : ''}
512
586
  ${actionState.showUse ? `<button type="button" class="codex-provider-primary-button" data-provider-action="use"${useDisabled}>${escapeHtml(tx('Use for this project', '用于当前项目'))}</button>` : ''}
@@ -526,7 +600,32 @@
526
600
  };
527
601
  }
528
602
 
529
- function handleClick(instance, event) {
603
+ async function handleClick(instance, event) {
604
+ const answer = event.target.closest('[data-provider-confirm-answer]');
605
+ if (answer) {
606
+ event.preventDefault();
607
+ finishProviderConfirmation(instance, answer.dataset.providerConfirmAnswer === 'confirm');
608
+ return;
609
+ }
610
+ if (instance.confirmation) return;
611
+ const template = event.target.closest('[data-provider-template]');
612
+ if (template && (!instance.busy || instance.busy === 'failed')) {
613
+ instance.draft = instance.Profiles.buildPresetDraft(template.dataset.providerTemplate);
614
+ instance.draft.name = providerTemplateTitle(instance, template.dataset.providerTemplate);
615
+ instance.templatePicker = false;
616
+ instance.dirty = true;
617
+ instance.root.dataset.dirty = 'true';
618
+ instance.secretAction = 'unchanged';
619
+ instance.verificationStale = true;
620
+ render(instance);
621
+ instance.root.querySelector('[data-provider-field="apiKey"]')?.focus();
622
+ return;
623
+ }
624
+ const modelAction = event.target.closest('[data-provider-model-action]');
625
+ if (modelAction) {
626
+ if (!instance.busy || instance.busy === 'failed') handleModelAction(instance, modelAction.dataset.providerModelAction, modelAction.dataset.modelId || '');
627
+ return;
628
+ }
530
629
  const action = event.target.closest('[data-provider-action]')?.dataset.providerAction;
531
630
  const providerRow = event.target.closest('[data-provider-row]');
532
631
  if (providerRow) {
@@ -538,16 +637,41 @@
538
637
  }
539
638
  event.preventDefault();
540
639
  if (action === 'close') {
541
- requestClose(instance);
640
+ if (instance.embedded) {
641
+ const host = instance.embeddedContainer;
642
+ const catalog = instance.pendingCatalog?.catalog || instance.catalog;
643
+ if (await requestClose(instance) && host?.isConnected) open(instance, catalog, { container: host });
644
+ } else requestClose(instance);
645
+ return;
646
+ }
647
+ if (action === 'reload') {
648
+ if (!instance.busy || instance.busy === 'failed') instance.callbacks.onReload?.();
542
649
  return;
543
650
  }
544
651
  if (action === 'add') {
545
- selectProvider(instance, '__new__');
652
+ if (instance.selectedId === '__new__') {
653
+ if (instance.dirty && !await confirmProviderAction(instance, instance.tx('Discard unsaved provider changes?', '放弃尚未保存的模型服务配置吗?'))) return;
654
+ instance.draft = instance.Profiles.buildEmptyDraft();
655
+ instance.dirty = false;
656
+ delete instance.root.dataset.dirty;
657
+ instance.templatePicker = true;
658
+ render(instance);
659
+ } else selectProvider(instance, '__new__');
660
+ return;
661
+ }
662
+ if (action === 'templates-back') {
663
+ selectProvider(instance, instance.previousProviderId || 'builtin');
664
+ return;
665
+ }
666
+ if (action === 'toggle-secret') {
667
+ const input = instance.root.querySelector('[data-provider-field="apiKey"]');
668
+ if (input) input.type = input.type === 'password' ? 'text' : 'password';
669
+ event.target.closest('button').textContent = instance.tx(input?.type === 'text' ? 'Hide' : 'Show', input?.type === 'text' ? '隐藏' : '显示');
546
670
  return;
547
671
  }
548
672
  if (action === 'reload-external') {
549
673
  if (!instance.pendingCatalog) return;
550
- if (instance.dirty && !instance.document.defaultView.confirm(instance.tx(
674
+ if (instance.dirty && !await confirmProviderAction(instance, instance.tx(
551
675
  'Discard this draft and load the latest provider settings?',
552
676
  '放弃当前草稿并加载最新模型服务配置吗?'
553
677
  ))) {
@@ -566,7 +690,7 @@
566
690
  '立即删除已存储的 API 密钥吗?重新加载已保存配置时,未保存的表单改动会被放弃。'
567
691
  )
568
692
  : instance.tx('Remove the stored API key now?', '立即删除已存储的 API 密钥吗?');
569
- if (instance.document.defaultView.confirm(warning)) {
693
+ if (await confirmProviderAction(instance, warning, 'secret')) {
570
694
  instance.callbacks.onClearSecret?.({
571
695
  profileId: provider.id,
572
696
  expectedRevision: provider.revision
@@ -588,15 +712,16 @@
588
712
  }
589
713
  if (action === 'delete') {
590
714
  const context = readContext(instance);
591
- if (instance.document.defaultView.confirm(instance.tx(
715
+ if (await confirmProviderAction(instance, instance.tx(
592
716
  `Delete ${context.draft.name}?`,
593
717
  `删除 ${context.draft.name} 吗?`
594
- ))) {
718
+ ), 'provider')) {
595
719
  instance.callbacks.onDelete?.(context);
596
720
  }
597
721
  return;
598
722
  }
599
723
  if (action === 'test') {
724
+ if (!validateConnectionForm(instance)) return;
600
725
  const modelId = getSelectedTestModelId(instance);
601
726
  invalidateVerification(instance, false, modelId);
602
727
  instance.callbacks.onTest?.(readContext(instance));
@@ -628,6 +753,7 @@
628
753
  return;
629
754
  }
630
755
  if (action === 'save' || action === 'save-use') {
756
+ if (!validateConnectionForm(instance)) return;
631
757
  const context = readContext(instance);
632
758
  if (action === 'save-use' && !instance.root.querySelector('[data-provider-disclosure]')?.checked) {
633
759
  setStatus(instance, {
@@ -641,6 +767,21 @@
641
767
  }
642
768
 
643
769
  function handleInput(instance, event) {
770
+ if (instance.modelEditor && event.target.closest('[data-provider-model-editor]')) {
771
+ updateModelReasoningSummary(instance);
772
+ return;
773
+ }
774
+ if (event.target.matches('[data-provider-search]')) {
775
+ instance.providerFilter = event.target.value;
776
+ for (const row of instance.root.querySelectorAll('[data-provider-search-text]'))
777
+ row.hidden = !row.dataset.providerSearchText.includes(instance.providerFilter.toLowerCase());
778
+ return;
779
+ }
780
+ if (event.target.matches('[data-provider-model-search]')) {
781
+ instance.modelFilter = event.target.value;
782
+ filterModelRows(instance);
783
+ return;
784
+ }
644
785
  if (event.target.matches('[data-provider-test-model]')) {
645
786
  refreshTestState(instance);
646
787
  return;
@@ -650,13 +791,16 @@
650
791
  if (event.target.dataset.providerField === 'apiKey') {
651
792
  instance.secretAction = event.target.value ? 'replace' : (getSelectedProvider(instance)?.hasSecret ? 'unchanged' : 'clear');
652
793
  }
653
- if (field === 'baseUrl') {
794
+ const destinationChanged = field === 'baseUrl'
795
+ || (field === 'wireApiPreference' && syncPresetProtocol(instance, event.target.value));
796
+ if (destinationChanged) {
654
797
  const disclosure = instance.root.querySelector('[data-provider-disclosure]');
655
798
  if (disclosure) disclosure.checked = false;
656
799
  updateDisclosureText(instance);
657
800
  }
658
801
  markDirty(instance, { invalidateVerification: field !== 'name' });
659
- if (field === 'baseUrl') {
802
+ if (field === 'wireApiPreference') updatePresetConnectionHint(instance);
803
+ if (destinationChanged) {
660
804
  renderFooter(instance);
661
805
  applyBusyState(instance);
662
806
  }
@@ -664,6 +808,24 @@
664
808
  }
665
809
 
666
810
  function handleKeydown(instance, event) {
811
+ if (instance.confirmation) {
812
+ if (event.key === 'Escape') {
813
+ event.preventDefault(); event.stopPropagation();
814
+ finishProviderConfirmation(instance, false);
815
+ }
816
+ return;
817
+ }
818
+ if (instance.modelEditor) {
819
+ if (event.key === 'Escape') {
820
+ event.preventDefault(); event.stopPropagation(); closeModelEditor(instance);
821
+ } else if (event.key === 'Enter' && !event.isComposing
822
+ && event.target.matches('input:not([type="checkbox"])')) {
823
+ event.preventDefault(); commitModelEditor(instance);
824
+ }
825
+ return;
826
+ }
827
+ // The outer settings dialog owns focus traversal and Escape when embedded.
828
+ if (instance.embedded) return;
667
829
  if (event.key === 'Escape') {
668
830
  event.preventDefault();
669
831
  requestClose(instance);
@@ -688,16 +850,20 @@
688
850
  }
689
851
  }
690
852
 
691
- function selectProvider(instance, id) {
853
+ async function selectProvider(instance, id) {
692
854
  if (id === instance.selectedId) {
693
855
  return;
694
856
  }
695
- if (instance.dirty && !instance.document.defaultView.confirm(instance.tx(
857
+ if (instance.dirty && !await confirmProviderAction(instance, instance.tx(
696
858
  'Discard unsaved provider changes?',
697
859
  '放弃尚未保存的模型服务配置吗?'
698
860
  ))) {
699
861
  return;
700
862
  }
863
+ if (id === '__new__') instance.previousProviderId = instance.selectedId;
864
+ instance.templatePicker = id === '__new__';
865
+ instance.modelFilter = '';
866
+ instance.verificationStale = false;
701
867
  instance.selectedId = id;
702
868
  instance.draft = id === '__new__'
703
869
  ? instance.Profiles.buildEmptyDraft()
@@ -714,32 +880,16 @@
714
880
  function readContext(instance) {
715
881
  const provider = getSelectedProvider(instance) || instance.Profiles.buildEmptyDraft();
716
882
  const get = name => instance.root.querySelector(`[data-provider-field="${name}"]`);
717
- const defaultModelId = String(get('defaultModelId')?.value || '').trim();
718
- const modelIds = [defaultModelId, ...String(get('additionalModels')?.value || '').split(/\r?\n/)]
719
- .map(value => value.trim())
720
- .filter((value, index, values) => value && values.indexOf(value) === index);
721
- const existingModels = new Map((provider.models || []).map(model => [model.id, model]));
722
- const upstreamResponseMode = get('upstreamResponseMode')?.value || 'auto';
883
+ const modelDraft = instance.draft || provider;
884
+ const defaultModelId = modelDraft.defaultModelId || '';
723
885
  const draft = {
724
886
  name: String(get('name')?.value || '').trim(),
725
887
  baseUrl: String(get('baseUrl')?.value || '').trim(),
726
888
  wireApiPreference: get('wireApiPreference')?.value || 'auto',
727
- models: modelIds.map(id => {
728
- const existing = existingModels.get(id) || {};
729
- return {
730
- ...existing,
731
- id,
732
- label: existing.label || id,
733
- reasoningEfforts: Array.isArray(existing.reasoningEfforts) ? existing.reasoningEfforts : [],
734
- upstreamResponseMode: id === defaultModelId
735
- ? upstreamResponseMode
736
- : existing.upstreamResponseMode || 'auto',
737
- resolvedUpstreamResponseMode: existing.resolvedUpstreamResponseMode || '',
738
- contextWindow: Number(get('contextWindow')?.value || 262144),
739
- supportsParallelToolCalls: Boolean(get('supportsParallelToolCalls')?.checked),
740
- inputModalities: String(get('inputModalities')?.value || 'text').split(',')
741
- };
742
- }),
889
+ models: (modelDraft.models || []).map(model => ({
890
+ ...model, reasoningEfforts: [...(model.reasoningEfforts || [])],
891
+ inputModalities: [...(model.inputModalities || ['text'])]
892
+ })),
743
893
  defaultModelId,
744
894
  requestTimeoutMs: Number(get('requestTimeoutMs')?.value || 30000),
745
895
  reasoningAdapter: get('reasoningAdapter')?.value || 'auto',
@@ -794,7 +944,9 @@
794
944
  instance.root.dataset.dirty = 'true';
795
945
  if (options.invalidateVerification !== false) {
796
946
  instance.verifications = {};
947
+ instance.verificationStale = true;
797
948
  invalidateVerification(instance, true);
949
+ refreshModelStatuses(instance);
798
950
  }
799
951
  if (footerNeedsRefresh) {
800
952
  // Saved providers initially render Use (inactive) or no primary action
@@ -813,6 +965,7 @@
813
965
  setStatus(instance, { tone: '', title: '' });
814
966
  }
815
967
  syncTestAction(instance);
968
+ refreshModelStatuses(instance);
816
969
  applyBusyState(instance);
817
970
  }
818
971
 
@@ -822,7 +975,9 @@
822
975
  const canActivate = canActivateCurrentProvider(instance);
823
976
  instance.root.dataset.busy = instance.busy || '';
824
977
  for (const element of instance.root.querySelectorAll('input, select, textarea, button')) {
825
- if (element.matches('[data-provider-action="close"]')) {
978
+ if (element.matches('[data-provider-confirm-answer]')) {
979
+ element.disabled = false;
980
+ } else if (element.matches('[data-provider-action="close"]')) {
826
981
  element.disabled = false;
827
982
  } else if (element.matches('[data-provider-action="cancel-test"]')) {
828
983
  element.disabled = false;
@@ -900,7 +1055,9 @@
900
1055
 
901
1056
  function canSaveCurrentDraft(instance) {
902
1057
  const provider = getSelectedProvider(instance);
903
- return Boolean(provider && provider.kind === 'custom');
1058
+ const draft = instance.draft || provider;
1059
+ return Boolean(!instance.templatePicker && !instance.modelEditor && provider?.kind === 'custom'
1060
+ && draft?.models?.length && draft.models.some(model => model.id === draft.defaultModelId));
904
1061
  }
905
1062
 
906
1063
  function canActivateCurrentProvider(instance) {
@@ -914,11 +1071,12 @@
914
1071
  function formatVerification(instance, provider, modelId) {
915
1072
  const transient = instance.verifications[modelId];
916
1073
  if (transient?.status === 'failed') {
917
- return instance.tx('Failed in this dialog', '本次测试失败');
1074
+ return instance.tx('Failed in this dialog', '本次测试失败') + (transient.errorCode ? ' · ' + transient.errorCode : '');
918
1075
  }
919
1076
  if (transient?.status === 'tested') {
920
1077
  return instance.tx('Tested for this draft', '当前草稿已测试');
921
1078
  }
1079
+ if (instance.verificationStale) return instance.tx('Settings changed; retest when ready', '配置已更改,可重新测试');
922
1080
  const diagnostic = provider?.modelDiagnostics?.[modelId];
923
1081
  if (diagnostic?.status === 'tested') {
924
1082
  const mode = diagnostic.upstreamResponseMode;
@@ -935,8 +1093,9 @@
935
1093
  const provider = getSelectedProvider(instance);
936
1094
  const modelId = getSelectedTestModelId(instance) || provider?.defaultModelId;
937
1095
  state.textContent = formatVerification(instance, provider, modelId);
938
- const status = instance.verifications[modelId]?.status || provider?.modelDiagnostics?.[modelId]?.status || '';
1096
+ const status = instance.verifications[modelId]?.status || (!instance.verificationStale && provider?.modelDiagnostics?.[modelId]?.status) || '';
939
1097
  state.dataset.tone = status === 'failed' ? 'failed' : status === 'tested' ? 'success' : '';
1098
+ refreshModelStatuses(instance);
940
1099
  }
941
1100
 
942
1101
  function getSelectedTestModelId(instance) {
@@ -947,7 +1106,43 @@
947
1106
  return String(instance.root.querySelector('[data-provider-field="baseUrl"]')?.value || '').trim();
948
1107
  }
949
1108
 
1109
+ function syncPresetProtocol(instance, protocol) {
1110
+ const get = name => instance.root.querySelector('[data-provider-field="' + name + '"]');
1111
+ if (get('fullEndpoint')?.checked) return false;
1112
+ const info = instance.Profiles.getPresetConnectionInfo(get('baseUrl')?.value);
1113
+ const next = info && instance.Profiles.getPresetConnection(info.id, protocol);
1114
+ if (!next || next.baseUrl === info.connection.baseUrl) return false;
1115
+ get('baseUrl').value = next.baseUrl;
1116
+ // Respect manually overridden authentication and reasoning settings.
1117
+ for (const field of ['authMode', 'reasoningAdapter']) {
1118
+ const input = get(field);
1119
+ if (input?.value === info.connection[field]) input.value = next[field];
1120
+ }
1121
+ return true;
1122
+ }
1123
+
1124
+ function updatePresetConnectionHint(instance) {
1125
+ const hint = instance.root.querySelector('[data-provider-connection-hint]');
1126
+ if (!hint) return;
1127
+ const info = instance.Profiles.getPresetConnectionInfo(getCurrentBaseUrl(instance));
1128
+ const protocol = instance.root.querySelector('[data-provider-field="wireApiPreference"]')?.value;
1129
+ let text = '';
1130
+ if (info && protocol !== 'auto' && protocol !== info.connection.wireApiPreference) {
1131
+ text = instance.tx('This preset address uses a different protocol. Enter an endpoint matching the selected API format.',
1132
+ '此预设地址与所选协议不同,请填写该协议对应的地址。');
1133
+ } else if (info?.id === 'kimi') {
1134
+ text = instance.tx('This is the Kimi Open Platform China endpoint. Kimi Code and international keys use their own endpoints.',
1135
+ '此处为 Kimi 开放平台中国站地址;Kimi Code 和国际站密钥需要各自对应的端点。');
1136
+ } else if (info?.id === 'glm' && info.connection.wireApiPreference === 'anthropic') {
1137
+ text = instance.tx('GLM Anthropic access and the standard pay-as-you-go API use different endpoints. Confirm the key has access to this endpoint; Chat uses the standard API.',
1138
+ 'GLM Anthropic 接入与通用按量 API 的端点不同。请确认密钥具备此端点的权限;Chat 使用通用 API 地址。');
1139
+ }
1140
+ hint.textContent = text;
1141
+ hint.hidden = !text;
1142
+ }
1143
+
950
1144
  function updateDisclosureText(instance) {
1145
+ updatePresetConnectionHint(instance);
951
1146
  const text = instance.root.querySelector('[data-provider-disclosure-text]');
952
1147
  if (!text) return;
953
1148
  const baseUrl = getCurrentBaseUrl(instance) || instance.tx('this endpoint', '此端点');
@@ -971,6 +1166,361 @@
971
1166
  button.setAttribute('aria-label', actionLabel);
972
1167
  }
973
1168
 
1169
+
1170
+
1171
+ function providerTemplateTitle(instance, id) {
1172
+ if (id === 'kimi') return 'Kimi';
1173
+ if (id === 'glm') return 'GLM';
1174
+ if (id === 'deepseek') return 'DeepSeek';
1175
+ if (id === 'openai-compatible') return instance.tx('OpenAI-compatible API', 'OpenAI 兼容接口');
1176
+ if (id === 'anthropic-compatible') return instance.tx('Anthropic-compatible API', 'Anthropic 兼容接口');
1177
+ return instance.tx('Custom connection', '自定义连接');
1178
+ }
1179
+
1180
+ function confirmProviderAction(instance, message, kind = 'discard') {
1181
+ if (!instance.root || instance.root.hidden || instance.confirmation) return Promise.resolve(false);
1182
+ const modal = instance.root.querySelector('[data-provider-confirmation]');
1183
+ if (!modal) return Promise.resolve(false);
1184
+ const tx = instance.tx;
1185
+ const titles = {
1186
+ discard: tx('Unsaved changes', '尚未保存的修改'),
1187
+ secret: tx('Remove API key?', '清除 API 密钥?'),
1188
+ provider: tx('Delete connection?', '删除连接?'),
1189
+ model: tx('Remove model?', '移除模型?')
1190
+ };
1191
+ const labels = {
1192
+ discard: tx('Discard changes', '放弃修改'),
1193
+ secret: tx('Remove key', '清除密钥'),
1194
+ provider: tx('Delete connection', '删除连接'),
1195
+ model: tx('Remove model', '移除模型')
1196
+ };
1197
+ const cancel = kind === 'discard' ? tx('Keep editing', '继续编辑') : tx('Cancel', '取消');
1198
+ const provider = getSelectedProvider(instance);
1199
+ return new Promise(resolve => {
1200
+ instance.confirmation = { resolve, selectedId: instance.selectedId,
1201
+ revision: provider?.revision || 0, returnFocus: instance.document.activeElement };
1202
+ modal.innerHTML = '<header><span class="codex-provider-confirm-brand">Codex Overleaf Link</span>'
1203
+ + '<h3 id="codex-provider-confirm-title">' + escapeHtml(titles[kind] || titles.discard) + '</h3></header>'
1204
+ + '<p id="codex-provider-confirm-message">' + escapeHtml(message) + '</p>'
1205
+ + '<footer><button type="button" class="codex-provider-secondary-button" data-provider-confirm-answer="cancel">'
1206
+ + escapeHtml(cancel) + '</button><button type="button" class="codex-provider-danger-button" data-provider-confirm-answer="confirm">'
1207
+ + escapeHtml(labels[kind] || labels.discard) + '</button></footer>';
1208
+ try {
1209
+ if (modal.showModal) modal.showModal(); else modal.setAttribute('open', '');
1210
+ modal.querySelector('[data-provider-confirm-answer="cancel"]')?.focus();
1211
+ } catch (_error) {
1212
+ finishProviderConfirmation(instance, false);
1213
+ setStatus(instance, { tone: 'failed', title: tx('The confirmation could not be opened. No changes were discarded.',
1214
+ '确认窗口无法打开,未丢弃任何修改。') });
1215
+ }
1216
+ });
1217
+ }
1218
+
1219
+ function finishProviderConfirmation(instance, confirmed, restoreFocus = true) {
1220
+ const pending = instance.confirmation;
1221
+ if (!pending) return;
1222
+ instance.confirmation = null;
1223
+ const modal = instance.root?.querySelector('[data-provider-confirmation]');
1224
+ modal?.close?.();
1225
+ modal?.removeAttribute('open');
1226
+ modal?.replaceChildren();
1227
+ const sameTarget = Boolean(instance.root && !instance.root.hidden
1228
+ && instance.selectedId === pending.selectedId
1229
+ && (getSelectedProvider(instance)?.revision || 0) === pending.revision);
1230
+ pending.resolve(confirmed === true && sameTarget);
1231
+ if (restoreFocus) pending.returnFocus?.focus?.();
1232
+ }
1233
+
1234
+ function renderProviderTemplateIcon(preset) {
1235
+ const vendorPath = PROVIDER_VENDOR_ICON_PATHS[preset.id] || '';
1236
+ const path = preset.id === 'openai-compatible' ? CODEX_BLOSSOM_PATH
1237
+ : preset.id === 'anthropic-compatible' ? ANTHROPIC_LOGO_PATH : vendorPath;
1238
+ return path
1239
+ ? '<svg class="codex-provider-brand-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path fill-rule="'
1240
+ + (vendorPath ? 'evenodd' : 'nonzero') + '" d="' + path + '"></path></svg>'
1241
+ : escapeHtml(preset.short || '+');
1242
+ }
1243
+
1244
+ function renderTemplatePicker(instance, detail) {
1245
+ const tx = instance.tx;
1246
+ detail.innerHTML = '<div class="codex-provider-template-head"><button type="button" class="codex-provider-inline-button" data-provider-action="templates-back">'
1247
+ + escapeHtml(tx('Back', '返回')) + '</button><h3>' + escapeHtml(tx('Add a connection', '添加连接')) + '</h3></div>'
1248
+ + '<p class="codex-provider-help">' + escapeHtml(tx(
1249
+ 'Service shortcuts default to Anthropic Messages. Chat Completions remains available; preset addresses follow the selected protocol. Add the matching API key and model IDs separately.',
1250
+ '服务快捷入口默认采用 Anthropic Messages,也可切换 Chat Completions,预设地址随协议联动。请另行填写对应密钥和模型 ID。')) + '</p>'
1251
+ + '<div class="codex-provider-template-grid">' + instance.Profiles.getProviderPresets().map(preset =>
1252
+ '<button type="button" class="codex-provider-template" data-provider-template="' + escapeAttr(preset.id) + '">'
1253
+ + '<span class="codex-provider-template-mark" aria-hidden="true">' + renderProviderTemplateIcon(preset) + '</span>'
1254
+ + '<span><strong>' + escapeHtml(providerTemplateTitle(instance, preset.id))
1255
+ + '</strong><small>' + escapeHtml(preset.id === 'custom' ? tx('Gateway or self-hosted API', '网关或自托管 API')
1256
+ : preset.baseUrl ? (preset.wireApiPreference === 'anthropic' ? 'Anthropic Messages' : 'Chat Completions') + ' · ' + getEndpointHost(preset.baseUrl)
1257
+ : preset.wireApiPreference === 'anthropic' ? 'Anthropic Messages'
1258
+ : preset.wireApiPreference === 'responses' ? 'OpenAI Responses' : 'Responses / Chat Completions')
1259
+ + '</small></span><span aria-hidden="true">›</span></button>').join('') + '</div>'
1260
+ + '<p class="codex-provider-help">' + escapeHtml(tx(
1261
+ 'Adding or saving a connection does not switch the current project. Credentials remain with the Native Host.',
1262
+ '添加或保存连接不会切换当前项目。已保存的密钥仍由 Native Host 保管。')) + '</p>';
1263
+ }
1264
+
1265
+ function modelButton(action, id, label, path, selected = false) {
1266
+ return '<button type="button" class="codex-provider-model-action" data-provider-model-action="'
1267
+ + action + '" data-model-id="' + escapeAttr(id) + '" title="' + escapeAttr(label)
1268
+ + '" aria-label="' + escapeAttr(label) + '"' + (selected ? ' data-selected="true"' : '') + '>'
1269
+ + '<svg viewBox="0 0 20 20" aria-hidden="true"><path d="' + path + '"></path></svg></button>';
1270
+ }
1271
+
1272
+ function renderModelList(instance) {
1273
+ const section = instance.root.querySelector('[data-provider-model-section]');
1274
+ if (!section) return;
1275
+ const tx = instance.tx, provider = getSelectedProvider(instance), draft = instance.draft || provider;
1276
+ const models = draft.models || [];
1277
+ section.innerHTML = '<div class="codex-provider-section-head"><div><h4>' + escapeHtml(tx('Models', '模型'))
1278
+ + ' <span>' + models.length + '/32</span></h4><p>' + escapeHtml(tx(
1279
+ 'Model IDs are sent exactly as configured.', '模型 ID 将按配置原样发送。')) + '</p></div>'
1280
+ + '<button type="button" class="codex-provider-inline-button" data-provider-model-action="add">+ '
1281
+ + escapeHtml(tx('Add model', '添加模型')) + '</button></div>'
1282
+ + (models.length ? '<label class="codex-provider-search"><span class="codex-provider-sr-only">'
1283
+ + escapeHtml(tx('Search models', '搜索模型')) + '</span><input type="search" data-provider-model-search value="'
1284
+ + escapeAttr(instance.modelFilter) + '" placeholder="' + escapeAttr(tx('Search models', '搜索模型')) + '"></label>' : '')
1285
+ + '<div class="codex-provider-model-list">' + models.map(model => {
1286
+ const isDefault = model.id === draft.defaultModelId;
1287
+ const context = Number(model.contextWindow) || 262144;
1288
+ const facts = [
1289
+ context >= 1000000 ? (context / 1000000).toFixed(1).replace(/\.0$/, '') + 'M' : Math.round(context / 1024) + 'K',
1290
+ (model.inputModalities || []).includes('image') ? tx('Text + image', '文本与图像') : tx('Text', '文本')
1291
+ ];
1292
+ return '<div class="codex-provider-model-row" data-provider-model-row data-provider-model-search-text="'
1293
+ + escapeAttr((model.id + ' ' + model.label).toLowerCase()) + '">'
1294
+ + '<button type="button" class="codex-provider-model-identity" data-provider-model-action="edit" data-model-id="' + escapeAttr(model.id) + '">'
1295
+ + '<strong>' + escapeHtml(model.label || model.id) + (isDefault ? '<span class="codex-provider-default-tag">'
1296
+ + escapeHtml(tx('Default', '默认')) + '</span>' : '') + '</strong>'
1297
+ + (model.label && model.label !== model.id ? '<code>' + escapeHtml(model.id) + '</code>' : '')
1298
+ + '<small>' + escapeHtml(facts.join(' · ')) + '</small></button>'
1299
+ + '<div class="codex-provider-model-actions">'
1300
+ + modelButton('test', model.id, tx('Test ', '测试 ') + model.id, 'M7 4l9 6-9 6V4Z')
1301
+ + modelButton('default', model.id, tx('Set as default: ', '设为默认:') + model.id, 'm10 2 2.5 5 5.5.8-4 3.9.9 5.5-4.9-2.6-4.9 2.6.9-5.5-4-3.9L7.5 7Z', isDefault)
1302
+ + modelButton('remove', model.id, tx('Remove ', '移除 ') + model.id, 'M5 5l10 10M15 5 5 15')
1303
+ + '</div><span class="codex-provider-model-status" data-provider-model-status="' + escapeAttr(model.id) + '"></span></div>';
1304
+ }).join('') + '</div>'
1305
+ + (!models.length ? '<div class="codex-provider-model-empty">' + escapeHtml(tx(
1306
+ 'Add the first model to finish this connection. No model is inferred from the provider name.',
1307
+ '添加第一个模型以完成配置。服务名称不会被当成模型 ID。')) + '</div>' : '')
1308
+ + '<p class="codex-provider-help" data-provider-model-no-results hidden>' + escapeHtml(tx('No matching models.', '没有匹配的模型。')) + '</p>';
1309
+ refreshModelStatuses(instance);
1310
+ filterModelRows(instance);
1311
+ }
1312
+
1313
+ function filterModelRows(instance) {
1314
+ let visible = 0;
1315
+ const rows = instance.root.querySelectorAll('[data-provider-model-row]');
1316
+ for (const row of rows) {
1317
+ row.hidden = !row.dataset.providerModelSearchText.includes(instance.modelFilter.toLowerCase());
1318
+ if (!row.hidden) visible++;
1319
+ }
1320
+ const empty = instance.root.querySelector('[data-provider-model-no-results]');
1321
+ if (empty) empty.hidden = !rows.length || visible > 0;
1322
+ }
1323
+
1324
+ function refreshModelStatuses(instance) {
1325
+ const provider = getSelectedProvider(instance);
1326
+ for (const element of instance.root.querySelectorAll('[data-provider-model-status]')) {
1327
+ const id = element.dataset.providerModelStatus;
1328
+ const testing = instance.busy === 'testing' && id === getSelectedTestModelId(instance);
1329
+ const status = instance.verifications[id]?.status
1330
+ || (!instance.verificationStale && provider?.modelDiagnostics?.[id]?.status) || '';
1331
+ element.textContent = testing ? instance.tx('Testing connection…', '正在测试连接…') : formatVerification(instance, provider, id);
1332
+ element.dataset.tone = testing ? 'progress' : status === 'tested' ? 'success' : status === 'failed' ? 'failed' : '';
1333
+ }
1334
+ }
1335
+
1336
+ function replaceModelDraft(instance, next) {
1337
+ const secret = instance.root.querySelector('[data-provider-field="apiKey"]')?.value || '';
1338
+ const disclosure = instance.root.querySelector('[data-provider-disclosure]')?.checked === true;
1339
+ const advanced = instance.root.querySelector('.codex-provider-advanced')?.open === true;
1340
+ const detail = instance.root.querySelector('[data-provider-detail]');
1341
+ const scrollTop = detail?.scrollTop || 0;
1342
+ const selectedTest = getSelectedTestModelId(instance);
1343
+ instance.draft = { ...getSelectedProvider(instance), ...next };
1344
+ renderDetail(instance);
1345
+ const input = instance.root.querySelector('[data-provider-field="apiKey"]');
1346
+ if (input) input.value = secret;
1347
+ const checkbox = instance.root.querySelector('[data-provider-disclosure]');
1348
+ if (checkbox) checkbox.checked = disclosure;
1349
+ const advancedPanel = instance.root.querySelector('.codex-provider-advanced');
1350
+ if (advancedPanel) advancedPanel.open = advanced;
1351
+ const testSelect = instance.root.querySelector('[data-provider-test-model]');
1352
+ if (testSelect && next.models.some(model => model.id === selectedTest)) testSelect.value = selectedTest;
1353
+ if (detail) detail.scrollTop = scrollTop;
1354
+ markDirty(instance);
1355
+ renderFooter(instance);
1356
+ applyBusyState(instance);
1357
+ }
1358
+
1359
+ async function handleModelAction(instance, action, id) {
1360
+ if (action === 'cancel-editor') { closeModelEditor(instance); return; }
1361
+ if (action === 'apply-editor') { commitModelEditor(instance); return; }
1362
+ if (action === 'add' || action === 'edit') { openModelEditor(instance, action === 'edit' ? id : ''); return; }
1363
+ if (action === 'test') {
1364
+ if (!validateConnectionForm(instance)) return;
1365
+ const select = instance.root.querySelector('[data-provider-test-model]');
1366
+ if (select) select.value = id;
1367
+ invalidateVerification(instance, false, id);
1368
+ instance.callbacks.onTest?.(readContext(instance));
1369
+ return;
1370
+ }
1371
+ const context = readContext(instance), draft = context.draft;
1372
+ if (!draft.models.some(model => model.id === id)) return;
1373
+ if (action === 'default') {
1374
+ if (draft.defaultModelId !== id) replaceModelDraft(instance, { ...draft, defaultModelId: id });
1375
+ } else if (action === 'remove' && await confirmProviderAction(instance, instance.tx(
1376
+ 'Remove ' + id + ' from this provider? This takes effect after Save.',
1377
+ '从此服务移除 ' + id + '?保存后生效。'), 'model')) {
1378
+ replaceModelDraft(instance, instance.Profiles.removeDraftModel(draft, id));
1379
+ }
1380
+ }
1381
+
1382
+ function openModelEditor(instance, id) {
1383
+ const provider = readContext(instance).draft;
1384
+ const existing = provider.models.find(model => model.id === id);
1385
+ if (id && !existing) return;
1386
+ const model = existing || { id: '', label: '', contextWindow: provider.contextWindow,
1387
+ maxOutputTokens: null, inputModalities: provider.inputModalities,
1388
+ supportsParallelToolCalls: provider.supportsParallelToolCalls,
1389
+ upstreamResponseMode: 'auto', reasoningEfforts: [], reasoningAdapter: '', reasoningCapability: '', baseInstructions: '' };
1390
+ const tx = instance.tx, modal = instance.root.querySelector('[data-provider-model-editor]');
1391
+ instance.modelEditor = { id, providerId: instance.selectedId, returnFocus: instance.document.activeElement };
1392
+ modal.classList.add('is-minimal');
1393
+ const field = (name, label, value, attributes = '', help = '') => '<label class="codex-provider-field"><span>'
1394
+ + escapeHtml(label) + '</span><input data-model-field="' + name + '" value="' + escapeAttr(value ?? '')
1395
+ + '" ' + attributes + '>' + (help ? '<small>' + escapeHtml(help) + '</small>' : '') + '</label>';
1396
+ const select = (name, label, value, options) => '<label class="codex-provider-field"><span>'
1397
+ + escapeHtml(label) + '</span><select data-model-field="' + name + '">'
1398
+ + options.map(([key, title]) => '<option value="' + key + '"' + (value === key ? ' selected' : '') + '>'
1399
+ + escapeHtml(title) + '</option>').join('') + '</select></label>';
1400
+ modal.innerHTML = '<header class="codex-provider-model-editor-head"><h3 id="codex-provider-model-title">'
1401
+ + escapeHtml(id ? tx('Edit model', '编辑模型') : tx('Add model', '添加模型'))
1402
+ + '</h3><button type="button" class="codex-provider-model-close" data-provider-model-action="cancel-editor" aria-label="'
1403
+ + escapeAttr(tx('Close model editor', '关闭模型编辑')) + '"><svg viewBox="0 0 20 20" aria-hidden="true"><path d="m5 5 10 10M15 5 5 15"></path></svg></button></header>'
1404
+ + '<div class="codex-provider-model-editor-body"><div class="codex-provider-model-basics">'
1405
+ + field('id', tx('Model ID', '模型 ID'), model.id, 'type="text" maxlength="200" spellcheck="false" autofocus placeholder="'
1406
+ + escapeAttr(tx('Exact model ID from the provider', '填写服务商提供的模型 ID')) + '"')
1407
+ + '<div class="codex-provider-form-grid">'
1408
+ + field('contextWindow', tx('Context window', '上下文窗口'), model.contextWindow, 'type="number" min="8192" max="4000000" step="1"')
1409
+ + field('maxOutputTokens', tx('Max output tokens', '最大输出 Token'), model.maxOutputTokens,
1410
+ 'type="number" min="256" max="65536" placeholder="' + Number(provider.maxOutputTokens || 65536) + '"',
1411
+ tx('Optional; leave empty to inherit.', '可留空,沿用服务设置。'))
1412
+ + '</div><section class="codex-provider-model-types"><div class="codex-provider-model-types-head"><span>'
1413
+ + escapeHtml(tx('Input type', '输入类型')) + '</span><small>'
1414
+ + escapeHtml(tx('Output: text', '输出:文本')) + '</small></div><div class="codex-provider-type-options">'
1415
+ + '<span class="codex-provider-type-chip is-fixed" aria-label="' + escapeAttr(tx('Text input is required', '文本输入为必选项'))
1416
+ + '"><svg viewBox="0 0 16 16" aria-hidden="true"><path d="m3 8 3 3 7-7"></path></svg>'
1417
+ + escapeHtml(tx('Text', '文本')) + '<small>' + escapeHtml(tx('Required', '必选')) + '</small></span>'
1418
+ + '<label class="codex-provider-type-chip"><input type="checkbox" data-model-field="supportsImages"'
1419
+ + ((model.inputModalities || []).includes('image') ? ' checked' : '') + '><span>' + escapeHtml(tx('Images', '图片'))
1420
+ + '</span></label></div></section></div>'
1421
+ + '<details class="codex-provider-model-settings" data-model-reasoning><summary><span>'
1422
+ + escapeHtml(tx('Reasoning', '推理设置')) + '</span><small data-model-reasoning-summary></small></summary>'
1423
+ + '<div class="codex-provider-model-settings-body"><p class="codex-provider-help">'
1424
+ + escapeHtml(tx('Leave levels unchecked to use provider defaults. Only select levels supported by this endpoint.',
1425
+ '档位全部留空时沿用服务默认配置;仅选择该端点支持的档位。')) + '</p><div class="codex-provider-effort-chips">'
1426
+ + instance.Profiles.MODEL_EFFORTS.map(effort => '<label><input type="checkbox" data-model-effort value="' + effort + '"'
1427
+ + ((model.reasoningEfforts || []).includes(effort) ? ' checked' : '') + '><span>' + effort + '</span></label>').join('')
1428
+ + '</div><div class="codex-provider-form-grid">'
1429
+ + select('reasoningAdapter', tx('Parameter format', '参数格式'), model.reasoningAdapter || '',
1430
+ [['', tx('Inherit provider', '沿用服务配置')], ['auto', tx('Auto-detect', '自动识别')], ['none', tx('Disabled', '禁用')],
1431
+ ['deepseek', 'DeepSeek'], ['anthropic', 'Anthropic'], ['reasoning_effort', 'reasoning_effort'],
1432
+ ['openrouter', 'OpenRouter'], ['enable_thinking', 'enable_thinking'], ['thinking', 'thinking.type'], ['reasoning_split', 'reasoning_split']])
1433
+ + select('reasoningCapability', tx('Reasoning control', '推理控制'), model.reasoningCapability || '',
1434
+ [['', tx('Inherit / selected levels', '继承或使用所选档位')], ['none', tx('Not supported', '不支持')],
1435
+ ['toggle', tx('On / Off', '开启/关闭')], ['effort', tx('Effort levels', '档位选择')]])
1436
+ + '</div></div></details><details class="codex-provider-model-settings"><summary><span>'
1437
+ + escapeHtml(tx('Advanced', '高级设置')) + '</span><small>'
1438
+ + escapeHtml(tx('Name and compatibility', '名称与兼容性')) + '</small></summary>'
1439
+ + '<div class="codex-provider-model-settings-body"><div class="codex-provider-form-grid">'
1440
+ + field('label', tx('Display name (optional)', '显示名称(可选)'), model.label, 'type="text" maxlength="200"')
1441
+ + select('upstreamResponseMode', tx('Response mode', '响应模式'), model.upstreamResponseMode || 'auto',
1442
+ [['auto', tx('Auto', '自动')], ['streaming', tx('Streaming', '流式')], ['buffered', tx('Buffered', '缓冲')]])
1443
+ + '</div><label class="codex-provider-model-checkbox"><input type="checkbox" data-model-field="supportsParallelToolCalls"'
1444
+ + (model.supportsParallelToolCalls ? ' checked' : '') + '> ' + escapeHtml(tx('Parallel tool calls', '并行工具调用')) + '</label>'
1445
+ + '<label class="codex-provider-field"><span>' + escapeHtml(tx('Base instructions (optional)', '基础指令(可选)'))
1446
+ + '</span><textarea data-model-field="baseInstructions" rows="3" maxlength="8000">' + escapeHtml(model.baseInstructions || '')
1447
+ + '</textarea></label></div></details><p role="alert" class="codex-provider-model-error" data-model-error></p></div>'
1448
+ + '<footer class="codex-provider-model-editor-foot"><small>' + escapeHtml(tx('Saved with the provider.', '随服务配置一起保存。'))
1449
+ + '</small><button type="button" class="codex-provider-secondary-button" data-provider-model-action="cancel-editor">'
1450
+ + escapeHtml(tx('Cancel', '取消')) + '</button><button type="button" class="codex-provider-primary-button" data-provider-model-action="apply-editor">'
1451
+ + escapeHtml(id ? tx('Apply', '应用') : tx('Add model', '添加模型')) + '</button></footer>';
1452
+ updateModelReasoningSummary(instance);
1453
+ if (modal.showModal) modal.showModal(); else modal.setAttribute('open', '');
1454
+ }
1455
+
1456
+ function updateModelReasoningSummary(instance) {
1457
+ const modal = instance.root.querySelector('[data-provider-model-editor]');
1458
+ const summary = modal?.querySelector('[data-model-reasoning-summary]');
1459
+ if (!summary) return;
1460
+ const capability = modal.querySelector('[data-model-field="reasoningCapability"]')?.value;
1461
+ const adapter = modal.querySelector('[data-model-field="reasoningAdapter"]')?.value;
1462
+ const levels = Array.from(modal.querySelectorAll('[data-model-effort]:checked')).map(input => input.value);
1463
+ summary.textContent = capability === 'none' || adapter === 'none' ? instance.tx('Disabled', '已关闭')
1464
+ : levels.length ? levels.join(' · ')
1465
+ : capability === 'toggle' ? instance.tx('On / Off', '开关控制')
1466
+ : adapter && adapter !== 'auto' ? adapter
1467
+ : capability === 'effort' ? instance.tx('Provider levels', '服务默认档位')
1468
+ : instance.tx('Provider defaults', '沿用服务配置');
1469
+ summary.title = summary.textContent;
1470
+ }
1471
+
1472
+ function closeModelEditor(instance, restoreFocus = true) {
1473
+ if (!instance.modelEditor) return;
1474
+ const focus = instance.modelEditor.returnFocus;
1475
+ const modal = instance.root?.querySelector('[data-provider-model-editor]');
1476
+ instance.modelEditor = null;
1477
+ modal?.close?.();
1478
+ modal?.removeAttribute('open');
1479
+ if (modal) modal.replaceChildren();
1480
+ if (restoreFocus) focus?.focus?.();
1481
+ }
1482
+
1483
+ function commitModelEditor(instance) {
1484
+ const editor = instance.modelEditor;
1485
+ if (!editor || editor.providerId !== instance.selectedId) return;
1486
+ const modal = instance.root.querySelector('[data-provider-model-editor]');
1487
+ const get = key => modal.querySelector('[data-model-field="' + key + '"]');
1488
+ const patch = Object.fromEntries(['id', 'label', 'contextWindow', 'maxOutputTokens',
1489
+ 'upstreamResponseMode', 'reasoningAdapter', 'reasoningCapability', 'baseInstructions']
1490
+ .map(key => [key, get(key)?.value || '']));
1491
+ patch.inputModalities = get('supportsImages')?.checked ? ['text', 'image'] : ['text'];
1492
+ patch.supportsParallelToolCalls = get('supportsParallelToolCalls')?.checked === true;
1493
+ patch.reasoningEfforts = Array.from(modal.querySelectorAll('[data-model-effort]:checked')).map(input => input.value);
1494
+ try {
1495
+ const draft = readContext(instance).draft;
1496
+ const next = instance.Profiles.updateDraftModel(draft, editor.id, patch);
1497
+ const unchanged = JSON.stringify(next.models) === JSON.stringify(draft.models)
1498
+ && next.defaultModelId === draft.defaultModelId;
1499
+ closeModelEditor(instance, false);
1500
+ if (!unchanged) replaceModelDraft(instance, next);
1501
+ } catch (error) {
1502
+ const translated = {
1503
+ id: instance.tx(error.message, '请检查模型 ID:必填、最多 200 字符、不可重复,且单个服务最多 32 个模型。'),
1504
+ contextWindow: instance.tx(error.message, '上下文窗口必须为 8192 至 4000000 之间的整数。'),
1505
+ maxOutputTokens: instance.tx(error.message, '输出上限须为 256 至 65536 之间的整数,并小于上下文窗口。')
1506
+ };
1507
+ modal.querySelector('[data-model-error]').textContent = translated[error.field] || error.message;
1508
+ if (error.field) get(error.field)?.focus();
1509
+ }
1510
+ }
1511
+
1512
+ function validateConnectionForm(instance) {
1513
+ const context = readContext(instance), tx = instance.tx;
1514
+ let message = '';
1515
+ if (!context.draft.name) message = tx('Enter a provider name.', '请填写服务名称。');
1516
+ else if (!context.draft.baseUrl) message = tx('Enter the API Base URL.', '请填写 API 地址。');
1517
+ else if (!context.draft.models.length || !context.draft.models.some(model => model.id === context.draft.defaultModelId))
1518
+ message = tx('Add at least one model and select a default.', '请添加至少一个模型,并选择默认模型。');
1519
+ if (message) { setStatus(instance, { tone: 'failed', title: message }); return false; }
1520
+ return true;
1521
+ }
1522
+
1523
+
974
1524
  function formatSecretSavedAt(instance, value) {
975
1525
  const timestamp = Number(value);
976
1526
  if (!Number.isFinite(timestamp) || timestamp <= 0) {
@@ -1008,6 +1558,7 @@
1008
1558
  }
1009
1559
 
1010
1560
  function formatJsonRecord(value) {
1561
+ if (typeof value === 'string') return value;
1011
1562
  if (!value || typeof value !== 'object' || Array.isArray(value) || !Object.keys(value).length) {
1012
1563
  return '';
1013
1564
  }
@@ -1019,6 +1570,8 @@
1019
1570
  }
1020
1571
 
1021
1572
  function destroy(instance) {
1573
+ finishProviderConfirmation(instance, false, false);
1574
+ closeModelEditor(instance, false);
1022
1575
  instance.root?.close?.();
1023
1576
  instance.root?.remove?.();
1024
1577
  instance.root = null;