@aigne/core 1.9.0 → 1.10.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.
Files changed (47) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/lib/cjs/agents/agent.d.ts +29 -3
  3. package/lib/cjs/agents/agent.js +47 -22
  4. package/lib/cjs/agents/ai-agent.d.ts +3 -2
  5. package/lib/cjs/agents/ai-agent.js +22 -9
  6. package/lib/cjs/agents/user-agent.d.ts +3 -3
  7. package/lib/cjs/agents/user-agent.js +14 -8
  8. package/lib/cjs/execution-engine/context.d.ts +18 -7
  9. package/lib/cjs/execution-engine/context.js +76 -28
  10. package/lib/cjs/models/chat-model.d.ts +4 -0
  11. package/lib/cjs/models/chat-model.js +6 -0
  12. package/lib/cjs/models/open-router-chat-model.d.ts +1 -0
  13. package/lib/cjs/models/open-router-chat-model.js +1 -0
  14. package/lib/cjs/models/openai-chat-model.d.ts +12 -3
  15. package/lib/cjs/models/openai-chat-model.js +112 -46
  16. package/lib/cjs/utils/stream-utils.d.ts +15 -0
  17. package/lib/cjs/utils/stream-utils.js +159 -0
  18. package/lib/cjs/utils/type-utils.d.ts +1 -0
  19. package/lib/cjs/utils/type-utils.js +7 -0
  20. package/lib/dts/agents/agent.d.ts +29 -3
  21. package/lib/dts/agents/ai-agent.d.ts +3 -2
  22. package/lib/dts/agents/user-agent.d.ts +3 -3
  23. package/lib/dts/execution-engine/context.d.ts +18 -7
  24. package/lib/dts/models/chat-model.d.ts +4 -0
  25. package/lib/dts/models/open-router-chat-model.d.ts +1 -0
  26. package/lib/dts/models/openai-chat-model.d.ts +12 -3
  27. package/lib/dts/utils/stream-utils.d.ts +15 -0
  28. package/lib/dts/utils/type-utils.d.ts +1 -0
  29. package/lib/esm/agents/agent.d.ts +29 -3
  30. package/lib/esm/agents/agent.js +47 -22
  31. package/lib/esm/agents/ai-agent.d.ts +3 -2
  32. package/lib/esm/agents/ai-agent.js +23 -10
  33. package/lib/esm/agents/user-agent.d.ts +3 -3
  34. package/lib/esm/agents/user-agent.js +15 -9
  35. package/lib/esm/execution-engine/context.d.ts +18 -7
  36. package/lib/esm/execution-engine/context.js +78 -30
  37. package/lib/esm/models/chat-model.d.ts +4 -0
  38. package/lib/esm/models/chat-model.js +6 -0
  39. package/lib/esm/models/open-router-chat-model.d.ts +1 -0
  40. package/lib/esm/models/open-router-chat-model.js +1 -0
  41. package/lib/esm/models/openai-chat-model.d.ts +12 -3
  42. package/lib/esm/models/openai-chat-model.js +112 -45
  43. package/lib/esm/utils/stream-utils.d.ts +15 -0
  44. package/lib/esm/utils/stream-utils.js +144 -0
  45. package/lib/esm/utils/type-utils.d.ts +1 -0
  46. package/lib/esm/utils/type-utils.js +6 -0
  47. package/package.json +2 -1
@@ -7,6 +7,7 @@ export declare function isEmpty(obj: unknown): boolean;
7
7
  export declare function isNonNullable<T>(value: T): value is NonNullable<T>;
8
8
  export declare function isNotEmpty<T>(arr: T[]): arr is [T, ...T[]];
9
9
  export declare function duplicates<T>(arr: T[], key?: (item: T) => unknown): T[];
10
+ export declare function omitBy<T extends Record<string, unknown>, K extends keyof T>(obj: T, predicate: (value: T[K], key: K) => boolean): Partial<T>;
10
11
  export declare function orArrayToArray<T>(value?: T | T[]): T[];
11
12
  export declare function createAccessorArray<T>(array: T[], accessor: (array: T[], name: string) => T | undefined): T[] & {
12
13
  [key: string]: T;
@@ -19,6 +19,9 @@ export interface AgentOptions<I extends Message = Message, O extends Message = M
19
19
  disableEvents?: boolean;
20
20
  memory?: AgentMemory | AgentMemoryOptions | true;
21
21
  }
