@aigne/core 1.70.1-beta → 1.71.0-beta

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 (49) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/lib/cjs/agents/agent.d.ts +4 -0
  3. package/lib/cjs/agents/agent.js +3 -0
  4. package/lib/cjs/agents/image-agent.js +2 -2
  5. package/lib/cjs/agents/types.d.ts +9 -1
  6. package/lib/cjs/loader/agent-js.d.ts +1 -2
  7. package/lib/cjs/loader/agent-js.js +2 -2
  8. package/lib/cjs/loader/agent-yaml.d.ts +19 -3
  9. package/lib/cjs/loader/agent-yaml.js +151 -110
  10. package/lib/cjs/loader/index.d.ts +10 -1
  11. package/lib/cjs/loader/index.js +42 -17
  12. package/lib/cjs/prompt/prompt-builder.d.ts +3 -3
  13. package/lib/cjs/prompt/prompt-builder.js +8 -2
  14. package/lib/cjs/prompt/skills/afs.js +41 -4
  15. package/lib/cjs/prompt/template.d.ts +1 -0
  16. package/lib/cjs/prompt/template.js +5 -3
  17. package/lib/cjs/utils/agent-utils.d.ts +3 -2
  18. package/lib/cjs/utils/agent-utils.js +7 -0
  19. package/lib/cjs/utils/token-estimator.d.ts +9 -0
  20. package/lib/cjs/utils/token-estimator.js +66 -0
  21. package/lib/dts/agents/agent.d.ts +4 -0
  22. package/lib/dts/agents/types.d.ts +9 -1
  23. package/lib/dts/loader/agent-js.d.ts +1 -2
  24. package/lib/dts/loader/agent-yaml.d.ts +19 -3
  25. package/lib/dts/loader/index.d.ts +10 -1
  26. package/lib/dts/prompt/prompt-builder.d.ts +3 -3
  27. package/lib/dts/prompt/template.d.ts +1 -0
  28. package/lib/dts/utils/agent-utils.d.ts +3 -2
  29. package/lib/dts/utils/token-estimator.d.ts +9 -0
  30. package/lib/esm/agents/agent.d.ts +4 -0
  31. package/lib/esm/agents/agent.js +3 -0
  32. package/lib/esm/agents/image-agent.js +2 -2
  33. package/lib/esm/agents/types.d.ts +9 -1
  34. package/lib/esm/loader/agent-js.d.ts +1 -2
  35. package/lib/esm/loader/agent-js.js +2 -2
  36. package/lib/esm/loader/agent-yaml.d.ts +19 -3
  37. package/lib/esm/loader/agent-yaml.js +147 -110
  38. package/lib/esm/loader/index.d.ts +10 -1
  39. package/lib/esm/loader/index.js +41 -19
  40. package/lib/esm/prompt/prompt-builder.d.ts +3 -3
  41. package/lib/esm/prompt/prompt-builder.js +8 -2
  42. package/lib/esm/prompt/skills/afs.js +41 -4
  43. package/lib/esm/prompt/template.d.ts +1 -0
  44. package/lib/esm/prompt/template.js +5 -3
  45. package/lib/esm/utils/agent-utils.d.ts +3 -2
  46. package/lib/esm/utils/agent-utils.js +6 -0
  47. package/lib/esm/utils/token-estimator.d.ts +9 -0
  48. package/lib/esm/utils/token-estimator.js +63 -0
  49. package/package.json +4 -4
@@ -9,6 +9,68 @@ import { tryOrThrow } from "../utils/type-utils.js";
9
9
  import { codeToFunctionAgentFn } from "./function-agent.js";
10
10
  import { camelizeSchema, chatModelSchema, defaultInputSchema, imageModelSchema, inputOutputSchema, optionalize, } from "./schema.js";
