@galaxy-yearn/codex-deepseek-gateway 0.2.0 → 0.2.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 +19 -10
- package/README.zh-CN.md +220 -0
- package/config/codex-model-catalog.json +18 -8
- package/config/codex-model-catalog.zh.json +22 -12
- package/package.json +2 -2
- package/src/codex-launch.js +30 -7
- package/src/codex-sessions.js +69 -13
- package/src/config.js +6 -5
- package/src/firecrawl.js +8 -3
- package/src/protocol.js +127 -155
- package/src/reasoning-cache.js +235 -0
- package/src/server.js +184 -129
- package/src/web-search-emulator.js +180 -106
- package/state/reasoning-cache.example.jsonl +1 -0
- package/src/session-store.js +0 -282
- package/state/sessions.example.json +0 -72
package/src/session-store.js
DELETED
|
@@ -1,282 +0,0 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
2
|
-
import { dirname } from 'node:path';
|
|
3
|
-
import { isObject, safeJsonParse } from './common.js';
|
|
4
|
-
|
|
5
|
-
const SESSION_STORE_VERSION = 4;
|
|
6
|
-
const DEFAULT_MAX_SESSIONS = 500;
|
|
7
|
-
const DEFAULT_MAX_BYTES = 16 * 1024 * 1024;
|
|
8
|
-
|
|
9
|
-
export class SessionStore {
|
|
10
|
-
constructor({
|
|
11
|
-
maxToolCallMessages = 1000,
|
|
12
|
-
maxSessions = DEFAULT_MAX_SESSIONS,
|
|
13
|
-
maxBytes = DEFAULT_MAX_BYTES,
|
|
14
|
-
persistPath = '',
|
|
15
|
-
} = {}) {
|
|
16
|
-
this.map = new Map();
|
|
17
|
-
this.toolCallMessages = new Map();
|
|
18
|
-
this.conversations = new Map();
|
|
19
|
-
this.maxToolCallMessages = positiveInteger(maxToolCallMessages, 1000);
|
|
20
|
-
this.maxSessions = positiveInteger(maxSessions, DEFAULT_MAX_SESSIONS);
|
|
21
|
-
this.maxBytes = positiveInteger(maxBytes, DEFAULT_MAX_BYTES);
|
|
22
|
-
this.persistPath = persistPath || '';
|
|
23
|
-
this.lastLoadError = null;
|
|
24
|
-
this.lastPersistError = null;
|
|
25
|
-
this.loadFromDisk();
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
get(id) {
|
|
29
|
-
if (!id) return null;
|
|
30
|
-
return this.map.get(String(id)) || null;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
set(id, value) {
|
|
34
|
-
if (!id) return value;
|
|
35
|
-
const session = normalizeSession(id, value);
|
|
36
|
-
this.map.set(String(id), session);
|
|
37
|
-
for (const message of session.messages) this.indexAssistantMessage(message);
|
|
38
|
-
this.pruneSessions();
|
|
39
|
-
this.persist();
|
|
40
|
-
return value;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
getConversation(id) {
|
|
44
|
-
if (!id) return null;
|
|
45
|
-
const responseId = this.conversations.get(String(id));
|
|
46
|
-
return responseId ? this.get(responseId) : null;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
setConversation(id, responseId) {
|
|
50
|
-
if (!id || !responseId) return;
|
|
51
|
-
this.conversations.set(String(id), String(responseId));
|
|
52
|
-
this.dropDanglingConversations();
|
|
53
|
-
this.persist();
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
indexAssistantMessage(message) {
|
|
57
|
-
if (message?.role !== 'assistant' || !Array.isArray(message.tool_calls)) return false;
|
|
58
|
-
const shared = cloneAssistantMessage(message);
|
|
59
|
-
let indexed = false;
|
|
60
|
-
for (const toolCall of message.tool_calls) {
|
|
61
|
-
if (!toolCall?.id) continue;
|
|
62
|
-
const key = String(toolCall.id);
|
|
63
|
-
this.toolCallMessages.delete(key);
|
|
64
|
-
this.toolCallMessages.set(key, shared);
|
|
65
|
-
indexed = true;
|
|
66
|
-
while (this.toolCallMessages.size > this.maxToolCallMessages) {
|
|
67
|
-
const oldestKey = this.toolCallMessages.keys().next().value;
|
|
68
|
-
this.toolCallMessages.delete(oldestKey);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
return indexed;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
rememberAssistantMessage(message) {
|
|
75
|
-
if (!this.indexAssistantMessage(message)) return false;
|
|
76
|
-
this.persist();
|
|
77
|
-
return true;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
getAssistantMessageForToolCall(callId) {
|
|
81
|
-
if (!callId) return null;
|
|
82
|
-
const message = this.toolCallMessages.get(String(callId));
|
|
83
|
-
return message ? cloneAssistantMessage(message) : null;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
delete(id) {
|
|
87
|
-
if (!id) return false;
|
|
88
|
-
const deleted = this.map.delete(String(id));
|
|
89
|
-
if (deleted) {
|
|
90
|
-
this.dropDanglingConversations();
|
|
91
|
-
this.persist();
|
|
92
|
-
}
|
|
93
|
-
return deleted;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
clear() {
|
|
97
|
-
this.map.clear();
|
|
98
|
-
this.toolCallMessages.clear();
|
|
99
|
-
this.conversations.clear();
|
|
100
|
-
this.persist();
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
loadFromDisk() {
|
|
104
|
-
if (!this.persistPath || !existsSync(this.persistPath)) return;
|
|
105
|
-
try {
|
|
106
|
-
const parsed = safeJsonParse(readFileSync(this.persistPath, 'utf8').replace(/^/, ''));
|
|
107
|
-
if (!parsed.ok || !isObject(parsed.value)) return;
|
|
108
|
-
this.map.clear();
|
|
109
|
-
this.conversations.clear();
|
|
110
|
-
this.toolCallMessages.clear();
|
|
111
|
-
for (const [id, session] of entriesFrom(parsed.value.sessions)) {
|
|
112
|
-
if (!id || !isObject(session)) continue;
|
|
113
|
-
this.map.set(String(id), normalizeSession(id, session));
|
|
114
|
-
}
|
|
115
|
-
for (const [id, responseId] of entriesFrom(parsed.value.conversations)) {
|
|
116
|
-
if (!id || !responseId) continue;
|
|
117
|
-
const normalizedResponseId = String(responseId);
|
|
118
|
-
if (this.map.has(normalizedResponseId)) this.conversations.set(String(id), normalizedResponseId);
|
|
119
|
-
}
|
|
120
|
-
const messagePool = Array.isArray(parsed.value.assistantMessages) ? parsed.value.assistantMessages : [];
|
|
121
|
-
const storedToolCallMessages = entriesFrom(parsed.value.toolCallMessages);
|
|
122
|
-
const sharedMessages = new Map();
|
|
123
|
-
for (const [callId, stored] of storedToolCallMessages) {
|
|
124
|
-
if (!callId) continue;
|
|
125
|
-
const message = resolveStoredToolCallMessage(stored, messagePool);
|
|
126
|
-
if (!message) continue;
|
|
127
|
-
const fingerprint = JSON.stringify(message);
|
|
128
|
-
if (!sharedMessages.has(fingerprint)) sharedMessages.set(fingerprint, message);
|
|
129
|
-
this.toolCallMessages.set(String(callId), sharedMessages.get(fingerprint));
|
|
130
|
-
while (this.toolCallMessages.size > this.maxToolCallMessages) {
|
|
131
|
-
this.toolCallMessages.delete(this.toolCallMessages.keys().next().value);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
this.pruneSessions();
|
|
135
|
-
if (!storedToolCallMessages.length) this.rebuildToolCallIndex();
|
|
136
|
-
} catch (error) {
|
|
137
|
-
this.lastLoadError = error;
|
|
138
|
-
this.map.clear();
|
|
139
|
-
this.conversations.clear();
|
|
140
|
-
this.toolCallMessages.clear();
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
persist() {
|
|
145
|
-
if (!this.persistPath) return;
|
|
146
|
-
try {
|
|
147
|
-
mkdirSync(dirname(this.persistPath), { recursive: true });
|
|
148
|
-
let serialized = this.serializePayload();
|
|
149
|
-
while (serialized.length > this.maxBytes && this.map.size > 1) {
|
|
150
|
-
this.map.delete(this.map.keys().next().value);
|
|
151
|
-
this.dropDanglingConversations();
|
|
152
|
-
serialized = this.serializePayload();
|
|
153
|
-
}
|
|
154
|
-
while (serialized.length > this.maxBytes && this.toolCallMessages.size > 0) {
|
|
155
|
-
this.toolCallMessages.delete(this.toolCallMessages.keys().next().value);
|
|
156
|
-
serialized = this.serializePayload();
|
|
157
|
-
}
|
|
158
|
-
const tmpPath = `${this.persistPath}.tmp-${process.pid}-${Date.now()}`;
|
|
159
|
-
writeFileSync(tmpPath, serialized, 'utf8');
|
|
160
|
-
renameSync(tmpPath, this.persistPath);
|
|
161
|
-
this.lastPersistError = null;
|
|
162
|
-
} catch (error) {
|
|
163
|
-
this.lastPersistError = error;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
serializePayload() {
|
|
168
|
-
return `${JSON.stringify(this.persistPayload(), null, 2)}\n`;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
persistPayload() {
|
|
172
|
-
const assistantMessages = [];
|
|
173
|
-
const poolIndexes = new Map();
|
|
174
|
-
const toolCallMessages = [];
|
|
175
|
-
for (const [callId, message] of this.toolCallMessages.entries()) {
|
|
176
|
-
const fingerprint = JSON.stringify(message);
|
|
177
|
-
let index = poolIndexes.get(fingerprint);
|
|
178
|
-
if (index === undefined) {
|
|
179
|
-
index = assistantMessages.length;
|
|
180
|
-
poolIndexes.set(fingerprint, index);
|
|
181
|
-
assistantMessages.push(message);
|
|
182
|
-
}
|
|
183
|
-
toolCallMessages.push([callId, index]);
|
|
184
|
-
}
|
|
185
|
-
return {
|
|
186
|
-
version: SESSION_STORE_VERSION,
|
|
187
|
-
sessions: Array.from(this.map.entries()),
|
|
188
|
-
conversations: Array.from(this.conversations.entries()),
|
|
189
|
-
assistantMessages,
|
|
190
|
-
toolCallMessages,
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
pruneSessions() {
|
|
195
|
-
while (this.map.size > this.maxSessions) {
|
|
196
|
-
const oldestKey = this.map.keys().next().value;
|
|
197
|
-
this.map.delete(oldestKey);
|
|
198
|
-
}
|
|
199
|
-
this.dropDanglingConversations();
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
dropDanglingConversations() {
|
|
203
|
-
for (const [conversationId, responseId] of this.conversations.entries()) {
|
|
204
|
-
if (!this.map.has(responseId)) this.conversations.delete(conversationId);
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
rebuildToolCallIndex() {
|
|
209
|
-
this.toolCallMessages.clear();
|
|
210
|
-
for (const session of this.map.values()) {
|
|
211
|
-
for (const message of Array.isArray(session?.messages) ? session.messages : []) {
|
|
212
|
-
this.indexAssistantMessage(message);
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
function normalizeSession(id, value) {
|
|
219
|
-
const messages = Array.isArray(value?.messages) ? value.messages : legacyHistoryMessages(value?.history);
|
|
220
|
-
return { id: String(value?.id || id), messages: stripLeadingSystemMessages(messages) };
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
function legacyHistoryMessages(history) {
|
|
224
|
-
const turns = Array.isArray(history) ? history : [];
|
|
225
|
-
if (!turns.length) return [];
|
|
226
|
-
if (turns.every((turn) => Array.isArray(turn?.historyMessages))) {
|
|
227
|
-
return turns[turns.length - 1].historyMessages.slice();
|
|
228
|
-
}
|
|
229
|
-
const messages = [];
|
|
230
|
-
for (const turn of turns) {
|
|
231
|
-
if (Array.isArray(turn?.historyMessages)) {
|
|
232
|
-
messages.push(...turn.historyMessages);
|
|
233
|
-
continue;
|
|
234
|
-
}
|
|
235
|
-
if (Array.isArray(turn?.inputMessages)) {
|
|
236
|
-
messages.push(...turn.inputMessages);
|
|
237
|
-
} else if (Array.isArray(turn?.chatRequest?.messages)) {
|
|
238
|
-
messages.push(...turn.chatRequest.messages);
|
|
239
|
-
}
|
|
240
|
-
if (turn?.assistantMessage) messages.push(turn.assistantMessage);
|
|
241
|
-
}
|
|
242
|
-
return messages;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
function stripLeadingSystemMessages(messages) {
|
|
246
|
-
let start = 0;
|
|
247
|
-
while (start < messages.length && (messages[start]?.role === 'system' || messages[start]?.role === 'developer')) {
|
|
248
|
-
start += 1;
|
|
249
|
-
}
|
|
250
|
-
return start ? messages.slice(start) : messages;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
function entriesFrom(value) {
|
|
254
|
-
if (Array.isArray(value)) return value;
|
|
255
|
-
if (isObject(value)) return Object.entries(value);
|
|
256
|
-
return [];
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
function resolveStoredToolCallMessage(stored, messagePool) {
|
|
260
|
-
if (isObject(stored)) return stored;
|
|
261
|
-
if (Number.isInteger(stored) && stored >= 0 && stored < messagePool.length && isObject(messagePool[stored])) {
|
|
262
|
-
return messagePool[stored];
|
|
263
|
-
}
|
|
264
|
-
return null;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
function positiveInteger(value, fallback) {
|
|
268
|
-
const number = Number(value);
|
|
269
|
-
return Number.isFinite(number) && number > 0 ? Math.floor(number) : fallback;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
function cloneAssistantMessage(message) {
|
|
273
|
-
return {
|
|
274
|
-
...message,
|
|
275
|
-
tool_calls: Array.isArray(message.tool_calls)
|
|
276
|
-
? message.tool_calls.map((toolCall) => ({
|
|
277
|
-
...toolCall,
|
|
278
|
-
function: toolCall.function ? { ...toolCall.function } : toolCall.function,
|
|
279
|
-
}))
|
|
280
|
-
: undefined,
|
|
281
|
-
};
|
|
282
|
-
}
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 4,
|
|
3
|
-
"sessions": [
|
|
4
|
-
[
|
|
5
|
-
"resp_0example1234567890abcdef",
|
|
6
|
-
{
|
|
7
|
-
"id": "resp_0example1234567890abcdef",
|
|
8
|
-
"messages": [
|
|
9
|
-
{ "role": "user", "content": "Find the config loader and summarize it." },
|
|
10
|
-
{
|
|
11
|
-
"role": "assistant",
|
|
12
|
-
"content": "",
|
|
13
|
-
"reasoning_content": "Raw DeepSeek thinking text, kept verbatim so thinking-mode tool turns can be replayed upstream.",
|
|
14
|
-
"tool_calls": [
|
|
15
|
-
{
|
|
16
|
-
"id": "call_example0001",
|
|
17
|
-
"type": "function",
|
|
18
|
-
"function": {
|
|
19
|
-
"name": "shell_command",
|
|
20
|
-
"arguments": "{\"command\":\"rg --files -g 'config*'\"}"
|
|
21
|
-
}
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
"id": "call_example0002",
|
|
25
|
-
"type": "function",
|
|
26
|
-
"function": {
|
|
27
|
-
"name": "shell_command",
|
|
28
|
-
"arguments": "{\"command\":\"rg -n 'loadConfig' src\"}"
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
]
|
|
32
|
-
},
|
|
33
|
-
{ "role": "tool", "tool_call_id": "call_example0001", "content": "src/config.js" },
|
|
34
|
-
{ "role": "tool", "tool_call_id": "call_example0002", "content": "src/config.js:12" },
|
|
35
|
-
{ "role": "assistant", "content": "src/config.js merges env vars and gateway.local.json into one config object." }
|
|
36
|
-
]
|
|
37
|
-
}
|
|
38
|
-
]
|
|
39
|
-
],
|
|
40
|
-
"conversations": [
|
|
41
|
-
["conv_example", "resp_0example1234567890abcdef"]
|
|
42
|
-
],
|
|
43
|
-
"assistantMessages": [
|
|
44
|
-
{
|
|
45
|
-
"role": "assistant",
|
|
46
|
-
"content": "",
|
|
47
|
-
"reasoning_content": "Raw DeepSeek thinking text, kept verbatim so thinking-mode tool turns can be replayed upstream.",
|
|
48
|
-
"tool_calls": [
|
|
49
|
-
{
|
|
50
|
-
"id": "call_example0001",
|
|
51
|
-
"type": "function",
|
|
52
|
-
"function": {
|
|
53
|
-
"name": "shell_command",
|
|
54
|
-
"arguments": "{\"command\":\"rg --files -g 'config*'\"}"
|
|
55
|
-
}
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
"id": "call_example0002",
|
|
59
|
-
"type": "function",
|
|
60
|
-
"function": {
|
|
61
|
-
"name": "shell_command",
|
|
62
|
-
"arguments": "{\"command\":\"rg -n 'loadConfig' src\"}"
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
]
|
|
66
|
-
}
|
|
67
|
-
],
|
|
68
|
-
"toolCallMessages": [
|
|
69
|
-
["call_example0001", 0],
|
|
70
|
-
["call_example0002", 0]
|
|
71
|
-
]
|
|
72
|
-
}
|