@aigne/core 1.29.1 → 1.32.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 (64) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/lib/cjs/agents/agent.d.ts +0 -2
  3. package/lib/cjs/agents/agent.js +4 -9
  4. package/lib/cjs/agents/ai-agent.d.ts +74 -0
  5. package/lib/cjs/agents/ai-agent.js +44 -4
  6. package/lib/cjs/agents/user-agent.js +1 -1
  7. package/lib/cjs/aigne/context.d.ts +4 -0
  8. package/lib/cjs/aigne/context.js +0 -10
  9. package/lib/cjs/aigne/index.d.ts +1 -1
  10. package/lib/cjs/aigne/index.js +1 -1
  11. package/lib/cjs/aigne/message-queue.js +6 -6
  12. package/lib/cjs/loader/index.js +3 -0
  13. package/lib/cjs/prompt/prompt-builder.d.ts +6 -2
  14. package/lib/cjs/prompt/prompt-builder.js +48 -19
  15. package/lib/cjs/prompt/prompts/memory-message-template.d.ts +1 -1
  16. package/lib/cjs/prompt/prompts/memory-message-template.js +2 -4
  17. package/lib/cjs/prompt/prompts/structured-stream-instructions.d.ts +7 -0
  18. package/lib/cjs/prompt/prompts/structured-stream-instructions.js +27 -0
  19. package/lib/cjs/prompt/template.d.ts +21 -7
  20. package/lib/cjs/prompt/template.js +57 -19
  21. package/lib/cjs/utils/mcp-utils.d.ts +1 -1
  22. package/lib/cjs/utils/stream-utils.js +3 -0
  23. package/lib/cjs/utils/structured-stream-extractor.d.ts +14 -0
  24. package/lib/cjs/utils/structured-stream-extractor.js +60 -0
  25. package/lib/cjs/utils/type-utils.d.ts +1 -1
  26. package/lib/cjs/utils/type-utils.js +3 -3
  27. package/lib/dts/agents/agent.d.ts +0 -2
  28. package/lib/dts/agents/ai-agent.d.ts +74 -0
  29. package/lib/dts/aigne/context.d.ts +4 -0
  30. package/lib/dts/aigne/index.d.ts +1 -1
  31. package/lib/dts/prompt/prompt-builder.d.ts +6 -2
  32. package/lib/dts/prompt/prompts/memory-message-template.d.ts +1 -1
  33. package/lib/dts/prompt/prompts/structured-stream-instructions.d.ts +7 -0
  34. package/lib/dts/prompt/template.d.ts +21 -7
  35. package/lib/dts/utils/mcp-utils.d.ts +1 -1
  36. package/lib/dts/utils/structured-stream-extractor.d.ts +14 -0
  37. package/lib/dts/utils/type-utils.d.ts +1 -1
  38. package/lib/esm/agents/agent.d.ts +0 -2
  39. package/lib/esm/agents/agent.js +5 -10
  40. package/lib/esm/agents/ai-agent.d.ts +74 -0
  41. package/lib/esm/agents/ai-agent.js +44 -4
  42. package/lib/esm/agents/user-agent.js +2 -2
  43. package/lib/esm/aigne/context.d.ts +4 -0
  44. package/lib/esm/aigne/context.js +1 -11
  45. package/lib/esm/aigne/index.d.ts +1 -1
  46. package/lib/esm/aigne/index.js +1 -1
  47. package/lib/esm/aigne/message-queue.js +7 -7
  48. package/lib/esm/loader/agent-js.js +1 -1
  49. package/lib/esm/loader/index.js +3 -0
  50. package/lib/esm/prompt/prompt-builder.d.ts +6 -2
  51. package/lib/esm/prompt/prompt-builder.js +49 -20
  52. package/lib/esm/prompt/prompts/memory-message-template.d.ts +1 -1
  53. package/lib/esm/prompt/prompts/memory-message-template.js +2 -4
  54. package/lib/esm/prompt/prompts/structured-stream-instructions.d.ts +7 -0
  55. package/lib/esm/prompt/prompts/structured-stream-instructions.js +24 -0
  56. package/lib/esm/prompt/template.d.ts +21 -7
  57. package/lib/esm/prompt/template.js +54 -17
  58. package/lib/esm/utils/mcp-utils.d.ts +1 -1
  59. package/lib/esm/utils/stream-utils.js +4 -1
  60. package/lib/esm/utils/structured-stream-extractor.d.ts +14 -0
  61. package/lib/esm/utils/structured-stream-extractor.js +56 -0
  62. package/lib/esm/utils/type-utils.d.ts +1 -1
  63. package/lib/esm/utils/type-utils.js +2 -2
  64. package/package.json +16 -14
