@galaxy-yearn/codex-deepseek-gateway 0.1.7 → 0.2.0
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 +54 -36
- package/config/codex-model-catalog.json +20 -24
- package/config/codex-model-catalog.zh.json +20 -24
- package/package.json +2 -1
- package/src/codex-launch.js +0 -1
- package/src/common.js +18 -3
- package/src/config.js +12 -2
- package/src/firecrawl.js +7 -16
- package/src/model-map.js +6 -20
- package/src/protocol.js +1319 -450
- package/src/server.js +510 -350
- package/src/session-store.js +228 -13
- package/src/tavily.js +2 -11
- package/src/upstream.js +14 -12
- package/src/web-search-emulator.js +12 -11
- package/state/sessions.example.json +72 -0
package/src/session-store.js
CHANGED
|
@@ -1,57 +1,272 @@
|
|
|
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
|
+
|
|
1
9
|
export class SessionStore {
|
|
2
|
-
constructor({
|
|
10
|
+
constructor({
|
|
11
|
+
maxToolCallMessages = 1000,
|
|
12
|
+
maxSessions = DEFAULT_MAX_SESSIONS,
|
|
13
|
+
maxBytes = DEFAULT_MAX_BYTES,
|
|
14
|
+
persistPath = '',
|
|
15
|
+
} = {}) {
|
|
3
16
|
this.map = new Map();
|
|
4
17
|
this.toolCallMessages = new Map();
|
|
5
18
|
this.conversations = new Map();
|
|
6
|
-
this.maxToolCallMessages = maxToolCallMessages;
|
|
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();
|
|
7
26
|
}
|
|
8
27
|
|
|
9
28
|
get(id) {
|
|
10
|
-
|
|
29
|
+
if (!id) return null;
|
|
30
|
+
return this.map.get(String(id)) || null;
|
|
11
31
|
}
|
|
12
32
|
|
|
13
33
|
set(id, value) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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();
|
|
18
40
|
return value;
|
|
19
41
|
}
|
|
20
42
|
|
|
21
43
|
getConversation(id) {
|
|
22
|
-
|
|
44
|
+
if (!id) return null;
|
|
45
|
+
const responseId = this.conversations.get(String(id));
|
|
23
46
|
return responseId ? this.get(responseId) : null;
|
|
24
47
|
}
|
|
25
48
|
|
|
26
49
|
setConversation(id, responseId) {
|
|
27
|
-
if (id
|
|
50
|
+
if (!id || !responseId) return;
|
|
51
|
+
this.conversations.set(String(id), String(responseId));
|
|
52
|
+
this.dropDanglingConversations();
|
|
53
|
+
this.persist();
|
|
28
54
|
}
|
|
29
55
|
|
|
30
56
|
indexAssistantMessage(message) {
|
|
31
|
-
if (message?.role !== 'assistant' || !Array.isArray(message.tool_calls)) return;
|
|
57
|
+
if (message?.role !== 'assistant' || !Array.isArray(message.tool_calls)) return false;
|
|
58
|
+
const shared = cloneAssistantMessage(message);
|
|
59
|
+
let indexed = false;
|
|
32
60
|
for (const toolCall of message.tool_calls) {
|
|
33
61
|
if (!toolCall?.id) continue;
|
|
34
|
-
|
|
62
|
+
const key = String(toolCall.id);
|
|
63
|
+
this.toolCallMessages.delete(key);
|
|
64
|
+
this.toolCallMessages.set(key, shared);
|
|
65
|
+
indexed = true;
|
|
35
66
|
while (this.toolCallMessages.size > this.maxToolCallMessages) {
|
|
36
67
|
const oldestKey = this.toolCallMessages.keys().next().value;
|
|
37
68
|
this.toolCallMessages.delete(oldestKey);
|
|
38
69
|
}
|
|
39
70
|
}
|
|
71
|
+
return indexed;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
rememberAssistantMessage(message) {
|
|
75
|
+
if (!this.indexAssistantMessage(message)) return false;
|
|
76
|
+
this.persist();
|
|
77
|
+
return true;
|
|
40
78
|
}
|
|
41
79
|
|
|
42
80
|
getAssistantMessageForToolCall(callId) {
|
|
43
|
-
|
|
81
|
+
if (!callId) return null;
|
|
82
|
+
const message = this.toolCallMessages.get(String(callId));
|
|
83
|
+
return message ? cloneAssistantMessage(message) : null;
|
|
44
84
|
}
|
|
45
85
|
|
|
46
86
|
delete(id) {
|
|
47
|
-
|
|
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;
|
|
48
94
|
}
|
|
49
95
|
|
|
50
96
|
clear() {
|
|
51
97
|
this.map.clear();
|
|
52
98
|
this.toolCallMessages.clear();
|
|
53
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();
|
|
54
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;
|
|
55
270
|
}
|
|
56
271
|
|
|
57
272
|
function cloneAssistantMessage(message) {
|
package/src/tavily.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isObject, joinUrl, safeJsonParse } from './common.js';
|
|
1
|
+
import { isObject, joinUrl, parseBoolean, safeJsonParse } from './common.js';
|
|
2
2
|
|
|
3
3
|
const DEFAULT_MAX_RESULTS = 5;
|
|
4
4
|
const HARD_MAX_RESULTS = 10;
|
|
@@ -14,15 +14,6 @@ function clampInteger(value, min, max, fallback) {
|
|
|
14
14
|
return Math.min(max, Math.max(min, Math.trunc(number)));
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
function booleanValue(value, fallback) {
|
|
18
|
-
if (typeof value === 'boolean') return value;
|
|
19
|
-
if (value == null || value === '') return fallback;
|
|
20
|
-
const normalized = String(value).trim().toLowerCase();
|
|
21
|
-
if (['1', 'true', 'yes', 'on'].includes(normalized)) return true;
|
|
22
|
-
if (['0', 'false', 'no', 'off'].includes(normalized)) return false;
|
|
23
|
-
return fallback;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
17
|
function cleanText(value, maxChars = DEFAULT_SNIPPET_CHARS) {
|
|
27
18
|
if (value == null) return '';
|
|
28
19
|
const text = String(value)
|
|
@@ -111,7 +102,7 @@ export function buildTavilySearchRequest(args = {}, config = {}) {
|
|
|
111
102
|
query: normalized.query,
|
|
112
103
|
search_depth: normalized.searchDepth,
|
|
113
104
|
max_results: normalized.maxResults,
|
|
114
|
-
include_answer:
|
|
105
|
+
include_answer: parseBoolean(config.tavilyIncludeAnswer, true),
|
|
115
106
|
include_raw_content: false,
|
|
116
107
|
};
|
|
117
108
|
if (normalized.topic) body.topic = normalized.topic;
|
package/src/upstream.js
CHANGED
|
@@ -82,9 +82,11 @@ export async function relayChatCompletionsResponse({
|
|
|
82
82
|
onStreamChunk,
|
|
83
83
|
passThrough = true,
|
|
84
84
|
writeHeaders = true,
|
|
85
|
+
endResponse = true,
|
|
85
86
|
}) {
|
|
86
87
|
const contentType = upstreamResponse.headers.get('content-type') || '';
|
|
87
88
|
const isStream = contentType.includes('text/event-stream');
|
|
89
|
+
const writable = () => !res.destroyed && !res.writableEnded;
|
|
88
90
|
|
|
89
91
|
if (!isStream) {
|
|
90
92
|
const data = await readJsonResponse(upstreamResponse);
|
|
@@ -92,18 +94,18 @@ export async function relayChatCompletionsResponse({
|
|
|
92
94
|
onStreamChunk({ data });
|
|
93
95
|
onStreamChunk({ done: true });
|
|
94
96
|
}
|
|
95
|
-
if (passThrough) {
|
|
97
|
+
if (passThrough && writable()) {
|
|
96
98
|
res.writeHead(upstreamResponse.status, {
|
|
97
99
|
'content-type': 'application/json; charset=utf-8',
|
|
98
100
|
});
|
|
99
101
|
res.end(JSON.stringify(data));
|
|
100
|
-
} else {
|
|
102
|
+
} else if (endResponse && writable()) {
|
|
101
103
|
res.end();
|
|
102
104
|
}
|
|
103
105
|
return;
|
|
104
106
|
}
|
|
105
107
|
|
|
106
|
-
if (writeHeaders) {
|
|
108
|
+
if (writeHeaders && writable()) {
|
|
107
109
|
res.writeHead(upstreamResponse.status, {
|
|
108
110
|
'content-type': 'text/event-stream; charset=utf-8',
|
|
109
111
|
'cache-control': 'no-cache, no-transform',
|
|
@@ -114,17 +116,17 @@ export async function relayChatCompletionsResponse({
|
|
|
114
116
|
|
|
115
117
|
const reader = upstreamResponse.body?.getReader?.();
|
|
116
118
|
if (!reader) {
|
|
117
|
-
res.end();
|
|
119
|
+
if (endResponse && writable()) res.end();
|
|
118
120
|
return;
|
|
119
121
|
}
|
|
120
122
|
|
|
121
123
|
const parser = new SseParser();
|
|
122
124
|
let sawDone = false;
|
|
123
|
-
const emitDone = () => {
|
|
125
|
+
const emitDone = ({ eof = false } = {}) => {
|
|
124
126
|
if (sawDone) return;
|
|
125
127
|
sawDone = true;
|
|
126
|
-
if (typeof onStreamChunk === 'function') onStreamChunk({ done: true });
|
|
127
|
-
if (passThrough) res.write(`data: [DONE]\n\n`);
|
|
128
|
+
if (typeof onStreamChunk === 'function') onStreamChunk(eof ? { done: true, eof: true } : { done: true });
|
|
129
|
+
if (passThrough && writable()) res.write(`data: [DONE]\n\n`);
|
|
128
130
|
};
|
|
129
131
|
|
|
130
132
|
while (true) {
|
|
@@ -134,11 +136,11 @@ export async function relayChatCompletionsResponse({
|
|
|
134
136
|
for (const event of events) {
|
|
135
137
|
if (event.done) {
|
|
136
138
|
emitDone();
|
|
137
|
-
res.end();
|
|
139
|
+
if (endResponse && writable()) res.end();
|
|
138
140
|
return;
|
|
139
141
|
}
|
|
140
142
|
if (typeof onStreamChunk === 'function') onStreamChunk(event);
|
|
141
|
-
if (passThrough) res.write(`data: ${event.data}\n\n`);
|
|
143
|
+
if (passThrough && writable()) res.write(`data: ${event.data}\n\n`);
|
|
142
144
|
}
|
|
143
145
|
}
|
|
144
146
|
|
|
@@ -148,8 +150,8 @@ export async function relayChatCompletionsResponse({
|
|
|
148
150
|
break;
|
|
149
151
|
}
|
|
150
152
|
if (typeof onStreamChunk === 'function') onStreamChunk(event);
|
|
151
|
-
if (passThrough) res.write(`data: ${event.data}\n\n`);
|
|
153
|
+
if (passThrough && writable()) res.write(`data: ${event.data}\n\n`);
|
|
152
154
|
}
|
|
153
|
-
emitDone();
|
|
154
|
-
res.end();
|
|
155
|
+
emitDone({ eof: true });
|
|
156
|
+
if (endResponse && writable()) res.end();
|
|
155
157
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { generateId, isObject,
|
|
1
|
+
import { generateId, isObject, parseJsonObject, toText } from './common.js';
|
|
2
2
|
import { callFirecrawlScrape } from './firecrawl.js';
|
|
3
3
|
import { callTavilySearch, formatTavilySearchResult } from './tavily.js';
|
|
4
4
|
|
|
@@ -14,7 +14,7 @@ const INTERNAL_WEB_TOOL_NAMES = new Set([
|
|
|
14
14
|
INTERNAL_WEB_OPEN_PAGE_TOOL,
|
|
15
15
|
INTERNAL_WEB_FIND_IN_PAGE_TOOL,
|
|
16
16
|
]);
|
|
17
|
-
const MAX_SEARCH_ROUNDS =
|
|
17
|
+
const MAX_SEARCH_ROUNDS = 40;
|
|
18
18
|
|
|
19
19
|
const WEB_SEARCH_INSTRUCTIONS = [
|
|
20
20
|
'For live web information, use tavily_search.',
|
|
@@ -135,13 +135,6 @@ function clone(value) {
|
|
|
135
135
|
return value == null ? value : JSON.parse(JSON.stringify(value));
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
-
function parseJsonObject(text) {
|
|
139
|
-
if (isObject(text)) return text;
|
|
140
|
-
if (typeof text !== 'string' || !text.trim()) return {};
|
|
141
|
-
const parsed = safeJsonParse(text);
|
|
142
|
-
return parsed.ok && isObject(parsed.value) ? parsed.value : {};
|
|
143
|
-
}
|
|
144
|
-
|
|
145
138
|
function webSearchToolOptions(tools) {
|
|
146
139
|
const options = {};
|
|
147
140
|
for (const tool of Array.isArray(tools) ? tools : []) {
|
|
@@ -395,7 +388,7 @@ export function knownExternalToolCallsCompletion(completion, tools) {
|
|
|
395
388
|
|
|
396
389
|
export function maxWebSearchRounds(config = {}) {
|
|
397
390
|
const value = Number(config.tavilyMaxSearchRounds);
|
|
398
|
-
if (!Number.isFinite(value)) return
|
|
391
|
+
if (!Number.isFinite(value)) return 20;
|
|
399
392
|
return Math.min(MAX_SEARCH_ROUNDS, Math.max(0, Math.trunc(value)));
|
|
400
393
|
}
|
|
401
394
|
|
|
@@ -683,6 +676,14 @@ function buildAnnotations(text, searches = [], openedPages = []) {
|
|
|
683
676
|
return annotations.sort((a, b) => a.start_index - b.start_index);
|
|
684
677
|
}
|
|
685
678
|
|
|
679
|
+
export function annotateMessagePartWithWebCitations(part, searches = [], openedPages = []) {
|
|
680
|
+
if (!part || part.type !== 'output_text' || typeof part.text !== 'string') return;
|
|
681
|
+
if (!(searches || []).length && !(openedPages || []).length) return;
|
|
682
|
+
const annotations = buildAnnotations(part.text, searches, openedPages);
|
|
683
|
+
if (!annotations.length) return;
|
|
684
|
+
part.annotations = [...(Array.isArray(part.annotations) ? part.annotations : []), ...annotations];
|
|
685
|
+
}
|
|
686
|
+
|
|
686
687
|
export function applyWebSearchOutputCompatibility(payload, searches = [], normalized = payload?.normalized, openedPages = []) {
|
|
687
688
|
if (!payload || !Array.isArray(payload.output)) return payload;
|
|
688
689
|
const explicitOpenedPages = (openedPages || []).filter((page) => !page.auto);
|
|
@@ -732,7 +733,7 @@ export function applyWebSearchOutputCompatibility(payload, searches = [], normal
|
|
|
732
733
|
}
|
|
733
734
|
payload.output = output;
|
|
734
735
|
payload.output_text = output
|
|
735
|
-
.filter((item) => item?.type === 'message')
|
|
736
|
+
.filter((item) => item?.type === 'message' && item.phase !== 'commentary')
|
|
736
737
|
.flatMap((item) => Array.isArray(item.content) ? item.content : [])
|
|
737
738
|
.filter((part) => part?.type === 'output_text' && typeof part.text === 'string')
|
|
738
739
|
.map((part) => part.text)
|
|
@@ -0,0 +1,72 @@
|
|
|
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
|
+
}
|