@ailuntz/minilink 0.9.12 → 0.9.14

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.
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ailuntz/minilink",
3
- "version": "0.9.12",
3
+ "version": "0.9.14",
4
4
  "description": "Use your Codex profiles in the Desktop model menu.",
5
5
  "type": "module",
6
6
  "license": "MIT AND Apache-2.0",
package/src/history.mjs CHANGED
@@ -21,10 +21,11 @@ function checkedPath(path, home, threadId) {
21
21
  return true;
22
22
  }
23
23
 
24
- // Native Codex replays provider reasoning items from its JSONL journal. Some
25
- // Responses-compatible providers persist reasoning_text and opaque encrypted
26
- // content that another provider rejects. Normalize only those items, keeping
27
- // every line's byte length so the native journal offsets remain valid.
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.
28
29
  export function repairThreadHistory(path, { threadId, home = codexHome(), backupDir = defaultBackups(home) } = {}) {
29
30
  if (!existsSync(path)) return null;
30
31
  if (!/^[0-9a-f-]{36}$/.test(threadId || '')) throw new Error('任务 ID 无效,无法备份历史。');
@@ -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' && Array.isArray(record.payload.content) && record.payload.content.length) {
43
- record.payload.content = [];
44
- delete record.payload.encrypted_content;
45
- const compact = Buffer.from(JSON.stringify(record));
43
+ if (record.type === 'response_item' && record.payload?.type === 'reasoning') {
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,13 +55,7 @@ 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, item_json FROM thread_items WHERE thread_id = ? AND item_type = 'reasoning'").all(threadId).flatMap(row => {
60
- const item = JSON.parse(row.item_json);
61
- if (!Array.isArray(item.content) || !item.content.length) return [];
62
- item.content = [];
63
- delete item.encrypted_content;
64
- return [{ rowid: row.rowid, original: row.item_json, replacement: JSON.stringify(item) }];
65
- });
58
+ if (database) rows = database.prepare("SELECT rowid, * FROM thread_items WHERE thread_id = ? AND item_type = 'reasoning'").all(threadId);
66
59
  if (!patches.length && !rows.length) return null;
67
60
  mkdirSync(backupDir, { recursive: true, mode: 0o700 });
68
61
  const stem = `${threadId}-${randomUUID()}`;
@@ -70,21 +63,30 @@ export function repairThreadHistory(path, { threadId, home = codexHome(), backup
70
63
  const databaseBackup = join(backupDir, `${stem}.sqlite-reasoning.json`);
71
64
  copyFileSync(path, backup, constants.COPYFILE_EXCL);
72
65
  chmodSync(backup, 0o600);
73
- writeFileSync(databaseBackup, JSON.stringify({ databasePath, rows: rows.map(({ rowid, original }) => ({ rowid, original })) }), { mode: 0o600, flag: 'wx' });
66
+ writeFileSync(databaseBackup, JSON.stringify({ databasePath, rows }), { mode: 0o600, flag: 'wx' });
74
67
  const mode = lstatSync(path).mode & 0o777;
75
- const updateRows = values => {
76
- if (!existsSync(databasePath) || !values.length) return;
68
+ const updateRows = restore => {
69
+ if (!existsSync(databasePath) || !rows.length) return;
77
70
  const connection = new DatabaseSync(databasePath);
78
71
  connection.exec('BEGIN IMMEDIATE');
79
72
  try {
80
- const update = connection.prepare('UPDATE thread_items SET item_json = ? WHERE rowid = ?');
81
- for (const row of values) update.run(row.replacement ?? row.original, row.rowid);
73
+ if (restore) {
74
+ for (const row of rows) {
75
+ const keys = Object.keys(row);
76
+ const columns = keys.map(key => `"${key.replaceAll('"', '""')}"`).join(', ');
77
+ connection.prepare(`INSERT INTO thread_items (${columns}) VALUES (${keys.map(() => '?').join(', ')})`).run(...keys.map(key => row[key]));
78
+ }
79
+ } else {
80
+ const remove = connection.prepare('DELETE FROM thread_items WHERE rowid = ?');
81
+ for (const row of rows) remove.run(row.rowid);
82
+ }
82
83
  connection.exec('COMMIT');
83
84
  } catch (error) { connection.exec('ROLLBACK'); throw error; }
84
85
  finally { connection.close(); }
85
86
  };
86
- try { updateRows(rows); atomicBytes(path, after, mode); }
87
- catch (error) { updateRows(rows.map(row => ({ rowid: row.rowid, original: row.original }))); throw error; }
87
+ let removed = false;
88
+ try { updateRows(false); removed = true; atomicBytes(path, after, mode); }
89
+ catch (error) { if (removed) updateRows(true); throw error; }
88
90
  return {
89
91
  backup, databaseBackup,
90
92
  count: Math.max(patches.length, rows.length),
@@ -94,7 +96,7 @@ export function repairThreadHistory(path, { threadId, home = codexHome(), backup
94
96
  if (!current.subarray(patch.start, patch.start + patch.replacement.length).equals(patch.replacement)) throw new Error('任务历史已被其他进程修改,请使用备份恢复。');
95
97
  patch.original.copy(restored, patch.start);
96
98
  }
97
- updateRows(rows.map(row => ({ rowid: row.rowid, original: row.original })));
99
+ updateRows(true);
98
100
  atomicBytes(path, restored, mode);
99
101
  }
100
102
  };