@qoniai/qoni 0.4.1 → 0.4.3
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 +31 -0
- package/README.md +23 -1
- package/README.zh-CN.md +16 -1
- package/dist/index.cjs +209 -107
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -2
- package/dist/index.d.ts +7 -2
- package/dist/index.js +209 -107
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -224,6 +224,7 @@ interface RawRequestInput extends TokenInput {
|
|
|
224
224
|
headers?: Record<string, string>;
|
|
225
225
|
}
|
|
226
226
|
interface RequestPayload {
|
|
227
|
+
signal?: AbortSignal;
|
|
227
228
|
query?: JsonObject;
|
|
228
229
|
body?: JsonObject | BodyInit;
|
|
229
230
|
headers?: Record<string, string>;
|
|
@@ -638,7 +639,7 @@ interface RunWaitOptions {
|
|
|
638
639
|
* with the token + run addressing already bound.
|
|
639
640
|
*/
|
|
640
641
|
interface RunWireOps {
|
|
641
|
-
getDetail(): Promise<JsonObject>;
|
|
642
|
+
getDetail(signal?: AbortSignal): Promise<JsonObject>;
|
|
642
643
|
streamEvents(opts: RunEventsOptions): AsyncIterable<QoniEvent<unknown>>;
|
|
643
644
|
/**
|
|
644
645
|
* Invoke an interaction action by posting to its declared endpoint. The
|
|
@@ -648,7 +649,7 @@ interface RunWireOps {
|
|
|
648
649
|
postAction(action: Action, body: JsonObject | undefined): Promise<void>;
|
|
649
650
|
cancel(reason?: string): Promise<JsonObject>;
|
|
650
651
|
/** Products without artifacts leave this undefined → `result.artifacts` is []. */
|
|
651
|
-
listArtifacts?(): Promise<Artifact[]>;
|
|
652
|
+
listArtifacts?(signal?: AbortSignal): Promise<Artifact[]>;
|
|
652
653
|
}
|
|
653
654
|
/**
|
|
654
655
|
* Handle to a single run. Generic over the product's event set (分型): a
|
|
@@ -782,6 +783,7 @@ declare function createDoAnythingNamespace(transport: QoniTransport): {
|
|
|
782
783
|
createRun: <T = unknown>(input: RuntimeTokenInput & JsonObject) => Promise<QoniResponse<T>>;
|
|
783
784
|
getRun: <T = unknown>(input: RuntimeTokenInput & {
|
|
784
785
|
runId: string;
|
|
786
|
+
signal?: AbortSignal;
|
|
785
787
|
}) => Promise<QoniResponse<T>>;
|
|
786
788
|
events: <T = unknown>(input: RuntimeTokenInput & {
|
|
787
789
|
runId: string;
|
|
@@ -862,6 +864,7 @@ declare function createDeepResearchNamespace(transport: QoniTransport): {
|
|
|
862
864
|
run: <T = unknown>(input: RuntimeTokenInput & JsonObject) => Promise<QoniResponse<T>>;
|
|
863
865
|
get: <T = unknown>(input: RuntimeTokenInput & {
|
|
864
866
|
runId: string;
|
|
867
|
+
signal?: AbortSignal;
|
|
865
868
|
}) => Promise<QoniResponse<T>>;
|
|
866
869
|
events: <T = unknown>(input: RuntimeTokenInput & {
|
|
867
870
|
runId: string;
|
|
@@ -885,6 +888,7 @@ declare function createDeepResearchNamespace(transport: QoniTransport): {
|
|
|
885
888
|
} & JsonObject) => Promise<QoniResponse<T>>;
|
|
886
889
|
listArtifacts: <T = unknown>(input: RuntimeTokenInput & {
|
|
887
890
|
runId: string;
|
|
891
|
+
signal?: AbortSignal;
|
|
888
892
|
}) => Promise<QoniResponse<T>>;
|
|
889
893
|
getArtifact: <T = unknown>(input: RuntimeTokenInput & {
|
|
890
894
|
runId: string;
|
|
@@ -1190,6 +1194,7 @@ declare function createWebSearchNamespace(transport: QoniTransport): {
|
|
|
1190
1194
|
run: <T = unknown>(input: RuntimeTokenInput & JsonObject) => Promise<QoniResponse<T>>;
|
|
1191
1195
|
get: <T = unknown>(input: RuntimeTokenInput & {
|
|
1192
1196
|
runId: string;
|
|
1197
|
+
signal?: AbortSignal;
|
|
1193
1198
|
}) => Promise<QoniResponse<T>>;
|
|
1194
1199
|
events: <T = unknown>(input: RuntimeTokenInput & {
|
|
1195
1200
|
runId: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -224,6 +224,7 @@ interface RawRequestInput extends TokenInput {
|
|
|
224
224
|
headers?: Record<string, string>;
|
|
225
225
|
}
|
|
226
226
|
interface RequestPayload {
|
|
227
|
+
signal?: AbortSignal;
|
|
227
228
|
query?: JsonObject;
|
|
228
229
|
body?: JsonObject | BodyInit;
|
|
229
230
|
headers?: Record<string, string>;
|
|
@@ -638,7 +639,7 @@ interface RunWaitOptions {
|
|
|
638
639
|
* with the token + run addressing already bound.
|
|
639
640
|
*/
|
|
640
641
|
interface RunWireOps {
|
|
641
|
-
getDetail(): Promise<JsonObject>;
|
|
642
|
+
getDetail(signal?: AbortSignal): Promise<JsonObject>;
|
|
642
643
|
streamEvents(opts: RunEventsOptions): AsyncIterable<QoniEvent<unknown>>;
|
|
643
644
|
/**
|
|
644
645
|
* Invoke an interaction action by posting to its declared endpoint. The
|
|
@@ -648,7 +649,7 @@ interface RunWireOps {
|
|
|
648
649
|
postAction(action: Action, body: JsonObject | undefined): Promise<void>;
|
|
649
650
|
cancel(reason?: string): Promise<JsonObject>;
|
|
650
651
|
/** Products without artifacts leave this undefined → `result.artifacts` is []. */
|
|
651
|
-
listArtifacts?(): Promise<Artifact[]>;
|
|
652
|
+
listArtifacts?(signal?: AbortSignal): Promise<Artifact[]>;
|
|
652
653
|
}
|
|
653
654
|
/**
|
|
654
655
|
* Handle to a single run. Generic over the product's event set (分型): a
|
|
@@ -782,6 +783,7 @@ declare function createDoAnythingNamespace(transport: QoniTransport): {
|
|
|
782
783
|
createRun: <T = unknown>(input: RuntimeTokenInput & JsonObject) => Promise<QoniResponse<T>>;
|
|
783
784
|
getRun: <T = unknown>(input: RuntimeTokenInput & {
|
|
784
785
|
runId: string;
|
|
786
|
+
signal?: AbortSignal;
|
|
785
787
|
}) => Promise<QoniResponse<T>>;
|
|
786
788
|
events: <T = unknown>(input: RuntimeTokenInput & {
|
|
787
789
|
runId: string;
|
|
@@ -862,6 +864,7 @@ declare function createDeepResearchNamespace(transport: QoniTransport): {
|
|
|
862
864
|
run: <T = unknown>(input: RuntimeTokenInput & JsonObject) => Promise<QoniResponse<T>>;
|
|
863
865
|
get: <T = unknown>(input: RuntimeTokenInput & {
|
|
864
866
|
runId: string;
|
|
867
|
+
signal?: AbortSignal;
|
|
865
868
|
}) => Promise<QoniResponse<T>>;
|
|
866
869
|
events: <T = unknown>(input: RuntimeTokenInput & {
|
|
867
870
|
runId: string;
|
|
@@ -885,6 +888,7 @@ declare function createDeepResearchNamespace(transport: QoniTransport): {
|
|
|
885
888
|
} & JsonObject) => Promise<QoniResponse<T>>;
|
|
886
889
|
listArtifacts: <T = unknown>(input: RuntimeTokenInput & {
|
|
887
890
|
runId: string;
|
|
891
|
+
signal?: AbortSignal;
|
|
888
892
|
}) => Promise<QoniResponse<T>>;
|
|
889
893
|
getArtifact: <T = unknown>(input: RuntimeTokenInput & {
|
|
890
894
|
runId: string;
|
|
@@ -1190,6 +1194,7 @@ declare function createWebSearchNamespace(transport: QoniTransport): {
|
|
|
1190
1194
|
run: <T = unknown>(input: RuntimeTokenInput & JsonObject) => Promise<QoniResponse<T>>;
|
|
1191
1195
|
get: <T = unknown>(input: RuntimeTokenInput & {
|
|
1192
1196
|
runId: string;
|
|
1197
|
+
signal?: AbortSignal;
|
|
1193
1198
|
}) => Promise<QoniResponse<T>>;
|
|
1194
1199
|
events: <T = unknown>(input: RuntimeTokenInput & {
|
|
1195
1200
|
runId: string;
|
package/dist/index.js
CHANGED
|
@@ -349,8 +349,14 @@ function decodeDataUri(uri) {
|
|
|
349
349
|
const match = /^data:([^;,]*)?(;base64)?,(.*)$/s.exec(uri);
|
|
350
350
|
if (!match) return void 0;
|
|
351
351
|
const [, mime, isBase64, body] = match;
|
|
352
|
-
const
|
|
353
|
-
|
|
352
|
+
const encoded = isBase64 ? body.replace(/\s/g, "") : body;
|
|
353
|
+
if (isBase64 && (!/^[A-Za-z0-9+/_-]+={0,2}$/.test(encoded) || encoded.length % 4 === 1 || encoded.includes("=") && encoded.length % 4 !== 0)) return void 0;
|
|
354
|
+
try {
|
|
355
|
+
const bytes = isBase64 ? new Uint8Array(Buffer.from(encoded, "base64")) : new TextEncoder().encode(decodeURIComponent(encoded));
|
|
356
|
+
return bytes.length ? { bytes, mime: mime || "application/octet-stream" } : void 0;
|
|
357
|
+
} catch {
|
|
358
|
+
return void 0;
|
|
359
|
+
}
|
|
354
360
|
}
|
|
355
361
|
function asString(value) {
|
|
356
362
|
return typeof value === "string" && value ? value : void 0;
|
|
@@ -768,11 +774,11 @@ var RunHandle = class {
|
|
|
768
774
|
}
|
|
769
775
|
/** Drive the run to a terminal state and return the settled result. */
|
|
770
776
|
async wait(opts = {}) {
|
|
771
|
-
const { signal, timedOut } = withTimeout(opts.signal, opts.timeoutMs);
|
|
777
|
+
const { signal, timedOut, dispose } = withTimeout(opts.signal, opts.timeoutMs);
|
|
772
778
|
let lastEventId;
|
|
773
779
|
let eventIdAtLastClose;
|
|
774
780
|
let sawCleanClose = false;
|
|
775
|
-
let
|
|
781
|
+
let terminalDetail;
|
|
776
782
|
let terminalDoneReason;
|
|
777
783
|
let screenshotIndex = 0;
|
|
778
784
|
try {
|
|
@@ -780,24 +786,28 @@ var RunHandle = class {
|
|
|
780
786
|
for await (const event of this.events({
|
|
781
787
|
signal,
|
|
782
788
|
lastEventId,
|
|
783
|
-
onWireEvent:
|
|
789
|
+
onWireEvent: (wire) => waitWithSignal(() => {
|
|
790
|
+
if (wire.id !== void 0) lastEventId = wire.id;
|
|
791
|
+
return opts.onWireEvent?.(wire);
|
|
792
|
+
}, signal),
|
|
784
793
|
onReconnect: opts.onReconnect
|
|
785
794
|
})) {
|
|
786
|
-
|
|
787
|
-
await opts.onEvent?.(event);
|
|
795
|
+
await waitWithSignal(() => opts.onEvent?.(event), signal);
|
|
788
796
|
if (event.type === "screenshot") {
|
|
789
|
-
await opts.onScreenshot?.(event.image, screenshotIndex++);
|
|
797
|
+
await waitWithSignal(() => opts.onScreenshot?.(event.image, screenshotIndex++), signal);
|
|
790
798
|
} else if (event.type === "interaction") {
|
|
791
|
-
await opts.onInteraction?.(this.interactionHandle(event.data), event);
|
|
799
|
+
await waitWithSignal(() => opts.onInteraction?.(this.interactionHandle(event.data), event), signal);
|
|
792
800
|
}
|
|
793
801
|
if (event.isTerminal) {
|
|
794
|
-
terminalPayload = isJsonObject(event.raw.data) ? event.raw.data : void 0;
|
|
795
802
|
if (event.type === "done") terminalDoneReason = event.data.terminalReason || void 0;
|
|
796
803
|
break settled;
|
|
797
804
|
}
|
|
798
805
|
}
|
|
799
|
-
const detail2 = await this.ops.getDetail();
|
|
800
|
-
if (isTerminalRunStatus(detail2.status))
|
|
806
|
+
const detail2 = await waitWithSignal(() => this.ops.getDetail(signal), signal);
|
|
807
|
+
if (isTerminalRunStatus(normalizeRunStatus(this.id, detail2).status)) {
|
|
808
|
+
terminalDetail = detail2;
|
|
809
|
+
break;
|
|
810
|
+
}
|
|
801
811
|
if (sawCleanClose && lastEventId === eventIdAtLastClose) {
|
|
802
812
|
throw new QoniError(
|
|
803
813
|
`run event stream ended before a terminal event while the run is still "${String(detail2.status)}" \u2014 re-attach or poll status()`,
|
|
@@ -807,6 +817,11 @@ var RunHandle = class {
|
|
|
807
817
|
sawCleanClose = true;
|
|
808
818
|
eventIdAtLastClose = lastEventId;
|
|
809
819
|
}
|
|
820
|
+
const detail = terminalDetail ?? await waitWithSignal(() => this.ops.getDetail(signal), signal);
|
|
821
|
+
this.#adoptSessionRef(detail);
|
|
822
|
+
const result = settleRunResult(this.id, detail, [], terminalDoneReason);
|
|
823
|
+
result.artifacts = this.ops.listArtifacts ? await waitWithSignal(() => this.ops.listArtifacts(signal), signal) : [];
|
|
824
|
+
return result;
|
|
810
825
|
} catch (err) {
|
|
811
826
|
if (timedOut() && !opts.signal?.aborted) {
|
|
812
827
|
throw new QoniTimeoutError(
|
|
@@ -815,17 +830,9 @@ var RunHandle = class {
|
|
|
815
830
|
);
|
|
816
831
|
}
|
|
817
832
|
throw err;
|
|
833
|
+
} finally {
|
|
834
|
+
dispose();
|
|
818
835
|
}
|
|
819
|
-
let detail;
|
|
820
|
-
try {
|
|
821
|
-
detail = await this.ops.getDetail();
|
|
822
|
-
} catch (err) {
|
|
823
|
-
if (!terminalPayload) throw err;
|
|
824
|
-
detail = terminalPayload;
|
|
825
|
-
}
|
|
826
|
-
this.#adoptSessionRef(detail);
|
|
827
|
-
const artifacts = this.ops.listArtifacts ? await this.ops.listArtifacts() : [];
|
|
828
|
-
return settleRunResult(this.id, detail, artifacts, terminalDoneReason);
|
|
829
836
|
}
|
|
830
837
|
/**
|
|
831
838
|
* Wrap an {@link Interaction} (from `event.data` of an `interaction` event)
|
|
@@ -878,15 +885,12 @@ function normalizeRunStatus(id, detail) {
|
|
|
878
885
|
function settleRunResult(runId, detail, artifacts, fallbackTerminalReason) {
|
|
879
886
|
const terminalReason = (typeof detail.terminal_reason === "string" ? detail.terminal_reason : void 0) ?? fallbackTerminalReason;
|
|
880
887
|
const isTaskSuccessful = typeof detail.is_task_successful === "boolean" ? detail.is_task_successful : void 0;
|
|
881
|
-
|
|
882
|
-
if (isTerminalRunStatus(
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
status = "failed";
|
|
888
|
-
} else {
|
|
889
|
-
status = "succeeded";
|
|
888
|
+
const status = normalizeRunStatus(runId, detail).status;
|
|
889
|
+
if (!isTerminalRunStatus(status)) {
|
|
890
|
+
throw new QoniError(
|
|
891
|
+
"run detail did not confirm a terminal state \u2014 re-attach or poll status()",
|
|
892
|
+
{ code: "upstream.failed", retryable: true, body: detail }
|
|
893
|
+
);
|
|
890
894
|
}
|
|
891
895
|
return {
|
|
892
896
|
runId,
|
|
@@ -913,19 +917,40 @@ function isVideoFrame(wire) {
|
|
|
913
917
|
return type === "browser_video_frame" || type === "run.browser_video_frame";
|
|
914
918
|
}
|
|
915
919
|
function withTimeout(signal, timeoutMs) {
|
|
916
|
-
if (!timeoutMs) return { signal, timedOut: () => false
|
|
920
|
+
if (!timeoutMs) return { signal, timedOut: () => false, dispose: () => {
|
|
921
|
+
} };
|
|
917
922
|
const controller = new AbortController();
|
|
918
923
|
let fired = false;
|
|
919
924
|
const timer = setTimeout(() => {
|
|
920
925
|
fired = true;
|
|
921
926
|
controller.abort();
|
|
922
927
|
}, timeoutMs);
|
|
923
|
-
|
|
928
|
+
const onAbort = () => controller.abort(signal?.reason);
|
|
929
|
+
const dispose = () => {
|
|
930
|
+
clearTimeout(timer);
|
|
931
|
+
signal?.removeEventListener("abort", onAbort);
|
|
932
|
+
};
|
|
933
|
+
controller.signal.addEventListener("abort", dispose, { once: true });
|
|
924
934
|
if (signal) {
|
|
925
|
-
if (signal.aborted)
|
|
926
|
-
else signal.addEventListener("abort",
|
|
935
|
+
if (signal.aborted) onAbort();
|
|
936
|
+
else signal.addEventListener("abort", onAbort, { once: true });
|
|
927
937
|
}
|
|
928
|
-
return { signal: controller.signal, timedOut: () => fired };
|
|
938
|
+
return { signal: controller.signal, timedOut: () => fired, dispose };
|
|
939
|
+
}
|
|
940
|
+
function waitWithSignal(operation, signal) {
|
|
941
|
+
if (!signal) return Promise.resolve().then(operation);
|
|
942
|
+
return new Promise((resolve, reject) => {
|
|
943
|
+
const onAbort = () => reject(signal.reason ?? new DOMException("Aborted", "AbortError"));
|
|
944
|
+
if (signal.aborted) {
|
|
945
|
+
onAbort();
|
|
946
|
+
return;
|
|
947
|
+
}
|
|
948
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
949
|
+
Promise.resolve().then(() => {
|
|
950
|
+
signal.throwIfAborted();
|
|
951
|
+
return operation();
|
|
952
|
+
}).then(resolve, reject).finally(() => signal.removeEventListener("abort", onAbort));
|
|
953
|
+
});
|
|
929
954
|
}
|
|
930
955
|
|
|
931
956
|
// src/deep-research.ts
|
|
@@ -940,6 +965,7 @@ function createDeepResearchNamespace(transport) {
|
|
|
940
965
|
`/deep_research/runs/${encodeURIComponent(input.runId)}`,
|
|
941
966
|
input.token,
|
|
942
967
|
{
|
|
968
|
+
signal: input.signal,
|
|
943
969
|
requiredScopes: ["webagent.deep_research:read"]
|
|
944
970
|
}
|
|
945
971
|
),
|
|
@@ -985,6 +1011,7 @@ function createDeepResearchNamespace(transport) {
|
|
|
985
1011
|
`/deep_research/runs/${encodeURIComponent(input.runId)}/artifacts`,
|
|
986
1012
|
input.token,
|
|
987
1013
|
{
|
|
1014
|
+
signal: input.signal,
|
|
988
1015
|
requiredScopes: ["webagent.deep_research:read"]
|
|
989
1016
|
}
|
|
990
1017
|
),
|
|
@@ -999,7 +1026,7 @@ function createDeepResearchNamespace(transport) {
|
|
|
999
1026
|
};
|
|
1000
1027
|
function buildOps(token, runId) {
|
|
1001
1028
|
return {
|
|
1002
|
-
getDetail: async () => asRecord((await api.get({ token, runId })).data),
|
|
1029
|
+
getDetail: async (signal) => asRecord((await api.get({ token, runId, signal })).data),
|
|
1003
1030
|
streamEvents: (opts) => api.events({
|
|
1004
1031
|
token,
|
|
1005
1032
|
runId,
|
|
@@ -1019,8 +1046,8 @@ function createDeepResearchNamespace(transport) {
|
|
|
1019
1046
|
);
|
|
1020
1047
|
},
|
|
1021
1048
|
cancel: async () => asRecord((await api.cancel({ token, runId })).data),
|
|
1022
|
-
listArtifacts: async () => {
|
|
1023
|
-
const res = await api.listArtifacts({ token, runId });
|
|
1049
|
+
listArtifacts: async (signal) => {
|
|
1050
|
+
const res = await api.listArtifacts({ token, runId, signal });
|
|
1024
1051
|
const items = Array.isArray(res.data?.items) ? res.data.items : [];
|
|
1025
1052
|
return items.filter(isJsonObject).map((item) => toArtifact(item, token, runId));
|
|
1026
1053
|
}
|
|
@@ -1140,6 +1167,7 @@ function createDoAnythingNamespace(transport) {
|
|
|
1140
1167
|
`/do_anything/runs/${encodeURIComponent(input.runId)}`,
|
|
1141
1168
|
input.token,
|
|
1142
1169
|
{
|
|
1170
|
+
signal: input.signal,
|
|
1143
1171
|
requiredScopes: ["webagent.do_anything:read"]
|
|
1144
1172
|
}
|
|
1145
1173
|
),
|
|
@@ -1197,7 +1225,7 @@ function createDoAnythingNamespace(transport) {
|
|
|
1197
1225
|
};
|
|
1198
1226
|
function buildOps(token, runId, capture) {
|
|
1199
1227
|
return {
|
|
1200
|
-
getDetail: async () => asRecord2((await api.getRun({ token, runId })).data),
|
|
1228
|
+
getDetail: async (signal) => asRecord2((await api.getRun({ token, runId, signal })).data),
|
|
1201
1229
|
streamEvents: (opts) => api.events({
|
|
1202
1230
|
token,
|
|
1203
1231
|
runId,
|
|
@@ -1791,6 +1819,7 @@ function createWebSearchNamespace(transport) {
|
|
|
1791
1819
|
`/web_search/runs/${encodeURIComponent(input.runId)}`,
|
|
1792
1820
|
input.token,
|
|
1793
1821
|
{
|
|
1822
|
+
signal: input.signal,
|
|
1794
1823
|
requiredScopes: ["webagent.web_search:read"]
|
|
1795
1824
|
}
|
|
1796
1825
|
),
|
|
@@ -1816,7 +1845,7 @@ function createWebSearchNamespace(transport) {
|
|
|
1816
1845
|
};
|
|
1817
1846
|
function buildOps(token, runId) {
|
|
1818
1847
|
return {
|
|
1819
|
-
getDetail: async () => asRecord4((await api.get({ token, runId })).data),
|
|
1848
|
+
getDetail: async (signal) => asRecord4((await api.get({ token, runId, signal })).data),
|
|
1820
1849
|
streamEvents: (opts) => api.events({
|
|
1821
1850
|
token,
|
|
1822
1851
|
runId,
|
|
@@ -2089,7 +2118,9 @@ var Qoni = class {
|
|
|
2089
2118
|
}
|
|
2090
2119
|
}
|
|
2091
2120
|
async webAgentJson(method, path, token, payload = {}) {
|
|
2121
|
+
payload.signal?.throwIfAborted();
|
|
2092
2122
|
const runtimeToken = await this.productTokenFor("webagent", token, payload.requiredScopes);
|
|
2123
|
+
payload.signal?.throwIfAborted();
|
|
2093
2124
|
const baseUrl = await this.baseUrlFor("webagent");
|
|
2094
2125
|
return this.jsonRequest(
|
|
2095
2126
|
baseUrl,
|
|
@@ -2100,9 +2131,11 @@ var Qoni = class {
|
|
|
2100
2131
|
// Binary GET against the WebAgent API — artifact content downloads. JSON
|
|
2101
2132
|
// error payloads still map to typed QoniErrors.
|
|
2102
2133
|
async webAgentBytes(path, token, payload = {}) {
|
|
2134
|
+
payload.signal?.throwIfAborted();
|
|
2103
2135
|
const runtimeToken = await this.productTokenFor("webagent", token, payload.requiredScopes);
|
|
2136
|
+
payload.signal?.throwIfAborted();
|
|
2104
2137
|
const baseUrl = await this.baseUrlFor("webagent");
|
|
2105
|
-
|
|
2138
|
+
return this.fetchWithTimeout(
|
|
2106
2139
|
urlWithBasePath(baseUrl, this.webAgentPath(runtimeToken, path), payload.query),
|
|
2107
2140
|
{
|
|
2108
2141
|
method: "GET",
|
|
@@ -2111,20 +2144,22 @@ var Qoni = class {
|
|
|
2111
2144
|
authorization: `Bearer ${runtimeToken}`,
|
|
2112
2145
|
...payload.headers || {}
|
|
2113
2146
|
}
|
|
2114
|
-
}
|
|
2147
|
+
},
|
|
2148
|
+
async (response, signal) => {
|
|
2149
|
+
if (!response.ok) {
|
|
2150
|
+
throw errorFromPayload(
|
|
2151
|
+
response.status,
|
|
2152
|
+
await readJson(response, signal),
|
|
2153
|
+
`Qoni request failed with HTTP ${response.status}`
|
|
2154
|
+
);
|
|
2155
|
+
}
|
|
2156
|
+
return {
|
|
2157
|
+
bytes: await readBody(response, signal),
|
|
2158
|
+
mime: response.headers.get("content-type") || "application/octet-stream"
|
|
2159
|
+
};
|
|
2160
|
+
},
|
|
2161
|
+
payload.signal
|
|
2115
2162
|
);
|
|
2116
|
-
if (!response.ok) {
|
|
2117
|
-
throw errorFromPayload(
|
|
2118
|
-
response.status,
|
|
2119
|
-
await readJson(response),
|
|
2120
|
-
`Qoni request failed with HTTP ${response.status}`
|
|
2121
|
-
);
|
|
2122
|
-
}
|
|
2123
|
-
const buffer = await response.arrayBuffer();
|
|
2124
|
-
return {
|
|
2125
|
-
bytes: new Uint8Array(buffer),
|
|
2126
|
-
mime: response.headers.get("content-type") || "application/octet-stream"
|
|
2127
|
-
};
|
|
2128
2163
|
}
|
|
2129
2164
|
async *webAgentSSE(path, token, payload = {}) {
|
|
2130
2165
|
const runtimeToken = await this.productTokenFor("webagent", token, payload.requiredScopes);
|
|
@@ -2164,15 +2199,16 @@ var Qoni = class {
|
|
|
2164
2199
|
});
|
|
2165
2200
|
}
|
|
2166
2201
|
async jsonRequest(baseUrl, pathname, options) {
|
|
2167
|
-
const response = await this.fetchWithTimeout(
|
|
2202
|
+
const { response, payload } = await this.fetchWithTimeout(
|
|
2168
2203
|
urlWithBasePath(baseUrl, pathname, options.query),
|
|
2169
2204
|
{
|
|
2170
2205
|
method: options.method,
|
|
2171
2206
|
headers: requestHeaders(options),
|
|
2172
2207
|
body: requestBody(options.body)
|
|
2173
|
-
}
|
|
2208
|
+
},
|
|
2209
|
+
async (response2, signal) => ({ response: response2, payload: await readJson(response2, signal) }),
|
|
2210
|
+
options.signal
|
|
2174
2211
|
);
|
|
2175
|
-
const payload = await readJson(response);
|
|
2176
2212
|
if (!response.ok) {
|
|
2177
2213
|
throw errorFromPayload(
|
|
2178
2214
|
response.status,
|
|
@@ -2222,45 +2258,55 @@ var Qoni = class {
|
|
|
2222
2258
|
}
|
|
2223
2259
|
}
|
|
2224
2260
|
async *sseConnectOnce(baseUrl, pathname, options) {
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
accept: "text/event-stream",
|
|
2231
|
-
authorization: `Bearer ${options.token}`,
|
|
2232
|
-
...options.headers || {},
|
|
2233
|
-
...options.lastEventId ? { "last-event-id": options.lastEventId } : {}
|
|
2234
|
-
}
|
|
2235
|
-
},
|
|
2236
|
-
options.signal,
|
|
2237
|
-
{ streamBody: true }
|
|
2238
|
-
);
|
|
2239
|
-
if (!response.ok) {
|
|
2240
|
-
throw errorFromPayload(
|
|
2241
|
-
response.status,
|
|
2242
|
-
await readJson(response),
|
|
2243
|
-
`Qoni stream failed with HTTP ${response.status}`
|
|
2244
|
-
);
|
|
2245
|
-
}
|
|
2246
|
-
if (!response.body) {
|
|
2247
|
-
throw errorFromPayload(502, void 0, "Qoni stream response has no body");
|
|
2248
|
-
}
|
|
2249
|
-
const reader = response.body.getReader();
|
|
2261
|
+
let cleanup;
|
|
2262
|
+
let reader;
|
|
2263
|
+
const onAbort = () => {
|
|
2264
|
+
void reader?.cancel(options.signal?.reason).catch(() => void 0);
|
|
2265
|
+
};
|
|
2250
2266
|
const decoder = new TextDecoder();
|
|
2251
2267
|
let buffer = "";
|
|
2252
2268
|
try {
|
|
2269
|
+
const response = await this.fetchWithTimeout(
|
|
2270
|
+
urlWithBasePath(baseUrl, pathname, options.query),
|
|
2271
|
+
{
|
|
2272
|
+
method: "GET",
|
|
2273
|
+
headers: {
|
|
2274
|
+
accept: "text/event-stream",
|
|
2275
|
+
authorization: `Bearer ${options.token}`,
|
|
2276
|
+
...options.headers || {},
|
|
2277
|
+
...options.lastEventId ? { "last-event-id": options.lastEventId } : {}
|
|
2278
|
+
}
|
|
2279
|
+
},
|
|
2280
|
+
async (response2, signal) => {
|
|
2281
|
+
if (!response2.ok) {
|
|
2282
|
+
throw errorFromPayload(
|
|
2283
|
+
response2.status,
|
|
2284
|
+
await readJson(response2, signal),
|
|
2285
|
+
`Qoni stream failed with HTTP ${response2.status}`
|
|
2286
|
+
);
|
|
2287
|
+
}
|
|
2288
|
+
return response2;
|
|
2289
|
+
},
|
|
2290
|
+
options.signal,
|
|
2291
|
+
{ streamCleanup: (dispose) => {
|
|
2292
|
+
cleanup = dispose;
|
|
2293
|
+
} }
|
|
2294
|
+
);
|
|
2295
|
+
if (!response.body) {
|
|
2296
|
+
throw errorFromPayload(502, void 0, "Qoni stream response has no body");
|
|
2297
|
+
}
|
|
2298
|
+
reader = response.body.getReader();
|
|
2299
|
+
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
2253
2300
|
while (true) {
|
|
2254
|
-
|
|
2255
|
-
await reader.cancel().catch(() => void 0);
|
|
2256
|
-
throw options.signal.reason || new DOMException("Aborted", "AbortError");
|
|
2257
|
-
}
|
|
2301
|
+
options.signal?.throwIfAborted();
|
|
2258
2302
|
const { done, value } = await reader.read();
|
|
2303
|
+
options.signal?.throwIfAborted();
|
|
2259
2304
|
if (done) break;
|
|
2260
2305
|
buffer += decoder.decode(value, { stream: true });
|
|
2261
2306
|
const chunks = buffer.split(/\r?\n\r?\n/);
|
|
2262
2307
|
buffer = chunks.pop() || "";
|
|
2263
2308
|
for (const chunk of chunks) {
|
|
2309
|
+
options.signal?.throwIfAborted();
|
|
2264
2310
|
const event = parseSSEEvent(chunk);
|
|
2265
2311
|
if (event) yield event;
|
|
2266
2312
|
}
|
|
@@ -2268,14 +2314,16 @@ var Qoni = class {
|
|
|
2268
2314
|
const finalEvent = parseSSEEvent(buffer);
|
|
2269
2315
|
if (finalEvent) yield finalEvent;
|
|
2270
2316
|
} finally {
|
|
2271
|
-
|
|
2317
|
+
cleanup?.();
|
|
2318
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
2319
|
+
void reader?.cancel().catch(() => void 0);
|
|
2272
2320
|
try {
|
|
2273
|
-
reader
|
|
2321
|
+
reader?.releaseLock();
|
|
2274
2322
|
} catch {
|
|
2275
2323
|
}
|
|
2276
2324
|
}
|
|
2277
2325
|
}
|
|
2278
|
-
async fetchWithTimeout(input, init, signal, opts) {
|
|
2326
|
+
async fetchWithTimeout(input, init, consume, signal, opts) {
|
|
2279
2327
|
if (signal?.aborted) throw signal.reason || new DOMException("Aborted", "AbortError");
|
|
2280
2328
|
const controller = new AbortController();
|
|
2281
2329
|
const resolved = resolveLocalGenauthRequest(input);
|
|
@@ -2290,17 +2338,39 @@ var Qoni = class {
|
|
|
2290
2338
|
didTimeout = true;
|
|
2291
2339
|
controller.abort();
|
|
2292
2340
|
}, this.timeoutMs);
|
|
2341
|
+
let response;
|
|
2293
2342
|
try {
|
|
2294
|
-
|
|
2343
|
+
response = await new Promise((resolve, reject) => {
|
|
2344
|
+
const onAbort = () => reject(controller.signal.reason);
|
|
2345
|
+
controller.signal.addEventListener("abort", onAbort, { once: true });
|
|
2346
|
+
Promise.resolve().then(() => {
|
|
2347
|
+
controller.signal.throwIfAborted();
|
|
2348
|
+
return this.fetchImpl(resolved.url, { ...requestInit, signal: controller.signal });
|
|
2349
|
+
}).then((response2) => {
|
|
2350
|
+
if (controller.signal.aborted) {
|
|
2351
|
+
void response2.body?.cancel(controller.signal.reason).catch(() => void 0);
|
|
2352
|
+
reject(controller.signal.reason);
|
|
2353
|
+
} else {
|
|
2354
|
+
resolve(response2);
|
|
2355
|
+
}
|
|
2356
|
+
}, reject).finally(() => controller.signal.removeEventListener("abort", onAbort));
|
|
2357
|
+
});
|
|
2358
|
+
const result = await consume(response, controller.signal);
|
|
2359
|
+
controller.signal.throwIfAborted();
|
|
2360
|
+
return result;
|
|
2295
2361
|
} catch (error) {
|
|
2296
|
-
|
|
2362
|
+
void response?.body?.cancel().catch(() => void 0);
|
|
2363
|
+
if (signal?.aborted) throw signal.reason;
|
|
2364
|
+
if (didTimeout || isConnectTimeoutError(error)) {
|
|
2297
2365
|
throw timeoutError(error);
|
|
2298
2366
|
}
|
|
2299
2367
|
if (isFetchNetworkError(error)) throw networkError(error);
|
|
2300
2368
|
throw error;
|
|
2301
2369
|
} finally {
|
|
2302
2370
|
clearTimeout(timeout);
|
|
2303
|
-
|
|
2371
|
+
const cleanup = () => signal?.removeEventListener("abort", abortFromSignal);
|
|
2372
|
+
if (opts?.streamCleanup) opts.streamCleanup(cleanup);
|
|
2373
|
+
else cleanup();
|
|
2304
2374
|
}
|
|
2305
2375
|
}
|
|
2306
2376
|
async baseUrlFor(service) {
|
|
@@ -2356,7 +2426,7 @@ var Qoni = class {
|
|
|
2356
2426
|
pathname,
|
|
2357
2427
|
{}
|
|
2358
2428
|
);
|
|
2359
|
-
const response = await this.fetchWithTimeout(
|
|
2429
|
+
const { response, payload } = await this.fetchWithTimeout(
|
|
2360
2430
|
urlWithBasePath(this.host, pathname),
|
|
2361
2431
|
{
|
|
2362
2432
|
method: "GET",
|
|
@@ -2364,9 +2434,9 @@ var Qoni = class {
|
|
|
2364
2434
|
accept: "application/json",
|
|
2365
2435
|
...headers
|
|
2366
2436
|
}
|
|
2367
|
-
}
|
|
2437
|
+
},
|
|
2438
|
+
async (response2, signal) => ({ response: response2, payload: await readJson(response2, signal) })
|
|
2368
2439
|
);
|
|
2369
|
-
const payload = await readJson(response);
|
|
2370
2440
|
if (!response.ok) {
|
|
2371
2441
|
throw errorFromPayload(
|
|
2372
2442
|
response.status,
|
|
@@ -2460,18 +2530,52 @@ function requestBody(body) {
|
|
|
2460
2530
|
function isBodyInit(value) {
|
|
2461
2531
|
return typeof value === "string" || value instanceof ArrayBuffer || value instanceof Blob || value instanceof FormData || value instanceof URLSearchParams;
|
|
2462
2532
|
}
|
|
2463
|
-
async function
|
|
2533
|
+
async function readBody(response, signal) {
|
|
2534
|
+
if (!response.body) {
|
|
2535
|
+
signal.throwIfAborted();
|
|
2536
|
+
return new Uint8Array();
|
|
2537
|
+
}
|
|
2538
|
+
const reader = response.body.getReader();
|
|
2539
|
+
const onAbort = () => {
|
|
2540
|
+
void reader.cancel(signal.reason).catch(() => void 0);
|
|
2541
|
+
};
|
|
2542
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
2543
|
+
try {
|
|
2544
|
+
const chunks = [];
|
|
2545
|
+
let size = 0;
|
|
2546
|
+
for (; ; ) {
|
|
2547
|
+
signal.throwIfAborted();
|
|
2548
|
+
const { done, value } = await reader.read();
|
|
2549
|
+
signal.throwIfAborted();
|
|
2550
|
+
if (done) break;
|
|
2551
|
+
chunks.push(value);
|
|
2552
|
+
size += value.byteLength;
|
|
2553
|
+
}
|
|
2554
|
+
const bytes = new Uint8Array(size);
|
|
2555
|
+
let offset = 0;
|
|
2556
|
+
for (const chunk of chunks) {
|
|
2557
|
+
bytes.set(chunk, offset);
|
|
2558
|
+
offset += chunk.byteLength;
|
|
2559
|
+
}
|
|
2560
|
+
return bytes;
|
|
2561
|
+
} finally {
|
|
2562
|
+
signal.removeEventListener("abort", onAbort);
|
|
2563
|
+
void reader.cancel().catch(() => void 0);
|
|
2564
|
+
reader.releaseLock();
|
|
2565
|
+
}
|
|
2566
|
+
}
|
|
2567
|
+
async function readJson(response, signal) {
|
|
2464
2568
|
const contentType = response.headers.get("content-type")?.toLowerCase() || "";
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
if (
|
|
2468
|
-
|
|
2569
|
+
const body = new TextDecoder().decode(await readBody(response, signal));
|
|
2570
|
+
try {
|
|
2571
|
+
if (contentType.includes("ndjson") || contentType.includes("jsonl")) {
|
|
2572
|
+
if (!body.trim()) return [];
|
|
2469
2573
|
return body.split(/\r?\n/).filter((line) => line.trim().length > 0).map((line) => JSON.parse(line));
|
|
2470
|
-
} catch {
|
|
2471
|
-
return void 0;
|
|
2472
2574
|
}
|
|
2575
|
+
return JSON.parse(body);
|
|
2576
|
+
} catch {
|
|
2577
|
+
return void 0;
|
|
2473
2578
|
}
|
|
2474
|
-
return response.json().catch(() => void 0);
|
|
2475
2579
|
}
|
|
2476
2580
|
function normalizeResponse(payload, _service) {
|
|
2477
2581
|
if (isJsonObject(payload) && ("data" in payload || "meta" in payload)) {
|
|
@@ -2714,6 +2818,7 @@ function isRetryableStreamError(err) {
|
|
|
2714
2818
|
if (err && typeof err === "object") {
|
|
2715
2819
|
const e = err;
|
|
2716
2820
|
if (e.name === "AbortError") return false;
|
|
2821
|
+
if (e.status === 408) return true;
|
|
2717
2822
|
if (typeof e.retryable === "boolean") return e.retryable;
|
|
2718
2823
|
if (typeof e.status === "number") {
|
|
2719
2824
|
return e.status >= 500 || e.status === 408 || e.status === 429;
|
|
@@ -2820,9 +2925,6 @@ function normalizeDelegateAgentTokenResponse(response) {
|
|
|
2820
2925
|
}
|
|
2821
2926
|
};
|
|
2822
2927
|
}
|
|
2823
|
-
function isAbortError(error) {
|
|
2824
|
-
return error instanceof DOMException && error.name === "AbortError";
|
|
2825
|
-
}
|
|
2826
2928
|
function isConnectTimeoutError(error) {
|
|
2827
2929
|
const cause = errorCause(error);
|
|
2828
2930
|
return errorCode(cause) === "UND_ERR_CONNECT_TIMEOUT" || errorName(cause) === "ConnectTimeoutError";
|