@aigne/core 1.58.2 → 1.59.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
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.59.0](https://github.com/AIGNE-io/aigne-framework/compare/core-v1.58.3...core-v1.59.0) (2025-09-09)
4
+
5
+
6
+ ### Features
7
+
8
+ * support custom prefer input file type ([#469](https://github.com/AIGNE-io/aigne-framework/issues/469)) ([db0161b](https://github.com/AIGNE-io/aigne-framework/commit/db0161bbac52542c771ee2f40f361636b0668075))
9
+
10
+ ## [1.58.3](https://github.com/AIGNE-io/aigne-framework/compare/core-v1.58.2...core-v1.58.3) (2025-09-08)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * handle absolute paths in agent YAML prompt URLs ([#466](https://github.com/AIGNE-io/aigne-framework/issues/466)) ([a07a088](https://github.com/AIGNE-io/aigne-framework/commit/a07a0880728f65fc831578763b62ce5144d1aed8))
16
+ * support optional field sturectured output for gemini ([#468](https://github.com/AIGNE-io/aigne-framework/issues/468)) ([70c6279](https://github.com/AIGNE-io/aigne-framework/commit/70c62795039a2862e3333f26707329489bf938de))
17
+
3
18
  ## [1.58.2](https://github.com/AIGNE-io/aigne-framework/compare/core-v1.58.1...core-v1.58.2) (2025-09-05)
4
19
 
5
20
 
@@ -1,6 +1,6 @@
1
1
  import { nodejs } from "@aigne/platform-helpers/nodejs/index.js";
2
2
  import type * as prompts from "@inquirer/prompts";
3
- import { ZodObject, type ZodType } from "zod";
3
+ import { type ZodObject, type ZodType } from "zod";
4
4
  import type { AgentEvent, Context, UserContext } from "../aigne/context.js";
5
5
  import type { MessagePayload } from "../aigne/message-queue.js";
6
6
  import type { ContextUsage } from "../aigne/usage.js";
@@ -51,6 +51,7 @@ const fast_deep_equal_1 = __importDefault(require("fast-deep-equal"));
51
51
  const nunjucks_1 = __importDefault(require("nunjucks"));
52
52
  const zod_1 = require("zod");
53
53
  const agent_utils_js_1 = require("../utils/agent-utils.js");
54
+ const json_schema_js_1 = require("../utils/json-schema.js");
54
55
  const logger_js_1 = require("../utils/logger.js");
55
56
  const stream_utils_js_1 = require("../utils/stream-utils.js");
56
57
  const type_utils_js_1 = require("../utils/type-utils.js");
@@ -748,7 +749,7 @@ async function agentProcessResultToObject(response) {
748
749
  : response;
749
750
  }
750
751
  function checkAgentInputOutputSchema(schema) {
751
- if (!(schema instanceof zod_1.ZodObject) && typeof schema !== "function") {
752
+ if (typeof schema !== "function" && !(0, json_schema_js_1.isZodSchema)(schema)) {
752
753
  throw new Error(`schema must be a zod object or function return a zod object, got: ${typeof schema}`);
753
754
  }
754
755
  }
@@ -3,6 +3,10 @@ import { type PromiseOrValue } from "../utils/type-utils.js";
3
3
  import { Agent, type AgentInvokeOptions, type AgentOptions, type AgentProcessResult, type AgentResponse, type AgentResponseStream, type Message } from "./agent.js";
4
4
  export declare class StructuredOutputError extends Error {
5
5
  }
6
+ export interface ChatModelOptions extends Omit<AgentOptions<ChatModelInput, ChatModelOutput>, "inputSchema" | "outputSchema"> {
7
+ model?: string;
8
+ modelOptions?: Omit<ModelOptions, "model">;
9
+ }
6
10
  /**
7
11
  * ChatModel is an abstract base class for interacting with Large Language Models (LLMs).
8
12
  *
@@ -27,8 +31,9 @@ export declare class StructuredOutputError extends Error {
27
31
  * {@includeCode ../../test/agents/chat-model.test.ts#example-chat-model-tools}
28
32
  */
29
33
  export declare abstract class ChatModel extends Agent<ChatModelInput, ChatModelOutput> {
34
+ options?: ChatModelOptions | undefined;
30
35
  tag: string;
31
- constructor(options?: Omit<AgentOptions<ChatModelInput, ChatModelOutput>, "inputSchema" | "outputSchema">);
36
+ constructor(options?: ChatModelOptions | undefined);
32
37
  get credential(): PromiseOrValue<{
33
38
  url?: string;
34
39
  apiKey?: string;
@@ -145,7 +150,7 @@ export interface ChatModelInput extends Message {
145
150
  /**
146
151
  * Model-specific configuration options
147
152
  */
148
- modelOptions?: ChatModelOptions;
153
+ modelOptions?: ModelOptions;
149
154
  }
150
155
  /**
151
156
  * Message role types
@@ -578,7 +583,7 @@ export type Modality = "text" | "image" | "audio";
578
583
  *
579
584
  * Contains various parameters for controlling model behavior, such as model name, temperature, etc.
580
585
  */
581
- export interface ChatModelOptions {
586
+ export interface ModelOptions {
582
587
  /**
583
588
  * Model name or version
584
589
  */
@@ -604,6 +609,7 @@ export interface ChatModelOptions {
604
609
  */
605
610
  parallelToolCalls?: boolean;
606
611
  modalities?: Modality[];
612
+ preferFileInputType?: "file" | "url";
607
613
  }
608
614
  /**
609
615
  * Output message format for ChatModel
@@ -76,10 +76,11 @@ exports.StructuredOutputError = StructuredOutputError;
76
76
  * {@includeCode ../../test/agents/chat-model.test.ts#example-chat-model-tools}
77
77
  */
78
78
  class ChatModel extends agent_js_1.Agent {
79
+ options;
79
80
  tag = "ChatModelAgent";
80
81
  constructor(options) {
81
82
  if (options)
82
- (0, type_utils_js_1.checkArguments)("ChatModel", agent_js_1.agentOptionsSchema, options);
83
+ (0, type_utils_js_1.checkArguments)("ChatModel", chatModelOptionsSchema, options);
83
84
  const retryOnError = options?.retryOnError === false
84
85
  ? false
85
86
  : options?.retryOnError === true
@@ -94,6 +95,7 @@ class ChatModel extends agent_js_1.Agent {
94
95
  outputSchema: chatModelOutputSchema,
95
96
  retryOnError,
96
97
  });
98
+ this.options = options;
97
99
  }
98
100
  get credential() {
99
101
  return {};
@@ -188,6 +190,18 @@ class ChatModel extends agent_js_1.Agent {
188
190
  mimeType: item.mimeType || ChatModel.getMimeType(item.filename || item.path),
189
191
  };
190
192
  }
193
+ if ((input.modelOptions?.preferFileInputType ||
194
+ this.options?.modelOptions?.preferFileInputType) !== "url") {
195
+ if (item.type === "url") {
196
+ return {
197
+ ...item,
198
+ type: "file",
199
+ data: Buffer.from(await (await this.downloadFile(item.url)).arrayBuffer()).toString("base64"),
200
+ url: undefined,
201
+ mimeType: item.mimeType || ChatModel.getMimeType(item.filename || item.url),
202
+ };
203
+ }
204
+ }
191
205
  return item;
192
206
  })),
193
207
  };
@@ -216,14 +230,6 @@ class ChatModel extends agent_js_1.Agent {
216
230
  }
217
231
  }
218
232
  }
219
- if (input.responseFormat?.type === "json_schema" &&
220
- // NOTE: Should not validate if there are tool calls
221
- !output.toolCalls?.length) {
222
- const ajv = new ajv_1.Ajv();
223
- if (!ajv.validate(input.responseFormat.jsonSchema.schema, output.json)) {
224
- throw new StructuredOutputError(`Output JSON does not conform to the provided JSON schema: ${ajv.errorsText()}`);
225
- }
226
- }
227
233
  super.postprocess(input, output, options);
228
234
  const { usage } = output;
229
235
  if (usage) {
@@ -241,6 +247,16 @@ class ChatModel extends agent_js_1.Agent {
241
247
  files: await Promise.all(files.map((file) => this.transformFileOutput(input, file, options))),
242
248
  };
243
249
  }
250
+ // Remove fields with `null` value for validation
251
+ output = (0, type_utils_js_1.omitByDeep)(output, (value) => (0, type_utils_js_1.isNil)(value));
252
+ if (input.responseFormat?.type === "json_schema" &&
253
+ // NOTE: Should not validate if there are tool calls
254
+ !output.toolCalls?.length) {
255
+ const ajv = new ajv_1.Ajv();
256
+ if (!ajv.validate(input.responseFormat.jsonSchema.schema, output.json)) {
257
+ throw new StructuredOutputError(`Output JSON does not conform to the provided JSON schema: ${ajv.errorsText()}`);
258
+ }
259
+ }
244
260
  return super.processAgentOutput(input, output, options);
245
261
  }
246
262
  async transformFileOutput(input, data, options) {
@@ -377,7 +393,7 @@ const chatModelInputToolChoiceSchema = zod_1.z.union([
377
393
  zod_1.z.literal("required"),
378
394
  chatModelInputToolSchema,
379
395
  ]);
380
- const chatModelOptionsSchema = zod_1.z.object({
396
+ const modelOptionsSchema = zod_1.z.object({
381
397
  model: zod_1.z.string().optional(),
382
398
  temperature: zod_1.z.number().optional(),
383
399
  topP: zod_1.z.number().optional(),
@@ -386,12 +402,16 @@ const chatModelOptionsSchema = zod_1.z.object({
386
402
  parallelToolCalls: zod_1.z.boolean().optional().default(true),
387
403
  modalities: zod_1.z.array(zod_1.z.enum(["text", "image", "audio"])).optional(),
388
404
  });
405
+ const chatModelOptionsSchema = agent_js_1.agentOptionsSchema.extend({
406
+ model: zod_1.z.string().optional(),
407
+ modelOptions: modelOptionsSchema.optional(),
408
+ });
389
409
  const chatModelInputSchema = zod_1.z.object({
390
410
  messages: zod_1.z.array(chatModelInputMessageSchema),
391
411
  responseFormat: chatModelInputResponseFormatSchema.optional(),
392
412
  tools: zod_1.z.array(chatModelInputToolSchema).optional(),
393
413
  toolChoice: chatModelInputToolChoiceSchema.optional(),
394
- modelOptions: chatModelOptionsSchema.optional(),
414
+ modelOptions: modelOptionsSchema.optional(),
395
415
  });
396
416
  var FileOutputType;
397
417
  (function (FileOutputType) {
@@ -32,14 +32,14 @@ export declare const imageModelInputSchema: z.ZodObject<{
32
32
  }, "strip", z.ZodTypeAny, {
33
33
  prompt: string;
34
34
  model?: string | undefined;
35
- responseFormat?: "base64" | "url" | undefined;
36
35
  image?: string | string[] | undefined;
36
+ responseFormat?: "base64" | "url" | undefined;
37
37
  n?: number | undefined;
38
38
  }, {
39
39
  prompt: string;
40
40
  model?: string | undefined;
41
- responseFormat?: "base64" | "url" | undefined;
42
41
  image?: string | string[] | undefined;
42
+ responseFormat?: "base64" | "url" | undefined;
43
43
  n?: number | undefined;
44
44
  }>;
45
45
  export interface ImageModelOutput extends Message {
@@ -59,7 +59,9 @@ async function parseAgentFile(path, data) {
59
59
  ])
60
60
  .transform((v) => typeof v === "string"
61
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 }))));
62
+ : Promise.resolve(index_js_1.nodejs.path.isAbsolute(v.url)
63
+ ? v.url
64
+ : 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
65
  return (0, schema_js_1.camelizeSchema)(zod_1.z.discriminatedUnion("type", [
64
66
  zod_1.z
65
67
  .object({
@@ -68,6 +70,7 @@ async function parseAgentFile(path, data) {
68
70
  inputKey: (0, schema_js_1.optionalize)(zod_1.z.string()),
69
71
  outputKey: (0, schema_js_1.optionalize)(zod_1.z.string()),
70
72
  toolChoice: (0, schema_js_1.optionalize)(zod_1.z.nativeEnum(ai_agent_js_1.AIAgentToolChoice)),
73
+ structuredStreamMode: (0, schema_js_1.optionalize)(zod_1.z.boolean()),
71
74
  })
72
75
  .extend(baseAgentSchema.shape),
73
76
  zod_1.z
@@ -10,3 +10,4 @@ export declare function parseJSON(json: string): any;
10
10
  * @returns The union array with at least 1 item (but the type is at least 2 items for z.union)
11
11
  */
12
12
  export declare function ensureZodUnionArray<T extends z.ZodType>(union: T[]): [T, T, ...T[]];
13
+ export declare function isZodSchema(schema: ZodType): schema is ZodType;
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.outputSchemaToResponseFormatSchema = outputSchemaToResponseFormatSchema;
4
4
  exports.parseJSON = parseJSON;
5
5
  exports.ensureZodUnionArray = ensureZodUnionArray;
6
+ exports.isZodSchema = isZodSchema;
6
7
  const zod_to_json_schema_1 = require("zod-to-json-schema");
7
8
  const logger_js_1 = require("./logger.js");
8
9
  function outputSchemaToResponseFormatSchema(agentOutput) {
@@ -45,3 +46,8 @@ function ensureZodUnionArray(union) {
45
46
  }
46
47
  return union;
47
48
  }
49
+ function isZodSchema(schema) {
50
+ if (!schema || typeof schema !== "object")
51
+ return false;
52
+ return typeof schema.parse === "function" && typeof schema.safeParse === "function";
53
+ }
@@ -19,6 +19,7 @@ export declare function pick<T extends object, K extends keyof T>(obj: T, ...key
19
19
  export declare function omit<T extends object, K extends keyof T>(obj: T, ...keys: (K | K[])[]): Omit<T, K>;
20
20
  export declare function omitDeep<T, K>(obj: T, ...keys: (K | K[])[]): unknown;
21
21
  export declare function omitBy<T extends object, K extends keyof T>(obj: T, predicate: (value: T[K], key: K) => boolean): Partial<T>;
22
+ export declare function omitByDeep(obj: any, predicate: (value: any, key: any) => boolean): any;
22
23
  export declare function flat<T>(...value: (T | T[])[]): NonNullable<T>[];
23
24
  export declare function createAccessorArray<T>(array: T[], accessor: (array: T[], name: string) => T | undefined): T[] & {
24
25
  [key: string]: T;
@@ -12,6 +12,7 @@ exports.pick = pick;
12
12
  exports.omit = omit;
13
13
  exports.omitDeep = omitDeep;
14
14
  exports.omitBy = omitBy;
15
+ exports.omitByDeep = omitByDeep;
15
16
  exports.flat = flat;
16
17
  exports.createAccessorArray = createAccessorArray;
17
18
  exports.checkArguments = checkArguments;
@@ -102,6 +103,24 @@ function omitBy(obj, predicate) {
102
103
  return !predicate(value, k);
103
104
  }));
104
105
  }
106
+ function omitByDeep(obj, predicate) {
107
+ if (obj === null || obj === undefined)
108
+ return obj;
109
+ if (Array.isArray(obj)) {
110
+ return obj.map((item) => omitByDeep(item, predicate));
111
+ }
112
+ if (typeof obj === "object") {
113
+ const result = {};
114
+ for (const [key, value] of Object.entries(obj)) {
115
+ const newValue = omitByDeep(value, predicate);
116
+ if (!predicate(newValue, key)) {
117
+ result[key] = newValue;
118
+ }
119
+ }
120
+ return result;
121
+ }
122
+ return obj;
123
+ }
105
124
  function flat(...value) {
106
125
  return value.flat().filter(isNonNullable);
107
126
  }
@@ -1,6 +1,6 @@
1
1
  import { nodejs } from "@aigne/platform-helpers/nodejs/index.js";
2
2
  import type * as prompts from "@inquirer/prompts";
3
- import { ZodObject, type ZodType } from "zod";
3
+ import { type ZodObject, type ZodType } from "zod";
4
4
  import type { AgentEvent, Context, UserContext } from "../aigne/context.js";
5
5
  import type { MessagePayload } from "../aigne/message-queue.js";
6
6
  import type { ContextUsage } from "../aigne/usage.js";
@@ -3,6 +3,10 @@ import { type PromiseOrValue } from "../utils/type-utils.js";
3
3
  import { Agent, type AgentInvokeOptions, type AgentOptions, type AgentProcessResult, type AgentResponse, type AgentResponseStream, type Message } from "./agent.js";
4
4
  export declare class StructuredOutputError extends Error {
5
5
  }
6
+ export interface ChatModelOptions extends Omit<AgentOptions<ChatModelInput, ChatModelOutput>, "inputSchema" | "outputSchema"> {
7
+ model?: string;
8
+ modelOptions?: Omit<ModelOptions, "model">;
9
+ }
6
10
  /**
7
11
  * ChatModel is an abstract base class for interacting with Large Language Models (LLMs).
8
12
  *
@@ -27,8 +31,9 @@ export declare class StructuredOutputError extends Error {
27
31
  * {@includeCode ../../test/agents/chat-model.test.ts#example-chat-model-tools}
28
32
  */
29
33
  export declare abstract class ChatModel extends Agent<ChatModelInput, ChatModelOutput> {
34
+ options?: ChatModelOptions | undefined;
30
35
  tag: string;
31
- constructor(options?: Omit<AgentOptions<ChatModelInput, ChatModelOutput>, "inputSchema" | "outputSchema">);
36
+ constructor(options?: ChatModelOptions | undefined);
32
37
  get credential(): PromiseOrValue<{
33
38
  url?: string;
34
39
  apiKey?: string;
@@ -145,7 +150,7 @@ export interface ChatModelInput extends Message {
145
150
  /**
146
151
  * Model-specific configuration options
147
152
  */
148
- modelOptions?: ChatModelOptions;
153
+ modelOptions?: ModelOptions;
149
154
  }
150
155
  /**
151
156
  * Message role types
@@ -578,7 +583,7 @@ export type Modality = "text" | "image" | "audio";
578
583
  *
579
584
  * Contains various parameters for controlling model behavior, such as model name, temperature, etc.
580
585
  */
581
- export interface ChatModelOptions {
586
+ export interface ModelOptions {
582
587
  /**
583
588
  * Model name or version
584
589
  */
@@ -604,6 +609,7 @@ export interface ChatModelOptions {
604
609
  */
605
610
  parallelToolCalls?: boolean;
606
611
  modalities?: Modality[];
612
+ preferFileInputType?: "file" | "url";
607
613
  }
608
614
  /**
609
615
  * Output message format for ChatModel
@@ -32,14 +32,14 @@ export declare const imageModelInputSchema: z.ZodObject<{
32
32
  }, "strip", z.ZodTypeAny, {
33
33
  prompt: string;
34
34
  model?: string | undefined;
35
- responseFormat?: "base64" | "url" | undefined;
36
35
  image?: string | string[] | undefined;
36
+ responseFormat?: "base64" | "url" | undefined;
37
37
  n?: number | undefined;
38
38
  }, {
39
39
  prompt: string;
40
40
  model?: string | undefined;
41
- responseFormat?: "base64" | "url" | undefined;
42
41
  image?: string | string[] | undefined;
42
+ responseFormat?: "base64" | "url" | undefined;
43
43
  n?: number | undefined;
44
44
  }>;
45
45
  export interface ImageModelOutput extends Message {
@@ -10,3 +10,4 @@ export declare function parseJSON(json: string): any;
10
10
  * @returns The union array with at least 1 item (but the type is at least 2 items for z.union)
11
11
  */
12
12
  export declare function ensureZodUnionArray<T extends z.ZodType>(union: T[]): [T, T, ...T[]];
13
+ export declare function isZodSchema(schema: ZodType): schema is ZodType;
@@ -19,6 +19,7 @@ export declare function pick<T extends object, K extends keyof T>(obj: T, ...key
19
19
  export declare function omit<T extends object, K extends keyof T>(obj: T, ...keys: (K | K[])[]): Omit<T, K>;
20
20
  export declare function omitDeep<T, K>(obj: T, ...keys: (K | K[])[]): unknown;
21
21
  export declare function omitBy<T extends object, K extends keyof T>(obj: T, predicate: (value: T[K], key: K) => boolean): Partial<T>;
22
+ export declare function omitByDeep(obj: any, predicate: (value: any, key: any) => boolean): any;
22
23
  export declare function flat<T>(...value: (T | T[])[]): NonNullable<T>[];
23
24
  export declare function createAccessorArray<T>(array: T[], accessor: (array: T[], name: string) => T | undefined): T[] & {
24
25
  [key: string]: T;
@@ -1,6 +1,6 @@
1
1
  import { nodejs } from "@aigne/platform-helpers/nodejs/index.js";
2
2
  import type * as prompts from "@inquirer/prompts";
3
- import { ZodObject, type ZodType } from "zod";
3
+ import { type ZodObject, type ZodType } from "zod";
4
4
  import type { AgentEvent, Context, UserContext } from "../aigne/context.js";
5
5
  import type { MessagePayload } from "../aigne/message-queue.js";
6
6
  import type { ContextUsage } from "../aigne/usage.js";
@@ -1,8 +1,9 @@
1
1
  import { nodejs } from "@aigne/platform-helpers/nodejs/index.js";
2
2
  import equal from "fast-deep-equal";
3
3
  import nunjucks from "nunjucks";
4
- import { ZodObject, z } from "zod";
4
+ import { z } from "zod";
5
5
  import { sortHooks } from "../utils/agent-utils.js";
6
+ import { isZodSchema } from "../utils/json-schema.js";
6
7
  import { logger } from "../utils/logger.js";
7
8
  import { agentResponseStreamToObject, asyncGeneratorToReadableStream, isAsyncGenerator, mergeAgentResponseChunk, objectToAgentResponseStream, onAgentResponseStreamEnd, } from "../utils/stream-utils.js";
8
9
  import { checkArguments, createAccessorArray, flat, isEmpty, isNil, isNonNullable, isRecord, } from "../utils/type-utils.js";
@@ -699,7 +700,7 @@ export async function agentProcessResultToObject(response) {
699
700
  : response;
700
701
  }
701
702
  function checkAgentInputOutputSchema(schema) {
702
- if (!(schema instanceof ZodObject) && typeof schema !== "function") {
703
+ if (typeof schema !== "function" && !isZodSchema(schema)) {
703
704
  throw new Error(`schema must be a zod object or function return a zod object, got: ${typeof schema}`);
704
705
  }
705
706
  }
@@ -3,6 +3,10 @@ import { type PromiseOrValue } from "../utils/type-utils.js";
3
3
  import { Agent, type AgentInvokeOptions, type AgentOptions, type AgentProcessResult, type AgentResponse, type AgentResponseStream, type Message } from "./agent.js";
4
4
  export declare class StructuredOutputError extends Error {
5
5
  }
6
+ export interface ChatModelOptions extends Omit<AgentOptions<ChatModelInput, ChatModelOutput>, "inputSchema" | "outputSchema"> {
7
+ model?: string;
8
+ modelOptions?: Omit<ModelOptions, "model">;
9
+ }
6
10
  /**
7
11
  * ChatModel is an abstract base class for interacting with Large Language Models (LLMs).
8
12
  *
@@ -27,8 +31,9 @@ export declare class StructuredOutputError extends Error {
27
31
  * {@includeCode ../../test/agents/chat-model.test.ts#example-chat-model-tools}
28
32
  */
29
33
  export declare abstract class ChatModel extends Agent<ChatModelInput, ChatModelOutput> {
34
+ options?: ChatModelOptions | undefined;
30
35
  tag: string;
31
- constructor(options?: Omit<AgentOptions<ChatModelInput, ChatModelOutput>, "inputSchema" | "outputSchema">);
36
+ constructor(options?: ChatModelOptions | undefined);
32
37
  get credential(): PromiseOrValue<{
33
38
  url?: string;
34
39
  apiKey?: string;
@@ -145,7 +150,7 @@ export interface ChatModelInput extends Message {
145
150
  /**
146
151
  * Model-specific configuration options
147
152
  */
148
- modelOptions?: ChatModelOptions;
153
+ modelOptions?: ModelOptions;
149
154
  }
150
155
  /**
151
156
  * Message role types
@@ -578,7 +583,7 @@ export type Modality = "text" | "image" | "audio";
578
583
  *
579
584
  * Contains various parameters for controlling model behavior, such as model name, temperature, etc.
580
585
  */
581
- export interface ChatModelOptions {
586
+ export interface ModelOptions {
582
587
  /**
583
588
  * Model name or version
584
589
  */
@@ -604,6 +609,7 @@ export interface ChatModelOptions {
604
609
  */
605
610
  parallelToolCalls?: boolean;
606
611
  modalities?: Modality[];
612
+ preferFileInputType?: "file" | "url";
607
613
  }
608
614
  /**
609
615
  * Output message format for ChatModel
@@ -4,7 +4,7 @@ import mime from "mime";
4
4
  import { v7 } from "uuid";
5
5
  import { z } from "zod";
6
6
  import { optionalize } from "../loader/schema.js";
7
- import { checkArguments, pick } from "../utils/type-utils.js";
7
+ import { checkArguments, isNil, omitByDeep, pick, } from "../utils/type-utils.js";
8
8
  import { Agent, agentOptionsSchema, } from "./agent.js";
9
9
  const CHAT_MODEL_DEFAULT_RETRY_OPTIONS = {
10
10
  retries: 3,
@@ -36,10 +36,11 @@ export class StructuredOutputError extends Error {
36
36
  * {@includeCode ../../test/agents/chat-model.test.ts#example-chat-model-tools}
37
37
  */
38
38
  export class ChatModel extends Agent {
39
+ options;
39
40
  tag = "ChatModelAgent";
40
41
  constructor(options) {
41
42
  if (options)
42
- checkArguments("ChatModel", agentOptionsSchema, options);
43
+ checkArguments("ChatModel", chatModelOptionsSchema, options);
43
44
  const retryOnError = options?.retryOnError === false
44
45
  ? false
45
46
  : options?.retryOnError === true
@@ -54,6 +55,7 @@ export class ChatModel extends Agent {
54
55
  outputSchema: chatModelOutputSchema,
55
56
  retryOnError,
56
57
  });
58
+ this.options = options;
57
59
  }
58
60
  get credential() {
59
61
  return {};
@@ -148,6 +150,18 @@ export class ChatModel extends Agent {
148
150
  mimeType: item.mimeType || ChatModel.getMimeType(item.filename || item.path),
149
151
  };
150
152
  }
153
+ if ((input.modelOptions?.preferFileInputType ||
154
+ this.options?.modelOptions?.preferFileInputType) !== "url") {
155
+ if (item.type === "url") {
156
+ return {
157
+ ...item,
158
+ type: "file",
159
+ data: Buffer.from(await (await this.downloadFile(item.url)).arrayBuffer()).toString("base64"),
160
+ url: undefined,
161
+ mimeType: item.mimeType || ChatModel.getMimeType(item.filename || item.url),
162
+ };
163
+ }
164
+ }
151
165
  return item;
152
166
  })),
153
167
  };
@@ -176,14 +190,6 @@ export class ChatModel extends Agent {
176
190
  }
177
191
  }
178
192
  }
179
- if (input.responseFormat?.type === "json_schema" &&
180
- // NOTE: Should not validate if there are tool calls
181
- !output.toolCalls?.length) {
182
- const ajv = new Ajv();
183
- if (!ajv.validate(input.responseFormat.jsonSchema.schema, output.json)) {
184
- throw new StructuredOutputError(`Output JSON does not conform to the provided JSON schema: ${ajv.errorsText()}`);
185
- }
186
- }
187
193
  super.postprocess(input, output, options);
188
194
  const { usage } = output;
189
195
  if (usage) {
@@ -201,6 +207,16 @@ export class ChatModel extends Agent {
201
207
  files: await Promise.all(files.map((file) => this.transformFileOutput(input, file, options))),
202
208
  };
203
209
  }
210
+ // Remove fields with `null` value for validation
211
+ output = omitByDeep(output, (value) => isNil(value));
212
+ if (input.responseFormat?.type === "json_schema" &&
213
+ // NOTE: Should not validate if there are tool calls
214
+ !output.toolCalls?.length) {
215
+ const ajv = new Ajv();
216
+ if (!ajv.validate(input.responseFormat.jsonSchema.schema, output.json)) {
217
+ throw new StructuredOutputError(`Output JSON does not conform to the provided JSON schema: ${ajv.errorsText()}`);
218
+ }
219
+ }
204
220
  return super.processAgentOutput(input, output, options);
205
221
  }
206
222
  async transformFileOutput(input, data, options) {
@@ -336,7 +352,7 @@ const chatModelInputToolChoiceSchema = z.union([
336
352
  z.literal("required"),
337
353
  chatModelInputToolSchema,
338
354
  ]);
339
- const chatModelOptionsSchema = z.object({
355
+ const modelOptionsSchema = z.object({
340
356
  model: z.string().optional(),
341
357
  temperature: z.number().optional(),
342
358
  topP: z.number().optional(),
@@ -345,12 +361,16 @@ const chatModelOptionsSchema = z.object({
345
361
  parallelToolCalls: z.boolean().optional().default(true),
346
362
  modalities: z.array(z.enum(["text", "image", "audio"])).optional(),
347
363
  });
364
+ const chatModelOptionsSchema = agentOptionsSchema.extend({
365
+ model: z.string().optional(),
366
+ modelOptions: modelOptionsSchema.optional(),
367
+ });
348
368
  const chatModelInputSchema = z.object({
349
369
  messages: z.array(chatModelInputMessageSchema),
350
370
  responseFormat: chatModelInputResponseFormatSchema.optional(),
351
371
  tools: z.array(chatModelInputToolSchema).optional(),
352
372
  toolChoice: chatModelInputToolChoiceSchema.optional(),
353
- modelOptions: chatModelOptionsSchema.optional(),
373
+ modelOptions: modelOptionsSchema.optional(),
354
374
  });
355
375
  export var FileOutputType;
356
376
  (function (FileOutputType) {
@@ -32,14 +32,14 @@ export declare const imageModelInputSchema: z.ZodObject<{
32
32
  }, "strip", z.ZodTypeAny, {
33
33
  prompt: string;
34
34
  model?: string | undefined;
35
- responseFormat?: "base64" | "url" | undefined;
36
35
  image?: string | string[] | undefined;
36
+ responseFormat?: "base64" | "url" | undefined;
37
37
  n?: number | undefined;
38
38
  }, {
39
39
  prompt: string;
40
40
  model?: string | undefined;
41
- responseFormat?: "base64" | "url" | undefined;
42
41
  image?: string | string[] | undefined;
42
+ responseFormat?: "base64" | "url" | undefined;
43
43
  n?: number | undefined;
44
44
  }>;
45
45
  export interface ImageModelOutput extends Message {
@@ -55,7 +55,9 @@ export async function parseAgentFile(path, data) {
55
55
  ])
56
56
  .transform((v) => typeof v === "string"
57
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 }))));
58
+ : Promise.resolve(nodejs.path.isAbsolute(v.url)
59
+ ? v.url
60
+ : nodejs.path.join(nodejs.path.dirname(path), v.url)).then((path) => nodejs.fs.readFile(path, "utf8").then((content) => ({ content, path }))));
59
61
  return camelizeSchema(z.discriminatedUnion("type", [
60
62
  z
61
63
  .object({
@@ -64,6 +66,7 @@ export async function parseAgentFile(path, data) {
64
66
  inputKey: optionalize(z.string()),
65
67
  outputKey: optionalize(z.string()),
66
68
  toolChoice: optionalize(z.nativeEnum(AIAgentToolChoice)),
69
+ structuredStreamMode: optionalize(z.boolean()),
67
70
  })
68
71
  .extend(baseAgentSchema.shape),
69
72
  z
@@ -10,3 +10,4 @@ export declare function parseJSON(json: string): any;
10
10
  * @returns The union array with at least 1 item (but the type is at least 2 items for z.union)
11
11
  */
12
12
  export declare function ensureZodUnionArray<T extends z.ZodType>(union: T[]): [T, T, ...T[]];
13
+ export declare function isZodSchema(schema: ZodType): schema is ZodType;
@@ -40,3 +40,8 @@ export function ensureZodUnionArray(union) {
40
40
  }
41
41
  return union;
42
42
  }
43
+ export function isZodSchema(schema) {
44
+ if (!schema || typeof schema !== "object")
45
+ return false;
46
+ return typeof schema.parse === "function" && typeof schema.safeParse === "function";
47
+ }
@@ -19,6 +19,7 @@ export declare function pick<T extends object, K extends keyof T>(obj: T, ...key
19
19
  export declare function omit<T extends object, K extends keyof T>(obj: T, ...keys: (K | K[])[]): Omit<T, K>;
20
20
  export declare function omitDeep<T, K>(obj: T, ...keys: (K | K[])[]): unknown;
21
21
  export declare function omitBy<T extends object, K extends keyof T>(obj: T, predicate: (value: T[K], key: K) => boolean): Partial<T>;
22
+ export declare function omitByDeep(obj: any, predicate: (value: any, key: any) => boolean): any;
22
23
  export declare function flat<T>(...value: (T | T[])[]): NonNullable<T>[];
23
24
  export declare function createAccessorArray<T>(array: T[], accessor: (array: T[], name: string) => T | undefined): T[] & {
24
25
  [key: string]: T;
@@ -84,6 +84,24 @@ export function omitBy(obj, predicate) {
84
84
  return !predicate(value, k);
85
85
  }));
86
86
  }
87
+ export function omitByDeep(obj, predicate) {
88
+ if (obj === null || obj === undefined)
89
+ return obj;
90
+ if (Array.isArray(obj)) {
91
+ return obj.map((item) => omitByDeep(item, predicate));
92
+ }
93
+ if (typeof obj === "object") {
94
+ const result = {};
95
+ for (const [key, value] of Object.entries(obj)) {
96
+ const newValue = omitByDeep(value, predicate);
97
+ if (!predicate(newValue, key)) {
98
+ result[key] = newValue;
99
+ }
100
+ }
101
+ return result;
102
+ }
103
+ return obj;
104
+ }
87
105
  export function flat(...value) {
88
106
  return value.flat().filter(isNonNullable);
89
107
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aigne/core",
3
- "version": "1.58.2",
3
+ "version": "1.59.0",
4
4
  "description": "The functional core of agentic AI",
5
5
  "publishConfig": {
6
6
  "access": "public"