11
11
  export async function parseAgentFile(path, data) {
12
+ const agentSchema = getAgentSchema({ filepath: path });
13
+ return agentSchema.parseAsync({
14
+ ...data,
15
+ model: data.model || data.chatModel || data.chat_model,
16
+ });
17
+ }
18
+ export async function loadAgentFromYamlFile(path, options) {
19
+ const raw = await tryOrThrow(() => nodejs.fs.readFile(path, "utf8"), (error) => new Error(`Failed to load agent definition from ${path}: ${error.message}`));
20
+ const json = tryOrThrow(() => parse(raw), (error) => new Error(`Failed to parse agent definition from ${path}: ${error.message}`));
21
+ if (!["ai", "image", "mcp", "team", "transform", "function"].includes(json?.type)) {
22
+ if (typeof json?.type === "string") {
23
+ if (!options?.require)
24
+ throw new Error(`Module loader is not provided to load agent type module ${json.type} from ${path}`);
25
+ const Mod = await options.require(json.type, { parent: path });
26
+ if (typeof Mod?.default?.prototype?.constructor !== "function") {
27
+ throw new Error(`The agent type module ${json.type} does not export a default Agent class`);
28
+ }
29
+ json.agentClass = Mod.default;
30
+ }
31
+ }
32
+ const agent = await tryOrThrow(async () => await parseAgentFile(path, {
33
+ ...json,
34
+ type: json.type ?? "ai",
35
+ skills: json.skills ?? json.tools,
36
+ }), (error) => new Error(`Failed to validate agent definition from ${path}: ${error.message}`));
37
+ return agent;
38
+ }
39
+ const instructionItemSchema = z.union([
40
+ z.object({
41
+ role: roleSchema.default("system"),
42
+ url: z.string(),
43
+ }),
44
+ z.object({
45
+ role: roleSchema.default("system"),
46
+ content: z.string(),
47
+ }),
48
+ ]);
49
+ const parseInstructionItem = ({ filepath }) => async ({ role, ...v }) => {
50
+ if (role === "tool")
51
+ throw new Error(`'tool' role is not allowed in instruction item in agent file ${filepath}`);
52
+ if ("content" in v && typeof v.content === "string") {
53
+ return { role, content: v.content, path: filepath };
54
+ }
55
+ if ("url" in v && typeof v.url === "string") {
56
+ const url = nodejs.path.isAbsolute(v.url)
57
+ ? v.url
58
+ : nodejs.path.join(nodejs.path.dirname(filepath), v.url);
59
+ return nodejs.fs.readFile(url, "utf8").then((content) => ({ role, content, path: url }));
60
+ }
61
+ throw new Error(`Invalid instruction item in agent file ${filepath}. Expected 'content' or 'url' property`);
62
+ };
63
+ export const getInstructionsSchema = ({ filepath }) => z
64
+ .union([z.string(), instructionItemSchema, z.array(instructionItemSchema)])
65
+ .transform(async (v) => {
66
+ if (typeof v === "string")
67
+ return [{ role: "system", content: v, path: filepath }];
68
+ if (Array.isArray(v)) {
69
+ return Promise.all(v.map((item) => parseInstructionItem({ filepath })(item)));
70
+ }
71
+ return [await parseInstructionItem({ filepath })(v)];
72
+ });
73
+ export const getAgentSchema = ({ filepath }) => {
12
74
  const agentSchema = z.lazy(() => {
13
75
  const nestAgentSchema = z.lazy(() => z.union([
14
76
  agentSchema,
@@ -37,9 +99,9 @@ export async function parseAgentFile(path, data) {
37
99
  imageModel: optionalize(imageModelSchema),
38
100
  taskTitle: optionalize(z.string()),
39
101
  taskRenderMode: optionalize(z.union([z.literal("hide"), z.literal("collapse")])),
40
- inputSchema: optionalize(inputOutputSchema({ path })).transform((v) => v ? jsonSchemaToZod(v) : undefined),
102
+ inputSchema: optionalize(inputOutputSchema({ path: filepath })).transform((v) => v ? jsonSchemaToZod(v) : undefined),
41
103
  defaultInput: optionalize(defaultInputSchema),
42
- outputSchema: optionalize(inputOutputSchema({ path })).transform((v) => v ? jsonSchemaToZod(v) : undefined),
104
+ outputSchema: optionalize(inputOutputSchema({ path: filepath })).transform((v) => v ? jsonSchemaToZod(v) : undefined),
43
105
  includeInputInOutput: optionalize(z.boolean()),
44
106
  hooks: optionalize(z.union([hooksSchema, z.array(hooksSchema)])),
45
107
  skills: optionalize(z.array(nestAgentSchema)),
@@ -62,117 +124,92 @@ export async function parseAgentFile(path, data) {
62
124
  ]))),
63
125
  })),
64
126
  ])),
127
+ shareAFS: optionalize(z.boolean()),
65
128
  });