@@ -50,6 +50,48 @@ export interface AIAgentOptions<I extends Message = Message, O extends Message =
50
50
  * @default true
51
51
  */
52
52
  catchToolsError?: boolean;
53
+ /**
54
+ * Whether to enable structured stream mode
55
+ *
56
+ * When enabled, the AI model's streaming response will be processed to extract
57
+ * structured metadata. The model needs to include specific format metadata tags
58
+ * (like <metadata></metadata>) in its response, which will be parsed as JSON
59
+ * objects and passed through the stream.
60
+ *
61
+ * This is useful for scenarios that need to extract structured information
62
+ * (like classifications, scores, tags, etc.) from AI responses.
63
+ *
64
+ * @default false
65
+ */
66
+ structuredStreamMode?: boolean;
67
+ /**
68
+ * Custom structured stream instructions configuration
69
+ *
70
+ * Allows customization of structured stream mode behavior, including:
71
+ * - instructions: Prompt instructions to guide the AI model on how to output structured data
72
+ * - metadataStart: Metadata start marker (e.g., "<metadata>")
73
+ * - metadataEnd: Metadata end marker (e.g., "</metadata>")
74
+ * - parse: Function to parse metadata content, converting raw string to object
75
+ *
76
+ * If not provided, the default STRUCTURED_STREAM_INSTRUCTIONS configuration will be used,
77
+ * which outputs structured data in YAML format within <metadata> tags.
78
+ *
79
+ * @example
80
+ * ```typescript
81
+ * {
82
+ * instructions: "Output metadata in JSON format at the end of response in markdown code block with json language",
83
+ * metadataStart: "```json",
84
+ * metadataEnd: "```",
85
+ * parse: JSON.parse
86
+ * }
87
+ * ```
88
+ */
89
+ customStructuredStreamInstructions?: {
90
+ instructions: string | PromptBuilder;
91
+ metadataStart: string;
92
+ metadataEnd: string;
93
+ parse: (raw: string) => object;
94
+ };
53
95
  /**
54
96
  * Whether to include memory agents as tools for the AI model
55
97
  *
@@ -217,6 +259,38 @@ export declare class AIAgent<I extends Message = any, O extends Message = any> e
217
259
  * @default true
218
260
  */
219
261
  catchToolsError: boolean;
262
+ /**
263
+ * Whether to enable structured stream mode
264
+ *
265
+ * When enabled, the AI model's streaming response will be processed to extract
266
+ * structured metadata. The model needs to include specific format metadata tags
267
+ * (like <metadata></metadata>) in its response, which will be parsed as JSON
268
+ * objects and passed through the stream.
269
+ *
270
+ * This is useful for scenarios that need to extract structured information
271
+ * (like classifications, scores, tags, etc.) from AI responses.
272
+ *
273
+ * @default false
274
+ */
275
+ structuredStreamMode?: boolean;
276
+ /**
277
+ * Custom structured stream instructions configuration
278
+ *
279
+ * Allows customization of structured stream mode behavior, including:
280
+ * - instructions: Prompt instructions to guide the AI model on how to output structured data
281
+ * - metadataStart: Metadata start marker (e.g., "<metadata>")
282
+ * - metadataEnd: Metadata end marker (e.g., "</metadata>")
283
+ * - parse: Function to parse metadata content, converting raw string to object
284
+ *
285
+ * If not provided, the default STRUCTURED_STREAM_INSTRUCTIONS configuration will be used,
286
+ * which outputs structured data in YAML format within <metadata> tags.
287
+ */
288
+ customStructuredStreamInstructions?: {
289
+ instructions: PromptBuilder;
290
+ metadataStart: string;
291
+ metadataEnd: string;
292
+ parse: (raw: string) => object;
293
+ };
220
294
  /**
221
295
  * Process an input message and generate a response
222
296
  *
@@ -1,6 +1,8 @@
1
1
  import { z } from "zod";
2
2
  import { PromptBuilder } from "../prompt/prompt-builder.js";
3
+ import { STRUCTURED_STREAM_INSTRUCTIONS } from "../prompt/prompts/structured-stream-instructions.js";
3
4
  import { AgentMessageTemplate, ToolMessageTemplate } from "../prompt/template.js";
5
+ import { ExtractMetadataTransform } from "../utils/structured-stream-extractor.js";
4
6
  import { checkArguments, isEmpty } from "../utils/type-utils.js";
5
7
  import { Agent, agentOptionsSchema, isAgentResponseDelta, } from "./agent.js";
6
8
  import { isTransferAgentOutput } from "./types.js";
@@ -112,6 +114,13 @@ export class AIAgent extends Agent {
112
114
  this.memoryPromptTemplate = options.memoryPromptTemplate;
113
115
  if (typeof options.catchToolsError === "boolean")
114
116
  this.catchToolsError = options.catchToolsError;
117
+ this.structuredStreamMode = options.structuredStreamMode;
118
+ this.customStructuredStreamInstructions = options.customStructuredStreamInstructions && {
119
+ ...options.customStructuredStreamInstructions,
120
+ instructions: typeof options.customStructuredStreamInstructions.instructions === "string"
121
+ ? PromptBuilder.from(options.customStructuredStreamInstructions.instructions)
122
+ : options.customStructuredStreamInstructions.instructions,
123
+ };
115
124
  if (!this.inputKey && !this.instructions) {
116
125
  throw new Error("AIAgent requires either inputKey or instructions to be set");
117
126
  }
@@ -181,6 +190,33 @@ export class AIAgent extends Agent {
181
190
  * @default true
182
191
  */
