codex-overleaf-link 2.4.1 → 2.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (397) hide show
  1. package/README.md +275 -305
  2. package/README.zh-CN.md +290 -320
  3. package/extension/bootstrap/background.js +99 -19
  4. package/extension/bootstrap/manifest.template.json +9 -4
  5. package/extension/bootstrap/popup.html +11 -16
  6. package/extension/bootstrap/popupFallback.js +55 -0
  7. package/extension/bootstrap/update.js +25 -2
  8. package/extension/bootstrap/updateStatus.js +1 -0
  9. package/extension/runtime-manifest.json +3 -1
  10. package/extension/src/background.js +234 -56
  11. package/extension/src/backgroundBootstrapMigration.js +181 -0
  12. package/extension/src/backgroundSharedSessions.js +440 -0
  13. package/extension/src/backgroundUpdateCoordinator.js +541 -381
  14. package/extension/src/backgroundUpdateSafety.js +173 -0
  15. package/extension/src/content/activeTurnControl.js +16 -2
  16. package/extension/src/content/applyResultFormatters.js +46 -73
  17. package/extension/src/content/assetTransferBroker.js +244 -12
  18. package/extension/src/content/changeHistoryView.js +175 -0
  19. package/extension/src/content/composerAttachments.js +117 -67
  20. package/extension/src/content/composerPanel.js +41 -8
  21. package/extension/src/content/contentRuntime.js +1084 -1099
  22. package/extension/src/content/diagnosticsController.js +122 -97
  23. package/extension/src/content/diagnosticsExport.js +236 -0
  24. package/extension/src/content/diagnosticsPanel.js +78 -19
  25. package/extension/src/content/diffReviewPanel.js +8 -5
  26. package/extension/src/content/directoryCleanupRecovery.js +54 -0
  27. package/extension/src/content/generated/content.bundle.js +14680 -3606
  28. package/extension/src/content/generated/content.bundle.meta.json +30 -3
  29. package/extension/src/content/globalPreferencesController.js +1 -1
  30. package/extension/src/content/legacyGlobalRegistry.js +25 -1
  31. package/extension/src/content/localSkillsPanel.js +141 -16
  32. package/extension/src/content/markdownDomRenderer.js +9 -4
  33. package/extension/src/content/markdownText.js +5 -6
  34. package/extension/src/content/modelPicker.js +8 -5
  35. package/extension/src/content/nativeCompatibilityController.js +45 -20
  36. package/extension/src/content/otWarmMirror.js +4 -5
  37. package/extension/src/content/otWarmMirrorController.js +17 -20
  38. package/extension/src/content/pageBridgeClient.js +33 -5
  39. package/extension/src/content/panelMaintenance.js +87 -132
  40. package/extension/src/content/panelRenderer.js +10 -0
  41. package/extension/src/content/pendingInputView.js +109 -76
  42. package/extension/src/content/projectProviderSelection.js +13 -4
  43. package/extension/src/content/projectSessionCleanup.js +109 -62
  44. package/extension/src/content/projectSettingsCoordinator.js +39 -2
  45. package/extension/src/content/providerSettingsCoordinator.js +63 -14
  46. package/extension/src/content/providerSettingsDialog.js +724 -113
  47. package/extension/src/content/recentProjects.js +21 -6
  48. package/extension/src/content/runActivitySummary.js +665 -0
  49. package/extension/src/content/runCompletionMeta.js +344 -0
  50. package/extension/src/content/runController.js +40 -15
  51. package/extension/src/content/runFailureNotice.js +34 -12
  52. package/extension/src/content/runGuidanceController.js +76 -30
  53. package/extension/src/content/runGuidanceView.js +59 -10
  54. package/extension/src/content/runPresence.js +306 -0
  55. package/extension/src/content/runQueueScheduler.js +150 -43
  56. package/extension/src/content/runRecoveryActions.js +146 -0
  57. package/extension/src/content/runResultActions.js +11 -8
  58. package/extension/src/content/runReviewController.js +574 -0
  59. package/extension/src/content/runScrollLayout.js +21 -2
  60. package/extension/src/content/runTimelineView.js +315 -290
  61. package/extension/src/content/scopedPersistencePanelState.js +51 -6
  62. package/extension/src/content/selectionContextView.js +300 -0
  63. package/extension/src/content/sensitivePreflight.js +140 -0
  64. package/extension/src/content/sessionForkController.js +80 -17
  65. package/extension/src/content/sessionManager.js +1 -1
  66. package/extension/src/content/sessionPersistence.js +6 -1
  67. package/extension/src/content/settingsPanel.js +84 -55
  68. package/extension/src/content/settingsWorkbench.js +379 -0
  69. package/extension/src/content/sharedSessionViewSync.js +109 -0
  70. package/extension/src/content/subagentActivityView.js +195 -0
  71. package/extension/src/content/trackedChangeCaptureController.js +7 -3
  72. package/extension/src/content/updateIdle.js +4 -1
  73. package/extension/src/content/updateNotice.js +179 -56
  74. package/extension/src/content/updateSafetyReceiver.js +58 -0
  75. package/extension/src/content/writebackController.js +308 -2
  76. package/extension/src/content/writebackOrchestrator.js +213 -199
  77. package/extension/src/content/writebackPlan.js +231 -0
  78. package/extension/src/content/writebackRecovery.js +122 -0
  79. package/extension/src/content/writingStyleSettings.js +600 -0
  80. package/extension/src/content/writtenChangesView.js +152 -0
  81. package/extension/src/page/binaryAssetUploader.js +223 -23
  82. package/extension/src/page/fileMover.js +256 -0
  83. package/extension/src/page/overleafProjectSnapshot.js +1 -0
  84. package/extension/src/page/ownedDirectoryCleanup.js +94 -0
  85. package/extension/src/page/referenceProjects.js +149 -0
  86. package/extension/src/page/reviewContext.js +113 -0
  87. package/extension/src/page/saveState.js +20 -6
  88. package/extension/src/page/snapshotRouter.js +124 -48
  89. package/extension/src/page/textCoordinates.js +102 -0
  90. package/extension/src/page/textFileCreator.js +442 -71
  91. package/extension/src/page/trackedChangeCapture.js +53 -57
  92. package/extension/src/page/trackedChangeOwnership.js +216 -0
  93. package/extension/src/page/trackedChangeReplay.js +184 -0
  94. package/extension/src/page/trackedChangesLifecycle.js +343 -339
  95. package/extension/src/page/treeOperations.js +33 -12
  96. package/extension/src/page/updateSafetyProbe.js +41 -0
  97. package/extension/src/page/writebackReceiptJournal.js +114 -0
  98. package/extension/src/page/writebackRouter.js +242 -201
  99. package/extension/src/pageBridge.js +73 -102
  100. package/extension/src/popup.js +261 -65
  101. package/extension/src/shared/agentTranscript.js +41 -4
  102. package/extension/src/shared/auditRecords.js +73 -0
  103. package/extension/src/shared/changeHistoryModel.js +94 -0
  104. package/extension/src/shared/compatibility.js +22 -6
  105. package/extension/src/shared/compileAdapter.js +101 -37
  106. package/extension/src/shared/composerAttachmentDraft.js +151 -0
  107. package/extension/src/shared/directoryCleanupProof.js +43 -0
  108. package/extension/src/shared/failureReasons.js +329 -3
  109. package/extension/src/shared/globalPreferences.js +31 -14
  110. package/extension/src/shared/governanceRules.js +4 -37
  111. package/extension/src/shared/i18n.js +389 -25
  112. package/extension/src/shared/managedUpdateProjection.js +8 -5
  113. package/extension/src/shared/nativeRequestIdentity.js +1 -0
  114. package/extension/src/shared/pageRpcContract.js +9 -2
  115. package/extension/src/shared/projectFiles.js +21 -44
  116. package/extension/src/shared/providerProfiles.js +114 -3
  117. package/extension/src/shared/runActivityModel.js +361 -0
  118. package/extension/src/shared/runExecutionSnapshot.js +8 -2
  119. package/extension/src/shared/runExecutionSnapshotCodec.js +16 -4
  120. package/extension/src/shared/runInputQueue.js +48 -5
  121. package/extension/src/shared/scopedPersistenceBrowserAdapter.js +6 -3
  122. package/extension/src/shared/selectionContext.js +37 -0
  123. package/extension/src/shared/sessionState.js +166 -30
  124. package/extension/src/shared/settlementFacts.js +10 -2
  125. package/extension/src/shared/sharedSessionBridge.js +311 -0
  126. package/extension/src/shared/sharedSessionTransfer.js +140 -0
  127. package/extension/src/shared/staleGuard.js +118 -13
  128. package/extension/src/shared/storageDb.js +77 -78
  129. package/extension/src/shared/storageMigration.js +74 -111
  130. package/extension/src/shared/storageRunActions.js +211 -22
  131. package/extension/src/shared/storageValuePruning.js +40 -0
  132. package/extension/src/shared/subagentActivityModel.js +174 -0
  133. package/extension/src/shared/undoOperations.js +216 -6
  134. package/extension/src/shared/updateConsent.js +42 -11
  135. package/extension/src/shared/updateRuntimeIdentity.js +1 -1
  136. package/extension/src/shared/writebackIntent.js +78 -0
  137. package/extension/src/shared/writebackOperationEvidence.js +4 -15
  138. package/extension/src/shared/writebackSettlement.js +67 -48
  139. package/extension/src/shared/writingStyle.js +33 -0
  140. package/extension/styles/panel.css +1987 -385
  141. package/extension/styles/popup.css +110 -0
  142. package/native-host/src/anthropicBridgeRequest.js +6 -3
  143. package/native-host/src/anthropicBridgeResponse.js +6 -2
  144. package/native-host/src/anthropicMessagesBridge.js +24 -16
  145. package/native-host/src/bootstrapMigration.js +296 -0
  146. package/native-host/src/bootstrapMigrationContract.js +101 -0
  147. package/native-host/src/bootstrapUpdate.js +233 -0
  148. package/native-host/src/bootstrapUpdateWatchdog.js +44 -0
  149. package/native-host/src/chatBridgeResponse.js +10 -0
  150. package/native-host/src/chatCompletionsBridge.js +21 -15
  151. package/native-host/src/codexConfigPaths.js +106 -0
  152. package/native-host/src/codexHome.js +66 -73
  153. package/native-host/src/codexHomeScope.js +124 -0
  154. package/native-host/src/codexInheritedAuth.js +90 -0
  155. package/native-host/src/codexLocalSkills.js +301 -0
  156. package/native-host/src/codexModelRecovery.js +107 -0
  157. package/native-host/src/codexPromptAssembly.js +33 -12
  158. package/native-host/src/codexProviderLaunch.js +3 -3
  159. package/native-host/src/codexProviderTest.js +24 -26
  160. package/native-host/src/codexRuntimeIdentity.js +1 -0
  161. package/native-host/src/codexSessionRunner.js +211 -318
  162. package/native-host/src/codexSessionTiming.js +21 -8
  163. package/native-host/src/codexSkillIsolation.js +170 -0
  164. package/native-host/src/codexThreadFork.js +19 -3
  165. package/native-host/src/index.js +19 -7
  166. package/native-host/src/managedInstall.js +115 -39
  167. package/native-host/src/managedInstallRecovery.js +140 -0
  168. package/native-host/src/managedRuntimeEvidence.js +57 -0
  169. package/native-host/src/mirrorWorkspace.js +92 -22
  170. package/native-host/src/nativeDoctor.js +15 -0
  171. package/native-host/src/nativeEnvironment.js +70 -0
  172. package/native-host/src/nativeHelperLifecycle.js +21 -0
  173. package/native-host/src/nativeHostRegistration.js +33 -0
  174. package/native-host/src/nativeQuotas.js +15 -8
  175. package/native-host/src/nativeRunLifecycle.js +71 -0
  176. package/native-host/src/overleafWorkspaceRules.js +16 -0
  177. package/native-host/src/processStopBarrier.js +115 -0
  178. package/native-host/src/projectPathPlatform.js +30 -0
  179. package/native-host/src/providerHttp.js +80 -0
  180. package/native-host/src/providerModelCatalog.js +4 -3
  181. package/native-host/src/providerProfile.js +17 -3
  182. package/native-host/src/providerReasoning.js +10 -11
  183. package/native-host/src/providerRuntime.js +1 -0
  184. package/native-host/src/providerStore.js +76 -14
  185. package/native-host/src/releaseTransport.js +14 -5
  186. package/native-host/src/responsesPassthroughBridge.js +266 -52
  187. package/native-host/src/runtimeInstaller.js +21 -1
  188. package/native-host/src/selectionScope.js +86 -0
  189. package/native-host/src/skills/parallel-subagents/SKILL.md +31 -3
  190. package/native-host/src/skills/writing-style-builder/SKILL.md +146 -0
  191. package/native-host/src/subagentAdmission.js +26 -0
  192. package/native-host/src/subagentBroker.js +176 -159
  193. package/native-host/src/subagentTelemetry.js +325 -0
  194. package/native-host/src/subagentWorkspace.js +103 -0
  195. package/native-host/src/subagentWorkspacePath.js +34 -0
  196. package/native-host/src/taskRunnerRuntime.js +130 -57
  197. package/native-host/src/tomlStatements.js +140 -0
  198. package/native-host/src/turnAttachments.js +113 -0
  199. package/native-host/src/updateManager.js +406 -38
  200. package/native-host/src/updateNetworkControl.js +75 -0
  201. package/native-host/src/updateTrust.js +21 -2
  202. package/native-host/src/vendor/pdfjs/LICENSE +177 -0
  203. package/native-host/src/vendor/pdfjs/SOURCE.txt +3 -0
  204. package/native-host/src/vendor/pdfjs/cmaps/78-EUC-H.bcmap +0 -0
  205. package/native-host/src/vendor/pdfjs/cmaps/78-EUC-V.bcmap +0 -0
  206. package/native-host/src/vendor/pdfjs/cmaps/78-H.bcmap +0 -0
  207. package/native-host/src/vendor/pdfjs/cmaps/78-RKSJ-H.bcmap +0 -0
  208. package/native-host/src/vendor/pdfjs/cmaps/78-RKSJ-V.bcmap +0 -0
  209. package/native-host/src/vendor/pdfjs/cmaps/78-V.bcmap +0 -0
  210. package/native-host/src/vendor/pdfjs/cmaps/78ms-RKSJ-H.bcmap +0 -0
  211. package/native-host/src/vendor/pdfjs/cmaps/78ms-RKSJ-V.bcmap +0 -0
  212. package/native-host/src/vendor/pdfjs/cmaps/83pv-RKSJ-H.bcmap +0 -0
  213. package/native-host/src/vendor/pdfjs/cmaps/90ms-RKSJ-H.bcmap +0 -0
  214. package/native-host/src/vendor/pdfjs/cmaps/90ms-RKSJ-V.bcmap +0 -0
  215. package/native-host/src/vendor/pdfjs/cmaps/90msp-RKSJ-H.bcmap +0 -0
  216. package/native-host/src/vendor/pdfjs/cmaps/90msp-RKSJ-V.bcmap +0 -0
  217. package/native-host/src/vendor/pdfjs/cmaps/90pv-RKSJ-H.bcmap +0 -0
  218. package/native-host/src/vendor/pdfjs/cmaps/90pv-RKSJ-V.bcmap +0 -0
  219. package/native-host/src/vendor/pdfjs/cmaps/Add-H.bcmap +0 -0
  220. package/native-host/src/vendor/pdfjs/cmaps/Add-RKSJ-H.bcmap +0 -0
  221. package/native-host/src/vendor/pdfjs/cmaps/Add-RKSJ-V.bcmap +0 -0
  222. package/native-host/src/vendor/pdfjs/cmaps/Add-V.bcmap +0 -0
  223. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-0.bcmap +0 -0
  224. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-1.bcmap +0 -0
  225. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-2.bcmap +0 -0
  226. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-3.bcmap +0 -0
  227. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-4.bcmap +0 -0
  228. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-5.bcmap +0 -0
  229. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-6.bcmap +0 -0
  230. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-UCS2.bcmap +0 -0
  231. package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-0.bcmap +0 -0
  232. package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-1.bcmap +0 -0
  233. package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-2.bcmap +0 -0
  234. package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-3.bcmap +0 -0
  235. package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-4.bcmap +0 -0
  236. package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-5.bcmap +0 -0
  237. package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-UCS2.bcmap +0 -0
  238. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-0.bcmap +0 -0
  239. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-1.bcmap +0 -0
  240. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-2.bcmap +0 -0
  241. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-3.bcmap +0 -0
  242. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-4.bcmap +0 -0
  243. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-5.bcmap +0 -0
  244. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-6.bcmap +0 -0
  245. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-UCS2.bcmap +0 -0
  246. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Korea1-0.bcmap +0 -0
  247. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Korea1-1.bcmap +0 -0
  248. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Korea1-2.bcmap +0 -0
  249. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Korea1-UCS2.bcmap +0 -0
  250. package/native-host/src/vendor/pdfjs/cmaps/B5-H.bcmap +0 -0
  251. package/native-host/src/vendor/pdfjs/cmaps/B5-V.bcmap +0 -0
  252. package/native-host/src/vendor/pdfjs/cmaps/B5pc-H.bcmap +0 -0
  253. package/native-host/src/vendor/pdfjs/cmaps/B5pc-V.bcmap +0 -0
  254. package/native-host/src/vendor/pdfjs/cmaps/CNS-EUC-H.bcmap +0 -0
  255. package/native-host/src/vendor/pdfjs/cmaps/CNS-EUC-V.bcmap +0 -0
  256. package/native-host/src/vendor/pdfjs/cmaps/CNS1-H.bcmap +0 -0
  257. package/native-host/src/vendor/pdfjs/cmaps/CNS1-V.bcmap +0 -0
  258. package/native-host/src/vendor/pdfjs/cmaps/CNS2-H.bcmap +0 -0
  259. package/native-host/src/vendor/pdfjs/cmaps/CNS2-V.bcmap +3 -0
  260. package/native-host/src/vendor/pdfjs/cmaps/ETHK-B5-H.bcmap +0 -0
  261. package/native-host/src/vendor/pdfjs/cmaps/ETHK-B5-V.bcmap +0 -0
  262. package/native-host/src/vendor/pdfjs/cmaps/ETen-B5-H.bcmap +0 -0
  263. package/native-host/src/vendor/pdfjs/cmaps/ETen-B5-V.bcmap +0 -0
  264. package/native-host/src/vendor/pdfjs/cmaps/ETenms-B5-H.bcmap +3 -0
  265. package/native-host/src/vendor/pdfjs/cmaps/ETenms-B5-V.bcmap +0 -0
  266. package/native-host/src/vendor/pdfjs/cmaps/EUC-H.bcmap +0 -0
  267. package/native-host/src/vendor/pdfjs/cmaps/EUC-V.bcmap +0 -0
  268. package/native-host/src/vendor/pdfjs/cmaps/Ext-H.bcmap +0 -0
  269. package/native-host/src/vendor/pdfjs/cmaps/Ext-RKSJ-H.bcmap +0 -0
  270. package/native-host/src/vendor/pdfjs/cmaps/Ext-RKSJ-V.bcmap +0 -0
  271. package/native-host/src/vendor/pdfjs/cmaps/Ext-V.bcmap +0 -0
  272. package/native-host/src/vendor/pdfjs/cmaps/GB-EUC-H.bcmap +0 -0
  273. package/native-host/src/vendor/pdfjs/cmaps/GB-EUC-V.bcmap +0 -0
  274. package/native-host/src/vendor/pdfjs/cmaps/GB-H.bcmap +4 -0
  275. package/native-host/src/vendor/pdfjs/cmaps/GB-V.bcmap +0 -0
  276. package/native-host/src/vendor/pdfjs/cmaps/GBK-EUC-H.bcmap +0 -0
  277. package/native-host/src/vendor/pdfjs/cmaps/GBK-EUC-V.bcmap +0 -0
  278. package/native-host/src/vendor/pdfjs/cmaps/GBK2K-H.bcmap +0 -0
  279. package/native-host/src/vendor/pdfjs/cmaps/GBK2K-V.bcmap +0 -0
  280. package/native-host/src/vendor/pdfjs/cmaps/GBKp-EUC-H.bcmap +0 -0
  281. package/native-host/src/vendor/pdfjs/cmaps/GBKp-EUC-V.bcmap +0 -0
  282. package/native-host/src/vendor/pdfjs/cmaps/GBT-EUC-H.bcmap +0 -0
  283. package/native-host/src/vendor/pdfjs/cmaps/GBT-EUC-V.bcmap +0 -0
  284. package/native-host/src/vendor/pdfjs/cmaps/GBT-H.bcmap +0 -0
  285. package/native-host/src/vendor/pdfjs/cmaps/GBT-V.bcmap +0 -0
  286. package/native-host/src/vendor/pdfjs/cmaps/GBTpc-EUC-H.bcmap +0 -0
  287. package/native-host/src/vendor/pdfjs/cmaps/GBTpc-EUC-V.bcmap +0 -0
  288. package/native-host/src/vendor/pdfjs/cmaps/GBpc-EUC-H.bcmap +0 -0
  289. package/native-host/src/vendor/pdfjs/cmaps/GBpc-EUC-V.bcmap +0 -0
  290. package/native-host/src/vendor/pdfjs/cmaps/H.bcmap +0 -0
  291. package/native-host/src/vendor/pdfjs/cmaps/HKdla-B5-H.bcmap +0 -0
  292. package/native-host/src/vendor/pdfjs/cmaps/HKdla-B5-V.bcmap +0 -0
  293. package/native-host/src/vendor/pdfjs/cmaps/HKdlb-B5-H.bcmap +0 -0
  294. package/native-host/src/vendor/pdfjs/cmaps/HKdlb-B5-V.bcmap +0 -0
  295. package/native-host/src/vendor/pdfjs/cmaps/HKgccs-B5-H.bcmap +0 -0
  296. package/native-host/src/vendor/pdfjs/cmaps/HKgccs-B5-V.bcmap +0 -0
  297. package/native-host/src/vendor/pdfjs/cmaps/HKm314-B5-H.bcmap +0 -0
  298. package/native-host/src/vendor/pdfjs/cmaps/HKm314-B5-V.bcmap +0 -0
  299. package/native-host/src/vendor/pdfjs/cmaps/HKm471-B5-H.bcmap +0 -0
  300. package/native-host/src/vendor/pdfjs/cmaps/HKm471-B5-V.bcmap +0 -0
  301. package/native-host/src/vendor/pdfjs/cmaps/HKscs-B5-H.bcmap +0 -0
  302. package/native-host/src/vendor/pdfjs/cmaps/HKscs-B5-V.bcmap +0 -0
  303. package/native-host/src/vendor/pdfjs/cmaps/Hankaku.bcmap +0 -0
  304. package/native-host/src/vendor/pdfjs/cmaps/Hiragana.bcmap +0 -0
  305. package/native-host/src/vendor/pdfjs/cmaps/KSC-EUC-H.bcmap +0 -0
  306. package/native-host/src/vendor/pdfjs/cmaps/KSC-EUC-V.bcmap +0 -0
  307. package/native-host/src/vendor/pdfjs/cmaps/KSC-H.bcmap +0 -0
  308. package/native-host/src/vendor/pdfjs/cmaps/KSC-Johab-H.bcmap +0 -0
  309. package/native-host/src/vendor/pdfjs/cmaps/KSC-Johab-V.bcmap +0 -0
  310. package/native-host/src/vendor/pdfjs/cmaps/KSC-V.bcmap +0 -0
  311. package/native-host/src/vendor/pdfjs/cmaps/KSCms-UHC-H.bcmap +0 -0
  312. package/native-host/src/vendor/pdfjs/cmaps/KSCms-UHC-HW-H.bcmap +0 -0
  313. package/native-host/src/vendor/pdfjs/cmaps/KSCms-UHC-HW-V.bcmap +0 -0
  314. package/native-host/src/vendor/pdfjs/cmaps/KSCms-UHC-V.bcmap +0 -0
  315. package/native-host/src/vendor/pdfjs/cmaps/KSCpc-EUC-H.bcmap +0 -0
  316. package/native-host/src/vendor/pdfjs/cmaps/KSCpc-EUC-V.bcmap +0 -0
  317. package/native-host/src/vendor/pdfjs/cmaps/Katakana.bcmap +0 -0
  318. package/native-host/src/vendor/pdfjs/cmaps/LICENSE +36 -0
  319. package/native-host/src/vendor/pdfjs/cmaps/NWP-H.bcmap +0 -0
  320. package/native-host/src/vendor/pdfjs/cmaps/NWP-V.bcmap +0 -0
  321. package/native-host/src/vendor/pdfjs/cmaps/RKSJ-H.bcmap +0 -0
  322. package/native-host/src/vendor/pdfjs/cmaps/RKSJ-V.bcmap +0 -0
  323. package/native-host/src/vendor/pdfjs/cmaps/Roman.bcmap +0 -0
  324. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UCS2-H.bcmap +0 -0
  325. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UCS2-V.bcmap +0 -0
  326. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF16-H.bcmap +0 -0
  327. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF16-V.bcmap +0 -0
  328. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF32-H.bcmap +0 -0
  329. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF32-V.bcmap +0 -0
  330. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF8-H.bcmap +0 -0
  331. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF8-V.bcmap +0 -0
  332. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UCS2-H.bcmap +0 -0
  333. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UCS2-V.bcmap +0 -0
  334. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF16-H.bcmap +0 -0
  335. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF16-V.bcmap +0 -0
  336. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF32-H.bcmap +0 -0
  337. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF32-V.bcmap +0 -0
  338. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF8-H.bcmap +0 -0
  339. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF8-V.bcmap +0 -0
  340. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UCS2-H.bcmap +0 -0
  341. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UCS2-HW-H.bcmap +0 -0
  342. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UCS2-HW-V.bcmap +0 -0
  343. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UCS2-V.bcmap +0 -0
  344. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF16-H.bcmap +0 -0
  345. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF16-V.bcmap +0 -0
  346. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF32-H.bcmap +0 -0
  347. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF32-V.bcmap +0 -0
  348. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF8-H.bcmap +0 -0
  349. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF8-V.bcmap +0 -0
  350. package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF16-H.bcmap +0 -0
  351. package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF16-V.bcmap +0 -0
  352. package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF32-H.bcmap +0 -0
  353. package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF32-V.bcmap +0 -0
  354. package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF8-H.bcmap +0 -0
  355. package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF8-V.bcmap +0 -0
  356. package/native-host/src/vendor/pdfjs/cmaps/UniJISPro-UCS2-HW-V.bcmap +0 -0
  357. package/native-host/src/vendor/pdfjs/cmaps/UniJISPro-UCS2-V.bcmap +0 -0
  358. package/native-host/src/vendor/pdfjs/cmaps/UniJISPro-UTF8-V.bcmap +0 -0
  359. package/native-host/src/vendor/pdfjs/cmaps/UniJISX0213-UTF32-H.bcmap +0 -0
  360. package/native-host/src/vendor/pdfjs/cmaps/UniJISX0213-UTF32-V.bcmap +0 -0
  361. package/native-host/src/vendor/pdfjs/cmaps/UniJISX02132004-UTF32-H.bcmap +0 -0
  362. package/native-host/src/vendor/pdfjs/cmaps/UniJISX02132004-UTF32-V.bcmap +0 -0
  363. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UCS2-H.bcmap +0 -0
  364. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UCS2-V.bcmap +0 -0
  365. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF16-H.bcmap +0 -0
  366. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF16-V.bcmap +0 -0
  367. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF32-H.bcmap +0 -0
  368. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF32-V.bcmap +0 -0
  369. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF8-H.bcmap +0 -0
  370. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF8-V.bcmap +0 -0
  371. package/native-host/src/vendor/pdfjs/cmaps/V.bcmap +0 -0
  372. package/native-host/src/vendor/pdfjs/cmaps/WP-Symbol.bcmap +0 -0
  373. package/native-host/src/vendor/pdfjs/pdf.mjs +26740 -0
  374. package/native-host/src/vendor/pdfjs/pdf.worker.mjs +62201 -0
  375. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitDingbats.pfb +0 -0
  376. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitFixed.pfb +0 -0
  377. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitFixedBold.pfb +0 -0
  378. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitFixedBoldItalic.pfb +0 -0
  379. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitFixedItalic.pfb +0 -0
  380. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitSerif.pfb +0 -0
  381. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitSerifBold.pfb +0 -0
  382. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitSerifBoldItalic.pfb +0 -0
  383. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitSerifItalic.pfb +0 -0
  384. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitSymbol.pfb +0 -0
  385. package/native-host/src/vendor/pdfjs/standard_fonts/LICENSE_FOXIT +27 -0
  386. package/native-host/src/vendor/pdfjs/standard_fonts/LICENSE_LIBERATION +102 -0
  387. package/native-host/src/vendor/pdfjs/standard_fonts/LiberationSans-Bold.ttf +0 -0
  388. package/native-host/src/vendor/pdfjs/standard_fonts/LiberationSans-BoldItalic.ttf +0 -0
  389. package/native-host/src/vendor/pdfjs/standard_fonts/LiberationSans-Italic.ttf +0 -0
  390. package/native-host/src/vendor/pdfjs/standard_fonts/LiberationSans-Regular.ttf +0 -0
  391. package/native-host/src/writingStyleEvidence.js +102 -0
  392. package/native-host/src/writingStylePdfWorker.js +35 -0
  393. package/native-host/src/writingStyleProfile.js +165 -0
  394. package/native-host/src/writingStyleRuntime.js +475 -0
  395. package/native-host/src/writingStyleStore.js +73 -0
  396. package/package.json +1 -1
  397. package/scripts/install-managed.mjs +1 -0
