codex-overleaf-link 2.1.0 → 2.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -23
- package/extension/bootstrap/manifest.template.json +1 -1
- package/extension/src/content/contentRuntime.js +13 -13
- package/extension/src/content/modelPicker.js +30 -8
- package/extension/src/content/providerSettingsCoordinator.js +78 -20
- package/extension/src/content/providerSettingsDialog.js +30 -5
- package/extension/src/content/sessionManager.js +6 -2
- package/extension/src/shared/auditRecords.js +8 -0
- package/extension/src/shared/compatibility.js +1 -1
- package/extension/src/shared/providerProfiles.js +17 -4
- package/extension/src/shared/sessionState.js +20 -1
- package/extension/src/shared/storageDb.js +5 -5
- package/extension/src/shared/storageMigration.js +1 -0
- package/extension/src/shared/storageRunActions.js +11 -1
- package/extension/styles/panel.css +105 -3
- package/native-host/src/providerRuntime.js +27 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<h1>Codex Overleaf Link</h1>
|
|
4
4
|
<p><strong>Empower Overleaf with Codex.</strong></p>
|
|
5
5
|
<p>
|
|
6
|
-
<img src="https://img.shields.io/badge/version-2.1.
|
|
6
|
+
<img src="https://img.shields.io/badge/version-2.1.2-blue" alt="version">
|
|
7
7
|
<img src="https://img.shields.io/badge/platform-macOS%20%2F%20Windows%20%2F%20Linux-lightgrey" alt="platform">
|
|
8
8
|
<img src="https://img.shields.io/badge/chrome-MV3-green" alt="chrome manifest v3">
|
|
9
9
|
<img src="https://img.shields.io/badge/node-%3E%3D20-brightgreen" alt="node version">
|
|
@@ -38,14 +38,14 @@ One command installs the managed native host **and** managed extension runtime.
|
|
|
38
38
|
macOS / Linux:
|
|
39
39
|
|
|
40
40
|
```bash
|
|
41
|
-
CODEX_OVERLEAF_REF=v2.1.
|
|
41
|
+
CODEX_OVERLEAF_REF=v2.1.2 bash -c "$(curl -fsSL https://raw.githubusercontent.com/Ghqqqq/codex-overleaf-link/v2.1.2/install.sh)"
|
|
42
42
|
```
|
|
43
43
|
|
|
44
44
|
Windows PowerShell:
|
|
45
45
|
|
|
46
46
|
```powershell
|
|
47
|
-
iwr https://raw.githubusercontent.com/Ghqqqq/codex-overleaf-link/v2.1.
|
|
48
|
-
$env:CODEX_OVERLEAF_REF='v2.1.
|
|
47
|
+
iwr https://raw.githubusercontent.com/Ghqqqq/codex-overleaf-link/v2.1.2/install.ps1 -OutFile install.ps1
|
|
48
|
+
$env:CODEX_OVERLEAF_REF='v2.1.2'
|
|
49
49
|
powershell -ExecutionPolicy Bypass -File install.ps1
|
|
50
50
|
```
|
|
51
51
|
|
|
@@ -56,7 +56,7 @@ Then, in the `chrome://extensions` tab the script opened: enable **Developer mod
|
|
|
56
56
|
`npm exec` installs the same managed native host and extension runtime without keeping a source checkout. Use it if you prefer a pinned npm package.
|
|
57
57
|
|
|
58
58
|
```bash
|
|
59
|
-
npm exec --yes codex-overleaf-link@2.1.
|
|
59
|
+
npm exec --yes codex-overleaf-link@2.1.2 -- install-managed
|
|
60
60
|
```
|
|
61
61
|
|
|
62
62
|
Then, in `chrome://extensions`, enable **Developer mode**, click **Load unpacked**, and select the managed extension path printed by the command. The Release extension zip remains available for explicitly unmanaged/manual installations.
|
|
@@ -86,9 +86,9 @@ npm installs, updates, and uninstalls the coordinated managed extension/native p
|
|
|
86
86
|
|
|
87
87
|
| Action | Command |
|
|
88
88
|
|--------|---------|
|
|
89
|
-
| Install / update | `npm exec --yes codex-overleaf-link@2.1.
|
|
90
|
-
| Diagnose | `npm exec --yes codex-overleaf-link@2.1.
|
|
91
|
-
| Uninstall | `npm exec --yes codex-overleaf-link@2.1.
|
|
89
|
+
| Install / update | `npm exec --yes codex-overleaf-link@2.1.2 -- install-managed` |
|
|
90
|
+
| Diagnose | `npm exec --yes codex-overleaf-link@2.1.2 -- doctor` |
|
|
91
|
+
| Uninstall | `npm exec --yes codex-overleaf-link@2.1.2 -- uninstall-managed` |
|
|
92
92
|
|
|
93
93
|
Use `--extension-id <chrome-extension-id>` only for a custom/dev unpacked extension id that differs from the official bundled id.
|
|
94
94
|
|
|
@@ -98,13 +98,13 @@ Managed installations discover signed stable updates automatically and apply the
|
|
|
98
98
|
|
|
99
99
|
## GitHub Release Artifacts
|
|
100
100
|
|
|
101
|
-
The v2.1.
|
|
101
|
+
The v2.1.2 GitHub Release contains:
|
|
102
102
|
|
|
103
|
-
- `codex-overleaf-link-extension-v2.1.
|
|
104
|
-
- `codex-overleaf-native-host-v2.1.
|
|
105
|
-
- `codex-overleaf-link-2.1.
|
|
106
|
-
- `install.sh`: release-pinned macOS / Linux installer that defaults to `v2.1.
|
|
107
|
-
- `install.ps1`: release-pinned Windows PowerShell installer that defaults to `v2.1.
|
|
103
|
+
- `codex-overleaf-link-extension-v2.1.2.zip`: loadable Chrome extension package for manual unpacked installation.
|
|
104
|
+
- `codex-overleaf-native-host-v2.1.2.tar.gz`: native host runtime files used by the installer and release verification.
|
|
105
|
+
- `codex-overleaf-link-2.1.2.tgz`: npm native host CLI package for pinned install, doctor, and uninstall flows.
|
|
106
|
+
- `install.sh`: release-pinned macOS / Linux installer that defaults to `v2.1.2` when run directly from the release artifact.
|
|
107
|
+
- `install.ps1`: release-pinned Windows PowerShell installer that defaults to `v2.1.2` when run directly from the release artifact.
|
|
108
108
|
- `uninstall-native-host.mjs`: native host uninstaller that removes the Chrome Native Messaging manifest, bridge executable, and runtime copy.
|
|
109
109
|
- `nativeHostPlatform.js`, `manifest.js`, `runtimeInstaller.js`: helper files required by the loose uninstaller asset.
|
|
110
110
|
- `SHA256SUMS` and `release-manifest.json`: checksum and artifact metadata for release verification.
|
|
@@ -115,7 +115,7 @@ The v2.1.0 GitHub Release contains:
|
|
|
115
115
|
Remove the native host (use `--browser chromium` on Linux Chromium):
|
|
116
116
|
|
|
117
117
|
```bash
|
|
118
|
-
npm exec --yes codex-overleaf-link@2.1.
|
|
118
|
+
npm exec --yes codex-overleaf-link@2.1.2 -- uninstall-managed
|
|
119
119
|
```
|
|
120
120
|
|
|
121
121
|
The same command works on Windows PowerShell. If you installed from a manual checkout or source installer, you can also run `npm run uninstall:native` inside the repo, use `node ~/.codex-overleaf/source/scripts/uninstall-native-host.mjs` on macOS / Linux, or use `node $env:LOCALAPPDATA\CodexOverleaf\source\scripts\uninstall-native-host.mjs` on Windows PowerShell.
|
|
@@ -150,13 +150,13 @@ Then remove the extension from `chrome://extensions`. To delete local data: on m
|
|
|
150
150
|
Linux Chromium install or update:
|
|
151
151
|
|
|
152
152
|
```bash
|
|
153
|
-
CODEX_OVERLEAF_REF=v2.1.
|
|
153
|
+
CODEX_OVERLEAF_REF=v2.1.2 bash -c "$(curl -fsSL https://raw.githubusercontent.com/Ghqqqq/codex-overleaf-link/v2.1.2/install.sh)" -- --browser chromium
|
|
154
154
|
```
|
|
155
155
|
|
|
156
156
|
Linux Chromium uninstall:
|
|
157
157
|
|
|
158
158
|
```bash
|
|
159
|
-
npm exec --yes codex-overleaf-link@2.1.
|
|
159
|
+
npm exec --yes codex-overleaf-link@2.1.2 -- uninstall-managed --browser chromium
|
|
160
160
|
```
|
|
161
161
|
|
|
162
162
|
## Features
|
|
@@ -292,7 +292,7 @@ Composer attachments are turn-scoped Codex context. Limits are 8 attachments per
|
|
|
292
292
|
Re-run any [native host installer](#install), reload the extension in `chrome://extensions`, then refresh the Overleaf tab. This also fixes extension/native version mismatch and native protocol mismatch.
|
|
293
293
|
|
|
294
294
|
```bash
|
|
295
|
-
npm exec --yes codex-overleaf-link@2.1.
|
|
295
|
+
npm exec --yes codex-overleaf-link@2.1.2 -- install-managed
|
|
296
296
|
```
|
|
297
297
|
|
|
298
298
|
**The Windows popup or panel shows a Bash recovery command**
|
|
@@ -341,8 +341,8 @@ Use this matrix for release-candidate signoff and compatibility reports. Record
|
|
|
341
341
|
| Browser/channel/version | Google Chrome channel and version. | Google Chrome channel and version. | Google Chrome channel and version. | Chromium channel/package and version. |
|
|
342
342
|
| Install mode | Manual unpacked extension from GitHub Release zip or checkout. | Manual unpacked extension from GitHub Release zip or checkout. | Manual unpacked extension from GitHub Release zip or checkout. | Manual unpacked extension from GitHub Release zip or checkout; native host installed with `--browser chromium`. |
|
|
343
343
|
| Extension id | Bundled id `illdpneeeopfffmiepaejglgmhpmdhdc`, or actual custom id passed with `--extension-id`. | Bundled id `illdpneeeopfffmiepaejglgmhpmdhdc`, or actual custom id passed with `--extension-id`. | Bundled id `illdpneeeopfffmiepaejglgmhpmdhdc`, or actual custom id passed with `--extension-id`. | Bundled id `illdpneeeopfffmiepaejglgmhpmdhdc`, or actual custom id passed with `--extension-id`. |
|
|
344
|
-
| Installer/update command | `npm exec --yes codex-overleaf-link@2.1.
|
|
345
|
-
| Uninstall command | `npm exec --yes codex-overleaf-link@2.1.
|
|
344
|
+
| Installer/update command | `npm exec --yes codex-overleaf-link@2.1.2 -- install-managed` | `npm exec --yes codex-overleaf-link@2.1.2 -- install-managed` | `npm exec --yes codex-overleaf-link@2.1.2 -- install-managed` | `npm exec --yes codex-overleaf-link@2.1.2 -- install-managed --browser chromium` |
|
|
345
|
+
| Uninstall command | `npm exec --yes codex-overleaf-link@2.1.2 -- uninstall-managed` | `npm exec --yes codex-overleaf-link@2.1.2 -- uninstall-managed` | `npm exec --yes codex-overleaf-link@2.1.2 -- uninstall-managed` | `npm exec --yes codex-overleaf-link@2.1.2 -- uninstall-managed --browser chromium` |
|
|
346
346
|
| Manifest/registry path | `~/Library/Application Support/Google/Chrome/NativeMessagingHosts/com.codex.overleaf.json` | `HKCU\Software\Google\Chrome\NativeMessagingHosts\com.codex.overleaf` -> `%LOCALAPPDATA%\CodexOverleaf\native-host-runtime\com.codex.overleaf.json` | `~/.config/google-chrome/NativeMessagingHosts/com.codex.overleaf.json` | `~/.config/chromium/NativeMessagingHosts/com.codex.overleaf.json` |
|
|
347
347
|
| Bridge/runtime/source path | Bridge `~/.codex-overleaf/codex-overleaf-bridge`; runtime `~/.codex-overleaf/native-host-runtime`; source `~/.codex-overleaf/source`. | Bridge `%LOCALAPPDATA%\CodexOverleaf\codex-overleaf-bridge.cmd`; runtime `%LOCALAPPDATA%\CodexOverleaf\native-host-runtime`; source `%LOCALAPPDATA%\CodexOverleaf\source`. | Bridge `~/.codex-overleaf/codex-overleaf-bridge`; runtime `~/.codex-overleaf/native-host-runtime`; source `~/.codex-overleaf/source`. | Bridge `~/.codex-overleaf/codex-overleaf-bridge`; runtime `~/.codex-overleaf/native-host-runtime`; source `~/.codex-overleaf/source`. |
|
|
348
348
|
| Node/Git/Codex/TeX | Node.js >= 20; Git; Codex CLI installed and logged in; TeX optional. | Node.js >= 20; Git; Codex CLI installed and logged in; TeX optional. | Node.js >= 20; Git; Codex CLI installed and logged in; TeX optional. | Node.js >= 20; Git; Codex CLI installed and logged in; TeX optional. |
|
|
@@ -378,7 +378,7 @@ Contributions are welcome. Please open an issue before submitting large changes
|
|
|
378
378
|
The recommended v1.9 installation owns one stable unpacked-extension directory and a versioned native-host directory:
|
|
379
379
|
|
|
380
380
|
```bash
|
|
381
|
-
npm exec --yes codex-overleaf-link@2.1.
|
|
381
|
+
npm exec --yes codex-overleaf-link@2.1.2 -- install-managed
|
|
382
382
|
```
|
|
383
383
|
|
|
384
384
|
Load the printed extension path once from `chrome://extensions`. Starting with the next signed stable release, Codex Overleaf Link checks GitHub Releases automatically, downloads and verifies the coordinated extension/native-host bundle, waits until every connected Overleaf tab is saved and idle, and then updates both components together. A failed health check restores the previous version automatically.
|
|
@@ -388,11 +388,11 @@ The stable updater ignores drafts and prereleases, performs no telemetry, and ca
|
|
|
388
388
|
The legacy native-only installer remains available for source or unmanaged extension directories:
|
|
389
389
|
|
|
390
390
|
```bash
|
|
391
|
-
npm exec --yes codex-overleaf-link@2.1.
|
|
391
|
+
npm exec --yes codex-overleaf-link@2.1.2 -- install-native
|
|
392
392
|
```
|
|
393
393
|
|
|
394
394
|
Legacy native-only installations can be removed with:
|
|
395
395
|
|
|
396
396
|
```bash
|
|
397
|
-
npm exec --yes codex-overleaf-link@2.1.
|
|
397
|
+
npm exec --yes codex-overleaf-link@2.1.2 -- uninstall-native
|
|
398
398
|
```
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
(function initCodexOverleafContentRuntime(root) {
|
|
2
2
|
'use strict';
|
|
3
|
-
|
|
4
3
|
function init() {
|
|
5
4
|
'use strict';
|
|
6
5
|
|
|
@@ -438,7 +437,7 @@
|
|
|
438
437
|
saveState,
|
|
439
438
|
saveStateSoon,
|
|
440
439
|
readPanelInputs,
|
|
441
|
-
applyStateToPanel,
|
|
440
|
+
applyStateToPanel, syncSessionProvider: () => providerSettingsCoordinator.syncSessionProvider(),
|
|
442
441
|
getPanel: () => panel,
|
|
443
442
|
getState: () => state,
|
|
444
443
|
setState: next => { state = next; },
|
|
@@ -569,7 +568,8 @@
|
|
|
569
568
|
tr,
|
|
570
569
|
tx,
|
|
571
570
|
getLocale,
|
|
572
|
-
sendBackgroundNative,
|
|
571
|
+
sendBackgroundNative, getProviderSelection: () => providerSettingsCoordinator.getRunSelection(state?.providerId)
|
|
572
|
+
|| { providerId: state?.providerId || 'builtin', providerRevision: 0 },
|
|
573
573
|
readSelectedSpeedInput,
|
|
574
574
|
getRenderedModelEntries,
|
|
575
575
|
persistPanelInputs,
|
|
@@ -603,8 +603,8 @@
|
|
|
603
603
|
document,
|
|
604
604
|
window,
|
|
605
605
|
getSettingsPanelInstance: () => settingsPanelInstance,
|
|
606
|
-
getSelectedModel: () => state?.model || '',
|
|
607
|
-
|
|
606
|
+
getSelectedModel: () => state?.model || '', getSelectedProviderId: () => state?.providerId || 'builtin',
|
|
607
|
+
setSelectedProviderId: providerId => { state = updateActiveSession(state, { providerId: providerId || 'builtin' }); },
|
|
608
608
|
refreshModelOptions: loadModelOptions, persistInputs: persistPanelInputs
|
|
609
609
|
});
|
|
610
610
|
// v1.8.1 D3: the dashboard was a one-shot render — another tab finishing a
|
|
@@ -898,9 +898,7 @@
|
|
|
898
898
|
}
|
|
899
899
|
})
|
|
900
900
|
.catch(() => { /* fail-closed inside refreshAccountScopeId */ });
|
|
901
|
-
|
|
902
|
-
applyFallbackModelOptions(resolveSelectedModel(), error);
|
|
903
|
-
});
|
|
901
|
+
providerSettingsCoordinator.syncSessionProvider().catch(() => {});
|
|
904
902
|
syncOtWarmMirrorController().catch(error => {
|
|
905
903
|
updateOtStatusDisplay('unavailable');
|
|
906
904
|
appendPlainLog(tx(`Failed to sync experimental OT warm mirror: ${error.message}`, `同步实验性 OT 预热镜像失败:${error.message}`));
|
|
@@ -2935,7 +2933,7 @@
|
|
|
2935
2933
|
});
|
|
2936
2934
|
return {
|
|
2937
2935
|
...params,
|
|
2938
|
-
providerSelection: providerSettingsCoordinator.getRunSelection()
|
|
2936
|
+
providerSelection: providerSettingsCoordinator.getRunSelection(state?.providerId)
|
|
2939
2937
|
};
|
|
2940
2938
|
}
|
|
2941
2939
|
|
|
@@ -2949,6 +2947,7 @@
|
|
|
2949
2947
|
sessionId: input.sessionId || '',
|
|
2950
2948
|
turnId: currentRunView?.recordId || '',
|
|
2951
2949
|
mode: input.mode || state?.mode || '',
|
|
2950
|
+
...providerSettingsCoordinator.getProviderSnapshot(state?.providerId),
|
|
2952
2951
|
model: state?.model || '',
|
|
2953
2952
|
reasoningEffort: state?.reasoningEffort || '',
|
|
2954
2953
|
speedTier: state?.speedTier || '',
|
|
@@ -5967,7 +5966,7 @@
|
|
|
5967
5966
|
const { prefs, sessions, activeSessionId } = await Migration.runMigrationIfNeeded(projectId, legacyKey);
|
|
5968
5967
|
|
|
5969
5968
|
return {
|
|
5970
|
-
model: prefs.model || '',
|
|
5969
|
+
model: prefs.model || '', providerId: prefs.providerId || 'builtin',
|
|
5971
5970
|
reasoningEffort: prefs.reasoningEffort || '',
|
|
5972
5971
|
speedTier: prefs.speedTier || '',
|
|
5973
5972
|
mode: prefs.mode || '',
|
|
@@ -5992,7 +5991,7 @@
|
|
|
5992
5991
|
runs: Array.isArray(session.runs) ? session.runs : [],
|
|
5993
5992
|
history: Array.isArray(session.history) ? session.history : [],
|
|
5994
5993
|
task: typeof session.task === 'string' ? session.task : '',
|
|
5995
|
-
mode: session.mode || prefs.mode || 'confirm',
|
|
5994
|
+
mode: session.mode || prefs.mode || 'confirm', providerId: session.providerId || 'builtin',
|
|
5996
5995
|
model: session.model || prefs.model || 'gpt-5.4',
|
|
5997
5996
|
reasoningEffort: session.reasoningEffort || prefs.reasoningEffort || 'high',
|
|
5998
5997
|
speedTier: session.speedTier || prefs.speedTier || 'standard',
|
|
@@ -6012,7 +6011,7 @@
|
|
|
6012
6011
|
const fallback = stored[storageKey] || stored[LEGACY_STORAGE_KEY] || {};
|
|
6013
6012
|
if (fallback?.__codexOverleafCompactFallback === true) {
|
|
6014
6013
|
return {
|
|
6015
|
-
mode: fallback.mode || '',
|
|
6014
|
+
mode: fallback.mode || '', providerId: fallback.providerId || 'builtin',
|
|
6016
6015
|
model: fallback.model || '',
|
|
6017
6016
|
reasoningEffort: fallback.reasoningEffort || '',
|
|
6018
6017
|
speedTier: fallback.speedTier || '',
|
|
@@ -6453,7 +6452,7 @@
|
|
|
6453
6452
|
|
|
6454
6453
|
function applyStateToPanel() {
|
|
6455
6454
|
applyPanelTheme(getThemePreference());
|
|
6456
|
-
if (!modelSelectHasOption(state.model)) {
|
|
6455
|
+
if ((state.providerId || 'builtin') === 'builtin' && !modelSelectHasOption(state.model)) {
|
|
6457
6456
|
renderModelOptions(getModelCatalog().FALLBACK_MODELS, state.model);
|
|
6458
6457
|
}
|
|
6459
6458
|
panel.querySelector('[data-model]').value = state.model;
|
|
@@ -6545,6 +6544,7 @@
|
|
|
6545
6544
|
id: createRunId(),
|
|
6546
6545
|
task,
|
|
6547
6546
|
mode,
|
|
6547
|
+
...providerSettingsCoordinator.getProviderSnapshot(state?.providerId),
|
|
6548
6548
|
model,
|
|
6549
6549
|
reasoningEffort,
|
|
6550
6550
|
speedTier,
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
tx,
|
|
14
14
|
getLocale,
|
|
15
15
|
sendBackgroundNative,
|
|
16
|
+
getProviderSelection,
|
|
16
17
|
readSelectedSpeedInput,
|
|
17
18
|
getRenderedModelEntries,
|
|
18
19
|
persistPanelInputs,
|
|
@@ -33,6 +34,7 @@
|
|
|
33
34
|
} = Support;
|
|
34
35
|
|
|
35
36
|
let modelDiscovery = { status: 'fallback', source: 'fallback', fetchedAt: '' };
|
|
37
|
+
let modelLoadGeneration = 0;
|
|
36
38
|
|
|
37
39
|
function getModelDiscovery() {
|
|
38
40
|
return modelDiscovery;
|
|
@@ -96,7 +98,9 @@
|
|
|
96
98
|
updateModelDisplay();
|
|
97
99
|
await persistPanelInputs();
|
|
98
100
|
}
|
|
99
|
-
async function loadModelOptions() {
|
|
101
|
+
async function loadModelOptions(providerSelection) {
|
|
102
|
+
const generation = ++modelLoadGeneration;
|
|
103
|
+
const selection = providerSelection || getProviderSelection?.() || null;
|
|
100
104
|
const selectedModel = resolveSelectedModel();
|
|
101
105
|
const modelCatalog = getModelCatalog();
|
|
102
106
|
const fallbackModels = modelCatalog.FALLBACK_MODELS;
|
|
@@ -104,8 +108,19 @@
|
|
|
104
108
|
try {
|
|
105
109
|
const response = await sendBackgroundNative({
|
|
106
110
|
method: 'codex.models',
|
|
107
|
-
params: {}
|
|
111
|
+
params: selection ? { providerSelection: selection } : {}
|
|
108
112
|
});
|
|
113
|
+
if (generation !== modelLoadGeneration) {
|
|
114
|
+
return { stale: true };
|
|
115
|
+
}
|
|
116
|
+
const responseProviderId = response?.result?.providerId || '';
|
|
117
|
+
if (selection?.providerId && responseProviderId && responseProviderId !== selection.providerId) {
|
|
118
|
+
return { stale: true };
|
|
119
|
+
}
|
|
120
|
+
if (selection?.providerId && selection.providerId !== 'builtin'
|
|
121
|
+
&& Number(response?.result?.providerRevision) !== Number(selection.providerRevision)) {
|
|
122
|
+
return { stale: true };
|
|
123
|
+
}
|
|
109
124
|
const currentSelectedModel = resolveSelectedModel() || selectedModel;
|
|
110
125
|
const hasDiscoveredModels = response?.ok
|
|
111
126
|
&& Array.isArray(response.result?.models)
|
|
@@ -121,17 +136,25 @@
|
|
|
121
136
|
applyUnavailableModelOptions({ code: 'provider_model_catalog_invalid', message: 'The active provider returned no usable models.' });
|
|
122
137
|
return;
|
|
123
138
|
}
|
|
124
|
-
renderModelOptions(normalized.models, retainedSelectedModel);
|
|
139
|
+
renderModelOptions(normalized.models, retainedSelectedModel, { allowUnknownSelected: false });
|
|
125
140
|
modelDiscovery = {
|
|
126
141
|
status: hasDiscoveredModels && !normalized.usedFallback ? 'discovered' : 'fallback',
|
|
127
142
|
source: hasDiscoveredModels ? response.result?.source || 'unknown' : 'fallback',
|
|
128
143
|
fetchedAt: hasDiscoveredModels ? response.result?.fetchedAt || '' : '',
|
|
129
144
|
errorCode: hasDiscoveredModels ? '' : response?.error?.code || '',
|
|
130
|
-
errorMessage: hasDiscoveredModels ? '' : response?.error?.message || ''
|
|
145
|
+
errorMessage: hasDiscoveredModels ? '' : response?.error?.message || '',
|
|
146
|
+
providerId: responseProviderId || selection?.providerId || 'builtin',
|
|
147
|
+
providerRevision: Number(response?.result?.providerRevision || selection?.providerRevision || 0)
|
|
131
148
|
};
|
|
132
149
|
updateModelDisplay();
|
|
150
|
+
await persistPanelInputs();
|
|
151
|
+
return { stale: false, providerId: modelDiscovery.providerId };
|
|
133
152
|
} catch (error) {
|
|
153
|
+
if (generation !== modelLoadGeneration) {
|
|
154
|
+
return { stale: true };
|
|
155
|
+
}
|
|
134
156
|
applyUnavailableModelOptions(error);
|
|
157
|
+
return { stale: false, error };
|
|
135
158
|
}
|
|
136
159
|
}
|
|
137
160
|
|
|
@@ -140,14 +163,13 @@
|
|
|
140
163
|
if (modelSelect) {
|
|
141
164
|
modelSelect.textContent = '';
|
|
142
165
|
const option = document.createElement('option');
|
|
143
|
-
option.value =
|
|
166
|
+
option.value = resolveSelectedModel();
|
|
144
167
|
option.textContent = tx('Models unavailable', '模型不可用');
|
|
145
168
|
option.disabled = true;
|
|
146
169
|
modelSelect.append(option);
|
|
147
170
|
modelSelect.value = '';
|
|
148
171
|
modelSelect.disabled = true;
|
|
149
172
|
}
|
|
150
|
-
if (getState()) getState().model = '';
|
|
151
173
|
modelDiscovery = {
|
|
152
174
|
status: 'unavailable',
|
|
153
175
|
source: 'custom-provider',
|
|
@@ -182,7 +204,7 @@
|
|
|
182
204
|
return Support.getModelCatalog({ getPanel });
|
|
183
205
|
}
|
|
184
206
|
|
|
185
|
-
function renderModelOptions(models, selectedModel) {
|
|
207
|
+
function renderModelOptions(models, selectedModel, options = {}) {
|
|
186
208
|
const modelSelect = getPanel()?.querySelector('[data-model]');
|
|
187
209
|
if (!modelSelect) {
|
|
188
210
|
return;
|
|
@@ -219,7 +241,7 @@
|
|
|
219
241
|
}
|
|
220
242
|
}
|
|
221
243
|
|
|
222
|
-
if (selectedId && !renderedSelected) {
|
|
244
|
+
if (selectedId && !renderedSelected && options.allowUnknownSelected !== false) {
|
|
223
245
|
const option = document.createElement('option');
|
|
224
246
|
option.value = selectedId;
|
|
225
247
|
option.textContent = `${selectedId} (custom)`;
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
sendBackgroundNative: options.sendBackgroundNative,
|
|
9
9
|
getSettingsPanelInstance: options.getSettingsPanelInstance || (() => null),
|
|
10
10
|
getSelectedModel: options.getSelectedModel || (() => ''),
|
|
11
|
+
getSelectedProviderId: options.getSelectedProviderId || (() => 'builtin'),
|
|
12
|
+
setSelectedProviderId: options.setSelectedProviderId || (() => {}),
|
|
11
13
|
clearSelectedModel: options.clearSelectedModel || (() => {}),
|
|
12
14
|
refreshModelOptions: options.refreshModelOptions || (() => {}),
|
|
13
15
|
persistInputs: options.persistInputs || (() => {}),
|
|
@@ -38,7 +40,12 @@
|
|
|
38
40
|
open: () => open(instance),
|
|
39
41
|
refreshSummary: () => refresh(instance),
|
|
40
42
|
ensureLoaded: () => ensureLoaded(instance),
|
|
41
|
-
getRunSelection:
|
|
43
|
+
getRunSelection: providerId => instance.loaded
|
|
44
|
+
? Profiles.buildRunSelection(instance.catalog, providerId || instance.getSelectedProviderId())
|
|
45
|
+
: null,
|
|
46
|
+
getProviderSnapshot: providerId => getProviderSnapshot(instance, providerId),
|
|
47
|
+
isProviderAvailable: providerId => Boolean(Profiles.getProviderById(instance.catalog, providerId)),
|
|
48
|
+
syncSessionProvider: () => syncSessionProvider(instance),
|
|
42
49
|
getCatalog: () => instance.catalog,
|
|
43
50
|
destroy: () => destroy(instance),
|
|
44
51
|
_instance: instance
|
|
@@ -86,19 +93,33 @@
|
|
|
86
93
|
return instance.catalog;
|
|
87
94
|
}
|
|
88
95
|
|
|
96
|
+
async function syncSessionProvider(instance) {
|
|
97
|
+
try {
|
|
98
|
+
await ensureLoaded(instance);
|
|
99
|
+
} catch (_error) {
|
|
100
|
+
return instance.refreshModelOptions(
|
|
101
|
+
window.CodexOverleafProviderProfiles.buildRunSelection(instance.catalog, instance.getSelectedProviderId())
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
return reconcileProviderSelection(instance, instance.catalog, { forceSessionRefresh: true });
|
|
105
|
+
}
|
|
106
|
+
|
|
89
107
|
async function reconcileProviderSelection(instance, catalog, change = {}) {
|
|
90
|
-
|
|
91
|
-
|
|
108
|
+
const previousProviderId = instance.getSelectedProviderId() || 'builtin';
|
|
109
|
+
const requestedProviderId = typeof change.sessionProviderId === 'string' && change.sessionProviderId
|
|
110
|
+
? change.sessionProviderId
|
|
111
|
+
: previousProviderId;
|
|
112
|
+
const sessionProviderChanged = requestedProviderId !== previousProviderId;
|
|
113
|
+
if (sessionProviderChanged) {
|
|
114
|
+
instance.setSelectedProviderId(requestedProviderId);
|
|
92
115
|
}
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
&& previousModel
|
|
97
|
-
&& !activeProvider.models.some(model => model.id === previousModel);
|
|
98
|
-
if (change.activeProviderChanged || previousModelUnavailable) {
|
|
99
|
-
instance.clearSelectedModel();
|
|
116
|
+
const changedProviderIds = Array.isArray(change.changedProviderIds) ? change.changedProviderIds : [];
|
|
117
|
+
if (!change.forceSessionRefresh && !sessionProviderChanged && !changedProviderIds.includes(requestedProviderId)) {
|
|
118
|
+
return;
|
|
100
119
|
}
|
|
101
|
-
await instance.refreshModelOptions(
|
|
120
|
+
await instance.refreshModelOptions(
|
|
121
|
+
window.CodexOverleafProviderProfiles.buildRunSelection(catalog, requestedProviderId)
|
|
122
|
+
);
|
|
102
123
|
await instance.persistInputs();
|
|
103
124
|
}
|
|
104
125
|
|
|
@@ -174,7 +195,9 @@
|
|
|
174
195
|
updateDialog: true,
|
|
175
196
|
selectedId: result.savedProviderId
|
|
176
197
|
});
|
|
177
|
-
await notifyChanged(instance, applied.change
|
|
198
|
+
await notifyChanged(instance, applied.change, action.activate === true
|
|
199
|
+
? { sessionProviderId: result.savedProviderId }
|
|
200
|
+
: {});
|
|
178
201
|
instance.dialog.setBusy('', '');
|
|
179
202
|
const fellBackToBuiltin = previousCatalog.activeProviderId === result.savedProviderId
|
|
180
203
|
&& result.activeProviderId === 'builtin'
|
|
@@ -249,7 +272,7 @@
|
|
|
249
272
|
updateDialog: true,
|
|
250
273
|
selectedId: context.profileId
|
|
251
274
|
});
|
|
252
|
-
await notifyChanged(instance, applied.change);
|
|
275
|
+
await notifyChanged(instance, applied.change, { sessionProviderId: context.profileId });
|
|
253
276
|
instance.dialog.setBusy('', '');
|
|
254
277
|
instance.dialog.setStatus({
|
|
255
278
|
tone: 'success',
|
|
@@ -269,7 +292,7 @@
|
|
|
269
292
|
expectedRevision: 0
|
|
270
293
|
});
|
|
271
294
|
const applied = applyCatalog(instance, result, { updateDialog: true, selectedId: 'builtin' });
|
|
272
|
-
await notifyChanged(instance, applied.change);
|
|
295
|
+
await notifyChanged(instance, applied.change, { sessionProviderId: 'builtin' });
|
|
273
296
|
instance.dialog.setBusy('', '');
|
|
274
297
|
instance.dialog.setStatus({ tone: 'success', title: instance.tx('Built-in Codex is active.', '已启用内置 Codex。') });
|
|
275
298
|
} catch (error) {
|
|
@@ -315,22 +338,22 @@
|
|
|
315
338
|
function updateSettingsSummary(instance, override = {}) {
|
|
316
339
|
const active = window.CodexOverleafProviderProfiles.getActiveProvider(instance.catalog);
|
|
317
340
|
const summary = override.summary || (active.kind === 'builtin'
|
|
318
|
-
? instance.tx('Built-in Codex
|
|
319
|
-
: `${active.name} · ${active.defaultModelId || instance.tx('No model', '未配置模型')} · ${instance.tx('
|
|
341
|
+
? instance.tx('Built-in Codex · default for new sessions', '内置 Codex · 新会话默认')
|
|
342
|
+
: `${active.name} · ${active.defaultModelId || instance.tx('No model', '未配置模型')} · ${instance.tx('Default for new sessions', '新会话默认')}`);
|
|
320
343
|
instance.getSettingsPanelInstance()?.setProviderSummary?.({
|
|
321
344
|
summary,
|
|
322
345
|
tone: override.tone || (active.kind === 'custom' && active.wireApiPreference === 'auto' && !active.resolvedWireApi ? 'warning' : 'ok')
|
|
323
346
|
});
|
|
324
347
|
}
|
|
325
348
|
|
|
326
|
-
async function notifyChanged(instance, change = emptyCatalogChange()) {
|
|
349
|
+
async function notifyChanged(instance, change = emptyCatalogChange(), localChange = {}) {
|
|
327
350
|
try {
|
|
328
351
|
instance.channel?.postMessage?.({ type: 'provider-settings-changed', change, at: Date.now() });
|
|
329
352
|
} catch (_error) {
|
|
330
353
|
// Revision checks remain authoritative when BroadcastChannel is unavailable.
|
|
331
354
|
}
|
|
332
355
|
try {
|
|
333
|
-
await instance.onProviderChanged(instance.catalog, change);
|
|
356
|
+
await instance.onProviderChanged(instance.catalog, { ...change, ...localChange });
|
|
334
357
|
} catch (_error) {
|
|
335
358
|
// The authoritative catalog is already saved. A later refresh can retry
|
|
336
359
|
// the local model projection without rolling back the provider change.
|
|
@@ -356,9 +379,19 @@
|
|
|
356
379
|
function describeCatalogChange(previous, next) {
|
|
357
380
|
const previousActive = window.CodexOverleafProviderProfiles.getActiveProvider(previous);
|
|
358
381
|
const nextActive = window.CodexOverleafProviderProfiles.getActiveProvider(next);
|
|
382
|
+
const providerIds = new Set([
|
|
383
|
+
...(previous.providers || []).map(provider => provider.id),
|
|
384
|
+
...(next.providers || []).map(provider => provider.id)
|
|
385
|
+
]);
|
|
386
|
+
const changedProviderIds = Array.from(providerIds).filter(providerId => {
|
|
387
|
+
const previousProvider = window.CodexOverleafProviderProfiles.getProviderById(previous, providerId);
|
|
388
|
+
const nextProvider = window.CodexOverleafProviderProfiles.getProviderById(next, providerId);
|
|
389
|
+
return modelCatalogSignature(previousProvider) !== modelCatalogSignature(nextProvider);
|
|
390
|
+
});
|
|
359
391
|
return {
|
|
360
392
|
activeProviderChanged: previousActive.id !== nextActive.id,
|
|
361
|
-
activeModelCatalogChanged: modelCatalogSignature(previousActive) !== modelCatalogSignature(nextActive)
|
|
393
|
+
activeModelCatalogChanged: modelCatalogSignature(previousActive) !== modelCatalogSignature(nextActive),
|
|
394
|
+
changedProviderIds
|
|
362
395
|
};
|
|
363
396
|
}
|
|
364
397
|
|
|
@@ -377,7 +410,32 @@
|
|
|
377
410
|
}
|
|
378
411
|
|
|
379
412
|
function emptyCatalogChange() {
|
|
380
|
-
return { activeProviderChanged: false, activeModelCatalogChanged: false };
|
|
413
|
+
return { activeProviderChanged: false, activeModelCatalogChanged: false, changedProviderIds: [] };
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function getProviderSnapshot(instance, providerId) {
|
|
417
|
+
const requestedId = providerId || instance.getSelectedProviderId() || 'builtin';
|
|
418
|
+
const provider = window.CodexOverleafProviderProfiles.getProviderById(instance.catalog, requestedId);
|
|
419
|
+
if (!provider) {
|
|
420
|
+
return {
|
|
421
|
+
providerId: requestedId,
|
|
422
|
+
providerName: 'Unavailable provider',
|
|
423
|
+
providerRevision: 0,
|
|
424
|
+
providerEndpointHost: ''
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
let endpointHost = '';
|
|
428
|
+
try {
|
|
429
|
+
endpointHost = provider.baseUrl ? new URL(provider.baseUrl).host : '';
|
|
430
|
+
} catch (_error) {
|
|
431
|
+
endpointHost = '';
|
|
432
|
+
}
|
|
433
|
+
return {
|
|
434
|
+
providerId: provider.id,
|
|
435
|
+
providerName: provider.name || provider.id,
|
|
436
|
+
providerRevision: provider.revision || 0,
|
|
437
|
+
providerEndpointHost: endpointHost
|
|
438
|
+
};
|
|
381
439
|
}
|
|
382
440
|
|
|
383
441
|
function formatError(instance, error) {
|
|
@@ -408,10 +408,31 @@
|
|
|
408
408
|
))}</span>
|
|
409
409
|
</label>
|
|
410
410
|
<div class="codex-provider-test-row">
|
|
411
|
-
<
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
411
|
+
<div class="codex-provider-test-copy">
|
|
412
|
+
<strong>${escapeHtml(tx('Connection check', '连接检查'))}</strong>
|
|
413
|
+
<span>${escapeHtml(tx('Probe one model before using this provider.', '使用此服务商前,可选择一个模型进行探测。'))}</span>
|
|
414
|
+
</div>
|
|
415
|
+
<div class="codex-provider-test-controls">
|
|
416
|
+
<label class="codex-provider-test-model">
|
|
417
|
+
<span class="codex-provider-test-model-label">${escapeHtml(tx('Test model', '测试模型'))}</span>
|
|
418
|
+
<span class="codex-provider-test-select-shell">
|
|
419
|
+
<select data-provider-test-model aria-label="${escapeAttr(tx('Model to test', '要测试的模型'))}">
|
|
420
|
+
${(draft.models || []).map(model => `<option value="${escapeAttr(model.id)}" ${model.id === draft.defaultModelId ? 'selected' : ''}>${escapeHtml(model.label || model.id)}</option>`).join('')}
|
|
421
|
+
</select>
|
|
422
|
+
<svg class="codex-provider-test-chevron" viewBox="0 0 16 16" aria-hidden="true">
|
|
423
|
+
<path d="m4 6 4 4 4-4"></path>
|
|
424
|
+
</svg>
|
|
425
|
+
</span>
|
|
426
|
+
</label>
|
|
427
|
+
<button type="button" class="codex-provider-test-button" data-provider-action="test">
|
|
428
|
+
<svg viewBox="0 0 18 18" aria-hidden="true">
|
|
429
|
+
<circle cx="5" cy="9" r="2.25"></circle>
|
|
430
|
+
<circle cx="13" cy="9" r="2.25"></circle>
|
|
431
|
+
<path d="M7.25 9h3.5"></path>
|
|
432
|
+
</svg>
|
|
433
|
+
<span data-provider-test-action-label>${escapeHtml(tx('Test connection', '测试连接'))}</span>
|
|
434
|
+
</button>
|
|
435
|
+
</div>
|
|
415
436
|
<span class="codex-provider-test-state" data-provider-test-state>${escapeHtml(formatVerification(instance, provider, draft.defaultModelId))}</span>
|
|
416
437
|
</div>
|
|
417
438
|
`;
|
|
@@ -918,9 +939,13 @@
|
|
|
918
939
|
if (!button) return;
|
|
919
940
|
const testing = instance.busy === 'testing';
|
|
920
941
|
button.dataset.providerAction = testing ? 'cancel-test' : 'test';
|
|
921
|
-
|
|
942
|
+
const actionLabel = testing
|
|
922
943
|
? instance.tx('Cancel test', '取消测试')
|
|
923
944
|
: instance.tx('Test connection', '测试连接');
|
|
945
|
+
const labelEl = button.querySelector('[data-provider-test-action-label]');
|
|
946
|
+
if (labelEl) labelEl.textContent = actionLabel;
|
|
947
|
+
else button.textContent = actionLabel;
|
|
948
|
+
button.setAttribute('aria-label', actionLabel);
|
|
924
949
|
}
|
|
925
950
|
|
|
926
951
|
function formatSecretSavedAt(instance, value) {
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
saveStateSoon,
|
|
29
29
|
readPanelInputs,
|
|
30
30
|
applyStateToPanel,
|
|
31
|
+
syncSessionProvider,
|
|
31
32
|
getPanel,
|
|
32
33
|
getState,
|
|
33
34
|
setState,
|
|
@@ -98,6 +99,7 @@
|
|
|
98
99
|
}
|
|
99
100
|
const session = createSession({
|
|
100
101
|
mode: getState().mode,
|
|
102
|
+
providerId: getState().providerId || 'builtin',
|
|
101
103
|
model: getState().model,
|
|
102
104
|
reasoningEffort: getState().reasoningEffort,
|
|
103
105
|
speedTier: getState().speedTier,
|
|
@@ -108,8 +110,9 @@
|
|
|
108
110
|
sessions: [...(getState().sessions || []), session].slice(-20),
|
|
109
111
|
activeSessionId: session.id
|
|
110
112
|
}));
|
|
111
|
-
await saveState();
|
|
112
113
|
applyStateToPanel();
|
|
114
|
+
await syncSessionProvider?.();
|
|
115
|
+
await saveState();
|
|
113
116
|
getPanel().querySelector('[data-task]')?.focus();
|
|
114
117
|
}
|
|
115
118
|
|
|
@@ -130,8 +133,9 @@
|
|
|
130
133
|
}
|
|
131
134
|
readPanelInputs();
|
|
132
135
|
setState(setActiveSession(getState(), sessionId));
|
|
133
|
-
await saveState();
|
|
134
136
|
applyStateToPanel();
|
|
137
|
+
await syncSessionProvider?.();
|
|
138
|
+
await saveState();
|
|
135
139
|
}
|
|
136
140
|
|
|
137
141
|
async function deleteSessionWithConfirm(sessionId) {
|
|
@@ -31,6 +31,10 @@
|
|
|
31
31
|
createdAt: redactSecretLikeText(stringField(input.createdAt)) || now,
|
|
32
32
|
completedAt: '',
|
|
33
33
|
mode: redactSecretLikeText(stringField(input.mode)),
|
|
34
|
+
providerId: redactSecretLikeText(stringField(input.providerId)) || 'builtin',
|
|
35
|
+
providerName: redactSecretLikeText(stringField(input.providerName)),
|
|
36
|
+
providerRevision: finiteNumber(input.providerRevision) || 0,
|
|
37
|
+
providerEndpointHost: redactSecretLikeText(stringField(input.providerEndpointHost)),
|
|
34
38
|
model: redactSecretLikeText(stringField(input.model)),
|
|
35
39
|
reasoningEffort: redactSecretLikeText(stringField(input.reasoningEffort)),
|
|
36
40
|
speedTier: redactSecretLikeText(stringField(input.speedTier)),
|
|
@@ -85,6 +89,10 @@
|
|
|
85
89
|
createdAt: redactSecretLikeText(stringField(input.createdAt)) || new Date().toISOString(),
|
|
86
90
|
completedAt: redactSecretLikeText(stringField(input.completedAt)),
|
|
87
91
|
mode: redactSecretLikeText(stringField(input.mode)),
|
|
92
|
+
providerId: redactSecretLikeText(stringField(input.providerId)) || 'builtin',
|
|
93
|
+
providerName: redactSecretLikeText(stringField(input.providerName)),
|
|
94
|
+
providerRevision: finiteNumber(input.providerRevision) || 0,
|
|
95
|
+
providerEndpointHost: redactSecretLikeText(stringField(input.providerEndpointHost)),
|
|
88
96
|
model: redactSecretLikeText(stringField(input.model)),
|
|
89
97
|
reasoningEffort: redactSecretLikeText(stringField(input.reasoningEffort)),
|
|
90
98
|
speedTier: redactSecretLikeText(stringField(input.speedTier)),
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
const MIN_NATIVE_VERSION = '1.0.0';
|
|
13
13
|
const MIN_COMPATIBLE_NATIVE_VERSION = '1.0.0';
|
|
14
14
|
const MIN_COMPATIBLE_EXTENSION_VERSION = '1.0.0';
|
|
15
|
-
const BUILD_TARGET_VERSION = '2.1.
|
|
15
|
+
const BUILD_TARGET_VERSION = '2.1.2';
|
|
16
16
|
const DEFAULT_CHROME_EXTENSION_ID = 'illdpneeeopfffmiepaejglgmhpmdhdc';
|
|
17
17
|
const REQUIRED_CAPABILITIES = Object.freeze([
|
|
18
18
|
'bridgePing',
|
|
@@ -175,11 +175,23 @@
|
|
|
175
175
|
|| buildBuiltinProvider();
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
-
function
|
|
179
|
-
const
|
|
178
|
+
function getProviderById(catalog = {}, providerId = '') {
|
|
179
|
+
const normalized = normalizeCatalog(catalog);
|
|
180
|
+
const requestedId = normalizeText(providerId) || normalized.activeProviderId || BUILTIN_PROVIDER_ID;
|
|
181
|
+
if (requestedId === BUILTIN_PROVIDER_ID) {
|
|
182
|
+
return normalized.providers.find(provider => provider.id === BUILTIN_PROVIDER_ID)
|
|
183
|
+
|| buildBuiltinProvider();
|
|
184
|
+
}
|
|
185
|
+
return normalized.providers.find(provider => provider.id === requestedId) || null;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function buildRunSelection(catalog = {}, providerId = '') {
|
|
189
|
+
const normalized = normalizeCatalog(catalog);
|
|
190
|
+
const requestedId = normalizeText(providerId) || normalized.activeProviderId || BUILTIN_PROVIDER_ID;
|
|
191
|
+
const provider = getProviderById(normalized, requestedId);
|
|
180
192
|
return {
|
|
181
|
-
providerId:
|
|
182
|
-
providerRevision:
|
|
193
|
+
providerId: requestedId,
|
|
194
|
+
providerRevision: provider?.revision || 0
|
|
183
195
|
};
|
|
184
196
|
}
|
|
185
197
|
|
|
@@ -277,6 +289,7 @@
|
|
|
277
289
|
buildEmptyDraft,
|
|
278
290
|
buildRunSelection,
|
|
279
291
|
getActiveProvider,
|
|
292
|
+
getProviderById,
|
|
280
293
|
normalizeCatalog,
|
|
281
294
|
normalizeProvider
|
|
282
295
|
};
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
const DEFAULT_PANEL_STATE = {
|
|
15
15
|
mode: 'confirm',
|
|
16
|
+
providerId: 'builtin',
|
|
16
17
|
model: 'gpt-5.4',
|
|
17
18
|
reasoningEffort: 'high',
|
|
18
19
|
speedTier: 'standard',
|
|
@@ -177,6 +178,7 @@
|
|
|
177
178
|
...legacySession,
|
|
178
179
|
task: state.task,
|
|
179
180
|
mode: state.mode,
|
|
181
|
+
providerId: 'builtin',
|
|
180
182
|
model: state.model,
|
|
181
183
|
reasoningEffort: state.reasoningEffort,
|
|
182
184
|
speedTier: state.speedTier,
|
|
@@ -208,6 +210,9 @@
|
|
|
208
210
|
runs,
|
|
209
211
|
task: sanitizeAssistantVisibleText(session.task),
|
|
210
212
|
mode: VALID_MODES.has(session.mode) ? session.mode : fallbackState.mode,
|
|
213
|
+
providerId: typeof session.providerId === 'string' && session.providerId.trim()
|
|
214
|
+
? session.providerId.trim()
|
|
215
|
+
: 'builtin',
|
|
211
216
|
model: typeof session.model === 'string' && session.model ? session.model : fallbackState.model,
|
|
212
217
|
reasoningEffort: VALID_REASONING.has(session.reasoningEffort)
|
|
213
218
|
? session.reasoningEffort
|
|
@@ -237,6 +242,7 @@
|
|
|
237
242
|
id: `recovered_${session.id}_${index}`,
|
|
238
243
|
task,
|
|
239
244
|
mode: session.mode,
|
|
245
|
+
providerId: session.providerId || 'builtin',
|
|
240
246
|
model: session.model,
|
|
241
247
|
reasoningEffort: session.reasoningEffort,
|
|
242
248
|
speedTier: session.speedTier,
|
|
@@ -269,6 +275,7 @@
|
|
|
269
275
|
if (!active) {
|
|
270
276
|
const session = createSession({
|
|
271
277
|
mode: state.mode,
|
|
278
|
+
providerId: state.providerId,
|
|
272
279
|
model: state.model,
|
|
273
280
|
reasoningEffort: state.reasoningEffort,
|
|
274
281
|
speedTier: state.speedTier,
|
|
@@ -283,12 +290,16 @@
|
|
|
283
290
|
id: active.id,
|
|
284
291
|
history: Array.isArray(active.history) ? active.history.slice(-10) : [],
|
|
285
292
|
focusFiles: normalizeFocusFiles(active.focusFiles),
|
|
286
|
-
codexThreadId: active.codexThreadId || ''
|
|
293
|
+
codexThreadId: active.codexThreadId || '',
|
|
294
|
+
providerId: active.providerId || 'builtin'
|
|
287
295
|
};
|
|
288
296
|
state.runs = Array.isArray(active.runs) ? active.runs : [];
|
|
289
297
|
state.task = typeof active.task === 'string' ? active.task : '';
|
|
290
298
|
state.focusFiles = normalizeFocusFiles(active.focusFiles);
|
|
291
299
|
state.mode = VALID_MODES.has(active.mode) ? active.mode : DEFAULT_PANEL_STATE.mode;
|
|
300
|
+
state.providerId = typeof active.providerId === 'string' && active.providerId
|
|
301
|
+
? active.providerId
|
|
302
|
+
: 'builtin';
|
|
292
303
|
state.model = typeof active.model === 'string' && active.model ? active.model : DEFAULT_PANEL_STATE.model;
|
|
293
304
|
state.reasoningEffort = VALID_REASONING.has(active.reasoningEffort)
|
|
294
305
|
? active.reasoningEffort
|
|
@@ -318,6 +329,9 @@
|
|
|
318
329
|
runs,
|
|
319
330
|
task: sanitizeAssistantVisibleText(overrides.task),
|
|
320
331
|
mode: VALID_MODES.has(overrides.mode) ? overrides.mode : DEFAULT_PANEL_STATE.mode,
|
|
332
|
+
providerId: typeof overrides.providerId === 'string' && overrides.providerId.trim()
|
|
333
|
+
? overrides.providerId.trim()
|
|
334
|
+
: 'builtin',
|
|
321
335
|
model: typeof overrides.model === 'string' && overrides.model ? overrides.model : DEFAULT_PANEL_STATE.model,
|
|
322
336
|
reasoningEffort: VALID_REASONING.has(overrides.reasoningEffort)
|
|
323
337
|
? overrides.reasoningEffort
|
|
@@ -404,6 +418,7 @@
|
|
|
404
418
|
if (!remaining.length) {
|
|
405
419
|
const replacement = createSession({
|
|
406
420
|
mode: state.mode,
|
|
421
|
+
providerId: state.providerId,
|
|
407
422
|
model: state.model,
|
|
408
423
|
reasoningEffort: state.reasoningEffort,
|
|
409
424
|
speedTier: state.speedTier,
|
|
@@ -589,6 +604,10 @@
|
|
|
589
604
|
id: run.id,
|
|
590
605
|
task: sanitizeAssistantVisibleText(run.task) || 'untitled task',
|
|
591
606
|
mode: typeof run.mode === 'string' ? run.mode : '',
|
|
607
|
+
providerId: typeof run.providerId === 'string' && run.providerId ? run.providerId : 'builtin',
|
|
608
|
+
providerName: typeof run.providerName === 'string' ? run.providerName : '',
|
|
609
|
+
providerRevision: Number.isFinite(Number(run.providerRevision)) ? Number(run.providerRevision) : 0,
|
|
610
|
+
providerEndpointHost: typeof run.providerEndpointHost === 'string' ? run.providerEndpointHost : '',
|
|
592
611
|
model: typeof run.model === 'string' ? run.model : '',
|
|
593
612
|
reasoningEffort: typeof run.reasoningEffort === 'string' ? run.reasoningEffort : '',
|
|
594
613
|
speedTier: typeof run.speedTier === 'string' ? run.speedTier : '',
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
}
|
|
7
7
|
})(typeof globalThis !== 'undefined' ? globalThis : window, function storageDbFactory(StorageRunActions) {
|
|
8
8
|
'use strict';
|
|
9
|
-
if (!StorageRunActions?.compactRunsForStorage || !StorageRunActions?.compactRunActionPayload) {
|
|
9
|
+
if (!StorageRunActions?.compactRunsForStorage || !StorageRunActions?.compactRunActionPayload || !StorageRunActions?.compactProviderSnapshot) {
|
|
10
10
|
throw new Error('Codex Overleaf storage run-action helpers are unavailable.');
|
|
11
11
|
}
|
|
12
12
|
// IndexedDB versions are monotonic; v2.0 RC profiles have already opened v3.
|
|
@@ -275,7 +275,7 @@
|
|
|
275
275
|
history: compactHistoryForStorage(input.history),
|
|
276
276
|
runs: compactRunsForStorage(input.runs, options),
|
|
277
277
|
task: normalizeDisplayTextForStorage(input.task, SESSION_STORAGE_LIMITS.taskChars),
|
|
278
|
-
mode: typeof input.mode === 'string' ? input.mode : '',
|
|
278
|
+
mode: typeof input.mode === 'string' ? input.mode : '', providerId: typeof input.providerId === 'string' && input.providerId ? input.providerId : 'builtin',
|
|
279
279
|
model: typeof input.model === 'string' ? input.model : '',
|
|
280
280
|
reasoningEffort: typeof input.reasoningEffort === 'string' ? input.reasoningEffort : '',
|
|
281
281
|
speedTier: typeof input.speedTier === 'string' ? input.speedTier : '',
|
|
@@ -402,7 +402,7 @@
|
|
|
402
402
|
turnId: typeof input.turnId === 'string' ? input.turnId : '',
|
|
403
403
|
createdAt: typeof input.createdAt === 'string' ? input.createdAt : now,
|
|
404
404
|
completedAt: typeof input.completedAt === 'string' ? input.completedAt : '',
|
|
405
|
-
mode: typeof input.mode === 'string' ? input.mode : '',
|
|
405
|
+
mode: typeof input.mode === 'string' ? input.mode : '', ...StorageRunActions.compactProviderSnapshot(input),
|
|
406
406
|
model: typeof input.model === 'string' ? input.model : '',
|
|
407
407
|
reasoningEffort: typeof input.reasoningEffort === 'string' ? input.reasoningEffort : '',
|
|
408
408
|
speedTier: typeof input.speedTier === 'string' ? input.speedTier : '',
|
|
@@ -426,7 +426,7 @@
|
|
|
426
426
|
|
|
427
427
|
function extractLightweightPrefs(state, projectId) {
|
|
428
428
|
var prefs = {
|
|
429
|
-
storageSchemaVersion: TARGET_SCHEMA_VERSION,
|
|
429
|
+
storageSchemaVersion: TARGET_SCHEMA_VERSION, providerId: typeof state.providerId === 'string' && state.providerId ? state.providerId : 'builtin',
|
|
430
430
|
model: typeof state.model === 'string' ? state.model : '',
|
|
431
431
|
reasoningEffort: typeof state.reasoningEffort === 'string' ? state.reasoningEffort : '',
|
|
432
432
|
speedTier: typeof state.speedTier === 'string' ? state.speedTier : '',
|
|
@@ -647,7 +647,7 @@
|
|
|
647
647
|
var compact = {
|
|
648
648
|
id: run.id,
|
|
649
649
|
task: normalizeDisplayTextForStorage(run.task || 'untitled task', SESSION_STORAGE_LIMITS.taskChars),
|
|
650
|
-
mode: typeof run.mode === 'string' ? redactSecretLikeText(run.mode) : '',
|
|
650
|
+
mode: typeof run.mode === 'string' ? redactSecretLikeText(run.mode) : '', ...StorageRunActions.compactProviderSnapshot(run),
|
|
651
651
|
model: normalizeTextField(run.model, 80),
|
|
652
652
|
reasoningEffort: typeof run.reasoningEffort === 'string' ? redactSecretLikeText(run.reasoningEffort) : '',
|
|
653
653
|
speedTier: typeof run.speedTier === 'string' ? redactSecretLikeText(run.speedTier) : '',
|
|
@@ -81,6 +81,7 @@
|
|
|
81
81
|
runs: Array.isArray(legacy.runs) ? legacy.runs : [],
|
|
82
82
|
task: typeof legacy.task === 'string' ? legacy.task : '',
|
|
83
83
|
mode: typeof legacy.mode === 'string' ? legacy.mode : legacyBlob.mode || '',
|
|
84
|
+
providerId: typeof legacy.providerId === 'string' && legacy.providerId ? legacy.providerId : 'builtin',
|
|
84
85
|
model: typeof legacy.model === 'string' ? legacy.model : legacyBlob.model || '',
|
|
85
86
|
reasoningEffort: typeof legacy.reasoningEffort === 'string' ? legacy.reasoningEffort : legacyBlob.reasoningEffort || '',
|
|
86
87
|
speedTier: typeof legacy.speedTier === 'string' ? legacy.speedTier : legacyBlob.speedTier || '',
|
|
@@ -92,8 +92,18 @@
|
|
|
92
92
|
return value.length * 2;
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
+
function compactProviderSnapshot(value) {
|
|
96
|
+
return {
|
|
97
|
+
providerId: typeof value.providerId === 'string' && value.providerId ? value.providerId : 'builtin',
|
|
98
|
+
providerName: typeof value.providerName === 'string' ? value.providerName : '',
|
|
99
|
+
providerRevision: Number.isFinite(Number(value.providerRevision)) ? Number(value.providerRevision) : 0,
|
|
100
|
+
providerEndpointHost: typeof value.providerEndpointHost === 'string' ? value.providerEndpointHost : ''
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
95
104
|
return {
|
|
96
105
|
compactRunsForStorage: compactRunsForStorage,
|
|
97
|
-
compactRunActionPayload: compactRunActionPayload
|
|
106
|
+
compactRunActionPayload: compactRunActionPayload,
|
|
107
|
+
compactProviderSnapshot: compactProviderSnapshot
|
|
98
108
|
};
|
|
99
109
|
});
|
|
@@ -264,14 +264,111 @@
|
|
|
264
264
|
}
|
|
265
265
|
|
|
266
266
|
.codex-provider-test-row {
|
|
267
|
-
display:
|
|
268
|
-
|
|
267
|
+
display: grid;
|
|
268
|
+
grid-template-columns: minmax(0, 1fr);
|
|
269
269
|
gap: 12px;
|
|
270
270
|
margin-top: 18px;
|
|
271
|
+
padding: 14px;
|
|
272
|
+
border: 1px solid rgba(255, 255, 255, 0.085);
|
|
273
|
+
border-radius: 12px;
|
|
274
|
+
background:
|
|
275
|
+
radial-gradient(circle at 0 0, rgba(121, 213, 138, 0.08), transparent 44%),
|
|
276
|
+
rgba(255, 255, 255, 0.025);
|
|
277
|
+
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.025);
|
|
271
278
|
}
|
|
272
279
|
|
|
273
|
-
.codex-provider-test-
|
|
280
|
+
.codex-provider-test-copy { display: grid; gap: 3px; min-width: 0; }
|
|
281
|
+
.codex-provider-test-copy strong { color: var(--provider-text); font-size: 12px; font-weight: 750; letter-spacing: 0.01em; }
|
|
282
|
+
.codex-provider-test-copy > span { color: var(--provider-muted); font-size: 11px; line-height: 1.4; }
|
|
283
|
+
|
|
284
|
+
.codex-provider-test-controls {
|
|
285
|
+
display: grid;
|
|
286
|
+
grid-template-columns: minmax(180px, 1fr) auto;
|
|
287
|
+
align-items: end;
|
|
288
|
+
gap: 10px;
|
|
289
|
+
min-width: 0;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
.codex-provider-test-model { display: grid; gap: 6px; min-width: 0; }
|
|
293
|
+
.codex-provider-test-model-label {
|
|
294
|
+
color: var(--provider-muted);
|
|
295
|
+
font-size: 10px;
|
|
296
|
+
font-weight: 750;
|
|
297
|
+
letter-spacing: 0.08em;
|
|
298
|
+
text-transform: uppercase;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
.codex-provider-test-select-shell { position: relative; display: block; min-width: 0; border-radius: 9px; }
|
|
302
|
+
.codex-provider-test-select-shell select {
|
|
303
|
+
width: 100%;
|
|
304
|
+
min-width: 0;
|
|
305
|
+
min-height: 40px;
|
|
306
|
+
padding: 0 38px 0 12px;
|
|
307
|
+
border: 1px solid var(--provider-line);
|
|
308
|
+
border-radius: 9px;
|
|
309
|
+
appearance: none;
|
|
310
|
+
-webkit-appearance: none;
|
|
311
|
+
background: rgba(7, 12, 10, 0.58);
|
|
312
|
+
color: var(--provider-text);
|
|
313
|
+
color-scheme: dark;
|
|
314
|
+
font: inherit;
|
|
315
|
+
font-size: 12px;
|
|
316
|
+
font-weight: 650;
|
|
317
|
+
line-height: 1;
|
|
318
|
+
outline: none;
|
|
319
|
+
cursor: pointer;
|
|
320
|
+
}
|
|
321
|
+
.codex-provider-test-select-shell select:hover { border-color: rgba(121, 213, 138, 0.34); background: rgba(12, 19, 15, 0.72); }
|
|
322
|
+
.codex-provider-test-select-shell:focus-within { box-shadow: 0 0 0 3px rgba(121, 213, 138, 0.12); }
|
|
323
|
+
.codex-provider-test-select-shell:focus-within select { border-color: rgba(121, 213, 138, 0.66); }
|
|
324
|
+
.codex-provider-test-chevron {
|
|
325
|
+
position: absolute;
|
|
326
|
+
top: 50%;
|
|
327
|
+
right: 12px;
|
|
328
|
+
width: 16px;
|
|
329
|
+
height: 16px;
|
|
330
|
+
transform: translateY(-50%);
|
|
331
|
+
fill: none;
|
|
332
|
+
stroke: var(--provider-muted);
|
|
333
|
+
stroke-width: 1.7;
|
|
334
|
+
stroke-linecap: round;
|
|
335
|
+
stroke-linejoin: round;
|
|
336
|
+
pointer-events: none;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
.codex-provider-test-button {
|
|
340
|
+
display: inline-flex;
|
|
341
|
+
align-items: center;
|
|
342
|
+
justify-content: center;
|
|
343
|
+
gap: 8px;
|
|
344
|
+
min-height: 40px;
|
|
345
|
+
padding: 0 14px;
|
|
346
|
+
border: 1px solid rgba(121, 213, 138, 0.36);
|
|
347
|
+
border-radius: 9px;
|
|
348
|
+
background: linear-gradient(180deg, rgba(66, 132, 78, 0.38), rgba(42, 91, 52, 0.34));
|
|
349
|
+
color: #b8edc2;
|
|
350
|
+
box-shadow: 0 5px 15px rgba(6, 19, 10, 0.16);
|
|
351
|
+
font: inherit;
|
|
352
|
+
font-size: 12px;
|
|
353
|
+
font-weight: 750;
|
|
354
|
+
white-space: nowrap;
|
|
355
|
+
cursor: pointer;
|
|
356
|
+
transition: border-color 140ms ease, background 140ms ease, color 140ms ease, transform 140ms ease;
|
|
357
|
+
}
|
|
358
|
+
.codex-provider-test-button svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.6; }
|
|
359
|
+
.codex-provider-test-button:not(:disabled):hover {
|
|
360
|
+
border-color: rgba(121, 213, 138, 0.7);
|
|
361
|
+
background: linear-gradient(180deg, rgba(72, 151, 88, 0.5), rgba(44, 102, 57, 0.46));
|
|
362
|
+
color: #d4f7da;
|
|
363
|
+
transform: translateY(-1px);
|
|
364
|
+
}
|
|
365
|
+
.codex-provider-test-button:not(:disabled):active { transform: translateY(0); }
|
|
366
|
+
.codex-provider-test-button[data-provider-action="cancel-test"] { border-color: rgba(242, 129, 113, 0.42); background: rgba(122, 55, 47, 0.24); color: #f3aaa0; }
|
|
367
|
+
|
|
368
|
+
.codex-provider-test-state { min-height: 16px; color: var(--provider-muted); font-size: 11px; line-height: 1.4; }
|
|
369
|
+
.codex-provider-test-state:empty { display: none; }
|
|
274
370
|
.codex-provider-test-state[data-tone="success"] { color: var(--provider-accent); }
|
|
371
|
+
.codex-provider-test-state[data-tone="failed"] { color: var(--provider-danger); }
|
|
275
372
|
|
|
276
373
|
.codex-provider-dialog-foot {
|
|
277
374
|
min-height: 70px;
|
|
@@ -334,6 +431,11 @@
|
|
|
334
431
|
.codex-provider-builtin .codex-provider-active-badge,
|
|
335
432
|
.codex-provider-builtin button { margin-top: 18px; }
|
|
336
433
|
|
|
434
|
+
@media (max-width: 620px) {
|
|
435
|
+
.codex-provider-test-controls { grid-template-columns: 1fr; align-items: stretch; }
|
|
436
|
+
.codex-provider-test-button { width: 100%; }
|
|
437
|
+
}
|
|
438
|
+
|
|
337
439
|
@keyframes codex-provider-spin { to { transform: rotate(360deg); } }
|
|
338
440
|
|
|
339
441
|
@media (max-width: 720px) {
|
|
@@ -213,12 +213,24 @@ function cancelProviderTest(operationId) {
|
|
|
213
213
|
|
|
214
214
|
function resolveProviderModels(params = {}, env = process.env, resolveBuiltInModels) {
|
|
215
215
|
const state = loadProviderState(env);
|
|
216
|
-
|
|
217
|
-
|
|
216
|
+
const selection = params.providerSelection;
|
|
217
|
+
const requestedProviderId = selection?.providerId || state.public.activeProviderId || 'builtin';
|
|
218
|
+
if (requestedProviderId === 'builtin') {
|
|
219
|
+
const result = resolveBuiltInModels(params, env);
|
|
220
|
+
const attachProvider = value => ({
|
|
221
|
+
...(value || {}),
|
|
222
|
+
providerId: 'builtin',
|
|
223
|
+
providerRevision: 0,
|
|
224
|
+
providerName: 'Built-in Codex'
|
|
225
|
+
});
|
|
226
|
+
return result && typeof result.then === 'function' ? result.then(attachProvider) : attachProvider(result);
|
|
218
227
|
}
|
|
219
|
-
const profile = state.public.profiles.find(item => item.id ===
|
|
228
|
+
const profile = state.public.profiles.find(item => item.id === requestedProviderId);
|
|
220
229
|
if (!profile) {
|
|
221
|
-
throw providerError('provider_not_found', 'The
|
|
230
|
+
throw providerError('provider_not_found', 'The selected provider no longer exists.');
|
|
231
|
+
}
|
|
232
|
+
if (selection?.providerId && Number(selection.providerRevision) !== profile.revision) {
|
|
233
|
+
throw providerError('provider_revision_conflict', 'The selected provider changed. Refresh its model list and retry.');
|
|
222
234
|
}
|
|
223
235
|
return {
|
|
224
236
|
providerId: profile.id,
|
|
@@ -245,10 +257,8 @@ function resolveRunProvider(params = {}, env = process.env) {
|
|
|
245
257
|
const state = loadProviderState(env);
|
|
246
258
|
const selection = params.providerSelection;
|
|
247
259
|
const activeProviderId = state.public.activeProviderId || 'builtin';
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
}
|
|
251
|
-
if (activeProviderId === 'builtin') {
|
|
260
|
+
const requestedProviderId = selection?.providerId || activeProviderId;
|
|
261
|
+
if (requestedProviderId === 'builtin') {
|
|
252
262
|
return {
|
|
253
263
|
modelId: params.model || '',
|
|
254
264
|
reasoningEffort: params.reasoningEffort || '',
|
|
@@ -256,15 +266,15 @@ function resolveRunProvider(params = {}, env = process.env) {
|
|
|
256
266
|
providerSelection: { providerId: 'builtin', providerRevision: 0 }
|
|
257
267
|
};
|
|
258
268
|
}
|
|
259
|
-
const profile = state.public.profiles.find(item => item.id ===
|
|
269
|
+
const profile = state.public.profiles.find(item => item.id === requestedProviderId);
|
|
260
270
|
if (!profile) {
|
|
261
|
-
throw providerError('provider_not_found', 'The
|
|
271
|
+
throw providerError('provider_not_found', 'The selected provider no longer exists.');
|
|
262
272
|
}
|
|
263
273
|
if (!selection?.providerId) {
|
|
264
274
|
throw providerError('provider_selection_unavailable', 'The active provider has not been confirmed by this Overleaf tab. Wait for Provider settings to load and retry.');
|
|
265
275
|
}
|
|
266
276
|
if (Number(selection.providerRevision) !== profile.revision) {
|
|
267
|
-
throw providerError('provider_revision_conflict', 'The
|
|
277
|
+
throw providerError('provider_revision_conflict', 'The selected provider changed. Refresh its model list and retry.');
|
|
268
278
|
}
|
|
269
279
|
const endpointHost = getEndpointHost(profile.baseUrl);
|
|
270
280
|
if (profile.endpointDisclosureHost !== endpointHost || profile.endpointDisclosureBaseUrl !== profile.baseUrl) {
|
|
@@ -302,7 +312,9 @@ function resolveRunProvider(params = {}, env = process.env) {
|
|
|
302
312
|
reasoningEffort,
|
|
303
313
|
upstreamResponseMode: validDiagnostic ? diagnostic.upstreamResponseMode : ''
|
|
304
314
|
}),
|
|
305
|
-
providerSelection: { providerId: profile.id, providerRevision: profile.revision }
|
|
315
|
+
providerSelection: { providerId: profile.id, providerRevision: profile.revision },
|
|
316
|
+
providerName: profile.name,
|
|
317
|
+
providerEndpointHost: endpointHost
|
|
306
318
|
};
|
|
307
319
|
}
|
|
308
320
|
|
|
@@ -315,9 +327,10 @@ async function resolveRunProviderForRun(params = {}, env = process.env, options
|
|
|
315
327
|
}
|
|
316
328
|
}
|
|
317
329
|
const state = loadProviderState(env);
|
|
318
|
-
const
|
|
330
|
+
const requestedProviderId = params.providerSelection?.providerId || state.public.activeProviderId || 'builtin';
|
|
331
|
+
const profile = state.public.profiles.find(item => item.id === requestedProviderId);
|
|
319
332
|
if (!profile) {
|
|
320
|
-
throw providerError('provider_not_found', 'The
|
|
333
|
+
throw providerError('provider_not_found', 'The selected provider no longer exists.');
|
|
321
334
|
}
|
|
322
335
|
const modelId = String(params.model || profile.defaultModelId).trim();
|
|
323
336
|
options.emit?.({
|