22
+ export interface AgentCallOptions {
23
+ streaming?: boolean;
24
+ }
22
25
  export declare abstract class Agent<I extends Message = Message, O extends Message = Message> {
23
26
  constructor({ inputSchema, outputSchema, ...options }: AgentOptions<I, O>);
24
27
  readonly memory?: AgentMemory;
@@ -50,14 +53,37 @@ export declare abstract class Agent<I extends Message = Message, O extends Messa
50
53
  get isCallable(): boolean;
51
54
  private checkContextStatus;
52
55
  private newDefaultContext;
53
- call(input: I | string, context?: Context): Promise<O>;
56
+ call(input: I | string, context: Context | undefined, options: AgentCallOptions & {
57
+ streaming: true;
58
+ }): Promise<AgentResponseStream<O>>;
59
+ call(input: I | string, context?: Context, options?: AgentCallOptions & {
60
+ streaming?: false;
61
+ }): Promise<O>;
62
+ call(input: I | string, context?: Context, options?: AgentCallOptions): Promise<AgentResponse<O>>;
63
+ private processAgentOutput;
64
+ private processAgentError;
54
65
  protected checkUsageAgentCalls(context: Context): void;
55
66
  protected preprocess(_: I, context: Context): void;
56
67
  protected postprocess(input: I, output: O, context: Context): void;
57
- abstract process(input: I, context: Context): Promise<O | TransferAgentOutput>;
68
+ abstract process(input: I, context: Context, options?: AgentCallOptions): AgentProcessResult<O | TransferAgentOutput>;
58
69
  shutdown(): Promise<void>;
59
70
  [inspect.custom](): string;
60
71
  }
72
+ export type AgentResponse<T> = T | AgentResponseStream<T>;
73
+ export type AgentResponseStream<T> = ReadableStream<AgentResponseChunk<T>>;
74
+ export type AgentResponseChunk<T> = AgentResponseDelta<T>;
75
+ export interface AgentResponseDelta<T> {
76
+ delta: {
77
+ text?: Partial<{
78
+ [key in keyof T as Extract<T[key], string> extends string ? key : never]: string;
79
+ }> | {
80
+ [key: string]: string;
81
+ };
82
+ json?: Partial<T>;
83
+ };
84
+ }
85
+ export type AgentProcessAsyncGenerator<O extends Message> = AsyncGenerator<AgentResponseChunk<O>, Partial<O> | undefined | void>;
86
+ export type AgentProcessResult<O extends Message> = Promise<AgentResponse<O>> | AgentProcessAsyncGenerator<O>;
61
87
  export type AgentInputOutputSchema<I extends Message = Message> = ZodType<I> | ((agent: Agent) => ZodType<I>);
62
88
  export interface FunctionAgentOptions<I extends Message = Message, O extends Message = Message> extends AgentOptions<I, O> {
63
89
  fn?: FunctionAgentFn<I, O>;
@@ -66,6 +92,6 @@ export declare class FunctionAgent<I extends Message = Message, O extends Messag
66
92
  static from<I extends Message, O extends Message>(options: FunctionAgentOptions<I, O> | FunctionAgentFn<I, O>): FunctionAgent<I, O>;
67
93
  constructor(options: FunctionAgentOptions<I, O>);
68
94
  fn: FunctionAgentFn<I, O>;
69
- process(input: I, context: Context): Promise<TransferAgentOutput | O>;
95
+ process(input: I, context: Context, options?: AgentCallOptions): Promise<AgentResponse<O | TransferAgentOutput>>;
70
96
  }
71
97
  export type FunctionAgentFn<I extends Message = Message, O extends Message = Message> = (input: I, context: Context) => O | Promise<O> | Agent | Promise<Agent>;
@@ -2,6 +2,7 @@ import { inspect } from "node:util";
2
2
  import { ZodObject, z } from "zod";
3
3
  import { createMessage } from "../prompt/prompt-builder.js";
4
4
  import { logger } from "../utils/logger.js";
5
+ import { agentResponseStreamToObject, asyncGeneratorToReadableStream, isAsyncGenerator, objectToAgentResponseStream, onAgentResponseStreamEnd, } from "../utils/stream-utils.js";
5
6
  import { checkArguments, createAccessorArray, orArrayToArray, } from "../utils/type-utils.js";
6
7
  import { AgentMemory } from "./memory.js";
7
8
  import { replaceTransferAgentToName, transferToAgentOutput, } from "./types.js";