@@ -4,15 +4,59 @@
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) {
10
48
  throw new Error('Provider settings dialog requires provider profiles.');
11
49
  }
50
+ const translations = new Map();
51
+ const translate = options.tx || (english => english);
12
52
  const instance = {
13
53
  Profiles,
14
54
  document: options.document || document,
15
- tx: options.tx || ((english) => english),
55
+ tx: (english, chinese) => {
56
+ translations.set(english, [english, chinese]); translations.set(chinese, [english, chinese]);
57
+ return translate(english, chinese);
58
+ },
59
+ translateStatusText: text => translations.has(text) ? translate(...translations.get(text)) : options.translateStatusText?.(text) || text,
16
60
  callbacks: options.callbacks || {},
17
61
  catalog: Profiles.normalizeCatalog({}),
18
62
  selectedId: 'builtin',
@@ -23,11 +67,20 @@
23
67
  verifications: {},
24
68
  pendingCatalog: null,
25
69
  root: null,
26
- returnFocus: null
70
+ returnFocus: null,
71
+ providerFilter: '',
72
+ modelFilter: '',
73
+ templatePicker: false,
74
+ previousProviderId: 'builtin',
75
+ modelEditor: null,
76
+ verificationStale: false,
77
+ embedded: false,
78
+ embeddedContainer: null,
79
+ confirmation: null
27
80
  };
