@aigne/openai 0.11.4 → 0.12.1

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,43 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.12.1](https://github.com/AIGNE-io/aigne-framework/compare/openai-v0.12.0...openai-v0.12.1) (2025-08-21)
4
+
5
+
6
+ ### Dependencies
7
+
8
+ * The following workspace dependencies were updated
9
+ * dependencies
10
+ * @aigne/core bumped to 1.54.0
11
+ * devDependencies
12
+ * @aigne/test-utils bumped to 0.5.33
13
+
14
+ ## [0.12.0](https://github.com/AIGNE-io/aigne-framework/compare/openai-v0.11.5...openai-v0.12.0) (2025-08-20)
15
+
16
+
17
+ ### Features
18
+
19
+ * add ImageModel/ImageAgent support ([#383](https://github.com/AIGNE-io/aigne-framework/issues/383)) ([96a2093](https://github.com/AIGNE-io/aigne-framework/commit/96a209368d91d98f47db6de1e404640368a86fa8))
20
+
21
+
22
+ ### Dependencies
23
+
24
+ * The following workspace dependencies were updated
25
+ * dependencies
26
+ * @aigne/core bumped to 1.53.0
27
+ * devDependencies
28
+ * @aigne/test-utils bumped to 0.5.32
29
+
30
+ ## [0.11.5](https://github.com/AIGNE-io/aigne-framework/compare/openai-v0.11.4...openai-v0.11.5) (2025-08-20)
31
+
32
+
33
+ ### Dependencies
34
+
35
+ * The following workspace dependencies were updated
36
+ * dependencies
37
+ * @aigne/core bumped to 1.52.0
38
+ * devDependencies
39
+ * @aigne/test-utils bumped to 0.5.31
40
+
3
41
  ## [0.11.4](https://github.com/AIGNE-io/aigne-framework/compare/openai-v0.11.3...openai-v0.11.4) (2025-08-18)
4
42
 
5
43
 
@@ -1 +1,2 @@
1
1
  export * from "./openai-chat-model.js";
2
+ export * from "./openai-image-model.js";
package/lib/cjs/index.js CHANGED
@@ -15,3 +15,4 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./openai-chat-model.js"), exports);
18
+ __exportStar(require("./openai-image-model.js"), exports);
@@ -1,6 +1,6 @@
1
1
  import { type AgentProcessResult, ChatModel, type ChatModelInput, type ChatModelInputMessage, type ChatModelInputTool, type ChatModelOptions, type ChatModelOutput } from "@aigne/core";
2
2
  import { type PromiseOrValue } from "@aigne/core/utils/type-utils.js";
3
- import OpenAI, { type ClientOptions } from "openai";
3
+ import type { ClientOptions, OpenAI } from "openai";
4
4
  import type { ChatCompletionMessageParam, ChatCompletionTool } from "openai/resources";
5
5
  import { z } from "zod";
6
6
  export interface OpenAIChatModelCapabilities {
@@ -131,12 +131,12 @@ export declare class OpenAIChatModel extends ChatModel {
131
131
  protected supportsToolsEmptyParameters: boolean;
132
132
  protected supportsToolStreaming: boolean;
133
133
  protected supportsTemperature: boolean;
134
- client(): Promise<OpenAI>;
135
- getCredential(): Promise<{
134
+ get client(): OpenAI;
135
+ get credential(): {
136
136
  url: string | undefined;
137
137
  apiKey: string | undefined;
138
138
  model: string;
139
- }>;
139
+ };
140
140
  get modelOptions(): ChatModelOptions | undefined;
141
141
  /**
142
142
  * Process the input and generate a response
@@ -1,7 +1,4 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.OpenAIChatModel = exports.openAIChatModelOptionsSchema = void 0;
7
4
  exports.contentsFromInputMessages = contentsFromInputMessages;
@@ -14,9 +11,9 @@ const prompts_js_1 = require("@aigne/core/utils/prompts.js");
14
11
  const stream_utils_js_1 = require("@aigne/core/utils/stream-utils.js");
15
12
  const type_utils_js_1 = require("@aigne/core/utils/type-utils.js");
16
13
  const ajv_1 = require("ajv");
17
- const openai_1 = __importDefault(require("openai"));
18
14
  const uuid_1 = require("uuid");
19
15
  const zod_1 = require("zod");
16
+ const openai_js_1 = require("./openai.js");
20
17
  const CHAT_MODEL_OPENAI_DEFAULT_MODEL = "gpt-4o-mini";
21
18
  const OPENAI_CHAT_MODEL_CAPABILITIES = {
22
19
  "o4-mini": { supportsParallelToolCalls: false, supportsTemperature: false },
@@ -81,18 +78,18 @@ class OpenAIChatModel extends core_1.ChatModel {
81
78
  supportsToolsEmptyParameters = true;
82
79
  supportsToolStreaming = true;
83
80
  supportsTemperature = true;
84
- async client() {
85
- const { apiKey, url } = await this.getCredential();
81
+ get client() {
82
+ const { apiKey, url } = this.credential;
86
83
  if (!apiKey)
87
84
  throw new Error(`${this.name} requires an API key. Please provide it via \`options.apiKey\`, or set the \`${this.apiKeyEnvName}\` environment variable`);
88
- this._client ??= new CustomOpenAI({
85
+ this._client ??= new openai_js_1.CustomOpenAI({
89
86
  baseURL: url,
90
87
  apiKey,
91
88
  ...this.options?.clientOptions,
92
89
  });
93
90
  return this._client;
94
91
  }
95
- async getCredential() {
92
+ get credential() {
96
93
  return {
97
94
  url: this.options?.baseURL || process.env.OPENAI_BASE_URL,
98
95
  apiKey: this.options?.apiKey || process.env[this.apiKeyEnvName] || this.apiKeyDefault,
@@ -113,7 +110,7 @@ class OpenAIChatModel extends core_1.ChatModel {
113
110
  ajv = new ajv_1.Ajv();
114
111
  async _process(input) {
115
112
  const messages = await this.getRunMessages(input);
116
- const { model } = await this.getCredential();
113
+ const model = input.modelOptions?.model || this.credential.model;
117
114
  const body = {
118
115
  model,
119
116
  temperature: this.supportsTemperature
@@ -134,8 +131,7 @@ class OpenAIChatModel extends core_1.ChatModel {
134
131
  return await this.requestStructuredOutput(body, input.responseFormat);
135
132
  }
136
133
  const { jsonMode, responseFormat } = await this.getRunResponseFormat(input);
137
- const client = await this.client();
138
- const stream = (await client.chat.completions.create({
134
+ const stream = (await this.client.chat.completions.create({
139
135
  ...body,
140
136
  tools: toolsFromInputTools(input.tools, {
141
137
  addTypeToEmptyParameters: !this.supportsToolsEmptyParameters,
@@ -214,8 +210,7 @@ class OpenAIChatModel extends core_1.ChatModel {
214
210
  const { jsonMode, responseFormat: resolvedResponseFormat } = await this.getRunResponseFormat({
215
211
  responseFormat,
216
212
  });
217
- const client = await this.client();
218
- const res = (await client.chat.completions.create({
213
+ const res = (await this.client.chat.completions.create({
219
214
  ...body,
220
215
  response_format: resolvedResponseFormat,
221
216
  }));
@@ -428,13 +423,4 @@ function handleCompleteToolCall(toolCalls, call) {
428
423
  args: call.function?.arguments || "",
429
424
  });
430
425
  }
431
- // Use a custom OpenAI client to handle API errors for better error messages
432
- class CustomOpenAI extends openai_1.default {
433
- makeStatusError(status, error, message, headers) {
434
- if (!("error" in error) || typeof error.error !== "string") {
435
- message = JSON.stringify(error);
436
- }
437
- return super.makeStatusError(status, error, message, headers);
438
- }
439
- }
440
426
  // safeParseJSON is now imported from @aigne/core
@@ -0,0 +1,55 @@
1
+ import { ImageModel, type ImageModelInput, type ImageModelOptions, type ImageModelOutput } from "@aigne/core";
2
+ import { type Camelize } from "@aigne/core/utils/camelize.js";
3
+ import type OpenAI from "openai";
4
+ import type { ClientOptions } from "openai";
5
+ export interface OpenAIImageModelInput extends ImageModelInput, Camelize<Omit<OpenAI.ImageGenerateParams, "prompt" | "model" | "n" | "response_format">> {
6
+ }
7
+ export interface OpenAIImageModelOutput extends ImageModelOutput {
8
+ }
9
+ export interface OpenAIImageModelOptions extends ImageModelOptions<OpenAIImageModelInput, OpenAIImageModelOutput> {
10
+ /**
11
+ * API key for OpenAI API
12
+ *
13
+ * If not provided, will look for OPENAI_API_KEY in environment variables
14
+ */
15
+ apiKey?: string;
16
+ /**
17
+ * Base URL for OpenAI API
18
+ *
19
+ * Useful for proxies or alternate endpoints
20
+ */
21
+ baseURL?: string;
22
+ /**
23
+ * OpenAI model to use
24
+ *
25
+ * Defaults to 'dall-e-2'
26
+ */
27
+ model?: string;
28
+ /**
29
+ * Additional model options to control behavior
30
+ */
31
+ modelOptions?: Omit<Partial<OpenAIImageModelInput>, "model">;
32
+ /**
33
+ * Client options for OpenAI API
34
+ */
35
+ clientOptions?: Partial<ClientOptions>;
36
+ }
37
+ export declare class OpenAIImageModel extends ImageModel<OpenAIImageModelInput, OpenAIImageModelOutput> {
38
+ options?: OpenAIImageModelOptions | undefined;
39
+ constructor(options?: OpenAIImageModelOptions | undefined);
40
+ protected _client?: OpenAI;
41
+ protected apiKeyEnvName: string;
42
+ get client(): OpenAI;
43
+ get credential(): {
44
+ url: string | undefined;
45
+ apiKey: string | undefined;
46
+ model: string;
47
+ };
48
+ get modelOptions(): Omit<Partial<OpenAIImageModelInput>, "model"> | undefined;
49
+ /**
50
+ * Process the input and generate a response
51
+ * @param input The input to process
52
+ * @returns The generated response
53
+ */
54
+ process(input: ImageModelInput): Promise<ImageModelOutput>;
55
+ }
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OpenAIImageModel = void 0;
4
+ const core_1 = require("@aigne/core");
5
+ const camelize_js_1 = require("@aigne/core/utils/camelize.js");
6
+ const type_utils_js_1 = require("@aigne/core/utils/type-utils.js");
7
+ const zod_1 = require("zod");
8
+ const openai_js_1 = require("./openai.js");
9
+ const DEFAULT_MODEL = "dall-e-2";
10
+ const openAIImageModelInputSchema = core_1.imageModelInputSchema.extend({});
11
+ const openAIImageModelOptionsSchema = zod_1.z.object({
12
+ apiKey: zod_1.z.string().optional(),
13
+ baseURL: zod_1.z.string().optional(),
14
+ model: zod_1.z.string().optional(),
15
+ });
16
+ class OpenAIImageModel extends core_1.ImageModel {
17
+ options;
18
+ constructor(options) {
19
+ super({
20
+ ...options,
21
+ inputSchema: openAIImageModelInputSchema,
22
+ description: options?.description ?? "Draw or edit image by OpenAI image models",
23
+ });
24
+ this.options = options;
25
+ if (options)
26
+ (0, type_utils_js_1.checkArguments)(this.name, openAIImageModelOptionsSchema, options);
27
+ }
28
+ _client;
29
+ apiKeyEnvName = "OPENAI_API_KEY";
30
+ get client() {
31
+ if (this._client)
32
+ return this._client;
33
+ const { apiKey, url } = this.credential;
34
+ if (!apiKey)
35
+ throw new Error(`${this.name} requires an API key. Please provide it via \`options.apiKey\`, or set the \`${this.apiKeyEnvName}\` environment variable`);
36
+ this._client ??= new openai_js_1.CustomOpenAI({
37
+ baseURL: url,
38
+ apiKey,
39
+ ...this.options?.clientOptions,
40
+ });
41
+ return this._client;
42
+ }
43
+ get credential() {
44
+ return {
45
+ url: this.options?.baseURL || process.env.OPENAI_BASE_URL,
46
+ apiKey: this.options?.apiKey || process.env[this.apiKeyEnvName],
47
+ model: this.options?.model || DEFAULT_MODEL,
48
+ };
49
+ }
50
+ get modelOptions() {
51
+ return this.options?.modelOptions;
52
+ }
53
+ /**
54
+ * Process the input and generate a response
55
+ * @param input The input to process
56
+ * @returns The generated response
57
+ */
58
+ async process(input) {
59
+ const model = input.model || this.credential.model;
60
+ const inputKeys = [
61
+ "background",
62
+ "moderation",
63
+ "output_compression",
64
+ "output_format",
65
+ "prompt",
66
+ "quality",
67
+ "size",
68
+ "style",
69
+ "user",
70
+ ];
71
+ let responseFormat;
72
+ if (model !== "gpt-image-1") {
73
+ responseFormat = input.responseFormat === "base64" ? "b64_json" : "url";
74
+ }
75
+ const body = {
76
+ ...(0, camelize_js_1.snakelize)((0, type_utils_js_1.pick)({ ...this.modelOptions, ...input }, inputKeys)),
77
+ response_format: responseFormat,
78
+ model,
79
+ };
80
+ const response = await this.client.images.generate({ ...body });
81
+ return {
82
+ images: (response.data ?? []).map((image) => {
83
+ if (image.url)
84
+ return { url: image.url };
85
+ if (image.b64_json)
86
+ return { base64: image.b64_json };
87
+ throw new Error("Image response does not contain a valid URL or base64 data");
88
+ }),
89
+ usage: {
90
+ inputTokens: response.usage?.input_tokens || 0,
91
+ outputTokens: response.usage?.output_tokens || 0,
92
+ },
93
+ model,
94
+ };
95
+ }
96
+ }
97
+ exports.OpenAIImageModel = OpenAIImageModel;
@@ -0,0 +1,4 @@
1
+ import OpenAI, { type APIError } from "openai";
2
+ export declare class CustomOpenAI extends OpenAI {
3
+ protected makeStatusError(status: number, error: object, message: string | undefined, headers: Headers): APIError;
4
+ }
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.CustomOpenAI = void 0;
7
+ const openai_1 = __importDefault(require("openai"));
8
+ // Use a custom OpenAI client to handle API errors for better error messages
9
+ class CustomOpenAI extends openai_1.default {
10
+ makeStatusError(status, error, message, headers) {
11
+ if (!("error" in error) || typeof error.error !== "string") {
12
+ message = JSON.stringify(error);
13
+ }
14
+ return super.makeStatusError(status, error, message, headers);
15
+ }
16
+ }
17
+ exports.CustomOpenAI = CustomOpenAI;
@@ -1 +1,2 @@
1
1
  export * from "./openai-chat-model.js";
2
+ export * from "./openai-image-model.js";
@@ -1,6 +1,6 @@
1
1
  import { type AgentProcessResult, ChatModel, type ChatModelInput, type ChatModelInputMessage, type ChatModelInputTool, type ChatModelOptions, type ChatModelOutput } from "@aigne/core";
2
2
  import { type PromiseOrValue } from "@aigne/core/utils/type-utils.js";
3
- import OpenAI, { type ClientOptions } from "openai";
3
+ import type { ClientOptions, OpenAI } from "openai";
4
4
  import type { ChatCompletionMessageParam, ChatCompletionTool } from "openai/resources";
5
5
  import { z } from "zod";
6
6
  export interface OpenAIChatModelCapabilities {
@@ -131,12 +131,12 @@ export declare class OpenAIChatModel extends ChatModel {
131
131
  protected supportsToolsEmptyParameters: boolean;
132
132
  protected supportsToolStreaming: boolean;
133
133
  protected supportsTemperature: boolean;
134
- client(): Promise<OpenAI>;
135
- getCredential(): Promise<{
134
+ get client(): OpenAI;
135
+ get credential(): {
136
136
  url: string | undefined;
137
137
  apiKey: string | undefined;
138
138
  model: string;
139
- }>;
139
+ };
140
140
  get modelOptions(): ChatModelOptions | undefined;
141
141
  /**
142
142
  * Process the input and generate a response
@@ -0,0 +1,55 @@
1
+ import { ImageModel, type ImageModelInput, type ImageModelOptions, type ImageModelOutput } from "@aigne/core";
2
+ import { type Camelize } from "@aigne/core/utils/camelize.js";
3
+ import type OpenAI from "openai";
4
+ import type { ClientOptions } from "openai";
5
+ export interface OpenAIImageModelInput extends ImageModelInput, Camelize<Omit<OpenAI.ImageGenerateParams, "prompt" | "model" | "n" | "response_format">> {
6
+ }
7
+ export interface OpenAIImageModelOutput extends ImageModelOutput {
8
+ }
9
+ export interface OpenAIImageModelOptions extends ImageModelOptions<OpenAIImageModelInput, OpenAIImageModelOutput> {
10
+ /**
11
+ * API key for OpenAI API
12
+ *
13
+ * If not provided, will look for OPENAI_API_KEY in environment variables
14
+ */
15
+ apiKey?: string;
16
+ /**
17
+ * Base URL for OpenAI API
18
+ *
19
+ * Useful for proxies or alternate endpoints
20
+ */
21
+ baseURL?: string;
22
+ /**
23
+ * OpenAI model to use
24
+ *
25
+ * Defaults to 'dall-e-2'
26
+ */
27
+ model?: string;
28
+ /**
29
+ * Additional model options to control behavior
30
+ */
31
+ modelOptions?: Omit<Partial<OpenAIImageModelInput>, "model">;
32
+ /**
33
+ * Client options for OpenAI API
34
+ */
35
+ clientOptions?: Partial<ClientOptions>;
36
+ }
37
+ export declare class OpenAIImageModel extends ImageModel<OpenAIImageModelInput, OpenAIImageModelOutput> {
38
+ options?: OpenAIImageModelOptions | undefined;
39
+ constructor(options?: OpenAIImageModelOptions | undefined);
40
+ protected _client?: OpenAI;
41
+ protected apiKeyEnvName: string;
42
+ get client(): OpenAI;
43
+ get credential(): {
44
+ url: string | undefined;
45
+ apiKey: string | undefined;
46
+ model: string;
47
+ };
48
+ get modelOptions(): Omit<Partial<OpenAIImageModelInput>, "model"> | undefined;
49
+ /**
50
+ * Process the input and generate a response
51
+ * @param input The input to process
52
+ * @returns The generated response
53
+ */
54
+ process(input: ImageModelInput): Promise<ImageModelOutput>;
55
+ }
@@ -0,0 +1,4 @@
1
+ import OpenAI, { type APIError } from "openai";
2
+ export declare class CustomOpenAI extends OpenAI {
3
+ protected makeStatusError(status: number, error: object, message: string | undefined, headers: Headers): APIError;
4
+ }
@@ -1 +1,2 @@
1
1
  export * from "./openai-chat-model.js";
2
+ export * from "./openai-image-model.js";
package/lib/esm/index.js CHANGED
@@ -1 +1,2 @@
1
1
  export * from "./openai-chat-model.js";
2
+ export * from "./openai-image-model.js";
@@ -1,6 +1,6 @@
1
1
  import { type AgentProcessResult, ChatModel, type ChatModelInput, type ChatModelInputMessage, type ChatModelInputTool, type ChatModelOptions, type ChatModelOutput } from "@aigne/core";
2
2
  import { type PromiseOrValue } from "@aigne/core/utils/type-utils.js";
3
- import OpenAI, { type ClientOptions } from "openai";
3
+ import type { ClientOptions, OpenAI } from "openai";
4
4
  import type { ChatCompletionMessageParam, ChatCompletionTool } from "openai/resources";
5
5
  import { z } from "zod";
6
6
  export interface OpenAIChatModelCapabilities {
@@ -131,12 +131,12 @@ export declare class OpenAIChatModel extends ChatModel {
131
131
  protected supportsToolsEmptyParameters: boolean;
132
132
  protected supportsToolStreaming: boolean;
133
133
  protected supportsTemperature: boolean;
134
- client(): Promise<OpenAI>;
135
- getCredential(): Promise<{
134
+ get client(): OpenAI;
135
+ get credential(): {
136
136
  url: string | undefined;
137
137
  apiKey: string | undefined;
138
138
  model: string;
139
- }>;
139
+ };
140
140
  get modelOptions(): ChatModelOptions | undefined;
141
141
  /**
142
142
  * Process the input and generate a response
@@ -5,9 +5,9 @@ import { getJsonOutputPrompt } from "@aigne/core/utils/prompts.js";
5
5
  import { agentResponseStreamToObject } from "@aigne/core/utils/stream-utils.js";
6
6
  import { checkArguments, isNonNullable, } from "@aigne/core/utils/type-utils.js";
7
7
  import { Ajv } from "ajv";
8
- import OpenAI from "openai";
9
8
  import { v7 } from "uuid";
10
9
  import { z } from "zod";
10
+ import { CustomOpenAI } from "./openai.js";
11
11
  const CHAT_MODEL_OPENAI_DEFAULT_MODEL = "gpt-4o-mini";
12
12
  const OPENAI_CHAT_MODEL_CAPABILITIES = {
13
13
  "o4-mini": { supportsParallelToolCalls: false, supportsTemperature: false },
@@ -72,8 +72,8 @@ export class OpenAIChatModel extends ChatModel {
72
72
  supportsToolsEmptyParameters = true;
73
73
  supportsToolStreaming = true;
74
74
  supportsTemperature = true;
75
- async client() {
76
- const { apiKey, url } = await this.getCredential();
75
+ get client() {
76
+ const { apiKey, url } = this.credential;
77
77
  if (!apiKey)
78
78
  throw new Error(`${this.name} requires an API key. Please provide it via \`options.apiKey\`, or set the \`${this.apiKeyEnvName}\` environment variable`);
79
79
  this._client ??= new CustomOpenAI({
@@ -83,7 +83,7 @@ export class OpenAIChatModel extends ChatModel {
83
83
  });
84
84
  return this._client;
85
85
  }
86
- async getCredential() {
86
+ get credential() {
87
87
  return {
88
88
  url: this.options?.baseURL || process.env.OPENAI_BASE_URL,
89
89
  apiKey: this.options?.apiKey || process.env[this.apiKeyEnvName] || this.apiKeyDefault,
@@ -104,7 +104,7 @@ export class OpenAIChatModel extends ChatModel {
104
104
  ajv = new Ajv();
105
105
  async _process(input) {
106
106
  const messages = await this.getRunMessages(input);
107
- const { model } = await this.getCredential();
107
+ const model = input.modelOptions?.model || this.credential.model;
108
108
  const body = {
109
109
  model,
110
110
  temperature: this.supportsTemperature
@@ -125,8 +125,7 @@ export class OpenAIChatModel extends ChatModel {
125
125
  return await this.requestStructuredOutput(body, input.responseFormat);
126
126
  }
127
127
  const { jsonMode, responseFormat } = await this.getRunResponseFormat(input);
128
- const client = await this.client();
129
- const stream = (await client.chat.completions.create({
128
+ const stream = (await this.client.chat.completions.create({
130
129
  ...body,
131
130
  tools: toolsFromInputTools(input.tools, {
132
131
  addTypeToEmptyParameters: !this.supportsToolsEmptyParameters,
@@ -205,8 +204,7 @@ export class OpenAIChatModel extends ChatModel {
205
204
  const { jsonMode, responseFormat: resolvedResponseFormat } = await this.getRunResponseFormat({
206
205
  responseFormat,
207
206
  });
208
- const client = await this.client();
209
- const res = (await client.chat.completions.create({
207
+ const res = (await this.client.chat.completions.create({
210
208
  ...body,
211
209
  response_format: resolvedResponseFormat,
212
210
  }));
@@ -418,13 +416,4 @@ function handleCompleteToolCall(toolCalls, call) {
418
416
  args: call.function?.arguments || "",
419
417
  });
420
418
  }
421
- // Use a custom OpenAI client to handle API errors for better error messages
422
- class CustomOpenAI extends OpenAI {
423
- makeStatusError(status, error, message, headers) {
424
- if (!("error" in error) || typeof error.error !== "string") {
425
- message = JSON.stringify(error);
426
- }
427
- return super.makeStatusError(status, error, message, headers);
428
- }
429
- }
430
419
  // safeParseJSON is now imported from @aigne/core
@@ -0,0 +1,55 @@
1
+ import { ImageModel, type ImageModelInput, type ImageModelOptions, type ImageModelOutput } from "@aigne/core";
2
+ import { type Camelize } from "@aigne/core/utils/camelize.js";
3
+ import type OpenAI from "openai";
4
+ import type { ClientOptions } from "openai";
5
+ export interface OpenAIImageModelInput extends ImageModelInput, Camelize<Omit<OpenAI.ImageGenerateParams, "prompt" | "model" | "n" | "response_format">> {
6
+ }
7
+ export interface OpenAIImageModelOutput extends ImageModelOutput {
8
+ }
9
+ export interface OpenAIImageModelOptions extends ImageModelOptions<OpenAIImageModelInput, OpenAIImageModelOutput> {
10
+ /**
11
+ * API key for OpenAI API
12
+ *
13
+ * If not provided, will look for OPENAI_API_KEY in environment variables
14
+ */
15
+ apiKey?: string;
16
+ /**
17
+ * Base URL for OpenAI API
18
+ *
19
+ * Useful for proxies or alternate endpoints
20
+ */
21
+ baseURL?: string;
22
+ /**
23
+ * OpenAI model to use
24
+ *
25
+ * Defaults to 'dall-e-2'
26
+ */
27
+ model?: string;
28
+ /**
29
+ * Additional model options to control behavior
30
+ */
31
+ modelOptions?: Omit<Partial<OpenAIImageModelInput>, "model">;
32
+ /**
33
+ * Client options for OpenAI API
34
+ */
35
+ clientOptions?: Partial<ClientOptions>;
36
+ }
37
+ export declare class OpenAIImageModel extends ImageModel<OpenAIImageModelInput, OpenAIImageModelOutput> {
38
+ options?: OpenAIImageModelOptions | undefined;
39
+ constructor(options?: OpenAIImageModelOptions | undefined);
40
+ protected _client?: OpenAI;
41
+ protected apiKeyEnvName: string;
42
+ get client(): OpenAI;
43
+ get credential(): {
44
+ url: string | undefined;
45
+ apiKey: string | undefined;
46
+ model: string;
47
+ };
48
+ get modelOptions(): Omit<Partial<OpenAIImageModelInput>, "model"> | undefined;
49
+ /**
50
+ * Process the input and generate a response
51
+ * @param input The input to process
52
+ * @returns The generated response
53
+ */
54
+ process(input: ImageModelInput): Promise<ImageModelOutput>;
55
+ }
@@ -0,0 +1,93 @@
1
+ import { ImageModel, imageModelInputSchema, } from "@aigne/core";
2
+ import { snakelize } from "@aigne/core/utils/camelize.js";
3
+ import { checkArguments, pick } from "@aigne/core/utils/type-utils.js";
4
+ import { z } from "zod";
5
+ import { CustomOpenAI } from "./openai.js";
6
+ const DEFAULT_MODEL = "dall-e-2";
7
+ const openAIImageModelInputSchema = imageModelInputSchema.extend({});
8
+ const openAIImageModelOptionsSchema = z.object({
9
+ apiKey: z.string().optional(),
10
+ baseURL: z.string().optional(),
11
+ model: z.string().optional(),
12
+ });
13
+ export class OpenAIImageModel extends ImageModel {
14
+ options;
15
+ constructor(options) {
16
+ super({
17
+ ...options,
18
+ inputSchema: openAIImageModelInputSchema,
19
+ description: options?.description ?? "Draw or edit image by OpenAI image models",
20
+ });
21
+ this.options = options;
22
+ if (options)
23
+ checkArguments(this.name, openAIImageModelOptionsSchema, options);
24
+ }
25
+ _client;
26
+ apiKeyEnvName = "OPENAI_API_KEY";
27
+ get client() {
28
+ if (this._client)
29
+ return this._client;
30
+ const { apiKey, url } = this.credential;
31
+ if (!apiKey)
32
+ throw new Error(`${this.name} requires an API key. Please provide it via \`options.apiKey\`, or set the \`${this.apiKeyEnvName}\` environment variable`);
33
+ this._client ??= new CustomOpenAI({
34
+ baseURL: url,
35
+ apiKey,
36
+ ...this.options?.clientOptions,
37
+ });
38
+ return this._client;
39
+ }
40
+ get credential() {
41
+ return {
42
+ url: this.options?.baseURL || process.env.OPENAI_BASE_URL,
43
+ apiKey: this.options?.apiKey || process.env[this.apiKeyEnvName],
44
+ model: this.options?.model || DEFAULT_MODEL,
45
+ };
46
+ }
47
+ get modelOptions() {
48
+ return this.options?.modelOptions;
49
+ }
50
+ /**
51
+ * Process the input and generate a response
52
+ * @param input The input to process
53
+ * @returns The generated response
54
+ */
55
+ async process(input) {
56
+ const model = input.model || this.credential.model;
57
+ const inputKeys = [
58
+ "background",
59
+ "moderation",
60
+ "output_compression",
61
+ "output_format",
62
+ "prompt",
63
+ "quality",
64
+ "size",
65
+ "style",
66
+ "user",
67
+ ];
68
+ let responseFormat;
69
+ if (model !== "gpt-image-1") {
70
+ responseFormat = input.responseFormat === "base64" ? "b64_json" : "url";
71
+ }
72
+ const body = {
73
+ ...snakelize(pick({ ...this.modelOptions, ...input }, inputKeys)),
74
+ response_format: responseFormat,
75
+ model,
76
+ };
77
+ const response = await this.client.images.generate({ ...body });
78
+ return {
79
+ images: (response.data ?? []).map((image) => {
80
+ if (image.url)
81
+ return { url: image.url };
82
+ if (image.b64_json)
83
+ return { base64: image.b64_json };
84
+ throw new Error("Image response does not contain a valid URL or base64 data");
85
+ }),
86
+ usage: {
87
+ inputTokens: response.usage?.input_tokens || 0,
88
+ outputTokens: response.usage?.output_tokens || 0,
89
+ },
90
+ model,
91
+ };
92
+ }
93
+ }
@@ -0,0 +1,4 @@
1
+ import OpenAI, { type APIError } from "openai";
2
+ export declare class CustomOpenAI extends OpenAI {
3
+ protected makeStatusError(status: number, error: object, message: string | undefined, headers: Headers): APIError;
4
+ }
@@ -0,0 +1,10 @@
1
+ import OpenAI from "openai";
2
+ // Use a custom OpenAI client to handle API errors for better error messages
3
+ export class CustomOpenAI extends OpenAI {
4
+ makeStatusError(status, error, message, headers) {
5
+ if (!("error" in error) || typeof error.error !== "string") {
6
+ message = JSON.stringify(error);
7
+ }
8
+ return super.makeStatusError(status, error, message, headers);
9
+ }
10
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aigne/openai",
3
- "version": "0.11.4",
3
+ "version": "0.12.1",
4
4
  "description": "AIGNE OpenAI SDK for integrating with OpenAI's GPT models and API services",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -39,7 +39,7 @@
39
39
  "openai": "^5.8.3",
40
40
  "uuid": "^11.1.0",
41
41
  "zod": "^3.25.67",
42
- "@aigne/core": "^1.51.0"
42
+ "@aigne/core": "^1.54.0"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@types/bun": "^1.2.18",
@@ -47,7 +47,7 @@
47
47
  "npm-run-all": "^4.1.5",
48
48
  "rimraf": "^6.0.1",
49
49
  "typescript": "^5.8.3",
50
- "@aigne/test-utils": "^0.5.30"
50
+ "@aigne/test-utils": "^0.5.33"
51
51
  },
52
52
  "scripts": {
53
53
  "lint": "tsc --noEmit",