@aigne/core 1.57.2 → 1.57.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.57.3](https://github.com/AIGNE-io/aigne-framework/compare/core-v1.57.2...core-v1.57.3) (2025-08-30)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **core:** improve nested prompt file resolution ([#437](https://github.com/AIGNE-io/aigne-framework/issues/437)) ([38b5b13](https://github.com/AIGNE-io/aigne-framework/commit/38b5b1397b7897cddef39d60c8cae2152e37dc5b))
9
+
3
10
  ## [1.57.2](https://github.com/AIGNE-io/aigne-framework/compare/core-v1.57.1...core-v1.57.2) (2025-08-29)
4
11
 
5
12
 
@@ -32,16 +32,20 @@ export interface BaseAgentSchema {
32
32
  subscribeTopic?: string[];
33
33
  };
34
34
  }
35
+ export type Instructions = {
36
+ content: string;
37
+ path: string;
38
+ };
35
39
  export interface AIAgentSchema extends BaseAgentSchema {
36
40
  type: "ai";
37
- instructions?: string;
41
+ instructions?: Instructions;
38
42
  inputKey?: string;
39
43
  outputKey?: string;
40
44
  toolChoice?: AIAgentToolChoice;
41
45
  }
42
46
  export interface ImageAgentSchema extends BaseAgentSchema {
43
47
  type: "image";
44
- instructions: string;
48
+ instructions: Instructions;
45
49
  modelOptions?: Record<string, any>;
46
50
  }
47
51
  export interface MCPAgentSchema extends BaseAgentSchema {
@@ -58,8 +58,8 @@ async function parseAgentFile(path, data) {
58
58
  }),
59
59
  ])