28
81
  ensureRoot(instance);
29
82
  return {
30
- open: catalog => open(instance, catalog),
83
+ open: (catalog, presentation) => open(instance, catalog, presentation),
31
84
  close: () => requestClose(instance),
32
85
  setCatalog: (catalog, selectedId) => setCatalog(instance, catalog, selectedId),
33
86
  offerCatalog: (catalog, selectedId) => offerCatalog(instance, catalog, selectedId),
@@ -36,7 +89,8 @@
36
89
  setVerification: verification => setVerification(instance, verification),
37
90
  setVerificationFailure: failure => setVerificationFailure(instance, failure),
38
91
  setTestProgress: progress => setTestProgress(instance, progress),
39
- hasUnsavedChanges: () => instance.dirty,
92
+ refreshLocale: () => refreshLocale(instance),
93
+ hasUnsavedChanges: () => instance.dirty || Boolean(instance.modelEditor || instance.confirmation),
40
94
  isOpen: () => Boolean(instance.root && !instance.root.hidden),
41
95
  destroy: () => destroy(instance),
42
96
  _instance: instance
@@ -48,7 +102,7 @@
48
102
  return instance.root;
49
103
  }
50
104
  const root = instance.document.createElement('dialog');
51
- root.className = 'codex-provider-dialog-root';
105
+ root.className = 'codex-provider-dialog-root codex-provider-v2';
52
106
  root.setAttribute('aria-labelledby', 'codex-provider-dialog-title');
53
107
  root.hidden = true;
54
108
  root.innerHTML = `
@@ -62,6 +116,7 @@
62
116
  </div>
63
117
  <p data-provider-dialog-subtitle></p>
64
118
  </div>
119
+ <button type="button" class="codex-provider-inline-button" data-provider-action="reload" hidden></button>
65
120
  <button type="button" class="codex-provider-dialog-close" data-provider-action="close" aria-label="Close">×</button>
66
121
  </header>
67
122
  <div class="codex-provider-dialog-body">
@@ -74,6 +129,23 @@
74
129
  </footer>
75
130
  </section>
76
131
  `;
132
+ const modelDialog = instance.document.createElement('dialog');
133
+ modelDialog.className = 'codex-provider-model-editor';
134
+ modelDialog.setAttribute('data-provider-model-editor', '');
135
+ modelDialog.setAttribute('aria-labelledby', 'codex-provider-model-title');
136
+ modelDialog.addEventListener('cancel', event => {
137
+ event.preventDefault(); event.stopPropagation(); closeModelEditor(instance);
138
+ });
139
+ root.appendChild(modelDialog);
140
+ const confirmation = instance.document.createElement('dialog');
141
+ confirmation.className = 'codex-provider-confirmation';
142
+ confirmation.setAttribute('data-provider-confirmation', '');
143
+ confirmation.setAttribute('aria-labelledby', 'codex-provider-confirm-title');
144
+ confirmation.setAttribute('aria-describedby', 'codex-provider-confirm-message');
145
+ confirmation.addEventListener('cancel', event => {
146
+ event.preventDefault(); event.stopPropagation(); finishProviderConfirmation(instance, false);
147
+ });
148
+ root.appendChild(confirmation);
77
149
  instance.document.documentElement.appendChild(root);
78
150
  instance.root = root;
79
151
  root.addEventListener('click', event => handleClick(instance, event));
@@ -86,22 +158,38 @@
86
158
  return root;
87
159
  }
88
160
 
