Skip to content

Commit 1987802

Browse files
Miriadresearch
andcommitted
fix: rewrite pollResearch response parsing to match actual API format
The NotebookLM POLL_RESEARCH response is a nested array of tasks: [[taskId, [?, queryInfo, ?, sourcesAndSummary, statusCode]], ...] Not a flat array as previously assumed. Also adds debug logging to rpcCall and startResearch to diagnose response parsing issues during local testing. Co-authored-by: research <research@miriad.systems>
1 parent 0c1cfd0 commit 1987802

File tree

1 file changed

+78
-37
lines changed

1 file changed

+78
-37
lines changed

lib/services/notebooklm/client.ts

Lines changed: 78 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ export class NotebookLMClient {
144144
}
145145

146146
const responseText = await response.text();
147+
console.log(`[NotebookLM] RPC ${methodId} response: ${responseText.length} bytes, first 200: ${responseText.substring(0, 200)}`);
147148
return decodeResponse(responseText, methodId);
148149
}
149150

@@ -341,6 +342,7 @@ export class NotebookLMClient {
341342
}
342343

343344
const result = await this.rpcCall(methodId, params);
345+
console.log(`[NotebookLM] startResearch raw result:`, JSON.stringify(result));
344346
const resultArr = result as unknown[];
345347

346348
// Extract task ID from response
@@ -380,6 +382,8 @@ export class NotebookLMClient {
380382
EXTENDED_FETCH_TIMEOUT_MS
381383
);
382384

385+
console.log(`[NotebookLM] pollResearch raw result:`, JSON.stringify(result)?.substring(0, 500));
386+
383387
const resultArr = result as unknown[];
384388
const emptyResult: ResearchResult = {
385389
taskId: '',
@@ -389,54 +393,91 @@ export class NotebookLMClient {
389393
summary: '',
390394
};
391395

392-
if (!Array.isArray(resultArr)) {
396+
if (!Array.isArray(resultArr) || resultArr.length === 0) {
393397
return emptyResult;
394398
}
395399

396-
// Extract task ID
397-
const taskId =
398-
typeof resultArr[0] === 'string' ? resultArr[0] : '';
399-
400+
// Response format (from notebooklm-py _research.py):
401+
// result = [[task_data, ...], ...] or [task_data, ...]
402+
// task_data = [task_id, task_info]
403+
// task_info = [?, query_info, ?, sources_and_summary, status_code]
404+
// query_info = [query_text, ...]
405+
// sources_and_summary = [sources_array, summary_text]
400406
// Research status: 1=in_progress, 2=completed
401-
const statusCode = safeGet(resultArr, 1);
402-
let status: ResearchResult['status'] = 'no_research';
403-
if (statusCode === 1) {
404-
status = 'in_progress';
405-
} else if (statusCode === 2) {
406-
status = 'completed';
407+
408+
// Unwrap if double-nested
409+
let tasks = resultArr;
410+
if (
411+
Array.isArray(tasks[0]) &&
412+
tasks[0].length > 0 &&
413+
Array.isArray(tasks[0][0]) &&
414+
tasks[0][0].length > 0 &&
415+
Array.isArray(tasks[0][0][0])
416+
) {
417+
tasks = tasks[0] as unknown[];
418+
} else if (
419+
Array.isArray(tasks[0]) &&
420+
tasks[0].length > 0 &&
421+
typeof tasks[0][0] === 'string'
422+
) {
423+
// Already at task level: [[taskId, taskInfo], ...]
424+
// wrap in array for uniform processing
425+
tasks = [tasks];
407426
}
408427

409-
// Extract query
410-
const query =
411-
typeof safeGet(resultArr, 2) === 'string'
412-
? (safeGet(resultArr, 2) as string)
413-
: '';
414-
415-
// Extract sources — typically an array of [url, title] pairs
416-
const sources: Array<{ url: string; title: string }> = [];
417-
const sourcesArr = safeGet(resultArr, 3);
418-
if (Array.isArray(sourcesArr)) {
419-
for (const src of sourcesArr) {
420-
if (Array.isArray(src)) {
421-
const srcUrl = safeGet(src, 2, 0);
422-
const srcTitle = safeGet(src, 2, 1);
423-
if (typeof srcUrl === 'string') {
424-
sources.push({
425-
url: srcUrl,
426-
title: typeof srcTitle === 'string' ? srcTitle : '',
427-
});
428+
// Find the most recent task
429+
for (const taskData of tasks) {
430+
if (!Array.isArray(taskData) || taskData.length < 2) continue;
431+
432+
const taskId = taskData[0];
433+
const taskInfo = taskData[1];
434+
435+
if (typeof taskId !== 'string' || !Array.isArray(taskInfo)) continue;
436+
437+
const queryInfo = taskInfo[1];
438+
const sourcesAndSummary = taskInfo[3];
439+
const statusCode = taskInfo[4];
440+
441+
const queryText =
442+
Array.isArray(queryInfo) && typeof queryInfo[0] === 'string'
443+
? queryInfo[0]
444+
: '';
445+
446+
// Parse sources
447+
const sources: Array<{ url: string; title: string }> = [];
448+
let summary = '';
449+
450+
if (Array.isArray(sourcesAndSummary) && sourcesAndSummary.length >= 1) {
451+
const sourcesData = Array.isArray(sourcesAndSummary[0])
452+
? sourcesAndSummary[0]
453+
: [];
454+
if (
455+
sourcesAndSummary.length >= 2 &&
456+
typeof sourcesAndSummary[1] === 'string'
457+
) {
458+
summary = sourcesAndSummary[1];
459+
}
460+
461+
for (const src of sourcesData) {
462+
if (!Array.isArray(src) || src.length < 2) continue;
463+
const url =
464+
typeof src[0] === 'string' ? src[0] : '';
465+
const title =
466+
typeof src[1] === 'string' ? src[1] : '';
467+
if (title || url) {
468+
sources.push({ url, title });
428469
}
429470
}
430471
}
431-
}
432472

433-
// Extract summary
434-
const summary =
435-
typeof safeGet(resultArr, 4) === 'string'
436-
? (safeGet(resultArr, 4) as string)
437-
: '';
473+
// Research status: 1=in_progress, 2=completed
474+
const status: ResearchResult['status'] =
475+
statusCode === 2 ? 'completed' : 'in_progress';
476+
477+
return { taskId, status, query: queryText, sources, summary };
478+
}
438479

439-
return { taskId, status, query, sources, summary };
480+
return emptyResult;
440481
}
441482

442483
/**

0 commit comments

Comments
 (0)