@microsoft/webiq 0.0.1-alpha.2 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +426 -2
- package/dist/index.d.mts +1167 -0
- package/dist/index.d.ts +1167 -1
- package/dist/index.js +825 -2
- package/dist/index.mjs +792 -0
- package/package.json +64 -20
package/README.md
CHANGED
|
@@ -1,3 +1,427 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Web IQ TypeScript SDK
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Official TypeScript SDK for Web IQ APIs.
|
|
4
|
+
|
|
5
|
+
[](https://nodejs.org/)
|
|
6
|
+
[](#license)
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install @microsoft/webiq
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Quick Start
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
import { WebIQClient, BrowseContentFormat, ContentFormat } from '@microsoft/webiq';
|
|
18
|
+
|
|
19
|
+
const client = new WebIQClient({
|
|
20
|
+
apiKey: process.env.WEBIQ_API_KEY,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// Web search with content format
|
|
24
|
+
const web = await client.web.search('TypeScript programming', {
|
|
25
|
+
maxResults: 5,
|
|
26
|
+
contentFormat: ContentFormat.HTML, // Use enum, not the string "html"
|
|
27
|
+
});
|
|
28
|
+
for (const result of web.webResults ?? []) {
|
|
29
|
+
console.log(`${result.title}: ${result.url}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// News search
|
|
33
|
+
const news = await client.news.search('technology', { maxResults: 5 });
|
|
34
|
+
for (const item of news.newsResults ?? []) {
|
|
35
|
+
console.log(`${item.title} - ${item.source}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Video search
|
|
39
|
+
const videos = await client.videos.search('machine learning tutorial');
|
|
40
|
+
for (const video of videos.videoResults ?? []) {
|
|
41
|
+
console.log(`${video.title} (${video.length})`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Browse a URL in markdown format
|
|
45
|
+
const page = await client.browse.fetch('https://www.microsoft.com', {
|
|
46
|
+
contentFormat: BrowseContentFormat.MARKDOWN,
|
|
47
|
+
});
|
|
48
|
+
console.log(page.content);
|
|
49
|
+
|
|
50
|
+
// Classic search with multiple answer types
|
|
51
|
+
const classic = await client.classic.search('Artificial intelligence trends', {
|
|
52
|
+
responseFilter: ['webResults'],
|
|
53
|
+
});
|
|
54
|
+
// responseFilter selects which answer types come back; the matching
|
|
55
|
+
// fields (e.g. classic.webResults) are populated dynamically and typed as `any`.
|
|
56
|
+
for (const r of classic.webResults ?? []) {
|
|
57
|
+
console.log(` - ${r.title}: ${r.url}`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
await client.close();
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Authentication
|
|
64
|
+
|
|
65
|
+
### API Key
|
|
66
|
+
|
|
67
|
+
Get an API key at [webiq.microsoft.ai](https://webiq.microsoft.ai).
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
import { WebIQClient } from '@microsoft/webiq';
|
|
71
|
+
|
|
72
|
+
const client = new WebIQClient({
|
|
73
|
+
apiKey: process.env.WEBIQ_API_KEY,
|
|
74
|
+
});
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### EntraID (Azure AD)
|
|
78
|
+
|
|
79
|
+
Pass any `@azure/identity` `TokenCredential`:
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
import { DefaultAzureCredential } from '@azure/identity';
|
|
83
|
+
import { WebIQClient } from '@microsoft/webiq';
|
|
84
|
+
|
|
85
|
+
const client = new WebIQClient({
|
|
86
|
+
credential: new DefaultAzureCredential(),
|
|
87
|
+
});
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## API Reference
|
|
91
|
+
|
|
92
|
+
### Web Search
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
const results = await client.web.search(query, {
|
|
96
|
+
maxResults: 10, // 1-50, default 10
|
|
97
|
+
language: 'en', // ISO 639-1 code
|
|
98
|
+
region: 'US', // Country/region code
|
|
99
|
+
location: 'lat:40.7;long:-74.0', // Optional
|
|
100
|
+
contentFormat: ContentFormat.HTML, // PASSAGE, TEXT, HTML, MARKDOWN
|
|
101
|
+
maxLength: 10000, // 1-500000
|
|
102
|
+
});
|
|
103
|
+
// results.webResults → [{title, url, content, lastUpdatedAt, crawledAt, language, isAdult}]
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### News Search
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
const results = await client.news.search(query, {
|
|
110
|
+
maxResults: 10, // 1-20, default 10
|
|
111
|
+
language: 'en',
|
|
112
|
+
region: 'US',
|
|
113
|
+
location: 'lat:40.7;long:-74.0', // Optional
|
|
114
|
+
contentFormat: ContentFormat.TEXT,
|
|
115
|
+
maxLength: 10000,
|
|
116
|
+
});
|
|
117
|
+
// results.newsResults → [{title, url, content, source, lastUpdatedAt, thumbnail, isAdult}]
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Video Search
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
const results = await client.videos.search(query, {
|
|
124
|
+
maxResults: 30, // 1-30, default 30
|
|
125
|
+
language: 'en',
|
|
126
|
+
region: 'US',
|
|
127
|
+
enablePlaylist: true,
|
|
128
|
+
freshness: 'month', // week, month, year
|
|
129
|
+
});
|
|
130
|
+
// results.videoResults → [{title, url, length, viewCount, publishedBy, moments, ...}]
|
|
131
|
+
// results.playlists → [{title, videos}]
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Browse
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
const page = await client.browse.fetch(url, {
|
|
138
|
+
maxLength: 10000, // 1-500000
|
|
139
|
+
liveCrawl: 'fallback', // "none" (default) | "fallback" | "force"
|
|
140
|
+
includeWebLinks: true,
|
|
141
|
+
includeImageLinks: true,
|
|
142
|
+
renderDynamicPages: false,
|
|
143
|
+
contentFormat: BrowseContentFormat.MARKDOWN,
|
|
144
|
+
});
|
|
145
|
+
// page → {url, title, content, isAdult, retryAfter, traceId, ...}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Image Search
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
import { ImageAspectRatio, ImageSize, SafeSearch } from '@microsoft/webiq';
|
|
152
|
+
|
|
153
|
+
const results = await client.images.search(query, {
|
|
154
|
+
maxResults: 30, // 1-30, default 30
|
|
155
|
+
language: 'en',
|
|
156
|
+
region: 'US',
|
|
157
|
+
aspectRatio: ImageAspectRatio.WIDE, // SQUARE, WIDE, TALL
|
|
158
|
+
imageSize: ImageSize.LARGE, // SMALL, MEDIUM, LARGE, EXTRA_LARGE
|
|
159
|
+
safeSearch: SafeSearch.STRICT, // OFF, STRICT
|
|
160
|
+
watermarkFree: true,
|
|
161
|
+
});
|
|
162
|
+
// results.imageResults → [{title, url, hostPageUrl, caption, width, height, thumbnailUrl, ...}]
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Classic Search
|
|
166
|
+
|
|
167
|
+
Given a query, classic search will search and retrieve content from webpages, images, videos, news, weather, sports, etc.
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
import { ContentFormat, SafeSearchMode } from '@microsoft/webiq';
|
|
171
|
+
|
|
172
|
+
const response = await client.classic.search(query, {
|
|
173
|
+
maxAnswerTypes: 6, // 1-6, default 6
|
|
174
|
+
language: 'en', // ISO 639-1/2 or BCP 47
|
|
175
|
+
region: 'US', // Country/region code
|
|
176
|
+
location: 'lat:40.7;long:-74.0', // Optional
|
|
177
|
+
maxResultsWeb: 10, // 1-50, default 10
|
|
178
|
+
maxLength: 10000, // 1-500000
|
|
179
|
+
contentFormat: ContentFormat.HTML,
|
|
180
|
+
freshness: 'month', // day, week, month, year, or date range
|
|
181
|
+
responseFilter: ['webResults', 'newsResults'], // Answer types to include
|
|
182
|
+
safeSearch: SafeSearchMode.MODERATE, // OFF, MODERATE, STRICT
|
|
183
|
+
});
|
|
184
|
+
// response.querySignals → {originalQuery, normalizedQuery, isDefensive, isAdult, isNav, isFresh}
|
|
185
|
+
// response.traceId → string
|
|
186
|
+
// Additional dynamic fields for different answer types
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Enum Types
|
|
190
|
+
|
|
191
|
+
Some parameters require enum values instead of plain strings. Import them from `@microsoft/webiq`:
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
import { BrowseContentFormat, ContentFormat } from '@microsoft/webiq';
|
|
195
|
+
|
|
196
|
+
// ContentFormat: format of returned content for web, news, and classic search
|
|
197
|
+
ContentFormat.PASSAGE; // Selected passages only (plain text)
|
|
198
|
+
ContentFormat.TEXT; // Full page text (plain text)
|
|
199
|
+
ContentFormat.HTML; // HTML format (default for web search)
|
|
200
|
+
ContentFormat.MARKDOWN; // Markdown format
|
|
201
|
+
|
|
202
|
+
// BrowseContentFormat: format for browse (no PASSAGE option)
|
|
203
|
+
BrowseContentFormat.TEXT; // Full page text
|
|
204
|
+
BrowseContentFormat.HTML; // HTML format (default)
|
|
205
|
+
BrowseContentFormat.MARKDOWN; // Markdown format
|
|
206
|
+
|
|
207
|
+
// Usage in web search
|
|
208
|
+
const results = await client.web.search('query', {
|
|
209
|
+
contentFormat: ContentFormat.MARKDOWN,
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
// Usage in browse
|
|
213
|
+
const page = await client.browse.fetch('https://www.microsoft.com', {
|
|
214
|
+
contentFormat: BrowseContentFormat.HTML,
|
|
215
|
+
});
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Configuration
|
|
219
|
+
|
|
220
|
+
### Timeout and Retry
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
import { WebIQClient, RetryPolicy } from '@microsoft/webiq';
|
|
224
|
+
|
|
225
|
+
const client = new WebIQClient({
|
|
226
|
+
apiKey: 'your-api-key',
|
|
227
|
+
timeout: 10000, // milliseconds (default: 10000)
|
|
228
|
+
retry: new RetryPolicy({
|
|
229
|
+
maxRetries: 2, // defaults shown
|
|
230
|
+
baseDelaySeconds: 0.25,
|
|
231
|
+
maxDelaySeconds: 4.0,
|
|
232
|
+
}),
|
|
233
|
+
});
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Cancellation
|
|
237
|
+
|
|
238
|
+
Every search and browse method accepts an `AbortSignal` via `options.signal`.
|
|
239
|
+
When the signal aborts, the in-flight request is cancelled, retries stop, and
|
|
240
|
+
the signal's `reason` is re-thrown unchanged (no `APIConnectionError` wrapping).
|
|
241
|
+
|
|
242
|
+
Propagate a client disconnect from an HTTP handler:
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
import express from 'express';
|
|
246
|
+
|
|
247
|
+
app.get('/search', async (req, res) => {
|
|
248
|
+
try {
|
|
249
|
+
const result = await client.web.search(req.query.q as string, {
|
|
250
|
+
signal: req.signal, // express/fastify expose the request's AbortSignal
|
|
251
|
+
});
|
|
252
|
+
res.json(result);
|
|
253
|
+
} catch (err) {
|
|
254
|
+
if (err instanceof DOMException && err.name === 'AbortError') return; // client gone
|
|
255
|
+
throw err;
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
Apply a wall-clock budget that includes retries (the `timeout` option is per
|
|
261
|
+
attempt only):
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
const result = await client.web.search('latest news', {
|
|
265
|
+
signal: AbortSignal.timeout(5_000),
|
|
266
|
+
});
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
Combine multiple signals (e.g. a request signal plus a wall-clock cap):
|
|
270
|
+
|
|
271
|
+
```typescript
|
|
272
|
+
const result = await client.web.search('latest news', {
|
|
273
|
+
signal: AbortSignal.any([req.signal, AbortSignal.timeout(5_000)]),
|
|
274
|
+
});
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### Custom fetch (proxy, TLS, ...)
|
|
278
|
+
|
|
279
|
+
For advanced HTTP settings — proxy, custom TLS, mTLS, etc. — pass a custom
|
|
280
|
+
`fetchFn` that wraps the global `fetch`. Spread `init` in your wrapper
|
|
281
|
+
so headers, body, and the internal `AbortSignal` are preserved.
|
|
282
|
+
|
|
283
|
+
```typescript
|
|
284
|
+
import { ProxyAgent } from 'undici';
|
|
285
|
+
import { WebIQClient } from '@microsoft/webiq';
|
|
286
|
+
|
|
287
|
+
const proxyAgent = new ProxyAgent('http://my-proxy:8080');
|
|
288
|
+
|
|
289
|
+
const client = new WebIQClient({
|
|
290
|
+
apiKey: 'your-api-key',
|
|
291
|
+
// `dispatcher` is a Node/undici-specific init field; the `FetchFunction`
|
|
292
|
+
// type accepts extra fields so no type assertion is needed.
|
|
293
|
+
fetchFn: (url, init) => fetch(url, { ...init, dispatcher: proxyAgent }),
|
|
294
|
+
});
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### Telemetry
|
|
298
|
+
|
|
299
|
+
```typescript
|
|
300
|
+
import { WebIQClient, TelemetryEvent } from '@microsoft/webiq';
|
|
301
|
+
|
|
302
|
+
const client = new WebIQClient({
|
|
303
|
+
apiKey: 'your-api-key',
|
|
304
|
+
telemetryHook: (event: TelemetryEvent) => {
|
|
305
|
+
console.log(`${event.method} ${event.path} → ${event.statusCode} (${event.elapsedMs}ms)`);
|
|
306
|
+
},
|
|
307
|
+
});
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
## Error Handling
|
|
311
|
+
|
|
312
|
+
The SDK throws specific error types for different error conditions. All errors extend `WebIQError`.
|
|
313
|
+
|
|
314
|
+
### Error Hierarchy
|
|
315
|
+
|
|
316
|
+
| Error Class | HTTP Status | When |
|
|
317
|
+
| ----------------------- | ---------------------------- | ------------------------------------------------- |
|
|
318
|
+
| `AuthenticationError` | 401 | Invalid or missing API key |
|
|
319
|
+
| `PermissionDeniedError` | 403 | Authenticated but not authorized for the resource |
|
|
320
|
+
| `RateLimitError` | 429, 430 | Rate limit / concurrent-request limit exceeded |
|
|
321
|
+
| `APIStatusError` | 400, 404, 500, 503, 504, ... | All other HTTP errors |
|
|
322
|
+
| `APIConnectionError` | — | Network issues, DNS failures, timeouts |
|
|
323
|
+
| `WebIQError` | — | Base class for all SDK errors |
|
|
324
|
+
|
|
325
|
+
`PermissionDeniedError` and `RateLimitError` both extend `APIStatusError`, so a broader `instanceof APIStatusError` check still matches them.
|
|
326
|
+
|
|
327
|
+
### Rate limits are never auto-retried
|
|
328
|
+
|
|
329
|
+
The SDK does **not** automatically retry `429` (rate limit) or `430` (concurrent-request limit) responses. As soon as the API returns one, the transport throws `RateLimitError` so your application can decide what to do — back off, queue the request, surface it to the user, etc. Generic retry settings (`RetryPolicy.retryOnStatus`, `maxRetries`) do **not** apply to rate limits.
|
|
330
|
+
|
|
331
|
+
The server reports the back-off hint in the response **body** as the `retryAfter` field — typically a duration with an `s` suffix (e.g. `"30s"`, `"60s"`). The SDK surfaces that value unchanged on `error.retryAfter`.
|
|
332
|
+
|
|
333
|
+
```typescript
|
|
334
|
+
import { RateLimitError } from '@microsoft/webiq';
|
|
335
|
+
|
|
336
|
+
try {
|
|
337
|
+
const results = await client.web.search('test');
|
|
338
|
+
} catch (error) {
|
|
339
|
+
if (error instanceof RateLimitError) {
|
|
340
|
+
// error.retryAfter is the server-provided value from the response body
|
|
341
|
+
// (e.g. "60s"). The SDK does not parse or normalize it for you.
|
|
342
|
+
console.warn(`Rate limited. Retry after: ${error.retryAfter}`);
|
|
343
|
+
// Your retry strategy lives here — the SDK will not retry for you.
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### Basic Error Handling
|
|
349
|
+
|
|
350
|
+
```typescript
|
|
351
|
+
import {
|
|
352
|
+
WebIQError,
|
|
353
|
+
APIConnectionError,
|
|
354
|
+
APIStatusError,
|
|
355
|
+
AuthenticationError,
|
|
356
|
+
PermissionDeniedError,
|
|
357
|
+
RateLimitError,
|
|
358
|
+
} from '@microsoft/webiq';
|
|
359
|
+
|
|
360
|
+
try {
|
|
361
|
+
const results = await client.web.search('test');
|
|
362
|
+
} catch (error) {
|
|
363
|
+
if (error instanceof PermissionDeniedError) {
|
|
364
|
+
// 403 — authenticated, but not allowed to call this resource
|
|
365
|
+
console.error(`Forbidden (HTTP ${error.statusCode}):`, error.message);
|
|
366
|
+
} else if (error instanceof AuthenticationError) {
|
|
367
|
+
// 401 — invalid or missing API key
|
|
368
|
+
console.error(`Auth failed (HTTP ${error.statusCode}):`, error.message);
|
|
369
|
+
} else if (error instanceof RateLimitError) {
|
|
370
|
+
// 429 or 430 — rate limit / concurrent-request limit (never auto-retried)
|
|
371
|
+
console.error(`Rate limited. Retry after: ${error.retryAfter}`);
|
|
372
|
+
} else if (error instanceof APIStatusError) {
|
|
373
|
+
// Other HTTP errors (400, 404, 500, 503, 504, etc.)
|
|
374
|
+
console.error(`API error (HTTP ${error.statusCode}):`, error.message);
|
|
375
|
+
} else if (error instanceof APIConnectionError) {
|
|
376
|
+
// Network issues
|
|
377
|
+
console.error('Connection failed:', error.message);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
### Inspecting Error Details
|
|
383
|
+
|
|
384
|
+
All `APIStatusError` instances (including `PermissionDeniedError` and `RateLimitError`) expose the full error response body. The pattern below applies when the **server** rejects a request (e.g. a 4xx the SDK couldn't pre-validate, or a transient 5xx). `browse.fetch` is a convenient way to trigger one — a missing or filtered URL surfaces as a structured error:
|
|
385
|
+
|
|
386
|
+
```typescript
|
|
387
|
+
import { WebIQClient, APIStatusError } from '@microsoft/webiq';
|
|
388
|
+
|
|
389
|
+
async function main() {
|
|
390
|
+
const client = new WebIQClient({ apiKey: 'your-api-key' });
|
|
391
|
+
|
|
392
|
+
try {
|
|
393
|
+
await client.browse.fetch('https://www.not-microsoft.com');
|
|
394
|
+
} catch (error) {
|
|
395
|
+
if (error instanceof APIStatusError) {
|
|
396
|
+
console.error(`Status: ${error.statusCode}`); // e.g. 404
|
|
397
|
+
console.error(`Message: ${error.message}`); // e.g. "No result is found"
|
|
398
|
+
|
|
399
|
+
// Full error body from the API response
|
|
400
|
+
if (error.body && typeof error.body === 'object') {
|
|
401
|
+
const body = error.body as Record<string, unknown>;
|
|
402
|
+
console.error(`Error code: ${body.errorCode}`); // e.g. "BrowseApiDocNotFound"
|
|
403
|
+
console.error(`Category: ${body.errorCategory}`); // e.g. "UserError"
|
|
404
|
+
console.error(`Details: ${body.technicalDetails}`); // e.g. "NotFound"
|
|
405
|
+
console.error(`Trace ID: ${body.traceId}`); // for debugging with support
|
|
406
|
+
console.error(`Retry after: ${body.retryAfter}`); // for retryable errors
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
main().catch(console.error);
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
## Concurrent Requests
|
|
416
|
+
|
|
417
|
+
```typescript
|
|
418
|
+
const [web, news, videos] = await Promise.all([
|
|
419
|
+
client.web.search('TypeScript'),
|
|
420
|
+
client.news.search('programming'),
|
|
421
|
+
client.videos.search('tutorial'),
|
|
422
|
+
]);
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
## License
|
|
426
|
+
|
|
427
|
+
MIT
|