@aigne/core 1.46.1 → 1.47.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 +7 -0
- package/lib/cjs/agents/agent.d.ts +2 -0
- package/lib/cjs/agents/agent.js +9 -0
- package/lib/cjs/agents/ai-agent.d.ts +2 -0
- package/lib/cjs/agents/ai-agent.js +10 -1
- package/lib/cjs/aigne/context.d.ts +1 -0
- package/lib/cjs/prompt/prompts/structured-stream-instructions.js +3 -3
- package/lib/cjs/utils/event-stream.js +2 -1
- package/lib/cjs/utils/stream-utils.d.ts +1 -1
- package/lib/cjs/utils/stream-utils.js +1 -1
- package/lib/dts/agents/agent.d.ts +2 -0
- package/lib/dts/agents/ai-agent.d.ts +2 -0
- package/lib/dts/aigne/context.d.ts +1 -0
- package/lib/dts/utils/stream-utils.d.ts +1 -1
- package/lib/esm/agents/agent.d.ts +2 -0
- package/lib/esm/agents/agent.js +6 -0
- package/lib/esm/agents/ai-agent.d.ts +2 -0
- package/lib/esm/agents/ai-agent.js +10 -1
- package/lib/esm/aigne/context.d.ts +1 -0
- package/lib/esm/prompt/prompts/structured-stream-instructions.js +3 -3
- package/lib/esm/utils/event-stream.js +2 -1
- package/lib/esm/utils/stream-utils.d.ts +1 -1
- package/lib/esm/utils/stream-utils.js +2 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -12,6 +12,13 @@
|
|
|
12
12
|
* dependencies
|
|
13
13
|
* @aigne/observability bumped to 0.1.0
|
|
14
14
|
|
|
15
|
+
## [1.47.0](https://github.com/AIGNE-io/aigne-framework/compare/core-v1.46.1...core-v1.47.0) (2025-08-11)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Features
|
|
19
|
+
|
|
20
|
+
* enhance AI agent streaming with thinking mode support ([#343](https://github.com/AIGNE-io/aigne-framework/issues/343)) ([bea2a39](https://github.com/AIGNE-io/aigne-framework/commit/bea2a39a2610c2fe58e46ad612b5103726159ab9))
|
|
21
|
+
|
|
15
22
|
## [1.46.1](https://github.com/AIGNE-io/aigne-framework/compare/core-v1.46.0...core-v1.46.1) (2025-08-08)
|
|
16
23
|
|
|
17
24
|
|
|
@@ -258,6 +258,7 @@ export declare abstract class Agent<I extends Message = any, O extends Message =
|
|
|
258
258
|
*/
|
|
259
259
|
readonly description?: string;
|
|
260
260
|
taskTitle?: string;
|
|
261
|
+
renderTaskTitle(input: I): string | undefined;
|
|
261
262
|
private readonly _inputSchema?;
|
|
262
263
|
defaultInput?: Partial<{
|
|
263
264
|
[key in keyof I]: {
|
|
@@ -724,6 +725,7 @@ export interface AgentResponseProgress {
|
|
|
724
725
|
progress: ({
|
|
725
726
|
event: "agentStarted";
|
|
726
727
|
input: Message;
|
|
728
|
+
taskTitle?: string;
|
|
727
729
|
} | {
|
|
728
730
|
event: "agentSucceed";
|
|
729
731
|
output: Message;
|
package/lib/cjs/agents/agent.js
CHANGED
|
@@ -35,6 +35,9 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
return result;
|
|
36
36
|
};
|
|
37
37
|
})();
|
|
38
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
39
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
40
|
+
};
|
|
38
41
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
42
|
exports.FunctionAgent = exports.Agent = exports.agentOptionsSchema = exports.DEFAULT_INPUT_ACTION_GET = void 0;
|
|
40
43
|
exports.isEmptyChunk = isEmptyChunk;
|
|
@@ -44,6 +47,7 @@ exports.textDelta = textDelta;
|
|
|
44
47
|
exports.jsonDelta = jsonDelta;
|
|
45
48
|
exports.agentProcessResultToObject = agentProcessResultToObject;
|
|
46
49
|
const index_js_1 = require("@aigne/platform-helpers/nodejs/index.js");
|
|
50
|
+
const nunjucks_1 = __importDefault(require("nunjucks"));
|
|
47
51
|
const zod_1 = require("zod");
|
|
48
52
|
const logger_js_1 = require("../utils/logger.js");
|
|
49
53
|
const stream_utils_js_1 = require("../utils/stream-utils.js");
|
|
@@ -195,6 +199,11 @@ class Agent {
|
|
|
195
199
|
*/
|
|
196
200
|
description;
|
|
197
201
|
taskTitle;
|
|
202
|
+
renderTaskTitle(input) {
|
|
203
|
+
if (!this.taskTitle)
|
|
204
|
+
return;
|
|
205
|
+
return nunjucks_1.default.renderString(this.taskTitle, { ...input });
|
|
206
|
+
}
|
|
198
207
|
_inputSchema;
|
|
199
208
|
defaultInput;
|
|
200
209
|
_outputSchema;
|
|
@@ -64,6 +64,7 @@ export interface AIAgentOptions<I extends Message = Message, O extends Message =
|
|
|
64
64
|
* @default false
|
|
65
65
|
*/
|
|
66
66
|
structuredStreamMode?: boolean;
|
|
67
|
+
ignoreTextOfStructuredStreamMode?: (output: O) => boolean;
|
|
67
68
|
/**
|
|
68
69
|
* Custom structured stream instructions configuration
|
|
69
70
|
*
|
|
@@ -273,6 +274,7 @@ export declare class AIAgent<I extends Message = any, O extends Message = any> e
|
|
|
273
274
|
* @default false
|
|
274
275
|
*/
|
|
275
276
|
structuredStreamMode?: boolean;
|
|
277
|
+
ignoreTextOfStructuredStreamMode?: (output: O) => boolean;
|
|
276
278
|
/**
|
|
277
279
|
* Custom structured stream instructions configuration
|
|
278
280
|
*
|
|
@@ -118,6 +118,7 @@ class AIAgent extends agent_js_1.Agent {
|
|
|
118
118
|
if (typeof options.catchToolsError === "boolean")
|
|
119
119
|
this.catchToolsError = options.catchToolsError;
|
|
120
120
|
this.structuredStreamMode = options.structuredStreamMode;
|
|
121
|
+
this.ignoreTextOfStructuredStreamMode = options.ignoreTextOfStructuredStreamMode;
|
|
121
122
|
this.customStructuredStreamInstructions = options.customStructuredStreamInstructions && {
|
|
122
123
|
...options.customStructuredStreamInstructions,
|
|
123
124
|
instructions: typeof options.customStructuredStreamInstructions.instructions === "string"
|
|
@@ -207,6 +208,7 @@ class AIAgent extends agent_js_1.Agent {
|
|
|
207
208
|
* @default false
|
|
208
209
|
*/
|
|
209
210
|
structuredStreamMode;
|
|
211
|
+
ignoreTextOfStructuredStreamMode;
|
|
210
212
|
/**
|
|
211
213
|
* Custom structured stream instructions configuration
|
|
212
214
|
*
|
|
@@ -248,13 +250,20 @@ class AIAgent extends agent_js_1.Agent {
|
|
|
248
250
|
const { metadataStart, metadataEnd, parse } = this.customStructuredStreamInstructions || structured_stream_instructions_js_1.STRUCTURED_STREAM_INSTRUCTIONS;
|
|
249
251
|
stream = stream.pipeThrough(new structured_stream_extractor_js_1.ExtractMetadataTransform({ start: metadataStart, end: metadataEnd, parse }));
|
|
250
252
|
}
|
|
253
|
+
let isTextIgnored = false;
|
|
251
254
|
for await (const value of stream) {
|
|
252
255
|
if ((0, agent_js_1.isAgentResponseDelta)(value)) {
|
|
253
|
-
if (value.delta.text?.text) {
|
|
256
|
+
if (!isTextIgnored && value.delta.text?.text) {
|
|
254
257
|
yield { delta: { text: { [outputKey]: value.delta.text.text } } };
|
|
255
258
|
}
|
|
256
259
|
if (value.delta.json) {
|
|
257
260
|
Object.assign(modelOutput, value.delta.json);
|
|
261
|
+
if (this.structuredStreamMode) {
|
|
262
|
+
yield { delta: { json: value.delta.json.json } };
|
|
263
|
+
if (!isTextIgnored && modelOutput.json && this.ignoreTextOfStructuredStreamMode) {
|
|
264
|
+
isTextIgnored = this.ignoreTextOfStructuredStreamMode(modelOutput.json);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
258
267
|
}
|
|
259
268
|
}
|
|
260
269
|
}
|
|
@@ -5,8 +5,8 @@ const yaml_1 = require("yaml");
|
|
|
5
5
|
exports.STRUCTURED_STREAM_INSTRUCTIONS = {
|
|
6
6
|
instructions: `\
|
|
7
7
|
<output-rules>
|
|
8
|
-
- First, output the
|
|
9
|
-
- At the end of the response
|
|
8
|
+
- First, use <metadata></metadata> tags to output metadata. The metadata should be output in YAML format as structured data, and must conform to the format defined in <metadata-schema></metadata-schema>.
|
|
9
|
+
- At the end of the response output the regular response content.
|
|
10
10
|
</output-rules>
|
|
11
11
|
|
|
12
12
|
<metadata-schema>
|
|
@@ -14,11 +14,11 @@ exports.STRUCTURED_STREAM_INSTRUCTIONS = {
|
|
|
14
14
|
</metadata-schema>
|
|
15
15
|
|
|
16
16
|
<output-example>
|
|
17
|
-
Here is the regular response content
|
|
18
17
|
<metadata>
|
|
19
18
|
foo: bar
|
|
20
19
|
baz: 123
|
|
21
20
|
</metadata>
|
|
21
|
+
Here is the regular response content
|
|
22
22
|
</output-example>
|
|
23
23
|
`,
|
|
24
24
|
metadataStart: "<metadata>",
|
|
@@ -132,7 +132,8 @@ class AgentResponseProgressStream extends ReadableStream {
|
|
|
132
132
|
controller.close();
|
|
133
133
|
};
|
|
134
134
|
const onAgentStarted = (event) => {
|
|
135
|
-
|
|
135
|
+
const taskTitle = event.agent.renderTaskTitle(event.input);
|
|
136
|
+
writeEvent("agentStarted", { ...event, taskTitle });
|
|
136
137
|
};
|
|
137
138
|
const onAgentSucceed = (event) => {
|
|
138
139
|
writeEvent("agentSucceed", event);
|
|
@@ -23,4 +23,4 @@ export declare function stringToAgentResponseStream(str: string, key?: "text" |
|
|
|
23
23
|
export declare function toReadableStream(stream: NodeJS.ReadStream): ReadableStream<Uint8Array<ArrayBufferLike>>;
|
|
24
24
|
export declare function readAllString(stream: NodeJS.ReadStream | ReadableStream): Promise<string>;
|
|
25
25
|
export declare function mergeReadableStreams<T1, T2>(s1: ReadableStream<T1>, s2: ReadableStream<T2>): ReadableStream<T1 | T2>;
|
|
26
|
-
export declare function mergeReadableStreams(...streams: ReadableStream<any>[]): ReadableStream<any>;
|
|
26
|
+
export declare function mergeReadableStreams(...streams: (ReadableStream<any> | undefined)[]): ReadableStream<any>;
|
|
@@ -207,7 +207,7 @@ function mergeReadableStreams(...streams) {
|
|
|
207
207
|
return new ReadableStream({
|
|
208
208
|
async pull(controller) {
|
|
209
209
|
try {
|
|
210
|
-
readers ??= streams.map((s) => ({ reader: s.getReader(), data: [] }));
|
|
210
|
+
readers ??= streams.filter(type_utils_js_1.isNonNullable).map((s) => ({ reader: s.getReader(), data: [] }));
|
|
211
211
|
while (readers.length) {
|
|
212
212
|
const chunk = await Promise.race(readers.map((i) => {
|
|
213
213
|
i.reading ??= i.reader.read().then((result) => ({ result, item: i }));
|
|
@@ -258,6 +258,7 @@ export declare abstract class Agent<I extends Message = any, O extends Message =
|
|
|
258
258
|
*/
|
|
259
259
|
readonly description?: string;
|
|
260
260
|
taskTitle?: string;
|
|
261
|
+
renderTaskTitle(input: I): string | undefined;
|
|
261
262
|
private readonly _inputSchema?;
|
|
262
263
|
defaultInput?: Partial<{
|
|
263
264
|
[key in keyof I]: {
|
|
@@ -724,6 +725,7 @@ export interface AgentResponseProgress {
|
|
|
724
725
|
progress: ({
|
|
725
726
|
event: "agentStarted";
|
|
726
727
|
input: Message;
|
|
728
|
+
taskTitle?: string;
|
|
727
729
|
} | {
|
|
728
730
|
event: "agentSucceed";
|
|
729
731
|
output: Message;
|
|
@@ -64,6 +64,7 @@ export interface AIAgentOptions<I extends Message = Message, O extends Message =
|
|
|
64
64
|
* @default false
|
|
65
65
|
*/
|
|
66
66
|
structuredStreamMode?: boolean;
|
|
67
|
+
ignoreTextOfStructuredStreamMode?: (output: O) => boolean;
|
|
67
68
|
/**
|
|
68
69
|
* Custom structured stream instructions configuration
|
|
69
70
|
*
|
|
@@ -273,6 +274,7 @@ export declare class AIAgent<I extends Message = any, O extends Message = any> e
|
|
|
273
274
|
* @default false
|
|
274
275
|
*/
|
|
275
276
|
structuredStreamMode?: boolean;
|
|
277
|
+
ignoreTextOfStructuredStreamMode?: (output: O) => boolean;
|
|
276
278
|
/**
|
|
277
279
|
* Custom structured stream instructions configuration
|
|
278
280
|
*
|
|
@@ -23,4 +23,4 @@ export declare function stringToAgentResponseStream(str: string, key?: "text" |
|
|
|
23
23
|
export declare function toReadableStream(stream: NodeJS.ReadStream): ReadableStream<Uint8Array<ArrayBufferLike>>;
|
|
24
24
|
export declare function readAllString(stream: NodeJS.ReadStream | ReadableStream): Promise<string>;
|
|
25
25
|
export declare function mergeReadableStreams<T1, T2>(s1: ReadableStream<T1>, s2: ReadableStream<T2>): ReadableStream<T1 | T2>;
|
|
26
|
-
export declare function mergeReadableStreams(...streams: ReadableStream<any>[]): ReadableStream<any>;
|
|
26
|
+
export declare function mergeReadableStreams(...streams: (ReadableStream<any> | undefined)[]): ReadableStream<any>;
|
|
@@ -258,6 +258,7 @@ export declare abstract class Agent<I extends Message = any, O extends Message =
|
|
|
258
258
|
*/
|
|
259
259
|
readonly description?: string;
|
|
260
260
|
taskTitle?: string;
|
|
261
|
+
renderTaskTitle(input: I): string | undefined;
|
|
261
262
|
private readonly _inputSchema?;
|
|
262
263
|
defaultInput?: Partial<{
|
|
263
264
|
[key in keyof I]: {
|
|
@@ -724,6 +725,7 @@ export interface AgentResponseProgress {
|
|
|
724
725
|
progress: ({
|
|
725
726
|
event: "agentStarted";
|
|
726
727
|
input: Message;
|
|
728
|
+
taskTitle?: string;
|
|
727
729
|
} | {
|
|
728
730
|
event: "agentSucceed";
|
|
729
731
|
output: Message;
|
package/lib/esm/agents/agent.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { nodejs } from "@aigne/platform-helpers/nodejs/index.js";
|
|
2
|
+
import nunjucks from "nunjucks";
|
|
2
3
|
import { ZodObject, z } from "zod";
|
|
3
4
|
import { logger } from "../utils/logger.js";
|
|
4
5
|
import { agentResponseStreamToObject, asyncGeneratorToReadableStream, isAsyncGenerator, objectToAgentResponseStream, onAgentResponseStreamEnd, } from "../utils/stream-utils.js";
|
|
@@ -150,6 +151,11 @@ export class Agent {
|
|
|
150
151
|
*/
|
|
151
152
|
description;
|
|
152
153
|
taskTitle;
|
|
154
|
+
renderTaskTitle(input) {
|
|
155
|
+
if (!this.taskTitle)
|
|
156
|
+
return;
|
|
157
|
+
return nunjucks.renderString(this.taskTitle, { ...input });
|
|
158
|
+
}
|
|
153
159
|
_inputSchema;
|
|
154
160
|
defaultInput;
|
|
155
161
|
_outputSchema;
|
|
@@ -64,6 +64,7 @@ export interface AIAgentOptions<I extends Message = Message, O extends Message =
|
|
|
64
64
|
* @default false
|
|
65
65
|
*/
|
|
66
66
|
structuredStreamMode?: boolean;
|
|
67
|
+
ignoreTextOfStructuredStreamMode?: (output: O) => boolean;
|
|
67
68
|
/**
|
|
68
69
|
* Custom structured stream instructions configuration
|
|
69
70
|
*
|
|
@@ -273,6 +274,7 @@ export declare class AIAgent<I extends Message = any, O extends Message = any> e
|
|
|
273
274
|
* @default false
|
|
274
275
|
*/
|
|
275
276
|
structuredStreamMode?: boolean;
|
|
277
|
+
ignoreTextOfStructuredStreamMode?: (output: O) => boolean;
|
|
276
278
|
/**
|
|
277
279
|
* Custom structured stream instructions configuration
|
|
278
280
|
*
|
|
@@ -115,6 +115,7 @@ export class AIAgent extends Agent {
|
|
|
115
115
|
if (typeof options.catchToolsError === "boolean")
|
|
116
116
|
this.catchToolsError = options.catchToolsError;
|
|
117
117
|
this.structuredStreamMode = options.structuredStreamMode;
|
|
118
|
+
this.ignoreTextOfStructuredStreamMode = options.ignoreTextOfStructuredStreamMode;
|
|
118
119
|
this.customStructuredStreamInstructions = options.customStructuredStreamInstructions && {
|
|
119
120
|
...options.customStructuredStreamInstructions,
|
|
120
121
|
instructions: typeof options.customStructuredStreamInstructions.instructions === "string"
|
|
@@ -204,6 +205,7 @@ export class AIAgent extends Agent {
|
|
|
204
205
|
* @default false
|
|
205
206
|
*/
|
|
206
207
|
structuredStreamMode;
|
|
208
|
+
ignoreTextOfStructuredStreamMode;
|
|
207
209
|
/**
|
|
208
210
|
* Custom structured stream instructions configuration
|
|
209
211
|
*
|
|
@@ -245,13 +247,20 @@ export class AIAgent extends Agent {
|
|
|
245
247
|
const { metadataStart, metadataEnd, parse } = this.customStructuredStreamInstructions || STRUCTURED_STREAM_INSTRUCTIONS;
|
|
246
248
|
stream = stream.pipeThrough(new ExtractMetadataTransform({ start: metadataStart, end: metadataEnd, parse }));
|
|
247
249
|
}
|
|
250
|
+
let isTextIgnored = false;
|
|
248
251
|
for await (const value of stream) {
|
|
249
252
|
if (isAgentResponseDelta(value)) {
|
|
250
|
-
if (value.delta.text?.text) {
|
|
253
|
+
if (!isTextIgnored && value.delta.text?.text) {
|
|
251
254
|
yield { delta: { text: { [outputKey]: value.delta.text.text } } };
|
|
252
255
|
}
|
|
253
256
|
if (value.delta.json) {
|
|
254
257
|
Object.assign(modelOutput, value.delta.json);
|
|
258
|
+
if (this.structuredStreamMode) {
|
|
259
|
+
yield { delta: { json: value.delta.json.json } };
|
|
260
|
+
if (!isTextIgnored && modelOutput.json && this.ignoreTextOfStructuredStreamMode) {
|
|
261
|
+
isTextIgnored = this.ignoreTextOfStructuredStreamMode(modelOutput.json);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
255
264
|
}
|
|
256
265
|
}
|
|
257
266
|
}
|
|
@@ -2,8 +2,8 @@ import { parse } from "yaml";
|
|
|
2
2
|
export const STRUCTURED_STREAM_INSTRUCTIONS = {
|
|
3
3
|
instructions: `\
|
|
4
4
|
<output-rules>
|
|
5
|
-
- First, output the
|
|
6
|
-
- At the end of the response
|
|
5
|
+
- First, use <metadata></metadata> tags to output metadata. The metadata should be output in YAML format as structured data, and must conform to the format defined in <metadata-schema></metadata-schema>.
|
|
6
|
+
- At the end of the response output the regular response content.
|
|
7
7
|
</output-rules>
|
|
8
8
|
|
|
9
9
|
<metadata-schema>
|
|
@@ -11,11 +11,11 @@ export const STRUCTURED_STREAM_INSTRUCTIONS = {
|
|
|
11
11
|
</metadata-schema>
|
|
12
12
|
|
|
13
13
|
<output-example>
|
|
14
|
-
Here is the regular response content
|
|
15
14
|
<metadata>
|
|
16
15
|
foo: bar
|
|
17
16
|
baz: 123
|
|
18
17
|
</metadata>
|
|
18
|
+
Here is the regular response content
|
|
19
19
|
</output-example>
|
|
20
20
|
`,
|
|
21
21
|
metadataStart: "<metadata>",
|
|
@@ -126,7 +126,8 @@ export class AgentResponseProgressStream extends ReadableStream {
|
|
|
126
126
|
controller.close();
|
|
127
127
|
};
|
|
128
128
|
const onAgentStarted = (event) => {
|
|
129
|
-
|
|
129
|
+
const taskTitle = event.agent.renderTaskTitle(event.input);
|
|
130
|
+
writeEvent("agentStarted", { ...event, taskTitle });
|
|
130
131
|
};
|
|
131
132
|
const onAgentSucceed = (event) => {
|
|
132
133
|
writeEvent("agentSucceed", event);
|
|
@@ -23,4 +23,4 @@ export declare function stringToAgentResponseStream(str: string, key?: "text" |
|
|
|
23
23
|
export declare function toReadableStream(stream: NodeJS.ReadStream): ReadableStream<Uint8Array<ArrayBufferLike>>;
|
|
24
24
|
export declare function readAllString(stream: NodeJS.ReadStream | ReadableStream): Promise<string>;
|
|
25
25
|
export declare function mergeReadableStreams<T1, T2>(s1: ReadableStream<T1>, s2: ReadableStream<T2>): ReadableStream<T1 | T2>;
|
|
26
|
-
export declare function mergeReadableStreams(...streams: ReadableStream<any>[]): ReadableStream<any>;
|
|
26
|
+
export declare function mergeReadableStreams(...streams: (ReadableStream<any> | undefined)[]): ReadableStream<any>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import equal from "fast-deep-equal";
|
|
2
2
|
import { isAgentResponseDelta, isEmptyChunk, } from "../agents/agent.js";
|
|
3
|
-
import { isRecord, omitBy } from "./type-utils.js";
|
|
3
|
+
import { isNonNullable, isRecord, omitBy } from "./type-utils.js";
|
|
4
4
|
import "./stream-polyfill.js";
|
|
5
5
|
export function objectToAgentResponseStream(json) {
|
|
6
6
|
if (!isRecord(json)) {
|
|
@@ -189,7 +189,7 @@ export function mergeReadableStreams(...streams) {
|
|
|
189
189
|
return new ReadableStream({
|
|
190
190
|
async pull(controller) {
|
|
191
191
|
try {
|
|
192
|
-
readers ??= streams.map((s) => ({ reader: s.getReader(), data: [] }));
|
|
192
|
+
readers ??= streams.filter(isNonNullable).map((s) => ({ reader: s.getReader(), data: [] }));
|
|
193
193
|
while (readers.length) {
|
|
194
194
|
const chunk = await Promise.race(readers.map((i) => {
|
|
195
195
|
i.reading ??= i.reader.read().then((result) => ({ result, item: i }));
|