60
60
  .transform((v) => typeof v === "string"
61
- ? v
62
- : v && index_js_1.nodejs.fs.readFile(index_js_1.nodejs.path.join(index_js_1.nodejs.path.dirname(path), v.url), "utf8"));
61
+ ? { content: v, path }
62
+ : Promise.resolve(index_js_1.nodejs.path.join(index_js_1.nodejs.path.dirname(path), v.url)).then((path) => index_js_1.nodejs.fs.readFile(path, "utf8").then((content) => ({ content, path }))));
63
63
  return (0, schema_js_1.camelizeSchema)(zod_1.z.discriminatedUnion("type", [
64
64
  zod_1.z
65
65
  .object({
@@ -14,9 +14,7 @@ export interface LoadOptions {
14
14
  key?: string | number;
15
15
  }
16
16
  export declare function load(path: string, options?: LoadOptions): Promise<AIGNEOptions>;
17
- export declare function loadAgent(path: string, options?: LoadOptions & {
18
- rootDir?: string;
19
- }, agentOptions?: AgentOptions): Promise<Agent>;
17
+ export declare function loadAgent(path: string, options?: LoadOptions, agentOptions?: AgentOptions): Promise<Agent>;
20
18
  declare const aigneFileSchema: z.ZodObject<{
21
19
  name: z.ZodType<string | undefined, z.ZodTypeDef, string | undefined>;
22
20
  description: z.ZodType<string | undefined, z.ZodTypeDef, string | undefined>;
@@ -22,10 +22,7 @@ async function load(path, options = {}) {
22
22
  options.key ??= Date.now();
23
23
  const { aigne, rootDir } = await loadAIGNEFile(path);
24
24
  const allAgentPaths = new Set((0, type_utils_js_1.flat)(aigne.agents, aigne.skills, aigne.mcpServer?.agents, aigne.cli?.agents, aigne.cli?.chat).map((i) => index_js_1.nodejs.path.join(rootDir, i)));
25
- const allAgents = Object.fromEntries(await Promise.all(Array.from(allAgentPaths).map(async (path) => [
26
- path,
27
- await loadAgent(path, { ...options, rootDir }),
28
- ])));
25
+ const allAgents = Object.fromEntries(await Promise.all(Array.from(allAgentPaths).map(async (path) => [path, await loadAgent(path, options)])));
29
26
  const pickAgents = (paths) => paths.map((filename) => allAgents[index_js_1.nodejs.path.join(rootDir, filename)]).filter(type_utils_js_1.isNonNullable);
30
27
  return {
31
28
  ...aigne,
@@ -88,7 +85,6 @@ async function parseHooks(path, hooks, options) {
88
85
  })));
89
86
  }
90
87
  async function parseAgent(path, agent, options, agentOptions) {
91
- const workingDir = options?.rootDir ?? index_js_1.nodejs.path.dirname(path);
92
88
  const skills = "skills" in agent
93
89
  ? agent.skills &&
94
90
  (await Promise.all(agent.skills.map((skill) => loadNestAgent(path, skill, options))))
@@ -106,17 +102,25 @@ async function parseAgent(path, agent, options, agentOptions) {
106
102
  ...[agentOptions?.hooks].flat().filter(type_utils_js_1.isNonNullable),
107
103
  ],
108
104
  };
105
+ let instructions;
106
+ if ("instructions" in agent && agent.instructions) {
107
+ instructions = prompt_builder_js_1.PromptBuilder.from(agent.instructions.content, {
108
+ workingDir: index_js_1.nodejs.path.dirname(agent.instructions.path),
109
+ });
110
+ }
109
111
  switch (agent.type) {
110
112
  case "ai": {
111
113
  return ai_agent_js_1.AIAgent.from({
112
114
  ...baseOptions,
113
- instructions: agent.instructions && prompt_builder_js_1.PromptBuilder.from(agent.instructions, { workingDir }),
115
+ instructions,
114
116
  });
115
117
  }
116
118
  case "image": {
119
+ if (!instructions)
120
+ throw new Error(`Missing required instructions for image agent at path: ${path}`);
117
121
  return image_agent_js_1.ImageAgent.from({
118
122
  ...baseOptions,
119
- instructions: prompt_builder_js_1.PromptBuilder.from(agent.instructions, { workingDir }),
123
+ instructions,
120
124
  });
121
125
  }
122
126
  case "mcp": {
@@ -17,7 +17,6 @@ export declare class CustomLoader extends nunjucks.Loader {
17
17
  workingDir: string;
18
18
  });
19
19
  async: boolean;
20
- private findFile;
21
20
  getSource(name: string, callback: Callback<Error, LoaderSource>): LoaderSource;
22
21
  }
23
22
  export declare class ChatMessageTemplate {
@@ -38,7 +38,6 @@ class PromptTemplate {
38
38
  }
39
39
  }
40
40
  exports.PromptTemplate = PromptTemplate;
41
- const IGNORED_PROMPT_DIRS = ["node_modules", ".git"];
42
41
  class CustomLoader extends nunjucks_1.default.Loader {
43
42
  options;
44
43
  constructor(options) {
@@ -46,39 +45,20 @@ class CustomLoader extends nunjucks_1.default.Loader {
46
45
  this.options = options;
47
46
  }
48
47
  async = true;
49
- async findFile(dir, name) {
50
- name = name.replace(/^[./]*/, "");
51
- const files = await index_js_1.nodejs.fs.readdir(dir, { withFileTypes: true });
52
- const file = files.find((f) => index_js_1.nodejs.path.join(f.parentPath, f.name).endsWith(name));
53
- if (file)
54
- return index_js_1.nodejs.path.join(file.parentPath, file.name);
55
- for (const entry of files) {
56
- if (entry.isDirectory()) {
57
- if (IGNORED_PROMPT_DIRS.includes(entry.name))
58
- continue;
59
- const result = await this.findFile(index_js_1.nodejs.path.join(entry.parentPath, entry.name), name);
60
- if (result)
61
- return result;
62
- }
63
- }
64
- }
65
48
  getSource(name, callback) {
66
49
  let result = null;
67
- this.findFile(this.options.workingDir, name).then((path) => {
68
- if (!path) {
69
- callback(new Error(`Template not found: ${name}`), null);
70
- return;
71
- }
72
- return index_js_1.nodejs.fs.readFile(path, "utf-8").then((content) => {
73
- result = {
74
- src: content,
75
- path: name,
76
- noCache: true,
77
- };
78
- callback(null, result);
79
- }, (error) => {
80
- callback(error, null);
81
- });
50
+ const path = index_js_1.nodejs.path.isAbsolute(name)
51
+ ? name
52
+ : index_js_1.nodejs.path.join(this.options.workingDir, name);
53
+ index_js_1.nodejs.fs.readFile(path, "utf-8").then((content) => {
54
+ result = {
55
+ src: content,
56
+ path,
57
+ noCache: true,
58
+ };
59
+ callback(null, result);
60
+ }, (error) => {
61
+ callback(error, null);
82
62
  });
83
63
  // nunjucks expects return LoaderSource synchronously, but we handle it asynchronously.
84
64
  return result;
@@ -32,16 +32,20 @@ export interface BaseAgentSchema {
32
32
  subscribeTopic?: string[];
33
33
  };
34
34
  }
35
+ export type Instructions = {
36
+ content: string;
37
+ path: string;
38
+ };
35
39
  export interface AIAgentSchema extends BaseAgentSchema {
36
40
  type: "ai";
37
- instructions?: string;
41
+ instructions?: Instructions;
38
42
  inputKey?: string;
39
43
  outputKey?: string;
40
44
  toolChoice?: AIAgentToolChoice;
41
45
  }
42
46
  export interface ImageAgentSchema extends BaseAgentSchema {
43
47
  type: "image";
44
- instructions: string;
48
+ instructions: Instructions;
45
49
  modelOptions?: Record<string, any>;
46
50
  }
47
51
  export interface MCPAgentSchema extends BaseAgentSchema {
@@ -14,9 +14,7 @@ export interface LoadOptions {
14
14
  key?: string | number;
15
15
  }
16
16
  export declare function load(path: string, options?: LoadOptions): Promise<AIGNEOptions>;
17
- export declare function loadAgent(path: string, options?: LoadOptions & {
18
- rootDir?: string;
19
- }, agentOptions?: AgentOptions): Promise<Agent>;
17
+ export declare function loadAgent(path: string, options?: LoadOptions, agentOptions?: AgentOptions): Promise<Agent>;
20
18
  declare const aigneFileSchema: z.ZodObject<{
21
19
  name: z.ZodType<string | undefined, z.ZodTypeDef, string | undefined>;
22
20
  description: z.ZodType<string | undefined, z.ZodTypeDef, string | undefined>;
@@ -17,7 +17,6 @@ export declare class CustomLoader extends nunjucks.Loader {
17
17
  workingDir: string;
18
18
  });
19
19
  async: boolean;
20
- private findFile;
21
20
  getSource(name: string, callback: Callback<Error, LoaderSource>): LoaderSource;
22
21
  }
23
22
  export declare class ChatMessageTemplate {
@@ -32,16 +32,20 @@ export interface BaseAgentSchema {
32
32
  subscribeTopic?: string[];
33
33
  };
34
34
  }
35
+ export type Instructions = {
36
+ content: string;
37
+ path: string;
38
+ };
35
39
  export interface AIAgentSchema extends BaseAgentSchema {
36
40
  type: "ai";
37
- instructions?: string;
41
+ instructions?: Instructions;
38
42
  inputKey?: string;
39
43
  outputKey?: string;
40
44
  toolChoice?: AIAgentToolChoice;
41
45
  }
42
46
  export interface ImageAgentSchema extends BaseAgentSchema {
43
47
  type: "image";
44
- instructions: string;
48
+ instructions: Instructions;
45
49
  modelOptions?: Record<string, any>;
46
50
  }
47
51
  export interface MCPAgentSchema extends BaseAgentSchema {
@@ -54,8 +54,8 @@ export async function parseAgentFile(path, data) {
54
54
  }),
55
55
  ])
56
56
  .transform((v) => typeof v === "string"
57
- ? v
58
- : v && nodejs.fs.readFile(nodejs.path.join(nodejs.path.dirname(path), v.url), "utf8"));
57
+ ? { content: v, path }
58
+ : Promise.resolve(nodejs.path.join(nodejs.path.dirname(path), v.url)).then((path) => nodejs.fs.readFile(path, "utf8").then((content) => ({ content, path }))));
59
59
  return camelizeSchema(z.discriminatedUnion("type", [
60
60
  z
61
61
  .object({
@@ -14,9 +14,7 @@ export interface LoadOptions {
14
14
  key?: string | number;
15
15
  }
16
16
  export declare function load(path: string, options?: LoadOptions): Promise<AIGNEOptions>;
17
- export declare function loadAgent(path: string, options?: LoadOptions & {
18
- rootDir?: string;
19
- }, agentOptions?: AgentOptions): Promise<Agent>;
17
+ export declare function loadAgent(path: string, options?: LoadOptions, agentOptions?: AgentOptions): Promise<Agent>;
20
18
  declare const aigneFileSchema: z.ZodObject<{
21
19
  name: z.ZodType<string | undefined, z.ZodTypeDef, string | undefined>;
22
20
  description: z.ZodType<string | undefined, z.ZodTypeDef, string | undefined>;
@@ -17,10 +17,7 @@ export async function load(path, options = {}) {
17
17
  options.key ??= Date.now();
18
18
  const { aigne, rootDir } = await loadAIGNEFile(path);
19
19
  const allAgentPaths = new Set(flat(aigne.agents, aigne.skills, aigne.mcpServer?.agents, aigne.cli?.agents, aigne.cli?.chat).map((i) => nodejs.path.join(rootDir, i)));
20
- const allAgents = Object.fromEntries(await Promise.all(Array.from(allAgentPaths).map(async (path) => [
21
- path,
22
- await loadAgent(path, { ...options, rootDir }),
23
- ])));
20
+ const allAgents = Object.fromEntries(await Promise.all(Array.from(allAgentPaths).map(async (path) => [path, await loadAgent(path, options)])));
24
21
  const pickAgents = (paths) => paths.map((filename) => allAgents[nodejs.path.join(rootDir, filename)]).filter(isNonNullable);
25
22
  return {
26
23
  ...aigne,
@@ -83,7 +80,6 @@ async function parseHooks(path, hooks, options) {
83
80
  })));
84
81
  }
85
82
  async function parseAgent(path, agent, options, agentOptions) {
86
- const workingDir = options?.rootDir ?? nodejs.path.dirname(path);
87
83
  const skills = "skills" in agent
88
84
  ? agent.skills &&
89
85
  (await Promise.all(agent.skills.map((skill) => loadNestAgent(path, skill, options))))
@@ -101,17 +97,25 @@ async function parseAgent(path, agent, options, agentOptions) {
101
97
  ...[agentOptions?.hooks].flat().filter(isNonNullable),
102
98
  ],
103
99
  };
100
+ let instructions;
101
+ if ("instructions" in agent && agent.instructions) {
102
+ instructions = PromptBuilder.from(agent.instructions.content, {
103
+ workingDir: nodejs.path.dirname(agent.instructions.path),
104
+ });
105
+ }
104
106
  switch (agent.type) {
105
107
  case "ai": {
106
108
  return AIAgent.from({
107
109
  ...baseOptions,
108
- instructions: agent.instructions && PromptBuilder.from(agent.instructions, { workingDir }),
110
+ instructions,
109
111
  });
110
112
  }
111
113
  case "image": {
114
+ if (!instructions)
115
+ throw new Error(`Missing required instructions for image agent at path: ${path}`);
112
116
  return ImageAgent.from({
113
117
  ...baseOptions,
114
- instructions: PromptBuilder.from(agent.instructions, { workingDir }),
118
+ instructions,
115
119
  });
116
120
  }
117
121
  case "mcp": {
@@ -17,7 +17,6 @@ export declare class CustomLoader extends nunjucks.Loader {
17
17
  workingDir: string;
18
18
  });
19
19
  async: boolean;
20
- private findFile;
21
20
  getSource(name: string, callback: Callback<Error, LoaderSource>): LoaderSource;
22
21
  }
23
22
  export declare class ChatMessageTemplate {
@@ -30,7 +30,6 @@ export class PromptTemplate {
30
30
  }));
31
31
  }
32
32
  }
33
- const IGNORED_PROMPT_DIRS = ["node_modules", ".git"];
34
33
  export class CustomLoader extends nunjucks.Loader {
35
34
  options;
36
35
  constructor(options) {
@@ -38,39 +37,20 @@ export class CustomLoader extends nunjucks.Loader {
38
37
  this.options = options;
39
38
  }
40
39
  async = true;
41
- async findFile(dir, name) {
42
- name = name.replace(/^[./]*/, "");
43
- const files = await nodejs.fs.readdir(dir, { withFileTypes: true });
44
- const file = files.find((f) => nodejs.path.join(f.parentPath, f.name).endsWith(name));
45
- if (file)
46
- return nodejs.path.join(file.parentPath, file.name);
47
- for (const entry of files) {
48
- if (entry.isDirectory()) {
49
- if (IGNORED_PROMPT_DIRS.includes(entry.name))
50
- continue;
51
- const result = await this.findFile(nodejs.path.join(entry.parentPath, entry.name), name);
52
- if (result)
53
- return result;
54
- }
55
- }
56
- }
57
40
  getSource(name, callback) {
58
41
  let result = null;
59
- this.findFile(this.options.workingDir, name).then((path) => {
60
- if (!path) {
61
- callback(new Error(`Template not found: ${name}`), null);
62
- return;
63
- }
64
- return nodejs.fs.readFile(path, "utf-8").then((content) => {
65
- result = {
66
- src: content,
67
- path: name,
68
- noCache: true,
69
- };
70
- callback(null, result);
71
- }, (error) => {
72
- callback(error, null);
73
- });
42
+ const path = nodejs.path.isAbsolute(name)
43
+ ? name
44
+ : nodejs.path.join(this.options.workingDir, name);
45
+ nodejs.fs.readFile(path, "utf-8").then((content) => {
46
+ result = {
47
+ src: content,
48
+ path,
49
+ noCache: true,
50
+ };
51
+ callback(null, result);
52
+ }, (error) => {
53
+ callback(error, null);
74
54
  });
75
55
  // nunjucks expects return LoaderSource synchronously, but we handle it asynchronously.
76
56
  return result;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aigne/core",
3
- "version": "1.57.2",
3
+ "version": "1.57.3",
4
4
  "description": "The functional core of agentic AI",
5
5
  "publishConfig": {
6
6
  "access": "public"