codex-overleaf-link 2.5.0 → 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.
- package/README.md +38 -26
- package/README.zh-CN.md +39 -27
- package/extension/bootstrap/background.js +52 -9
- package/extension/bootstrap/manifest.template.json +1 -1
- package/extension/bootstrap/popup.html +1 -0
- package/extension/bootstrap/popupFallback.js +55 -0
- package/extension/bootstrap/updateStatus.js +1 -0
- package/extension/src/background.js +206 -53
- package/extension/src/backgroundBootstrapMigration.js +181 -0
- package/extension/src/backgroundSharedSessions.js +74 -14
- package/extension/src/backgroundUpdateCoordinator.js +118 -21
- package/extension/src/backgroundUpdateSafety.js +173 -0
- package/extension/src/content/activeTurnControl.js +8 -2
- package/extension/src/content/applyResultFormatters.js +46 -73
- package/extension/src/content/assetTransferBroker.js +72 -14
- package/extension/src/content/composerAttachments.js +93 -45
- package/extension/src/content/composerPanel.js +18 -0
- package/extension/src/content/contentRuntime.js +661 -676
- package/extension/src/content/diagnosticsController.js +122 -96
- package/extension/src/content/diagnosticsExport.js +19 -5
- package/extension/src/content/diagnosticsPanel.js +30 -0
- package/extension/src/content/directoryCleanupRecovery.js +54 -0
- package/extension/src/content/generated/content.bundle.js +6879 -2786
- package/extension/src/content/generated/content.bundle.meta.json +11 -3
- package/extension/src/content/globalPreferencesController.js +1 -1
- package/extension/src/content/legacyGlobalRegistry.js +6 -0
- package/extension/src/content/localSkillsPanel.js +102 -12
- package/extension/src/content/markdownText.js +5 -6
- package/extension/src/content/modelPicker.js +7 -4
- package/extension/src/content/nativeCompatibilityController.js +45 -20
- package/extension/src/content/otWarmMirror.js +4 -5
- package/extension/src/content/otWarmMirrorController.js +17 -20
- package/extension/src/content/pageBridgeClient.js +15 -2
- package/extension/src/content/panelMaintenance.js +40 -12
- package/extension/src/content/panelRenderer.js +6 -0
- package/extension/src/content/projectProviderSelection.js +13 -4
- package/extension/src/content/projectSessionCleanup.js +109 -62
- package/extension/src/content/projectSettingsCoordinator.js +39 -2
- package/extension/src/content/providerSettingsCoordinator.js +46 -8
- package/extension/src/content/providerSettingsDialog.js +71 -13
- package/extension/src/content/recentProjects.js +11 -5
- package/extension/src/content/runActivitySummary.js +40 -6
- package/extension/src/content/runCompletionMeta.js +344 -0
- package/extension/src/content/runController.js +37 -14
- package/extension/src/content/runFailureNotice.js +21 -12
- package/extension/src/content/runGuidanceController.js +72 -27
- package/extension/src/content/runGuidanceView.js +59 -10
- package/extension/src/content/runQueueScheduler.js +148 -42
- package/extension/src/content/runRecoveryActions.js +146 -0
- package/extension/src/content/runResultActions.js +8 -125
- package/extension/src/content/runReviewController.js +574 -0
- package/extension/src/content/runScrollLayout.js +21 -2
- package/extension/src/content/runTimelineView.js +146 -153
- package/extension/src/content/scopedPersistencePanelState.js +51 -6
- package/extension/src/content/selectionContextView.js +1 -1
- package/extension/src/content/sensitivePreflight.js +140 -0
- package/extension/src/content/sessionForkController.js +80 -17
- package/extension/src/content/sessionManager.js +1 -1
- package/extension/src/content/settingsPanel.js +7 -3
- package/extension/src/content/settingsWorkbench.js +5 -0
- package/extension/src/content/sharedSessionViewSync.js +45 -8
- package/extension/src/content/updateIdle.js +4 -1
- package/extension/src/content/updateNotice.js +60 -8
- package/extension/src/content/updateSafetyReceiver.js +58 -0
- package/extension/src/content/writebackController.js +53 -4
- package/extension/src/content/writebackOrchestrator.js +82 -73
- package/extension/src/content/writingStyleSettings.js +35 -11
- package/extension/src/page/binaryAssetUploader.js +136 -14
- package/extension/src/page/fileMover.js +256 -0
- package/extension/src/page/ownedDirectoryCleanup.js +94 -0
- package/extension/src/page/referenceProjects.js +1 -0
- package/extension/src/page/reviewContext.js +113 -0
- package/extension/src/page/saveState.js +6 -4
- package/extension/src/page/snapshotRouter.js +84 -39
- package/extension/src/page/textFileCreator.js +105 -25
- package/extension/src/page/trackedChangeCapture.js +12 -2
- package/extension/src/page/trackedChangeOwnership.js +19 -1
- package/extension/src/page/trackedChangeReplay.js +41 -21
- package/extension/src/page/trackedChangesLifecycle.js +217 -199
- package/extension/src/page/treeOperations.js +7 -10
- package/extension/src/page/updateSafetyProbe.js +41 -0
- package/extension/src/page/writebackReceiptJournal.js +2 -0
- package/extension/src/page/writebackRouter.js +80 -79
- package/extension/src/pageBridge.js +19 -3
- package/extension/src/popup.js +62 -10
- package/extension/src/shared/agentTranscript.js +23 -0
- package/extension/src/shared/auditRecords.js +2 -0
- package/extension/src/shared/compatibility.js +22 -6
- package/extension/src/shared/compileAdapter.js +79 -57
- package/extension/src/shared/composerAttachmentDraft.js +151 -0
- package/extension/src/shared/directoryCleanupProof.js +43 -0
- package/extension/src/shared/failureReasons.js +309 -3
- package/extension/src/shared/globalPreferences.js +30 -13
- package/extension/src/shared/governanceRules.js +4 -37
- package/extension/src/shared/i18n.js +298 -2
- package/extension/src/shared/managedUpdateProjection.js +2 -2
- package/extension/src/shared/nativeRequestIdentity.js +1 -0
- package/extension/src/shared/pageRpcContract.js +5 -4
- package/extension/src/shared/projectFiles.js +21 -44
- package/extension/src/shared/providerProfiles.js +5 -3
- package/extension/src/shared/runInputQueue.js +48 -5
- package/extension/src/shared/selectionContext.js +12 -3
- package/extension/src/shared/sessionState.js +72 -27
- package/extension/src/shared/settlementFacts.js +10 -2
- package/extension/src/shared/sharedSessionBridge.js +37 -14
- package/extension/src/shared/sharedSessionTransfer.js +140 -0
- package/extension/src/shared/staleGuard.js +118 -13
- package/extension/src/shared/storageDb.js +58 -26
- package/extension/src/shared/storageMigration.js +71 -111
- package/extension/src/shared/storageRunActions.js +209 -26
- package/extension/src/shared/subagentActivityModel.js +1 -1
- package/extension/src/shared/undoOperations.js +136 -13
- package/extension/src/shared/updateConsent.js +6 -4
- package/extension/src/shared/updateRuntimeIdentity.js +1 -1
- package/extension/src/shared/writebackIntent.js +16 -0
- package/extension/src/shared/writebackOperationEvidence.js +4 -15
- package/extension/src/shared/writebackSettlement.js +38 -38
- package/extension/styles/panel.css +19 -5
- package/native-host/src/anthropicBridgeRequest.js +4 -2
- package/native-host/src/anthropicBridgeResponse.js +6 -2
- package/native-host/src/anthropicMessagesBridge.js +24 -16
- package/native-host/src/bootstrapMigration.js +296 -0
- package/native-host/src/bootstrapMigrationContract.js +101 -0
- package/native-host/src/bootstrapUpdate.js +233 -0
- package/native-host/src/bootstrapUpdateWatchdog.js +44 -0
- package/native-host/src/chatBridgeResponse.js +10 -0
- package/native-host/src/chatCompletionsBridge.js +21 -15
- package/native-host/src/codexConfigPaths.js +106 -0
- package/native-host/src/codexHome.js +66 -73
- package/native-host/src/codexHomeScope.js +124 -0
- package/native-host/src/codexInheritedAuth.js +90 -0
- package/native-host/src/codexLocalSkills.js +301 -0
- package/native-host/src/codexPromptAssembly.js +31 -12
- package/native-host/src/codexProviderTest.js +24 -26
- package/native-host/src/codexSessionRunner.js +110 -183
- package/native-host/src/codexSessionTiming.js +18 -8
- package/native-host/src/codexSkillIsolation.js +170 -0
- package/native-host/src/codexThreadFork.js +19 -3
- package/native-host/src/index.js +19 -7
- package/native-host/src/managedInstall.js +110 -35
- package/native-host/src/managedInstallRecovery.js +140 -0
- package/native-host/src/managedRuntimeEvidence.js +57 -0
- package/native-host/src/mirrorWorkspace.js +92 -22
- package/native-host/src/nativeDoctor.js +15 -0
- package/native-host/src/nativeHelperLifecycle.js +21 -0
- package/native-host/src/nativeHostRegistration.js +33 -0
- package/native-host/src/nativeRunLifecycle.js +71 -0
- package/native-host/src/processStopBarrier.js +42 -7
- package/native-host/src/projectPathPlatform.js +30 -0
- package/native-host/src/providerHttp.js +80 -0
- package/native-host/src/providerProfile.js +4 -2
- package/native-host/src/providerReasoning.js +2 -7
- package/native-host/src/providerRuntime.js +1 -0
- package/native-host/src/providerStore.js +72 -14
- package/native-host/src/responsesPassthroughBridge.js +266 -52
- package/native-host/src/runtimeInstaller.js +21 -1
- package/native-host/src/selectionScope.js +36 -8
- package/native-host/src/subagentAdmission.js +26 -0
- package/native-host/src/subagentBroker.js +79 -66
- package/native-host/src/subagentTelemetry.js +19 -10
- package/native-host/src/subagentWorkspacePath.js +5 -2
- package/native-host/src/taskRunnerRuntime.js +77 -54
- package/native-host/src/tomlStatements.js +140 -0
- package/native-host/src/updateManager.js +297 -39
- package/native-host/src/writingStyleRuntime.js +57 -29
- package/native-host/src/writingStyleStore.js +73 -0
- package/package.json +1 -1
- package/scripts/install-managed.mjs +1 -0
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// Native Host restarts and only permits work after positive exit evidence.
|
|
5
5
|
const fs = require('node:fs');
|
|
6
6
|
const path = require('node:path');
|
|
7
|
-
const { getProjectMirror } = require('./mirrorWorkspace');
|
|
7
|
+
const { getProjectMirror, getDefaultMirrorRoot } = require('./mirrorWorkspace');
|
|
8
8
|
const pending = new Map();
|
|
9
9
|
|
|
10
10
|
function location(projectId, rootDir) {
|
|
@@ -13,16 +13,23 @@ function location(projectId, rootDir) {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
function load(file) {
|
|
16
|
-
if (pending.has(file)) return pending.get(file);
|
|
17
16
|
let records = [];
|
|
18
17
|
try {
|
|
19
18
|
if (fs.statSync(file).size > 65536) throw new Error('Oversized stop barrier');
|
|
20
19
|
const value = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
21
20
|
if (!Array.isArray(value) || value.length > 64) throw new Error('Invalid stop barrier');
|
|
22
|
-
records = value;
|
|
21
|
+
records = value.map(record => record && typeof record === 'object' ? record : { unknown: true });
|
|
23
22
|
} catch (error) {
|
|
24
23
|
if (error.code !== 'ENOENT') records = [{ unknown: true }];
|
|
25
24
|
}
|
|
25
|
+
// Other Hosts can publish a barrier while this Host is already alive. Keep
|
|
26
|
+
// in-memory callbacks and failed writes, but never cache an empty disk read.
|
|
27
|
+
const key = ({ isStopped, ...record }) => JSON.stringify(record);
|
|
28
|
+
const merged = new Map(records.map(record => [key(record), record]));
|
|
29
|
+
for (const record of pending.get(file) || []) {
|
|
30
|
+
if (typeof record.isStopped === 'function' || record.persistenceFailed) merged.set(key(record), record);
|
|
31
|
+
}
|
|
32
|
+
records = Array.from(merged.values());
|
|
26
33
|
pending.set(file, records);
|
|
27
34
|
return records;
|
|
28
35
|
}
|
|
@@ -53,13 +60,17 @@ function stopped(record) {
|
|
|
53
60
|
function record(projectId, rootDir, error) {
|
|
54
61
|
const file = location(projectId, rootDir);
|
|
55
62
|
const records = load(file);
|
|
56
|
-
|
|
63
|
+
const entry = {
|
|
57
64
|
processId: error.processId, processGroup: error.processGroup === true,
|
|
58
65
|
platform: process.platform, createdAt: new Date().toISOString(),
|
|
59
66
|
isStopped: error.isStopped
|
|
60
|
-
}
|
|
67
|
+
};
|
|
68
|
+
records.push(entry);
|
|
61
69
|
try { persist(file, records); }
|
|
62
|
-
catch (failure) {
|
|
70
|
+
catch (failure) {
|
|
71
|
+
Object.defineProperty(entry, 'persistenceFailed', { value: true });
|
|
72
|
+
error.stopBarrierPersistenceError = failure.message;
|
|
73
|
+
}
|
|
63
74
|
}
|
|
64
75
|
|
|
65
76
|
function isBlocked(projectId, rootDir) {
|
|
@@ -76,5 +87,29 @@ function isBlocked(projectId, rootDir) {
|
|
|
76
87
|
return remaining.length > 0;
|
|
77
88
|
}
|
|
78
89
|
|
|
79
|
-
|
|
90
|
+
// Update safety observes the existing project quarantine. It neither searches
|
|
91
|
+
// unrelated OS processes nor mutates a project's recovery records.
|
|
92
|
+
function getBlockedProjectCount(rootDir, options = {}) {
|
|
93
|
+
const root = path.resolve(rootDir || getDefaultMirrorRoot(options));
|
|
94
|
+
const files = new Set();
|
|
95
|
+
let unknown = 0;
|
|
96
|
+
try {
|
|
97
|
+
for (const entry of fs.readdirSync(root, { withFileTypes: true })) {
|
|
98
|
+
if (entry.isDirectory()) files.add(path.join(root, entry.name, '.codex-stop-barrier.json'));
|
|
99
|
+
else if (entry.isSymbolicLink()) unknown += 1;
|
|
100
|
+
}
|
|
101
|
+
} catch (error) {
|
|
102
|
+
if (error.code !== 'ENOENT') unknown += 1;
|
|
103
|
+
}
|
|
104
|
+
// Include failed persistence owned by this Host, even if its directory was
|
|
105
|
+
// never created. A disk inventory alone cannot release that quarantine.
|
|
106
|
+
for (const file of pending.keys()) {
|
|
107
|
+
if (path.dirname(path.dirname(file)) === root) files.add(file);
|
|
108
|
+
}
|
|
109
|
+
for (const file of files) {
|
|
110
|
+
if (load(file).some(value => !stopped(value))) unknown += 1;
|
|
111
|
+
}
|
|
112
|
+
return unknown;
|
|
113
|
+
}
|
|
80
114
|
|
|
115
|
+
module.exports = { record, isBlocked, getBlockedProjectCount };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Canonical project names stay literal. Reject names the local filesystem would
|
|
4
|
+
// reinterpret instead of attempting to escape, trim or rename them.
|
|
5
|
+
function isRepresentableProjectPath(value, platform = process.platform) {
|
|
6
|
+
if (typeof value !== 'string' || !value) return false;
|
|
7
|
+
if (platform !== 'win32') return true;
|
|
8
|
+
return value.split('/').every(part => part && part !== '.' && part !== '..'
|
|
9
|
+
&& part.length <= 255 && !/[<>:"\\|?*\u0000-\u001f]/.test(part) && !/[ .]$/.test(part)
|
|
10
|
+
&& !/^(?:con|prn|aux|nul|conin\$|conout\$|com[1-9¹²³]|lpt[1-9¹²³])(?:\.|$)/i.test(part));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function assertProjectPathsForPlatform(paths, platform = process.platform) {
|
|
14
|
+
const identities = new Map();
|
|
15
|
+
for (const value of paths) {
|
|
16
|
+
let valid = isRepresentableProjectPath(value, platform);
|
|
17
|
+
if (valid && platform === 'win32') {
|
|
18
|
+
const parts = value.split('/');
|
|
19
|
+
for (let index = 1; index <= parts.length; index += 1) {
|
|
20
|
+
const literal = parts.slice(0, index).join('/'), key = literal.toLowerCase();
|
|
21
|
+
if (identities.has(key) && identities.get(key) !== literal) valid = false;
|
|
22
|
+
identities.set(key, literal);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (!valid) throw Object.assign(new Error(`Project path cannot be represented faithfully on ${platform}: ${value}`),
|
|
26
|
+
{ code: 'project_path_unrepresentable', path: value });
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
module.exports = { isRepresentableProjectPath, assertProjectPathsForPlatform };
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { providerError } = require('./providerProfile');
|
|
4
|
+
const REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);
|
|
5
|
+
const BODY_HEADERS = ['content-encoding', 'content-language', 'content-location', 'content-type', 'content-length'];
|
|
6
|
+
|
|
7
|
+
function carriesEndpointCredentials(headers, launch) {
|
|
8
|
+
const namedHeader = String(launch.apiKeyHeaderName || '').toLowerCase();
|
|
9
|
+
return [...headers].some(([name, value]) => value && (
|
|
10
|
+
name === namedHeader
|
|
11
|
+
|| /(?:^|[-_])(?:authorization|auth|api[-_]?key|token|secret|cookie|credential|password|signature)(?:$|[-_])/i.test(name)
|
|
12
|
+
|| launch.apiKey && value.includes(launch.apiKey)
|
|
13
|
+
));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Fetch's method/body redirect rules remain intact. Credentials configured for
|
|
17
|
+
// this endpoint never authorize a request to a different origin.
|
|
18
|
+
async function fetchProvider(url, options, launch = {}) {
|
|
19
|
+
let current = new URL(url);
|
|
20
|
+
let method = String(options.method || 'GET').toUpperCase();
|
|
21
|
+
let body = options.body;
|
|
22
|
+
const headers = new Headers(options.headers);
|
|
23
|
+
const credentialed = carriesEndpointCredentials(headers, launch);
|
|
24
|
+
for (let redirects = 0; ; redirects += 1) {
|
|
25
|
+
const response = await fetch(current, { ...options, method, body, headers, redirect: 'manual' });
|
|
26
|
+
const location = response.headers.get('location');
|
|
27
|
+
if (!REDIRECT_STATUSES.has(response.status) || location === null) return response;
|
|
28
|
+
await response.body?.cancel().catch(() => {});
|
|
29
|
+
if (redirects >= 20) throw providerError('provider_upstream_error', 'The provider exceeded the redirect limit.');
|
|
30
|
+
let next;
|
|
31
|
+
try { next = new URL(location, current); }
|
|
32
|
+
catch (_) { throw providerError('provider_upstream_error', 'The provider returned an invalid redirect.'); }
|
|
33
|
+
if (!['http:', 'https:'].includes(next.protocol) || next.username || next.password) {
|
|
34
|
+
throw providerError('provider_upstream_error', 'The provider returned an unsupported redirect.');
|
|
35
|
+
}
|
|
36
|
+
if (credentialed && next.origin !== current.origin) {
|
|
37
|
+
throw providerError('provider_upstream_error', 'The provider redirected to a different origin; endpoint credentials were not forwarded.');
|
|
38
|
+
}
|
|
39
|
+
if ((response.status === 301 || response.status === 302) && method === 'POST'
|
|
40
|
+
|| response.status === 303 && method !== 'GET' && method !== 'HEAD') {
|
|
41
|
+
method = 'GET';
|
|
42
|
+
body = undefined;
|
|
43
|
+
for (const name of BODY_HEADERS) headers.delete(name);
|
|
44
|
+
}
|
|
45
|
+
current = next;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Preserve ordinary Response.text()/json() capacity and UTF-8 semantics while
|
|
50
|
+
// reporting each body chunk to the existing idle lifecycle. Compact/SSE limits
|
|
51
|
+
// remain in their own readers; this does not introduce a new response cap.
|
|
52
|
+
async function readProviderText(response, onActivity = () => {}) {
|
|
53
|
+
if (!response.body) return '';
|
|
54
|
+
const reader = response.body.getReader();
|
|
55
|
+
const decoder = new TextDecoder();
|
|
56
|
+
const chunks = [];
|
|
57
|
+
let complete = false;
|
|
58
|
+
try {
|
|
59
|
+
while (true) {
|
|
60
|
+
const { done, value } = await reader.read();
|
|
61
|
+
if (done) break;
|
|
62
|
+
onActivity();
|
|
63
|
+
chunks.push(decoder.decode(value, { stream: true }));
|
|
64
|
+
}
|
|
65
|
+
complete = true;
|
|
66
|
+
chunks.push(decoder.decode());
|
|
67
|
+
return chunks.join('');
|
|
68
|
+
} finally {
|
|
69
|
+
if (!complete) await reader.cancel().catch(() => {});
|
|
70
|
+
reader.releaseLock();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function readProviderJson(response, onActivity) {
|
|
75
|
+
const text = await readProviderText(response, onActivity);
|
|
76
|
+
try { return JSON.parse(text); }
|
|
77
|
+
catch (_) { throw providerError('provider_response_invalid', 'The provider returned an invalid JSON response.'); }
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports = { fetchProvider, readProviderText, readProviderJson };
|
|
@@ -65,7 +65,8 @@ function normalizeProviderDraft(value = {}) {
|
|
|
65
65
|
anthropicThinkingMode: normalizeAnthropicThinkingMode(value.anthropicThinkingMode),
|
|
66
66
|
anthropicPromptCaching: value.anthropicPromptCaching === true,
|
|
67
67
|
impersonateClaudeCode: value.impersonateClaudeCode === true,
|
|
68
|
-
maxOutputTokens: normalizeMaxOutputTokens(value.maxOutputTokens)
|
|
68
|
+
maxOutputTokens: normalizeMaxOutputTokens(value.maxOutputTokens),
|
|
69
|
+
outputBudgetVersion: 1
|
|
69
70
|
};
|
|
70
71
|
}
|
|
71
72
|
|
|
@@ -245,6 +246,7 @@ function sanitizeProfile(profile, hasSecret) {
|
|
|
245
246
|
anthropicPromptCaching: profile.anthropicPromptCaching === true,
|
|
246
247
|
impersonateClaudeCode: profile.impersonateClaudeCode === true,
|
|
247
248
|
maxOutputTokens: normalizeMaxOutputTokens(profile.maxOutputTokens),
|
|
249
|
+
outputBudgetVersion: 1,
|
|
248
250
|
hasSecret: Boolean(hasSecret),
|
|
249
251
|
secretUpdatedAt: profile.secretUpdatedAt || 0,
|
|
250
252
|
endpointDisclosureHost: profile.endpointDisclosureHost || '',
|
|
@@ -419,7 +421,7 @@ function normalizeUpstreamResponseMode(value, allowAuto) {
|
|
|
419
421
|
|
|
420
422
|
function normalizeMaxOutputTokens(value) {
|
|
421
423
|
const number = Number(value);
|
|
422
|
-
if (!Number.isFinite(number)
|
|
424
|
+
if (!Number.isFinite(number)) return 65536;
|
|
423
425
|
return Math.min(65536, Math.max(256, Math.floor(number)));
|
|
424
426
|
}
|
|
425
427
|
|
|
@@ -13,7 +13,6 @@ const REASONING_ADAPTERS = new Set([
|
|
|
13
13
|
]);
|
|
14
14
|
const REASONING_CAPABILITIES = new Set(['auto', 'none', 'toggle', 'effort']);
|
|
15
15
|
const REASONING_EFFORTS = new Set(['none', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max']);
|
|
16
|
-
const DEEPSEEK_MAX_REASONING_OUTPUT_TOKENS = 65536;
|
|
17
16
|
|
|
18
17
|
function normalizeReasoningAdapter(value) {
|
|
19
18
|
const normalized = String(value || '').trim().toLowerCase();
|
|
@@ -165,12 +164,8 @@ function resolveReasoningMaxOutputTokens(value, responsesRequest = {}, launch =
|
|
|
165
164
|
if (!Number.isFinite(number)) {
|
|
166
165
|
return value;
|
|
167
166
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
const adapter = resolveReasoningAdapter(launch, modelId || launch.modelId);
|
|
171
|
-
return adapter === 'deepseek' && ['xhigh', 'max'].includes(effort)
|
|
172
|
-
? Math.max(normalized, DEEPSEEK_MAX_REASONING_OUTPUT_TOKENS)
|
|
173
|
-
: normalized;
|
|
167
|
+
// Reasoning effort cannot silently increase the configured output maximum.
|
|
168
|
+
return Math.max(1, Math.floor(number));
|
|
174
169
|
}
|
|
175
170
|
|
|
176
171
|
function requiresReasoningContentReplay(launch = {}, modelId = '') {
|
|
@@ -92,6 +92,7 @@ async function testProvider(params, env, emit = () => {}, externalSignal = null)
|
|
|
92
92
|
}, totalBudgetMs);
|
|
93
93
|
const onExternalAbort = () => controller.abort();
|
|
94
94
|
externalSignal?.addEventListener?.('abort', onExternalAbort, { once: true });
|
|
95
|
+
if (externalSignal?.aborted) onExternalAbort();
|
|
95
96
|
activeTests.get(operationId)?.abort?.();
|
|
96
97
|
activeTests.set(operationId, controller);
|
|
97
98
|
const protocols = draft.wireApiPreference === 'auto'
|
|
@@ -4,6 +4,7 @@ const crypto = require('node:crypto');
|
|
|
4
4
|
const fs = require('node:fs');
|
|
5
5
|
const os = require('node:os');
|
|
6
6
|
const path = require('node:path');
|
|
7
|
+
const { isDeepStrictEqual } = require('node:util');
|
|
7
8
|
const {
|
|
8
9
|
computeConnectionFingerprint,
|
|
9
10
|
computeDraftFingerprint,
|
|
@@ -16,6 +17,9 @@ const {
|
|
|
16
17
|
} = require('./providerProfile');
|
|
17
18
|
|
|
18
19
|
const SCHEMA_VERSION = 1;
|
|
20
|
+
const STORE_FILE_MAX_BYTES = 1024 * 1024;
|
|
21
|
+
// A compact journal contains two bounded Store files plus revision/schema keys.
|
|
22
|
+
const STORE_TRANSACTION_MAX_BYTES = 2 * STORE_FILE_MAX_BYTES + 1024;
|
|
19
23
|
const STORE_LOCK_TIMEOUT_MS = 2500;
|
|
20
24
|
const STORE_LOCK_STALE_MS = 30000;
|
|
21
25
|
const STORE_LOCK_RETRY_MS = 25;
|
|
@@ -357,6 +361,7 @@ function loadProviderStateUnlocked(env = process.env) {
|
|
|
357
361
|
: [];
|
|
358
362
|
return {
|
|
359
363
|
paths,
|
|
364
|
+
stored: { public: publicStore, secrets: secretStore },
|
|
360
365
|
public: {
|
|
361
366
|
schemaVersion: SCHEMA_VERSION,
|
|
362
367
|
storeRevision: normalizeRevision(publicStore.storeRevision),
|
|
@@ -373,23 +378,35 @@ function loadProviderStateUnlocked(env = process.env) {
|
|
|
373
378
|
|
|
374
379
|
function commitProviderState(previous, next) {
|
|
375
380
|
const revision = previous.public.storeRevision + 1;
|
|
376
|
-
const
|
|
377
|
-
const
|
|
381
|
+
const previousPublic = previous.stored.public;
|
|
382
|
+
const previousSecrets = previous.stored.secrets;
|
|
383
|
+
const nextPublic = { ...mergeStoredChanges(previousPublic, previous.public, next.public), schemaVersion: SCHEMA_VERSION, storeRevision: revision };
|
|
384
|
+
const nextSecrets = { ...mergeStoredChanges(previousSecrets, previous.secrets, next.secrets), schemaVersion: SCHEMA_VERSION, storeRevision: revision };
|
|
385
|
+
const compactPublic = Buffer.byteLength(`${JSON.stringify(previousPublic, null, 2)}\n`) > STORE_FILE_MAX_BYTES;
|
|
386
|
+
const compactSecrets = Buffer.byteLength(`${JSON.stringify(previousSecrets, null, 2)}\n`) > STORE_FILE_MAX_BYTES;
|
|
378
387
|
const journal = {
|
|
379
388
|
schemaVersion: SCHEMA_VERSION,
|
|
380
389
|
nextRevision: revision,
|
|
381
|
-
previousPublic
|
|
382
|
-
previousSecrets
|
|
390
|
+
previousPublic,
|
|
391
|
+
previousSecrets,
|
|
392
|
+
compactPublic,
|
|
393
|
+
compactSecrets
|
|
383
394
|
};
|
|
395
|
+
// Validate every write, including rollback copies, before replacing any file.
|
|
396
|
+
serializeStoreJson(previous.paths.publicFile, nextPublic, compactPublic);
|
|
397
|
+
serializeStoreJson(previous.paths.secretFile, nextSecrets, compactSecrets);
|
|
398
|
+
serializeStoreJson(previous.paths.publicFile, previousPublic, compactPublic);
|
|
399
|
+
serializeStoreJson(previous.paths.secretFile, previousSecrets, compactSecrets);
|
|
400
|
+
serializeStoreJson(previous.paths.transactionFile, journal);
|
|
384
401
|
atomicWriteJson(previous.paths.transactionFile, journal);
|
|
385
402
|
try {
|
|
386
|
-
atomicWriteJson(previous.paths.secretFile, nextSecrets);
|
|
387
|
-
atomicWriteJson(previous.paths.publicFile, nextPublic);
|
|
403
|
+
atomicWriteJson(previous.paths.secretFile, nextSecrets, compactSecrets);
|
|
404
|
+
atomicWriteJson(previous.paths.publicFile, nextPublic, compactPublic);
|
|
388
405
|
fs.rmSync(previous.paths.transactionFile, { force: true });
|
|
389
406
|
} catch (error) {
|
|
390
407
|
try {
|
|
391
|
-
atomicWriteJson(previous.paths.secretFile,
|
|
392
|
-
atomicWriteJson(previous.paths.publicFile,
|
|
408
|
+
atomicWriteJson(previous.paths.secretFile, previousSecrets, compactSecrets);
|
|
409
|
+
atomicWriteJson(previous.paths.publicFile, previousPublic, compactPublic);
|
|
393
410
|
fs.rmSync(previous.paths.transactionFile, { force: true });
|
|
394
411
|
} catch (_restoreError) {
|
|
395
412
|
// The journal remains the recovery source on the next Native Host start.
|
|
@@ -398,6 +415,26 @@ function commitProviderState(previous, next) {
|
|
|
398
415
|
}
|
|
399
416
|
}
|
|
400
417
|
|
|
418
|
+
function mergeStoredChanges(stored, before, after) {
|
|
419
|
+
if (isDeepStrictEqual(before, after)) return stored;
|
|
420
|
+
if (!stored || !before || !after || typeof stored !== 'object' || typeof before !== 'object' || typeof after !== 'object') return after;
|
|
421
|
+
if (Array.isArray(before) || Array.isArray(after)) {
|
|
422
|
+
// Profiles and models carry stable IDs; changed membership must not persist
|
|
423
|
+
// normalization defaults into unrelated records at the storage boundary.
|
|
424
|
+
if (!Array.isArray(stored) || !Array.isArray(before) || !Array.isArray(after)
|
|
425
|
+
|| [...before, ...after].some(item => !item?.id)) return after;
|
|
426
|
+
const oldById = new Map(before.map(item => [item.id, item]));
|
|
427
|
+
const storedById = new Map(stored.map(item => [typeof item === 'string' ? item : item?.id, item]));
|
|
428
|
+
return after.map(item => mergeStoredChanges(storedById.get(item.id), oldById.get(item.id), item));
|
|
429
|
+
}
|
|
430
|
+
const result = { ...stored };
|
|
431
|
+
for (const key of new Set([...Object.keys(before), ...Object.keys(after)])) {
|
|
432
|
+
if (!Object.prototype.hasOwnProperty.call(after, key)) delete result[key];
|
|
433
|
+
else if (!isDeepStrictEqual(before[key], after[key])) result[key] = mergeStoredChanges(stored[key], before[key], after[key]);
|
|
434
|
+
}
|
|
435
|
+
return result;
|
|
436
|
+
}
|
|
437
|
+
|
|
401
438
|
function recoverInterruptedTransaction(paths) {
|
|
402
439
|
if (!fs.existsSync(paths.transactionFile)) {
|
|
403
440
|
return;
|
|
@@ -412,8 +449,10 @@ function recoverInterruptedTransaction(paths) {
|
|
|
412
449
|
fs.rmSync(paths.transactionFile, { force: true });
|
|
413
450
|
return;
|
|
414
451
|
}
|
|
415
|
-
|
|
416
|
-
|
|
452
|
+
serializeStoreJson(paths.secretFile, journal.previousSecrets, journal.compactSecrets === true);
|
|
453
|
+
serializeStoreJson(paths.publicFile, journal.previousPublic, journal.compactPublic === true);
|
|
454
|
+
atomicWriteJson(paths.secretFile, journal.previousSecrets, journal.compactSecrets === true);
|
|
455
|
+
atomicWriteJson(paths.publicFile, journal.previousPublic, journal.compactPublic === true);
|
|
417
456
|
fs.rmSync(paths.transactionFile, { force: true });
|
|
418
457
|
}
|
|
419
458
|
|
|
@@ -502,11 +541,16 @@ function normalizeStoredProfile(value) {
|
|
|
502
541
|
if (!value || typeof value !== 'object' || !normalizeId(value.id)) {
|
|
503
542
|
throw providerError('provider_store_corrupt', 'Provider store contains an invalid profile.');
|
|
504
543
|
}
|
|
505
|
-
const
|
|
544
|
+
const legacyOutputBudget = value.outputBudgetVersion !== 1
|
|
545
|
+
&& [8192, 32768].includes(Number(value.maxOutputTokens));
|
|
546
|
+
const normalized = normalizeProviderDraft(legacyOutputBudget ? { ...value, maxOutputTokens: 65536 } : value);
|
|
506
547
|
return {
|
|
507
548
|
id: normalizeId(value.id),
|
|
508
549
|
revision: Math.max(1, normalizeRevision(value.revision)),
|
|
509
550
|
...normalized,
|
|
551
|
+
// Keep the old provenance until an explicit upsert persists the new value.
|
|
552
|
+
// Unrelated mutations must not mark an unmigrated raw record as current.
|
|
553
|
+
outputBudgetVersion: value.outputBudgetVersion === 1 ? 1 : 0,
|
|
510
554
|
resolvedWireApi: isResolvedWireApi(value.resolvedWireApi) ? value.resolvedWireApi : '',
|
|
511
555
|
endpointDisclosureHost: String(value.endpointDisclosureHost || ''),
|
|
512
556
|
endpointDisclosureBaseUrl: String(value.endpointDisclosureBaseUrl || ''),
|
|
@@ -634,11 +678,12 @@ function ensurePrivateDirectory(directory) {
|
|
|
634
678
|
}
|
|
635
679
|
}
|
|
636
680
|
|
|
637
|
-
function atomicWriteJson(target, value) {
|
|
681
|
+
function atomicWriteJson(target, value, compact = false) {
|
|
682
|
+
const serialized = serializeStoreJson(target, value, compact);
|
|
638
683
|
const temp = `${target}.tmp-${process.pid}-${Date.now()}-${crypto.randomBytes(4).toString('hex')}`;
|
|
639
684
|
const fd = fs.openSync(temp, 'w', 0o600);
|
|
640
685
|
try {
|
|
641
|
-
fs.writeFileSync(fd,
|
|
686
|
+
fs.writeFileSync(fd, serialized, 'utf8');
|
|
642
687
|
fs.fsyncSync(fd);
|
|
643
688
|
} finally {
|
|
644
689
|
fs.closeSync(fd);
|
|
@@ -649,13 +694,26 @@ function atomicWriteJson(target, value) {
|
|
|
649
694
|
fs.renameSync(temp, target);
|
|
650
695
|
}
|
|
651
696
|
|
|
697
|
+
function getStoreFileMaxBytes(target) {
|
|
698
|
+
return path.basename(target) === 'provider-transaction.json' ? STORE_TRANSACTION_MAX_BYTES : STORE_FILE_MAX_BYTES;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
function serializeStoreJson(target, value, compact = false) {
|
|
702
|
+
const serialized = `${JSON.stringify(value, null, compact || path.basename(target) === 'provider-transaction.json' ? undefined : 2)}\n`;
|
|
703
|
+
const maxBytes = getStoreFileMaxBytes(target);
|
|
704
|
+
if (Buffer.byteLength(serialized, 'utf8') > maxBytes) {
|
|
705
|
+
throw providerError('provider_store_too_large', `Provider settings exceed the ${maxBytes}-byte limit for ${path.basename(target)}. Reduce the configuration and retry.`);
|
|
706
|
+
}
|
|
707
|
+
return serialized;
|
|
708
|
+
}
|
|
709
|
+
|
|
652
710
|
function readJson(target, fallback) {
|
|
653
711
|
if (!fs.existsSync(target)) {
|
|
654
712
|
return fallback;
|
|
655
713
|
}
|
|
656
714
|
try {
|
|
657
715
|
const stat = fs.statSync(target);
|
|
658
|
-
if (!stat.isFile() || stat.size >
|
|
716
|
+
if (!stat.isFile() || stat.size > getStoreFileMaxBytes(target)) {
|
|
659
717
|
throw new Error('invalid provider store file');
|
|
660
718
|
}
|
|
661
719
|
return JSON.parse(fs.readFileSync(target, 'utf8'));
|