10x-chat 0.2.3 → 0.3.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.
Files changed (93) hide show
  1. package/dist/core/bundle.d.ts.map +1 -1
  2. package/dist/core/bundle.js +0 -2
  3. package/dist/core/bundle.js.map +1 -1
  4. package/dist/notebooklm/api/artifacts.d.ts +60 -0
  5. package/dist/notebooklm/api/artifacts.d.ts.map +1 -0
  6. package/dist/notebooklm/api/artifacts.js +1246 -0
  7. package/dist/notebooklm/api/artifacts.js.map +1 -0
  8. package/dist/notebooklm/api/chat.d.ts +21 -0
  9. package/dist/notebooklm/api/chat.d.ts.map +1 -0
  10. package/dist/notebooklm/api/chat.js +353 -0
  11. package/dist/notebooklm/api/chat.js.map +1 -0
  12. package/dist/notebooklm/api/notebooks.d.ts +22 -0
  13. package/dist/notebooklm/api/notebooks.d.ts.map +1 -0
  14. package/dist/notebooklm/api/notebooks.js +109 -0
  15. package/dist/notebooklm/api/notebooks.js.map +1 -0
  16. package/dist/notebooklm/api/notes.d.ts +18 -0
  17. package/dist/notebooklm/api/notes.d.ts.map +1 -0
  18. package/dist/notebooklm/api/notes.js +140 -0
  19. package/dist/notebooklm/api/notes.js.map +1 -0
  20. package/dist/notebooklm/api/research.d.ts +12 -0
  21. package/dist/notebooklm/api/research.d.ts.map +1 -0
  22. package/dist/notebooklm/api/research.js +168 -0
  23. package/dist/notebooklm/api/research.js.map +1 -0
  24. package/dist/notebooklm/api/settings.d.ts +11 -0
  25. package/dist/notebooklm/api/settings.d.ts.map +1 -0
  26. package/dist/notebooklm/api/settings.js +55 -0
  27. package/dist/notebooklm/api/settings.js.map +1 -0
  28. package/dist/notebooklm/api/sharing.d.ts +14 -0
  29. package/dist/notebooklm/api/sharing.d.ts.map +1 -0
  30. package/dist/notebooklm/api/sharing.js +70 -0
  31. package/dist/notebooklm/api/sharing.js.map +1 -0
  32. package/dist/notebooklm/api/sources.d.ts +34 -0
  33. package/dist/notebooklm/api/sources.d.ts.map +1 -0
  34. package/dist/notebooklm/api/sources.js +539 -0
  35. package/dist/notebooklm/api/sources.js.map +1 -0
  36. package/dist/notebooklm/auth.d.ts +30 -0
  37. package/dist/notebooklm/auth.d.ts.map +1 -0
  38. package/dist/notebooklm/auth.js +265 -0
  39. package/dist/notebooklm/auth.js.map +1 -0
  40. package/dist/notebooklm/client.d.ts +29 -0
  41. package/dist/notebooklm/client.d.ts.map +1 -0
  42. package/dist/notebooklm/client.js +94 -0
  43. package/dist/notebooklm/client.js.map +1 -0
  44. package/dist/notebooklm/core.d.ts +52 -0
  45. package/dist/notebooklm/core.d.ts.map +1 -0
  46. package/dist/notebooklm/core.js +330 -0
  47. package/dist/notebooklm/core.js.map +1 -0
  48. package/dist/notebooklm/errors.d.ts +189 -0
  49. package/dist/notebooklm/errors.d.ts.map +1 -0
  50. package/dist/notebooklm/errors.js +325 -0
  51. package/dist/notebooklm/errors.js.map +1 -0
  52. package/dist/notebooklm/index.d.ts +16 -0
  53. package/dist/notebooklm/index.d.ts.map +1 -0
  54. package/dist/notebooklm/index.js +16 -0
  55. package/dist/notebooklm/index.js.map +1 -0
  56. package/dist/notebooklm/paths.d.ts +12 -0
  57. package/dist/notebooklm/paths.d.ts.map +1 -0
  58. package/dist/notebooklm/paths.js +49 -0
  59. package/dist/notebooklm/paths.js.map +1 -0
  60. package/dist/notebooklm/rpc/decoder.d.ts +8 -0
  61. package/dist/notebooklm/rpc/decoder.d.ts.map +1 -0
  62. package/dist/notebooklm/rpc/decoder.js +182 -0
  63. package/dist/notebooklm/rpc/decoder.js.map +1 -0
  64. package/dist/notebooklm/rpc/encoder.d.ts +12 -0
  65. package/dist/notebooklm/rpc/encoder.d.ts.map +1 -0
  66. package/dist/notebooklm/rpc/encoder.js +36 -0
  67. package/dist/notebooklm/rpc/encoder.js.map +1 -0
  68. package/dist/notebooklm/rpc/index.d.ts +4 -0
  69. package/dist/notebooklm/rpc/index.d.ts.map +1 -0
  70. package/dist/notebooklm/rpc/index.js +4 -0
  71. package/dist/notebooklm/rpc/index.js.map +1 -0
  72. package/dist/notebooklm/rpc/types.d.ts +168 -0
  73. package/dist/notebooklm/rpc/types.d.ts.map +1 -0
  74. package/dist/notebooklm/rpc/types.js +206 -0
  75. package/dist/notebooklm/rpc/types.js.map +1 -0
  76. package/dist/notebooklm/types.d.ts +220 -0
  77. package/dist/notebooklm/types.d.ts.map +1 -0
  78. package/dist/notebooklm/types.js +488 -0
  79. package/dist/notebooklm/types.js.map +1 -0
  80. package/dist/providers/index.d.ts +1 -0
  81. package/dist/providers/index.d.ts.map +1 -1
  82. package/dist/providers/index.js +1 -0
  83. package/dist/providers/index.js.map +1 -1
  84. package/dist/providers/notebooklm.d.ts +4 -0
  85. package/dist/providers/notebooklm.d.ts.map +1 -0
  86. package/dist/providers/notebooklm.js +98 -0
  87. package/dist/providers/notebooklm.js.map +1 -0
  88. package/dist/providers/registry.d.ts.map +1 -1
  89. package/dist/providers/registry.js +2 -0
  90. package/dist/providers/registry.js.map +1 -1
  91. package/dist/types.d.ts +1 -1
  92. package/dist/types.d.ts.map +1 -1
  93. package/package.json +1 -1