183
192
  catchToolsError = true;
193
+ /**
194
+ * Whether to enable structured stream mode
195
+ *
196
+ * When enabled, the AI model's streaming response will be processed to extract
197
+ * structured metadata. The model needs to include specific format metadata tags
198
+ * (like <metadata></metadata>) in its response, which will be parsed as JSON
199
+ * objects and passed through the stream.
200
+ *
201
+ * This is useful for scenarios that need to extract structured information
202
+ * (like classifications, scores, tags, etc.) from AI responses.
203
+ *
204
+ * @default false
205
+ */
206
+ structuredStreamMode;
207
+ /**
208
+ * Custom structured stream instructions configuration
209
+ *
210
+ * Allows customization of structured stream mode behavior, including:
211
+ * - instructions: Prompt instructions to guide the AI model on how to output structured data
212
+ * - metadataStart: Metadata start marker (e.g., "<metadata>")
213
+ * - metadataEnd: Metadata end marker (e.g., "</metadata>")
214
+ * - parse: Function to parse metadata content, converting raw string to object
215
+ *
216
+ * If not provided, the default STRUCTURED_STREAM_INSTRUCTIONS configuration will be used,
217
+ * which outputs structured data in YAML format within <metadata> tags.
218
+ */
219
+ customStructuredStreamInstructions;
184
220
  /**
185
221
  * Process an input message and generate a response
186
222
  *
@@ -204,7 +240,11 @@ export class AIAgent extends Agent {
204
240
  const outputKey = this.outputKey;
205
241
  for (;;) {
206
242
  const modelOutput = {};
207
- const stream = await options.context.invoke(model, { ...modelInput, messages: modelInput.messages.concat(toolCallMessages) }, { streaming: true });
243
+ let stream = await options.context.invoke(model, { ...modelInput, messages: modelInput.messages.concat(toolCallMessages) }, { streaming: true });
244
+ if (this.structuredStreamMode) {
245
+ const { metadataStart, metadataEnd, parse } = this.customStructuredStreamInstructions || STRUCTURED_STREAM_INSTRUCTIONS;
246
+ stream = stream.pipeThrough(new ExtractMetadataTransform({ start: metadataStart, end: metadataEnd, parse }));
247
+ }
208
248
  for await (const value of stream) {
209
249
  if (isAgentResponseDelta(value)) {
210
250
  if (value.delta.text?.text) {
@@ -243,7 +283,7 @@ export class AIAgent extends Agent {
243
283
  }
244
284
  // Continue LLM function calling loop if any tools were executed
245
285
  if (executedToolCalls.length) {
246
- toolCallMessages.push(AgentMessageTemplate.from(undefined, executedToolCalls.map(({ call }) => call)).format(), ...executedToolCalls.map(({ call, output }) => ToolMessageTemplate.from(output, call.id).format()));
286
+ toolCallMessages.push(await AgentMessageTemplate.from(undefined, executedToolCalls.map(({ call }) => call)).format(), ...(await Promise.all(executedToolCalls.map(({ call, output }) => ToolMessageTemplate.from(output, call.id).format()))));
247
287
  continue;
248
288
  }
249
289
  }
@@ -251,7 +291,7 @@ export class AIAgent extends Agent {
251
291
  if (json) {
252
292
  Object.assign(result, json);
253
293
  }
254
- else if (text) {
294
+ if (text) {
255
295
  Object.assign(result, { [outputKey]: text });
256
296
  }
257
297
  if (!isEmpty(result)) {
@@ -275,7 +315,7 @@ export class AIAgent extends Agent {
275
315
  * @protected
276
316
  */
