@microsoft/webiq 0.1.7 → 1.0.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/README.md +180 -62
- package/dist/index.d.mts +230 -14
- package/dist/index.d.ts +230 -14
- package/dist/index.js +404 -10
- package/dist/index.mjs +387 -11
- package/package.json +7 -5
package/dist/index.mjs
CHANGED
|
@@ -330,9 +330,31 @@ var APIStatusError = class _APIStatusError extends WebIQError {
|
|
|
330
330
|
this.name = "APIStatusError";
|
|
331
331
|
this.statusCode = statusCode;
|
|
332
332
|
this.body = options?.body;
|
|
333
|
-
this.requestId = options?.requestId;
|
|
334
|
-
this.traceId = options?.traceId;
|
|
335
333
|
Object.setPrototypeOf(this, _APIStatusError.prototype);
|
|
334
|
+
this.requestId = options?.requestId ?? this.stringField("requestId");
|
|
335
|
+
this.traceId = options?.traceId ?? this.stringField("traceId");
|
|
336
|
+
}
|
|
337
|
+
stringField(name) {
|
|
338
|
+
if (this.body && typeof this.body === "object" && name in this.body) {
|
|
339
|
+
const value = this.body[name];
|
|
340
|
+
return typeof value === "string" ? value : void 0;
|
|
341
|
+
}
|
|
342
|
+
return void 0;
|
|
343
|
+
}
|
|
344
|
+
get errorCode() {
|
|
345
|
+
return this.stringField("errorCode");
|
|
346
|
+
}
|
|
347
|
+
get errorCategory() {
|
|
348
|
+
return this.stringField("errorCategory");
|
|
349
|
+
}
|
|
350
|
+
get userMessage() {
|
|
351
|
+
return this.stringField("userMessage");
|
|
352
|
+
}
|
|
353
|
+
get technicalDetails() {
|
|
354
|
+
return this.stringField("technicalDetails");
|
|
355
|
+
}
|
|
356
|
+
get retryAfter() {
|
|
357
|
+
return this.stringField("retryAfter");
|
|
336
358
|
}
|
|
337
359
|
};
|
|
338
360
|
var RateLimitError = class _RateLimitError extends APIStatusError {
|
|
@@ -349,6 +371,20 @@ var RateLimitError = class _RateLimitError extends APIStatusError {
|
|
|
349
371
|
return void 0;
|
|
350
372
|
}
|
|
351
373
|
};
|
|
374
|
+
var BadRequestError = class _BadRequestError extends APIStatusError {
|
|
375
|
+
constructor(statusCode, message, options) {
|
|
376
|
+
super(statusCode, message, options);
|
|
377
|
+
this.name = "BadRequestError";
|
|
378
|
+
Object.setPrototypeOf(this, _BadRequestError.prototype);
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
var NotFoundError = class _NotFoundError extends APIStatusError {
|
|
382
|
+
constructor(statusCode, message, options) {
|
|
383
|
+
super(statusCode, message, options);
|
|
384
|
+
this.name = "NotFoundError";
|
|
385
|
+
Object.setPrototypeOf(this, _NotFoundError.prototype);
|
|
386
|
+
}
|
|
387
|
+
};
|
|
352
388
|
var AuthenticationError = class _AuthenticationError extends APIStatusError {
|
|
353
389
|
constructor(statusCode, message, options) {
|
|
354
390
|
super(statusCode, message, options);
|
|
@@ -396,7 +432,7 @@ var RetryPolicy = class {
|
|
|
396
432
|
this.maxRetries = options?.maxRetries ?? 2;
|
|
397
433
|
this.baseDelaySeconds = options?.baseDelaySeconds ?? 0.25;
|
|
398
434
|
this.maxDelaySeconds = options?.maxDelaySeconds ?? 4;
|
|
399
|
-
this.retryOnStatus = new Set(options?.retryOnStatus ?? [408, 500, 502,
|
|
435
|
+
this.retryOnStatus = new Set(options?.retryOnStatus ?? [408, 500, 502, 520, 523, 524]);
|
|
400
436
|
}
|
|
401
437
|
/**
|
|
402
438
|
* Compute the delay before the next retry attempt.
|
|
@@ -407,7 +443,7 @@ var RetryPolicy = class {
|
|
|
407
443
|
*/
|
|
408
444
|
computeDelay(attempt, retryAfter) {
|
|
409
445
|
if (retryAfter !== void 0) {
|
|
410
|
-
return
|
|
446
|
+
return retryAfter;
|
|
411
447
|
}
|
|
412
448
|
const delay = Math.random() * this.baseDelaySeconds * Math.pow(2, attempt - 1);
|
|
413
449
|
return Math.min(delay, this.maxDelaySeconds);
|
|
@@ -419,8 +455,24 @@ var RetryPolicy = class {
|
|
|
419
455
|
return this.retryOnStatus.has(statusCode);
|
|
420
456
|
}
|
|
421
457
|
};
|
|
422
|
-
function sleep(seconds) {
|
|
423
|
-
|
|
458
|
+
async function sleep(seconds, signal) {
|
|
459
|
+
let remainingMs = seconds * 1e3;
|
|
460
|
+
do {
|
|
461
|
+
signal?.throwIfAborted();
|
|
462
|
+
const delayMs = Math.min(remainingMs, 2147483647);
|
|
463
|
+
await new Promise((resolve, reject) => {
|
|
464
|
+
const onAbort = () => {
|
|
465
|
+
clearTimeout(timer);
|
|
466
|
+
reject(signal?.reason ?? new DOMException("Request aborted", "AbortError"));
|
|
467
|
+
};
|
|
468
|
+
const timer = setTimeout(() => {
|
|
469
|
+
signal?.removeEventListener("abort", onAbort);
|
|
470
|
+
resolve();
|
|
471
|
+
}, delayMs);
|
|
472
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
473
|
+
});
|
|
474
|
+
remainingMs -= delayMs;
|
|
475
|
+
} while (remainingMs > 0);
|
|
424
476
|
}
|
|
425
477
|
|
|
426
478
|
// src/telemetry.ts
|
|
@@ -465,15 +517,18 @@ function extractMetadata(body, headers) {
|
|
|
465
517
|
}
|
|
466
518
|
var HttpTransport = class {
|
|
467
519
|
baseUrl;
|
|
520
|
+
appId;
|
|
468
521
|
auth;
|
|
469
522
|
telemetry;
|
|
470
523
|
retry;
|
|
471
524
|
timeout;
|
|
472
525
|
fetchFn;
|
|
473
526
|
activeControllers = /* @__PURE__ */ new Set();
|
|
527
|
+
closeController = new AbortController();
|
|
474
528
|
closed = false;
|
|
475
529
|
constructor(options) {
|
|
476
530
|
this.baseUrl = options.baseUrl.replace(/\/$/, "");
|
|
531
|
+
this.appId = options.appId;
|
|
477
532
|
this.auth = options.auth;
|
|
478
533
|
this.telemetry = options.telemetryHook;
|
|
479
534
|
this.retry = options.retry ?? new RetryPolicy();
|
|
@@ -482,7 +537,19 @@ var HttpTransport = class {
|
|
|
482
537
|
}
|
|
483
538
|
async request(options) {
|
|
484
539
|
const { method, path, json, signal: callerSignal } = options;
|
|
540
|
+
const retrySignal = callerSignal ? AbortSignal.any([callerSignal, this.closeController.signal]) : this.closeController.signal;
|
|
485
541
|
const effectiveTimeout = options.timeout ?? this.timeout;
|
|
542
|
+
let url = `${this.baseUrl}${path}`;
|
|
543
|
+
if (this.appId) {
|
|
544
|
+
const fragmentIndex = url.indexOf("#");
|
|
545
|
+
const fragment = fragmentIndex < 0 ? "" : url.slice(fragmentIndex);
|
|
546
|
+
const target = fragmentIndex < 0 ? url : url.slice(0, fragmentIndex);
|
|
547
|
+
const queryIndex = target.indexOf("?");
|
|
548
|
+
const pathname = queryIndex < 0 ? target : target.slice(0, queryIndex);
|
|
549
|
+
const params = new URLSearchParams(queryIndex < 0 ? "" : target.slice(queryIndex + 1));
|
|
550
|
+
params.set("appid", this.appId);
|
|
551
|
+
url = `${pathname}?${params.toString()}${fragment}`;
|
|
552
|
+
}
|
|
486
553
|
let attempt = 0;
|
|
487
554
|
while (true) {
|
|
488
555
|
if (this.closed) {
|
|
@@ -501,7 +568,7 @@ var HttpTransport = class {
|
|
|
501
568
|
const fetchSignal = callerSignal ? AbortSignal.any([callerSignal, abortController.signal]) : abortController.signal;
|
|
502
569
|
let response;
|
|
503
570
|
try {
|
|
504
|
-
response = await this.fetchFn(
|
|
571
|
+
response = await this.fetchFn(url, {
|
|
505
572
|
method,
|
|
506
573
|
headers: {
|
|
507
574
|
"Content-Type": "application/json",
|
|
@@ -536,11 +603,13 @@ var HttpTransport = class {
|
|
|
536
603
|
traceId,
|
|
537
604
|
retryAfter,
|
|
538
605
|
elapsedMs: elapsed,
|
|
606
|
+
responseSizeBytes: responseText ? new TextEncoder().encode(responseText).length : 0,
|
|
539
607
|
errorBody: response.status >= 400 ? body : void 0
|
|
540
608
|
})
|
|
541
609
|
);
|
|
542
610
|
if (response.status === 401) {
|
|
543
|
-
|
|
611
|
+
const message = body && typeof body === "object" && "userMessage" in body ? String(body.userMessage) : "Unauthorized";
|
|
612
|
+
throw new AuthenticationError(response.status, message, {
|
|
544
613
|
body,
|
|
545
614
|
requestId,
|
|
546
615
|
traceId
|
|
@@ -561,9 +630,17 @@ var HttpTransport = class {
|
|
|
561
630
|
}
|
|
562
631
|
if (this.retry.shouldRetry(response.status) && attempt <= this.retry.maxRetries) {
|
|
563
632
|
const delay = this.retry.computeDelay(attempt, parseRetryAfter(retryAfter));
|
|
564
|
-
await sleep(delay);
|
|
633
|
+
await sleep(delay, retrySignal);
|
|
565
634
|
continue;
|
|
566
635
|
}
|
|
636
|
+
if (response.status === 400) {
|
|
637
|
+
const message = body && typeof body === "object" && "userMessage" in body ? String(body.userMessage) : "Bad request";
|
|
638
|
+
throw new BadRequestError(response.status, message, { body, requestId, traceId });
|
|
639
|
+
}
|
|
640
|
+
if (response.status === 404) {
|
|
641
|
+
const message = body && typeof body === "object" && "userMessage" in body ? String(body.userMessage) : "Not found";
|
|
642
|
+
throw new NotFoundError(response.status, message, { body, requestId, traceId });
|
|
643
|
+
}
|
|
567
644
|
if (response.status >= 400) {
|
|
568
645
|
const message = body && typeof body === "object" && "userMessage" in body ? String(body.userMessage) : `HTTP ${response.status}`;
|
|
569
646
|
throw new APIStatusError(response.status, message, { body, requestId, traceId });
|
|
@@ -593,7 +670,7 @@ var HttpTransport = class {
|
|
|
593
670
|
});
|
|
594
671
|
}
|
|
595
672
|
if (attempt <= this.retry.maxRetries) {
|
|
596
|
-
await sleep(this.retry.computeDelay(attempt));
|
|
673
|
+
await sleep(this.retry.computeDelay(attempt), retrySignal);
|
|
597
674
|
continue;
|
|
598
675
|
}
|
|
599
676
|
throw new APIConnectionError(
|
|
@@ -605,13 +682,293 @@ var HttpTransport = class {
|
|
|
605
682
|
}
|
|
606
683
|
async close() {
|
|
607
684
|
this.closed = true;
|
|
685
|
+
this.closeController.abort(new APIConnectionError("Transport is closed"));
|
|
608
686
|
Array.from(this.activeControllers).forEach((controller) => controller.abort());
|
|
609
687
|
this.activeControllers.clear();
|
|
610
688
|
}
|
|
611
689
|
};
|
|
612
690
|
|
|
691
|
+
// src/feedback.ts
|
|
692
|
+
var FeedbackError = class extends Error {
|
|
693
|
+
constructor(code, message, statusCode) {
|
|
694
|
+
super(message);
|
|
695
|
+
this.code = code;
|
|
696
|
+
this.statusCode = statusCode;
|
|
697
|
+
this.name = new.target.name;
|
|
698
|
+
}
|
|
699
|
+
code;
|
|
700
|
+
statusCode;
|
|
701
|
+
};
|
|
702
|
+
var InvalidFeedbackInputError = class extends FeedbackError {
|
|
703
|
+
constructor(message = "Invalid feedback input.", code = "invalid_feedback_input") {
|
|
704
|
+
super(code, message);
|
|
705
|
+
}
|
|
706
|
+
};
|
|
707
|
+
var MissingInstrumentationError = class extends FeedbackError {
|
|
708
|
+
constructor() {
|
|
709
|
+
super("missing_citation_base", "Citation instrumentation is unavailable.");
|
|
710
|
+
}
|
|
711
|
+
};
|
|
712
|
+
var InvalidInstrumentationSuffixError = class extends FeedbackError {
|
|
713
|
+
constructor(code = "malformed_instrumentation_suffix") {
|
|
714
|
+
super(code, "Every cited result requires a valid instrumentation suffix.");
|
|
715
|
+
}
|
|
716
|
+
};
|
|
717
|
+
var FeedbackConnectionError = class extends FeedbackError {
|
|
718
|
+
constructor() {
|
|
719
|
+
super("feedback_connection", "Feedback request failed.");
|
|
720
|
+
}
|
|
721
|
+
};
|
|
722
|
+
var FeedbackTimeoutError = class extends FeedbackError {
|
|
723
|
+
constructor() {
|
|
724
|
+
super("feedback_timeout", "Feedback request timed out.");
|
|
725
|
+
}
|
|
726
|
+
};
|
|
727
|
+
var FeedbackCancelledError = class extends FeedbackError {
|
|
728
|
+
constructor() {
|
|
729
|
+
super("feedback_cancelled", "Feedback request was cancelled.");
|
|
730
|
+
}
|
|
731
|
+
};
|
|
732
|
+
var FeedbackHttpError = class extends FeedbackError {
|
|
733
|
+
constructor(statusCode) {
|
|
734
|
+
super("feedback_http", "Feedback endpoint returned an unsuccessful response.", statusCode);
|
|
735
|
+
}
|
|
736
|
+
};
|
|
737
|
+
var FeedbackNavigationError = class extends FeedbackError {
|
|
738
|
+
constructor() {
|
|
739
|
+
super("feedback_navigation", "Feedback navigation failed.");
|
|
740
|
+
}
|
|
741
|
+
};
|
|
742
|
+
function isPresent(value) {
|
|
743
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
744
|
+
}
|
|
745
|
+
function buildClickPingUrl(response, result) {
|
|
746
|
+
if (isPresent(result.clickUrl) || !isPresent(response.instrumentationClickBase) || !isPresent(result.instrumentationSuffix)) {
|
|
747
|
+
return null;
|
|
748
|
+
}
|
|
749
|
+
return response.instrumentationClickBase + result.instrumentationSuffix;
|
|
750
|
+
}
|
|
751
|
+
function prepareClick(response, result) {
|
|
752
|
+
if (isPresent(result.clickUrl)) {
|
|
753
|
+
return { kind: "redirect", navigationUrl: result.clickUrl, pingUrl: null };
|
|
754
|
+
}
|
|
755
|
+
if (!isPresent(result.url)) {
|
|
756
|
+
throw new InvalidFeedbackInputError(
|
|
757
|
+
"A non-empty url or clickUrl is required to prepare a click.",
|
|
758
|
+
"missing_navigation_url"
|
|
759
|
+
);
|
|
760
|
+
}
|
|
761
|
+
const pingUrl = buildClickPingUrl(response, result);
|
|
762
|
+
return {
|
|
763
|
+
kind: pingUrl === null ? "untracked" : "ping",
|
|
764
|
+
navigationUrl: result.url,
|
|
765
|
+
pingUrl
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
function buildCitationPingUrl(response, results) {
|
|
769
|
+
const base = response.instrumentationCitationBase;
|
|
770
|
+
if (!isPresent(base)) {
|
|
771
|
+
throw new MissingInstrumentationError();
|
|
772
|
+
}
|
|
773
|
+
if (results.length === 0) {
|
|
774
|
+
throw new InvalidFeedbackInputError(
|
|
775
|
+
"At least one cited result is required.",
|
|
776
|
+
"empty_citation_results"
|
|
777
|
+
);
|
|
778
|
+
}
|
|
779
|
+
const payload = results.map((result) => {
|
|
780
|
+
const suffix = result.instrumentationSuffix;
|
|
781
|
+
if (!isPresent(suffix)) {
|
|
782
|
+
throw new InvalidInstrumentationSuffixError("missing_instrumentation_suffix");
|
|
783
|
+
}
|
|
784
|
+
const comma = suffix.indexOf(",");
|
|
785
|
+
const appNamespace = comma < 0 ? "" : suffix.slice(0, comma);
|
|
786
|
+
const key = comma < 0 ? "" : suffix.slice(comma + 1);
|
|
787
|
+
if (!isPresent(appNamespace) || !isPresent(key)) {
|
|
788
|
+
throw new InvalidInstrumentationSuffixError();
|
|
789
|
+
}
|
|
790
|
+
return { T: "CI.Info", FID: "LLM", Name: "citation", AppNS: appNamespace, K: key };
|
|
791
|
+
});
|
|
792
|
+
return base + encodeURIComponent(JSON.stringify(payload)).replace(
|
|
793
|
+
/[!'()*]/g,
|
|
794
|
+
(character) => `%${character.charCodeAt(0).toString(16).toUpperCase()}`
|
|
795
|
+
);
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
// src/feedback-client.ts
|
|
799
|
+
var MAX_FEEDBACK_TIMEOUT = 2147483647;
|
|
800
|
+
var DefaultFeedbackSender = class {
|
|
801
|
+
async send(url, options) {
|
|
802
|
+
const init = {
|
|
803
|
+
method: "GET",
|
|
804
|
+
credentials: "omit",
|
|
805
|
+
redirect: "error",
|
|
806
|
+
referrerPolicy: "no-referrer",
|
|
807
|
+
cache: "no-store",
|
|
808
|
+
signal: options?.signal
|
|
809
|
+
};
|
|
810
|
+
const response = await globalThis.fetch(url, init);
|
|
811
|
+
await response.body?.cancel();
|
|
812
|
+
return { status: response.status };
|
|
813
|
+
}
|
|
814
|
+
};
|
|
815
|
+
function record(value) {
|
|
816
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
817
|
+
}
|
|
818
|
+
function validateFields(value, fields) {
|
|
819
|
+
if (!record(value) || fields.some((key) => value[key] != null && typeof value[key] !== "string")) {
|
|
820
|
+
throw new InvalidFeedbackInputError();
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
function validate(options) {
|
|
824
|
+
if (!record(options)) throw new InvalidFeedbackInputError();
|
|
825
|
+
validateFields(options.response, ["instrumentationClickBase", "instrumentationCitationBase"]);
|
|
826
|
+
if (!["citation", "click"].includes(options.type) || !Array.isArray(options.events) || options.events.length === 0 || options.type === "click" && options.events.length !== 1 || options.navigation !== void 0 && !["auto", "manual"].includes(options.navigation) || options.navigationPolicy !== void 0 && !["always", "on-feedback-success"].includes(options.navigationPolicy) || options.failureMode !== void 0 && !["collect", "throw"].includes(options.failureMode) || options.signal !== void 0 && (!record(options.signal) || typeof options.signal.aborted !== "boolean" || typeof options.signal.addEventListener !== "function" || typeof options.signal.removeEventListener !== "function") || options.timeout !== void 0 && (!Number.isFinite(options.timeout) || options.timeout <= 0 || options.timeout > MAX_FEEDBACK_TIMEOUT)) {
|
|
827
|
+
throw new InvalidFeedbackInputError();
|
|
828
|
+
}
|
|
829
|
+
for (const event of options.events) {
|
|
830
|
+
if (!record(event) || "type" in event) throw new InvalidFeedbackInputError();
|
|
831
|
+
if (options.type === "citation") {
|
|
832
|
+
if ("result" in event || !Array.isArray(event.results) || event.results.length === 0) {
|
|
833
|
+
throw new InvalidFeedbackInputError();
|
|
834
|
+
}
|
|
835
|
+
for (const result of event.results) {
|
|
836
|
+
validateFields(result, ["url", "clickUrl", "instrumentationSuffix"]);
|
|
837
|
+
}
|
|
838
|
+
} else {
|
|
839
|
+
if ("results" in event) throw new InvalidFeedbackInputError();
|
|
840
|
+
validateFields(event.result, ["url", "clickUrl", "instrumentationSuffix"]);
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
function validateHttpUrl(url) {
|
|
845
|
+
let parsed;
|
|
846
|
+
try {
|
|
847
|
+
parsed = new URL(url);
|
|
848
|
+
} catch {
|
|
849
|
+
throw new InvalidFeedbackInputError("Feedback requires an absolute HTTP(S) URL.");
|
|
850
|
+
}
|
|
851
|
+
if (!["https:", "http:"].includes(parsed.protocol) || parsed.username || parsed.password) {
|
|
852
|
+
throw new InvalidFeedbackInputError(
|
|
853
|
+
"Feedback requires an absolute HTTP(S) URL without credentials."
|
|
854
|
+
);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
function browserNavigation() {
|
|
858
|
+
const runtime = globalThis;
|
|
859
|
+
const location = record(runtime) ? runtime.location : void 0;
|
|
860
|
+
if (record(location) && typeof location.assign === "function") {
|
|
861
|
+
const assign = location.assign.bind(location);
|
|
862
|
+
return {
|
|
863
|
+
navigate: (url) => {
|
|
864
|
+
assign(url);
|
|
865
|
+
}
|
|
866
|
+
};
|
|
867
|
+
}
|
|
868
|
+
return void 0;
|
|
869
|
+
}
|
|
870
|
+
var FeedbackClient = class {
|
|
871
|
+
sender;
|
|
872
|
+
navigator;
|
|
873
|
+
constructor(options = {}) {
|
|
874
|
+
this.sender = options.sender ?? new DefaultFeedbackSender();
|
|
875
|
+
this.navigator = options.navigationHandler ?? browserNavigation();
|
|
876
|
+
}
|
|
877
|
+
async pushEvents(options) {
|
|
878
|
+
validate(options);
|
|
879
|
+
let outcome;
|
|
880
|
+
let requestUrl;
|
|
881
|
+
if (options.type === "citation") {
|
|
882
|
+
const results = options.events.flatMap((event) => [...event.results]);
|
|
883
|
+
requestUrl = buildCitationPingUrl(options.response, results);
|
|
884
|
+
outcome = {
|
|
885
|
+
type: "citation",
|
|
886
|
+
feedbackStatus: "submitted",
|
|
887
|
+
navigationStatus: "not-applicable",
|
|
888
|
+
resultCount: results.length
|
|
889
|
+
};
|
|
890
|
+
} else {
|
|
891
|
+
const prepared = prepareClick(options.response, options.events[0].result);
|
|
892
|
+
validateHttpUrl(prepared.navigationUrl);
|
|
893
|
+
requestUrl = prepared.pingUrl ?? void 0;
|
|
894
|
+
outcome = {
|
|
895
|
+
type: "click",
|
|
896
|
+
clickMode: prepared.kind,
|
|
897
|
+
feedbackStatus: prepared.kind === "redirect" ? "pending-navigation" : prepared.kind === "ping" ? "submitted" : "not-instrumented",
|
|
898
|
+
navigationStatus: "required",
|
|
899
|
+
navigationUrl: prepared.navigationUrl
|
|
900
|
+
};
|
|
901
|
+
}
|
|
902
|
+
if (requestUrl !== void 0) validateHttpUrl(requestUrl);
|
|
903
|
+
if (options.signal?.aborted) throw new FeedbackCancelledError();
|
|
904
|
+
if (requestUrl !== void 0) {
|
|
905
|
+
try {
|
|
906
|
+
await this.send(requestUrl, options);
|
|
907
|
+
} catch (error) {
|
|
908
|
+
outcome.feedbackStatus = "failed";
|
|
909
|
+
outcome.feedbackError = error instanceof FeedbackError ? error : new FeedbackConnectionError();
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
if (outcome.type === "click") {
|
|
913
|
+
if (outcome.feedbackError && options.navigationPolicy === "on-feedback-success") {
|
|
914
|
+
outcome.navigationStatus = "blocked";
|
|
915
|
+
} else if (options.navigation !== "manual" && this.navigator) {
|
|
916
|
+
try {
|
|
917
|
+
await this.navigator.navigate(outcome.navigationUrl);
|
|
918
|
+
outcome.navigationStatus = "navigated";
|
|
919
|
+
if (outcome.clickMode === "redirect") outcome.feedbackStatus = "dispatched";
|
|
920
|
+
} catch {
|
|
921
|
+
outcome.navigationStatus = "failed";
|
|
922
|
+
outcome.navigationError = new FeedbackNavigationError();
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
if (options.failureMode === "throw") {
|
|
927
|
+
const error = outcome.feedbackError ?? (outcome.type === "click" ? outcome.navigationError : void 0);
|
|
928
|
+
if (error) throw error;
|
|
929
|
+
}
|
|
930
|
+
return {
|
|
931
|
+
outcomes: [outcome],
|
|
932
|
+
submittedEventCount: outcome.feedbackStatus === "submitted" ? outcome.type === "citation" ? outcome.resultCount : 1 : 0,
|
|
933
|
+
dispatchedEventCount: outcome.feedbackStatus === "dispatched" ? 1 : 0,
|
|
934
|
+
requestCount: requestUrl === void 0 ? 0 : 1,
|
|
935
|
+
navigated: outcome.navigationStatus === "navigated"
|
|
936
|
+
};
|
|
937
|
+
}
|
|
938
|
+
async send(url, options) {
|
|
939
|
+
const controller = new AbortController();
|
|
940
|
+
const timeout = options.timeout ?? 1e4;
|
|
941
|
+
let timer;
|
|
942
|
+
let onAbort;
|
|
943
|
+
const stopped = new Promise((_, reject) => {
|
|
944
|
+
onAbort = () => {
|
|
945
|
+
reject(new FeedbackCancelledError());
|
|
946
|
+
controller.abort();
|
|
947
|
+
};
|
|
948
|
+
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
949
|
+
timer = setTimeout(() => {
|
|
950
|
+
reject(new FeedbackTimeoutError());
|
|
951
|
+
controller.abort();
|
|
952
|
+
}, timeout);
|
|
953
|
+
});
|
|
954
|
+
try {
|
|
955
|
+
const response = await Promise.race([
|
|
956
|
+
this.sender.send(url, { signal: controller.signal, timeout }),
|
|
957
|
+
stopped
|
|
958
|
+
]);
|
|
959
|
+
if (!Number.isInteger(response.status)) throw new FeedbackConnectionError();
|
|
960
|
+
if (response.status < 200 || response.status >= 300)
|
|
961
|
+
throw new FeedbackHttpError(response.status);
|
|
962
|
+
} finally {
|
|
963
|
+
clearTimeout(timer);
|
|
964
|
+
if (onAbort) options.signal?.removeEventListener("abort", onAbort);
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
};
|
|
968
|
+
|
|
613
969
|
// src/client.ts
|
|
614
970
|
var WebIQClient = class {
|
|
971
|
+
feedback;
|
|
615
972
|
/** Web search resource */
|
|
616
973
|
web;
|
|
617
974
|
/** News search resource */
|
|
@@ -637,6 +994,7 @@ var WebIQClient = class {
|
|
|
637
994
|
const retry = options.retry instanceof RetryPolicy ? options.retry : options.retry ? new RetryPolicy(options.retry) : new RetryPolicy();
|
|
638
995
|
this._transport = new HttpTransport({
|
|
639
996
|
baseUrl: options.baseUrl ?? "https://api.microsoft.ai/v3",
|
|
997
|
+
appId: options.appId,
|
|
640
998
|
auth: this._auth,
|
|
641
999
|
telemetryHook: options.telemetryHook,
|
|
642
1000
|
retry,
|
|
@@ -648,6 +1006,7 @@ var WebIQClient = class {
|
|
|
648
1006
|
this.videos = new VideosResource(this._transport);
|
|
649
1007
|
this.images = new ImagesResource(this._transport);
|
|
650
1008
|
this.browse = new BrowseResource(this._transport);
|
|
1009
|
+
this.feedback = new FeedbackClient(options.feedback);
|
|
651
1010
|
}
|
|
652
1011
|
/**
|
|
653
1012
|
* Close the client and release resources.
|
|
@@ -717,6 +1076,15 @@ var LiveCrawlMode = /* @__PURE__ */ ((LiveCrawlMode2) => {
|
|
|
717
1076
|
return LiveCrawlMode2;
|
|
718
1077
|
})(LiveCrawlMode || {});
|
|
719
1078
|
|
|
1079
|
+
// src/generated-otc/models/RiskLevel.ts
|
|
1080
|
+
var RiskLevel = /* @__PURE__ */ ((RiskLevel2) => {
|
|
1081
|
+
RiskLevel2["LOW"] = "low";
|
|
1082
|
+
RiskLevel2["MODERATE"] = "moderate";
|
|
1083
|
+
RiskLevel2["HIGH"] = "high";
|
|
1084
|
+
RiskLevel2["VERY_HIGH"] = "veryHigh";
|
|
1085
|
+
return RiskLevel2;
|
|
1086
|
+
})(RiskLevel || {});
|
|
1087
|
+
|
|
720
1088
|
// src/generated-otc/models/SafeSearch.ts
|
|
721
1089
|
var SafeSearch = /* @__PURE__ */ ((SafeSearch2) => {
|
|
722
1090
|
SafeSearch2["OFF"] = "off";
|
|
@@ -724,6 +1092,14 @@ var SafeSearch = /* @__PURE__ */ ((SafeSearch2) => {
|
|
|
724
1092
|
return SafeSearch2;
|
|
725
1093
|
})(SafeSearch || {});
|
|
726
1094
|
|
|
1095
|
+
// src/generated-otc/models/SourceQuality.ts
|
|
1096
|
+
var SourceQuality = /* @__PURE__ */ ((SourceQuality2) => {
|
|
1097
|
+
SourceQuality2["INSUFFICIENT"] = "insufficient";
|
|
1098
|
+
SourceQuality2["ADEQUATE"] = "adequate";
|
|
1099
|
+
SourceQuality2["PREFERRED"] = "preferred";
|
|
1100
|
+
return SourceQuality2;
|
|
1101
|
+
})(SourceQuality || {});
|
|
1102
|
+
|
|
727
1103
|
// src/generated-otc/models/VideoDuration.ts
|
|
728
1104
|
var VideoDuration = /* @__PURE__ */ ((VideoDuration2) => {
|
|
729
1105
|
VideoDuration2["SHORT"] = "short";
|
|
@@ -741,4 +1117,4 @@ var VideoResolution = /* @__PURE__ */ ((VideoResolution2) => {
|
|
|
741
1117
|
return VideoResolution2;
|
|
742
1118
|
})(VideoResolution || {});
|
|
743
1119
|
|
|
744
|
-
export { APIConnectionError, APIStatusError, ApiKeyAuth, AuthenticationError, BrowseContentFormat, BrowseResource, ContentFormat, ContentTier, DEFAULT_SCOPE, DEFAULT_TIMEOUT, EntraIDAuth, HttpTransport, ImageAspectRatio, ImageColor, ImageSize, ImagesResource, LiveCrawlMode, NewsResource, PermissionDeniedError, RateLimitError, RetryPolicy, SafeSearch, VideoDuration, VideoResolution, VideosResource, WebIQClient, WebIQError, WebResource, createAuth, createClient, createTelemetryEvent, safeCall };
|
|
1120
|
+
export { APIConnectionError, APIStatusError, ApiKeyAuth, AuthenticationError, BadRequestError, BrowseContentFormat, BrowseResource, ContentFormat, ContentTier, DEFAULT_SCOPE, DEFAULT_TIMEOUT, DefaultFeedbackSender, EntraIDAuth, FeedbackCancelledError, FeedbackClient, FeedbackConnectionError, FeedbackError, FeedbackHttpError, FeedbackNavigationError, FeedbackTimeoutError, HttpTransport, ImageAspectRatio, ImageColor, ImageSize, ImagesResource, InvalidFeedbackInputError, InvalidInstrumentationSuffixError, LiveCrawlMode, MissingInstrumentationError, NewsResource, NotFoundError, PermissionDeniedError, RateLimitError, RetryPolicy, RiskLevel, SafeSearch, SourceQuality, VideoDuration, VideoResolution, VideosResource, WebIQClient, WebIQError, WebResource, buildCitationPingUrl, buildClickPingUrl, createAuth, createClient, createTelemetryEvent, prepareClick, safeCall };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@microsoft/webiq",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Web IQ API TypeScript SDK",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
"scripts": {
|
|
20
20
|
"build": "tsup",
|
|
21
21
|
"dev": "tsup --watch",
|
|
22
|
-
"lint": "eslint src
|
|
23
|
-
"lint:fix": "eslint src --
|
|
22
|
+
"lint": "eslint src",
|
|
23
|
+
"lint:fix": "eslint src --fix",
|
|
24
24
|
"format": "prettier --write \"src/**/*.ts\" \"*.md\" \"*.json\" --ignore-path ../../.prettierignore",
|
|
25
25
|
"format:check": "prettier --check \"src/**/*.ts\" \"*.md\" \"*.json\" --ignore-path ../../.prettierignore",
|
|
26
26
|
"typecheck": "tsc --noEmit",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"author": "Microsoft <webiqsdk@microsoft.com>",
|
|
37
37
|
"license": "MIT",
|
|
38
38
|
"engines": {
|
|
39
|
-
"node": ">=
|
|
39
|
+
"node": "^22.13.0 || >=24",
|
|
40
40
|
"npm": ">=10"
|
|
41
41
|
},
|
|
42
42
|
"publishConfig": {
|
|
@@ -45,9 +45,10 @@
|
|
|
45
45
|
"sideEffects": false,
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@azure/identity": ">=4.13.1",
|
|
48
|
+
"@eslint/js": "^10.0.1",
|
|
48
49
|
"@types/js-yaml": "^4.0.9",
|
|
49
50
|
"@types/node": "^20.10.0",
|
|
50
|
-
"eslint": "^8.
|
|
51
|
+
"eslint": "^10.8.0",
|
|
51
52
|
"js-yaml": "^5.2.0",
|
|
52
53
|
"openapi-typescript-codegen": "^0.30.0",
|
|
53
54
|
"prettier": "^3.5.3",
|
|
@@ -55,6 +56,7 @@
|
|
|
55
56
|
"tsup": "^8.0.0",
|
|
56
57
|
"tsx": "^4.21.0",
|
|
57
58
|
"typescript": "^5.3.0",
|
|
59
|
+
"typescript-eslint": "^8.68.0",
|
|
58
60
|
"vitest": "^4.0.18"
|
|
59
61
|
},
|
|
60
62
|
"peerDependencies": {
|