@galaxy-yearn/codex-deepseek-gateway 0.1.7 → 0.2.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.
@@ -0,0 +1,235 @@
1
+ import { appendFileSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
2
+ import { dirname } from 'node:path';
3
+ import { isObject, safeJsonParse } from './common.js';
4
+
5
+ const CACHE_VERSION = 1;
6
+ const DEFAULT_MAX_MESSAGES = 1000;
7
+ const DEFAULT_MAX_BYTES = 16 * 1024 * 1024;
8
+
9
+ export class ReasoningCache {
10
+ constructor({
11
+ maxMessages = DEFAULT_MAX_MESSAGES,
12
+ maxBytes = DEFAULT_MAX_BYTES,
13
+ persistPath = '',
14
+ legacyPath = '',
15
+ } = {}) {
16
+ this.map = new Map();
17
+ this.maxMessages = positiveInteger(maxMessages, DEFAULT_MAX_MESSAGES);
18
+ this.maxBytes = positiveInteger(maxBytes, DEFAULT_MAX_BYTES);
19
+ this.persistPath = persistPath || '';
20
+ this.legacyPath = legacyPath || '';
21
+ this.fileBytes = 0;
22
+ this.lastLoadError = null;
23
+ this.lastPersistError = null;
24
+ this.loadFromDisk();
25
+ if (this.persistPath && !existsSync(this.persistPath) && this.legacyPath && existsSync(this.legacyPath)) {
26
+ this.migrateLegacy();
27
+ }
28
+ }
29
+
30
+ rememberAssistantMessage(message) {
31
+ const shared = cloneAssistantMessage(message);
32
+ const callIds = toolCallIds(shared);
33
+ if (!callIds.length || callIds.every((callId) => sameJson(this.map.get(callId), shared))) return false;
34
+ this.indexMessage(callIds, shared);
35
+ this.persist(callIds, shared);
36
+ return true;
37
+ }
38
+
39
+ getAssistantMessageForToolCall(callId) {
40
+ if (!callId) return null;
41
+ const message = this.map.get(String(callId));
42
+ return message ? cloneAssistantMessage(message) : null;
43
+ }
44
+
45
+ loadFromDisk() {
46
+ if (!this.persistPath || !existsSync(this.persistPath)) return;
47
+ try {
48
+ const raw = readFileSync(this.persistPath, 'utf8');
49
+ const complete = !raw || raw.endsWith('\n');
50
+ const content = complete ? raw : raw.slice(0, raw.lastIndexOf('\n') + 1);
51
+ for (const line of content.split('\n')) {
52
+ if (!line) continue;
53
+ const parsed = safeJsonParse(line);
54
+ if (!parsed.ok || !isObject(parsed.value) || parsed.value.version !== CACHE_VERSION) {
55
+ throw new Error('Invalid reasoning cache record');
56
+ }
57
+ const callIds = Array.isArray(parsed.value.callIds) ? parsed.value.callIds.map(String) : [];
58
+ if (!callIds.length || !isObject(parsed.value.message)) continue;
59
+ this.indexMessage(callIds, cloneAssistantMessage(parsed.value.message));
60
+ }
61
+ this.fileBytes = Buffer.byteLength(raw, 'utf8');
62
+ if (!complete || this.fileBytes > this.maxBytes) this.compact();
63
+ } catch (error) {
64
+ this.lastLoadError = error;
65
+ this.map.clear();
66
+ this.fileBytes = 0;
67
+ }
68
+ }
69
+
70
+ migrateLegacy() {
71
+ try {
72
+ const parsed = safeJsonParse(readFileSync(this.legacyPath, 'utf8').replace(/^/, ''));
73
+ if (!parsed.ok || !isObject(parsed.value)) return;
74
+ this.loadLegacyPayload(parsed.value);
75
+ const journalPath = `${this.legacyPath}.journal`;
76
+ if (existsSync(journalPath)) this.loadLegacyJournal(journalPath);
77
+ this.compact();
78
+ rmSync(this.legacyPath, { force: true });
79
+ rmSync(journalPath, { force: true });
80
+ this.lastPersistError = null;
81
+ } catch (error) {
82
+ this.lastLoadError = error;
83
+ this.map.clear();
84
+ this.fileBytes = 0;
85
+ }
86
+ }
87
+
88
+ loadLegacyPayload(payload) {
89
+ const pool = Array.isArray(payload.assistantMessages) ? payload.assistantMessages : [];
90
+ const stored = entriesFrom(payload.toolCallMessages);
91
+ for (const [callId, value] of stored) {
92
+ const message = resolveLegacyMessage(value, pool);
93
+ if (callId && message) this.indexMessage([String(callId)], cloneAssistantMessage(message));
94
+ }
95
+ if (stored.length) return;
96
+ for (const [, session] of entriesFrom(payload.sessions)) {
97
+ for (const message of Array.isArray(session?.messages) ? session.messages : legacyHistoryMessages(session?.history)) {
98
+ const callIds = toolCallIds(message);
99
+ if (callIds.length) this.indexMessage(callIds, cloneAssistantMessage(message));
100
+ }
101
+ }
102
+ }
103
+
104
+ loadLegacyJournal(path) {
105
+ const raw = readFileSync(path, 'utf8');
106
+ const content = raw.endsWith('\n') ? raw : raw.slice(0, raw.lastIndexOf('\n') + 1);
107
+ for (const line of content.split('\n')) {
108
+ if (!line) continue;
109
+ const parsed = safeJsonParse(line);
110
+ if (!parsed.ok || !isObject(parsed.value)) throw new Error('Invalid legacy session journal record');
111
+ if (parsed.value.clear === true) this.map.clear();
112
+ for (const message of Array.isArray(parsed.value.assistantMessages) ? parsed.value.assistantMessages : []) {
113
+ const callIds = toolCallIds(message);
114
+ if (callIds.length) this.indexMessage(callIds, cloneAssistantMessage(message));
115
+ }
116
+ for (const [, session] of entriesFrom(parsed.value.sessions)) {
117
+ for (const message of Array.isArray(session?.messages) ? session.messages : []) {
118
+ const callIds = toolCallIds(message);
119
+ if (callIds.length) this.indexMessage(callIds, cloneAssistantMessage(message));
120
+ }
121
+ }
122
+ }
123
+ }
124
+
125
+ indexMessage(callIds, message) {
126
+ for (const callId of callIds) {
127
+ const key = String(callId);
128
+ this.map.delete(key);
129
+ this.map.set(key, message);
130
+ while (this.map.size > this.maxMessages) this.map.delete(this.map.keys().next().value);
131
+ }
132
+ }
133
+
134
+ persist(callIds, message) {
135
+ if (!this.persistPath) return;
136
+ try {
137
+ mkdirSync(dirname(this.persistPath), { recursive: true });
138
+ const serialized = serializeRecord(callIds, message);
139
+ const recordBytes = Buffer.byteLength(serialized, 'utf8');
140
+ if (this.fileBytes + recordBytes > this.maxBytes) {
141
+ this.compact();
142
+ } else {
143
+ appendFileSync(this.persistPath, serialized, 'utf8');
144
+ this.fileBytes += recordBytes;
145
+ }
146
+ this.lastPersistError = null;
147
+ } catch (error) {
148
+ this.lastPersistError = error;
149
+ }
150
+ }
151
+
152
+ compact() {
153
+ let serialized = this.serialize();
154
+ while (Buffer.byteLength(serialized, 'utf8') > this.maxBytes && this.map.size) {
155
+ this.map.delete(this.map.keys().next().value);
156
+ serialized = this.serialize();
157
+ }
158
+ if (!serialized) {
159
+ rmSync(this.persistPath, { force: true });
160
+ this.fileBytes = 0;
161
+ return;
162
+ }
163
+ mkdirSync(dirname(this.persistPath), { recursive: true });
164
+ const tmpPath = `${this.persistPath}.tmp-${process.pid}-${Date.now()}`;
165
+ writeFileSync(tmpPath, serialized, 'utf8');
166
+ renameSync(tmpPath, this.persistPath);
167
+ this.fileBytes = Buffer.byteLength(serialized, 'utf8');
168
+ }
169
+
170
+ serialize() {
171
+ const groups = new Map();
172
+ for (const [callId, message] of this.map.entries()) {
173
+ const fingerprint = JSON.stringify(message);
174
+ if (!groups.has(fingerprint)) groups.set(fingerprint, { callIds: [], message });
175
+ groups.get(fingerprint).callIds.push(callId);
176
+ }
177
+ return Array.from(groups.values(), ({ callIds, message }) => serializeRecord(callIds, message)).join('');
178
+ }
179
+ }
180
+
181
+ function serializeRecord(callIds, message) {
182
+ return `${JSON.stringify({ version: CACHE_VERSION, callIds, message })}\n`;
183
+ }
184
+
185
+ function toolCallIds(message) {
186
+ if (message?.role !== 'assistant' || !Array.isArray(message.tool_calls)) return [];
187
+ return message.tool_calls.filter((toolCall) => toolCall?.id).map((toolCall) => String(toolCall.id));
188
+ }
189
+
190
+ function entriesFrom(value) {
191
+ if (Array.isArray(value)) return value;
192
+ if (isObject(value)) return Object.entries(value);
193
+ return [];
194
+ }
195
+
196
+ function resolveLegacyMessage(value, pool) {
197
+ if (isObject(value)) return value;
198
+ if (Number.isInteger(value) && value >= 0 && value < pool.length && isObject(pool[value])) return pool[value];
199
+ return null;
200
+ }
201
+
202
+ function legacyHistoryMessages(history) {
203
+ const turns = Array.isArray(history) ? history : [];
204
+ if (!turns.length) return [];
205
+ if (turns.every((turn) => Array.isArray(turn?.historyMessages))) return turns.at(-1).historyMessages;
206
+ const messages = [];
207
+ for (const turn of turns) {
208
+ if (Array.isArray(turn?.historyMessages)) messages.push(...turn.historyMessages);
209
+ else if (Array.isArray(turn?.inputMessages)) messages.push(...turn.inputMessages);
210
+ else if (Array.isArray(turn?.chatRequest?.messages)) messages.push(...turn.chatRequest.messages);
211
+ if (turn?.assistantMessage) messages.push(turn.assistantMessage);
212
+ }
213
+ return messages;
214
+ }
215
+
216
+ function positiveInteger(value, fallback) {
217
+ const number = Number(value);
218
+ return Number.isFinite(number) && number > 0 ? Math.floor(number) : fallback;
219
+ }
220
+
221
+ function sameJson(left, right) {
222
+ return left !== undefined && JSON.stringify(left) === JSON.stringify(right);
223
+ }
224
+
225
+ function cloneAssistantMessage(message) {
226
+ return {
227
+ ...message,
228
+ tool_calls: Array.isArray(message?.tool_calls)
229
+ ? message.tool_calls.map((toolCall) => ({
230
+ ...toolCall,
231
+ function: toolCall.function ? { ...toolCall.function } : toolCall.function,
232
+ }))
233
+ : undefined,
234
+ };
235
+ }