@ailuntz/minilink 0.9.13 → 0.9.15
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/examples/providers/README.md +2 -0
- package/package.json +1 -1
- package/src/history.mjs +28 -22
|
@@ -10,4 +10,6 @@
|
|
|
10
10
|
|
|
11
11
|
MiniLink 的 Chat 模式仅支持文字。
|
|
12
12
|
|
|
13
|
+
跨供应商切换会保留可见对话,并移除不能由目标供应商复用的推理项(包括其加密内容和 ID)。无需在这些模型目录中设置 `store`。模型目录的 `context_window` 定义上限;Desktop 显示的“已用”取自模型响应的用量记录,不同供应商的数字不宜直接当作同一把尺子比较。
|
|
14
|
+
|
|
13
15
|
模型核对:2026-09-24。[DeepSeek](https://api-docs.deepseek.com/quick_start/pricing/) · [豆包](https://docs.volcengine.com/docs/ark/model-list?lang=zh)
|
package/package.json
CHANGED
package/src/history.mjs
CHANGED
|
@@ -21,11 +21,12 @@ function checkedPath(path, home, threadId) {
|
|
|
21
21
|
return true;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
|
|
24
|
+
// A reasoning item from another provider cannot be replayed safely: its
|
|
25
|
+
// content can fail validation, its ciphertext cannot be decrypted, and its ID
|
|
26
|
+
// may refer to a response that was never stored. Replace its journal record
|
|
27
|
+
// with a harmless event of the same byte length and remove the indexed item.
|
|
28
|
+
// Keep private backups so a failed switch can restore both stores.
|
|
29
|
+
export function repairThreadHistory(path, { threadId, home = codexHome(), backupDir = defaultBackups(home), onlyOrphaned = false } = {}) {
|
|
29
30
|
if (!existsSync(path)) return null;
|
|
30
31
|
if (!/^[0-9a-f-]{36}$/.test(threadId || '')) throw new Error('任务 ID 无效,无法备份历史。');
|
|
31
32
|
if (!checkedPath(path, home, threadId)) return null;
|
|
@@ -39,10 +40,8 @@ export function repairThreadHistory(path, { threadId, home = codexHome(), backup
|
|
|
39
40
|
let record;
|
|
40
41
|
try { record = JSON.parse(original.toString('utf8')); }
|
|
41
42
|
catch { throw new Error('任务历史包含无法解析的记录,未修改原文件。'); }
|
|
42
|
-
if (record.type === 'response_item' && record.payload?.type === 'reasoning' && (record.payload.content?.length
|
|
43
|
-
record.payload
|
|
44
|
-
delete record.payload.encrypted_content;
|
|
45
|
-
const compact = Buffer.from(JSON.stringify(record));
|
|
43
|
+
if (record.type === 'response_item' && record.payload?.type === 'reasoning' && (!onlyOrphaned || (!record.payload.content?.length && !record.payload.encrypted_content))) {
|
|
44
|
+
const compact = Buffer.from(JSON.stringify({ timestamp: record.timestamp, type: 'event_msg', payload: { type: 'token_count', info: null, rate_limits: null } }));
|
|
46
45
|
if (compact.length > original.length) throw new Error('任务历史记录无法等长修复,未修改原文件。');
|
|
47
46
|
const replacement = Buffer.alloc(original.length, 32);
|
|
48
47
|
compact.copy(replacement);
|
|
@@ -56,12 +55,10 @@ export function repairThreadHistory(path, { threadId, home = codexHome(), backup
|
|
|
56
55
|
const database = existsSync(databasePath) ? new DatabaseSync(databasePath) : null;
|
|
57
56
|
let rows = [];
|
|
58
57
|
try {
|
|
59
|
-
if (database) rows = database.prepare("SELECT rowid,
|
|
58
|
+
if (database) rows = database.prepare("SELECT rowid, * FROM thread_items WHERE thread_id = ? AND item_type = 'reasoning'").all(threadId).filter(row => {
|
|
59
|
+
if (!onlyOrphaned) return true;
|
|
60
60
|
const item = JSON.parse(row.item_json);
|
|
61
|
-
|
|
62
|
-
item.content = [];
|
|
63
|
-
delete item.encrypted_content;
|
|
64
|
-
return [{ rowid: row.rowid, original: row.item_json, replacement: JSON.stringify(item) }];
|
|
61
|
+
return !item.content?.length && !item.encrypted_content;
|
|
65
62
|
});
|
|
66
63
|
if (!patches.length && !rows.length) return null;
|
|
67
64
|
mkdirSync(backupDir, { recursive: true, mode: 0o700 });
|
|
@@ -70,21 +67,30 @@ export function repairThreadHistory(path, { threadId, home = codexHome(), backup
|
|
|
70
67
|
const databaseBackup = join(backupDir, `${stem}.sqlite-reasoning.json`);
|
|
71
68
|
copyFileSync(path, backup, constants.COPYFILE_EXCL);
|
|
72
69
|
chmodSync(backup, 0o600);
|
|
73
|
-
writeFileSync(databaseBackup, JSON.stringify({ databasePath, rows
|
|
70
|
+
writeFileSync(databaseBackup, JSON.stringify({ databasePath, rows }), { mode: 0o600, flag: 'wx' });
|
|
74
71
|
const mode = lstatSync(path).mode & 0o777;
|
|
75
|
-
const updateRows =
|
|
76
|
-
if (!existsSync(databasePath) || !
|
|
72
|
+
const updateRows = restore => {
|
|
73
|
+
if (!existsSync(databasePath) || !rows.length) return;
|
|
77
74
|
const connection = new DatabaseSync(databasePath);
|
|
78
75
|
connection.exec('BEGIN IMMEDIATE');
|
|
79
76
|
try {
|
|
80
|
-
|
|
81
|
-
|
|
77
|
+
if (restore) {
|
|
78
|
+
for (const row of rows) {
|
|
79
|
+
const keys = Object.keys(row);
|
|
80
|
+
const columns = keys.map(key => `"${key.replaceAll('"', '""')}"`).join(', ');
|
|
81
|
+
connection.prepare(`INSERT INTO thread_items (${columns}) VALUES (${keys.map(() => '?').join(', ')})`).run(...keys.map(key => row[key]));
|
|
82
|
+
}
|
|
83
|
+
} else {
|
|
84
|
+
const remove = connection.prepare('DELETE FROM thread_items WHERE rowid = ?');
|
|
85
|
+
for (const row of rows) remove.run(row.rowid);
|
|
86
|
+
}
|
|
82
87
|
connection.exec('COMMIT');
|
|
83
88
|
} catch (error) { connection.exec('ROLLBACK'); throw error; }
|
|
84
89
|
finally { connection.close(); }
|
|
85
90
|
};
|
|
86
|
-
|
|
87
|
-
|
|
91
|
+
let removed = false;
|
|
92
|
+
try { updateRows(false); removed = true; atomicBytes(path, after, mode); }
|
|
93
|
+
catch (error) { if (removed) updateRows(true); throw error; }
|
|
88
94
|
return {
|
|
89
95
|
backup, databaseBackup,
|
|
90
96
|
count: Math.max(patches.length, rows.length),
|
|
@@ -94,7 +100,7 @@ export function repairThreadHistory(path, { threadId, home = codexHome(), backup
|
|
|
94
100
|
if (!current.subarray(patch.start, patch.start + patch.replacement.length).equals(patch.replacement)) throw new Error('任务历史已被其他进程修改,请使用备份恢复。');
|
|
95
101
|
patch.original.copy(restored, patch.start);
|
|
96
102
|
}
|
|
97
|
-
updateRows(
|
|
103
|
+
updateRows(true);
|
|
98
104
|
atomicBytes(path, restored, mode);
|
|
99
105
|
}
|
|
100
106
|
};
|