66
- const instructionItemSchema = z.union([
67
- z.object({
68
- role: roleSchema.default("system"),
69
- url: z.string(),
70
- }),
71
- z.object({
72
- role: roleSchema.default("system"),
73
- content: z.string(),
74
- }),
75
- ]);
76
- const parseInstructionItem = async ({ role, ...v }) => {
77
- if (role === "tool")
78
- throw new Error(`'tool' role is not allowed in instruction item in agent file ${path}`);
79
- if ("content" in v && typeof v.content === "string") {
80
- return { role, content: v.content, path };
81
- }
82
- if ("url" in v && typeof v.url === "string") {
83
- const url = nodejs.path.isAbsolute(v.url)
84
- ? v.url
85
- : nodejs.path.join(nodejs.path.dirname(path), v.url);
86
- return nodejs.fs.readFile(url, "utf8").then((content) => ({ role, content, path: url }));
87
- }
88
- throw new Error(`Invalid instruction item in agent file ${path}. Expected 'content' or 'url' property`);
89
- };
90
- const instructionsSchema = z
91
- .union([z.string(), instructionItemSchema, z.array(instructionItemSchema)])
92
- .transform(async (v) => {
93
- if (typeof v === "string")
94
- return [{ role: "system", content: v, path }];
95
- if (Array.isArray(v)) {
96
- return Promise.all(v.map((item) => parseInstructionItem(item)));
97
- }
98
- return [await parseInstructionItem(v)];
99
- });
100
- return camelizeSchema(z.discriminatedUnion("type", [
101
- z
102
- .object({
103
- type: z.literal("ai"),
104
- instructions: optionalize(instructionsSchema),
105
- autoReorderSystemMessages: optionalize(z.boolean()),
106
- autoMergeSystemMessages: optionalize(z.boolean()),
107
- inputKey: optionalize(z.string()),
108
- outputKey: optionalize(z.string()),
109
- inputFileKey: optionalize(z.string()),
110
- outputFileKey: optionalize(z.string()),
111
- toolChoice: optionalize(z.nativeEnum(AIAgentToolChoice)),
112
- toolCallsConcurrency: optionalize(z.number().int().min(0)),
113
- keepTextInToolUses: optionalize(z.boolean()),
114
- catchToolsError: optionalize(z.boolean()),
115
- structuredStreamMode: optionalize(z.boolean()),
116
- })
117
- .extend(baseAgentSchema.shape),
118
- z
119
- .object({
120
- type: z.literal("image"),
121
- instructions: instructionsSchema,
122
- inputFileKey: optionalize(z.string()),
123
- })
124
- .extend(baseAgentSchema.shape),
125
- z
126
- .object({
127
- type: z.literal("mcp"),
128
- url: optionalize(z.string()),
129
- command: optionalize(z.string()),
130
- args: optionalize(z.array(z.string())),
131
- })
132
- .extend(baseAgentSchema.shape),
133
- z
134
- .object({
135
- type: z.literal("team"),
136
- mode: optionalize(z.nativeEnum(ProcessMode)),
137
- iterateOn: optionalize(z.string()),
138
- concurrency: optionalize(z.number().int().min(1)),
139
- iterateWithPreviousOutput: optionalize(z.boolean()),
140
- includeAllStepsOutput: optionalize(z.boolean()),
141
- reflection: camelizeSchema(optionalize(z.object({
142
- reviewer: nestAgentSchema,
143
- isApproved: z.string(),
144
- maxIterations: optionalize(z.number().int().min(1)),
145
- returnLastOnMaxIterations: optionalize(z.boolean()),
146
- customErrorMessage: optionalize(z.string()),
147
- }))),
148
- })
149
- .extend(baseAgentSchema.shape),
129
+ const instructionsSchema = getInstructionsSchema({ filepath: filepath });
130
+ return camelizeSchema(z.union([
150
131
  z
151
132
  .object({
152
- type: z.literal("transform"),
153
- jsonata: z.string(),
133
+ type: z.string(),
134
+ agentClass: z.custom((v) => typeof v?.prototype?.constructor === "function"),
154
135
  })
155
- .extend(baseAgentSchema.shape),
156
- z
157
- .object({
158
- type: z.literal("function"),
159
- process: z.preprocess((v) => (typeof v === "string" ? codeToFunctionAgentFn(v) : v), z.custom()),
160
- })
161
- .extend(baseAgentSchema.shape),
136
+ .extend(baseAgentSchema.shape)
137
+ .passthrough(),
138
+ z.discriminatedUnion("type", [
139
+ z
140
+ .object({
141
+ type: z.literal("ai"),
142
+ instructions: optionalize(instructionsSchema),
143
+ autoReorderSystemMessages: optionalize(z.boolean()),
144
+ autoMergeSystemMessages: optionalize(z.boolean()),
145
+ inputKey: optionalize(z.string()),
146
+ outputKey: optionalize(z.string()),
147
+ inputFileKey: optionalize(z.string()),
148
+ outputFileKey: optionalize(z.string()),
149
+ toolChoice: optionalize(z.nativeEnum(AIAgentToolChoice)),
150
+ toolCallsConcurrency: optionalize(z.number().int().min(0)),
151
+ keepTextInToolUses: optionalize(z.boolean()),
152
+ catchToolsError: optionalize(z.boolean()),
153
+ structuredStreamMode: optionalize(z.boolean()),
154
+ })
155
+ .extend(baseAgentSchema.shape),
156
+ z
157
+ .object({
158
+ type: z.literal("image"),
159
+ instructions: instructionsSchema,
160
+ inputFileKey: optionalize(z.string()),
161
+ })
162
+ .extend(baseAgentSchema.shape),
163
+ z
164
+ .object({
165
+ type: z.literal("mcp"),
166
+ url: optionalize(z.string()),
167
+ command: optionalize(z.string()),
168
+ args: optionalize(z.array(z.string())),
169
+ })
170
+ .extend(baseAgentSchema.shape),
171
+ z
172
+ .object({
173
+ type: z.literal("team"),
174
+ mode: optionalize(z.nativeEnum(ProcessMode)),
175
+ iterateOn: optionalize(z.string()),
176
+ concurrency: optionalize(z.number().int().min(1)),
177
+ iterateWithPreviousOutput: optionalize(z.boolean()),
178
+ includeAllStepsOutput: optionalize(z.boolean()),
179
+ reflection: camelizeSchema(optionalize(z.object({
180
+ reviewer: nestAgentSchema,
181
+ isApproved: z.string(),
182
+ maxIterations: optionalize(z.number().int().min(1)),
183
+ returnLastOnMaxIterations: optionalize(z.boolean()),
184
+ customErrorMessage: optionalize(z.string()),
185
+ }))),
186
+ })
187
+ .extend(baseAgentSchema.shape),
188
+ z
189
+ .object({
190
+ type: z.literal("transform"),
191
+ jsonata: z.string(),
192
+ })
193
+ .extend(baseAgentSchema.shape),
194
+ z
195
+ .object({
196
+ type: z.literal("function"),
197
+ process: z.preprocess((v) => (typeof v === "string" ? codeToFunctionAgentFn(v) : v), z.custom()),
198
+ })
199
+ .extend(baseAgentSchema.shape),
200
+ ]),
162
201
  ]));
163
202
  });
164
- return agentSchema.parseAsync({
165
- ...data,
166
- model: data.model || data.chatModel || data.chat_model,
167
- });
168
- }
169
- export async function loadAgentFromYamlFile(path) {
170
- const raw = await tryOrThrow(() => nodejs.fs.readFile(path, "utf8"), (error) => new Error(`Failed to load agent definition from ${path}: ${error.message}`));
171
- const json = tryOrThrow(() => parse(raw), (error) => new Error(`Failed to parse agent definition from ${path}: ${error.message}`));
172
- const agent = await tryOrThrow(async () => await parseAgentFile(path, {
173
- ...json,
174
- type: json.type ?? "ai",
175
- skills: json.skills ?? json.tools,
176
- }), (error) => new Error(`Failed to validate agent definition from ${path}: ${error.message}`));
177
- return agent;
178
- }
203
+ return agentSchema;
204
+ };
205
+ export const getNestAgentSchema = ({ filepath, }) => {
206
+ const agentSchema = getAgentSchema({ filepath });
207
+ return z.lazy(() => z.union([
208
+ agentSchema,
209
+ z.string(),
210
+ camelizeSchema(z.object({
211
+ url: z.string(),
212
+ defaultInput: optionalize(defaultInputSchema),
213
+ })),
214
+ ]));
215
+ };
@@ -1,11 +1,13 @@
1
- import { type AFSModule } from "@aigne/afs";
1
+ import { AFS, type AFSModule } from "@aigne/afs";
2
2
  import { type ZodType, z } from "zod";
