@aigne/core 1.5.1-2 → 1.6.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
@@ -22,6 +22,20 @@
22
22
  * rename @aigne/core-next to @aigne/core ([3a81009](https://github.com/AIGNE-io/aigne-framework/commit/3a8100962c81813217b687ae28e8de604419c622))
23
23
  * use text resource from MCP correctly ([8b9eba8](https://github.com/AIGNE-io/aigne-framework/commit/8b9eba83352ec096a2a5d4f410d4c4bde7420bce))
24
24
 
25
+ ## [1.6.0](https://github.com/AIGNE-io/aigne-framework/compare/core-v1.5.0...core-v1.6.0) (2025-04-08)
26
+
27
+
28
+ ### Features
29
+
30
+ * add `serve` command for @aigne/cli ([#54](https://github.com/AIGNE-io/aigne-framework/issues/54)) ([1cca843](https://github.com/AIGNE-io/aigne-framework/commit/1cca843f1760abe832b6651108fa858130f47355))
31
+ * add agent library support ([#51](https://github.com/AIGNE-io/aigne-framework/issues/51)) ([1f0d34d](https://github.com/AIGNE-io/aigne-framework/commit/1f0d34ddda3154283a4bc958ddb9b68b4ac106b0))
32
+ * support token/call/time limits for ExecutionEngine ([#44](https://github.com/AIGNE-io/aigne-framework/issues/44)) ([5a2ca0a](https://github.com/AIGNE-io/aigne-framework/commit/5a2ca0a033267dd4765f574b53dca71e932e53d4))
33
+
34
+
35
+ ### Bug Fixes
36
+
37
+ * support reconnect to the MCP server automatically ([#50](https://github.com/AIGNE-io/aigne-framework/issues/50)) ([898d83f](https://github.com/AIGNE-io/aigne-framework/commit/898d83f75fc655142b93c70a1afeda376a2e92b4))
38
+
25
39
  ## [1.5.0](https://github.com/AIGNE-io/aigne-framework/compare/core-v1.4.0...core-v1.5.0) (2025-03-27)
26
40
 
27
41
 
@@ -5,6 +5,8 @@ import { ChatModel } from "../models/chat-model.js";
5
5
  import { type ContextLimits, ExecutionContext, type Runnable } from "./context.js";
6
6
  import { type MessagePayload, MessageQueue, type MessageQueueListener, type Unsubscribe } from "./message-queue.js";
7
7
  export interface ExecutionEngineOptions {
8
+ name?: string;
9
+ description?: string;
8
10
  model?: ChatModel;
9
11
  tools?: Agent[];
10
12
  agents?: Agent[];
@@ -15,6 +17,8 @@ export declare class ExecutionEngine extends EventEmitter {
15
17
  path: string;
16
18
  } & ExecutionEngineOptions): Promise<ExecutionEngine>;
17
19
  constructor(options?: ExecutionEngineOptions);
20
+ name?: string;
21
+ description?: string;
18
22
  readonly messageQueue: MessageQueue;
19
23
  model?: ChatModel;
20
24
  readonly tools: Agent<Message, Message>[] & {
@@ -14,10 +14,12 @@ const context_js_1 = require("./context.js");
14
14
  const message_queue_js_1 = require("./message-queue.js");
15
15
  class ExecutionEngine extends node_events_1.default {
16
16
  static async load({ path, ...options }) {
17
- const { model, agents, tools } = await (0, index_js_1.load)({ path });
17
+ const { model, agents, tools, ...aigne } = await (0, index_js_1.load)({ path });
18
18
  return new ExecutionEngine({
19
19
  model,
20
20
  ...options,
21
+ name: options.name || aigne.name || undefined,
22
+ description: options.description || aigne.description || undefined,
21
23
  agents: agents.concat(options.agents ?? []),
22
24
  tools: tools.concat(options.tools ?? []),
23
25
  });
@@ -26,6 +28,8 @@ class ExecutionEngine extends node_events_1.default {
26
28
  if (options)
27
29
  (0, type_utils_js_1.checkArguments)("ExecutionEngine", executionEngineOptionsSchema, options);
28
30
  super();
31
+ this.name = options?.name;
32
+ this.description = options?.description;
29
33
  this.model = options?.model;
30
34
  this.limits = options?.limits;
31
35
  if (options?.tools?.length)
@@ -34,6 +38,8 @@ class ExecutionEngine extends node_events_1.default {
34
38
  this.addAgent(...options.agents);
35
39
  this.initProcessExitHandler();
36
40
  }
41
+ name;
42
+ description;
37
43
  messageQueue = new message_queue_js_1.MessageQueue();
38
44
  model;
39
45
  tools = (0, type_utils_js_1.createAccessorArray)([], (arr, name) => arr.find((i) => i.name === name));
@@ -1,10 +1,6 @@
1
1
  import { type ZodObject, type ZodType, z } from "zod";
2
2
  import type { Message } from "../agents/agent.js";
3
- export declare function loadAgentFromJsFile(path: string, { import: _import }?: {
4
- import?: (path: string) => Promise<{
5
- default: unknown;
6
- }>;
7
- }): Promise<{
3
+ export declare function loadAgentFromJsFile(path: string): Promise<{
8
4
  name: string;
9
5
  fn: (args_0: Message) => Message;
10
6
  description?: string | undefined;
@@ -52,8 +52,8 @@ const agentJsFileSchema = zod_1.z.object({
52
52
  .transform((v) => (v ? (0, json_schema_to_zod_1.jsonSchemaToZod)(v) : undefined)),
53
53
  fn: zod_1.z.function(),
54
54
  });
55
- async function loadAgentFromJsFile(path, { import: _import } = {}) {
56
- const { default: agent } = await (0, type_utils_js_1.tryOrThrow)(() => (_import ? _import(path) : Promise.resolve(`${path}`).then(s => __importStar(require(s)))), (error) => new Error(`Failed to load agent definition from ${path}: ${error.message}`));
55
+ async function loadAgentFromJsFile(path) {
56
+ const { default: agent } = await (0, type_utils_js_1.tryOrThrow)(() => Promise.resolve(`${path}`).then(s => __importStar(require(s))), (error) => new Error(`Failed to load agent definition from ${path}: ${error.message}`));
57
57
  if (typeof agent !== "function") {
58
58
  throw new Error(`Agent file ${path} must export a default function, but got ${typeof agent}`);
59
59
  }
@@ -1,8 +1,6 @@
1
- import { readFile } from "node:fs/promises";
2
1
  import { type ZodObject, type ZodType, z } from "zod";
3
- export declare function loadAgentFromYamlFile(path: string, { readFile: _readFile }?: {
4
- readFile?: typeof readFile;
5
- }): Promise<{
2
+ export declare function loadAgentFromYamlFile(path: string): Promise<{
3
+ type: "ai";
6
4
  name: string;
7
5
  description?: string | undefined;
8
6
  tools?: string[] | undefined;
@@ -18,4 +16,9 @@ export declare function loadAgentFromYamlFile(path: string, { readFile: _readFil
18
16
  [x: string]: any;
19
17
  }> | undefined;
20
18
  output_key?: string | undefined;
19
+ } | {
20
+ type: "mcp";
21
+ url?: string | undefined;
22
+ command?: string | undefined;
23
+ args?: string[] | undefined;
21
24
  }>;
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.loadAgentFromYamlFile = loadAgentFromYamlFile;
4
+ const promises_1 = require("node:fs/promises");
5
+ const json_schema_to_zod_1 = require("@aigne/json-schema-to-zod");
6
+ const yaml_1 = require("yaml");
7
+ const zod_1 = require("zod");
8
+ const type_utils_js_1 = require("../utils/type-utils.js");
9
+ const schema_js_1 = require("./schema.js");
10
+ const agentFileSchema = zod_1.z.discriminatedUnion("type", [
11
+ zod_1.z.object({
12
+ type: zod_1.z.literal("ai"),
13
+ name: zod_1.z.string(),
14
+ description: zod_1.z
15
+ .string()
16
+ .nullish()
17
+ .transform((v) => v ?? undefined),
18
+ instructions: zod_1.z
19
+ .string()
20
+ .nullish()
21
+ .transform((v) => v ?? undefined),
22
+ input_schema: schema_js_1.inputOutputSchema
23
+ .nullish()
24
+ .transform((v) => (v ? (0, json_schema_to_zod_1.jsonSchemaToZod)(v) : undefined)),
25
+ output_schema: schema_js_1.inputOutputSchema
26
+ .nullish()
27
+ .transform((v) => (v ? (0, json_schema_to_zod_1.jsonSchemaToZod)(v) : undefined)),
28
+ output_key: zod_1.z
29
+ .string()
30
+ .nullish()
31
+ .transform((v) => v ?? undefined),
32
+ tools: zod_1.z
33
+ .array(zod_1.z.string())
34
+ .nullish()
35
+ .transform((v) => v ?? undefined),
36
+ }),
37
+ zod_1.z.object({
38
+ type: zod_1.z.literal("mcp"),
39
+ url: zod_1.z
40
+ .string()
41
+ .nullish()
42
+ .transform((v) => v ?? undefined),
43
+ command: zod_1.z
44
+ .string()
45
+ .nullish()
46
+ .transform((v) => v ?? undefined),
47
+ args: zod_1.z
48
+ .array(zod_1.z.string())
49
+ .nullish()
50
+ .transform((v) => v ?? undefined),
51
+ }),
52
+ ]);
53
+ async function loadAgentFromYamlFile(path) {
54
+ const raw = await (0, type_utils_js_1.tryOrThrow)(() => (0, promises_1.readFile)(path, "utf8"), (error) => new Error(`Failed to load agent definition from ${path}: ${error.message}`));
55
+ const json = await (0, type_utils_js_1.tryOrThrow)(() => (0, yaml_1.parse)(raw), (error) => new Error(`Failed to parse agent definition from ${path}: ${error.message}`));
56
+ const agent = (0, type_utils_js_1.tryOrThrow)(() => agentFileSchema.parse({ ...json, type: json.type ?? "ai" }), (error) => new Error(`Failed to validate agent definition from ${path}: ${error.message}`));
57
+ return agent;
58
+ }
@@ -1,4 +1,3 @@
1
- import { readFile } from "node:fs/promises";
2
1
  import { type Agent } from "../agents/agent.js";
3
2
  import type { ChatModel } from "../models/chat-model.js";
4
3
  export interface LoadOptions {
@@ -8,15 +7,26 @@ export declare function load(options: LoadOptions): Promise<{
8
7
  model: ChatModel | undefined;
9
8
  agents: Agent<import("../agents/agent.js").Message, import("../agents/agent.js").Message>[];
10
9
  tools: Agent<import("../agents/agent.js").Message, import("../agents/agent.js").Message>[];
10
+ description?: string | null | undefined;
11
+ name?: string | null | undefined;
12
+ chat_model?: {
13
+ name?: string | null | undefined;
14
+ temperature?: number | null | undefined;
15
+ provider?: string | null | undefined;
16
+ top_p?: number | null | undefined;
17
+ frequent_penalty?: number | null | undefined;
18
+ presence_penalty?: number | null | undefined;
19
+ } | null | undefined;
11
20
  }>;
12
21
  export declare function loadAgent(path: string): Promise<Agent>;
13
- export declare function loadAIGNEFile(path: string, { readFile: _readFile }?: {
14
- readFile?: typeof readFile;
15
- }): Promise<{
22
+ export declare function loadAIGNEFile(path: string): Promise<{
23
+ description?: string | null | undefined;
16
24
  tools?: string[] | null | undefined;
25
+ name?: string | null | undefined;
17
26
  chat_model?: {
18
27
  name?: string | null | undefined;
19
28
  temperature?: number | null | undefined;
29
+ provider?: string | null | undefined;
20
30
  top_p?: number | null | undefined;
21
31
  frequent_penalty?: number | null | undefined;
22
32
  presence_penalty?: number | null | undefined;
@@ -9,19 +9,24 @@ const yaml_1 = require("yaml");
9
9
  const zod_1 = require("zod");
10
10
  const agent_js_1 = require("../agents/agent.js");
11
11
  const ai_agent_js_1 = require("../agents/ai-agent.js");
12
+ const mcp_agent_js_1 = require("../agents/mcp-agent.js");
13
+ const claude_chat_model_js_1 = require("../models/claude-chat-model.js");
12
14
  const openai_chat_model_js_1 = require("../models/openai-chat-model.js");
15
+ const xai_chat_model_js_1 = require("../models/xai-chat-model.js");
13
16
  const type_utils_js_1 = require("../utils/type-utils.js");
14
- const ai_agent_js_2 = require("./ai-agent.js");
15
- const function_agent_js_1 = require("./function-agent.js");
16
- const AIGNE_FILE_NAME = "aigne.yaml";
17
+ const agent_js_js_1 = require("./agent-js.js");
18
+ const agent_yaml_js_1 = require("./agent-yaml.js");
19
+ const DEFAULT_MODEL_PROVIDER = "openai";
20
+ const AIGNE_FILE_NAME = ["aigne.yaml", "aigne.yml"];
17
21
  async function load(options) {
18
22
  const { path } = options;
19
- const aigneFilePath = path.endsWith(AIGNE_FILE_NAME) ? path : (0, node_path_1.join)(path, AIGNE_FILE_NAME);
23
+ const aigneFilePath = await getAIGNEFilePath(path);
20
24
  const rootDir = (0, node_path_1.dirname)(aigneFilePath);
21
25
  const aigne = await loadAIGNEFile(aigneFilePath);
22
26
  const agents = await Promise.all((aigne.agents ?? []).map((filename) => loadAgent((0, node_path_1.join)(rootDir, filename))));
23
27
  const tools = await Promise.all((aigne.tools ?? []).map((filename) => loadAgent((0, node_path_1.join)(rootDir, filename))));
24
28
  return {
29
+ ...aigne,
25
30
  model: await loadModel(aigne.chat_model),
26
31
  agents,
27
32
  tools,
@@ -29,7 +34,7 @@ async function load(options) {
29
34
  }
30
35
  async function loadAgent(path) {
31
36
  if ((0, node_path_1.extname)(path) === ".js") {
32
- const agent = await (0, function_agent_js_1.loadAgentFromJsFile)(path);
37
+ const agent = await (0, agent_js_js_1.loadAgentFromJsFile)(path);
33
38
  return agent_js_1.FunctionAgent.from({
34
39
  name: agent.name,
35
40
  description: agent.description,
@@ -39,16 +44,32 @@ async function loadAgent(path) {
39
44
  });
40
45
  }
41
46
  if ((0, node_path_1.extname)(path) === ".yaml" || (0, node_path_1.extname)(path) === ".yml") {
42
- const agent = await (0, ai_agent_js_2.loadAgentFromYamlFile)(path);
43
- return ai_agent_js_1.AIAgent.from({
44
- name: agent.name,
45
- description: agent.description,
46
- instructions: agent.instructions,
47
- inputSchema: agent.input_schema,
48
- outputSchema: agent.output_schema,
49
- outputKey: agent.output_key,
50
- tools: await Promise.all((agent.tools ?? []).map((filename) => loadAgent((0, node_path_1.join)((0, node_path_1.dirname)(path), filename)))),
51
- });
47
+ const agent = await (0, agent_yaml_js_1.loadAgentFromYamlFile)(path);
48
+ if (agent.type === "ai") {
49
+ return ai_agent_js_1.AIAgent.from({
50
+ name: agent.name,
51
+ description: agent.description,
52
+ instructions: agent.instructions,
53
+ inputSchema: agent.input_schema,
54
+ outputSchema: agent.output_schema,
55
+ outputKey: agent.output_key,
56
+ tools: await Promise.all((agent.tools ?? []).map((filename) => loadAgent((0, node_path_1.join)((0, node_path_1.dirname)(path), filename)))),
57
+ });
58
+ }
59
+ if (agent.type === "mcp") {
60
+ if (agent.url) {
61
+ return mcp_agent_js_1.MCPAgent.from({
62
+ url: agent.url,
63
+ });
64
+ }
65
+ if (agent.command) {
66
+ return mcp_agent_js_1.MCPAgent.from({
67
+ command: agent.command,
68
+ args: agent.args,
69
+ });
70
+ }
71
+ throw new Error(`Missing url or command in mcp agent: ${path}`);
72
+ }
52
73
  }
53
74
  throw new Error(`Unsupported agent file type: ${path}`);
54
75
  }
@@ -62,17 +83,20 @@ async function loadModel(model) {
62
83
  frequencyPenalty: model.frequent_penalty ?? undefined,
63
84
  presencePenalty: model.presence_penalty ?? undefined,
64
85
  };
65
- // TODO: add support for other models such as AutoChatModel, ClaudeChatModel, etc.
66
- if (/^o1|gpt-/.test(model.name)) {
67
- return new openai_chat_model_js_1.OpenAIChatModel(params);
68
- }
69
- throw new Error(`Unsupported model: ${model.name}`);
86
+ const availableModels = [openai_chat_model_js_1.OpenAIChatModel, claude_chat_model_js_1.ClaudeChatModel, xai_chat_model_js_1.XAIChatModel];
87
+ const M = availableModels.find((m) => m.name.toLowerCase().includes(model.provider || DEFAULT_MODEL_PROVIDER));
88
+ if (!M)
89
+ throw new Error(`Unsupported model: ${model.provider} ${model.name}`);
90
+ return new M(params);
70
91
  }
71
92
  const aigneFileSchema = zod_1.z.object({
93
+ name: zod_1.z.string().nullish(),
94
+ description: zod_1.z.string().nullish(),
72
95
  chat_model: zod_1.z
73
96
  .union([
74
97
  zod_1.z.string(),
75
98
  zod_1.z.object({
99
+ provider: zod_1.z.string().nullish(),
76
100
  name: zod_1.z.string().nullish(),
77
101
  temperature: zod_1.z.number().min(0).max(2).nullish(),
78
102
  top_p: zod_1.z.number().min(0).nullish(),
@@ -85,9 +109,20 @@ const aigneFileSchema = zod_1.z.object({
85
109
  agents: zod_1.z.array(zod_1.z.string()).nullish(),
86
110
  tools: zod_1.z.array(zod_1.z.string()).nullish(),
87
111
  });
88
- async function loadAIGNEFile(path, { readFile: _readFile = promises_1.readFile } = {}) {
89
- const raw = await (0, type_utils_js_1.tryOrThrow)(() => _readFile(path, "utf8"), (error) => new Error(`Failed to load aigne.yaml from ${path}: ${error.message}`));
112
+ async function loadAIGNEFile(path) {
113
+ const raw = await (0, type_utils_js_1.tryOrThrow)(() => (0, promises_1.readFile)(path, "utf8"), (error) => new Error(`Failed to load aigne.yaml from ${path}: ${error.message}`));
90
114
  const json = await (0, type_utils_js_1.tryOrThrow)(() => (0, yaml_1.parse)(raw), (error) => new Error(`Failed to parse aigne.yaml from ${path}: ${error.message}`));
91
115
  const agent = (0, type_utils_js_1.tryOrThrow)(() => aigneFileSchema.parse(json), (error) => new Error(`Failed to validate aigne.yaml from ${path}: ${error.message}`));
92
116
  return agent;
93
117
  }
118
+ async function getAIGNEFilePath(path) {
119
+ const s = await (0, promises_1.stat)(path);
120
+ if (s.isDirectory()) {
121
+ for (const file of AIGNE_FILE_NAME) {
122
+ const filePath = (0, node_path_1.join)(path, file);
123
+ if (await (0, promises_1.exists)(filePath))
124
+ return filePath;
125
+ }
126
+ }
127
+ return path;
128
+ }
@@ -5,6 +5,8 @@ import { ChatModel } from "../models/chat-model.js";
5
5
  import { type ContextLimits, ExecutionContext, type Runnable } from "./context.js";
6
6
  import { type MessagePayload, MessageQueue, type MessageQueueListener, type Unsubscribe } from "./message-queue.js";
7
7
  export interface ExecutionEngineOptions {
8
+ name?: string;
9
+ description?: string;
8
10
  model?: ChatModel;
9
11
  tools?: Agent[];
10
12
  agents?: Agent[];
@@ -15,6 +17,8 @@ export declare class ExecutionEngine extends EventEmitter {
15
17
  path: string;
16
18
  } & ExecutionEngineOptions): Promise<ExecutionEngine>;
17
19
  constructor(options?: ExecutionEngineOptions);
20
+ name?: string;
21
+ description?: string;
18
22
  readonly messageQueue: MessageQueue;
19
23
  model?: ChatModel;
20
24
  readonly tools: Agent<Message, Message>[] & {
@@ -1,10 +1,6 @@
1
1
  import { type ZodObject, type ZodType, z } from "zod";
2
2
  import type { Message } from "../agents/agent.js";
3
- export declare function loadAgentFromJsFile(path: string, { import: _import }?: {
4
- import?: (path: string) => Promise<{
5
- default: unknown;
6
- }>;
7
- }): Promise<{
3
+ export declare function loadAgentFromJsFile(path: string): Promise<{
8
4
  name: string;
9
5
  fn: (args_0: Message) => Message;
10
6
  description?: string | undefined;
@@ -1,8 +1,6 @@
1
- import { readFile } from "node:fs/promises";
2
1
  import { type ZodObject, type ZodType, z } from "zod";
3
- export declare function loadAgentFromYamlFile(path: string, { readFile: _readFile }?: {
4
- readFile?: typeof readFile;
5
- }): Promise<{
2
+ export declare function loadAgentFromYamlFile(path: string): Promise<{
3
+ type: "ai";
6
4
  name: string;
7
5
  description?: string | undefined;
8
6
  tools?: string[] | undefined;
@@ -18,4 +16,9 @@ export declare function loadAgentFromYamlFile(path: string, { readFile: _readFil
18
16
  [x: string]: any;
19
17
  }> | undefined;
20
18
  output_key?: string | undefined;
19
+ } | {
20
+ type: "mcp";
21
+ url?: string | undefined;
22
+ command?: string | undefined;
23
+ args?: string[] | undefined;
21
24
  }>;
@@ -1,4 +1,3 @@
1
- import { readFile } from "node:fs/promises";
2
1
  import { type Agent } from "../agents/agent.js";
3
2
  import type { ChatModel } from "../models/chat-model.js";
4
3
  export interface LoadOptions {
@@ -8,15 +7,26 @@ export declare function load(options: LoadOptions): Promise<{
8
7
  model: ChatModel | undefined;
9
8
  agents: Agent<import("../agents/agent.js").Message, import("../agents/agent.js").Message>[];
10
9
  tools: Agent<import("../agents/agent.js").Message, import("../agents/agent.js").Message>[];
10
+ description?: string | null | undefined;
11
+ name?: string | null | undefined;
12
+ chat_model?: {
13
+ name?: string | null | undefined;
14
+ temperature?: number | null | undefined;
15
+ provider?: string | null | undefined;
16
+ top_p?: number | null | undefined;
17
+ frequent_penalty?: number | null | undefined;
18
+ presence_penalty?: number | null | undefined;
19
+ } | null | undefined;
11
20
  }>;
12
21
  export declare function loadAgent(path: string): Promise<Agent>;
13
- export declare function loadAIGNEFile(path: string, { readFile: _readFile }?: {
14
- readFile?: typeof readFile;
15
- }): Promise<{
22
+ export declare function loadAIGNEFile(path: string): Promise<{
23
+ description?: string | null | undefined;
16
24
  tools?: string[] | null | undefined;
25
+ name?: string | null | undefined;
17
26
  chat_model?: {
18
27
  name?: string | null | undefined;
19
28
  temperature?: number | null | undefined;
29
+ provider?: string | null | undefined;
20
30
  top_p?: number | null | undefined;
21
31
  frequent_penalty?: number | null | undefined;
22
32
  presence_penalty?: number | null | undefined;
@@ -5,6 +5,8 @@ import { ChatModel } from "../models/chat-model.js";
5
5
  import { type ContextLimits, ExecutionContext, type Runnable } from "./context.js";
6
6
  import { type MessagePayload, MessageQueue, type MessageQueueListener, type Unsubscribe } from "./message-queue.js";
7
7
  export interface ExecutionEngineOptions {
8
+ name?: string;
9
+ description?: string;
8
10
  model?: ChatModel;
9
11
  tools?: Agent[];
10
12
  agents?: Agent[];
@@ -15,6 +17,8 @@ export declare class ExecutionEngine extends EventEmitter {
15
17
  path: string;
16
18
  } & ExecutionEngineOptions): Promise<ExecutionEngine>;
17
19
  constructor(options?: ExecutionEngineOptions);
20
+ name?: string;
21
+ description?: string;
18
22
  readonly messageQueue: MessageQueue;
19
23
  model?: ChatModel;
20
24
  readonly tools: Agent<Message, Message>[] & {
@@ -8,10 +8,12 @@ import { ExecutionContext } from "./context.js";
8
8
  import { MessageQueue, } from "./message-queue.js";
9
9
  export class ExecutionEngine extends EventEmitter {
10
10
  static async load({ path, ...options }) {
11
- const { model, agents, tools } = await load({ path });
11
+ const { model, agents, tools, ...aigne } = await load({ path });
12
12
  return new ExecutionEngine({
13
13
  model,
14
14
  ...options,
15
+ name: options.name || aigne.name || undefined,
16
+ description: options.description || aigne.description || undefined,
15
17
  agents: agents.concat(options.agents ?? []),
16
18
  tools: tools.concat(options.tools ?? []),
17
19
  });
@@ -20,6 +22,8 @@ export class ExecutionEngine extends EventEmitter {
20
22
  if (options)
21
23
  checkArguments("ExecutionEngine", executionEngineOptionsSchema, options);
22
24
  super();
25
+ this.name = options?.name;
26
+ this.description = options?.description;
23
27
  this.model = options?.model;
24
28
  this.limits = options?.limits;
25
29
  if (options?.tools?.length)
@@ -28,6 +32,8 @@ export class ExecutionEngine extends EventEmitter {
28
32
  this.addAgent(...options.agents);
29
33
  this.initProcessExitHandler();
30
34
  }
35
+ name;
36
+ description;
31
37
  messageQueue = new MessageQueue();
32
38
  model;
33
39
  tools = createAccessorArray([], (arr, name) => arr.find((i) => i.name === name));
@@ -1,10 +1,6 @@
1
1
  import { type ZodObject, type ZodType, z } from "zod";
2
2
  import type { Message } from "../agents/agent.js";
3
- export declare function loadAgentFromJsFile(path: string, { import: _import }?: {
4
- import?: (path: string) => Promise<{
5
- default: unknown;
6
- }>;
7
- }): Promise<{
3
+ export declare function loadAgentFromJsFile(path: string): Promise<{
8
4
  name: string;
9
5
  fn: (args_0: Message) => Message;
10
6
  description?: string | undefined;
@@ -16,8 +16,8 @@ const agentJsFileSchema = z.object({
16
16
  .transform((v) => (v ? jsonSchemaToZod(v) : undefined)),
17
17
  fn: z.function(),
18
18
  });
19
- export async function loadAgentFromJsFile(path, { import: _import } = {}) {
20
- const { default: agent } = await tryOrThrow(() => (_import ? _import(path) : import(path)), (error) => new Error(`Failed to load agent definition from ${path}: ${error.message}`));
19
+ export async function loadAgentFromJsFile(path) {
20
+ const { default: agent } = await tryOrThrow(() => import(path), (error) => new Error(`Failed to load agent definition from ${path}: ${error.message}`));
21
21
  if (typeof agent !== "function") {
22
22
  throw new Error(`Agent file ${path} must export a default function, but got ${typeof agent}`);
23
23
  }
@@ -1,8 +1,6 @@
1
- import { readFile } from "node:fs/promises";
2
1
  import { type ZodObject, type ZodType, z } from "zod";
3
- export declare function loadAgentFromYamlFile(path: string, { readFile: _readFile }?: {
4
- readFile?: typeof readFile;
5
- }): Promise<{
2
+ export declare function loadAgentFromYamlFile(path: string): Promise<{
3
+ type: "ai";
6
4
  name: string;
7
5
  description?: string | undefined;
8
6
  tools?: string[] | undefined;
@@ -18,4 +16,9 @@ export declare function loadAgentFromYamlFile(path: string, { readFile: _readFil
18
16
  [x: string]: any;
19
17
  }> | undefined;
20
18
  output_key?: string | undefined;
19
+ } | {
20
+ type: "mcp";
21
+ url?: string | undefined;
22
+ command?: string | undefined;
23
+ args?: string[] | undefined;
21
24
  }>;
@@ -0,0 +1,55 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { jsonSchemaToZod } from "@aigne/json-schema-to-zod";
3
+ import { parse } from "yaml";
4
+ import { z } from "zod";
5
+ import { tryOrThrow } from "../utils/type-utils.js";
6
+ import { inputOutputSchema } from "./schema.js";
7
+ const agentFileSchema = z.discriminatedUnion("type", [
8
+ z.object({
9
+ type: z.literal("ai"),
10
+ name: z.string(),
11
+ description: z
12
+ .string()
13
+ .nullish()
14
+ .transform((v) => v ?? undefined),
15
+ instructions: z
16
+ .string()
17
+ .nullish()
18
+ .transform((v) => v ?? undefined),
19
+ input_schema: inputOutputSchema
20
+ .nullish()
21
+ .transform((v) => (v ? jsonSchemaToZod(v) : undefined)),
22
+ output_schema: inputOutputSchema
23
+ .nullish()
24
+ .transform((v) => (v ? jsonSchemaToZod(v) : undefined)),
25
+ output_key: z
26
+ .string()
27
+ .nullish()
28
+ .transform((v) => v ?? undefined),
29
+ tools: z
30
+ .array(z.string())
31
+ .nullish()
32
+ .transform((v) => v ?? undefined),
33
+ }),
34
+ z.object({
35
+ type: z.literal("mcp"),
36
+ url: z
37
+ .string()
38
+ .nullish()
39
+ .transform((v) => v ?? undefined),
40
+ command: z
41
+ .string()
42
+ .nullish()
43
+ .transform((v) => v ?? undefined),
44
+ args: z
45
+ .array(z.string())
46
+ .nullish()
47
+ .transform((v) => v ?? undefined),
48
+ }),
49
+ ]);
50
+ export async function loadAgentFromYamlFile(path) {
51
+ const raw = await tryOrThrow(() => readFile(path, "utf8"), (error) => new Error(`Failed to load agent definition from ${path}: ${error.message}`));
52
+ const json = await tryOrThrow(() => parse(raw), (error) => new Error(`Failed to parse agent definition from ${path}: ${error.message}`));
53
+ const agent = tryOrThrow(() => agentFileSchema.parse({ ...json, type: json.type ?? "ai" }), (error) => new Error(`Failed to validate agent definition from ${path}: ${error.message}`));
54
+ return agent;
55
+ }
@@ -1,4 +1,3 @@
1
- import { readFile } from "node:fs/promises";
2
1
  import { type Agent } from "../agents/agent.js";
3
2
  import type { ChatModel } from "../models/chat-model.js";
4
3
  export interface LoadOptions {
@@ -8,15 +7,26 @@ export declare function load(options: LoadOptions): Promise<{
8
7
  model: ChatModel | undefined;
9
8
  agents: Agent<import("../agents/agent.js").Message, import("../agents/agent.js").Message>[];
10
9
  tools: Agent<import("../agents/agent.js").Message, import("../agents/agent.js").Message>[];
10
+ description?: string | null | undefined;
11
+ name?: string | null | undefined;
12
+ chat_model?: {
13
+ name?: string | null | undefined;
14
+ temperature?: number | null | undefined;
15
+ provider?: string | null | undefined;
16
+ top_p?: number | null | undefined;
17
+ frequent_penalty?: number | null | undefined;
18
+ presence_penalty?: number | null | undefined;
19
+ } | null | undefined;
11
20
  }>;
12
21
  export declare function loadAgent(path: string): Promise<Agent>;
13
- export declare function loadAIGNEFile(path: string, { readFile: _readFile }?: {
14
- readFile?: typeof readFile;
15
- }): Promise<{
22
+ export declare function loadAIGNEFile(path: string): Promise<{
23
+ description?: string | null | undefined;
16
24
  tools?: string[] | null | undefined;
25
+ name?: string | null | undefined;
17
26
  chat_model?: {
18
27
  name?: string | null | undefined;
19
28
  temperature?: number | null | undefined;
29
+ provider?: string | null | undefined;
20
30
  top_p?: number | null | undefined;
21
31
  frequent_penalty?: number | null | undefined;
22
32
  presence_penalty?: number | null | undefined;
@@ -1,22 +1,27 @@
1
- import { readFile } from "node:fs/promises";
1
+ import { exists, readFile, stat } from "node:fs/promises";
2
2
  import { dirname, extname, join } from "node:path";
3
3
  import { parse } from "yaml";
4
4
  import { z } from "zod";
5
5
  import { FunctionAgent } from "../agents/agent.js";
6
6
  import { AIAgent } from "../agents/ai-agent.js";
7
+ import { MCPAgent } from "../agents/mcp-agent.js";
8
+ import { ClaudeChatModel } from "../models/claude-chat-model.js";
7
9
  import { OpenAIChatModel } from "../models/openai-chat-model.js";
10
+ import { XAIChatModel } from "../models/xai-chat-model.js";
8
11
  import { tryOrThrow } from "../utils/type-utils.js";
9
- import { loadAgentFromYamlFile } from "./ai-agent.js";
10
- import { loadAgentFromJsFile } from "./function-agent.js";
11
- const AIGNE_FILE_NAME = "aigne.yaml";
12
+ import { loadAgentFromJsFile } from "./agent-js.js";
13
+ import { loadAgentFromYamlFile } from "./agent-yaml.js";
14
+ const DEFAULT_MODEL_PROVIDER = "openai";
15
+ const AIGNE_FILE_NAME = ["aigne.yaml", "aigne.yml"];
12
16
  export async function load(options) {
13
17
  const { path } = options;
14
- const aigneFilePath = path.endsWith(AIGNE_FILE_NAME) ? path : join(path, AIGNE_FILE_NAME);
18
+ const aigneFilePath = await getAIGNEFilePath(path);
15
19
  const rootDir = dirname(aigneFilePath);
16
20
  const aigne = await loadAIGNEFile(aigneFilePath);
17
21
  const agents = await Promise.all((aigne.agents ?? []).map((filename) => loadAgent(join(rootDir, filename))));
18
22
  const tools = await Promise.all((aigne.tools ?? []).map((filename) => loadAgent(join(rootDir, filename))));
19
23
  return {
24
+ ...aigne,
20
25
  model: await loadModel(aigne.chat_model),
21
26
  agents,
22
27
  tools,
@@ -35,15 +40,31 @@ export async function loadAgent(path) {
35
40
  }
36
41
  if (extname(path) === ".yaml" || extname(path) === ".yml") {
37
42
  const agent = await loadAgentFromYamlFile(path);
38
- return AIAgent.from({
39
- name: agent.name,
40
- description: agent.description,
41
- instructions: agent.instructions,
42
- inputSchema: agent.input_schema,
43
- outputSchema: agent.output_schema,
44
- outputKey: agent.output_key,
45
- tools: await Promise.all((agent.tools ?? []).map((filename) => loadAgent(join(dirname(path), filename)))),
46
- });
43
+ if (agent.type === "ai") {
44
+ return AIAgent.from({
45
+ name: agent.name,
46
+ description: agent.description,
47
+ instructions: agent.instructions,
48
+ inputSchema: agent.input_schema,
49
+ outputSchema: agent.output_schema,
50
+ outputKey: agent.output_key,
51
+ tools: await Promise.all((agent.tools ?? []).map((filename) => loadAgent(join(dirname(path), filename)))),
52
+ });
53
+ }
54
+ if (agent.type === "mcp") {
55
+ if (agent.url) {
56
+ return MCPAgent.from({
57
+ url: agent.url,
58
+ });
59
+ }
60
+ if (agent.command) {
61
+ return MCPAgent.from({
62
+ command: agent.command,
63
+ args: agent.args,
64
+ });
65
+ }
66
+ throw new Error(`Missing url or command in mcp agent: ${path}`);
67
+ }
47
68
  }
48
69
  throw new Error(`Unsupported agent file type: ${path}`);
49
70
  }
@@ -57,17 +78,20 @@ async function loadModel(model) {
57
78
  frequencyPenalty: model.frequent_penalty ?? undefined,
58
79
  presencePenalty: model.presence_penalty ?? undefined,
59
80
  };
60
- // TODO: add support for other models such as AutoChatModel, ClaudeChatModel, etc.
61
- if (/^o1|gpt-/.test(model.name)) {
62
- return new OpenAIChatModel(params);
63
- }
64
- throw new Error(`Unsupported model: ${model.name}`);
81
+ const availableModels = [OpenAIChatModel, ClaudeChatModel, XAIChatModel];
82
+ const M = availableModels.find((m) => m.name.toLowerCase().includes(model.provider || DEFAULT_MODEL_PROVIDER));
83
+ if (!M)
84
+ throw new Error(`Unsupported model: ${model.provider} ${model.name}`);
85
+ return new M(params);
65
86
  }
66
87
  const aigneFileSchema = z.object({
88
+ name: z.string().nullish(),
89
+ description: z.string().nullish(),
67
90
  chat_model: z
68
91
  .union([
69
92
  z.string(),
70
93
  z.object({
94
+ provider: z.string().nullish(),
71
95
  name: z.string().nullish(),
72
96
  temperature: z.number().min(0).max(2).nullish(),
73
97
  top_p: z.number().min(0).nullish(),
@@ -80,9 +104,20 @@ const aigneFileSchema = z.object({
80
104
  agents: z.array(z.string()).nullish(),
81
105
  tools: z.array(z.string()).nullish(),
82
106
  });
83
- export async function loadAIGNEFile(path, { readFile: _readFile = readFile } = {}) {
84
- const raw = await tryOrThrow(() => _readFile(path, "utf8"), (error) => new Error(`Failed to load aigne.yaml from ${path}: ${error.message}`));
107
+ export async function loadAIGNEFile(path) {
108
+ const raw = await tryOrThrow(() => readFile(path, "utf8"), (error) => new Error(`Failed to load aigne.yaml from ${path}: ${error.message}`));
85
109
  const json = await tryOrThrow(() => parse(raw), (error) => new Error(`Failed to parse aigne.yaml from ${path}: ${error.message}`));
86
110
  const agent = tryOrThrow(() => aigneFileSchema.parse(json), (error) => new Error(`Failed to validate aigne.yaml from ${path}: ${error.message}`));
87
111
  return agent;
88
112
  }
113
+ async function getAIGNEFilePath(path) {
114
+ const s = await stat(path);
115
+ if (s.isDirectory()) {
116
+ for (const file of AIGNE_FILE_NAME) {
117
+ const filePath = join(path, file);
118
+ if (await exists(filePath))
119
+ return filePath;
120
+ }
121
+ }
122
+ return path;
123
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aigne/core",
3
- "version": "1.5.1-2",
3
+ "version": "1.6.0",
4
4
  "description": "AIGNE core library for building AI-powered applications",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -83,20 +83,21 @@
83
83
  "@types/bun": "^1.2.8",
84
84
  "@types/express": "^5.0.1",
85
85
  "@types/mustache": "^4.2.5",
86
- "@types/node": "^22.13.17",
86
+ "@types/node": "^22.14.0",
87
87
  "detect-port": "^2.1.0",
88
88
  "express": "^5.1.0",
89
89
  "npm-run-all": "^4.1.5",
90
90
  "openai": "^4.91.1",
91
91
  "rimraf": "^6.0.1",
92
- "typescript": "^5.8.2"
92
+ "typescript": "^5.8.2",
93
+ "@aigne/test-utils": "^1.0.0"
93
94
  },
94
95
  "scripts": {
95
96
  "lint": "tsc --noEmit",
96
97
  "build": "tsc --build scripts/tsconfig.build.json",
97
98
  "clean": "rimraf lib test/coverage",
98
- "test": "cd test && bun test",
99
- "test:coverage": "cd test && bun test --coverage --coverage-reporter=lcov --coverage-reporter=text",
99
+ "test": "bun --cwd test test",
100
+ "test:coverage": "bun --cwd test test --coverage --coverage-reporter=lcov --coverage-reporter=text",
100
101
  "postbuild": "echo '{\"type\": \"module\"}' > lib/esm/package.json && echo '{\"type\": \"commonjs\"}' > lib/cjs/package.json"
101
102
  }
102
103
  }
@@ -1,40 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.loadAgentFromYamlFile = loadAgentFromYamlFile;
4
- const promises_1 = require("node:fs/promises");
5
- const json_schema_to_zod_1 = require("@aigne/json-schema-to-zod");
6
- const yaml_1 = require("yaml");
7
- const zod_1 = require("zod");
8
- const type_utils_js_1 = require("../utils/type-utils.js");
9
- const schema_js_1 = require("./schema.js");
10
- const agentFileSchema = zod_1.z.object({
11
- name: zod_1.z.string(),
12
- description: zod_1.z
13
- .string()
14
- .nullish()
15
- .transform((v) => v ?? undefined),
16
- instructions: zod_1.z
17
- .string()
18
- .nullish()
19
- .transform((v) => v ?? undefined),
20
- input_schema: schema_js_1.inputOutputSchema
21
- .nullish()
22
- .transform((v) => (v ? (0, json_schema_to_zod_1.jsonSchemaToZod)(v) : undefined)),
23
- output_schema: schema_js_1.inputOutputSchema
24
- .nullish()
25
- .transform((v) => (v ? (0, json_schema_to_zod_1.jsonSchemaToZod)(v) : undefined)),
26
- output_key: zod_1.z
27
- .string()
28
- .nullish()
29
- .transform((v) => v ?? undefined),
30
- tools: zod_1.z
31
- .array(zod_1.z.string())
32
- .nullish()
33
- .transform((v) => v ?? undefined),
34
- });
35
- async function loadAgentFromYamlFile(path, { readFile: _readFile = promises_1.readFile } = {}) {
36
- const raw = await (0, type_utils_js_1.tryOrThrow)(() => _readFile(path, "utf8"), (error) => new Error(`Failed to load agent definition from ${path}: ${error.message}`));
37
- const json = await (0, type_utils_js_1.tryOrThrow)(() => (0, yaml_1.parse)(raw), (error) => new Error(`Failed to parse agent definition from ${path}: ${error.message}`));
38
- const agent = (0, type_utils_js_1.tryOrThrow)(() => agentFileSchema.parse(json), (error) => new Error(`Failed to validate agent definition from ${path}: ${error.message}`));
39
- return agent;
40
- }
@@ -1,37 +0,0 @@
1
- import { readFile } from "node:fs/promises";
2
- import { jsonSchemaToZod } from "@aigne/json-schema-to-zod";
3
- import { parse } from "yaml";
4
- import { z } from "zod";
5
- import { tryOrThrow } from "../utils/type-utils.js";
6
- import { inputOutputSchema } from "./schema.js";
7
- const agentFileSchema = z.object({
8
- name: z.string(),
9
- description: z
10
- .string()
11
- .nullish()
12
- .transform((v) => v ?? undefined),
13
- instructions: z
14
- .string()
15
- .nullish()
16
- .transform((v) => v ?? undefined),
17
- input_schema: inputOutputSchema
18
- .nullish()
19
- .transform((v) => (v ? jsonSchemaToZod(v) : undefined)),
20
- output_schema: inputOutputSchema
21
- .nullish()
22
- .transform((v) => (v ? jsonSchemaToZod(v) : undefined)),
23
- output_key: z
24
- .string()
25
- .nullish()
26
- .transform((v) => v ?? undefined),
27
- tools: z
28
- .array(z.string())
29
- .nullish()
30
- .transform((v) => v ?? undefined),
31
- });
32
- export async function loadAgentFromYamlFile(path, { readFile: _readFile = readFile } = {}) {
33
- const raw = await tryOrThrow(() => _readFile(path, "utf8"), (error) => new Error(`Failed to load agent definition from ${path}: ${error.message}`));
34
- const json = await tryOrThrow(() => parse(raw), (error) => new Error(`Failed to parse agent definition from ${path}: ${error.message}`));
35
- const agent = tryOrThrow(() => agentFileSchema.parse(json), (error) => new Error(`Failed to validate agent definition from ${path}: ${error.message}`));
36
- return agent;
37
- }