@boldvideo/bold-js 1.6.0 → 1.7.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/CHANGELOG.md CHANGED
@@ -1,5 +1,54 @@
1
1
  # @boldvideo/bold-js
2
2
 
3
+ ## 1.7.0
4
+
5
+ ### Minor Changes
6
+
7
+ - e6fb51b: Align SDK with Bold AI API v1 specification
8
+
9
+ **Breaking Changes:**
10
+
11
+ - Video-scoped chat: pass `videoId` in options instead of as separate arg: `bold.ai.chat({ videoId, prompt })`
12
+ - `Source.timestamp_end` and `Source.playback_id` are now required (were optional)
13
+ - `AIUsage` now uses `input_tokens`/`output_tokens` (was `prompt_tokens`/`completion_tokens`/`total_tokens`)
14
+ - Removed `TopicInput` type - `RecommendationsOptions.topics` now only accepts `string[]`
15
+ - `RecommendationsOptions.context` is now `AIContextMessage[]` (was `string`)
16
+
17
+ **New:**
18
+
19
+ - `bold.ai.chat(opts)` - Single method for all chat (pass `videoId` to scope to a video)
20
+ - `bold.ai.recommendations(opts)` - AI-powered video recommendations (replaces `recommend`)
21
+ - `ChatOptions.videoId` - Scope chat to a specific video
22
+ - `ChatOptions.currentTime` - Pass current playback position for context
23
+
24
+ **Deprecated (still work, will be removed in v2):**
25
+
26
+ - `bold.ai.ask()` → use `bold.ai.chat()`
27
+ - `bold.ai.coach()` → use `bold.ai.chat()`
28
+ - `bold.ai.recommend()` → use `bold.ai.recommendations()`
29
+ - `AskOptions` type → use `ChatOptions`
30
+ - `RecommendOptions` type → use `RecommendationsOptions`
31
+ - `RecommendResponse` type → use `RecommendationsResponse`
32
+
33
+ **Type Changes:**
34
+
35
+ - Added `Source.id` field (chunk identifier)
36
+ - Added `conversation_id` and `video_id` to `message_start` event
37
+ - Added `conversation_id`, `recommendations`, `guidance` to `message_complete` event
38
+ - Simplified `clarification` event to include `content` field
39
+ - Removed legacy event types: `token`, `answer`, `complete`
40
+
41
+ ## 1.6.1
42
+
43
+ ### Patch Changes
44
+
45
+ - 7aff057: Align SDK with API specification
46
+
47
+ - Renamed `synthesize` to `includeGuidance` in `RecommendOptions` to match API
48
+ - Renamed `why` to `reason` in `RecommendationVideo` type to match API response
49
+ - Added `tags` filter to `AskOptions` and `SearchOptions`
50
+ - Added `currentTime` to `ChatOptions` for playback context
51
+
3
52
  ## 1.6.0
4
53
 
5
54
  ### Minor Changes
package/README.md CHANGED
@@ -44,8 +44,8 @@ const bold = createClient('your-api-key');
44
44
  // Fetch videos
45
45
  const videos = await bold.videos.list();
46
46
 