3
3
  import { Agent, type AgentOptions } from "../agents/agent.js";
4
4
  import type { ChatModel } from "../agents/chat-model.js";
5
5
  import type { ImageModel } from "../agents/image-model.js";
6
6
  import type { AIGNEOptions } from "../aigne/aigne.js";
7
7
  import type { MemoryAgent, MemoryAgentOptions } from "../memory/memory.js";
8
+ import { PromptBuilder } from "../prompt/prompt-builder.js";
8
9
  import { type PromiseOrValue } from "../utils/type-utils.js";
10
+ import { type Instructions, loadAgentFromYamlFile, type NestAgentSchema } from "./agent-yaml.js";
9
11
  export interface LoadOptions {
10
12
  memories?: {
11
13
  new (parameters?: MemoryAgentOptions): MemoryAgent;
@@ -13,6 +15,7 @@ export interface LoadOptions {
13
15
  model?: ChatModel | ((model?: z.infer<typeof aigneFileSchema>["model"]) => PromiseOrValue<ChatModel | undefined>);
14
16
  imageModel?: ImageModel | ((model?: z.infer<typeof aigneFileSchema>["imageModel"]) => PromiseOrValue<ImageModel | undefined>);
15
17
  afs?: {
18
+ sharedAFS?: AFS;
16
19
  availableModules?: {
17
20
  module: string;
18
21
  alias?: string[];
@@ -20,9 +23,14 @@ export interface LoadOptions {
20
23
  }[];
21
24
  };
22
25
  aigne?: z.infer<typeof aigneFileSchema>;
26
+ require?: (modulePath: string, options: {
27
+ parent?: string;
28
+ }) => Promise<any>;
23
29
  }
24
30
  export declare function load(path: string, options?: LoadOptions): Promise<AIGNEOptions>;
25
31
  export declare function loadAgent(path: string, options?: LoadOptions, agentOptions?: AgentOptions): Promise<Agent>;
32
+ export declare function loadNestAgent(path: string, agent: NestAgentSchema, options?: LoadOptions, agentOptions?: AgentOptions & Record<string, unknown>): Promise<Agent>;
33
+ export declare function parseAgent(path: string, agent: Awaited<ReturnType<typeof loadAgentFromYamlFile>>, options?: LoadOptions, agentOptions?: AgentOptions): Promise<Agent>;
26
34
  type CliAgent = string | {
27
35
  url?: string;
28
36
  name?: string;
@@ -230,4 +238,5 @@ export declare function loadAIGNEFile(path: string): Promise<{
230
238
  aigne: z.infer<typeof aigneFileSchema>;
231
239
  rootDir: string;
232
240
  }>;
241
+ export declare function instructionsToPromptBuilder(instructions: Instructions): PromptBuilder;
233
242
  export {};
@@ -10,9 +10,10 @@ import { TeamAgent } from "../agents/team-agent.js";
10
10
  import { TransformAgent } from "../agents/transform-agent.js";
11
11
  import { PromptBuilder } from "../prompt/prompt-builder.js";
12
12
  import { ChatMessagesTemplate, parseChatMessages } from "../prompt/template.js";
13
+ import { isAgent } from "../utils/agent-utils.js";
13
14
  import { flat, isNil, isNonNullable, omitBy, tryOrThrow, } from "../utils/type-utils.js";
14
15
  import { loadAgentFromJsFile } from "./agent-js.js";
15
- import { loadAgentFromYamlFile } from "./agent-yaml.js";
16
+ import { loadAgentFromYamlFile, } from "./agent-yaml.js";
16
17
  import { camelizeSchema, chatModelSchema, imageModelSchema, optionalize } from "./schema.js";
17
18
  const AIGNE_FILE_NAME = ["aigne.yaml", "aigne.yml"];
18
19
  export async function load(path, options = {}) {
@@ -66,17 +67,18 @@ export async function loadAgent(path, options, agentOptions) {
66
67
  return parseAgent(path, agent, options, agentOptions);
67
68
  }
68
69
  if ([".yml", ".yaml"].includes(nodejs.path.extname(path))) {
69
- const agent = await loadAgentFromYamlFile(path);
70
+ const agent = await loadAgentFromYamlFile(path, options);
70
71
  return parseAgent(path, agent, options, agentOptions);
71
72
  }
72
73
  throw new Error(`Unsupported agent file type: ${path}`);
73
74
  }
74
- async function loadNestAgent(path, agent, options) {
75
+ export async function loadNestAgent(path, agent, options, agentOptions) {
75
76
  return typeof agent === "object" && "type" in agent
76
- ? parseAgent(path, agent, options)
77
+ ? parseAgent(path, agent, options, agentOptions)
77
78
  : typeof agent === "string"
78
- ? loadAgent(nodejs.path.join(nodejs.path.dirname(path), agent), options)
79
+ ? loadAgent(nodejs.path.join(nodejs.path.dirname(path), agent), options, agentOptions)
79
80
  : loadAgent(nodejs.path.join(nodejs.path.dirname(path), agent.url), options, {
81
+ ...agentOptions,
80
82
  defaultInput: agent.defaultInput,
81
83
  hooks: await parseHooks(path, agent.hooks, options),
82
84
  });
@@ -107,15 +109,18 @@ async function loadSkills(path, skills, options) {
107
109
  }
108
110
  return loadedSkills;
109
111
  }
110
- async function parseAgent(path, agent, options, agentOptions) {
111
- const skills = "skills" in agent && agent.skills ? await loadSkills(path, agent.skills, options) : undefined;
112
+ export async function parseAgent(path, agent, options, agentOptions) {
113
+ if (isAgent(agent))
114
+ return agent;
112
115
  const memory = "memory" in agent && options?.memories?.length
113
116
  ? await loadMemory(options.memories, typeof agent.memory === "object" ? agent.memory.provider : undefined, typeof agent.memory === "object" ? agent.memory : {})
114
117
  : undefined;
115
118
  let afs;
116
- if (typeof agent.afs === "boolean") {
117
- if (agent.afs)
118
- afs = new AFS();
119
+ if (agent.afs !== false && (!agent.afs || agent.afs === true) && options?.afs?.sharedAFS) {
120
+ afs = options.afs.sharedAFS;
121
+ }
122
+ else if (agent.afs === true) {
123
+ afs = new AFS();
119
124
  }
120
125
  else if (agent.afs) {
121
126
  afs = new AFS();
@@ -128,6 +133,12 @@ async function parseAgent(path, agent, options, agentOptions) {
128
133
  afs.mount(module);
129
134
  }
130
135
  }
136
+ const skills = "skills" in agent && agent.skills
137
+ ? await loadSkills(path, agent.skills, {
138
+ ...options,
139
+ afs: { ...options?.afs, sharedAFS: (agent.shareAFS && afs) || options?.afs?.sharedAFS },
140
+ })
141
+ : [];
131
142
  const model = agent.model && typeof options?.model === "function"
132
143
  ? await options.model({ ...options.aigne?.model, ...omitBy(agent.model, (v) => isNil(v)) })
133
144
  : undefined;
@@ -142,22 +153,17 @@ async function parseAgent(path, agent, options, agentOptions) {
142
153
  ...agent,
143
154
  model,
144
155
  imageModel,
145
- skills,
146
156
  memory,
147
157
  hooks: [
148
158
  ...((await parseHooks(path, agent.hooks, options)) ?? []),
149
159
  ...[agentOptions?.hooks].flat().filter(isNonNullable),
150
160
  ],
151
- afs,
161
+ skills: [...(agentOptions?.skills || []), ...skills],
162
+ afs: afs || agentOptions?.afs,
152
163
  };
153
164
  let instructions;
154
- if ("instructions" in agent && agent.instructions) {
155
- instructions = new PromptBuilder({
156
- instructions: ChatMessagesTemplate.from(parseChatMessages(agent.instructions.map((i) => ({
157
- ...i,
158
- options: { workingDir: nodejs.path.dirname(i.path) },
159
- })))),
160
- });
165
+ if ("instructions" in agent && agent.instructions && ["ai", "image"].includes(agent.type)) {
166
+ instructions = instructionsToPromptBuilder(agent.instructions);
161
167
  }
162
168
  switch (agent.type) {
163
169
  case "ai": {
@@ -214,6 +220,14 @@ async function parseAgent(path, agent, options, agentOptions) {
214
220
  });
215
221
  }
216
222
  }
223
+ if ("agentClass" in agent && agent.agentClass) {
224
+ return await agent.agentClass.load({
225
+ filepath: path,
226
+ parsed: baseOptions,
227
+ options,
228
+ });
229
+ }
230
+ throw new Error(`Unsupported agent type: ${"type" in agent ? agent.type : "unknown"} at path: ${path}`);
217
231
  }
218
232
  async function loadMemory(memories, provider, options) {
219
233
  const M = !provider
@@ -269,3 +283,11 @@ async function findAIGNEFile(path) {
269
283
  }
270
284
  throw new Error(`aigne.yaml not found in ${path}. Please ensure you are in the correct directory or provide a valid path.`);
271
285
  }
286
+ export function instructionsToPromptBuilder(instructions) {
287
+ return new PromptBuilder({
288
+ instructions: ChatMessagesTemplate.from(parseChatMessages(instructions.map((i) => ({
289
+ ...i,
290
+ options: { workingDir: nodejs.path.dirname(i.path) },
291
+ })))),
292
+ });
293
+ }
@@ -2,7 +2,6 @@ import type { GetPromptResult } from "@modelcontextprotocol/sdk/types.js";
2
2
  import { Agent, type AgentInvokeOptions, type Message } from "../agents/agent.js";
3
3
  import { type AIAgent } from "../agents/ai-agent.js";
4
4
  import type { ChatModel, ChatModelInput } from "../agents/chat-model.js";
5
- import type { ImageAgent } from "../agents/image-agent.js";
6
5
  import { type FileUnionContent } from "../agents/model.js";
7
6
  import { ChatMessagesTemplate } from "./template.js";
8
7
  export interface PromptBuilderOptions {
@@ -26,11 +25,12 @@ export declare class PromptBuilder {
26
25
  constructor(options?: PromptBuilderOptions);
27
26
  instructions?: string | ChatMessagesTemplate;
28
27
  workingDir?: string;
28
+ copy(): PromptBuilder;
29
29
  build(options: PromptBuildOptions): Promise<ChatModelInput & {
30
30
  toolAgents?: Agent[];
31
31
  }>;
32
- buildImagePrompt(options: Pick<PromptBuildOptions, "input" | "context"> & {
33
- agent: ImageAgent;
32
+ buildPrompt(options: Pick<PromptBuildOptions, "input" | "context"> & {
33
+ inputFileKey?: string;
34
34
  }): Promise<{
35
35
  prompt: string;
36
36
  image?: FileUnionContent[];
@@ -62,6 +62,12 @@ export class PromptBuilder {
62
62
  }
63
63
  instructions;
64
64
  workingDir;
65
+ copy() {
66
+ return new PromptBuilder({
67
+ instructions: typeof this.instructions === "string" ? this.instructions : this.instructions?.copy(),
68
+ workingDir: this.workingDir,
69
+ });
70
+ }
65
71
  async build(options) {
66
72
  return {
67
73
  messages: await this.buildMessages(options),
@@ -72,11 +78,11 @@ export class PromptBuilder {
72
78
  ...(await this.buildTools(options)),
73
79
  };
74
80
  }
75
- async buildImagePrompt(options) {
81
+ async buildPrompt(options) {
76
82
  const messages = (await (typeof this.instructions === "string"
77
83
  ? ChatMessagesTemplate.from([SystemMessageTemplate.from(this.instructions)])
78
84
  : this.instructions)?.format(this.getTemplateVariables(options), { workingDir: this.workingDir })) ?? [];
79
- const inputFileKey = options.agent?.inputFileKey;
85
+ const inputFileKey = options.inputFileKey;
80
86
  const files = flat(inputFileKey
81
87
  ? checkArguments("Check input files", optionalize(fileUnionContentsSchema), options.input?.[inputFileKey])
82
88
  : null);
@@ -52,19 +52,39 @@ export async function getAFSSkills(afs) {
52
52
  }),
53
53
  FunctionAgent.from({
54
54
  name: "afs_read",
55
- description: "Read file contents from the AFS - path must be an exact file path from list or search results",
55
+ description: `\
56
+ Read file contents from the AFS - path must be an exact file path from list or search results
57
+
58
+ Usage:
59
+ - Use withLineNumbers=true to get line numbers for code reviews or edits
60
+ `,
56
61
  inputSchema: z.object({
57
62
  path: z
58
63
  .string()
59
64
  .describe("Exact file path from list or search results (e.g., '/docs/api.md', '/src/utils/helper.js')"),
65
+ withLineNumbers: z
66
+ .boolean()
67
+ .optional()
68
+ .describe(`Whether to include line numbers in the returned content, default is false`),
60
69
  }),
61
70
  process: async (input) => {
62
71
  const result = await afs.read(input.path);
72
+ let content = result.result?.content;
73
+ if (input.withLineNumbers && typeof content === "string") {
74
+ content = content
75
+ .split("\n")
76
+ .map((line, idx) => `${idx + 1}| ${line}`)
77
+ .join("\n");
78
+ }
63
79
  return {
64
80
  status: "success",
65
81
  tool: "afs_read",
66
82
  path: input.path,
67
83
  ...result,
84
+ result: {
85
+ ...result.result,
86
+ content,
87
+ },
68
88
  };
69
89
  },
70
90
  }),
@@ -106,7 +126,9 @@ export async function getAFSSkills(afs) {
106
126
  }
107
127
  function buildTreeView(entries) {
108
128
  const tree = {};
129
+ const entryMap = new Map();
109
130
  for (const entry of entries) {
131
+ entryMap.set(entry.path, entry);
110
132
  const parts = entry.path.split("/").filter(Boolean);
111
133
  let current = tree;
112
134
  for (const part of parts) {
@@ -116,13 +138,28 @@ function buildTreeView(entries) {
116
138
  current = current[part];
117
139
  }
118
140
  }
119
- function renderTree(node, prefix = "") {
141
+ function renderTree(node, prefix = "", currentPath = "") {
120
142
  let result = "";
121
143
  const keys = Object.keys(node);
122
144
  keys.forEach((key, index) => {
123
145
  const isLast = index === keys.length - 1;
124
- result += `${prefix}${isLast ? "└── " : "├── "}${key}\n`;
125
- result += renderTree(node[key], `${prefix}${isLast ? " " : "│ "}`);
146
+ const fullPath = currentPath ? `${currentPath}/${key}` : `/${key}`;
147
+ const entry = entryMap.get(fullPath);
148
+ // Build metadata suffix
149
+ const metadataParts = [];
150
+ // Children count
151
+ const childrenCount = entry?.metadata?.childrenCount;
152
+ if (childrenCount !== undefined && childrenCount > 0) {
153
+ metadataParts.push(`${childrenCount} items`);
154
+ }
155
+ // Executable
156
+ if (entry?.metadata?.execute) {
157
+ metadataParts.push("executable");
158
+ }
159
+ const metadataSuffix = metadataParts.length > 0 ? ` [${metadataParts.join(", ")}]` : "";
160
+ result += `${prefix}${isLast ? "└── " : "├── "}${key}${metadataSuffix}`;
161
+ result += `\n`;
162
+ result += renderTree(node[key], `${prefix}${isLast ? " " : "│ "}`, fullPath);
126
163
  });
127
164
  return result;
128
165
  }
@@ -62,6 +62,7 @@ export declare class ChatMessagesTemplate {
62
62
  messages: ChatMessageTemplate[];
63
63
  static from(messages: ChatMessageTemplate[] | string): ChatMessagesTemplate;
64
64
  constructor(messages: ChatMessageTemplate[]);
65
+ copy(): ChatMessagesTemplate;
65
66
  format(variables?: Record<string, unknown>, options?: FormatOptions): Promise<ChatModelInputMessage[]>;
66
67
  }
67
68
  declare const chatMessageSchema: z.ZodUnion<[z.ZodObject<{
@@ -1,5 +1,6 @@
1
1
  import { nodejs } from "@aigne/platform-helpers/nodejs/index.js";
2
2
  import nunjucks from "nunjucks";
3
+ import { stringify } from "yaml";
3
4
  import { z } from "zod";
4
5
  import { isNil, omitBy } from "../utils/type-utils.js";
5
6
  import { setupFilters } from "./filters/index.js";
@@ -130,9 +131,7 @@ export class ToolMessageTemplate extends ChatMessageTemplate {
130
131
  return new ToolMessageTemplate(content, toolCallId, name, options);
131
132
  }
132
133
  constructor(content, toolCallId, name, options) {
133
- super("tool", typeof content === "string"
134
- ? content
135
- : JSON.stringify(content, (_, value) => typeof value === "bigint" ? value.toString() : value), name, options);
134
+ super("tool", typeof content === "string" ? content : stringify(content), name, options);
136
135
  this.toolCallId = toolCallId;
137
136
  }
138
137
  async format(_variables, _options) {
@@ -153,6 +152,9 @@ export class ChatMessagesTemplate {
153
152
  constructor(messages) {
154
153
  this.messages = messages;
155
154
  }
155
+ copy() {
156
+ return new ChatMessagesTemplate(this.messages.map((m) => m));
157
+ }
156
158
  async format(variables, options) {
157
159
  return Promise.all(this.messages.map((message) => message.format(variables, options)));
158
160
  }
@@ -1,4 +1,4 @@
1
- import type { AgentHooks } from "../agents/agent.js";
1
+ import type { Agent, AgentHooks } from "../agents/agent.js";
2
2
  import type { AIGNECLIAgents } from "../aigne/type.js";
3
3
  export declare function sortHooks(hooks: AgentHooks[]): AgentHooks[];
4
4
  export interface CLIAgent<T> {
@@ -9,4 +9,5 @@ export interface CLIAgent<T> {
9
9
  agents?: CLIAgent<T>[];
10
10
  }
11
11
  export declare function mapCliAgent<A, O>({ agent, agents, ...input }: CLIAgent<A>, transform: (input: A) => O): CLIAgent<O>;
12
- export declare function findCliAgent(cli: AIGNECLIAgents, parent: string[] | "*", name: string): import("../agents/agent.js").Agent<any, any> | undefined;
12
+ export declare function findCliAgent(cli: AIGNECLIAgents, parent: string[] | "*", name: string): Agent<any, any> | undefined;
13
+ export declare function isAgent<A extends Agent>(obj: any): obj is A;
@@ -46,3 +46,9 @@ function findCliAgentRecursive(agents, name) {
46
46
  }
47
47
  return undefined;
48
48
  }
49
+ export function isAgent(obj) {
50
+ return (obj &&
51
+ typeof obj.name === "string" &&
52
+ typeof obj.invoke === "function" &&
53
+ typeof obj.process === "function");
54
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Estimate tokens in text by analyzing character types
3
+ * This function handles mixed-language text (Chinese and English) by counting
4
+ * different character types and applying appropriate token ratios for each type
5
+ *
6
+ * @param text - The text to estimate
7
+ * @returns Estimated token count
8
+ */
9
+ export declare function estimateTokens(text: string): number;