gemini-reverse 1.0.1 → 1.0.3
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 +139 -154
- package/client.js +668 -152
- package/components/chatMixin.js +129 -70
- package/components/index.js +2 -1
- package/components/researchMixin.js +202 -0
- package/constants.js +157 -36
- package/index.d.ts +254 -46
- package/index.js +35 -5
- package/package.json +1 -1
- package/types/availablemodel.js +76 -0
- package/types/candidate.js +31 -17
- package/types/chathistory.js +36 -0
- package/types/chatinfo.js +23 -0
- package/types/index.js +26 -2
- package/types/modeloutput.js +16 -28
- package/types/research.js +65 -0
- package/types/researchresult.js +21 -0
- package/types/video.js +195 -0
- package/utils/accessToken.js +15 -7
- package/utils/index.js +22 -3
- package/utils/parsing.js +67 -38
- package/utils/research.js +175 -0
package/utils/accessToken.js
CHANGED
|
@@ -24,8 +24,12 @@ function parseCookies(headers, base = {}) {
|
|
|
24
24
|
|
|
25
25
|
function parseProxy(str) {
|
|
26
26
|
if (!str) return undefined;
|
|
27
|
-
try {
|
|
28
|
-
|
|
27
|
+
try {
|
|
28
|
+
const u = new URL(str);
|
|
29
|
+
return { protocol: u.protocol.replace(':', ''), host: u.hostname, port: parseInt(u.port) };
|
|
30
|
+
} catch {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
29
33
|
}
|
|
30
34
|
|
|
31
35
|
function cacheDir() {
|
|
@@ -44,8 +48,10 @@ async function sendInitRequest(cookies, proxy = null) {
|
|
|
44
48
|
const snlm0e = (t.match(/"SNlM0e":\s*"(.*?)"/) || [])[1] || null;
|
|
45
49
|
const cfb2h = (t.match(/"cfb2h":\s*"(.*?)"/) || [])[1] || null;
|
|
46
50
|
const fdrfje = (t.match(/"FdrFJe":\s*"(.*?)"/) || [])[1] || null;
|
|
47
|
-
|
|
48
|
-
|
|
51
|
+
const language = (t.match(/"TuX5cc":\s*"(.*?)"/) || [])[1] || null;
|
|
52
|
+
const pushId = (t.match(/"qKIAYe":\s*"(.*?)"/) || [])[1] || null;
|
|
53
|
+
if (!snlm0e && !cfb2h && !fdrfje && !language && !pushId) throw new AuthError('Cookies invalid.');
|
|
54
|
+
return [snlm0e, cfb2h, fdrfje, language, pushId, parseCookies(res.headers, cookies)];
|
|
49
55
|
}
|
|
50
56
|
|
|
51
57
|
async function getAccessToken(baseCookies, proxy = null, verbose = false) {
|
|
@@ -59,8 +65,9 @@ async function getAccessToken(baseCookies, proxy = null, verbose = false) {
|
|
|
59
65
|
const jar = (base, extra = {}) => ({ ...extraCookies, ...base, ...extra });
|
|
60
66
|
const dir = cacheDir();
|
|
61
67
|
|
|
62
|
-
if (baseCookies['__Secure-1PSID'])
|
|
68
|
+
if (baseCookies['__Secure-1PSID']) {
|
|
63
69
|
tasks.push(sendInitRequest(jar(baseCookies), proxy));
|
|
70
|
+
}
|
|
64
71
|
|
|
65
72
|
const psid = baseCookies['__Secure-1PSID'];
|
|
66
73
|
if (psid) {
|
|
@@ -84,8 +91,9 @@ async function getAccessToken(baseCookies, proxy = null, verbose = false) {
|
|
|
84
91
|
if (validCaches === 0 && verbose) console.debug('Skipping cached cookies. No valid caches found.');
|
|
85
92
|
}
|
|
86
93
|
|
|
87
|
-
if (!tasks.length)
|
|
94
|
+
if (!tasks.length) {
|
|
88
95
|
throw new AuthError('No valid cookies available. Please pass __Secure-1PSID and optionally __Secure-1PSIDTS.');
|
|
96
|
+
}
|
|
89
97
|
|
|
90
98
|
let lastErr;
|
|
91
99
|
for (let i = 0; i < tasks.length; i++) {
|
|
@@ -102,4 +110,4 @@ async function getAccessToken(baseCookies, proxy = null, verbose = false) {
|
|
|
102
110
|
throw new AuthError(`Failed to initialize client. (Failed attempts: ${tasks.length})`);
|
|
103
111
|
}
|
|
104
112
|
|
|
105
|
-
module.exports = { getAccessToken, sendInitRequest, cookieStr, parseCookies, parseProxy, cacheDir };
|
|
113
|
+
module.exports = { getAccessToken, sendInitRequest, cookieStr, parseCookies, parseProxy, cacheDir };
|
package/utils/index.js
CHANGED
|
@@ -1,9 +1,20 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { getAccessToken, sendInitRequest, cookieStr, parseCookies, parseProxy, cacheDir } = require('./accessToken');
|
|
4
|
-
const { getDeltaByFpLen, getCleanText,
|
|
4
|
+
const { getDeltaByFpLen, getCleanText, getNestedValue, parseResponseByFrame, extractJsonFromResponse } = require('./parsing');
|
|
5
5
|
const { rotate1psidts } = require('./rotate');
|
|
6
6
|
const { uploadFile, parseFileName, generateRandomName } = require('./upload');
|
|
7
|
+
const {
|
|
8
|
+
iterNested,
|
|
9
|
+
findFirstMatch,
|
|
10
|
+
findFirstString,
|
|
11
|
+
extractResearchId,
|
|
12
|
+
extractChatId,
|
|
13
|
+
collectResearchNotes,
|
|
14
|
+
findFirstDictKey,
|
|
15
|
+
extractDeepResearchPlan,
|
|
16
|
+
extractDeepResearchStatusPayload,
|
|
17
|
+
} = require('./research');
|
|
7
18
|
|
|
8
19
|
module.exports = {
|
|
9
20
|
getAccessToken,
|
|
@@ -14,7 +25,6 @@ module.exports = {
|
|
|
14
25
|
cacheDir,
|
|
15
26
|
getDeltaByFpLen,
|
|
16
27
|
getCleanText,
|
|
17
|
-
getFpLen,
|
|
18
28
|
getNestedValue,
|
|
19
29
|
parseResponseByFrame,
|
|
20
30
|
extractJsonFromResponse,
|
|
@@ -22,4 +32,13 @@ module.exports = {
|
|
|
22
32
|
uploadFile,
|
|
23
33
|
parseFileName,
|
|
24
34
|
generateRandomName,
|
|
25
|
-
|
|
35
|
+
iterNested,
|
|
36
|
+
findFirstMatch,
|
|
37
|
+
findFirstString,
|
|
38
|
+
extractResearchId,
|
|
39
|
+
extractChatId,
|
|
40
|
+
collectResearchNotes,
|
|
41
|
+
findFirstDictKey,
|
|
42
|
+
extractDeepResearchPlan,
|
|
43
|
+
extractDeepResearchStatusPayload,
|
|
44
|
+
};
|
package/utils/parsing.js
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const VOLATILE_RE = /[\s!"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~]/g;
|
|
4
|
-
const VOLATILE_SET = new Set(' \t\n\r\x0b\x0c!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~');
|
|
5
3
|
const FLICKER_ESC_RE = /\\+[`*_~].*$/;
|
|
6
4
|
|
|
7
5
|
function getCleanText(s) {
|
|
@@ -10,47 +8,71 @@ function getCleanText(s) {
|
|
|
10
8
|
return s.replace(FLICKER_ESC_RE, '');
|
|
11
9
|
}
|
|
12
10
|
|
|
13
|
-
function
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
function longestCommonSubsequenceBlocks(a, b) {
|
|
12
|
+
const blocks = [];
|
|
13
|
+
if (!a.length || !b.length) return blocks;
|
|
16
14
|
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
if (newC.startsWith(lastSentClean)) return [newC.slice(lastSentClean.length), newC];
|
|
15
|
+
const m = a.length, n = b.length;
|
|
16
|
+
const dp = Array.from({ length: m + 1 }, () => new Int32Array(n + 1));
|
|
20
17
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
for (let i = 0; i < newC.length; i++) {
|
|
27
|
-
if (!VOLATILE_SET.has(newC[i])) cur++;
|
|
28
|
-
if (cur === targetFpLen) { pLow = i + 1; found = true; break; }
|
|
18
|
+
for (let i = m - 1; i >= 0; i--) {
|
|
19
|
+
for (let j = n - 1; j >= 0; j--) {
|
|
20
|
+
if (a[i] === b[j]) {
|
|
21
|
+
dp[i][j] = dp[i + 1][j + 1] + 1;
|
|
22
|
+
}
|
|
29
23
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let i = 0, j = 0;
|
|
27
|
+
while (i < m && j < n) {
|
|
28
|
+
if (a[i] === b[j]) {
|
|
29
|
+
let size = 0;
|
|
30
|
+
const ai = i, bj = j;
|
|
31
|
+
while (i < m && j < n && a[i] === b[j]) { i++; j++; size++; }
|
|
32
|
+
if (size > 0) blocks.push({ a: ai, b: bj, size });
|
|
33
|
+
} else {
|
|
34
|
+
let bestLen = 0, bestI = i + 1, bestJ = j + 1;
|
|
35
|
+
for (let ni = i; ni < Math.min(i + 50, m); ni++) {
|
|
36
|
+
for (let nj = j; nj < Math.min(j + 50, n); nj++) {
|
|
37
|
+
if (dp[ni][nj] > bestLen) { bestLen = dp[ni][nj]; bestI = ni; bestJ = nj; }
|
|
38
|
+
}
|
|
34
39
|
}
|
|
35
|
-
|
|
40
|
+
i = bestI; j = bestJ;
|
|
36
41
|
}
|
|
37
42
|
}
|
|
43
|
+
return blocks;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function getDeltaByFpLen(newRaw, lastSentClean, isFinal) {
|
|
47
|
+
const newC = isFinal ? newRaw : getCleanText(newRaw);
|
|
48
|
+
|
|
49
|
+
if (newC.startsWith(lastSentClean)) {
|
|
50
|
+
return [newC.slice(lastSentClean.length), newC];
|
|
51
|
+
}
|
|
38
52
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
53
|
+
const searchLen = Math.min(3000, Math.max(1000, lastSentClean.length));
|
|
54
|
+
const actualLen = Math.min(searchLen, lastSentClean.length, newC.length);
|
|
55
|
+
|
|
56
|
+
if (actualLen === 0) return [newC, newC];
|
|
57
|
+
|
|
58
|
+
const tailLast = lastSentClean.slice(-actualLen);
|
|
59
|
+
const tailNew = newC.slice(-actualLen);
|
|
60
|
+
|
|
61
|
+
const blocks = longestCommonSubsequenceBlocks(tailLast, tailNew);
|
|
62
|
+
if (blocks.length > 0) {
|
|
63
|
+
const lastMatch = blocks[blocks.length - 1];
|
|
64
|
+
const matchEnd = lastMatch.b + lastMatch.size;
|
|
65
|
+
return [tailNew.slice(matchEnd), newC];
|
|
42
66
|
}
|
|
43
|
-
const suffix = lastSentClean.slice(lastIdx + 1);
|
|
44
67
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
else if (cs === '\\' && (i + 1) < suffix.length && suffix[i + 1] === cn) { i += 2; j++; }
|
|
51
|
-
else break;
|
|
68
|
+
const blocksAll = longestCommonSubsequenceBlocks(lastSentClean, newC);
|
|
69
|
+
if (blocksAll.length > 0) {
|
|
70
|
+
const lastMatch = blocksAll[blocksAll.length - 1];
|
|
71
|
+
const matchEnd = lastMatch.b + lastMatch.size;
|
|
72
|
+
return [newC.slice(matchEnd), newC];
|
|
52
73
|
}
|
|
53
|
-
|
|
74
|
+
|
|
75
|
+
return [newC, newC];
|
|
54
76
|
}
|
|
55
77
|
|
|
56
78
|
function getNestedValue(data, path, defaultVal = null) {
|
|
@@ -83,7 +105,9 @@ function parseResponseByFrame(content) {
|
|
|
83
105
|
const cp = content.codePointAt(idx);
|
|
84
106
|
const u = cp > 0xFFFF ? 2 : 1;
|
|
85
107
|
if (units + u > len) break;
|
|
86
|
-
units += u;
|
|
108
|
+
units += u;
|
|
109
|
+
chars += cp > 0xFFFF ? 2 : 1;
|
|
110
|
+
idx += cp > 0xFFFF ? 2 : 1;
|
|
87
111
|
}
|
|
88
112
|
if (units < len) break;
|
|
89
113
|
const end = start + chars;
|
|
@@ -92,7 +116,8 @@ function parseResponseByFrame(content) {
|
|
|
92
116
|
if (!chunk) continue;
|
|
93
117
|
try {
|
|
94
118
|
const parsed = JSON.parse(chunk);
|
|
95
|
-
if (Array.isArray(parsed)) frames.push(...parsed);
|
|
119
|
+
if (Array.isArray(parsed)) frames.push(...parsed);
|
|
120
|
+
else frames.push(parsed);
|
|
96
121
|
} catch {}
|
|
97
122
|
}
|
|
98
123
|
return [frames, content.slice(pos)];
|
|
@@ -104,16 +129,20 @@ function extractJsonFromResponse(text) {
|
|
|
104
129
|
c = c.trimStart();
|
|
105
130
|
const [r] = parseResponseByFrame(c);
|
|
106
131
|
if (r.length) return r;
|
|
107
|
-
try {
|
|
132
|
+
try {
|
|
133
|
+
const p = JSON.parse(c.trim());
|
|
134
|
+
return Array.isArray(p) ? p : [p];
|
|
135
|
+
} catch {}
|
|
108
136
|
const lines = [];
|
|
109
137
|
for (const line of c.trim().split('\n')) {
|
|
110
138
|
try {
|
|
111
139
|
const p = JSON.parse(line.trim());
|
|
112
|
-
if (Array.isArray(p)) lines.push(...p);
|
|
140
|
+
if (Array.isArray(p)) lines.push(...p);
|
|
141
|
+
else if (p && typeof p === 'object') lines.push(p);
|
|
113
142
|
} catch {}
|
|
114
143
|
}
|
|
115
144
|
if (lines.length) return lines;
|
|
116
145
|
throw new Error('Could not find valid JSON in response.');
|
|
117
146
|
}
|
|
118
147
|
|
|
119
|
-
module.exports = { getCleanText,
|
|
148
|
+
module.exports = { getCleanText, getDeltaByFpLen, getNestedValue, parseResponseByFrame, extractJsonFromResponse };
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { getNestedValue } = require('./parsing');
|
|
4
|
+
|
|
5
|
+
const _RESEARCH_ID_RE = /\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/i;
|
|
6
|
+
const _CHAT_ID_RE = /\bc_[A-Za-z0-9_]+\b/;
|
|
7
|
+
const _URL_RE = /^https?:\/\//;
|
|
8
|
+
|
|
9
|
+
function* iterNested(data) {
|
|
10
|
+
yield data;
|
|
11
|
+
if (Array.isArray(data)) {
|
|
12
|
+
for (const item of data) yield* iterNested(item);
|
|
13
|
+
} else if (data && typeof data === 'object') {
|
|
14
|
+
for (const item of Object.values(data)) yield* iterNested(item);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function findFirstMatch(data, pattern) {
|
|
19
|
+
for (const item of iterNested(data)) {
|
|
20
|
+
if (typeof item === 'string') {
|
|
21
|
+
const match = pattern.exec(item);
|
|
22
|
+
if (match) return match[0];
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function findFirstString(data, exclude = new Set()) {
|
|
29
|
+
for (const item of iterNested(data)) {
|
|
30
|
+
if (typeof item === 'string' && item && !exclude.has(item)) return item;
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function extractResearchId(data) {
|
|
36
|
+
return findFirstMatch(data, _RESEARCH_ID_RE);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function extractChatId(data) {
|
|
40
|
+
return findFirstMatch(data, _CHAT_ID_RE);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function collectResearchNotes(data, exclude = new Set()) {
|
|
44
|
+
const notes = [];
|
|
45
|
+
const seen = new Set();
|
|
46
|
+
for (const item of iterNested(data)) {
|
|
47
|
+
if (typeof item !== 'string') continue;
|
|
48
|
+
const text = item.trim();
|
|
49
|
+
if (!text || exclude.has(text) || seen.has(text) || _URL_RE.test(text) || text.length < 12) continue;
|
|
50
|
+
seen.add(text);
|
|
51
|
+
notes.push(text);
|
|
52
|
+
if (notes.length >= 12) break;
|
|
53
|
+
}
|
|
54
|
+
return notes;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function findFirstDictKey(data, key) {
|
|
58
|
+
for (const item of iterNested(data)) {
|
|
59
|
+
if (item && typeof item === 'object' && !Array.isArray(item) && key in item) return item;
|
|
60
|
+
}
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function extractDeepResearchPlan(candidateData, fallbackText = '') {
|
|
65
|
+
let metaDict = null;
|
|
66
|
+
let payload = null;
|
|
67
|
+
|
|
68
|
+
for (const key of ['56', '57']) {
|
|
69
|
+
metaDict = findFirstDictKey(candidateData, key);
|
|
70
|
+
if (metaDict && Array.isArray(metaDict[key])) {
|
|
71
|
+
payload = metaDict[key];
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (!metaDict || !payload) return null;
|
|
77
|
+
|
|
78
|
+
const researchId = extractResearchId(candidateData);
|
|
79
|
+
const title = getNestedValue(payload, [0]);
|
|
80
|
+
const stepsPayload = getNestedValue(payload, [1], []);
|
|
81
|
+
const steps = [];
|
|
82
|
+
if (Array.isArray(stepsPayload)) {
|
|
83
|
+
for (const step of stepsPayload) {
|
|
84
|
+
if (Array.isArray(step)) {
|
|
85
|
+
const label = step.length > 1 && typeof step[1] === 'string' ? step[1] : null;
|
|
86
|
+
const body = step.length > 2 && typeof step[2] === 'string' ? step[2] : null;
|
|
87
|
+
if (label && body) steps.push(`${label}: ${body}`);
|
|
88
|
+
else if (body) steps.push(body);
|
|
89
|
+
else if (label) steps.push(label);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const modifyPayload = getNestedValue(payload, [5]);
|
|
95
|
+
let modifyPrompt = null;
|
|
96
|
+
if (Array.isArray(modifyPayload)) modifyPrompt = findFirstString(modifyPayload);
|
|
97
|
+
|
|
98
|
+
const queryVal = getNestedValue(payload, [1, 0, 2]);
|
|
99
|
+
const query = typeof queryVal === 'string' ? queryVal : null;
|
|
100
|
+
const etaVal = getNestedValue(payload, [2]);
|
|
101
|
+
const eta_text = typeof etaVal === 'string' ? etaVal : null;
|
|
102
|
+
const confirmVal = getNestedValue(payload, [3, 0]);
|
|
103
|
+
const confirm_prompt = typeof confirmVal === 'string' ? confirmVal : null;
|
|
104
|
+
const confirmUrlVal = getNestedValue(payload, [4, 0]);
|
|
105
|
+
const confirmation_url = typeof confirmUrlVal === 'string' ? confirmUrlVal : null;
|
|
106
|
+
const rawState70 = metaDict['70'];
|
|
107
|
+
const raw_state = typeof rawState70 === 'number' ? rawState70 : null;
|
|
108
|
+
|
|
109
|
+
if (!title && !query && !steps.length && !eta_text && !confirm_prompt && !confirmation_url && !modifyPrompt) {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
research_id: researchId,
|
|
115
|
+
title: typeof title === 'string' ? title : null,
|
|
116
|
+
query,
|
|
117
|
+
steps,
|
|
118
|
+
eta_text,
|
|
119
|
+
confirm_prompt,
|
|
120
|
+
confirmation_url,
|
|
121
|
+
modify_prompt: modifyPrompt,
|
|
122
|
+
raw_state,
|
|
123
|
+
response_text: fallbackText || null,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function extractDeepResearchStatusPayload(payload) {
|
|
128
|
+
const data = Array.isArray(payload) && payload.length > 0 && Array.isArray(payload[0]) ? payload[0] : payload;
|
|
129
|
+
const researchId = extractResearchId(data);
|
|
130
|
+
if (!researchId) return null;
|
|
131
|
+
|
|
132
|
+
const title = getNestedValue(data, [1, 4, 0]);
|
|
133
|
+
const query = getNestedValue(data, [1, 4, 1]);
|
|
134
|
+
const cid = getNestedValue(data, [1, 3, 0]) || extractChatId(data);
|
|
135
|
+
|
|
136
|
+
let raw_state = null;
|
|
137
|
+
const metaDict = findFirstDictKey(data, '70');
|
|
138
|
+
if (metaDict && typeof metaDict['70'] === 'number') raw_state = metaDict['70'];
|
|
139
|
+
|
|
140
|
+
const markerStrings = [];
|
|
141
|
+
for (const item of iterNested(data)) {
|
|
142
|
+
if (typeof item === 'string' && item) markerStrings.push(item);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const done = markerStrings.some(s => s.includes('immersive_entry_chip'));
|
|
146
|
+
const awaitingConfirmation = markerStrings.some(s => s.includes('deep_research_confirmation_content'));
|
|
147
|
+
const state = done ? 'completed' : awaitingConfirmation ? 'awaiting_confirmation' : 'running';
|
|
148
|
+
|
|
149
|
+
const exclude = new Set([title, query, researchId, cid].filter(s => typeof s === 'string'));
|
|
150
|
+
const notes = collectResearchNotes(data, exclude);
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
research_id: researchId,
|
|
154
|
+
state,
|
|
155
|
+
title: typeof title === 'string' ? title : null,
|
|
156
|
+
query: typeof query === 'string' ? query : null,
|
|
157
|
+
cid: typeof cid === 'string' ? cid : null,
|
|
158
|
+
notes,
|
|
159
|
+
done,
|
|
160
|
+
raw_state,
|
|
161
|
+
raw: payload,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
module.exports = {
|
|
166
|
+
iterNested,
|
|
167
|
+
findFirstMatch,
|
|
168
|
+
findFirstString,
|
|
169
|
+
extractResearchId,
|
|
170
|
+
extractChatId,
|
|
171
|
+
collectResearchNotes,
|
|
172
|
+
findFirstDictKey,
|
|
173
|
+
extractDeepResearchPlan,
|
|
174
|
+
extractDeepResearchStatusPayload,
|
|
175
|
+
};
|