277
317
  async *_processRouter(input, model, modelInput, options, toolsMap) {
278
- const { toolCalls: [call] = [], } = await options.context.invoke(model, modelInput);
318
+ const { toolCalls: [call] = [] } = await options.context.invoke(model, modelInput);
279
319
  if (!call) {
280
320
  throw new Error("Router toolChoice requires exactly one tool to be executed");
281
321
  }
@@ -1,5 +1,5 @@
1
1
  import { toMessagePayload } from "../aigne/message-queue.js";
2
- import { orArrayToArray } from "../utils/type-utils.js";
2
+ import { flat } from "../utils/type-utils.js";
3
3
  import { Agent, } from "./agent.js";
4
4
  export class UserAgent extends Agent {
5
5
  tag = "UserAgent";
@@ -67,7 +67,7 @@ export class UserAgent extends Agent {
67
67
  let subscriptions = [];
68
68
  return new ReadableStream({
69
69
  start: (controller) => {
70
- const subscribeTopic = orArrayToArray(this.subscribeTopic);
70
+ const subscribeTopic = flat(this.subscribeTopic);
71
71
  subscriptions = subscribeTopic.map((topic) => this.subscribe(topic, (message) => {
72
72
  controller.enqueue({ ...message, topic });
73
73
  }));
@@ -54,11 +54,15 @@ export interface InvokeOptions<U extends UserContext = UserContext> extends Part
54
54
  * @default true
55
55
  */
56
56
  newContext?: boolean;
57
+ userContext?: U;
58
+ memories?: Pick<Memory, "content">[];
57
59
  }
58
60
  /**
59
61
  * @hidden
60
62
  */
61
63
  export interface UserContext extends Record<string, unknown> {
64
+ userId?: string;
65
+ sessionId?: string;
62
66
  }
63
67
  /**
64
68
  * @hidden
@@ -1,4 +1,4 @@
1
- import { SpanStatusCode, context, trace } from "@opentelemetry/api";
1
+ import { context, SpanStatusCode, trace } from "@opentelemetry/api";
2
2
  import equal from "fast-deep-equal";
3
3
  import { Emitter } from "strict-event-emitter";
4
4
  import { v7 } from "uuid";
@@ -231,22 +231,12 @@ export class AIGNEContext {
231
231
  span.setAttribute("output", JSON.stringify({}));
232
232
  }
233
233
  span.setStatus({ code: SpanStatusCode.OK });
234
- await this.observer?.traceExporter
235
- ?.upsertInitialSpan?.(span)
236
- .catch((err) => {
237
- logger.error("upsertInitialSpan error", err?.message || err);
238
- });
239
234
  span.end();
240
235
  break;
241
236
  }
242
237
  case "agentFailed": {
243
238
  const { error } = args[0];
244
239
  span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });
245
- await this.observer?.traceExporter
246
- ?.upsertInitialSpan?.(span)
247
- .catch((err) => {
248
- logger.error("upsertInitialSpan error", err?.message || err);
249
- });
250
240
  span.end();
251
241
  break;
252
242
  }
@@ -1,4 +1,4 @@
1
- export * from "./context.js";
2
1
  export * from "./aigne.js";
2
+ export * from "./context.js";
3
3
  export * from "./message-queue.js";
4
4
  export * from "./usage.js";
@@ -1,4 +1,4 @@
1
- export * from "./context.js";
2
1
  export * from "./aigne.js";
2
+ export * from "./context.js";
3
3
  export * from "./message-queue.js";
4
4
  export * from "./usage.js";
@@ -1,6 +1,6 @@
1
1
  import { Emitter } from "strict-event-emitter";
2
2
  import { z } from "zod";
3
- import { checkArguments, isNil, orArrayToArray } from "../utils/type-utils.js";
3
+ import { checkArguments, flat, isNil } from "../utils/type-utils.js";
4
4
  /**
5
5
  * @hidden
6
6
  */
@@ -41,7 +41,7 @@ export class MessageQueue {
41
41
  topic,
42
42
  payload,
43
43
  });
44
- for (const t of orArrayToArray(topic)) {
44
+ for (const t of flat(topic)) {
45
45
  this.events.emit(t, payload);
46
46
  }
47
47
  }
@@ -72,18 +72,18 @@ export class MessageQueue {
72
72
  topic,
73
73
  listener,
74
74
  });
75
- for (const t of orArrayToArray(topic)) {
75
+ for (const t of flat(topic)) {
76
76
  this.events.off(t, listener);
77
77
  }
78
78
  }
79
79
  }
80
80
  function on(events, event, listener) {
81
- orArrayToArray(event).forEach((e) => events.on(e, listener));
82
- return () => orArrayToArray(event).forEach((e) => events.off(e, listener));
81
+ flat(event).forEach((e) => events.on(e, listener));
82
+ return () => flat(event).forEach((e) => events.off(e, listener));
83
83
  }
84
84
  function once(events, event, listener) {
85
- orArrayToArray(event).forEach((e) => events.once(e, listener));
86
- return () => orArrayToArray(event).forEach((e) => events.off(e, listener));
85
+ flat(event).forEach((e) => events.once(e, listener));
86
+ return () => flat(event).forEach((e) => events.off(e, listener));
87
87
  }
88
88
  const subscribeArgsSchema = z.object({
89
89
  topic: z.union([z.string(), z.array(z.string())]),
@@ -12,7 +12,7 @@ const agentJsFileSchema = z.object({
12
12
  process: z.custom(),
13
13
  });
14
14
  export async function loadAgentFromJsFile(path) {
15
- const { default: agent } = await tryOrThrow(() => import(path), (error) => new Error(`Failed to load agent definition from ${path}: ${error.message}`));
15
+ const { default: agent } = await tryOrThrow(() => import(/* @vite-ignore */ path), (error) => new Error(`Failed to load agent definition from ${path}: ${error.message}`));
16
16
  if (agent instanceof Agent)
17
17
  return agent;
18
18
  if (typeof agent !== "function") {
@@ -6,6 +6,7 @@ import { AIAgent } from "../agents/ai-agent.js";
6
6
  import { MCPAgent } from "../agents/mcp-agent.js";
7
7
  import { TeamAgent } from "../agents/team-agent.js";
8
8
  import { TransformAgent } from "../agents/transform-agent.js";
9
+ import { PromptBuilder } from "../prompt/prompt-builder.js";
9
10
  import { tryOrThrow } from "../utils/type-utils.js";
10
11
  import { loadAgentFromJsFile } from "./agent-js.js";
11
12
  import { loadAgentFromYamlFile } from "./agent-yaml.js";
@@ -55,6 +56,8 @@ async function parseAgent(path, agent, options) {
55
56
  case "ai": {
56
57
  return AIAgent.from({
57
58
  ...agent,
59
+ instructions: agent.instructions &&
60
+ PromptBuilder.from(agent.instructions, { workingDir: nodejs.path.dirname(path) }),
58
61
  memory,
59
62
  skills,
60
63
  });
@@ -5,8 +5,9 @@ import type { ChatModel, ChatModelInput } from "../agents/chat-model.js";
5
5
  import { ChatMessagesTemplate } from "./template.js";
6
6
  export interface PromptBuilderOptions {
7
7
  instructions?: string | ChatMessagesTemplate;
8
+ workingDir?: string;
8
9
  }
9
- export interface PromptBuildOptions extends Pick<AgentInvokeOptions, "context"> {
10
+ export interface PromptBuildOptions extends Partial<Pick<AgentInvokeOptions, "context">> {
10
11
  agent?: AIAgent;
11
12
  input?: Message;
12
13
  model?: ChatModel;
@@ -15,11 +16,14 @@ export interface PromptBuildOptions extends Pick<AgentInvokeOptions, "context">
15
16
  export declare class PromptBuilder {
16
17
  static from(instructions: string | {
17
18
  path: string;
18
- } | GetPromptResult): PromptBuilder;
19
+ } | GetPromptResult, { workingDir }?: {
20
+ workingDir?: string;
21
+ }): PromptBuilder;
19
22
  private static fromFile;
20
23
  private static fromMCPPromptResult;
21
24
  constructor(options?: PromptBuilderOptions);
22
25
  instructions?: string | ChatMessagesTemplate;
26
+ workingDir?: string;
23
27
  build(options: PromptBuildOptions): Promise<ChatModelInput & {
24
28
  toolAgents?: Agent[];
25
29
  }>;
@@ -1,24 +1,26 @@
1
1
  import { nodejs } from "@aigne/platform-helpers/nodejs/index.js";
2
2
  import { stringify } from "yaml";
3
3
  import { ZodObject } from "zod";
4
+ import { zodToJsonSchema } from "zod-to-json-schema";
4
5
  import { Agent } from "../agents/agent.js";
5
6
  import { outputSchemaToResponseFormatSchema } from "../utils/json-schema.js";
6
- import { unique } from "../utils/type-utils.js";
7
+ import { isRecord, unique } from "../utils/type-utils.js";
7
8
  import { MEMORY_MESSAGE_TEMPLATE } from "./prompts/memory-message-template.js";
9
+ import { STRUCTURED_STREAM_INSTRUCTIONS } from "./prompts/structured-stream-instructions.js";
8
10
  import { AgentMessageTemplate, ChatMessagesTemplate, PromptTemplate, SystemMessageTemplate, UserMessageTemplate, } from "./template.js";
9
11
  export class PromptBuilder {
10
- static from(instructions) {
12
+ static from(instructions, { workingDir } = {}) {
11
13
  if (typeof instructions === "string")
12
- return new PromptBuilder({ instructions });
14
+ return new PromptBuilder({ instructions, workingDir: workingDir });
13
15
  if (isFromPromptResult(instructions))
14
16
  return PromptBuilder.fromMCPPromptResult(instructions);
15
17
  if (isFromPath(instructions))
16
- return PromptBuilder.fromFile(instructions.path);
18
+ return PromptBuilder.fromFile(instructions.path, { workingDir });
17
19
  throw new Error(`Invalid instructions ${instructions}`);
18
20
  }
19
- static fromFile(path) {
21
+ static fromFile(path, { workingDir }) {
20
22
  const text = nodejs.fsSync.readFileSync(path, "utf-8");
21
- return PromptBuilder.from(text);
23
+ return PromptBuilder.from(text, { workingDir: workingDir || nodejs.path.dirname(path) });
22
24
  }
23
25
  static fromMCPPromptResult(result) {
24
26
  return new PromptBuilder({
@@ -50,12 +52,16 @@ export class PromptBuilder {
50
52
  }
51
53
  constructor(options) {
52
54
  this.instructions = options?.instructions;
55
+ this.workingDir = options?.workingDir;
53
56
  }
54
57
  instructions;
58
+ workingDir;
55
59
  async build(options) {
56
60
  return {
57
61
  messages: await this.buildMessages(options),
58
- responseFormat: this.buildResponseFormat(options),
62
+ responseFormat: options.agent?.structuredStreamMode
63
+ ? undefined
64
+ : this.buildResponseFormat(options),
59
65
  ...this.buildTools(options),
60
66
  };
61
67
  }
@@ -63,18 +69,30 @@ export class PromptBuilder {
63
69
  const { input } = options;
64
70
  const inputKey = options.agent?.inputKey;
65
71
  const message = inputKey && typeof input?.[inputKey] === "string" ? input[inputKey] : undefined;
66
- const messages = (typeof this.instructions === "string"
72
+ const messages = (await (typeof this.instructions === "string"
67
73
  ? ChatMessagesTemplate.from([SystemMessageTemplate.from(this.instructions)])
68
- : this.instructions)?.format(options.input) ?? [];
74
+ : this.instructions)?.format(options.input, { workingDir: this.workingDir })) ?? [];
69
75
  const memories = [];
70
- if (options.agent?.inputKey) {
71
- memories.push(...(await options.agent.retrieveMemories({ search: message }, options)));
76
+ if (options.agent && options.context) {
77
+ memories.push(...(await options.agent.retrieveMemories({ search: message }, { context: options.context })));
72
78
  }
73
- if (options.context.memories?.length) {
79
+ if (options.context?.memories?.length) {
74
80
  memories.push(...options.context.memories);
75
81
  }
76
82
  if (memories.length)
77
- messages.push(...this.convertMemoriesToMessages(memories, options));
83
+ messages.push(...(await this.convertMemoriesToMessages(memories, options)));
84
+ // if the agent is using structured stream mode, add the instructions
85
+ const { structuredStreamMode, outputSchema } = options.agent || {};
86
+ if (structuredStreamMode && outputSchema) {
87
+ const instructions = options.agent?.customStructuredStreamInstructions?.instructions ||
88
+ PromptBuilder.from(STRUCTURED_STREAM_INSTRUCTIONS.instructions);
89
+ messages.push(...(await instructions.buildMessages({
90
+ input: {
91
+ ...input,
92
+ outputJsonSchema: zodToJsonSchema(outputSchema),
93
+ },
94
+ })));
95
+ }
78
96
  if (message) {
79
97
  messages.push({
80
98
  role: "user",
@@ -83,14 +101,25 @@ export class PromptBuilder {
83
101
  }
84
102
  return messages;
85
103
  }
86
- convertMemoriesToMessages(memories, options) {
87
- const str = stringify(memories.map((i) => i.content));
88
- return [
89
- {
104
+ async convertMemoriesToMessages(memories, options) {
105
+ const messages = [];
106
+ const other = [];
107
+ const stringOrStringify = (value) => typeof value === "string" ? value : stringify(value);
108
+ for (const { content } of memories) {
109
+ if (isRecord(content) && "input" in content && "output" in content) {
110
+ messages.push({ role: "user", content: stringOrStringify(content.input) }, { role: "agent", content: stringOrStringify(content.output) });
111
+ }
112
+ else {
113
+ other.push(content);
114
+ }
115
+ }
116
+ if (other.length) {
117
+ messages.unshift({
90
118
  role: "system",
91
- content: PromptTemplate.from(options.agent?.memoryPromptTemplate || MEMORY_MESSAGE_TEMPLATE).format({ memories: str }),
92
- },
93
- ];
119
+ content: await PromptTemplate.from(options.agent?.memoryPromptTemplate || MEMORY_MESSAGE_TEMPLATE).format({ memories: stringify(other) }),
120
+ });
121
+ }
122
+ return messages;
94
123
  }
95
124
  buildResponseFormat(options) {
96
125
  const outputSchema = options.outputSchema || options.agent?.outputSchema;
@@ -1 +1 @@
1
- export declare const MEMORY_MESSAGE_TEMPLATE = "Here are some useful memories to consider:\n\n<memories>\n{{memories}}\n</memories>\n";
1
+ export declare const MEMORY_MESSAGE_TEMPLATE = "<related-memories>\n{{memories}}\n</related-memories>\n";
@@ -1,7 +1,5 @@
1
1
  export const MEMORY_MESSAGE_TEMPLATE = `\
2
- Here are some useful memories to consider:
3
-
4
- <memories>
2
+ <related-memories>
5
3
  {{memories}}
6
- </memories>
4
+ </related-memories>
7
5
  `;
@@ -0,0 +1,7 @@
1
+ import { parse } from "yaml";
2
+ export declare const STRUCTURED_STREAM_INSTRUCTIONS: {
3
+ instructions: string;
4
+ metadataStart: string;
5
+ metadataEnd: string;
6
+ parse: typeof parse;
7
+ };
@@ -0,0 +1,24 @@
1
+ import { parse } from "yaml";
2
+ export const STRUCTURED_STREAM_INSTRUCTIONS = {
3
+ instructions: `\
4
+ <output-rules>
5
+ - First, output the regular response content.
6
+ - At the end of the response, use <metadata></metadata> tags to output metadata. The metadata should be output in YAML format as structured data, and must conform to the format defined in <metadata-schema></metadata-schema>.
7
+ </output-rules>
8
+
9
+ <metadata-schema>
10
+ {{outputJsonSchema}}
11
+ </metadata-schema>
12
+
13
+ <output-example>
14
+ Here is the regular response content
15
+ <metadata>
16
+ foo: bar
17
+ baz: 123
18
+ </metadata>
19
+ </output-example>
20
+ `,
21
+ metadataStart: "<metadata>",
22
+ metadataEnd: "</metadata>",
23
+ parse,
24
+ };
@@ -1,16 +1,30 @@
1
+ import nunjucks, { type Callback, type LoaderSource } from "nunjucks";
1
2
  import type { ChatModelInputMessage, ChatModelInputMessageContent, ChatModelOutputToolCall } from "../agents/chat-model.js";
3
+ export interface FormatOptions {
4
+ workingDir?: string;
5
+ }
2
6
  export declare class PromptTemplate {
3
7
  template: string;
4
8
  static from(template: string): PromptTemplate;
5
9
  constructor(template: string);
6
- format(variables?: Record<string, unknown>): string;
10
+ format(variables?: Record<string, unknown>, options?: FormatOptions): Promise<string>;
11
+ }
12
+ export declare class CustomLoader extends nunjucks.Loader {
13
+ options: {
14
+ workingDir: string;
15
+ };
16
+ constructor(options: {
17
+ workingDir: string;
18
+ });
19
+ async: boolean;
20
+ getSource(name: string, callback: Callback<Error, LoaderSource>): LoaderSource;
7
21
  }
8
22
  export declare class ChatMessageTemplate {
9
23
  role: "system" | "user" | "agent" | "tool";
10
24
  content?: ChatModelInputMessage["content"];
11
25
  name?: string | undefined;
12
26
  constructor(role: "system" | "user" | "agent" | "tool", content?: ChatModelInputMessage["content"], name?: string | undefined);
13
- format(variables?: Record<string, unknown>): ChatModelInputMessage;
27
+ format(variables?: Record<string, unknown>, options?: FormatOptions): Promise<ChatModelInputMessage>;
14
28
  }
15
29
  export declare class SystemMessageTemplate extends ChatMessageTemplate {
16
30
  static from(content: string, name?: string): SystemMessageTemplate;
@@ -22,19 +36,19 @@ export declare class AgentMessageTemplate extends ChatMessageTemplate {
22
36
  toolCalls?: ChatModelOutputToolCall[] | undefined;
23
37
  static from(template?: ChatModelInputMessage["content"], toolCalls?: ChatModelOutputToolCall[], name?: string): AgentMessageTemplate;
24
38
  constructor(content?: ChatModelInputMessage["content"], toolCalls?: ChatModelOutputToolCall[] | undefined, name?: string);
25
- format(variables?: Record<string, unknown>): {
39
+ format(variables?: Record<string, unknown>, options?: FormatOptions): Promise<{
26
40
  toolCalls: ChatModelOutputToolCall[] | undefined;
27
41
  role: import("../agents/chat-model.js").Role;
28
42
  content?: ChatModelInputMessageContent;
29
43
  toolCallId?: string;
30
44
  name?: string;
31
- };
45
+ }>;
32
46
  }
33
47
  export declare class ToolMessageTemplate extends ChatMessageTemplate {
34
48
  toolCallId: string;
35
49
  static from(content: object | string, toolCallId: string, name?: string): ToolMessageTemplate;
36
50
  constructor(content: object | string, toolCallId: string, name?: string);
37
- format(variables?: Record<string, unknown>): {
51
+ format(variables?: Record<string, unknown>, options?: FormatOptions): Promise<{
38
52
  toolCallId: string;
39
53
  role: import("../agents/chat-model.js").Role;
40
54
  content?: ChatModelInputMessageContent;
@@ -47,12 +61,12 @@ export declare class ToolMessageTemplate extends ChatMessageTemplate {
47
61
  };
48
62
  }[];
49
63
  name?: string;
50
- };
64
+ }>;
51
65
  }
52
66
  export declare class ChatMessagesTemplate {
53
67
  messages: ChatMessageTemplate[];
54
68
  static from(messages: ChatMessageTemplate[] | string): ChatMessagesTemplate;
55
69
  constructor(messages: ChatMessageTemplate[]);
56
- format(variables?: Record<string, unknown>): ChatModelInputMessage[];
70
+ format(variables?: Record<string, unknown>, options?: FormatOptions): Promise<ChatModelInputMessage[]>;
57
71
  }
58
72
  export declare function parseChatMessages(messages: unknown): ChatMessageTemplate[] | undefined;