@@ -94,7 +95,7 @@ export class Agent {
94
95
  async newDefaultContext() {
95
96
  return import("../execution-engine/context.js").then((m) => new m.ExecutionContext());
96
97
  }
97
- async call(input, context) {
98
+ async call(input, context, options) {
98
99
  const ctx = context ?? (await this.newDefaultContext());
99
100
  const message = typeof input === "string" ? createMessage(input) : input;
100
101
  logger.core("Call agent %s started with input: %O", this.name, input);
@@ -104,27 +105,51 @@ export class Agent {
104
105
  const parsedInput = checkArguments(`Agent ${this.name} input`, this.inputSchema, message);
105
106
  this.preprocess(parsedInput, ctx);
106
107
  this.checkContextStatus(ctx);
107
- const output = await this.process(parsedInput, ctx)
108
- .then((output) => {
109
- const parsedOutput = checkArguments(`Agent ${this.name} output`, this.outputSchema, output);
110
- return this.includeInputInOutput ? { ...parsedInput, ...parsedOutput } : parsedOutput;
111
- })
112
- .then((output) => {
113
- this.postprocess(parsedInput, output, ctx);
114
- return output;
115
- });
116
- logger.core("Call agent %s succeed with output: %O", this.name, input);
117
- if (!this.disableEvents)
118
- ctx.emit("agentSucceed", { agent: this, output });
119
- return output;
108
+ const response = await this.process(parsedInput, ctx, options);
109
+ if (options?.streaming) {
110
+ const stream = response instanceof ReadableStream
111
+ ? response
112
+ : isAsyncGenerator(response)
113
+ ? asyncGeneratorToReadableStream(response)
114
+ : objectToAgentResponseStream(response);
115
+ return onAgentResponseStreamEnd(stream, async (result) => {
116
+ return await this.processAgentOutput(parsedInput, result, ctx);
117
+ }, {
118
+ errorCallback: (error) => {
119
+ try {
120
+ this.processAgentError(error, ctx);
121
+ }
122
+ catch (error) {
123
+ return error;
124
+ }
125
+ },
126
+ });
127
+ }
128
+ return await this.processAgentOutput(parsedInput, response instanceof ReadableStream
129
+ ? await agentResponseStreamToObject(response)
130
+ : isAsyncGenerator(response)
131
+ ? await agentResponseStreamToObject(response)
132
+ : response, ctx);
120
133
  }
121
134
  catch (error) {
122
- logger.core("Call agent %s failed with error: %O", this.name, error);
123
- if (!this.disableEvents)
124
- ctx.emit("agentFailed", { agent: this, error });
125
- throw error;
135
+ this.processAgentError(error, ctx);
126
136
  }
127
137
  }
138
+ async processAgentOutput(input, output, context) {
139
+ const parsedOutput = checkArguments(`Agent ${this.name} output`, this.outputSchema, output);
140
+ const finalOutput = this.includeInputInOutput ? { ...input, ...parsedOutput } : parsedOutput;
141
+ this.postprocess(input, finalOutput, context);
142
+ logger.core("Call agent %s succeed with output: %O", this.name, finalOutput);
143
+ if (!this.disableEvents)
144
+ context.emit("agentSucceed", { agent: this, output: finalOutput });
145
+ return finalOutput;
146
+ }
147
+ processAgentError(error, context) {
148
+ logger.core("Call agent %s failed with error: %O", this.name, error);
149
+ if (!this.disableEvents)
150
+ context.emit("agentFailed", { agent: this, error });
151
+ throw error;
152
+ }
128
153
  checkUsageAgentCalls(context) {
129
154
  const { limits, usage } = context;
130
155
  if (limits?.maxAgentCalls && usage.agentCalls >= limits.maxAgentCalls) {
@@ -166,12 +191,12 @@ export class FunctionAgent extends Agent {
166
191
  this.fn = options.fn ?? (() => ({}));
167
192
  }
168
193
  fn;
169
- async process(input, context) {
170
- const result = await this.fn(input, context);
194
+ async process(input, context, options) {
195
+ let result = await this.fn(input, context);
171
196
  if (result instanceof Agent) {
172
- return transferToAgentOutput(result);
197
+ result = transferToAgentOutput(result);
173
198
  }
174
- return result;
199
+ return options?.streaming ? objectToAgentResponseStream(result) : result;
175
200
  }
176
201
  }
177
202
  function functionToAgent(agent) {
@@ -2,7 +2,8 @@ import { z } from "zod";
2
2
  import type { Context } from "../execution-engine/context.js";
3
3
  import { ChatModel } from "../models/chat-model.js";
4
4
  import { PromptBuilder } from "../prompt/prompt-builder.js";
5
- import { Agent, type AgentOptions, type Message } from "./agent.js";
5
+ import { Agent, type AgentOptions, type AgentProcessAsyncGenerator, type Message } from "./agent.js";
6
+ import { type TransferAgentOutput } from "./types.js";
6
7
  export interface AIAgentOptions<I extends Message = Message, O extends Message = Message> extends AgentOptions<I, O> {
7
8
  model?: ChatModel;
8
9
  instructions?: string | PromptBuilder;
@@ -64,5 +65,5 @@ export declare class AIAgent<I extends Message = Message, O extends Message = Me
64
65
  instructions: PromptBuilder;
65
66
  outputKey?: string;
66
67
  toolChoice?: AIAgentToolChoice;
67
- process(input: I, context: Context): Promise<import("./types.js").TransferAgentOutput | O>;
68
+ process(input: I, context: Context): AgentProcessAsyncGenerator<O | TransferAgentOutput>;
68
69
  }
@@ -2,7 +2,9 @@ import { z } from "zod";
2
2
  import { ChatModel } from "../models/chat-model.js";
3
3
  import { MESSAGE_KEY, PromptBuilder } from "../prompt/prompt-builder.js";
4
4
  import { AgentMessageTemplate, ToolMessageTemplate } from "../prompt/template.js";
5
- import { Agent } from "./agent.js";
5
+ import { readableStreamToAsyncIterator } from "../utils/stream-utils.js";
6
+ import { isEmpty } from "../utils/type-utils.js";
7
+ import { Agent, } from "./agent.js";
6
8
  import { isTransferAgentOutput } from "./types.js";
7
9
  export const aiAgentToolChoiceSchema = z.union([
8
10
  z.literal("auto"),
@@ -46,7 +48,7 @@ export class AIAgent extends Agent {
46
48
  instructions;
47
49
  outputKey;
48
50
  toolChoice;
49
- async process(input, context) {
51
+ async *process(input, context) {
50
52
  const model = context.model ?? this.model;
51
53
  if (!model)
52
54
  throw new Error("model is required to run AIAgent");
@@ -58,11 +60,19 @@ export class AIAgent extends Agent {
58
60
  });
59
61
  const toolsMap = new Map(toolAgents?.map((i) => [i.name, i]));
60
62
  const toolCallMessages = [];
63
+ const outputKey = this.outputKey || MESSAGE_KEY;
61
64
  for (;;) {
62
- const { text, json, toolCalls } = await context.call(model, {
63
- ...modelInput,
64
- messages: messages.concat(toolCallMessages),
65
- });
65
+ const modelOutput = {};
66
+ const stream = await context.call(model, { ...modelInput, messages: messages.concat(toolCallMessages) }, { streaming: true });
67
+ for await (const value of readableStreamToAsyncIterator(stream)) {
68
+ if (value.delta.text?.text) {
69
+ yield { delta: { text: { [outputKey]: value.delta.text.text } } };
70
+ }
71
+ if (value.delta.json) {
72
+ Object.assign(modelOutput, value.delta.json);
73
+ }
74
+ }
75
+ const { toolCalls, json, text } = modelOutput;
66
76
  if (toolCalls?.length) {
67
77
  const executedToolCalls = [];
68
78
  // Execute tools
@@ -84,7 +94,8 @@ export class AIAgent extends Agent {
84
94
  // Return the output of the first tool if the toolChoice is "router"
85
95
  if (this.toolChoice === "router") {
86
96
  const output = executedToolCalls[0]?.output;
87
- if (!output || executedToolCalls.length !== 1) {
97
+ const { supportsParallelToolCalls } = model.getModelCapabilities();
98
+ if (!output || (supportsParallelToolCalls && executedToolCalls.length !== 1)) {
88
99
  throw new Error("Router toolChoice requires exactly one tool to be executed");
89
100
  }
90
101
  return output;
@@ -96,11 +107,13 @@ export class AIAgent extends Agent {
96
107
  if (modelInput.responseFormat?.type === "json_schema") {
97
108
  Object.assign(result, json);
98
109
  }
99
- else {
100
- const outputKey = this.outputKey || MESSAGE_KEY;
110
+ else if (text) {
101
111
  Object.assign(result, { [outputKey]: text });
102
112
  }
103
- return result;
113
+ if (!isEmpty(result)) {
114
+ yield { delta: { json: result } };
115
+ }
116
+ return;
104
117
  }
105
118
  }
106
119
  }
@@ -2,7 +2,7 @@ import { ReadableStream } from "node:stream/web";
2
2
  import { type Context, type Runnable } from "../execution-engine/context.js";
3
3
  import type { MessagePayload } from "../execution-engine/message-queue.js";
4
4
  import { type PromiseOrValue } from "../utils/type-utils.js";
5
- import { Agent, type AgentOptions, type Message } from "./agent.js";
5
+ import { Agent, type AgentOptions, type AgentProcessAsyncGenerator, type Message } from "./agent.js";
6
6
  export interface UserAgentOptions<I extends Message = Message, O extends Message = Message> extends AgentOptions<I, O> {
7
7
  context: Context;
8
8
  process?: (input: I, context: Context) => PromiseOrValue<O>;
@@ -14,8 +14,8 @@ export declare class UserAgent<I extends Message = Message, O extends Message =
14
14
  context: Context;
15
15
  private _process?;
16
16
  private activeAgent?;
17
- call(input: string | I, context?: Context): Promise<O>;
18
- process(input: I, context: Context): Promise<O>;
17
+ call: Agent<I, O>["call"];
18
+ process(input: I, context: Context): AgentProcessAsyncGenerator<O>;
19
19
  publish: Context["publish"];
20
20
  subscribe: Context["subscribe"];
21
21
  unsubscribe: Context["unsubscribe"];
@@ -1,7 +1,8 @@
1
1
  import { ReadableStream } from "node:stream/web";
2
2
  import { createPublishMessage } from "../execution-engine/context.js";
3
+ import { readableStreamToAsyncIterator } from "../utils/stream-utils.js";
3
4
  import { orArrayToArray } from "../utils/type-utils.js";
4
- import { Agent } from "./agent.js";
5
+ import { Agent, } from "./agent.js";
5
6
  export class UserAgent extends Agent {
6
7
  static from(options) {
7
8
  return new UserAgent(options);
@@ -15,26 +16,31 @@ export class UserAgent extends Agent {
15
16
  context;
16
17
  _process;
17
18
  activeAgent;
18
- call(input, context) {
19
+ call = ((input, context, options) => {
19
20
  if (!context)
20
21
  this.context = this.context.newContext({ reset: true });
21
- return super.call(input, context ?? this.context);
22
- }
23
- async process(input, context) {
22
+ return super.call(input, context ?? this.context, options);
23
+ });
24
+ async *process(input, context) {
24
25
  if (this._process) {
25
- return this._process(input, context);
26
+ yield { delta: { json: await this._process(input, context) } };
27
+ return;
26
28
  }
27
29
  if (this.activeAgent) {
28
30
  const [output, agent] = await context.call(this.activeAgent, input, {
29
31
  returnActiveAgent: true,
32
+ streaming: true,
33
+ });
34
+ agent.then((agent) => {
35
+ this.activeAgent = agent;
30
36
  });
31
- this.activeAgent = agent;
32
- return output;
37
+ yield* readableStreamToAsyncIterator(output);
38
+ return;
33
39
  }
34
40
  const publicTopic = typeof this.publishTopic === "function" ? await this.publishTopic(input) : this.publishTopic;
35
41
  if (publicTopic?.length) {
36
42
  context.publish(publicTopic, createPublishMessage(input, this));
37
- return {};
43
+ return;
38
44
  }
39
45
  throw new Error("UserAgent must have a process function or a publishTopic");
40
46
  }
@@ -1,5 +1,5 @@
1
1
  import EventEmitter from "node:events";
2
- import { Agent, type FunctionAgentFn, type Message } from "../agents/agent.js";
2
+ import { Agent, type AgentCallOptions, type AgentProcessAsyncGenerator, type AgentResponse, type AgentResponseStream, type FunctionAgentFn, type Message } from "../agents/agent.js";
3
3
  import { UserAgent } from "../agents/user-agent.js";
4
4
  import type { ChatModel } from "../models/chat-model.js";
5
5
  import { type OmitPropertiesFromArrayFirstElement } from "../utils/type-utils.js";
@@ -27,7 +27,7 @@ export interface ContextEventMap {
27
27
  export type ContextEmitEventMap = {
28
28
  [K in keyof ContextEventMap]: OmitPropertiesFromArrayFirstElement<ContextEventMap[K], "contextId" | "parentContextId" | "timestamp">;
29
29
  };
30
- export interface CallOptions {
30
+ export interface CallOptions extends AgentCallOptions {
31
31
  returnActiveAgent?: boolean;
32
32
  disableTransfer?: boolean;
33
33
  }
@@ -48,19 +48,30 @@ export interface Context extends TypedEventEmitter<ContextEventMap, ContextEmitE
48
48
  * @param agent Agent to call
49
49
  * @param message Message to pass to the agent
50
50
  * @param options.returnActiveAgent return the active agent
51
+ * @param options.streaming return a stream of the output
51
52
  * @returns the output of the agent and the final active agent
52
53
  */
53
54
  call<I extends Message, O extends Message>(agent: Runnable<I, O>, message: I | string, options: CallOptions & {
54
55
  returnActiveAgent: true;
56
+ streaming?: false;
55
57
  }): Promise<[O, Runnable]>;
58
+ call<I extends Message, O extends Message>(agent: Runnable<I, O>, message: I | string, options: CallOptions & {
59
+ returnActiveAgent: true;
60
+ streaming: true;
61
+ }): Promise<[AgentResponseStream<O>, Promise<Runnable>]>;
56
62
  /**
57
63
  * Call an agent with a message
58
64
  * @param agent Agent to call
59
65
  * @param message Message to pass to the agent
60
66
  * @returns the output of the agent
61
67
  */
62
- call<I extends Message, O extends Message>(agent: Runnable<I, O>, message: I | string, options?: CallOptions): Promise<O>;
63
- call<I extends Message, O extends Message>(agent: Runnable<I, O>, message?: I | string, options?: CallOptions): UserAgent<I, O> | Promise<O | [O, Runnable]>;
68
+ call<I extends Message, O extends Message>(agent: Runnable<I, O>, message: I | string, options?: CallOptions & {
69
+ streaming?: false;
70
+ }): Promise<O>;
71
+ call<I extends Message, O extends Message>(agent: Runnable<I, O>, message: I | string, options: CallOptions & {
72
+ streaming: true;
73
+ }): Promise<AgentResponseStream<O>>;
74
+ call<I extends Message, O extends Message>(agent: Runnable<I, O>, message?: I | string, options?: CallOptions): UserAgent<I, O> | Promise<AgentResponse<O> | [AgentResponse<O>, Runnable]>;
64
75
  /**
65
76
  * Publish a message to a topic, the engine will call the listeners of the topic
66
77
  * @param topic topic name, or an array of topic names
@@ -99,6 +110,7 @@ export declare class ExecutionContext implements Context {
99
110
  reset?: boolean;
100
111
  }): ExecutionContext;
101
112
  call: Context["call"];
113
+ private onCallSuccess;
102
114
  publish: Context["publish"];
103
115
  subscribe: Context["subscribe"];
104
116
  unsubscribe: Context["unsubscribe"];
@@ -122,9 +134,8 @@ declare class ExecutionContextInternal {
122
134
  private timer?;
123
135
  private initTimeout;
124
136
  get status(): "normal" | "timeout";
125
- call<I extends Message, O extends Message>(agent: Runnable<I, O>, input: I, context: Context, options?: CallOptions): Promise<{
126
- agent: Runnable;
127
- output: O;
137
+ call<I extends Message, O extends Message>(agent: Runnable<I, O>, input: I, context: Context, options?: CallOptions): AgentProcessAsyncGenerator<O & {
138
+ __activeAgent__: Runnable;
128
139
  }>;
129
140
  private callAgent;
130
141
  }
@@ -1,11 +1,12 @@
1
1
  import EventEmitter from "node:events";
2
2
  import { v7 } from "uuid";
3
3
  import { z } from "zod";
4
- import { Agent } from "../agents/agent.js";
4
+ import { Agent, } from "../agents/agent.js";
5
5
  import { isTransferAgentOutput, transferAgentOutputKey } from "../agents/types.js";
6
6
  import { UserAgent } from "../agents/user-agent.js";
7
7
  import { createMessage } from "../prompt/prompt-builder.js";
8
- import { checkArguments, isNil, } from "../utils/type-utils.js";
8
+ import { agentResponseStreamToObject, asyncGeneratorToReadableStream, onAgentResponseStreamEnd, readableStreamToAsyncIterator, } from "../utils/stream-utils.js";
9
+ import { checkArguments, isNil, omitBy, } from "../utils/type-utils.js";
9
10
  import { MessageQueue, } from "./message-queue.js";
10
11
  import { newEmptyContextUsage } from "./usage.js";
11
12
  export function createPublishMessage(message, from) {
@@ -62,23 +63,49 @@ export class ExecutionContext {
62
63
  }
63
64
  const newContext = this.newContext();
64
65
  const msg = createMessage(message);
65
- return newContext.internal
66
- .call(agent, msg, newContext, options)
67
- .then(async ({ output, agent: activeAgent }) => {
68
- if (activeAgent instanceof Agent) {
69
- const publishTopics = typeof activeAgent.publishTopic === "function"
70
- ? await activeAgent.publishTopic(output)
71
- : activeAgent.publishTopic;
72
- if (publishTopics?.length) {
73
- newContext.publish(publishTopics, createPublishMessage(output, activeAgent));
66
+ return Promise.resolve(newContext.internal.call(agent, msg, newContext, options)).then(async (response) => {
67
+ if (!options?.streaming) {
68
+ const { __activeAgent__: activeAgent, ...output } = await agentResponseStreamToObject(response);
69
+ this.onCallSuccess(activeAgent, output, newContext);
70
+ if (options?.returnActiveAgent) {
71
+ return [output, activeAgent];
74
72
  }
73
+ return output;
75
74
  }
76
- if (options?.returnActiveAgent) {
77
- return [output, activeAgent];
75
+ const activeAgentPromise = Promise.withResolvers();
76
+ const stream = onAgentResponseStreamEnd(asyncGeneratorToReadableStream(response), async ({ __activeAgent__: activeAgent, ...output }) => {
77
+ this.onCallSuccess(activeAgent, output, newContext);
78
+ activeAgentPromise.resolve(activeAgent);
79
+ }, {
80
+ processChunk(chunk) {
81
+ if (chunk.delta.json) {
82
+ return {
83
+ ...chunk,
84
+ delta: {
85
+ ...chunk.delta,
86
+ json: omitBy(chunk.delta.json, (_, k) => k === "__activeAgent__"),
87
+ },
88
+ };
89
+ }
90
+ return chunk;
91
+ },
92
+ });
93
+ if (options.returnActiveAgent) {
94
+ return [stream, activeAgentPromise.promise];
78
95
  }
79
- return output;
96
+ return stream;
80
97
  });
81
98
  });
99
+ async onCallSuccess(activeAgent, output, context) {
100
+ if (activeAgent instanceof Agent) {
101
+ const publishTopics = typeof activeAgent.publishTopic === "function"
102
+ ? await activeAgent.publishTopic(output)
103
+ : activeAgent.publishTopic;
104
+ if (publishTopics?.length) {
105
+ context.publish(publishTopics, createPublishMessage(output, activeAgent));
106
+ }
107
+ }
108
+ }
82
109
  publish = ((topic, payload) => {
83
110
  return this.internal.messageQueue.publish(topic, { ...payload, context: this });
84
111
  });
@@ -144,11 +171,11 @@ class ExecutionContextInternal {
144
171
  get status() {
145
172
  return this.abortController.signal.aborted ? "timeout" : "normal";
146
173
  }
147
- async call(agent, input, context, options) {
174
+ call(agent, input, context, options) {
148
175
  this.initTimeout();
149
176
  return withAbortSignal(this.abortController.signal, new Error("ExecutionContext is timeout"), () => this.callAgent(agent, input, context, options));
150
177
  }
151
- async callAgent(agent, input, context, options) {
178
+ async *callAgent(agent, input, context, options) {
152
179
  let activeAgent = agent;
153
180
  let output;
154
181
  for (;;) {
@@ -157,7 +184,16 @@ class ExecutionContextInternal {
157
184
  result = await activeAgent(input, context);
158
185
  }
159
186
  else {
160
- result = await activeAgent.call(input, context);
187
+ result = {};
188
+ const stream = await activeAgent.call(input, context, { streaming: true });
189
+ for await (const value of readableStreamToAsyncIterator(stream)) {
190
+ if (value.delta.text) {
191
+ yield { delta: { text: value.delta.text } };
192
+ }
193
+ if (value.delta.json) {
194
+ Object.assign(result, value.delta.json);
195
+ }
196
+ }
161
197
  }
162
198
  if (result instanceof Agent) {
163
199
  activeAgent = result;
@@ -177,22 +213,34 @@ class ExecutionContextInternal {
177
213
  }
178
214
  if (!output)
179
215
  throw new Error("Unexpected empty output");
180
- return {
181
- agent: activeAgent,
182
- output,
216
+ yield {
217
+ delta: {
218
+ json: {
219
+ ...output,
220
+ __activeAgent__: activeAgent,
221
+ },
222
+ },
183
223
  };
184
224
  }
185
225
  }
186
- function withAbortSignal(signal, error, fn) {
187
- return new Promise((resolve, reject) => {
188
- const listener = () => reject(error);
189
- signal.addEventListener("abort", listener);
190
- fn()
191
- .then(resolve, reject)
192
- .finally(() => {
193
- signal.removeEventListener("abort", listener);
194
- });
195
- });
226
+ async function* withAbortSignal(signal, error, fn) {
227
+ const iterator = fn();
228
+ const timeoutPromise = Promise.withResolvers();
229
+ const listener = () => {
230
+ timeoutPromise.reject(error);
231
+ };
232
+ signal.addEventListener("abort", listener);
233
+ try {
234
+ for (;;) {
235
+ const next = await Promise.race([iterator.next(), timeoutPromise.promise]);
236
+ if (next.done)
237
+ break;
238
+ yield next.value;
239
+ }
240
+ }
241
+ finally {
242
+ signal.removeEventListener("abort", listener);
243
+ }
196
244
  }
197
245
  const executionContextCallArgsSchema = z.object({
198
246
  agent: z.union([z.function(), z.instanceof(Agent)]),
@@ -2,6 +2,10 @@ import { Agent, type Message } from "../agents/agent.js";
2
2
  import type { Context } from "../execution-engine/context.js";
3
3
  export declare abstract class ChatModel extends Agent<ChatModelInput, ChatModelOutput> {
4
4
  constructor();
5
+ protected supportsParallelToolCalls: boolean;
6
+ getModelCapabilities(): {
7
+ supportsParallelToolCalls: boolean;
8
+ };
5
9
  protected preprocess(input: ChatModelInput, context: Context): void;
6
10
  protected postprocess(input: ChatModelInput, output: ChatModelOutput, context: Context): void;
7
11
  }
@@ -7,6 +7,12 @@ export class ChatModel extends Agent {
7
7
  outputSchema: chatModelOutputSchema,
8
8
  });
9
9
  }
10
+ supportsParallelToolCalls = true;
11
+ getModelCapabilities() {
12
+ return {
13
+ supportsParallelToolCalls: this.supportsParallelToolCalls,
14
+ };
15
+ }
10
16
  preprocess(input, context) {
11
17
  super.preprocess(input, context);
12
18
  const { limits, usage } = context;
@@ -2,4 +2,5 @@ import { OpenAIChatModel, type OpenAIChatModelOptions } from "./openai-chat-mode
2
2
  export declare class OpenRouterChatModel extends OpenAIChatModel {
3
3
  constructor(options?: OpenAIChatModelOptions);
4
4
  protected apiKeyEnvName: string;
5
+ protected supportsParallelToolCalls: boolean;
5
6
  }
@@ -10,4 +10,5 @@ export class OpenRouterChatModel extends OpenAIChatModel {
10
10
  });
11
11
  }
12
12
  apiKeyEnvName = "OPEN_ROUTER_API_KEY";
13
+ supportsParallelToolCalls = false;
13
14
  }
@@ -1,8 +1,17 @@
1
1
  import OpenAI from "openai";
2
2
  import type { ChatCompletionMessageParam, ChatCompletionTool } from "openai/resources";
3
- import type { Stream } from "openai/streaming.js";
4
3
  import { z } from "zod";
4
+ import type { AgentCallOptions, AgentResponse } from "../agents/agent.js";
5
+ import type { Context } from "../execution-engine/context.js";
5
6
  import { ChatModel, type ChatModelInput, type ChatModelInputMessage, type ChatModelInputTool, type ChatModelOptions, type ChatModelOutput, type Role } from "./chat-model.js";
7
+ export interface OpenAIChatModelCapabilities {
8
+ supportsNativeStructuredOutputs: boolean;
9
+ supportsEndWithSystemMessage: boolean;
10
+ supportsToolsUseWithJsonSchema: boolean;
11
+ supportsParallelToolCalls: boolean;
12
+ supportsToolsEmptyParameters: boolean;
13
+ supportsTemperature: boolean;
14
+ }
6
15
  export interface OpenAIChatModelOptions {
7
16
  apiKey?: string;
8
17
  baseURL?: string;
@@ -71,9 +80,10 @@ export declare class OpenAIChatModel extends ChatModel {
71
80
  protected supportsToolsUseWithJsonSchema: boolean;
72
81
  protected supportsParallelToolCalls: boolean;
73
82
  protected supportsToolsEmptyParameters: boolean;
83
+ protected supportsTemperature: boolean;
74
84
  get client(): OpenAI;
75
85
  get modelOptions(): ChatModelOptions | undefined;
76
- process(input: ChatModelInput): Promise<ChatModelOutput>;
86
+ process(input: ChatModelInput, _context: Context, options?: AgentCallOptions): Promise<AgentResponse<ChatModelOutput>>;
77
87
  private getParallelToolCalls;
78
88
  private getRunMessages;
79
89
  private getRunResponseFormat;
@@ -87,4 +97,3 @@ export declare function toolsFromInputTools(tools?: ChatModelInputTool[], option
87
97
  addTypeToEmptyParameters?: boolean;
88
98
  }): ChatCompletionTool[] | undefined;
89
99
  export declare function jsonSchemaToOpenAIJsonSchema(schema: Record<string, unknown>): Record<string, unknown>;
90
- export declare function extractResultFromStream(stream: Stream<OpenAI.Chat.Completions.ChatCompletionChunk>, jsonMode?: boolean): Promise<ChatModelOutput>;