89
- function open(instance, catalog) {
161
+ function open(instance, catalog, presentation = {}) {
162
+ const host = presentation.container || null;
163
+ const parent = host || instance.document.documentElement;
164
+ if (instance.root.parentElement !== parent) {
165
+ instance.root.close?.();
166
+ parent.appendChild(instance.root);
167
+ }
168
+ instance.embedded = Boolean(host);
169
+ instance.embeddedContainer = host;
170
+ instance.root.dataset.embedded = String(instance.embedded);
171
+ instance.root.setAttribute('role', host ? 'region' : 'dialog');
90
172
  instance.returnFocus = instance.document.activeElement;
91
173
  instance.root.hidden = false;
92
174
  syncTheme(instance);
93
- setCatalog(instance, catalog || instance.catalog);
94
- if (!instance.root.open) instance.root.showModal?.();
175
+ if (!presentation.preserveDraft) setCatalog(instance, catalog || instance.catalog);
176
+ if (!instance.root.open) {
177
+ if (host) instance.root.show?.();
178
+ else instance.root.showModal?.();
179
+ }
180
+ if (presentation.preserveDraft) return;
95
181
  queueMicrotask(() => {
96
- if (!instance.root || instance.root.hidden) return;
182
+ if (!instance.root || instance.root.hidden || instance.root.closest('[hidden]')) return;
97
183
  const target = instance.root.querySelector('[data-provider-row][aria-current="true"]')
98
184
  || instance.root.querySelector('input, button, select, textarea');
99
185
  target?.focus?.();
100
186
  });
101
187
  }
102
188
 
103
- function requestClose(instance) {
104
- if (instance.dirty && !instance.document.defaultView.confirm(instance.tx(
189
+ async function requestClose(instance) {
190
+ if (instance.confirmation) { finishProviderConfirmation(instance, false); return false; }
191
+ if (instance.modelEditor) { closeModelEditor(instance); return false; }
192
+ if (instance.dirty && !await confirmProviderAction(instance, instance.tx(
105
193
  'Discard unsaved provider changes?',
106
194
  '放弃尚未保存的模型服务配置吗?'
107
195
  ))) {
@@ -111,6 +199,9 @@
111
199
  instance.callbacks.onCancelTest?.();
112
200
  instance.busy = '';
113
201
  }
202
+ const secretInput = instance.root.querySelector('[data-provider-field="apiKey"]');
203
+ if (secretInput) secretInput.value = '';
204
+ instance.secretAction = 'unchanged';
114
205
  instance.root.close?.();
115
206
  instance.root.hidden = true;
116
207
  instance.busy = '';
@@ -119,6 +210,7 @@
119
210
  }
120
211
 
121
212
  function setCatalog(instance, catalog, selectedId) {
213
+ finishProviderConfirmation(instance, false, false);
122
214
  instance.catalog = instance.Profiles.normalizeCatalog(catalog || {});
123
215
  const nextId = selectedId || (
124
216
  instance.catalog.providers.some(provider => provider.id === instance.selectedId)
@@ -131,13 +223,17 @@
131
223
  delete instance.root.dataset.dirty;
132
224
  instance.secretAction = 'unchanged';
133
225
  instance.verifications = {};
226
+ instance.verificationStale = false;
227
+ instance.templatePicker = false;
228
+ instance.modelFilter = '';
229
+ closeModelEditor(instance, false);
134
230
  instance.pendingCatalog = null;
135
231
  setStatus(instance, { tone: '', title: '' });
136
232
  render(instance);
137
233
  }
138
234
 
139
235
  function offerCatalog(instance, catalog, selectedId) {
140
- if (!instance.dirty) {
236
+ if (!instance.dirty && !instance.modelEditor && !instance.confirmation) {
141
237
  setCatalog(instance, catalog, selectedId);
142
238
  return true;
143
239
  }
@@ -156,14 +252,20 @@
156
252
 
157
253
  function render(instance) {
158
254
  const tx = instance.tx;
159
- instance.root.querySelector('[data-provider-dialog-title]').textContent = tx('Model API providers', '模型 API 服务');
255
+ instance.root.querySelector('[data-provider-action="close"]')?.setAttribute('aria-label', tx('Close', '关闭'));
256
+ const reload = instance.root.querySelector('[data-provider-action="reload"]');
257
+ if (reload) {
258
+ reload.hidden = !instance.embedded;
259
+ reload.textContent = tx('Refresh', '刷新');
260
+ }
261
+ instance.root.querySelector('[data-provider-dialog-title]').textContent = tx('Models & providers', '模型与服务');
160
262
  instance.root.querySelector('[data-provider-dialog-experimental]').textContent = tx(
161
263
  'Third-party experimental',
162
264
  '第三方实验性'
163
265
  );
164
266
  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 任务使用的实验性第三方集成;兼容性取决于具体模型服务与网关。'
267
+ 'Connect a provider, configure its models, and choose it for this project.',
268
+ '连接模型服务,管理模型,并选择当前项目使用的连接。'
167
269
  );
168
270
  if (instance.callbacks.hasCurrentProject?.() === false) instance.root.querySelector('[data-provider-dialog-subtitle]').textContent = tx(
169
271
  'Manage shared model API profiles. Open a project to choose which provider its future turns use.',
@@ -175,40 +277,71 @@
175
277
  }
176
278
 
177
279
  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('');
280
+ const tx = instance.tx, list = instance.root.querySelector('[data-provider-list]');
281
+ list.innerHTML = '<label class="codex-provider-search"><span class="codex-provider-sr-only">'
282
+ + escapeHtml(tx('Search providers', '搜索服务')) + '</span><input type="search" data-provider-search value="'
283
+ + escapeAttr(instance.providerFilter) + '" placeholder="' + escapeAttr(tx('Search providers', '搜索服务')) + '"></label>'
284
+ + '<div class="codex-provider-nav-items">' + instance.catalog.providers.map(provider => {
285
+ const active = provider.id === getCurrentProjectProviderId(instance);
286
+ const subtitle = active ? tx('Current project', '当前项目') : provider.kind === 'builtin'
287
+ ? tx('Local authentication', '本地身份验证')
288
+ : tx(provider.models.length + ' models', provider.models.length + ' 个模型');
289
+ const searchText = (provider.name + ' ' + (provider.baseUrl || '')).toLowerCase();
290
+ return '<button type="button" class="codex-provider-row" data-provider-row="' + escapeAttr(provider.id)
291
+ + '" data-provider-search-text="' + escapeAttr(searchText)
292
+ + '" aria-current="' + String(provider.id === instance.selectedId) + '"'
293
+ + (searchText.includes(instance.providerFilter.toLowerCase()) ? '' : ' hidden') + '>'
294
+ + '<span class="codex-provider-nav-mark" aria-hidden="true">'
295
+ + (provider.kind === 'builtin' ? renderProviderTemplateIcon({ id: 'openai-compatible' }) : escapeHtml(provider.name.slice(0, 1).toUpperCase())) + '</span>'
296
+ + '<span class="codex-provider-nav-copy"><span class="codex-provider-row-main" title="' + escapeAttr(provider.name) + '">' + escapeHtml(provider.name)
297
+ + '</span><span class="codex-provider-row-status" data-tone="' + (active ? 'active' : 'muted') + '">'
298
+ + escapeHtml(subtitle) + '</span></span></button>';
299
+ }).join('') + '</div><button type="button" class="codex-provider-add" data-provider-action="add">+ '
300
+ + escapeHtml(tx('Add provider', '添加模型服务')) + '</button>';
301
+ }
302
+
303
+ // Render a detached reference, then change only owned copy in the live DOM.
304
+ // Inputs, focus, selection, modal ownership, listeners and secret values stay put.
305
+ function refreshLocale(instance) {
306
+ if (!instance.root || instance.root.hidden) return;
307
+ const reference = { ...instance, root: instance.root.cloneNode(true) };
308
+ if (instance.root.querySelector('[data-provider-field="name"]')) reference.draft = readContext(instance).draft;
309
+ render(reference);
310
+ if (instance.modelEditor) openModelEditor(reference, instance.modelEditor.id, { show: false });
311
+ patchLocaleCopy(instance.root, reference.root);
312
+ if (instance.lastStatus) setStatus(instance, instance.lastStatus);
313
+ const confirmation = instance.root.querySelector('[data-provider-confirmation]');
314
+ if (instance.confirmation && confirmation) {
315
+ for (const node of confirmation.querySelectorAll('h3, p, button')) node.textContent = instance.translateStatusText(node.textContent);
316
+ }
317
+ const secret = instance.root.querySelector('[data-provider-field="apiKey"]');
318
+ const toggle = instance.root.querySelector('[data-provider-action="toggle-secret"]');
319
+ if (toggle) toggle.textContent = instance.tx(secret?.type === 'text' ? 'Hide' : 'Show', secret?.type === 'text' ? '隐藏' : '显示');
320
+ refreshTestState(instance); updateDisclosureText(instance);
321
+ if (instance.modelEditor) {
322
+ updateModelReasoningSummary(instance);
323
+ const error = instance.root.querySelector('[data-model-error]');
324
+ if (error && instance.modelEditor.error) error.textContent = formatModelEditorError(instance, instance.modelEditor.error);
325
+ }
326
+ }
327
+
328
+ function patchLocaleCopy(current, reference) {
329
+ if (!current || !reference || current.nodeType !== reference.nodeType) return;
330
+ if (current.nodeType === 3) { if (current.nodeValue !== reference.nodeValue) current.nodeValue = reference.nodeValue; return; }
331
+ if (current.nodeType !== 1 || current.tagName !== reference.tagName) return;
332
+ for (const attr of ['title', 'aria-label', 'placeholder']) {
333
+ const next = reference.getAttribute(attr);
334
+ if (next !== null && current.getAttribute(attr) !== next) current.setAttribute(attr, next);
335
+ }
336
+ if (['INPUT', 'TEXTAREA'].includes(current.tagName)) return;
337
+ const actual = current.childNodes, expected = reference.childNodes;
338
+ if (actual.length !== expected.length) return;
339
+ for (let index = 0; index < actual.length; index++) patchLocaleCopy(actual[index], expected[index]);
208
340
  }
209
341
 
210
342
  function renderDetail(instance) {
211
343
  const detail = instance.root.querySelector('[data-provider-detail]');
344
+ if (instance.templatePicker) { renderTemplatePicker(instance, detail); return; }
212
345
  const provider = getSelectedProvider(instance);
213
346
  if (!provider || provider.kind === 'builtin') {
214
347
  renderBuiltinDetail(instance, detail);
@@ -216,12 +349,11 @@
216
349
  }
217
350
  const tx = instance.tx;
218
351
  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
352
  const active = provider.id && provider.id === getCurrentProjectProviderId(instance);
353
+ const profileStatus = !provider.id
354
+ ? tx('Unsaved provider profile', '尚未保存的模型服务配置')
355
+ : active ? tx('Used by new runs in this project', '当前项目的新任务将使用此服务')
356
+ : tx('Saved provider profile', '已保存的模型服务配置');
225
357
  const acceptedHost = getEndpointHost(draft.baseUrl);
226
358
  const secretSavedAt = formatSecretSavedAt(instance, provider.secretUpdatedAt);
227
359
  const disclosureSatisfied = Boolean(
@@ -233,7 +365,7 @@
233
365
  <div class="codex-provider-detail-titleline">
234
366
  <div>
235
367
  <h3>${escapeHtml(draft.name || tx('Custom provider', '自定义模型服务'))}</h3>
236
- <p>${escapeHtml(active ? tx('Used by new runs in this project', '当前项目的新任务将使用此服务') : tx('Saved provider profile', '已保存的模型服务配置'))}</p>
368
+ <p>${escapeHtml(profileStatus)}</p>
237
369
  </div>
238
370
  ${active ? `<span class="codex-provider-active-badge">${escapeHtml(tx('Current project', '当前项目'))}</span>` : ''}
239
371
  </div>
@@ -246,11 +378,13 @@
246
378
  <span>${escapeHtml(tx('Base URL', '基础 URL'))}</span>
247
379
  <input type="url" data-provider-field="baseUrl" value="${escapeAttr(draft.baseUrl || '')}" placeholder="https://provider.example/v1" spellcheck="false">
248
380
  <small>${escapeHtml(tx('HTTPS is required except for localhost.', '除 localhost 外必须使用 HTTPS。'))}</small>
381
+ <small data-provider-connection-hint hidden></small>
249
382
  </label>
250
383
  <label class="codex-provider-field codex-provider-field--wide">
251
384
  <span>${escapeHtml(tx('API key', 'API 密钥'))}</span>
252
385
  <div class="codex-provider-secret-row">
253
386
  <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', '本地无鉴权端点可留空'))}">
387
+ <button type="button" class="codex-provider-inline-button" data-provider-action="toggle-secret">${escapeHtml(tx('Show', '显示'))}</button>
254
388
  ${provider.hasSecret ? `<button type="button" class="codex-provider-inline-button" data-provider-action="clear-secret">${escapeHtml(tx('Clear', '清除'))}</button>` : ''}
255
389
  </div>
256
390
  <small data-provider-secret-note>${provider.hasSecret
@@ -259,16 +393,8 @@
259
393
  : tx('Stored by the Native Host. The plaintext is never returned to the Extension.', '由 Native Host 保管。扩展不会读回密钥明文。'))}`
260
394
  : escapeHtml(tx('No API key is currently stored. Local no-auth endpoints may leave this empty.', '当前未存储 API 密钥。本地无鉴权端点可以留空。'))}</small>
261
395
  </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
396
  </div>
397
+ <section class="codex-provider-model-section" data-provider-model-section></section>
272
398
  <details class="codex-provider-advanced">
273
399
  <summary>${escapeHtml(tx('Advanced compatibility', '高级兼容设置'))}</summary>
274
400
  <div class="codex-provider-form-grid">
@@ -281,15 +407,6 @@
281
407
  <option value="anthropic" ${draft.wireApiPreference === 'anthropic' ? 'selected' : ''}>Anthropic Messages</option>
282
408
  </select>
283
409
  </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
410
  <label class="codex-provider-field">
294
411
  <span>${escapeHtml(tx('Request timeout', '请求超时'))}</span>
295
412
  <select data-provider-field="requestTimeoutMs">
@@ -311,7 +428,7 @@
311
428
  <input type="text" data-provider-field="apiKeyHeaderName" value="${escapeAttr(draft.apiKeyHeaderName || '')}" placeholder="X-API-Key" spellcheck="false">
312
429
  </label>
313
430
  <label class="codex-provider-field">
314
- <span>${escapeHtml(tx('Context window', '上下文窗口'))}</span>
431
+ <span>${escapeHtml(tx('Default context for new models', '新模型的默认上下文'))}</span>
315
432
  <input type="number" data-provider-field="contextWindow" min="8192" max="4000000" step="1024" value="${Number(draft.contextWindow) || 262144}">
316
433
  </label>
317
434
  <label class="codex-provider-field">
@@ -378,12 +495,12 @@
378
495
  <small>${escapeHtml(tx('Vendor-specific fields only; core model, messages, tools, and stream fields are protected.', '仅填写服务商特有字段;model、messages、tools 和 stream 等核心字段不可覆盖。'))}</small>
379
496
  </label>
380
497
  <label class="codex-provider-field">
381
- <span>Anthropic version</span>
498
+ <span>${escapeHtml(tx('Anthropic version', 'Anthropic 版本'))}</span>
382
499
  <input type="text" data-provider-field="anthropicVersion" value="${escapeAttr(draft.anthropicVersion || '2023-06-01')}" spellcheck="false">
383
500
  </label>
384
501
  <label class="codex-provider-field">
385
502
  <span>Anthropic beta</span>
386
- <input type="text" data-provider-field="anthropicBeta" value="${escapeAttr(draft.anthropicBeta || '')}" placeholder="optional" spellcheck="false">
503
+ <input type="text" data-provider-field="anthropicBeta" value="${escapeAttr(draft.anthropicBeta || '')}" placeholder="${escapeAttr(tx('optional', '可选'))}" spellcheck="false">
387
504
  </label>
388
505
  <label class="codex-provider-field">
389
506
  <span>${escapeHtml(tx('Anthropic thinking mode', 'Anthropic 思考模式'))}</span>
@@ -450,6 +567,14 @@
450
567
  <span class="codex-provider-test-state" data-provider-test-state>${escapeHtml(formatVerification(instance, provider, draft.defaultModelId))}</span>
451
568
  </div>
452
569
  `;
570
+ const protocol = detail.querySelector('[data-provider-field="wireApiPreference"]')?.closest('label');
571
+ const connection = detail.querySelector('.codex-provider-form-grid');
572
+ if (protocol && connection) { protocol.classList.add('codex-provider-field--wide'); connection.prepend(protocol); }
573
+ const name = detail.querySelector('[data-provider-field="name"]')?.closest('label');
574
+ const heading = detail.querySelector('.codex-provider-detail-titleline h3');
575
+ if (name && heading) { name.classList.add('codex-provider-name-field'); heading.replaceWith(name); }
576
+ updatePresetConnectionHint(instance);
577
+ renderModelList(instance);
453
578
  }
454
579
 
455
580
  function renderBuiltinDetail(instance, detail) {
@@ -484,8 +609,8 @@
484
609
  const actions = instance.root.querySelector('[data-provider-footer-actions]');
485
610
  const provider = getSelectedProvider(instance);
486
611
  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>`;
612
+ if (instance.templatePicker || !provider || provider.kind === 'builtin') {
613
+ actions.innerHTML = instance.embedded ? '' : `<button type="button" class="codex-provider-secondary-button" data-provider-action="close">${escapeHtml(tx('Close', '关闭'))}</button>`;
489
614
  return;
490
615
  }
491
616
  const active = provider.id && provider.id === getCurrentProjectProviderId(instance);
@@ -506,7 +631,7 @@
506
631
  ${provider.id ? `<button type="button" class="codex-provider-danger-button" data-provider-action="delete">${escapeHtml(tx('Delete', '删除'))}</button>` : ''}
507
632
  ${instance.pendingCatalog ? `<button type="button" class="codex-provider-secondary-button" data-provider-action="reload-external">${escapeHtml(tx('Reload latest', '加载最新配置'))}</button>` : ''}
508
633
  <span class="codex-provider-footer-spacer"></span>
509
- <button type="button" class="codex-provider-secondary-button" data-provider-action="close">${escapeHtml(tx('Cancel', '取消'))}</button>
634
+ ${!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
635
  ${actionState.showSave ? `<button type="button" class="codex-provider-secondary-button" data-provider-action="save"${saveDisabled}>${escapeHtml(tx('Save', '保存'))}</button>` : ''}
511
636
  ${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
637
  ${actionState.showUse ? `<button type="button" class="codex-provider-primary-button" data-provider-action="use"${useDisabled}>${escapeHtml(tx('Use for this project', '用于当前项目'))}</button>` : ''}
@@ -526,7 +651,32 @@
526
651
  };
527
652
  }
528
653
 
529
- function handleClick(instance, event) {
654
+ async function handleClick(instance, event) {
655
+ const answer = event.target.closest('[data-provider-confirm-answer]');
656
+ if (answer) {
657
+ event.preventDefault();
658
+ finishProviderConfirmation(instance, answer.dataset.providerConfirmAnswer === 'confirm');
659
+ return;
660
+ }
661
+ if (instance.confirmation) return;
662
+ const template = event.target.closest('[data-provider-template]');
663
+ if (template && (!instance.busy || instance.busy === 'failed')) {
664
+ instance.draft = instance.Profiles.buildPresetDraft(template.dataset.providerTemplate);
665
+ instance.draft.name = providerTemplateTitle(instance, template.dataset.providerTemplate);
666
+ instance.templatePicker = false;
667
+ instance.dirty = true;
668
+ instance.root.dataset.dirty = 'true';
669
+ instance.secretAction = 'unchanged';
670
+ instance.verificationStale = true;
671
+ render(instance);
672
+ instance.root.querySelector('[data-provider-field="apiKey"]')?.focus();
673
+ return;
674
+ }
675
+ const modelAction = event.target.closest('[data-provider-model-action]');
676
+ if (modelAction) {
677
+ if (!instance.busy || instance.busy === 'failed') handleModelAction(instance, modelAction.dataset.providerModelAction, modelAction.dataset.modelId || '');
678
+ return;
679
+ }
530
680
  const action = event.target.closest('[data-provider-action]')?.dataset.providerAction;
531
681
  const providerRow = event.target.closest('[data-provider-row]');
532
682
  if (providerRow) {
@@ -538,16 +688,41 @@
538
688
  }
539
689
  event.preventDefault();
540
690
  if (action === 'close') {
541
- requestClose(instance);
691
+ if (instance.embedded) {
692
+ const host = instance.embeddedContainer;
693
+ const catalog = instance.pendingCatalog?.catalog || instance.catalog;
694
+ if (await requestClose(instance) && host?.isConnected) open(instance, catalog, { container: host });
695
+ } else requestClose(instance);
696
+ return;
697
+ }
698
+ if (action === 'reload') {
699
+ if (!instance.busy || instance.busy === 'failed') instance.callbacks.onReload?.();
542
700
  return;
543
701
  }
544
702
  if (action === 'add') {
545
- selectProvider(instance, '__new__');
703
+ if (instance.selectedId === '__new__') {
704
+ if (instance.dirty && !await confirmProviderAction(instance, instance.tx('Discard unsaved provider changes?', '放弃尚未保存的模型服务配置吗?'))) return;
705
+ instance.draft = instance.Profiles.buildEmptyDraft();
706
+ instance.dirty = false;
707
+ delete instance.root.dataset.dirty;
708
+ instance.templatePicker = true;
709
+ render(instance);
710
+ } else selectProvider(instance, '__new__');
711
+ return;
712
+ }
713
+ if (action === 'templates-back') {
714
+ selectProvider(instance, instance.previousProviderId || 'builtin');
715
+ return;
716
+ }
717
+ if (action === 'toggle-secret') {
718
+ const input = instance.root.querySelector('[data-provider-field="apiKey"]');
719
+ if (input) input.type = input.type === 'password' ? 'text' : 'password';
720
+ event.target.closest('button').textContent = instance.tx(input?.type === 'text' ? 'Hide' : 'Show', input?.type === 'text' ? '隐藏' : '显示');
546
721
  return;
547
722
  }
548
723
  if (action === 'reload-external') {
549
724
  if (!instance.pendingCatalog) return;
550
- if (instance.dirty && !instance.document.defaultView.confirm(instance.tx(
725
+ if (instance.dirty && !await confirmProviderAction(instance, instance.tx(
551
726
  'Discard this draft and load the latest provider settings?',
552
727
  '放弃当前草稿并加载最新模型服务配置吗?'
553
728
  ))) {
@@ -566,7 +741,7 @@
566
741
  '立即删除已存储的 API 密钥吗?重新加载已保存配置时,未保存的表单改动会被放弃。'
567
742
  )
568
743
  : instance.tx('Remove the stored API key now?', '立即删除已存储的 API 密钥吗?');
569
- if (instance.document.defaultView.confirm(warning)) {
744
+ if (await confirmProviderAction(instance, warning, 'secret')) {
570
745
  instance.callbacks.onClearSecret?.({
571
746
  profileId: provider.id,
572
747
  expectedRevision: provider.revision
@@ -588,15 +763,16 @@
588
763
  }
589
764
  if (action === 'delete') {
590
765
  const context = readContext(instance);
591
- if (instance.document.defaultView.confirm(instance.tx(
766
+ if (await confirmProviderAction(instance, instance.tx(
592
767
  `Delete ${context.draft.name}?`,
593
768
  `删除 ${context.draft.name} 吗?`
594
- ))) {
769
+ ), 'provider')) {
595
770
  instance.callbacks.onDelete?.(context);
596
771
  }
597
772
  return;
598
773
  }
599
774
  if (action === 'test') {
775
+ if (!validateConnectionForm(instance)) return;
600
776
  const modelId = getSelectedTestModelId(instance);
601
777
  invalidateVerification(instance, false, modelId);
602
778
  instance.callbacks.onTest?.(readContext(instance));
@@ -628,6 +804,7 @@
628
804
  return;
629
805
  }
630
806
  if (action === 'save' || action === 'save-use') {
807
+ if (!validateConnectionForm(instance)) return;
631
808
  const context = readContext(instance);
632
809
  if (action === 'save-use' && !instance.root.querySelector('[data-provider-disclosure]')?.checked) {
633
810
  setStatus(instance, {
@@ -641,6 +818,21 @@
641
818
  }
642
819
 
643
820
  function handleInput(instance, event) {
821
+ if (instance.modelEditor && event.target.closest('[data-provider-model-editor]')) {
822
+ updateModelReasoningSummary(instance);
823
+ return;
824
+ }
825
+ if (event.target.matches('[data-provider-search]')) {
826
+ instance.providerFilter = event.target.value;
827
+ for (const row of instance.root.querySelectorAll('[data-provider-search-text]'))
828
+ row.hidden = !row.dataset.providerSearchText.includes(instance.providerFilter.toLowerCase());
829
+ return;
830
+ }
831
+ if (event.target.matches('[data-provider-model-search]')) {
832
+ instance.modelFilter = event.target.value;
833
+ filterModelRows(instance);
834
+ return;
835
+ }
644
836
  if (event.target.matches('[data-provider-test-model]')) {
645
837
  refreshTestState(instance);
646
838
  return;
@@ -650,13 +842,16 @@
650
842
  if (event.target.dataset.providerField === 'apiKey') {
651
843
  instance.secretAction = event.target.value ? 'replace' : (getSelectedProvider(instance)?.hasSecret ? 'unchanged' : 'clear');
652
844
  }
653
- if (field === 'baseUrl') {
845
+ const destinationChanged = field === 'baseUrl'
846
+ || (field === 'wireApiPreference' && syncPresetProtocol(instance, event.target.value));
847
+ if (destinationChanged) {
654
848
  const disclosure = instance.root.querySelector('[data-provider-disclosure]');
655
849
  if (disclosure) disclosure.checked = false;
656
850
  updateDisclosureText(instance);
657
851
  }
658
852
  markDirty(instance, { invalidateVerification: field !== 'name' });
659
- if (field === 'baseUrl') {
853
+ if (field === 'wireApiPreference') updatePresetConnectionHint(instance);
854
+ if (destinationChanged) {
660
855
  renderFooter(instance);
661
856
  applyBusyState(instance);
662
857
  }
@@ -664,6 +859,24 @@
664
859
  }
665
860
 
666
861
  function handleKeydown(instance, event) {
862
+ if (instance.confirmation) {
863
+ if (event.key === 'Escape') {
864
+ event.preventDefault(); event.stopPropagation();
865
+ finishProviderConfirmation(instance, false);
866
+ }
867
+ return;
868
+ }
869
+ if (instance.modelEditor) {
870
+ if (event.key === 'Escape') {
871
+ event.preventDefault(); event.stopPropagation(); closeModelEditor(instance);
872
+ } else if (event.key === 'Enter' && !event.isComposing
873
+ && event.target.matches('input:not([type="checkbox"])')) {
874
+ event.preventDefault(); commitModelEditor(instance);
875
+ }
876
+ return;
877
+ }
878
+ // The outer settings dialog owns focus traversal and Escape when embedded.
879
+ if (instance.embedded) return;
667
880
  if (event.key === 'Escape') {
668
881
  event.preventDefault();
669
882
  requestClose(instance);
@@ -688,16 +901,20 @@
688
901
  }
689
902
  }
690
903
 
691
- function selectProvider(instance, id) {
904
+ async function selectProvider(instance, id) {
692
905
  if (id === instance.selectedId) {
693
906
  return;
694
907
  }
695
- if (instance.dirty && !instance.document.defaultView.confirm(instance.tx(
908
+ if (instance.dirty && !await confirmProviderAction(instance, instance.tx(
696
909
  'Discard unsaved provider changes?',
697
910
  '放弃尚未保存的模型服务配置吗?'
698
911
  ))) {
699
912
  return;
700
913
  }
914
+ if (id === '__new__') instance.previousProviderId = instance.selectedId;
915
+ instance.templatePicker = id === '__new__';
916
+ instance.modelFilter = '';
917
+ instance.verificationStale = false;
701
918
  instance.selectedId = id;
702
919
  instance.draft = id === '__new__'
703
920
  ? instance.Profiles.buildEmptyDraft()
@@ -714,32 +931,16 @@
714
931
  function readContext(instance) {
715
932
  const provider = getSelectedProvider(instance) || instance.Profiles.buildEmptyDraft();
716
933
  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';
934
+ const modelDraft = instance.draft || provider;
935
+ const defaultModelId = modelDraft.defaultModelId || '';
723
936
  const draft = {
724
937
  name: String(get('name')?.value || '').trim(),
725
938
  baseUrl: String(get('baseUrl')?.value || '').trim(),
726
939
  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
- }),
940
+ models: (modelDraft.models || []).map(model => ({
941
+ ...model, reasoningEfforts: [...(model.reasoningEfforts || [])],
942
+ inputModalities: [...(model.inputModalities || ['text'])]
943
+ })),
743
944
  defaultModelId,
744
945
  requestTimeoutMs: Number(get('requestTimeoutMs')?.value || 30000),
745
946
  reasoningAdapter: get('reasoningAdapter')?.value || 'auto',
@@ -760,6 +961,7 @@
760
961
  ,anthropicPromptCaching: Boolean(get('anthropicPromptCaching')?.checked)
761
962
  ,impersonateClaudeCode: Boolean(get('impersonateClaudeCode')?.checked)
762
963
  ,maxOutputTokens: Number(get('maxOutputTokens')?.value || 65536)
964
+ ,outputBudgetVersion: 1
763
965
  };
764
966
  const apiKey = String(get('apiKey')?.value || '');
765
967
  const secretMutation = instance.secretAction === 'replace' && apiKey
@@ -794,7 +996,9 @@
794
996
  instance.root.dataset.dirty = 'true';
795
997
  if (options.invalidateVerification !== false) {
796
998
  instance.verifications = {};
999
+ instance.verificationStale = true;
797
1000
  invalidateVerification(instance, true);
1001
+ refreshModelStatuses(instance);
798
1002
  }
799
1003
  if (footerNeedsRefresh) {
800
1004
  // Saved providers initially render Use (inactive) or no primary action
@@ -813,6 +1017,7 @@
813
1017
  setStatus(instance, { tone: '', title: '' });
814
1018
  }
815
1019
  syncTestAction(instance);
1020
+ refreshModelStatuses(instance);
816
1021
  applyBusyState(instance);
817
1022
  }
818
1023
 
@@ -822,7 +1027,9 @@
822
1027
  const canActivate = canActivateCurrentProvider(instance);
823
1028
  instance.root.dataset.busy = instance.busy || '';
824
1029
  for (const element of instance.root.querySelectorAll('input, select, textarea, button')) {
825
- if (element.matches('[data-provider-action="close"]')) {
1030
+ if (element.matches('[data-provider-confirm-answer]')) {
1031
+ element.disabled = false;
1032
+ } else if (element.matches('[data-provider-action="close"]')) {
826
1033
  element.disabled = false;
827
1034
  } else if (element.matches('[data-provider-action="cancel-test"]')) {
828
1035
  element.disabled = false;
@@ -837,9 +1044,10 @@
837
1044
  }
838
1045
 
839
1046
  function setStatus(instance, status = {}) {
1047
+ instance.lastStatus = status;
840
1048
  const element = instance.root.querySelector('[data-provider-status]');
841
1049
  element.dataset.tone = status.tone || '';
842
- element.textContent = [status.title, status.detail].filter(Boolean).join(' ');
1050
+ element.textContent = [status.title, status.detail].filter(Boolean).map(text => instance.translateStatusText?.(text) || text).join(' ');
843
1051
  }
844
1052
 
845
1053
  function setVerification(instance, verification = {}) {
@@ -900,7 +1108,9 @@
900
1108
 
901
1109
  function canSaveCurrentDraft(instance) {
902
1110
  const provider = getSelectedProvider(instance);
903
- return Boolean(provider && provider.kind === 'custom');
1111
+ const draft = instance.draft || provider;
1112
+ return Boolean(!instance.templatePicker && !instance.modelEditor && provider?.kind === 'custom'
1113
+ && draft?.models?.length && draft.models.some(model => model.id === draft.defaultModelId));
904
1114
  }
905
1115
 
906
1116
  function canActivateCurrentProvider(instance) {
@@ -914,11 +1124,12 @@
914
1124
  function formatVerification(instance, provider, modelId) {
915
1125
  const transient = instance.verifications[modelId];
916
1126
  if (transient?.status === 'failed') {
917
- return instance.tx('Failed in this dialog', '本次测试失败');
1127
+ return instance.tx('Failed in this dialog', '本次测试失败') + (transient.errorCode ? ' · ' + transient.errorCode : '');
918
1128
  }
919
1129
  if (transient?.status === 'tested') {
920
1130
  return instance.tx('Tested for this draft', '当前草稿已测试');
921
1131
  }
1132
+ if (instance.verificationStale) return instance.tx('Settings changed; retest when ready', '配置已更改,可重新测试');
922
1133
  const diagnostic = provider?.modelDiagnostics?.[modelId];
923
1134
  if (diagnostic?.status === 'tested') {
924
1135
  const mode = diagnostic.upstreamResponseMode;
@@ -935,8 +1146,9 @@
935
1146
  const provider = getSelectedProvider(instance);
936
1147
  const modelId = getSelectedTestModelId(instance) || provider?.defaultModelId;
937
1148
  state.textContent = formatVerification(instance, provider, modelId);
938
- const status = instance.verifications[modelId]?.status || provider?.modelDiagnostics?.[modelId]?.status || '';
1149
+ const status = instance.verifications[modelId]?.status || (!instance.verificationStale && provider?.modelDiagnostics?.[modelId]?.status) || '';
939
1150
  state.dataset.tone = status === 'failed' ? 'failed' : status === 'tested' ? 'success' : '';
1151
+ refreshModelStatuses(instance);
940
1152
  }
941
1153
 
942
1154
  function getSelectedTestModelId(instance) {
@@ -947,7 +1159,43 @@
947
1159
  return String(instance.root.querySelector('[data-provider-field="baseUrl"]')?.value || '').trim();
948
1160
  }
949
1161
 
1162
+ function syncPresetProtocol(instance, protocol) {
1163
+ const get = name => instance.root.querySelector('[data-provider-field="' + name + '"]');
1164
+ if (get('fullEndpoint')?.checked) return false;
1165
+ const info = instance.Profiles.getPresetConnectionInfo(get('baseUrl')?.value);
1166
+ const next = info && instance.Profiles.getPresetConnection(info.id, protocol);
1167
+ if (!next || next.baseUrl === info.connection.baseUrl) return false;
1168
+ get('baseUrl').value = next.baseUrl;
1169
+ // Respect manually overridden authentication and reasoning settings.
1170
+ for (const field of ['authMode', 'reasoningAdapter']) {
1171
+ const input = get(field);
1172
+ if (input?.value === info.connection[field]) input.value = next[field];
1173
+ }
1174
+ return true;
1175
+ }
1176
+
1177
+ function updatePresetConnectionHint(instance) {
1178
+ const hint = instance.root.querySelector('[data-provider-connection-hint]');
1179
+ if (!hint) return;
1180
+ const info = instance.Profiles.getPresetConnectionInfo(getCurrentBaseUrl(instance));
1181
+ const protocol = instance.root.querySelector('[data-provider-field="wireApiPreference"]')?.value;
1182
+ let text = '';
1183
+ if (info && protocol !== 'auto' && protocol !== info.connection.wireApiPreference) {
1184
+ text = instance.tx('This preset address uses a different protocol. Enter an endpoint matching the selected API format.',
1185
+ '此预设地址与所选协议不同,请填写该协议对应的地址。');
1186
+ } else if (info?.id === 'kimi') {
1187
+ text = instance.tx('This is the Kimi Open Platform China endpoint. Kimi Code and international keys use their own endpoints.',
1188
+ '此处为 Kimi 开放平台中国站地址;Kimi Code 和国际站密钥需要各自对应的端点。');
1189
+ } else if (info?.id === 'glm' && info.connection.wireApiPreference === 'anthropic') {
1190
+ 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.',
1191
+ 'GLM Anthropic 接入与通用按量 API 的端点不同。请确认密钥具备此端点的权限;Chat 使用通用 API 地址。');
1192
+ }
1193
+ hint.textContent = text;
1194
+ hint.hidden = !text;
1195
+ }
1196
+
950
1197
  function updateDisclosureText(instance) {
1198
+ updatePresetConnectionHint(instance);
951
1199
  const text = instance.root.querySelector('[data-provider-disclosure-text]');
952
1200
  if (!text) return;
953
1201
  const baseUrl = getCurrentBaseUrl(instance) || instance.tx('this endpoint', '此端点');
@@ -971,6 +1219,366 @@
971
1219
  button.setAttribute('aria-label', actionLabel);
972
1220
  }
973
1221
 
1222
+
1223
+
1224
+ function providerTemplateTitle(instance, id) {
1225
+ if (id === 'kimi') return 'Kimi';
1226
+ if (id === 'glm') return 'GLM';
1227
+ if (id === 'deepseek') return 'DeepSeek';
1228
+ if (id === 'openai-compatible') return instance.tx('OpenAI-compatible API', 'OpenAI 兼容接口');
1229
+ if (id === 'anthropic-compatible') return instance.tx('Anthropic-compatible API', 'Anthropic 兼容接口');
1230
+ return instance.tx('Custom connection', '自定义连接');
1231
+ }
1232
+
1233
+ function confirmProviderAction(instance, message, kind = 'discard') {
1234
+ if (!instance.root || instance.root.hidden || instance.confirmation) return Promise.resolve(false);
1235
+ const modal = instance.root.querySelector('[data-provider-confirmation]');
1236
+ if (!modal) return Promise.resolve(false);
1237
+ const tx = instance.tx;
1238
+ const titles = {
1239
+ discard: tx('Unsaved changes', '尚未保存的修改'),
1240
+ secret: tx('Remove API key?', '清除 API 密钥?'),
1241
+ provider: tx('Delete connection?', '删除连接?'),
1242
+ model: tx('Remove model?', '移除模型?')
1243
+ };
1244
+ const labels = {
1245
+ discard: tx('Discard changes', '放弃修改'),
1246
+ secret: tx('Remove key', '清除密钥'),
1247
+ provider: tx('Delete connection', '删除连接'),
1248
+ model: tx('Remove model', '移除模型')
1249
+ };
1250
+ const cancel = kind === 'discard' ? tx('Keep editing', '继续编辑') : tx('Cancel', '取消');
1251
+ const provider = getSelectedProvider(instance);
1252
+ return new Promise(resolve => {
1253
+ instance.confirmation = { resolve, selectedId: instance.selectedId,
1254
+ revision: provider?.revision || 0, returnFocus: instance.document.activeElement };
1255
+ modal.innerHTML = '<header><span class="codex-provider-confirm-brand">Codex Overleaf Link</span>'
1256
+ + '<h3 id="codex-provider-confirm-title">' + escapeHtml(titles[kind] || titles.discard) + '</h3></header>'
1257
+ + '<p id="codex-provider-confirm-message">' + escapeHtml(message) + '</p>'
1258
+ + '<footer><button type="button" class="codex-provider-secondary-button" data-provider-confirm-answer="cancel">'
1259
+ + escapeHtml(cancel) + '</button><button type="button" class="codex-provider-danger-button" data-provider-confirm-answer="confirm">'
1260
+ + escapeHtml(labels[kind] || labels.discard) + '</button></footer>';
1261
+ try {
1262
+ if (modal.showModal) modal.showModal(); else modal.setAttribute('open', '');
1263
+ modal.querySelector('[data-provider-confirm-answer="cancel"]')?.focus();
1264
+ } catch (_error) {
1265
+ finishProviderConfirmation(instance, false);
1266
+ setStatus(instance, { tone: 'failed', title: tx('The confirmation could not be opened. No changes were discarded.',
1267
+ '确认窗口无法打开,未丢弃任何修改。') });
1268
+ }
1269
+ });
1270
+ }
1271
+
1272
+ function finishProviderConfirmation(instance, confirmed, restoreFocus = true) {
1273
+ const pending = instance.confirmation;
1274
+ if (!pending) return;
1275
+ instance.confirmation = null;
1276
+ const modal = instance.root?.querySelector('[data-provider-confirmation]');
1277
+ modal?.close?.();
1278
+ modal?.removeAttribute('open');
1279
+ modal?.replaceChildren();
1280
+ const sameTarget = Boolean(instance.root && !instance.root.hidden
1281
+ && instance.selectedId === pending.selectedId
1282
+ && (getSelectedProvider(instance)?.revision || 0) === pending.revision);
1283
+ pending.resolve(confirmed === true && sameTarget);
1284
+ if (restoreFocus) pending.returnFocus?.focus?.();
1285
+ }
1286
+
1287
+ function renderProviderTemplateIcon(preset) {
1288
+ const vendorPath = PROVIDER_VENDOR_ICON_PATHS[preset.id] || '';
1289
+ const path = preset.id === 'openai-compatible' ? CODEX_BLOSSOM_PATH
1290
+ : preset.id === 'anthropic-compatible' ? ANTHROPIC_LOGO_PATH : vendorPath;
1291
+ return path
1292
+ ? '<svg class="codex-provider-brand-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path fill-rule="'
1293
+ + (vendorPath ? 'evenodd' : 'nonzero') + '" d="' + path + '"></path></svg>'
1294
+ : escapeHtml(preset.short || '+');
1295
+ }
1296
+
1297
+ function renderTemplatePicker(instance, detail) {
1298
+ const tx = instance.tx;
1299
+ detail.innerHTML = '<div class="codex-provider-template-head"><button type="button" class="codex-provider-inline-button" data-provider-action="templates-back">'
1300
+ + escapeHtml(tx('Back', '返回')) + '</button><h3>' + escapeHtml(tx('Add a connection', '添加连接')) + '</h3></div>'
1301
+ + '<p class="codex-provider-help">' + escapeHtml(tx(
1302
+ '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.',
1303
+ '服务快捷入口默认采用 Anthropic Messages,也可切换 Chat Completions,预设地址随协议联动。请另行填写对应密钥和模型 ID。')) + '</p>'
1304
+ + '<div class="codex-provider-template-grid">' + instance.Profiles.getProviderPresets().map(preset =>
1305
+ '<button type="button" class="codex-provider-template" data-provider-template="' + escapeAttr(preset.id) + '">'
1306
+ + '<span class="codex-provider-template-mark" aria-hidden="true">' + renderProviderTemplateIcon(preset) + '</span>'
1307
+ + '<span><strong>' + escapeHtml(providerTemplateTitle(instance, preset.id))
1308
+ + '</strong><small>' + escapeHtml(preset.id === 'custom' ? tx('Gateway or self-hosted API', '网关或自托管 API')
1309
+ : preset.baseUrl ? (preset.wireApiPreference === 'anthropic' ? 'Anthropic Messages' : 'Chat Completions') + ' · ' + getEndpointHost(preset.baseUrl)
1310
+ : preset.wireApiPreference === 'anthropic' ? 'Anthropic Messages'
1311
+ : preset.wireApiPreference === 'responses' ? 'OpenAI Responses' : 'Responses / Chat Completions')
1312
+ + '</small></span><span aria-hidden="true">›</span></button>').join('') + '</div>'
1313
+ + '<p class="codex-provider-help">' + escapeHtml(tx(
1314
+ 'Adding or saving a connection does not switch the current project. Credentials remain with the Native Host.',
1315
+ '添加或保存连接不会切换当前项目。已保存的密钥仍由 Native Host 保管。')) + '</p>';
1316
+ }
1317
+
1318
+ function modelButton(action, id, label, path, selected = false) {
1319
+ return '<button type="button" class="codex-provider-model-action" data-provider-model-action="'
1320
+ + action + '" data-model-id="' + escapeAttr(id) + '" title="' + escapeAttr(label)
1321
+ + '" aria-label="' + escapeAttr(label) + '"' + (selected ? ' data-selected="true"' : '') + '>'
1322
+ + '<svg viewBox="0 0 20 20" aria-hidden="true"><path d="' + path + '"></path></svg></button>';
1323
+ }
1324
+
1325
+ function renderModelList(instance) {
1326
+ const section = instance.root.querySelector('[data-provider-model-section]');
1327
+ if (!section) return;
1328
+ const tx = instance.tx, provider = getSelectedProvider(instance), draft = instance.draft || provider;
1329
+ const models = draft.models || [];
1330
+ section.innerHTML = '<div class="codex-provider-section-head"><div><h4>' + escapeHtml(tx('Models', '模型'))
1331
+ + ' <span>' + models.length + '/32</span></h4><p>' + escapeHtml(tx(
1332
+ 'Model IDs are sent exactly as configured.', '模型 ID 将按配置原样发送。')) + '</p></div>'
1333
+ + '<button type="button" class="codex-provider-inline-button" data-provider-model-action="add">+ '
1334
+ + escapeHtml(tx('Add model', '添加模型')) + '</button></div>'
1335
+ + (models.length ? '<label class="codex-provider-search"><span class="codex-provider-sr-only">'
1336
+ + escapeHtml(tx('Search models', '搜索模型')) + '</span><input type="search" data-provider-model-search value="'
1337
+ + escapeAttr(instance.modelFilter) + '" placeholder="' + escapeAttr(tx('Search models', '搜索模型')) + '"></label>' : '')
1338
+ + '<div class="codex-provider-model-list">' + models.map(model => {
1339
+ const isDefault = model.id === draft.defaultModelId;
1340
+ const context = Number(model.contextWindow) || 262144;
1341
+ const facts = [
1342
+ context >= 1000000 ? (context / 1000000).toFixed(1).replace(/\.0$/, '') + 'M' : Math.round(context / 1024) + 'K',
1343
+ (model.inputModalities || []).includes('image') ? tx('Text + image', '文本与图像') : tx('Text', '文本')
1344
+ ];
1345
+ return '<div class="codex-provider-model-row" data-provider-model-row data-provider-model-search-text="'
1346
+ + escapeAttr((model.id + ' ' + model.label).toLowerCase()) + '">'
1347
+ + '<button type="button" class="codex-provider-model-identity" data-provider-model-action="edit" data-model-id="' + escapeAttr(model.id) + '">'
1348
+ + '<strong>' + escapeHtml(model.label || model.id) + (isDefault ? '<span class="codex-provider-default-tag">'
1349
+ + escapeHtml(tx('Default', '默认')) + '</span>' : '') + '</strong>'
1350
+ + (model.label && model.label !== model.id ? '<code>' + escapeHtml(model.id) + '</code>' : '')
1351
+ + '<small>' + escapeHtml(facts.join(' · ')) + '</small></button>'
1352
+ + '<div class="codex-provider-model-actions">'
1353
+ + modelButton('test', model.id, tx('Test ', '测试 ') + model.id, 'M7 4l9 6-9 6V4Z')
1354
+ + 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)
1355
+ + modelButton('remove', model.id, tx('Remove ', '移除 ') + model.id, 'M5 5l10 10M15 5 5 15')
1356
+ + '</div><span class="codex-provider-model-status" data-provider-model-status="' + escapeAttr(model.id) + '"></span></div>';
1357
+ }).join('') + '</div>'
1358
+ + (!models.length ? '<div class="codex-provider-model-empty">' + escapeHtml(tx(
1359
+ 'Add the first model to finish this connection. No model is inferred from the provider name.',
1360
+ '添加第一个模型以完成配置。服务名称不会被当成模型 ID。')) + '</div>' : '')
1361
+ + '<p class="codex-provider-help" data-provider-model-no-results hidden>' + escapeHtml(tx('No matching models.', '没有匹配的模型。')) + '</p>';
1362
+ refreshModelStatuses(instance);
1363
+ filterModelRows(instance);
1364
+ }
1365
+
1366
+ function filterModelRows(instance) {
1367
+ let visible = 0;
1368
+ const rows = instance.root.querySelectorAll('[data-provider-model-row]');
1369
+ for (const row of rows) {
1370
+ row.hidden = !row.dataset.providerModelSearchText.includes(instance.modelFilter.toLowerCase());
1371
+ if (!row.hidden) visible++;
1372
+ }
1373
+ const empty = instance.root.querySelector('[data-provider-model-no-results]');
1374
+ if (empty) empty.hidden = !rows.length || visible > 0;
1375
+ }
1376
+
1377
+ function refreshModelStatuses(instance) {
1378
+ const provider = getSelectedProvider(instance);
1379
+ for (const element of instance.root.querySelectorAll('[data-provider-model-status]')) {
1380
+ const id = element.dataset.providerModelStatus;
1381
+ const testing = instance.busy === 'testing' && id === getSelectedTestModelId(instance);
1382
+ const status = instance.verifications[id]?.status
1383
+ || (!instance.verificationStale && provider?.modelDiagnostics?.[id]?.status) || '';
1384
+ element.textContent = testing ? instance.tx('Testing connection…', '正在测试连接…') : formatVerification(instance, provider, id);
1385
+ element.dataset.tone = testing ? 'progress' : status === 'tested' ? 'success' : status === 'failed' ? 'failed' : '';
1386
+ }
1387
+ }
1388
+
1389
+ function replaceModelDraft(instance, next) {
1390
+ const secret = instance.root.querySelector('[data-provider-field="apiKey"]')?.value || '';
1391
+ const disclosure = instance.root.querySelector('[data-provider-disclosure]')?.checked === true;
1392
+ const advanced = instance.root.querySelector('.codex-provider-advanced')?.open === true;
1393
+ const detail = instance.root.querySelector('[data-provider-detail]');
1394
+ const scrollTop = detail?.scrollTop || 0;
1395
+ const selectedTest = getSelectedTestModelId(instance);
1396
+ instance.draft = { ...getSelectedProvider(instance), ...next };
1397
+ renderDetail(instance);
1398
+ const input = instance.root.querySelector('[data-provider-field="apiKey"]');
1399
+ if (input) input.value = secret;
1400
+ const checkbox = instance.root.querySelector('[data-provider-disclosure]');
1401
+ if (checkbox) checkbox.checked = disclosure;
1402
+ const advancedPanel = instance.root.querySelector('.codex-provider-advanced');
1403
+ if (advancedPanel) advancedPanel.open = advanced;
1404
+ const testSelect = instance.root.querySelector('[data-provider-test-model]');
1405
+ if (testSelect && next.models.some(model => model.id === selectedTest)) testSelect.value = selectedTest;
1406
+ if (detail) detail.scrollTop = scrollTop;
1407
+ markDirty(instance);
1408
+ renderFooter(instance);
1409
+ applyBusyState(instance);
1410
+ }
1411
+
1412
+ async function handleModelAction(instance, action, id) {
1413
+ if (action === 'cancel-editor') { closeModelEditor(instance); return; }
1414
+ if (action === 'apply-editor') { commitModelEditor(instance); return; }
1415
+ if (action === 'add' || action === 'edit') { openModelEditor(instance, action === 'edit' ? id : ''); return; }
1416
+ if (action === 'test') {
1417
+ if (!validateConnectionForm(instance)) return;
1418
+ const select = instance.root.querySelector('[data-provider-test-model]');
1419
+ if (select) select.value = id;
1420
+ invalidateVerification(instance, false, id);
1421
+ instance.callbacks.onTest?.(readContext(instance));
1422
+ return;
1423
+ }
1424
+ const context = readContext(instance), draft = context.draft;
1425
+ if (!draft.models.some(model => model.id === id)) return;
1426
+ if (action === 'default') {
1427
+ if (draft.defaultModelId !== id) replaceModelDraft(instance, { ...draft, defaultModelId: id });
1428
+ } else if (action === 'remove' && await confirmProviderAction(instance, instance.tx(
1429
+ 'Remove ' + id + ' from this provider? This takes effect after Save.',
1430
+ '从此服务移除 ' + id + '?保存后生效。'), 'model')) {
1431
+ replaceModelDraft(instance, instance.Profiles.removeDraftModel(draft, id));
1432
+ }
1433
+ }
1434
+
1435
+ function openModelEditor(instance, id, options = {}) {
1436
+ const provider = readContext(instance).draft;
1437
+ const existing = provider.models.find(model => model.id === id);
1438
+ if (id && !existing) return;
1439
+ const model = existing || { id: '', label: '', contextWindow: provider.contextWindow,
1440
+ maxOutputTokens: null, inputModalities: provider.inputModalities,
1441
+ supportsParallelToolCalls: provider.supportsParallelToolCalls,
1442
+ upstreamResponseMode: 'auto', reasoningEfforts: [], reasoningAdapter: '', reasoningCapability: '', baseInstructions: '' };
1443
+ const tx = instance.tx, modal = instance.root.querySelector('[data-provider-model-editor]');
1444
+ instance.modelEditor = { id, providerId: instance.selectedId, returnFocus: instance.document.activeElement };
1445
+ modal.classList.add('is-minimal');
1446
+ const field = (name, label, value, attributes = '', help = '') => '<label class="codex-provider-field"><span>'
1447
+ + escapeHtml(label) + '</span><input data-model-field="' + name + '" value="' + escapeAttr(value ?? '')
1448
+ + '" ' + attributes + '>' + (help ? '<small>' + escapeHtml(help) + '</small>' : '') + '</label>';
1449
+ const select = (name, label, value, options) => '<label class="codex-provider-field"><span>'
1450
+ + escapeHtml(label) + '</span><select data-model-field="' + name + '">'
1451
+ + options.map(([key, title]) => '<option value="' + key + '"' + (value === key ? ' selected' : '') + '>'
1452
+ + escapeHtml(title) + '</option>').join('') + '</select></label>';
1453
+ modal.innerHTML = '<header class="codex-provider-model-editor-head"><h3 id="codex-provider-model-title">'
1454
+ + escapeHtml(id ? tx('Edit model', '编辑模型') : tx('Add model', '添加模型'))
1455
+ + '</h3><button type="button" class="codex-provider-model-close" data-provider-model-action="cancel-editor" aria-label="'
1456
+ + 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>'
1457
+ + '<div class="codex-provider-model-editor-body"><div class="codex-provider-model-basics">'
1458
+ + field('id', tx('Model ID', '模型 ID'), model.id, 'type="text" maxlength="200" spellcheck="false" autofocus placeholder="'
1459
+ + escapeAttr(tx('Exact model ID from the provider', '填写服务商提供的模型 ID')) + '"')
1460
+ + '<div class="codex-provider-form-grid">'
1461
+ + field('contextWindow', tx('Context window', '上下文窗口'), model.contextWindow, 'type="number" min="8192" max="4000000" step="1"')
1462
+ + field('maxOutputTokens', tx('Max output tokens', '最大输出 Token'), model.maxOutputTokens,
1463
+ 'type="number" min="256" max="65536" placeholder="' + Number(provider.maxOutputTokens || 65536) + '"',
1464
+ tx('Optional; leave empty to inherit.', '可留空,沿用服务设置。'))
1465
+ + '</div><section class="codex-provider-model-types"><div class="codex-provider-model-types-head"><span>'
1466
+ + escapeHtml(tx('Input type', '输入类型')) + '</span><small>'
1467
+ + escapeHtml(tx('Output: text', '输出:文本')) + '</small></div><div class="codex-provider-type-options">'
1468
+ + '<span class="codex-provider-type-chip is-fixed" aria-label="' + escapeAttr(tx('Text input is required', '文本输入为必选项'))
1469
+ + '"><svg viewBox="0 0 16 16" aria-hidden="true"><path d="m3 8 3 3 7-7"></path></svg>'
1470
+ + escapeHtml(tx('Text', '文本')) + '<small>' + escapeHtml(tx('Required', '必选')) + '</small></span>'
1471
+ + '<label class="codex-provider-type-chip"><input type="checkbox" data-model-field="supportsImages"'
1472
+ + ((model.inputModalities || []).includes('image') ? ' checked' : '') + '><span>' + escapeHtml(tx('Images', '图片'))
1473
+ + '</span></label></div></section></div>'
1474
+ + '<details class="codex-provider-model-settings" data-model-reasoning><summary><span>'
1475
+ + escapeHtml(tx('Reasoning', '推理设置')) + '</span><small data-model-reasoning-summary></small></summary>'
1476
+ + '<div class="codex-provider-model-settings-body"><p class="codex-provider-help">'
1477
+ + escapeHtml(tx('Leave levels unchecked to use provider defaults. Only select levels supported by this endpoint.',
1478
+ '档位全部留空时沿用服务默认配置;仅选择该端点支持的档位。')) + '</p><div class="codex-provider-effort-chips">'
1479
+ + instance.Profiles.MODEL_EFFORTS.map(effort => '<label><input type="checkbox" data-model-effort value="' + effort + '"'
1480
+ + ((model.reasoningEfforts || []).includes(effort) ? ' checked' : '') + '><span>' + effort + '</span></label>').join('')
1481
+ + '</div><div class="codex-provider-form-grid">'
1482
+ + select('reasoningAdapter', tx('Parameter format', '参数格式'), model.reasoningAdapter || '',
1483
+ [['', tx('Inherit provider', '沿用服务配置')], ['auto', tx('Auto-detect', '自动识别')], ['none', tx('Disabled', '禁用')],
1484
+ ['deepseek', 'DeepSeek'], ['anthropic', 'Anthropic'], ['reasoning_effort', 'reasoning_effort'],
1485
+ ['openrouter', 'OpenRouter'], ['enable_thinking', 'enable_thinking'], ['thinking', 'thinking.type'], ['reasoning_split', 'reasoning_split']])
1486
+ + select('reasoningCapability', tx('Reasoning control', '推理控制'), model.reasoningCapability || '',
1487
+ [['', tx('Inherit / selected levels', '继承或使用所选档位')], ['none', tx('Not supported', '不支持')],
1488
+ ['toggle', tx('On / Off', '开启/关闭')], ['effort', tx('Effort levels', '档位选择')]])
1489
+ + '</div></div></details><details class="codex-provider-model-settings"><summary><span>'
1490
+ + escapeHtml(tx('Advanced', '高级设置')) + '</span><small>'
1491
+ + escapeHtml(tx('Name and compatibility', '名称与兼容性')) + '</small></summary>'
1492
+ + '<div class="codex-provider-model-settings-body"><div class="codex-provider-form-grid">'
1493
+ + field('label', tx('Display name (optional)', '显示名称(可选)'), model.label, 'type="text" maxlength="200"')
1494
+ + select('upstreamResponseMode', tx('Response mode', '响应模式'), model.upstreamResponseMode || 'auto',
1495
+ [['auto', tx('Auto', '自动')], ['streaming', tx('Streaming', '流式')], ['buffered', tx('Buffered', '缓冲')]])
1496
+ + '</div><label class="codex-provider-model-checkbox"><input type="checkbox" data-model-field="supportsParallelToolCalls"'
1497
+ + (model.supportsParallelToolCalls ? ' checked' : '') + '> ' + escapeHtml(tx('Parallel tool calls', '并行工具调用')) + '</label>'
1498
+ + '<label class="codex-provider-field"><span>' + escapeHtml(tx('Base instructions (optional)', '基础指令(可选)'))
1499
+ + '</span><textarea data-model-field="baseInstructions" rows="3" maxlength="8000">' + escapeHtml(model.baseInstructions || '')
1500
+ + '</textarea></label></div></details><p role="alert" class="codex-provider-model-error" data-model-error></p></div>'
1501
+ + '<footer class="codex-provider-model-editor-foot"><small>' + escapeHtml(tx('Saved with the provider.', '随服务配置一起保存。'))
1502
+ + '</small><button type="button" class="codex-provider-secondary-button" data-provider-model-action="cancel-editor">'
1503
+ + escapeHtml(tx('Cancel', '取消')) + '</button><button type="button" class="codex-provider-primary-button" data-provider-model-action="apply-editor">'
1504
+ + escapeHtml(id ? tx('Apply', '应用') : tx('Add model', '添加模型')) + '</button></footer>';
1505
+ updateModelReasoningSummary(instance);
1506
+ if (options.show !== false) { if (modal.showModal) modal.showModal(); else modal.setAttribute('open', ''); }
1507
+ }
1508
+
1509
+ function updateModelReasoningSummary(instance) {
1510
+ const modal = instance.root.querySelector('[data-provider-model-editor]');
1511
+ const summary = modal?.querySelector('[data-model-reasoning-summary]');
1512
+ if (!summary) return;
1513
+ const capability = modal.querySelector('[data-model-field="reasoningCapability"]')?.value;
1514
+ const adapter = modal.querySelector('[data-model-field="reasoningAdapter"]')?.value;
1515
+ const levels = Array.from(modal.querySelectorAll('[data-model-effort]:checked')).map(input => input.value);
1516
+ summary.textContent = capability === 'none' || adapter === 'none' ? instance.tx('Disabled', '已关闭')
1517
+ : levels.length ? levels.join(' · ')
1518
+ : capability === 'toggle' ? instance.tx('On / Off', '开关控制')
1519
+ : adapter && adapter !== 'auto' ? adapter
1520
+ : capability === 'effort' ? instance.tx('Provider levels', '服务默认档位')
1521
+ : instance.tx('Provider defaults', '沿用服务配置');
1522
+ summary.title = summary.textContent;
1523
+ }
1524
+
1525
+ function closeModelEditor(instance, restoreFocus = true) {
1526
+ if (!instance.modelEditor) return;
1527
+ const focus = instance.modelEditor.returnFocus;
1528
+ const modal = instance.root?.querySelector('[data-provider-model-editor]');
1529
+ instance.modelEditor = null;
1530
+ modal?.close?.();
1531
+ modal?.removeAttribute('open');
1532
+ if (modal) modal.replaceChildren();
1533
+ if (restoreFocus) focus?.focus?.();
1534
+ }
1535
+
1536
+ function commitModelEditor(instance) {
1537
+ const editor = instance.modelEditor;
1538
+ if (!editor || editor.providerId !== instance.selectedId) return;
1539
+ const modal = instance.root.querySelector('[data-provider-model-editor]');
1540
+ const get = key => modal.querySelector('[data-model-field="' + key + '"]');
1541
+ const patch = Object.fromEntries(['id', 'label', 'contextWindow', 'maxOutputTokens',
1542
+ 'upstreamResponseMode', 'reasoningAdapter', 'reasoningCapability', 'baseInstructions']
1543
+ .map(key => [key, get(key)?.value || '']));
1544
+ patch.inputModalities = get('supportsImages')?.checked ? ['text', 'image'] : ['text'];
1545
+ patch.supportsParallelToolCalls = get('supportsParallelToolCalls')?.checked === true;
1546
+ patch.reasoningEfforts = Array.from(modal.querySelectorAll('[data-model-effort]:checked')).map(input => input.value);
1547
+ try {
1548
+ const draft = readContext(instance).draft;
1549
+ const next = instance.Profiles.updateDraftModel(draft, editor.id, patch);
1550
+ const unchanged = JSON.stringify(next.models) === JSON.stringify(draft.models)
1551
+ && next.defaultModelId === draft.defaultModelId;
1552
+ closeModelEditor(instance, false);
1553
+ if (!unchanged) replaceModelDraft(instance, next);
1554
+ } catch (error) {
1555
+ editor.error = { field: error.field, message: error.message };
1556
+ modal.querySelector('[data-model-error]').textContent = formatModelEditorError(instance, editor.error);
1557
+ if (error.field) get(error.field)?.focus();
1558
+ }
1559
+ }
1560
+
1561
+ function formatModelEditorError(instance, error) {
1562
+ const chinese = {
1563
+ id: '请检查模型 ID:必填、最多 200 字符、不可重复,且单个服务最多 32 个模型。',
1564
+ contextWindow: '上下文窗口必须为 8192 至 4000000 之间的整数。',
1565
+ maxOutputTokens: '输出上限须为 256 至 65536 之间的整数,并小于上下文窗口。'
1566
+ }[error.field];
1567
+ return chinese ? instance.tx(error.message, chinese) : error.message;
1568
+ }
1569
+
1570
+ function validateConnectionForm(instance) {
1571
+ const context = readContext(instance), tx = instance.tx;
1572
+ let message = '';
1573
+ if (!context.draft.name) message = tx('Enter a provider name.', '请填写服务名称。');
1574
+ else if (!context.draft.baseUrl) message = tx('Enter the API Base URL.', '请填写 API 地址。');
1575
+ else if (!context.draft.models.length || !context.draft.models.some(model => model.id === context.draft.defaultModelId))
1576
+ message = tx('Add at least one model and select a default.', '请添加至少一个模型,并选择默认模型。');
1577
+ if (message) { setStatus(instance, { tone: 'failed', title: message }); return false; }
1578
+ return true;
1579
+ }
1580
+
1581
+
974
1582
  function formatSecretSavedAt(instance, value) {
975
1583
  const timestamp = Number(value);
976
1584
  if (!Number.isFinite(timestamp) || timestamp <= 0) {
@@ -1008,6 +1616,7 @@
1008
1616
  }
1009
1617
 
1010
1618
  function formatJsonRecord(value) {
1619
+ if (typeof value === 'string') return value;
1011
1620
  if (!value || typeof value !== 'object' || Array.isArray(value) || !Object.keys(value).length) {
1012
1621
  return '';
1013
1622
  }
@@ -1019,6 +1628,8 @@
1019
1628
  }
1020
1629
 
1021
1630
  function destroy(instance) {
1631
+ finishProviderConfirmation(instance, false, false);
1632
+ closeModelEditor(instance, false);
1022
1633
  instance.root?.close?.();
1023
1634
  instance.root?.remove?.();
1024
1635
  instance.root = null;