@@ -0,0 +1,1246 @@
1
+ import { mkdir, rename, rm, writeFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { setTimeout as sleep } from 'node:timers/promises';
4
+ import { ArtifactDownloadError, ArtifactNotFoundError, ArtifactNotReadyError, ArtifactParseError, RPCError, ValidationError, } from '../errors.js';
5
+ import { ArtifactStatus, ArtifactTypeCode, artifactStatusToStr, ExportType, ReportFormat, RPCMethod, } from '../rpc/types.js';
6
+ import { Artifact, ArtifactType, createGenerationStatus, } from '../types.js';
7
+ const MEDIA_ARTIFACT_TYPES = new Set([
8
+ ArtifactTypeCode.AUDIO,
9
+ ArtifactTypeCode.VIDEO,
10
+ ArtifactTypeCode.INFOGRAPHIC,
11
+ ArtifactTypeCode.SLIDE_DECK,
12
+ ]);
13
+ function extractAppData(htmlContent) {
14
+ const match = /data-app-data="([^"]+)"/.exec(htmlContent);
15
+ if (!match) {
16
+ throw new ArtifactParseError('quiz/flashcard', {
17
+ details: 'No data-app-data attribute found in HTML',
18
+ });
19
+ }
20
+ const encodedJson = match[1] ?? '';
21
+ const decodedJson = encodedJson
22
+ .replaceAll('"', '"')
23
+ .replaceAll(''', "'")
24
+ .replaceAll('&', '&')
25
+ .replaceAll('&lt;', '<')
26
+ .replaceAll('&gt;', '>');
27
+ return JSON.parse(decodedJson);
28
+ }
29
+ function formatQuizMarkdown(title, questions) {
30
+ const lines = [`# ${title}`, ''];
31
+ for (let index = 0; index < questions.length; index += 1) {
32
+ const question = questions[index] ?? {};
33
+ lines.push(`## Question ${index + 1}`);
34
+ lines.push(typeof question.question === 'string' ? question.question : '');
35
+ lines.push('');
36
+ const options = Array.isArray(question.answerOptions) ? question.answerOptions : [];
37
+ for (const option of options) {
38
+ if (!option || typeof option !== 'object') {
39
+ continue;
40
+ }
41
+ const marker = option.isCorrect ? '[x]' : '[ ]';
42
+ const text = typeof option.text === 'string'
43
+ ? option.text
44
+ : '';
45
+ lines.push(`- ${marker} ${text}`);
46
+ }
47
+ if (typeof question.hint === 'string' && question.hint.length > 0) {
48
+ lines.push('');
49
+ lines.push(`**Hint:** ${question.hint}`);
50
+ }
51
+ lines.push('');
52
+ }
53
+ return lines.join('\n');
54
+ }
55
+ function formatFlashcardsMarkdown(title, cards) {
56
+ const lines = [`# ${title}`, ''];
57
+ for (let index = 0; index < cards.length; index += 1) {
58
+ const card = cards[index] ?? {};
59
+ const front = typeof card.f === 'string' ? card.f : '';
60
+ const back = typeof card.b === 'string' ? card.b : '';
61
+ lines.push(`## Card ${index + 1}`);
62
+ lines.push('');
63
+ lines.push(`**Q:** ${front}`);
64
+ lines.push('');
65
+ lines.push(`**A:** ${back}`);
66
+ lines.push('');
67
+ lines.push('---');
68
+ lines.push('');
69
+ }
70
+ return lines.join('\n');
71
+ }
72
+ function extractCellText(cell) {
73
+ if (typeof cell === 'string') {
74
+ return cell;
75
+ }
76
+ if (typeof cell === 'number') {
77
+ return '';
78
+ }
79
+ if (Array.isArray(cell)) {
80
+ return cell.map((item) => extractCellText(item)).join('');
81
+ }
82
+ return '';
83
+ }
84
+ function parseDataTable(rawData) {
85
+ try {
86
+ const rowsArray = rawData[0][0][0][0][4][2];
87
+ if (!Array.isArray(rowsArray) || rowsArray.length === 0) {
88
+ throw new ArtifactParseError('data_table', { details: 'Empty data table' });
89
+ }
90
+ const headers = [];
91
+ const rows = [];
92
+ for (let i = 0; i < rowsArray.length; i += 1) {
93
+ const rowSection = rowsArray[i];
94
+ if (!Array.isArray(rowSection) || rowSection.length < 3 || !Array.isArray(rowSection[2])) {
95
+ continue;
96
+ }
97
+ const rowValues = rowSection[2].map((cell) => extractCellText(cell));
98
+ if (i === 0) {
99
+ headers.push(...rowValues);
100
+ }
101
+ else {
102
+ rows.push(rowValues);
103
+ }
104
+ }
105
+ if (headers.length === 0) {
106
+ throw new ArtifactParseError('data_table', {
107
+ details: 'Failed to extract headers from data table',
108
+ });
109
+ }
110
+ return [headers, rows];
111
+ }
112
+ catch (error) {
113
+ if (error instanceof ArtifactParseError) {
114
+ throw error;
115
+ }
116
+ throw new ArtifactParseError('data_table', {
117
+ details: `Failed to parse data table structure: ${String(error)}`,
118
+ cause: error instanceof Error ? error : null,
119
+ });
120
+ }
121
+ }
122
+ async function fetchWithTimeout(url, init, timeoutMs) {
123
+ const controller = new AbortController();
124
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
125
+ try {
126
+ return await fetch(url, {
127
+ ...init,
128
+ signal: controller.signal,
129
+ });
130
+ }
131
+ finally {
132
+ clearTimeout(timer);
133
+ }
134
+ }
135
+ export class ArtifactsAPI {
136
+ core;
137
+ notes;
138
+ constructor(core, notesApi) {
139
+ this.core = core;
140
+ this.notes = notesApi;
141
+ }
142
+ async list(notebookId, artifactType) {
143
+ console.debug(`Listing artifacts in notebook ${notebookId}`);
144
+ const artifacts = [];
145
+ const params = [[2], notebookId, 'NOT artifact.status = "ARTIFACT_STATUS_SUGGESTED"'];
146
+ const result = await this.core.rpcCall(RPCMethod.LIST_ARTIFACTS, params, `/notebook/${notebookId}`, true);
147
+ let artifactsData = [];
148
+ if (Array.isArray(result) && result.length > 0) {
149
+ artifactsData = Array.isArray(result[0]) ? result[0] : result;
150
+ }
151
+ for (const artifactData of artifactsData) {
152
+ if (!Array.isArray(artifactData) || artifactData.length === 0) {
153
+ continue;
154
+ }
155
+ const artifact = Artifact.fromApiResponse(artifactData);
156
+ if (!artifactType || artifact.kind === artifactType) {
157
+ artifacts.push(artifact);
158
+ }
159
+ }
160
+ if (!artifactType || artifactType === ArtifactType.MIND_MAP) {
161
+ try {
162
+ const mindMaps = await this.notes.listMindMaps(notebookId);
163
+ for (const mindMapData of mindMaps) {
164
+ if (!Array.isArray(mindMapData)) {
165
+ continue;
166
+ }
167
+ const mindMapArtifact = Artifact.fromMindMap(mindMapData);
168
+ if (mindMapArtifact && (!artifactType || mindMapArtifact.kind === artifactType)) {
169
+ artifacts.push(mindMapArtifact);
170
+ }
171
+ }
172
+ }
173
+ catch (error) {
174
+ console.warn(`Failed to fetch mind maps: ${String(error)}`);
175
+ }
176
+ }
177
+ return artifacts;
178
+ }
179
+ async get(notebookId, artifactId) {
180
+ console.debug(`Getting artifact ${artifactId} from notebook ${notebookId}`);
181
+ const artifacts = await this.list(notebookId);
182
+ return artifacts.find((artifact) => artifact.id === artifactId) ?? null;
183
+ }
184
+ async listAudio(notebookId) {
185
+ return this.list(notebookId, ArtifactType.AUDIO);
186
+ }
187
+ async listVideo(notebookId) {
188
+ return this.list(notebookId, ArtifactType.VIDEO);
189
+ }
190
+ async listReports(notebookId) {
191
+ return this.list(notebookId, ArtifactType.REPORT);
192
+ }
193
+ async listQuizzes(notebookId) {
194
+ return this.list(notebookId, ArtifactType.QUIZ);
195
+ }
196
+ async listFlashcards(notebookId) {
197
+ return this.list(notebookId, ArtifactType.FLASHCARDS);
198
+ }
199
+ async listInfographics(notebookId) {
200
+ return this.list(notebookId, ArtifactType.INFOGRAPHIC);
201
+ }
202
+ async listSlideDecks(notebookId) {
203
+ return this.list(notebookId, ArtifactType.SLIDE_DECK);
204
+ }
205
+ async listDataTables(notebookId) {
206
+ return this.list(notebookId, ArtifactType.DATA_TABLE);
207
+ }
208
+ async generateAudio(notebookId, sourceIds, language = 'en', instructions, audioFormat, audioLength) {
209
+ let ids = sourceIds;
210
+ if (!ids) {
211
+ ids = await this.core.getSourceIds(notebookId);
212
+ }
213
+ const sourceIdsTriple = ids.length > 0 ? ids.map((sid) => [[sid]]) : [];
214
+ const sourceIdsDouble = ids.length > 0 ? ids.map((sid) => [sid]) : [];
215
+ const formatCode = audioFormat ?? null;
216
+ const lengthCode = audioLength ?? null;
217
+ const params = [
218
+ [2],
219
+ notebookId,
220
+ [
221
+ null,
222
+ null,
223
+ 1,
224
+ sourceIdsTriple,
225
+ null,
226
+ null,
227
+ [
228
+ null,
229
+ [instructions ?? null, lengthCode, null, sourceIdsDouble, language, null, formatCode],
230
+ ],
231
+ ],
232
+ ];
233
+ return this.callGenerate(notebookId, params);
234
+ }
235
+ async generateVideo(notebookId, sourceIds, language = 'en', instructions, videoFormat, videoStyle) {
236
+ let ids = sourceIds;
237
+ if (!ids) {
238
+ ids = await this.core.getSourceIds(notebookId);
239
+ }
240
+ const sourceIdsTriple = ids.length > 0 ? ids.map((sid) => [[sid]]) : [];
241
+ const sourceIdsDouble = ids.length > 0 ? ids.map((sid) => [sid]) : [];
242
+ const formatCode = videoFormat ?? null;
243
+ const styleCode = videoStyle ?? null;
244
+ const params = [
245
+ [2],
246
+ notebookId,
247
+ [
248
+ null,
249
+ null,
250
+ 3,
251
+ sourceIdsTriple,
252
+ null,
253
+ null,
254
+ null,
255
+ null,
256
+ [
257
+ null,
258
+ null,
259
+ [sourceIdsDouble, language, instructions ?? null, null, formatCode, styleCode],
260
+ ],
261
+ ],
262
+ ];
263
+ return this.callGenerate(notebookId, params);
264
+ }
265
+ async generateReport(notebookId, reportFormat = ReportFormat.BRIEFING_DOC, sourceIds, language = 'en', customPrompt) {
266
+ let ids = sourceIds;
267
+ if (!ids) {
268
+ ids = await this.core.getSourceIds(notebookId);
269
+ }
270
+ const formatConfigs = {
271
+ [ReportFormat.BRIEFING_DOC]: {
272
+ title: 'Briefing Doc',
273
+ description: 'Key insights and important quotes',
274
+ prompt: 'Create a comprehensive briefing document that includes an Executive Summary, detailed analysis of key themes, important quotes with context, and actionable insights.',
275
+ },
276
+ [ReportFormat.STUDY_GUIDE]: {
277
+ title: 'Study Guide',
278
+ description: 'Short-answer quiz, essay questions, glossary',
279
+ prompt: 'Create a comprehensive study guide that includes key concepts, short-answer practice questions, essay prompts for deeper exploration, and a glossary of important terms.',
280
+ },
281
+ [ReportFormat.BLOG_POST]: {
282
+ title: 'Blog Post',
283
+ description: 'Insightful takeaways in readable article format',
284
+ prompt: 'Write an engaging blog post that presents the key insights in an accessible, reader-friendly format. Include an attention-grabbing introduction, well-organized sections, and a compelling conclusion with takeaways.',
285
+ },
286
+ [ReportFormat.CUSTOM]: {
287
+ title: 'Custom Report',
288
+ description: 'Custom format',
289
+ prompt: customPrompt ?? 'Create a report based on the provided sources.',
290
+ },
291
+ };
292
+ const config = formatConfigs[reportFormat];
293
+ const sourceIdsTriple = ids.length > 0 ? ids.map((sid) => [[sid]]) : [];
294
+ const sourceIdsDouble = ids.length > 0 ? ids.map((sid) => [sid]) : [];
295
+ const params = [
296
+ [2],
297
+ notebookId,
298
+ [
299
+ null,
300
+ null,
301
+ 2,
302
+ sourceIdsTriple,
303
+ null,
304
+ null,
305
+ null,
306
+ [
307
+ null,
308
+ [
309
+ config.title,
310
+ config.description,
311
+ null,
312
+ sourceIdsDouble,
313
+ language,
314
+ config.prompt,
315
+ null,
316
+ true,
317
+ ],
318
+ ],
319
+ ],
320
+ ];
321
+ return this.callGenerate(notebookId, params);
322
+ }
323
+ async generateStudyGuide(notebookId, sourceIds, language = 'en') {
324
+ return this.generateReport(notebookId, ReportFormat.STUDY_GUIDE, sourceIds, language);
325
+ }
326
+ async generateQuiz(notebookId, sourceIds, instructions, quantity, difficulty) {
327
+ let ids = sourceIds;
328
+ if (!ids) {
329
+ ids = await this.core.getSourceIds(notebookId);
330
+ }
331
+ const sourceIdsTriple = ids.length > 0 ? ids.map((sid) => [[sid]]) : [];
332
+ const quantityCode = quantity ?? null;
333
+ const difficultyCode = difficulty ?? null;
334
+ const params = [
335
+ [2],
336
+ notebookId,
337
+ [
338
+ null,
339
+ null,
340
+ 4,
341
+ sourceIdsTriple,
342
+ null,
343
+ null,
344
+ null,
345
+ null,
346
+ null,
347
+ [
348
+ null,
349
+ [2, null, instructions ?? null, null, null, null, null, [quantityCode, difficultyCode]],
350
+ ],
351
+ ],
352
+ ];
353
+ return this.callGenerate(notebookId, params);
354
+ }
355
+ async generateFlashcards(notebookId, sourceIds, instructions, quantity, difficulty) {
356
+ let ids = sourceIds;
357
+ if (!ids) {
358
+ ids = await this.core.getSourceIds(notebookId);
359
+ }
360
+ const sourceIdsTriple = ids.length > 0 ? ids.map((sid) => [[sid]]) : [];
361
+ const quantityCode = quantity ?? null;
362
+ const difficultyCode = difficulty ?? null;
363
+ const params = [
364
+ [2],
365
+ notebookId,
366
+ [
367
+ null,
368
+ null,
369
+ 4,
370
+ sourceIdsTriple,
371
+ null,
372
+ null,
373
+ null,
374
+ null,
375
+ null,
376
+ [null, [1, null, instructions ?? null, null, null, null, [difficultyCode, quantityCode]]],
377
+ ],
378
+ ];
379
+ return this.callGenerate(notebookId, params);
380
+ }
381
+ async generateInfographic(notebookId, sourceIds, language = 'en', instructions, orientation, detailLevel) {
382
+ let ids = sourceIds;
383
+ if (!ids) {
384
+ ids = await this.core.getSourceIds(notebookId);
385
+ }
386
+ const sourceIdsTriple = ids.length > 0 ? ids.map((sid) => [[sid]]) : [];
387
+ const orientationCode = orientation ?? null;
388
+ const detailCode = detailLevel ?? null;
389
+ const params = [
390
+ [2],
391
+ notebookId,
392
+ [
393
+ null,
394
+ null,
395
+ 7,
396
+ sourceIdsTriple,
397
+ null,
398
+ null,
399
+ null,
400
+ null,
401
+ null,
402
+ null,
403
+ null,
404
+ null,
405
+ null,
406
+ null,
407
+ [[instructions ?? null, language, null, orientationCode, detailCode]],
408
+ ],
409
+ ];
410
+ return this.callGenerate(notebookId, params);
411
+ }
412
+ async generateSlideDeck(notebookId, sourceIds, language = 'en', instructions, slideFormat, slideLength) {
413
+ let ids = sourceIds;
414
+ if (!ids) {
415
+ ids = await this.core.getSourceIds(notebookId);
416
+ }
417
+ const sourceIdsTriple = ids.length > 0 ? ids.map((sid) => [[sid]]) : [];
418
+ const formatCode = slideFormat ?? null;
419
+ const lengthCode = slideLength ?? null;
420
+ const params = [
421
+ [2],
422
+ notebookId,
423
+ [
424
+ null,
425
+ null,
426
+ 8,
427
+ sourceIdsTriple,
428
+ null,
429
+ null,
430
+ null,
431
+ null,
432
+ null,
433
+ null,
434
+ null,
435
+ null,
436
+ null,
437
+ null,
438
+ null,
439
+ null,
440
+ [[instructions ?? null, language, formatCode, lengthCode]],
441
+ ],
442
+ ];
443
+ return this.callGenerate(notebookId, params);
444
+ }
445
+ async generateDataTable(notebookId, sourceIds, language = 'en', instructions) {
446
+ let ids = sourceIds;
447
+ if (!ids) {
448
+ ids = await this.core.getSourceIds(notebookId);
449
+ }
450
+ const sourceIdsTriple = ids.length > 0 ? ids.map((sid) => [[sid]]) : [];
451
+ const params = [
452
+ [2],
453
+ notebookId,
454
+ [
455
+ null,
456
+ null,
457
+ 9,
458
+ sourceIdsTriple,
459
+ null,
460
+ null,
461
+ null,
462
+ null,
463
+ null,
464
+ null,
465
+ null,
466
+ null,
467
+ null,
468
+ null,
469
+ null,
470
+ null,
471
+ null,
472
+ null,
473
+ [null, [instructions ?? null, language]],
474
+ ],
475
+ ];
476
+ return this.callGenerate(notebookId, params);
477
+ }
478
+ async generateMindMap(notebookId, sourceIds) {
479
+ let ids = sourceIds;
480
+ if (!ids) {
481
+ ids = await this.core.getSourceIds(notebookId);
482
+ }
483
+ const sourceIdsNested = ids.length > 0 ? ids.map((sid) => [[sid]]) : [];
484
+ const params = [
485
+ sourceIdsNested,
486
+ null,
487
+ null,
488
+ null,
489
+ null,
490
+ ['interactive_mindmap', [['[CONTEXT]', '']], ''],
491
+ null,
492
+ [2, null, [1]],
493
+ ];
494
+ const result = await this.core.rpcCall(RPCMethod.GENERATE_MIND_MAP, params, `/notebook/${notebookId}`, true);
495
+ if (Array.isArray(result) &&
496
+ result.length > 0 &&
497
+ Array.isArray(result[0]) &&
498
+ result[0].length > 0) {
499
+ const mindMapJson = result[0][0];
500
+ let mindMapData = null;
501
+ let normalizedJson = '';
502
+ if (typeof mindMapJson === 'string') {
503
+ normalizedJson = mindMapJson;
504
+ try {
505
+ mindMapData = JSON.parse(mindMapJson);
506
+ }
507
+ catch {
508
+ mindMapData = mindMapJson;
509
+ }
510
+ }
511
+ else {
512
+ mindMapData = mindMapJson;
513
+ normalizedJson = JSON.stringify(mindMapJson);
514
+ }
515
+ let title = 'Mind Map';
516
+ if (mindMapData &&
517
+ typeof mindMapData === 'object' &&
518
+ typeof mindMapData.name === 'string') {
519
+ title = mindMapData.name;
520
+ }
521
+ const note = await this.notes.create(notebookId, title, normalizedJson);
522
+ return {
523
+ mindMap: mindMapData,
524
+ noteId: note.id,
525
+ };
526
+ }
527
+ return {
528
+ mindMap: null,
529
+ noteId: null,
530
+ };
531
+ }
532
+ async downloadAudio(notebookId, outputPath, artifactId) {
533
+ const artifactsData = await this.listRaw(notebookId);
534
+ const candidates = artifactsData.filter((artifact) => Array.isArray(artifact) &&
535
+ artifact.length > 4 &&
536
+ artifact[2] === ArtifactTypeCode.AUDIO &&
537
+ artifact[4] === ArtifactStatus.COMPLETED);
538
+ let audioArtifact;
539
+ if (artifactId) {
540
+ audioArtifact = candidates.find((artifact) => artifact[0] === artifactId);
541
+ if (!audioArtifact) {
542
+ throw new ArtifactNotReadyError('audio', { artifactId });
543
+ }
544
+ }
545
+ else {
546
+ audioArtifact = candidates[0];
547
+ }
548
+ if (!audioArtifact) {
549
+ throw new ArtifactNotReadyError('audio');
550
+ }
551
+ try {
552
+ const metadata = audioArtifact[6];
553
+ if (!Array.isArray(metadata) || metadata.length <= 5 || !Array.isArray(metadata[5])) {
554
+ throw new ArtifactParseError('audio', {
555
+ artifactId: artifactId ?? null,
556
+ details: 'Invalid audio metadata structure',
557
+ });
558
+ }
559
+ const mediaList = metadata[5];
560
+ if (mediaList.length === 0) {
561
+ throw new ArtifactParseError('audio', {
562
+ artifactId: artifactId ?? null,
563
+ details: 'No media URLs found',
564
+ });
565
+ }
566
+ let url = null;
567
+ for (const item of mediaList) {
568
+ if (Array.isArray(item) && typeof item[0] === 'string' && item[2] === 'audio/mp4') {
569
+ url = item[0];
570
+ break;
571
+ }
572
+ }
573
+ if (!url && Array.isArray(mediaList[0]) && typeof mediaList[0][0] === 'string') {
574
+ url = mediaList[0][0];
575
+ }
576
+ if (!url) {
577
+ throw new ArtifactDownloadError('audio', {
578
+ artifactId: artifactId ?? null,
579
+ details: 'Could not extract download URL',
580
+ });
581
+ }
582
+ return this.downloadUrl(url, outputPath);
583
+ }
584
+ catch (error) {
585
+ if (error instanceof ArtifactParseError || error instanceof ArtifactDownloadError) {
586
+ throw error;
587
+ }
588
+ throw new ArtifactParseError('audio', {
589
+ artifactId: artifactId ?? null,
590
+ details: `Failed to parse audio artifact structure: ${String(error)}`,
591
+ cause: error instanceof Error ? error : null,
592
+ });
593
+ }
594
+ }
595
+ async downloadVideo(notebookId, outputPath, artifactId) {
596
+ const artifactsData = await this.listRaw(notebookId);
597
+ const candidates = artifactsData.filter((artifact) => Array.isArray(artifact) &&
598
+ artifact.length > 4 &&
599
+ artifact[2] === ArtifactTypeCode.VIDEO &&
600
+ artifact[4] === ArtifactStatus.COMPLETED);
601
+ let videoArtifact;
602
+ if (artifactId) {
603
+ videoArtifact = candidates.find((artifact) => artifact[0] === artifactId);
604
+ if (!videoArtifact) {
605
+ throw new ArtifactNotReadyError('video', { artifactId });
606
+ }
607
+ }
608
+ else {
609
+ videoArtifact = candidates[0];
610
+ }
611
+ if (!videoArtifact) {
612
+ throw new ArtifactNotReadyError('video_overview');
613
+ }
614
+ try {
615
+ if (videoArtifact.length <= 8 || !Array.isArray(videoArtifact[8])) {
616
+ throw new ArtifactParseError('video_artifact', { details: 'Invalid structure' });
617
+ }
618
+ const metadata = videoArtifact[8];
619
+ let mediaList = null;
620
+ for (const item of metadata) {
621
+ if (Array.isArray(item) &&
622
+ item.length > 0 &&
623
+ Array.isArray(item[0]) &&
624
+ item[0].length > 0 &&
625
+ typeof item[0][0] === 'string' &&
626
+ item[0][0].startsWith('http')) {
627
+ mediaList = item;
628
+ break;
629
+ }
630
+ }
631
+ if (!mediaList) {
632
+ throw new ArtifactParseError('media', { details: 'No media URLs found' });
633
+ }
634
+ let url = null;
635
+ for (const item of mediaList) {
636
+ if (Array.isArray(item) && typeof item[0] === 'string' && item[2] === 'video/mp4') {
637
+ url = item[0];
638
+ if (item[1] === 4) {
639
+ break;
640
+ }
641
+ }
642
+ }
643
+ if (!url && Array.isArray(mediaList[0]) && typeof mediaList[0][0] === 'string') {
644
+ url = mediaList[0][0];
645
+ }
646
+ if (!url) {
647
+ throw new ArtifactDownloadError('media', { details: 'Could not extract download URL' });
648
+ }
649
+ return this.downloadUrl(url, outputPath);
650
+ }
651
+ catch (error) {
652
+ if (error instanceof ArtifactParseError || error instanceof ArtifactDownloadError) {
653
+ throw error;
654
+ }
655
+ throw new ArtifactParseError('video_artifact', {
656
+ details: `Failed to parse structure: ${String(error)}`,
657
+ cause: error instanceof Error ? error : null,
658
+ });
659
+ }
660
+ }
661
+ async downloadInfographic(notebookId, outputPath, artifactId) {
662
+ const artifactsData = await this.listRaw(notebookId);
663
+ const candidates = artifactsData.filter((artifact) => Array.isArray(artifact) &&
664
+ artifact.length > 4 &&
665
+ artifact[2] === ArtifactTypeCode.INFOGRAPHIC &&
666
+ artifact[4] === ArtifactStatus.COMPLETED);
667
+ let infoArtifact;
668
+ if (artifactId) {
669
+ infoArtifact = candidates.find((artifact) => artifact[0] === artifactId);
670
+ if (!infoArtifact) {
671
+ throw new ArtifactNotReadyError('infographic', { artifactId });
672
+ }
673
+ }
674
+ else {
675
+ infoArtifact = candidates[0];
676
+ }
677
+ if (!infoArtifact) {
678
+ throw new ArtifactNotReadyError('infographic');
679
+ }
680
+ try {
681
+ let metadata = null;
682
+ for (let i = infoArtifact.length - 1; i >= 0; i -= 1) {
683
+ const item = infoArtifact[i];
684
+ if (!Array.isArray(item) || item.length === 0 || !Array.isArray(item[0])) {
685
+ continue;
686
+ }
687
+ if (item.length > 2 &&
688
+ Array.isArray(item[2]) &&
689
+ item[2].length > 0 &&
690
+ Array.isArray(item[2][0]) &&
691
+ item[2][0].length > 1) {
692
+ const imgData = item[2][0][1];
693
+ if (Array.isArray(imgData) &&
694
+ typeof imgData[0] === 'string' &&
695
+ imgData[0].startsWith('http')) {
696
+ metadata = item;
697
+ break;
698
+ }
699
+ }
700
+ }
701
+ if (!metadata) {
702
+ throw new ArtifactParseError('infographic', { details: 'Could not find metadata' });
703
+ }
704
+ const url = metadata[2][0][1][0];
705
+ if (typeof url !== 'string') {
706
+ throw new ArtifactDownloadError('infographic', {
707
+ details: 'Could not extract download URL',
708
+ });
709
+ }
710
+ return this.downloadUrl(url, outputPath);
711
+ }
712
+ catch (error) {
713
+ if (error instanceof ArtifactParseError || error instanceof ArtifactDownloadError) {
714
+ throw error;
715
+ }
716
+ throw new ArtifactParseError('infographic', {
717
+ details: `Failed to parse structure: ${String(error)}`,
718
+ cause: error instanceof Error ? error : null,
719
+ });
720
+ }
721
+ }
722
+ async downloadSlideDeck(notebookId, outputPath, artifactId) {
723
+ const artifactsData = await this.listRaw(notebookId);
724
+ const candidates = artifactsData.filter((artifact) => Array.isArray(artifact) &&
725
+ artifact.length > 4 &&
726
+ artifact[2] === ArtifactTypeCode.SLIDE_DECK &&
727
+ artifact[4] === ArtifactStatus.COMPLETED);
728
+ let slideArtifact;
729
+ if (artifactId) {
730
+ slideArtifact = candidates.find((artifact) => artifact[0] === artifactId);
731
+ if (!slideArtifact) {
732
+ throw new ArtifactNotReadyError('slide_deck', { artifactId });
733
+ }
734
+ }
735
+ else {
736
+ slideArtifact = candidates[0];
737
+ }
738
+ if (!slideArtifact) {
739
+ throw new ArtifactNotReadyError('slide_deck');
740
+ }
741
+ try {
742
+ if (slideArtifact.length <= 16 ||
743
+ !Array.isArray(slideArtifact[16]) ||
744
+ slideArtifact[16].length < 4) {
745
+ throw new ArtifactParseError('slide_deck_metadata', { details: 'Invalid structure' });
746
+ }
747
+ const pdfUrl = slideArtifact[16][3];
748
+ if (typeof pdfUrl !== 'string' || !pdfUrl.startsWith('http')) {
749
+ throw new ArtifactDownloadError('slide_deck', {
750
+ details: 'Could not find PDF download URL',
751
+ });
752
+ }
753
+ return this.downloadUrl(pdfUrl, outputPath);
754
+ }
755
+ catch (error) {
756
+ if (error instanceof ArtifactParseError || error instanceof ArtifactDownloadError) {
757
+ throw error;
758
+ }
759
+ throw new ArtifactParseError('slide_deck', {
760
+ details: `Failed to parse structure: ${String(error)}`,
761
+ cause: error instanceof Error ? error : null,
762
+ });
763
+ }
764
+ }
765
+ async getArtifactContent(notebookId, artifactId) {
766
+ const result = await this.core.rpcCall(RPCMethod.GET_INTERACTIVE_HTML, [artifactId], `/notebook/${notebookId}`, true);
767
+ if (Array.isArray(result) &&
768
+ result.length > 0 &&
769
+ Array.isArray(result[0]) &&
770
+ result[0].length > 9 &&
771
+ Array.isArray(result[0][9]) &&
772
+ typeof result[0][9][0] === 'string') {
773
+ return result[0][9][0];
774
+ }
775
+ return null;
776
+ }
777
+ async downloadInteractiveArtifact(notebookId, outputPath, artifactId, outputFormat, artifactType) {
778
+ const validFormats = ['json', 'markdown', 'html'];
779
+ if (!validFormats.includes(outputFormat)) {
780
+ throw new ValidationError(`Invalid outputFormat: '${outputFormat}'. Use one of: ${validFormats.join(', ')}`);
781
+ }
782
+ const isQuiz = artifactType === 'quiz';
783
+ const defaultTitle = isQuiz ? 'Untitled Quiz' : 'Untitled Flashcards';
784
+ const artifacts = isQuiz
785
+ ? await this.listQuizzes(notebookId)
786
+ : await this.listFlashcards(notebookId);
787
+ const completed = artifacts.filter((artifact) => artifact.isCompleted);
788
+ if (completed.length === 0) {
789
+ throw new ArtifactNotReadyError(artifactType);
790
+ }
791
+ completed.sort((left, right) => (right.createdAt ? right.createdAt.getTime() : 0) -
792
+ (left.createdAt ? left.createdAt.getTime() : 0));
793
+ let artifact = completed[0];
794
+ if (artifactId) {
795
+ artifact = completed.find((entry) => entry.id === artifactId) ?? artifact;
796
+ if (!artifact || artifact.id !== artifactId) {
797
+ throw new ArtifactNotFoundError(artifactId, artifactType);
798
+ }
799
+ }
800
+ const htmlContent = await this.getArtifactContent(notebookId, artifact.id);
801
+ if (!htmlContent) {
802
+ throw new ArtifactDownloadError(artifactType, { details: 'Failed to fetch content' });
803
+ }
804
+ let appData;
805
+ try {
806
+ appData = extractAppData(htmlContent);
807
+ }
808
+ catch (error) {
809
+ throw new ArtifactParseError(artifactType, {
810
+ details: `Failed to parse content: ${String(error)}`,
811
+ cause: error instanceof Error ? error : null,
812
+ });
813
+ }
814
+ const title = artifact.title || defaultTitle;
815
+ const content = this.formatInteractiveContent(appData, title, outputFormat, htmlContent, isQuiz);
816
+ await mkdir(path.dirname(outputPath), { recursive: true });
817
+ await writeFile(outputPath, content, 'utf-8');
818
+ return outputPath;
819
+ }
820
+ formatInteractiveContent(appData, title, outputFormat, htmlContent, isQuiz) {
821
+ if (outputFormat === 'html') {
822
+ return htmlContent;
823
+ }
824
+ if (isQuiz) {
825
+ const questions = Array.isArray(appData.quiz)
826
+ ? appData.quiz
827
+ : [];
828
+ if (outputFormat === 'markdown') {
829
+ return formatQuizMarkdown(title, questions);
830
+ }
831
+ return JSON.stringify({ title, questions }, null, 2);
832
+ }
833
+ const cards = Array.isArray(appData.flashcards)
834
+ ? appData.flashcards
835
+ : [];
836
+ if (outputFormat === 'markdown') {
837
+ return formatFlashcardsMarkdown(title, cards);
838
+ }
839
+ const normalized = cards.map((card) => ({
840
+ front: typeof card.f === 'string' ? card.f : '',
841
+ back: typeof card.b === 'string' ? card.b : '',
842
+ }));
843
+ return JSON.stringify({ title, cards: normalized }, null, 2);
844
+ }
845
+ async downloadReport(notebookId, outputPath, artifactId) {
846
+ const artifactsData = await this.listRaw(notebookId);
847
+ const candidates = artifactsData.filter((artifact) => Array.isArray(artifact) &&
848
+ artifact.length > 7 &&
849
+ artifact[2] === ArtifactTypeCode.REPORT &&
850
+ artifact[4] === ArtifactStatus.COMPLETED);
851
+ const reportArtifact = this.selectArtifact(candidates, artifactId, 'Report', 'report');
852
+ try {
853
+ const contentWrapper = reportArtifact[7];
854
+ const markdownContent = Array.isArray(contentWrapper) && contentWrapper.length > 0
855
+ ? contentWrapper[0]
856
+ : contentWrapper;
857
+ if (typeof markdownContent !== 'string') {
858
+ throw new ArtifactParseError('report_content', { details: 'Invalid structure' });
859
+ }
860
+ await mkdir(path.dirname(outputPath), { recursive: true });
861
+ await writeFile(outputPath, markdownContent, 'utf-8');
862
+ return outputPath;
863
+ }
864
+ catch (error) {
865
+ if (error instanceof ArtifactParseError) {
866
+ throw error;
867
+ }
868
+ throw new ArtifactParseError('report', {
869
+ details: `Failed to parse structure: ${String(error)}`,
870
+ cause: error instanceof Error ? error : null,
871
+ });
872
+ }
873
+ }
874
+ async downloadMindMap(notebookId, outputPath, artifactId) {
875
+ const mindMaps = await this.notes.listMindMaps(notebookId);
876
+ if (mindMaps.length === 0) {
877
+ throw new ArtifactNotReadyError('mind_map');
878
+ }
879
+ let mindMap = mindMaps[0];
880
+ if (artifactId) {
881
+ const matched = mindMaps.find((entry) => Array.isArray(entry) && entry[0] === artifactId);
882
+ if (!matched) {
883
+ throw new ArtifactNotFoundError(artifactId, 'mind_map');
884
+ }
885
+ mindMap = matched;
886
+ }
887
+ try {
888
+ if (!Array.isArray(mindMap) ||
889
+ !Array.isArray(mindMap[1]) ||
890
+ typeof mindMap[1][1] !== 'string') {
891
+ throw new ArtifactParseError('mind_map_content', { details: 'Invalid structure' });
892
+ }
893
+ const jsonData = JSON.parse(mindMap[1][1]);
894
+ await mkdir(path.dirname(outputPath), { recursive: true });
895
+ await writeFile(outputPath, JSON.stringify(jsonData, null, 2), 'utf-8');
896
+ return outputPath;
897
+ }
898
+ catch (error) {
899
+ if (error instanceof ArtifactParseError) {
900
+ throw error;
901
+ }
902
+ throw new ArtifactParseError('mind_map', {
903
+ details: `Failed to parse structure: ${String(error)}`,
904
+ cause: error instanceof Error ? error : null,
905
+ });
906
+ }
907
+ }
908
+ async downloadDataTable(notebookId, outputPath, artifactId) {
909
+ const artifactsData = await this.listRaw(notebookId);
910
+ const candidates = artifactsData.filter((artifact) => Array.isArray(artifact) &&
911
+ artifact.length > 18 &&
912
+ artifact[2] === ArtifactTypeCode.DATA_TABLE &&
913
+ artifact[4] === ArtifactStatus.COMPLETED);
914
+ const tableArtifact = this.selectArtifact(candidates, artifactId, 'Data table', 'data table');
915
+ try {
916
+ if (!Array.isArray(tableArtifact[18])) {
917
+ throw new ArtifactParseError('data_table', { details: 'Invalid data table structure' });
918
+ }
919
+ const [headers, rows] = parseDataTable(tableArtifact[18]);
920
+ await mkdir(path.dirname(outputPath), { recursive: true });
921
+ const csvLines = [];
922
+ const toCsvCell = (value) => {
923
+ if (value.includes(',') || value.includes('"') || value.includes('\n')) {
924
+ return `"${value.replaceAll('"', '""')}"`;
925
+ }
926
+ return value;
927
+ };
928
+ csvLines.push(headers.map((header) => toCsvCell(header)).join(','));
929
+ for (const row of rows) {
930
+ csvLines.push(row.map((cell) => toCsvCell(cell)).join(','));
931
+ }
932
+ await writeFile(outputPath, `\uFEFF${csvLines.join('\n')}`, 'utf-8');
933
+ return outputPath;
934
+ }
935
+ catch (error) {
936
+ if (error instanceof ArtifactParseError) {
937
+ throw error;
938
+ }
939
+ throw new ArtifactParseError('data_table', {
940
+ details: `Failed to parse structure: ${String(error)}`,
941
+ cause: error instanceof Error ? error : null,
942
+ });
943
+ }
944
+ }
945
+ async downloadQuiz(notebookId, outputPath, artifactId, outputFormat = 'json') {
946
+ return this.downloadInteractiveArtifact(notebookId, outputPath, artifactId, outputFormat, 'quiz');
947
+ }
948
+ async downloadFlashcards(notebookId, outputPath, artifactId, outputFormat = 'json') {
949
+ return this.downloadInteractiveArtifact(notebookId, outputPath, artifactId, outputFormat, 'flashcards');
950
+ }
951
+ async delete(notebookId, artifactId) {
952
+ console.debug(`Deleting artifact ${artifactId} from notebook ${notebookId}`);
953
+ const params = [[2], artifactId];
954
+ await this.core.rpcCall(RPCMethod.DELETE_ARTIFACT, params, `/notebook/${notebookId}`, true);
955
+ return true;
956
+ }
957
+ async rename(notebookId, artifactId, newTitle) {
958
+ const params = [[artifactId, newTitle], [['title']]];
959
+ await this.core.rpcCall(RPCMethod.RENAME_ARTIFACT, params, `/notebook/${notebookId}`, true);
960
+ }
961
+ async pollStatus(notebookId, taskId) {
962
+ const artifactsData = await this.listRaw(notebookId);
963
+ for (const artifact of artifactsData) {
964
+ if (!Array.isArray(artifact) || artifact.length === 0 || artifact[0] !== taskId) {
965
+ continue;
966
+ }
967
+ let statusCode = typeof artifact[4] === 'number' ? artifact[4] : 0;
968
+ const artifactType = typeof artifact[2] === 'number' ? artifact[2] : 0;
969
+ if (statusCode === ArtifactStatus.COMPLETED && !this.isMediaReady(artifact, artifactType)) {
970
+ console.debug(`Artifact ${taskId} (type=${this.getArtifactTypeName(artifactType)}) status=COMPLETED but media not ready, continuing poll`);
971
+ statusCode = ArtifactStatus.PROCESSING;
972
+ }
973
+ return createGenerationStatus({
974
+ taskId,
975
+ status: artifactStatusToStr(statusCode),
976
+ });
977
+ }
978
+ return createGenerationStatus({
979
+ taskId,
980
+ status: 'pending',
981
+ });
982
+ }
983
+ async waitForCompletion(notebookId, taskId, initialInterval = 2, maxInterval = 10, timeout = 300, pollInterval) {
984
+ let currentInterval = pollInterval ?? initialInterval;
985
+ if (pollInterval !== undefined) {
986
+ console.warn('pollInterval is deprecated, use initialInterval instead');
987
+ }
988
+ const start = performance.now();
989
+ while (true) {
990
+ const status = await this.pollStatus(notebookId, taskId);
991
+ if (status.isComplete || status.isFailed) {
992
+ return status;
993
+ }
994
+ const elapsed = (performance.now() - start) / 1000;
995
+ if (elapsed > timeout) {
996
+ throw new Error(`Task ${taskId} timed out after ${timeout}s`);
997
+ }
998
+ const remaining = timeout - elapsed;
999
+ const sleepDuration = Math.min(currentInterval, remaining);
1000
+ if (sleepDuration > 0) {
1001
+ await sleep(Math.max(1, Math.floor(sleepDuration * 1000)));
1002
+ }
1003
+ currentInterval = Math.min(currentInterval * 2, maxInterval);
1004
+ }
1005
+ }
1006
+ async exportReport(notebookId, artifactId, title = 'Export', exportType = ExportType.DOCS) {
1007
+ const params = [null, artifactId, null, title, Number(exportType)];
1008
+ return this.core.rpcCall(RPCMethod.EXPORT_ARTIFACT, params, `/notebook/${notebookId}`, true);
1009
+ }
1010
+ async exportDataTable(notebookId, artifactId, title = 'Export') {
1011
+ const params = [null, artifactId, null, title, Number(ExportType.SHEETS)];
1012
+ return this.core.rpcCall(RPCMethod.EXPORT_ARTIFACT, params, `/notebook/${notebookId}`, true);
1013
+ }
1014
+ async export(notebookId, artifactId, content, title = 'Export', exportType = ExportType.DOCS) {
1015
+ const params = [null, artifactId, content, title, Number(exportType)];
1016
+ return this.core.rpcCall(RPCMethod.EXPORT_ARTIFACT, params, `/notebook/${notebookId}`, true);
1017
+ }
1018
+ async suggestReports(notebookId) {
1019
+ const params = [[2], notebookId];
1020
+ const result = await this.core.rpcCall(RPCMethod.GET_SUGGESTED_REPORTS, params, `/notebook/${notebookId}`, true);
1021
+ const suggestions = [];
1022
+ if (Array.isArray(result) && result.length > 0) {
1023
+ const items = Array.isArray(result[0]) ? result[0] : result;
1024
+ for (const item of items) {
1025
+ if (!Array.isArray(item) || item.length < 5) {
1026
+ continue;
1027
+ }
1028
+ suggestions.push({
1029
+ title: typeof item[0] === 'string' ? item[0] : '',
1030
+ description: typeof item[1] === 'string' ? item[1] : '',
1031
+ prompt: typeof item[4] === 'string' ? item[4] : '',
1032
+ audienceLevel: typeof item[5] === 'number' ? item[5] : 2,
1033
+ });
1034
+ }
1035
+ }
1036
+ return suggestions;
1037
+ }
1038
+ async callGenerate(notebookId, params) {
1039
+ const artifactType = Array.isArray(params[2]) && typeof params[2][2] !== 'undefined'
1040
+ ? String(params[2][2])
1041
+ : 'unknown';
1042
+ console.debug(`Generating artifact type=${artifactType} in notebook ${notebookId}`);
1043
+ try {
1044
+ const result = await this.core.rpcCall(RPCMethod.CREATE_ARTIFACT, params, `/notebook/${notebookId}`, true);
1045
+ return this.parseGenerationResult(result);
1046
+ }
1047
+ catch (error) {
1048
+ if (error instanceof RPCError && error.rpcCode === 'USER_DISPLAYABLE_ERROR') {
1049
+ return createGenerationStatus({
1050
+ taskId: '',
1051
+ status: 'failed',
1052
+ error: error.message,
1053
+ errorCode: String(error.rpcCode),
1054
+ });
1055
+ }
1056
+ throw error;
1057
+ }
1058
+ }
1059
+ async listRaw(notebookId) {
1060
+ const params = [[2], notebookId, 'NOT artifact.status = "ARTIFACT_STATUS_SUGGESTED"'];
1061
+ const result = await this.core.rpcCall(RPCMethod.LIST_ARTIFACTS, params, `/notebook/${notebookId}`, true);
1062
+ if (Array.isArray(result) && result.length > 0) {
1063
+ const data = Array.isArray(result[0]) ? result[0] : result;
1064
+ return data.filter((item) => Array.isArray(item));
1065
+ }
1066
+ return [];
1067
+ }
1068
+ selectArtifact(candidates, artifactId, typeName, typeNameLower) {
1069
+ if (artifactId) {
1070
+ const artifact = candidates.find((candidate) => candidate[0] === artifactId);
1071
+ if (!artifact) {
1072
+ throw new ArtifactNotReadyError(typeName.toLowerCase().replaceAll(' ', '_'), {
1073
+ artifactId,
1074
+ });
1075
+ }
1076
+ return artifact;
1077
+ }
1078
+ if (candidates.length === 0) {
1079
+ throw new ArtifactNotReadyError(typeNameLower);
1080
+ }
1081
+ candidates.sort((left, right) => {
1082
+ const leftTs = Array.isArray(left[15]) && typeof left[15][0] === 'number' ? left[15][0] : 0;
1083
+ const rightTs = Array.isArray(right[15]) && typeof right[15][0] === 'number' ? right[15][0] : 0;
1084
+ return rightTs - leftTs;
1085
+ });
1086
+ return candidates[0];
1087
+ }
1088
+ async fetchWithCookieRedirects(url, opts = {}) {
1089
+ const method = opts.method ?? 'GET';
1090
+ const timeoutMs = opts.timeoutMs ?? 60_000;
1091
+ const maxRedirects = opts.maxRedirects ?? 10;
1092
+ let currentUrl = url;
1093
+ for (let redirectCount = 0; redirectCount <= maxRedirects; redirectCount += 1) {
1094
+ const response = await fetchWithTimeout(currentUrl, {
1095
+ method,
1096
+ headers: opts.headers,
1097
+ body: opts.body ?? undefined,
1098
+ redirect: 'manual',
1099
+ }, timeoutMs);
1100
+ if ([301, 302, 303, 307, 308].includes(response.status)) {
1101
+ const location = response.headers.get('location');
1102
+ if (!location) {
1103
+ return response;
1104
+ }
1105
+ currentUrl = new URL(location, currentUrl).toString();
1106
+ continue;
1107
+ }
1108
+ return response;
1109
+ }
1110
+ throw new ArtifactDownloadError('media', {
1111
+ details: 'Too many redirects while downloading media',
1112
+ });
1113
+ }
1114
+ async downloadUrl(url, outputPath) {
1115
+ await mkdir(path.dirname(outputPath), { recursive: true });
1116
+ const tempFile = `${outputPath}.tmp`;
1117
+ try {
1118
+ const response = await this.fetchWithCookieRedirects(url, {
1119
+ headers: {
1120
+ Cookie: this.core.auth.cookieHeader,
1121
+ },
1122
+ timeoutMs: 60_000,
1123
+ });
1124
+ if (!response.ok) {
1125
+ throw new ArtifactDownloadError('media', {
1126
+ details: `HTTP ${response.status} ${response.statusText}`,
1127
+ });
1128
+ }
1129
+ const contentType = response.headers.get('content-type') ?? '';
1130
+ if (contentType.includes('text/html')) {
1131
+ throw new ArtifactDownloadError('media', {
1132
+ details: 'Download failed: received HTML instead of media file. Authentication may have expired. Re-authenticate and retry.',
1133
+ });
1134
+ }
1135
+ // Stream directly to disk to avoid OOM on large media files (videos can be >100MB)
1136
+ const body = response.body;
1137
+ if (!body) {
1138
+ throw new ArtifactDownloadError('media', {
1139
+ details: 'Response body is null',
1140
+ });
1141
+ }
1142
+ const { createWriteStream } = await import('node:fs');
1143
+ const { Writable } = await import('node:stream');
1144
+ const fileStream = createWriteStream(tempFile);
1145
+ const writableStream = Writable.toWeb(fileStream);
1146
+ await body.pipeTo(writableStream);
1147
+ await rename(tempFile, outputPath);
1148
+ return outputPath;
1149
+ }
1150
+ catch (error) {
1151
+ await rm(tempFile, { force: true });
1152
+ if (error instanceof ArtifactDownloadError) {
1153
+ throw error;
1154
+ }
1155
+ throw new ArtifactDownloadError('media', {
1156
+ details: `Download failed: ${String(error)}`,
1157
+ cause: error instanceof Error ? error : null,
1158
+ });
1159
+ }
1160
+ }
1161
+ parseGenerationResult(result) {
1162
+ if (Array.isArray(result) &&
1163
+ result.length > 0 &&
1164
+ Array.isArray(result[0]) &&
1165
+ result[0].length > 0) {
1166
+ const artifactData = result[0];
1167
+ const artifactId = typeof artifactData[0] === 'string' ? artifactData[0] : null;
1168
+ const statusCode = typeof artifactData[4] === 'number' ? artifactData[4] : null;
1169
+ if (artifactId) {
1170
+ return createGenerationStatus({
1171
+ taskId: artifactId,
1172
+ status: statusCode === null ? 'pending' : artifactStatusToStr(statusCode),
1173
+ });
1174
+ }
1175
+ }
1176
+ return createGenerationStatus({
1177
+ taskId: '',
1178
+ status: 'failed',
1179
+ error: 'Generation failed - no artifact_id returned',
1180
+ });
1181
+ }
1182
+ getArtifactTypeName(artifactType) {
1183
+ return ArtifactTypeCode[artifactType] ?? String(artifactType);
1184
+ }
1185
+ isValidMediaUrl(value) {
1186
+ return (typeof value === 'string' && (value.startsWith('http://') || value.startsWith('https://')));
1187
+ }
1188
+ findInfographicUrl(artifact) {
1189
+ for (let i = artifact.length - 1; i >= 0; i -= 1) {
1190
+ const item = artifact[i];
1191
+ if (!Array.isArray(item) ||
1192
+ item.length <= 2 ||
1193
+ !Array.isArray(item[2]) ||
1194
+ item[2].length === 0) {
1195
+ continue;
1196
+ }
1197
+ const firstContent = item[2][0];
1198
+ if (!Array.isArray(firstContent) || firstContent.length <= 1) {
1199
+ continue;
1200
+ }
1201
+ const imgData = firstContent[1];
1202
+ if (Array.isArray(imgData) && imgData.length > 0 && this.isValidMediaUrl(imgData[0])) {
1203
+ return imgData[0];
1204
+ }
1205
+ }
1206
+ return null;
1207
+ }
1208
+ isMediaReady(artifact, artifactType) {
1209
+ try {
1210
+ if (artifactType === ArtifactTypeCode.AUDIO) {
1211
+ if (artifact.length > 6 && Array.isArray(artifact[6]) && artifact[6].length > 5) {
1212
+ const mediaList = artifact[6][5];
1213
+ if (Array.isArray(mediaList) &&
1214
+ mediaList.length > 0 &&
1215
+ Array.isArray(mediaList[0]) &&
1216
+ mediaList[0].length > 0) {
1217
+ return this.isValidMediaUrl(mediaList[0][0]);
1218
+ }
1219
+ }
1220
+ return false;
1221
+ }
1222
+ if (artifactType === ArtifactTypeCode.VIDEO) {
1223
+ if (artifact.length > 8 && Array.isArray(artifact[8])) {
1224
+ return artifact[8].some((item) => Array.isArray(item) && item.length > 0 && this.isValidMediaUrl(item[0]));
1225
+ }
1226
+ return false;
1227
+ }
1228
+ if (artifactType === ArtifactTypeCode.INFOGRAPHIC) {
1229
+ return this.findInfographicUrl(artifact) !== null;
1230
+ }
1231
+ if (artifactType === ArtifactTypeCode.SLIDE_DECK) {
1232
+ return (artifact.length > 16 &&
1233
+ Array.isArray(artifact[16]) &&
1234
+ artifact[16].length > 3 &&
1235
+ this.isValidMediaUrl(artifact[16][3]));
1236
+ }
1237
+ return true;
1238
+ }
1239
+ catch (error) {
1240
+ const isMedia = MEDIA_ARTIFACT_TYPES.has(artifactType);
1241
+ console.debug(`Unexpected artifact structure for type ${artifactType} (media=${String(isMedia)}): ${String(error)}`);
1242
+ return !isMedia;
1243
+ }
1244
+ }
1245
+ }
1246
+ //# sourceMappingURL=artifacts.js.map