47
- // Get AI-powered recommendations
48
- const recs = await bold.ai.recommend({
47
+ // AI-powered recommendations
48
+ const recs = await bold.ai.recommendations({
49
49
  topics: ['sales', 'negotiation'],
50
50
  stream: false
51
51
  });
@@ -92,15 +92,47 @@ const settings = await bold.settings();
92
92
 
93
93
  All AI methods support both streaming (default) and non-streaming modes.
94
94
 
95
- ### Recommend
95
+ ### Chat
96
+
97
+ Library-wide conversational AI for deep Q&A across your entire video library.
98
+
99
+ ```typescript
100
+ // Streaming (default)
101
+ const stream = await bold.ai.chat({ prompt: 'How do I price my SaaS?' });
102
+
103
+ for await (const event of stream) {
104
+ if (event.type === 'text_delta') process.stdout.write(event.delta);
105
+ if (event.type === 'sources') console.log('Sources:', event.sources);
106
+ }
107
+
108
+ // Non-streaming
109
+ const response = await bold.ai.chat({
110
+ prompt: 'What are the best closing techniques?',
111
+ stream: false
112
+ });
113
+ console.log(response.content);
114
+ ```
115
+
116
+ **Options:**
117
+
118
+ | Parameter | Type | Description |
119
+ |-----------|------|-------------|
120
+ | `prompt` | `string` | The user's question (required) |
121
+ | `stream` | `boolean` | `true` (default) for SSE, `false` for JSON |
122
+ | `videoId` | `string` | If provided, scope to this video instead of whole library |
123
+ | `currentTime` | `number` | Current playback position (only with `videoId`) |
124
+ | `conversationId` | `string` | Pass to continue existing conversation |
125
+ | `collectionId` | `string` | Filter to a specific collection |
126
+ | `tags` | `string[]` | Filter by tags |
127
+
128
+ ### Recommendations
96
129
 
97
130
  Get AI-powered video recommendations based on topics — ideal for personalized learning paths, exam prep, and content discovery.
98
131
 
99
132
  ```typescript
100
133
  // Streaming (default)
101
- const stream = await bold.ai.recommend({
134
+ const stream = await bold.ai.recommendations({
102
135
  topics: ['contract law', 'ethics', 'client management'],
103
- context: 'I failed these topics on my certification exam'
104
136
  });
105
137
 
106
138
  for await (const event of stream) {
@@ -116,7 +148,7 @@ for await (const event of stream) {
116
148
  }
117
149
 
118
150
  // Non-streaming
119
- const response = await bold.ai.recommend({
151
+ const response = await bold.ai.recommendations({
120
152
  topics: ['sales', 'marketing'],
121
153
  stream: false
122
154
  });
@@ -128,38 +160,17 @@ console.log(response.recommendations);
128
160
 
129
161
  | Parameter | Type | Description |
130
162
  |-----------|------|-------------|
131
- | `topics` | `string[]` \| `string` | Topics to find content for (required) |
163
+ | `topics` | `string[]` | Topics to find content for (required) |
132
164
  | `stream` | `boolean` | `true` (default) for SSE, `false` for JSON |
133
165
  | `limit` | `number` | Max videos per topic (default: 5, max: 20) |
134
166
  | `collectionId` | `string` | Filter to a specific collection |
135
167
  | `tags` | `string[]` | Filter by tags |
136
- | `synthesize` | `boolean` | Include AI guidance (default: true) |
137
- | `context` | `string` | User context for personalized guidance |
138
-
139
- ### Coach / Ask
140
-
141
- Library-wide RAG assistant for answering questions across your entire video library.
142
-
143
- ```typescript
144
- // Streaming
145
- const stream = await bold.ai.coach({ prompt: 'How do I price my SaaS?' });
146
-
147
- for await (const event of stream) {
148
- if (event.type === 'text_delta') process.stdout.write(event.delta);
149
- if (event.type === 'sources') console.log('Sources:', event.sources);
150
- }
151
-
152
- // Non-streaming
153
- const response = await bold.ai.ask({
154
- prompt: 'What are the best closing techniques?',
155
- stream: false
156
- });
157
- console.log(response.content);
158
- ```
168
+ | `includeGuidance` | `boolean` | Include AI learning path narrative (default: true) |
169
+ | `context` | `AIContextMessage[]` | Previous conversation turns for follow-ups |
159
170
 
160
171
  ### Search
161
172
 
162
- Semantic search with light synthesis.
173
+ Fast semantic search with a brief AI-generated summary.
163
174
 
164
175
  ```typescript
165
176
  const stream = await bold.ai.search({
@@ -174,18 +185,26 @@ for await (const event of stream) {
174
185
  }
175
186
  ```
176
187
 
177
- ### Chat
188
+ ### Video-Scoped Chat
178
189
 
179
- Video-scoped conversation for Q&A about a specific video.
190
+ Chat about a specific video by passing `videoId`. Uses only that video's transcript as context.
180
191
 
181
192
  ```typescript
182
- const stream = await bold.ai.chat('video-id', {
193
+ const stream = await bold.ai.chat({
194
+ videoId: 'video-id',
183
195
  prompt: 'What is discussed at the 5 minute mark?'
184
196
  });
185
197
 
186
198
  for await (const event of stream) {
187
199
  if (event.type === 'text_delta') process.stdout.write(event.delta);
188
200
  }
201
+
202
+ // With playback context (coming soon)
203
+ const stream = await bold.ai.chat({
204
+ videoId: 'video-id',
205
+ prompt: 'What does she mean by that?',
206
+ currentTime: 847 // seconds
207
+ });
189
208
  ```
190
209
 
191
210
  ### Multi-turn Conversations
@@ -239,8 +258,10 @@ import type {
239
258
  Settings,
240
259
  AIEvent,
241
260
  AIResponse,
242
- RecommendOptions,
243
- RecommendResponse,
261
+ ChatOptions,
262
+ SearchOptions,
263
+ RecommendationsOptions,
264
+ RecommendationsResponse,
244
265
  Recommendation,
245
266
  Source
246
267
  } from '@boldvideo/bold-js';
@@ -248,6 +269,29 @@ import type {
248
269
 
249
270
  ---
250
271
 
272
+ ## Migration from v1.6.x
273
+
274
+ ### Method Changes
275
+
276
+ | Old | New | Notes |
277
+ |-----|-----|-------|
278
+ | `bold.ai.ask(opts)` | `bold.ai.chat(opts)` | `ask()` still works but is deprecated |
279
+ | `bold.ai.coach(opts)` | `bold.ai.chat(opts)` | `coach()` still works but is deprecated |
280
+ | `bold.ai.chat(videoId, opts)` | `bold.ai.chat({ videoId, ...opts })` | Pass `videoId` in options |
281
+ | `bold.ai.recommend(opts)` | `bold.ai.recommendations(opts)` | `recommend()` still works but is deprecated |
282
+
283
+ ### Type Renames
284
+
285
+ | Old Type | New Type |
286
+ |----------|----------|
287
+ | `AskOptions` | `ChatOptions` |
288
+ | `RecommendOptions` | `RecommendationsOptions` |
289
+ | `RecommendResponse` | `RecommendationsResponse` |
290
+
291
+ The old types are still exported as aliases for backward compatibility.
292
+
293
+ ---
294
+
251
295
  ## Related Links
252
296
 
253
297
  - **[Bold API Documentation](https://docs.boldvideo.io/docs/api)**
package/dist/index.cjs CHANGED
@@ -250,7 +250,7 @@ async function* parseSSE(response) {
250
250
  try {
251
251
  const event = JSON.parse(json);
252
252
  yield event;
253
- if (event.type === "complete" || event.type === "error") {
253
+ if (event.type === "message_complete" || event.type === "error") {
254
254
  await reader.cancel();
255
255
  return;
256
256
  }
@@ -301,19 +301,29 @@ async function jsonRequest(path, body, config) {
301
301
  return response.json();
302
302
  }
303
303
  function createAI(config) {
304
- async function ask(options) {
305
- const path = options.conversationId ? `ai/ask/${options.conversationId}` : "ai/ask";
304
+ async function chat(options) {
305
+ const isVideoScoped = !!options.videoId;
306
+ const basePath = isVideoScoped ? `ai/videos/${options.videoId}/chat` : "ai/chat";
307
+ const path = options.conversationId ? `${basePath}/${options.conversationId}` : basePath;
306
308
  const body = { prompt: options.prompt };
307
309
  if (options.collectionId)
308
310
  body.collection_id = options.collectionId;
311
+ if (options.tags)
312
+ body.tags = options.tags;
313
+ if (isVideoScoped && options.currentTime !== void 0) {
314
+ body.current_time = options.currentTime;
315
+ }
309
316
  if (options.stream === false) {
310
317
  body.stream = false;
311
318
  return jsonRequest(path, body, config);
312
319
  }
313
320
  return streamRequest(path, body, config);
314
321
  }
322
+ async function ask(options) {
323
+ return chat(options);
324
+ }
315
325
  async function coach(options) {
316
- return ask(options);
326
+ return chat(options);
317
327
  }
318
328
  async function search(options) {
319
329
  const path = "ai/search";
@@ -324,6 +334,8 @@ function createAI(config) {
324
334
  body.collection_id = options.collectionId;
325
335
  if (options.videoId)
326
336
  body.video_id = options.videoId;
337
+ if (options.tags)
338
+ body.tags = options.tags;
327
339
  if (options.context)
328
340
  body.context = options.context;
329
341
  if (options.stream === false) {
@@ -332,17 +344,8 @@ function createAI(config) {
332
344
  }
333
345
  return streamRequest(path, body, config);
334
346
  }
335
- async function chat(videoId, options) {
336
- const path = options.conversationId ? `ai/videos/${videoId}/chat/${options.conversationId}` : `ai/videos/${videoId}/chat`;
337
- const body = { prompt: options.prompt };
338
- if (options.stream === false) {
339
- body.stream = false;
340
- return jsonRequest(path, body, config);
341
- }
342
- return streamRequest(path, body, config);
343
- }
344
- async function recommend(options) {
345
- const path = "ai/recommend";
347
+ async function recommendations(options) {
348
+ const path = "ai/recommendations";
346
349
  const body = { topics: options.topics };
347
350
  if (options.limit)
348
351
  body.limit = options.limit;
@@ -350,8 +353,8 @@ function createAI(config) {
350
353
  body.collection_id = options.collectionId;
351
354
  if (options.tags)
352
355
  body.tags = options.tags;
353
- if (options.synthesize !== void 0)
354
- body.synthesize = options.synthesize;
356
+ if (options.includeGuidance !== void 0)
357
+ body.include_guidance = options.includeGuidance;
355
358
  if (options.context)
356
359
  body.context = options.context;
357
360
  if (options.stream === false) {
@@ -360,11 +363,15 @@ function createAI(config) {
360
363
  }
361
364
  return streamRequest(path, body, config);
362
365
  }
366
+ async function recommend(options) {
367
+ return recommendations(options);
368
+ }
363
369
  return {
370
+ chat,
364
371
  ask,
365
372
  coach,
366
373
  search,
367
- chat,
374
+ recommendations,
368
375
  recommend
369
376
  };
370
377
  }
package/dist/index.d.ts CHANGED
@@ -182,21 +182,21 @@ type Settings = {
182
182
  * Source citation from AI responses
183
183
  */
184
184
  interface Source {
185
+ id: string;
185
186
  video_id: string;
186
187
  title: string;
187
- timestamp: number;
188
- timestamp_end?: number;
189
188
  text: string;
190
- playback_id?: string;
189
+ timestamp: number;
190
+ timestamp_end: number;
191
+ playback_id: string;
191
192
  speaker?: string;
192
193
  }
193
194
  /**
194
195
  * Token usage statistics
195
196
  */
196
197
  interface AIUsage {
197
- prompt_tokens: number;
198
- completion_tokens: number;
199
- total_tokens: number;
198
+ input_tokens: number;
199
+ output_tokens: number;
200
200
  }
201
201
  /**
202
202
  * SSE event types for AI streaming responses
@@ -204,42 +204,36 @@ interface AIUsage {
204
204
  type AIEvent = {
205
205
  type: "message_start";
206
206
  id: string;
207
- model?: string;
207
+ conversation_id?: string;
208
+ video_id?: string;
208
209
  } | {
209
210
  type: "sources";
210
211
  sources: Source[];
211
- query?: string;
212
212
  } | {
213
213
  type: "text_delta";
214
214
  delta: string;
215
- } | {
216
- type: "token";
217
- content: string;
218
- } | {
219
- type: "answer";
220
- content: string;
221
- response_id?: string;
222
- context?: AIContextMessage[];
223
215
  } | {
224
216
  type: "clarification";
217
+ content: string;
225
218
  questions: string[];
226
219
  } | {
227
220
  type: "recommendations";
228
221
  recommendations: Recommendation[];
229
222
  } | {
230
223
  type: "message_complete";
224
+ id: string;
225
+ conversation_id?: string;
231
226
  content: string;
232
227
  sources: Source[];
233
228
  usage?: AIUsage;
234
229
  context?: AIContextMessage[];
235
- } | {
236
- type: "complete";
230
+ recommendations?: Recommendation[];
231
+ guidance?: string;
237
232
  } | {
238
233
  type: "error";
239
234
  code: string;
240
235
  message: string;
241
236
  retryable: boolean;
242
- details?: Record<string, unknown>;
243
237
  };
244
238
  /**
245
239
  * Non-streaming AI response
@@ -253,14 +247,31 @@ interface AIResponse {
253
247
  context?: AIContextMessage[];
254
248
  }
255
249
  /**
256
- * Options for bold.ai.ask() and bold.ai.coach()
250
+ * Options for bold.ai.chat()
251
+ *
252
+ * If `videoId` is provided, scopes chat to that video (hits /ai/videos/:id/chat).
253
+ * Otherwise, searches your entire library (hits /ai/chat).
257
254
  */
258
- interface AskOptions {
255
+ interface ChatOptions {
259
256
  prompt: string;
260
257
  stream?: boolean;
261
258
  conversationId?: string;
262
259
  collectionId?: string;
260
+ tags?: string[];
261
+ /**
262
+ * If provided, scope chat to a specific video instead of the whole library.
263
+ */
264
+ videoId?: string;
265
+ /**
266
+ * Current playback position in seconds. Only used when videoId is set.
267
+ * Helps AI understand what the viewer just watched.
268
+ */
269
+ currentTime?: number;
263
270
  }
271
+ /**
272
+ * @deprecated Use ChatOptions instead
273
+ */
274
+ type AskOptions = ChatOptions;
264
275
  /**
265
276
  * Conversation message for AI context
266
277
  */
@@ -277,20 +288,9 @@ interface SearchOptions {
277
288
  limit?: number;
278
289
  collectionId?: string;
279
290
  videoId?: string;
291
+ tags?: string[];
280
292
  context?: AIContextMessage[];
281
293
  }
282
- /**
283
- * Options for bold.ai.chat()
284
- *
285
- * conversationId: Pass to continue an existing conversation (multi-turn chat).
286
- * If omitted, a new conversation is created. The id is returned in the
287
- * message_start event - capture it to pass to subsequent requests.
288
- */
289
- interface ChatOptions {
290
- prompt: string;
291
- stream?: boolean;
292
- conversationId?: string;
293
- }
294
294
  /**
295
295
  * A recommended video with relevance score
296
296
  */
@@ -299,64 +299,82 @@ interface RecommendationVideo {
299
299
  title: string;
300
300
  playback_id: string;
301
301
  relevance: number;
302
- why: string;
302
+ reason: string;
303
303
  }
304
304
  /**
305
305
  * A topic recommendation with its videos
306
306
  */
307
307
  interface Recommendation {
308
308
  topic: string;
309
- position: number;
310
309
  videos: RecommendationVideo[];
311
310
  }
312
311
  /**
313
- * Topic input format for recommendations
314
- */
315
- type TopicInput = string | {
316
- q: string;
317
- priority?: number;
318
- };
319
- /**
320
- * Options for bold.ai.recommend()
312
+ * Options for bold.ai.recommendations()
321
313
  */
322
- interface RecommendOptions {
323
- topics: TopicInput[] | string;
314
+ interface RecommendationsOptions {
315
+ topics: string[];
324
316
  stream?: boolean;
325
317
  limit?: number;
326
318
  collectionId?: string;
327
319
  tags?: string[];
328
- synthesize?: boolean;
329
- context?: string;
320
+ includeGuidance?: boolean;
321
+ context?: AIContextMessage[];
330
322
  }
331
323
  /**
332
- * Non-streaming response for recommend endpoint
324
+ * @deprecated Use RecommendationsOptions instead
325
+ */
326
+ type RecommendOptions = RecommendationsOptions;
327
+ /**
328
+ * Non-streaming response for recommendations endpoint
333
329
  */
334
- interface RecommendResponse {
330
+ interface RecommendationsResponse {
335
331
  id: string;
336
332
  recommendations: Recommendation[];
337
333
  guidance: string;
338
334
  sources: Source[];
335
+ context?: AIContextMessage[];
336
+ usage?: AIUsage;
339
337
  }
338
+ /**
339
+ * @deprecated Use RecommendationsResponse instead
340
+ */
341
+ type RecommendResponse = RecommendationsResponse;
340
342
 
341
343
  /**
342
344
  * AI client interface for type-safe method overloading
343
345
  */
344
346
  interface AIClient {
345
347
  /**
346
- * Ask - Library-wide RAG assistant
348
+ * Chat - Conversational AI for Q&A
349
+ *
350
+ * If `videoId` is provided, scopes to that video. Otherwise searches your entire library.
347
351
  *
348
352
  * @example
349
- * // Streaming (default)
350
- * const stream = await bold.ai.ask({ prompt: "How do I price my SaaS?" });
353
+ * // Library-wide Q&A
354
+ * const stream = await bold.ai.chat({ prompt: "How do I price my SaaS?" });
351
355
  * for await (const event of stream) {
352
356
  * if (event.type === "text_delta") process.stdout.write(event.delta);
353
357
  * }
354
358
  *
355
359
  * @example
360
+ * // Video-scoped Q&A
361
+ * const stream = await bold.ai.chat({ videoId: "vid_xyz", prompt: "What does she mean?" });
362
+ *
363
+ * @example
356
364
  * // Non-streaming
357
- * const response = await bold.ai.ask({ prompt: "How do I price my SaaS?", stream: false });
365
+ * const response = await bold.ai.chat({ prompt: "How do I price my SaaS?", stream: false });
358
366
  * console.log(response.content);
359
367
  */
368
+ chat(options: ChatOptions & {
369
+ stream: false;
370
+ }): Promise<AIResponse>;
371
+ chat(options: ChatOptions & {
372
+ stream?: true;
373
+ }): Promise<AsyncIterable<AIEvent>>;
374
+ chat(options: ChatOptions): Promise<AsyncIterable<AIEvent> | AIResponse>;
375
+ /**
376
+ * @deprecated Use chat() instead. Will be removed in a future version.
377
+ */
360
378
  ask(options: AskOptions & {
361
379
  stream: false;
362
380
  }): Promise<AIResponse>;
@@ -365,7 +383,7 @@ interface AIClient {
365
383
  }): Promise<AsyncIterable<AIEvent>>;
366
384
  ask(options: AskOptions): Promise<AsyncIterable<AIEvent> | AIResponse>;
367
385
  /**
368
- * Coach - Alias for ask() (Library-wide RAG assistant)
386
+ * @deprecated Use chat() instead. Will be removed in a future version.
369
387
  */
370
388
  coach(options: AskOptions & {
371
389
  stream: false;
@@ -391,36 +409,30 @@ interface AIClient {
391
409
  }): Promise<AsyncIterable<AIEvent>>;
392
410
  search(options: SearchOptions): Promise<AsyncIterable<AIEvent> | AIResponse>;
393
411
  /**
394
- * Chat - Video-scoped conversation
395
- *
396
- * @example
397
- * const stream = await bold.ai.chat("video-id", { prompt: "What is discussed at 5 minutes?" });
398
- * for await (const event of stream) {
399
- * if (event.type === "text_delta") process.stdout.write(event.delta);
400
- * }
401
- */
402
- chat(videoId: string, options: ChatOptions & {
403
- stream: false;
404
- }): Promise<AIResponse>;
405
- chat(videoId: string, options: ChatOptions & {
406
- stream?: true;
407
- }): Promise<AsyncIterable<AIEvent>>;
408
- chat(videoId: string, options: ChatOptions): Promise<AsyncIterable<AIEvent> | AIResponse>;
409
- /**
410
- * Recommend - AI-powered video recommendations
412
+ * Recommendations - AI-powered video recommendations
411
413
  *
412
414
  * @example
413
415
  * // Streaming (default)
414
- * const stream = await bold.ai.recommend({ topics: ["sales", "negotiation"] });
416
+ * const stream = await bold.ai.recommendations({ topics: ["sales", "negotiation"] });
415
417
  * for await (const event of stream) {
416
418
  * if (event.type === "recommendations") console.log(event.recommendations);
417
419
  * }
418
420
  *
419
421
  * @example
420
422
  * // Non-streaming
421
- * const response = await bold.ai.recommend({ topics: ["sales"], stream: false });
423
+ * const response = await bold.ai.recommendations({ topics: ["sales"], stream: false });
422
424
  * console.log(response.guidance);
423
425
  */
426
+ recommendations(options: RecommendationsOptions & {
427
+ stream: false;
428
+ }): Promise<RecommendationsResponse>;
429
+ recommendations(options: RecommendationsOptions & {
430
+ stream?: true;
431
+ }): Promise<AsyncIterable<AIEvent>>;
432
+ recommendations(options: RecommendationsOptions): Promise<AsyncIterable<AIEvent> | RecommendationsResponse>;
433
+ /**
434
+ * @deprecated Use recommendations() instead. Will be removed in a future version.
435
+ */
424
436
  recommend(options: RecommendOptions & {
425
437
  stream: false;
426
438
  }): Promise<RecommendResponse>;
@@ -472,4 +484,4 @@ declare const DEFAULT_API_BASE_URL = "https://app.boldvideo.io/api/v1/";
472
484
  */
473
485
  declare const DEFAULT_INTERNAL_API_BASE_URL = "https://app.boldvideo.io/i/v1/";
474
486
 
475
- export { AIContextMessage, AIEvent, AIResponse, AIUsage, Account, AccountAI, AskOptions, AssistantConfig, ChatOptions, ClientOptions, DEFAULT_API_BASE_URL, DEFAULT_INTERNAL_API_BASE_URL, MenuItem, Playlist, Portal, PortalDisplay, PortalLayout, PortalNavigation, PortalTheme, RecommendOptions, RecommendResponse, Recommendation, RecommendationVideo, SearchOptions, Settings, Source, ThemeColors, ThemeConfig, TopicInput, Video, VideoAttachment, VideoDownloadUrls, VideoMetadata, VideoSubtitles, VideoTranscript, createClient };
487
+ export { AIContextMessage, AIEvent, AIResponse, AIUsage, Account, AccountAI, AskOptions, AssistantConfig, ChatOptions, ClientOptions, DEFAULT_API_BASE_URL, DEFAULT_INTERNAL_API_BASE_URL, MenuItem, Playlist, Portal, PortalDisplay, PortalLayout, PortalNavigation, PortalTheme, RecommendOptions, RecommendResponse, Recommendation, RecommendationVideo, RecommendationsOptions, RecommendationsResponse, SearchOptions, Settings, Source, ThemeColors, ThemeConfig, Video, VideoAttachment, VideoDownloadUrls, VideoMetadata, VideoSubtitles, VideoTranscript, createClient };
package/dist/index.js CHANGED
@@ -212,7 +212,7 @@ async function* parseSSE(response) {
212
212
  try {
213
213
  const event = JSON.parse(json);
214
214
  yield event;
215
- if (event.type === "complete" || event.type === "error") {
215
+ if (event.type === "message_complete" || event.type === "error") {
216
216
  await reader.cancel();
217
217
  return;
218
218
  }
@@ -263,19 +263,29 @@ async function jsonRequest(path, body, config) {
263
263
  return response.json();
264
264
  }
265
265
  function createAI(config) {
266
- async function ask(options) {
267
- const path = options.conversationId ? `ai/ask/${options.conversationId}` : "ai/ask";
266
+ async function chat(options) {
267
+ const isVideoScoped = !!options.videoId;
268
+ const basePath = isVideoScoped ? `ai/videos/${options.videoId}/chat` : "ai/chat";
269
+ const path = options.conversationId ? `${basePath}/${options.conversationId}` : basePath;
268
270
  const body = { prompt: options.prompt };
269
271
  if (options.collectionId)
270
272
  body.collection_id = options.collectionId;
273
+ if (options.tags)
274
+ body.tags = options.tags;
275
+ if (isVideoScoped && options.currentTime !== void 0) {
276
+ body.current_time = options.currentTime;
277
+ }
271
278
  if (options.stream === false) {
272
279
  body.stream = false;
273
280
  return jsonRequest(path, body, config);
274
281
  }
275
282
  return streamRequest(path, body, config);
276
283
  }
284
+ async function ask(options) {
285
+ return chat(options);
286
+ }
277
287
  async function coach(options) {
278
- return ask(options);
288
+ return chat(options);
279
289
  }
280
290
  async function search(options) {
281
291
  const path = "ai/search";
@@ -286,6 +296,8 @@ function createAI(config) {
286
296
  body.collection_id = options.collectionId;
287
297
  if (options.videoId)
288
298
  body.video_id = options.videoId;
299
+ if (options.tags)
300
+ body.tags = options.tags;
289
301
  if (options.context)
290
302
  body.context = options.context;
291
303
  if (options.stream === false) {
@@ -294,17 +306,8 @@ function createAI(config) {
294
306
  }
295
307
  return streamRequest(path, body, config);
296
308
  }
297
- async function chat(videoId, options) {
298
- const path = options.conversationId ? `ai/videos/${videoId}/chat/${options.conversationId}` : `ai/videos/${videoId}/chat`;
299
- const body = { prompt: options.prompt };
300
- if (options.stream === false) {
301
- body.stream = false;
302
- return jsonRequest(path, body, config);
303
- }
304
- return streamRequest(path, body, config);
305
- }
306
- async function recommend(options) {
307
- const path = "ai/recommend";
309
+ async function recommendations(options) {
310
+ const path = "ai/recommendations";
308
311
  const body = { topics: options.topics };
309
312
  if (options.limit)
310
313
  body.limit = options.limit;
@@ -312,8 +315,8 @@ function createAI(config) {
312
315
  body.collection_id = options.collectionId;
313
316
  if (options.tags)
314
317
  body.tags = options.tags;
315
- if (options.synthesize !== void 0)
316
- body.synthesize = options.synthesize;
318
+ if (options.includeGuidance !== void 0)
319
+ body.include_guidance = options.includeGuidance;
317
320
  if (options.context)
318
321
  body.context = options.context;
319
322
  if (options.stream === false) {
@@ -322,11 +325,15 @@ function createAI(config) {
322
325
  }
323
326
  return streamRequest(path, body, config);
324
327
  }
328
+ async function recommend(options) {
329
+ return recommendations(options);
330
+ }
325
331
  return {
332
+ chat,
326
333
  ask,
327
334
  coach,
328
335
  search,
329
- chat,
336
+ recommendations,
330
337
  recommend
331
338
  };
332
339
  }
package/llms.txt CHANGED
@@ -14,11 +14,11 @@ const videos = await bold.videos.list();
14
14
  const video = await bold.videos.get('video-id');
15
15
 
16
16
  // AI-powered recommendations
17
- const recs = await bold.ai.recommend({ topics: ['sales', 'negotiation'], stream: false });
17
+ const recs = await bold.ai.recommendations({ topics: ['sales', 'negotiation'], stream: false });
18
18
  console.log(recs.guidance);
19
19
 
20
20
  // AI streaming
21
- const stream = await bold.ai.coach({ prompt: 'How do I price my SaaS?' });
21
+ const stream = await bold.ai.chat({ prompt: 'How do I price my SaaS?' });
22
22
  for await (const event of stream) {
23
23
  if (event.type === 'text_delta') process.stdout.write(event.delta);
24
24
  }
@@ -43,11 +43,14 @@ for await (const event of stream) {
43
43
 
44
44
  All AI methods return `AsyncIterable<AIEvent>` (streaming) or `Promise<AIResponse>` (non-streaming).
45
45
 
46
- - `bold.ai.recommend(options)` - AI-powered video recommendations based on topics
47
- - `bold.ai.coach(options)` - Library-wide RAG assistant (alias: `ask`)
48
- - `bold.ai.ask(options)` - Library-wide RAG assistant
46
+ - `bold.ai.chat(options)` - Conversational AI for Q&A (pass `videoId` to scope to a video)
49
47
  - `bold.ai.search(options)` - Semantic search with synthesis
50
- - `bold.ai.chat(videoId, options)` - Video-scoped Q&A conversation
48
+ - `bold.ai.recommendations(options)` - AI-powered video recommendations based on topics
49
+
50
+ **Deprecated aliases** (still work but will be removed):
51
+ - `bold.ai.ask(options)` → use `chat()`
52
+ - `bold.ai.coach(options)` → use `chat()`
53
+ - `bold.ai.recommend(options)` → use `recommendations()`
51
54
 
52
55
  ### Analytics
53
56
 
@@ -56,28 +59,17 @@ All AI methods return `AsyncIterable<AIEvent>` (streaming) or `Promise<AIRespons
56
59
 
57
60
  ## AI Options
58
61
 
59
- ### RecommendOptions
60
-
61
- ```typescript
62
- {
63
- topics: string[] | string; // Topics to find content for (required)
64
- stream?: boolean; // Default: true
65
- limit?: number; // Max videos per topic (default: 5, max: 20)
66
- collectionId?: string; // Filter to collection
67
- tags?: string[]; // Filter by tags
68
- synthesize?: boolean; // Include AI guidance (default: true)
69
- context?: string; // User context for personalization
70
- }
71
- ```
72
-
73
- ### AskOptions / CoachOptions
62
+ ### ChatOptions
74
63
 
75
64
  ```typescript
76
65
  {
77
66
  prompt: string; // Question to ask (required)
78
67
  stream?: boolean; // Default: true
68
+ videoId?: string; // If set, scope to this video
69
+ currentTime?: number; // Current playback position (with videoId)
79
70
  conversationId?: string; // Continue existing conversation
80
71
  collectionId?: string; // Filter to collection
72
+ tags?: string[]; // Filter by tags
81
73
  }
82
74
  ```
83
75
 
@@ -90,17 +82,22 @@ All AI methods return `AsyncIterable<AIEvent>` (streaming) or `Promise<AIRespons
90
82
  limit?: number; // Max results
91
83
  collectionId?: string; // Filter to collection
92
84
  videoId?: string; // Search within specific video
85
+ tags?: string[]; // Filter by tags
93
86
  context?: AIContextMessage[]; // Conversation context
94
87
  }
95
88
  ```
96
89
 
97
- ### ChatOptions
90
+ ### RecommendationsOptions
98
91
 
99
92
  ```typescript
100
93
  {
101
- prompt: string; // Question about the video (required)
94
+ topics: string[]; // Topics to find content for (required)
102
95
  stream?: boolean; // Default: true
103
- conversationId?: string; // Continue existing conversation
96
+ limit?: number; // Max videos per topic (default: 5, max: 20)
97
+ collectionId?: string; // Filter to collection
98
+ tags?: string[]; // Filter by tags
99
+ includeGuidance?: boolean; // Include AI learning path narrative (default: true)
100
+ context?: AIContextMessage[]; // Previous conversation turns for follow-ups
104
101
  }
105
102
  ```
106
103
 
@@ -110,8 +107,8 @@ Key types exported:
110
107
 
111
108
  - `Video`, `Playlist`, `Settings`, `Portal`
112
109
  - `AIEvent`, `AIResponse`, `Source`, `AIUsage`
113
- - `AskOptions`, `SearchOptions`, `ChatOptions`
114
- - `RecommendOptions`, `RecommendResponse`, `Recommendation`, `RecommendationVideo`, `TopicInput`
110
+ - `ChatOptions`, `SearchOptions`
111
+ - `RecommendationsOptions`, `RecommendationsResponse`, `Recommendation`, `RecommendationVideo`
115
112
 
116
113
  ## Links
117
114
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@boldvideo/bold-js",
3
3
  "license": "MIT",
4
- "version": "1.6.0",
4
+ "version": "1.7.0",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
7
7
  "types": "dist/index.d.ts",