@boldvideo/bold-js 1.6.1 → 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,43 @@
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
+
3
41
  ## 1.6.1
4
42
 
5
43
  ### Patch 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
168
  | `includeGuidance` | `boolean` | Include AI learning path narrative (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
- ```
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,12 +185,13 @@ 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
 
@@ -187,8 +199,9 @@ for await (const event of stream) {
187
199
  if (event.type === 'text_delta') process.stdout.write(event.delta);
188
200
  }
189
201
 
190
- // With playback context - helps AI understand what viewer just watched
191
- const stream = await bold.ai.chat('video-id', {
202
+ // With playback context (coming soon)
203
+ const stream = await bold.ai.chat({
204
+ videoId: 'video-id',
192
205
  prompt: 'What does she mean by that?',
193
206
  currentTime: 847 // seconds
194
207
  });
@@ -245,8 +258,10 @@ import type {
245
258
  Settings,
246
259
  AIEvent,
247
260
  AIResponse,
248
- RecommendOptions,
249
- RecommendResponse,
261
+ ChatOptions,
262
+ SearchOptions,
263
+ RecommendationsOptions,
264
+ RecommendationsResponse,
250
265
  Recommendation,
251
266
  Source
252
267
  } from '@boldvideo/bold-js';
@@ -254,6 +269,29 @@ import type {
254
269
 
255
270
  ---
256
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
+
257
295
  ## Related Links
258
296
 
259
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,21 +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;
309
311
  if (options.tags)
310
312
  body.tags = options.tags;
313
+ if (isVideoScoped && options.currentTime !== void 0) {
314
+ body.current_time = options.currentTime;
315
+ }
311
316
  if (options.stream === false) {
312
317
  body.stream = false;
313
318
  return jsonRequest(path, body, config);
314
319
  }
315
320
  return streamRequest(path, body, config);
316
321
  }
322
+ async function ask(options) {
323
+ return chat(options);
324
+ }
317
325
  async function coach(options) {
318
- return ask(options);
326
+ return chat(options);
319
327
  }
320
328
  async function search(options) {
321
329
  const path = "ai/search";
@@ -336,19 +344,8 @@ function createAI(config) {
336
344
  }
337
345
  return streamRequest(path, body, config);
338
346
  }
339
- async function chat(videoId, options) {
340
- const path = options.conversationId ? `ai/videos/${videoId}/chat/${options.conversationId}` : `ai/videos/${videoId}/chat`;
341
- const body = { prompt: options.prompt };
342
- if (options.currentTime !== void 0)
343
- body.current_time = options.currentTime;
344
- if (options.stream === false) {
345
- body.stream = false;
346
- return jsonRequest(path, body, config);
347
- }
348
- return streamRequest(path, body, config);
349
- }
350
- async function recommend(options) {
351
- const path = "ai/recommend";
347
+ async function recommendations(options) {
348
+ const path = "ai/recommendations";
352
349
  const body = { topics: options.topics };
353
350
  if (options.limit)
354
351
  body.limit = options.limit;
@@ -366,11 +363,15 @@ function createAI(config) {
366
363
  }
367
364
  return streamRequest(path, body, config);
368
365
  }
366
+ async function recommend(options) {
367
+ return recommendations(options);
368
+ }
369
369
  return {
370
+ chat,
370
371
  ask,
371
372
  coach,
372
373
  search,
373
- chat,
374
+ recommendations,
374
375
  recommend
375
376
  };
376
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,15 +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;
263
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;
264
270
  }
271
+ /**
272
+ * @deprecated Use ChatOptions instead
273
+ */
274
+ type AskOptions = ChatOptions;
265
275
  /**
266
276
  * Conversation message for AI context
267
277
  */
@@ -281,19 +291,6 @@ interface SearchOptions {
281
291
  tags?: string[];
282
292
  context?: AIContextMessage[];
283
293
  }
284
- /**
285
- * Options for bold.ai.chat()
286
- *
287
- * conversationId: Pass to continue an existing conversation (multi-turn chat).
288
- * If omitted, a new conversation is created. The id is returned in the
289
- * message_start event - capture it to pass to subsequent requests.
290
- */
291
- interface ChatOptions {
292
- prompt: string;
293
- stream?: boolean;
294
- conversationId?: string;
295
- currentTime?: number;
296
- }
297
294
  /**
298
295
  * A recommended video with relevance score
299
296
  */
@@ -309,57 +306,75 @@ interface RecommendationVideo {
309
306
  */
310
307
  interface Recommendation {
311
308
  topic: string;
312
- position: number;
313
309
  videos: RecommendationVideo[];
314
310
  }
315
311
  /**
316
- * Topic input format for recommendations
317
- */
318
- type TopicInput = string | {
319
- q: string;
320
- priority?: number;
321
- };
322
- /**
323
- * Options for bold.ai.recommend()
312
+ * Options for bold.ai.recommendations()
324
313
  */
325
- interface RecommendOptions {
326
- topics: TopicInput[] | string;
314
+ interface RecommendationsOptions {
315
+ topics: string[];
327
316
  stream?: boolean;
328
317
  limit?: number;
329
318
  collectionId?: string;
330
319
  tags?: string[];
331
320
  includeGuidance?: boolean;
332
- context?: string;
321
+ context?: AIContextMessage[];
333
322
  }
334
323
  /**
335
- * Non-streaming response for recommend endpoint
324
+ * @deprecated Use RecommendationsOptions instead
325
+ */
326
+ type RecommendOptions = RecommendationsOptions;
327
+ /**
328
+ * Non-streaming response for recommendations endpoint
336
329
  */
337
- interface RecommendResponse {
330
+ interface RecommendationsResponse {
338
331
  id: string;
339
332
  recommendations: Recommendation[];
340
333
  guidance: string;
341
334
  sources: Source[];
335
+ context?: AIContextMessage[];
336
+ usage?: AIUsage;
342
337
  }
338
+ /**
339
+ * @deprecated Use RecommendationsResponse instead
340
+ */
341
+ type RecommendResponse = RecommendationsResponse;
343
342
 
344
343
  /**
345
344
  * AI client interface for type-safe method overloading
346
345
  */
347
346
  interface AIClient {
348
347
  /**
349
- * 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.
350
351
  *
351
352
  * @example
352
- * // Streaming (default)
353
- * 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?" });
354
355
  * for await (const event of stream) {
355
356
  * if (event.type === "text_delta") process.stdout.write(event.delta);
356
357
  * }
357
358
  *
358
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
359
364
  * // Non-streaming
360
- * 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 });
361
366
  * console.log(response.content);
362
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
+ */
363
378
  ask(options: AskOptions & {
364
379
  stream: false;
365
380
  }): Promise<AIResponse>;
@@ -368,7 +383,7 @@ interface AIClient {
368
383
  }): Promise<AsyncIterable<AIEvent>>;
369
384
  ask(options: AskOptions): Promise<AsyncIterable<AIEvent> | AIResponse>;
370
385
  /**
371
- * Coach - Alias for ask() (Library-wide RAG assistant)
386
+ * @deprecated Use chat() instead. Will be removed in a future version.
372
387
  */
373
388
  coach(options: AskOptions & {
374
389
  stream: false;
@@ -394,36 +409,30 @@ interface AIClient {
394
409
  }): Promise<AsyncIterable<AIEvent>>;
395
410
  search(options: SearchOptions): Promise<AsyncIterable<AIEvent> | AIResponse>;
396
411
  /**
397
- * Chat - Video-scoped conversation
398
- *
399
- * @example
400
- * const stream = await bold.ai.chat("video-id", { prompt: "What is discussed at 5 minutes?" });
401
- * for await (const event of stream) {
402
- * if (event.type === "text_delta") process.stdout.write(event.delta);
403
- * }
404
- */
405
- chat(videoId: string, options: ChatOptions & {
406
- stream: false;
407
- }): Promise<AIResponse>;
408
- chat(videoId: string, options: ChatOptions & {
409
- stream?: true;
410
- }): Promise<AsyncIterable<AIEvent>>;
411
- chat(videoId: string, options: ChatOptions): Promise<AsyncIterable<AIEvent> | AIResponse>;
412
- /**
413
- * Recommend - AI-powered video recommendations
412
+ * Recommendations - AI-powered video recommendations
414
413
  *
415
414
  * @example
416
415
  * // Streaming (default)
417
- * const stream = await bold.ai.recommend({ topics: ["sales", "negotiation"] });
416
+ * const stream = await bold.ai.recommendations({ topics: ["sales", "negotiation"] });
418
417
  * for await (const event of stream) {
419
418
  * if (event.type === "recommendations") console.log(event.recommendations);
420
419
  * }
421
420
  *
422
421
  * @example
423
422
  * // Non-streaming
424
- * const response = await bold.ai.recommend({ topics: ["sales"], stream: false });
423
+ * const response = await bold.ai.recommendations({ topics: ["sales"], stream: false });
425
424
  * console.log(response.guidance);
426
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
+ */
427
436
  recommend(options: RecommendOptions & {
428
437
  stream: false;
429
438
  }): Promise<RecommendResponse>;
@@ -475,4 +484,4 @@ declare const DEFAULT_API_BASE_URL = "https://app.boldvideo.io/api/v1/";
475
484
  */
476
485
  declare const DEFAULT_INTERNAL_API_BASE_URL = "https://app.boldvideo.io/i/v1/";
477
486
 
478
- 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,21 +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;
271
273
  if (options.tags)
272
274
  body.tags = options.tags;
275
+ if (isVideoScoped && options.currentTime !== void 0) {
276
+ body.current_time = options.currentTime;
277
+ }
273
278
  if (options.stream === false) {
274
279
  body.stream = false;
275
280
  return jsonRequest(path, body, config);
276
281
  }
277
282
  return streamRequest(path, body, config);
278
283
  }
284
+ async function ask(options) {
285
+ return chat(options);
286
+ }
279
287
  async function coach(options) {
280
- return ask(options);
288
+ return chat(options);
281
289
  }
282
290
  async function search(options) {
283
291
  const path = "ai/search";
@@ -298,19 +306,8 @@ function createAI(config) {
298
306
  }
299
307
  return streamRequest(path, body, config);
300
308
  }
301
- async function chat(videoId, options) {
302
- const path = options.conversationId ? `ai/videos/${videoId}/chat/${options.conversationId}` : `ai/videos/${videoId}/chat`;
303
- const body = { prompt: options.prompt };
304
- if (options.currentTime !== void 0)
305
- body.current_time = options.currentTime;
306
- if (options.stream === false) {
307
- body.stream = false;
308
- return jsonRequest(path, body, config);
309
- }
310
- return streamRequest(path, body, config);
311
- }
312
- async function recommend(options) {
313
- const path = "ai/recommend";
309
+ async function recommendations(options) {
310
+ const path = "ai/recommendations";
314
311
  const body = { topics: options.topics };
315
312
  if (options.limit)
316
313
  body.limit = options.limit;
@@ -328,11 +325,15 @@ function createAI(config) {
328
325
  }
329
326
  return streamRequest(path, body, config);
330
327
  }
328
+ async function recommend(options) {
329
+ return recommendations(options);
330
+ }
331
331
  return {
332
+ chat,
332
333
  ask,
333
334
  coach,
334
335
  search,
335
- chat,
336
+ recommendations,
336
337
  recommend
337
338
  };
338
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,26 +59,14 @@ 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
- includeGuidance?: boolean; // Include AI learning path narrative (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
81
72
  tags?: string[]; // Filter by tags
@@ -96,14 +87,17 @@ All AI methods return `AsyncIterable<AIEvent>` (streaming) or `Promise<AIRespons
96
87
  }
97
88
  ```
98
89
 
99
- ### ChatOptions
90
+ ### RecommendationsOptions
100
91
 
101
92
  ```typescript
102
93
  {
103
- prompt: string; // Question about the video (required)
94
+ topics: string[]; // Topics to find content for (required)
104
95
  stream?: boolean; // Default: true
105
- conversationId?: string; // Continue existing conversation
106
- currentTime?: number; // Current playback position in seconds
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
107
101
  }
108
102
  ```
109
103
 
@@ -113,8 +107,8 @@ Key types exported:
113
107
 
114
108
  - `Video`, `Playlist`, `Settings`, `Portal`
115
109
  - `AIEvent`, `AIResponse`, `Source`, `AIUsage`
116
- - `AskOptions`, `SearchOptions`, `ChatOptions`
117
- - `RecommendOptions`, `RecommendResponse`, `Recommendation`, `RecommendationVideo`, `TopicInput`
110
+ - `ChatOptions`, `SearchOptions`
111
+ - `RecommendationsOptions`, `RecommendationsResponse`, `Recommendation`, `RecommendationVideo`
118
112
 
119
113
  ## Links
120
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.1",
4
+ "version": "1.7.0",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
7
7
  "types": "dist/index.d.ts",