@boldvideo/bold-js 1.6.1 → 1.8.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 +58 -0
- package/README.md +95 -37
- package/dist/index.cjs +41 -20
- package/dist/index.d.ts +89 -80
- package/dist/index.js +41 -20
- package/llms.txt +23 -27
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,63 @@
|
|
|
1
1
|
# @boldvideo/bold-js
|
|
2
2
|
|
|
3
|
+
## 1.8.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 065d7ea: Transform all API responses to use camelCase (TypeScript/JavaScript convention)
|
|
8
|
+
|
|
9
|
+
**Breaking Changes:**
|
|
10
|
+
|
|
11
|
+
- **All response types now use camelCase** (was snake_case). API responses are transformed at the SDK boundary.
|
|
12
|
+
- `video_id` → `videoId`
|
|
13
|
+
- `timestamp_end` → `timestampEnd`
|
|
14
|
+
- `playback_id` → `playbackId`
|
|
15
|
+
- `input_tokens` → `inputTokens`
|
|
16
|
+
- `conversation_id` → `conversationId`
|
|
17
|
+
|
|
18
|
+
**Internal:**
|
|
19
|
+
|
|
20
|
+
- Added `camelizeKeys` utility for deep snake_case → camelCase transformation
|
|
21
|
+
- Applied transformation in `jsonRequest()` and `parseSSE()` at the transport boundary
|
|
22
|
+
|
|
23
|
+
## 1.7.0
|
|
24
|
+
|
|
25
|
+
### Minor Changes
|
|
26
|
+
|
|
27
|
+
- e6fb51b: Align SDK with Bold AI API v1 specification
|
|
28
|
+
|
|
29
|
+
**Breaking Changes:**
|
|
30
|
+
|
|
31
|
+
- Video-scoped chat: pass `videoId` in options instead of as separate arg: `bold.ai.chat({ videoId, prompt })`
|
|
32
|
+
- `Source.timestamp_end` and `Source.playback_id` are now required (were optional)
|
|
33
|
+
- `AIUsage` now uses `input_tokens`/`output_tokens` (was `prompt_tokens`/`completion_tokens`/`total_tokens`)
|
|
34
|
+
- Removed `TopicInput` type - `RecommendationsOptions.topics` now only accepts `string[]`
|
|
35
|
+
- `RecommendationsOptions.context` is now `AIContextMessage[]` (was `string`)
|
|
36
|
+
|
|
37
|
+
**New:**
|
|
38
|
+
|
|
39
|
+
- `bold.ai.chat(opts)` - Single method for all chat (pass `videoId` to scope to a video)
|
|
40
|
+
- `bold.ai.recommendations(opts)` - AI-powered video recommendations (replaces `recommend`)
|
|
41
|
+
- `ChatOptions.videoId` - Scope chat to a specific video
|
|
42
|
+
- `ChatOptions.currentTime` - Pass current playback position for context
|
|
43
|
+
|
|
44
|
+
**Deprecated (still work, will be removed in v2):**
|
|
45
|
+
|
|
46
|
+
- `bold.ai.ask()` → use `bold.ai.chat()`
|
|
47
|
+
- `bold.ai.coach()` → use `bold.ai.chat()`
|
|
48
|
+
- `bold.ai.recommend()` → use `bold.ai.recommendations()`
|
|
49
|
+
- `AskOptions` type → use `ChatOptions`
|
|
50
|
+
- `RecommendOptions` type → use `RecommendationsOptions`
|
|
51
|
+
- `RecommendResponse` type → use `RecommendationsResponse`
|
|
52
|
+
|
|
53
|
+
**Type Changes:**
|
|
54
|
+
|
|
55
|
+
- Added `Source.id` field (chunk identifier)
|
|
56
|
+
- Added `conversation_id` and `video_id` to `message_start` event
|
|
57
|
+
- Added `conversation_id`, `recommendations`, `guidance` to `message_complete` event
|
|
58
|
+
- Simplified `clarification` event to include `content` field
|
|
59
|
+
- Removed legacy event types: `token`, `answer`, `complete`
|
|
60
|
+
|
|
3
61
|
## 1.6.1
|
|
4
62
|
|
|
5
63
|
### 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
|
-
//
|
|
48
|
-
const recs = await bold.ai.
|
|
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
|
-
###
|
|
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.
|
|
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.
|
|
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[]`
|
|
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` | `
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
191
|
-
const stream = await bold.ai.chat(
|
|
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
|
-
|
|
249
|
-
|
|
261
|
+
ChatOptions,
|
|
262
|
+
SearchOptions,
|
|
263
|
+
RecommendationsOptions,
|
|
264
|
+
RecommendationsResponse,
|
|
250
265
|
Recommendation,
|
|
251
266
|
Source
|
|
252
267
|
} from '@boldvideo/bold-js';
|
|
@@ -254,6 +269,49 @@ import type {
|
|
|
254
269
|
|
|
255
270
|
---
|
|
256
271
|
|
|
272
|
+
## Migration from v1.6.x
|
|
273
|
+
|
|
274
|
+
### Breaking: Response types now use camelCase
|
|
275
|
+
|
|
276
|
+
All API responses are now transformed to use idiomatic TypeScript/JavaScript naming:
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
// Before (v1.6.x)
|
|
280
|
+
source.video_id
|
|
281
|
+
source.timestamp_end
|
|
282
|
+
source.playback_id
|
|
283
|
+
usage.input_tokens
|
|
284
|
+
event.conversation_id
|
|
285
|
+
|
|
286
|
+
// After (v1.7.0)
|
|
287
|
+
source.videoId
|
|
288
|
+
source.timestampEnd
|
|
289
|
+
source.playbackId
|
|
290
|
+
usage.inputTokens
|
|
291
|
+
event.conversationId
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### Method Changes
|
|
295
|
+
|
|
296
|
+
| Old | New | Notes |
|
|
297
|
+
|-----|-----|-------|
|
|
298
|
+
| `bold.ai.ask(opts)` | `bold.ai.chat(opts)` | `ask()` still works but is deprecated |
|
|
299
|
+
| `bold.ai.coach(opts)` | `bold.ai.chat(opts)` | `coach()` still works but is deprecated |
|
|
300
|
+
| `bold.ai.chat(videoId, opts)` | `bold.ai.chat({ videoId, ...opts })` | Pass `videoId` in options |
|
|
301
|
+
| `bold.ai.recommend(opts)` | `bold.ai.recommendations(opts)` | `recommend()` still works but is deprecated |
|
|
302
|
+
|
|
303
|
+
### Type Renames
|
|
304
|
+
|
|
305
|
+
| Old Type | New Type |
|
|
306
|
+
|----------|----------|
|
|
307
|
+
| `AskOptions` | `ChatOptions` |
|
|
308
|
+
| `RecommendOptions` | `RecommendationsOptions` |
|
|
309
|
+
| `RecommendResponse` | `RecommendationsResponse` |
|
|
310
|
+
|
|
311
|
+
The old types are still exported as aliases for backward compatibility.
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
257
315
|
## Related Links
|
|
258
316
|
|
|
259
317
|
- **[Bold API Documentation](https://docs.boldvideo.io/docs/api)**
|
package/dist/index.cjs
CHANGED
|
@@ -221,6 +221,24 @@ function basicInfos() {
|
|
|
221
221
|
};
|
|
222
222
|
}
|
|
223
223
|
|
|
224
|
+
// src/util/camelize.ts
|
|
225
|
+
var isPlainObject = (value) => value !== null && typeof value === "object" && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
|
|
226
|
+
var snakeToCamel = (key) => key.replace(/_([a-z0-9])/g, (_, c) => c.toUpperCase());
|
|
227
|
+
function camelizeKeys(input) {
|
|
228
|
+
if (Array.isArray(input)) {
|
|
229
|
+
return input.map((item) => camelizeKeys(item));
|
|
230
|
+
}
|
|
231
|
+
if (!isPlainObject(input)) {
|
|
232
|
+
return input;
|
|
233
|
+
}
|
|
234
|
+
const out = {};
|
|
235
|
+
for (const [rawKey, value] of Object.entries(input)) {
|
|
236
|
+
const key = snakeToCamel(rawKey);
|
|
237
|
+
out[key] = camelizeKeys(value);
|
|
238
|
+
}
|
|
239
|
+
return out;
|
|
240
|
+
}
|
|
241
|
+
|
|
224
242
|
// src/lib/ai.ts
|
|
225
243
|
async function* parseSSE(response) {
|
|
226
244
|
const reader = response.body?.getReader();
|
|
@@ -248,9 +266,10 @@ async function* parseSSE(response) {
|
|
|
248
266
|
if (!json)
|
|
249
267
|
continue;
|
|
250
268
|
try {
|
|
251
|
-
const
|
|
269
|
+
const raw = JSON.parse(json);
|
|
270
|
+
const event = camelizeKeys(raw);
|
|
252
271
|
yield event;
|
|
253
|
-
if (event.type === "
|
|
272
|
+
if (event.type === "message_complete" || event.type === "error") {
|
|
254
273
|
await reader.cancel();
|
|
255
274
|
return;
|
|
256
275
|
}
|
|
@@ -298,24 +317,33 @@ async function jsonRequest(path, body, config) {
|
|
|
298
317
|
if (!response.ok) {
|
|
299
318
|
throw new Error(`AI request failed: ${response.status} ${response.statusText}`);
|
|
300
319
|
}
|
|
301
|
-
|
|
320
|
+
const raw = await response.json();
|
|
321
|
+
return camelizeKeys(raw);
|
|
302
322
|
}
|
|
303
323
|
function createAI(config) {
|
|
304
|
-
async function
|
|
305
|
-
const
|
|
324
|
+
async function chat(options) {
|
|
325
|
+
const isVideoScoped = !!options.videoId;
|
|
326
|
+
const basePath = isVideoScoped ? `ai/videos/${options.videoId}/chat` : "ai/chat";
|
|
327
|
+
const path = options.conversationId ? `${basePath}/${options.conversationId}` : basePath;
|
|
306
328
|
const body = { prompt: options.prompt };
|
|
307
329
|
if (options.collectionId)
|
|
308
330
|
body.collection_id = options.collectionId;
|
|
309
331
|
if (options.tags)
|
|
310
332
|
body.tags = options.tags;
|
|
333
|
+
if (isVideoScoped && options.currentTime !== void 0) {
|
|
334
|
+
body.current_time = options.currentTime;
|
|
335
|
+
}
|
|
311
336
|
if (options.stream === false) {
|
|
312
337
|
body.stream = false;
|
|
313
338
|
return jsonRequest(path, body, config);
|
|
314
339
|
}
|
|
315
340
|
return streamRequest(path, body, config);
|
|
316
341
|
}
|
|
342
|
+
async function ask(options) {
|
|
343
|
+
return chat(options);
|
|
344
|
+
}
|
|
317
345
|
async function coach(options) {
|
|
318
|
-
return
|
|
346
|
+
return chat(options);
|
|
319
347
|
}
|
|
320
348
|
async function search(options) {
|
|
321
349
|
const path = "ai/search";
|
|
@@ -336,19 +364,8 @@ function createAI(config) {
|
|
|
336
364
|
}
|
|
337
365
|
return streamRequest(path, body, config);
|
|
338
366
|
}
|
|
339
|
-
async function
|
|
340
|
-
const path =
|
|
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";
|
|
367
|
+
async function recommendations(options) {
|
|
368
|
+
const path = "ai/recommendations";
|
|
352
369
|
const body = { topics: options.topics };
|
|
353
370
|
if (options.limit)
|
|
354
371
|
body.limit = options.limit;
|
|
@@ -366,11 +383,15 @@ function createAI(config) {
|
|
|
366
383
|
}
|
|
367
384
|
return streamRequest(path, body, config);
|
|
368
385
|
}
|
|
386
|
+
async function recommend(options) {
|
|
387
|
+
return recommendations(options);
|
|
388
|
+
}
|
|
369
389
|
return {
|
|
390
|
+
chat,
|
|
370
391
|
ask,
|
|
371
392
|
coach,
|
|
372
393
|
search,
|
|
373
|
-
|
|
394
|
+
recommendations,
|
|
374
395
|
recommend
|
|
375
396
|
};
|
|
376
397
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -182,70 +182,65 @@ type Settings = {
|
|
|
182
182
|
* Source citation from AI responses
|
|
183
183
|
*/
|
|
184
184
|
interface Source {
|
|
185
|
-
|
|
185
|
+
id: string;
|
|
186
|
+
videoId: string;
|
|
186
187
|
title: string;
|
|
187
|
-
timestamp: number;
|
|
188
|
-
timestamp_end?: number;
|
|
189
188
|
text: string;
|
|
190
|
-
|
|
189
|
+
timestamp: number;
|
|
190
|
+
timestampEnd: number;
|
|
191
|
+
playbackId: string;
|
|
191
192
|
speaker?: string;
|
|
192
193
|
}
|
|
193
194
|
/**
|
|
194
195
|
* Token usage statistics
|
|
195
196
|
*/
|
|
196
197
|
interface AIUsage {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
total_tokens: number;
|
|
198
|
+
inputTokens: number;
|
|
199
|
+
outputTokens: number;
|
|
200
200
|
}
|
|
201
201
|
/**
|
|
202
202
|
* SSE event types for AI streaming responses
|
|
203
203
|
*/
|
|
204
204
|
type AIEvent = {
|
|
205
205
|
type: "message_start";
|
|
206
|
-
|
|
207
|
-
|
|
206
|
+
conversationId?: string;
|
|
207
|
+
videoId?: string;
|
|
208
208
|
} | {
|
|
209
209
|
type: "sources";
|
|
210
210
|
sources: Source[];
|
|
211
|
-
query?: string;
|
|
212
211
|
} | {
|
|
213
212
|
type: "text_delta";
|
|
214
213
|
delta: string;
|
|
215
|
-
} | {
|
|
216
|
-
type: "token";
|
|
217
|
-
content: string;
|
|
218
|
-
} | {
|
|
219
|
-
type: "answer";
|
|
220
|
-
content: string;
|
|
221
|
-
response_id?: string;
|
|
222
|
-
context?: AIContextMessage[];
|
|
223
214
|
} | {
|
|
224
215
|
type: "clarification";
|
|
216
|
+
content: string;
|
|
225
217
|
questions: string[];
|
|
226
218
|
} | {
|
|
227
219
|
type: "recommendations";
|
|
228
220
|
recommendations: Recommendation[];
|
|
229
221
|
} | {
|
|
230
222
|
type: "message_complete";
|
|
223
|
+
conversationId?: string;
|
|
231
224
|
content: string;
|
|
232
225
|
sources: Source[];
|
|
233
226
|
usage?: AIUsage;
|
|
234
227
|
context?: AIContextMessage[];
|
|
235
|
-
|
|
236
|
-
|
|
228
|
+
recommendations?: Recommendation[];
|
|
229
|
+
guidance?: string;
|
|
237
230
|
} | {
|
|
238
231
|
type: "error";
|
|
239
232
|
code: string;
|
|
240
233
|
message: string;
|
|
241
234
|
retryable: boolean;
|
|
242
|
-
details?: Record<string, unknown>;
|
|
243
235
|
};
|
|
244
236
|
/**
|
|
245
|
-
* Non-streaming AI response
|
|
237
|
+
* Non-streaming AI response for /ai/chat, /ai/videos/:id/chat, and /ai/search
|
|
246
238
|
*/
|
|
247
239
|
interface AIResponse {
|
|
248
|
-
|
|
240
|
+
conversationId?: string;
|
|
241
|
+
videoId?: string;
|
|
242
|
+
/** @deprecated Use conversationId instead. Will be removed in v2. */
|
|
243
|
+
id?: string;
|
|
249
244
|
content: string;
|
|
250
245
|
sources: Source[];
|
|
251
246
|
usage: AIUsage;
|
|
@@ -253,15 +248,31 @@ interface AIResponse {
|
|
|
253
248
|
context?: AIContextMessage[];
|
|
254
249
|
}
|
|
255
250
|
/**
|
|
256
|
-
* Options for bold.ai.
|
|
251
|
+
* Options for bold.ai.chat()
|
|
252
|
+
*
|
|
253
|
+
* If `videoId` is provided, scopes chat to that video (hits /ai/videos/:id/chat).
|
|
254
|
+
* Otherwise, searches your entire library (hits /ai/chat).
|
|
257
255
|
*/
|
|
258
|
-
interface
|
|
256
|
+
interface ChatOptions {
|
|
259
257
|
prompt: string;
|
|
260
258
|
stream?: boolean;
|
|
261
259
|
conversationId?: string;
|
|
262
260
|
collectionId?: string;
|
|
263
261
|
tags?: string[];
|
|
262
|
+
/**
|
|
263
|
+
* If provided, scope chat to a specific video instead of the whole library.
|
|
264
|
+
*/
|
|
265
|
+
videoId?: string;
|
|
266
|
+
/**
|
|
267
|
+
* Current playback position in seconds. Only used when videoId is set.
|
|
268
|
+
* Helps AI understand what the viewer just watched.
|
|
269
|
+
*/
|
|
270
|
+
currentTime?: number;
|
|
264
271
|
}
|
|
272
|
+
/**
|
|
273
|
+
* @deprecated Use ChatOptions instead
|
|
274
|
+
*/
|
|
275
|
+
type AskOptions = ChatOptions;
|
|
265
276
|
/**
|
|
266
277
|
* Conversation message for AI context
|
|
267
278
|
*/
|
|
@@ -281,26 +292,13 @@ interface SearchOptions {
|
|
|
281
292
|
tags?: string[];
|
|
282
293
|
context?: AIContextMessage[];
|
|
283
294
|
}
|
|
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
295
|
/**
|
|
298
296
|
* A recommended video with relevance score
|
|
299
297
|
*/
|
|
300
298
|
interface RecommendationVideo {
|
|
301
|
-
|
|
299
|
+
videoId: string;
|
|
302
300
|
title: string;
|
|
303
|
-
|
|
301
|
+
playbackId: string;
|
|
304
302
|
relevance: number;
|
|
305
303
|
reason: string;
|
|
306
304
|
}
|
|
@@ -309,57 +307,74 @@ interface RecommendationVideo {
|
|
|
309
307
|
*/
|
|
310
308
|
interface Recommendation {
|
|
311
309
|
topic: string;
|
|
312
|
-
position: number;
|
|
313
310
|
videos: RecommendationVideo[];
|
|
314
311
|
}
|
|
315
312
|
/**
|
|
316
|
-
*
|
|
313
|
+
* Options for bold.ai.recommendations()
|
|
317
314
|
*/
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
priority?: number;
|
|
321
|
-
};
|
|
322
|
-
/**
|
|
323
|
-
* Options for bold.ai.recommend()
|
|
324
|
-
*/
|
|
325
|
-
interface RecommendOptions {
|
|
326
|
-
topics: TopicInput[] | string;
|
|
315
|
+
interface RecommendationsOptions {
|
|
316
|
+
topics: string[];
|
|
327
317
|
stream?: boolean;
|
|
328
318
|
limit?: number;
|
|
329
319
|
collectionId?: string;
|
|
330
320
|
tags?: string[];
|
|
331
321
|
includeGuidance?: boolean;
|
|
332
|
-
context?:
|
|
322
|
+
context?: AIContextMessage[];
|
|
333
323
|
}
|
|
334
324
|
/**
|
|
335
|
-
*
|
|
325
|
+
* @deprecated Use RecommendationsOptions instead
|
|
336
326
|
*/
|
|
337
|
-
|
|
338
|
-
|
|
327
|
+
type RecommendOptions = RecommendationsOptions;
|
|
328
|
+
/**
|
|
329
|
+
* Non-streaming response for recommendations endpoint
|
|
330
|
+
*/
|
|
331
|
+
interface RecommendationsResponse {
|
|
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
|
-
*
|
|
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
|
-
* //
|
|
353
|
-
* const stream = await bold.ai.
|
|
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.
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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.
|
|
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.
|
|
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,
|
|
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
|
@@ -183,6 +183,24 @@ function basicInfos() {
|
|
|
183
183
|
};
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
+
// src/util/camelize.ts
|
|
187
|
+
var isPlainObject = (value) => value !== null && typeof value === "object" && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
|
|
188
|
+
var snakeToCamel = (key) => key.replace(/_([a-z0-9])/g, (_, c) => c.toUpperCase());
|
|
189
|
+
function camelizeKeys(input) {
|
|
190
|
+
if (Array.isArray(input)) {
|
|
191
|
+
return input.map((item) => camelizeKeys(item));
|
|
192
|
+
}
|
|
193
|
+
if (!isPlainObject(input)) {
|
|
194
|
+
return input;
|
|
195
|
+
}
|
|
196
|
+
const out = {};
|
|
197
|
+
for (const [rawKey, value] of Object.entries(input)) {
|
|
198
|
+
const key = snakeToCamel(rawKey);
|
|
199
|
+
out[key] = camelizeKeys(value);
|
|
200
|
+
}
|
|
201
|
+
return out;
|
|
202
|
+
}
|
|
203
|
+
|
|
186
204
|
// src/lib/ai.ts
|
|
187
205
|
async function* parseSSE(response) {
|
|
188
206
|
const reader = response.body?.getReader();
|
|
@@ -210,9 +228,10 @@ async function* parseSSE(response) {
|
|
|
210
228
|
if (!json)
|
|
211
229
|
continue;
|
|
212
230
|
try {
|
|
213
|
-
const
|
|
231
|
+
const raw = JSON.parse(json);
|
|
232
|
+
const event = camelizeKeys(raw);
|
|
214
233
|
yield event;
|
|
215
|
-
if (event.type === "
|
|
234
|
+
if (event.type === "message_complete" || event.type === "error") {
|
|
216
235
|
await reader.cancel();
|
|
217
236
|
return;
|
|
218
237
|
}
|
|
@@ -260,24 +279,33 @@ async function jsonRequest(path, body, config) {
|
|
|
260
279
|
if (!response.ok) {
|
|
261
280
|
throw new Error(`AI request failed: ${response.status} ${response.statusText}`);
|
|
262
281
|
}
|
|
263
|
-
|
|
282
|
+
const raw = await response.json();
|
|
283
|
+
return camelizeKeys(raw);
|
|
264
284
|
}
|
|
265
285
|
function createAI(config) {
|
|
266
|
-
async function
|
|
267
|
-
const
|
|
286
|
+
async function chat(options) {
|
|
287
|
+
const isVideoScoped = !!options.videoId;
|
|
288
|
+
const basePath = isVideoScoped ? `ai/videos/${options.videoId}/chat` : "ai/chat";
|
|
289
|
+
const path = options.conversationId ? `${basePath}/${options.conversationId}` : basePath;
|
|
268
290
|
const body = { prompt: options.prompt };
|
|
269
291
|
if (options.collectionId)
|
|
270
292
|
body.collection_id = options.collectionId;
|
|
271
293
|
if (options.tags)
|
|
272
294
|
body.tags = options.tags;
|
|
295
|
+
if (isVideoScoped && options.currentTime !== void 0) {
|
|
296
|
+
body.current_time = options.currentTime;
|
|
297
|
+
}
|
|
273
298
|
if (options.stream === false) {
|
|
274
299
|
body.stream = false;
|
|
275
300
|
return jsonRequest(path, body, config);
|
|
276
301
|
}
|
|
277
302
|
return streamRequest(path, body, config);
|
|
278
303
|
}
|
|
304
|
+
async function ask(options) {
|
|
305
|
+
return chat(options);
|
|
306
|
+
}
|
|
279
307
|
async function coach(options) {
|
|
280
|
-
return
|
|
308
|
+
return chat(options);
|
|
281
309
|
}
|
|
282
310
|
async function search(options) {
|
|
283
311
|
const path = "ai/search";
|
|
@@ -298,19 +326,8 @@ function createAI(config) {
|
|
|
298
326
|
}
|
|
299
327
|
return streamRequest(path, body, config);
|
|
300
328
|
}
|
|
301
|
-
async function
|
|
302
|
-
const path =
|
|
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";
|
|
329
|
+
async function recommendations(options) {
|
|
330
|
+
const path = "ai/recommendations";
|
|
314
331
|
const body = { topics: options.topics };
|
|
315
332
|
if (options.limit)
|
|
316
333
|
body.limit = options.limit;
|
|
@@ -328,11 +345,15 @@ function createAI(config) {
|
|
|
328
345
|
}
|
|
329
346
|
return streamRequest(path, body, config);
|
|
330
347
|
}
|
|
348
|
+
async function recommend(options) {
|
|
349
|
+
return recommendations(options);
|
|
350
|
+
}
|
|
331
351
|
return {
|
|
352
|
+
chat,
|
|
332
353
|
ask,
|
|
333
354
|
coach,
|
|
334
355
|
search,
|
|
335
|
-
|
|
356
|
+
recommendations,
|
|
336
357
|
recommend
|
|
337
358
|
};
|
|
338
359
|
}
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
###
|
|
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
|
-
###
|
|
90
|
+
### RecommendationsOptions
|
|
100
91
|
|
|
101
92
|
```typescript
|
|
102
93
|
{
|
|
103
|
-
|
|
94
|
+
topics: string[]; // Topics to find content for (required)
|
|
104
95
|
stream?: boolean; // Default: true
|
|
105
|
-
|
|
106
|
-
|
|
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,10 @@ Key types exported:
|
|
|
113
107
|
|
|
114
108
|
- `Video`, `Playlist`, `Settings`, `Portal`
|
|
115
109
|
- `AIEvent`, `AIResponse`, `Source`, `AIUsage`
|
|
116
|
-
- `
|
|
117
|
-
- `
|
|
110
|
+
- `ChatOptions`, `SearchOptions`
|
|
111
|
+
- `RecommendationsOptions`, `RecommendationsResponse`, `Recommendation`, `RecommendationVideo`
|
|
112
|
+
|
|
113
|
+
All response types use camelCase (e.g., `source.videoId`, `source.timestampEnd`, `usage.inputTokens`).
|
|
118
114
|
|
|
119
115
|
## Links
|
|
120
116
|
|