@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 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;
@@ -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
  }
@@ -24,6 +24,7 @@ export interface AgentEvent {
24
24
  export interface ContextEventMap {
25
25
  agentStarted: [AgentEvent & {
26
26
  input: Message;
27
+ taskTitle?: string;
27
28
  }];
28
29
  agentSucceed: [AgentEvent & {
29
30
  output: Message;
@@ -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 regular response content.
9
- - At the end of the response, 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>.
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
- writeEvent("agentStarted", event);
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
  *
@@ -24,6 +24,7 @@ export interface AgentEvent {
24
24
  export interface ContextEventMap {
25
25
  agentStarted: [AgentEvent & {
26
26
  input: Message;
27
+ taskTitle?: string;
27
28
  }];
28
29
  agentSucceed: [AgentEvent & {
29
30
  output: Message;
@@ -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;
@@ -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
  }
@@ -24,6 +24,7 @@ export interface AgentEvent {
24
24
  export interface ContextEventMap {
25
25
  agentStarted: [AgentEvent & {
26
26
  input: Message;
27
+ taskTitle?: string;
27
28
  }];
28
29
  agentSucceed: [AgentEvent & {
29
30
  output: Message;
@@ -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 regular response content.
6
- - At the end of the response, 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>.
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
- writeEvent("agentStarted", event);
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 }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aigne/core",
3
- "version": "1.46.1",
3
+ "version": "1.47.0",
4
4
  "description": "The functional core of agentic AI",
5
5
  "publishConfig": {
6
6
  "access": "public"