From 650e0d878992ef7bfc054fb3070cad020f86ee6f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Oct 2025 22:44:22 +0000 Subject: [PATCH 1/4] Initial plan From 4a406fe707d47bf7d778e4481a9742bec419e7f8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Oct 2025 22:50:42 +0000 Subject: [PATCH 2/4] Update OpenAI library to 6.6.0 and change model to gpt-5-mini Co-authored-by: Bullrich <8524599+Bullrich@users.noreply.github.com> --- package-lock.json | 221 ++-------------------------------------------- package.json | 2 +- src/chat.ts | 2 +- src/image.ts | 2 +- src/story.ts | 2 +- src/verify.ts | 13 ++- 6 files changed, 18 insertions(+), 224 deletions(-) diff --git a/package-lock.json b/package-lock.json index fb64fc3..5023ee7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.0.0-updated-via-gh-releases", "license": "MIT", "dependencies": { - "openai": "^4.80.0" + "openai": "^6.6.0" }, "devDependencies": { "@eslint/js": "^9.2.0", @@ -159,23 +159,6 @@ "node": ">= 8" } }, - "node_modules/@types/node": { - "version": "18.19.31", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.31.tgz", - "integrity": "sha512-ArgCD39YpyyrtFKIqMDvjz79jto5fcI/SVUs2HwB+f0dAzq68yqOdyaSivLiLugSziTpNXLQrVb7RZFmdZzbhA==", - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/@types/node-fetch": { - "version": "2.6.11", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.11.tgz", - "integrity": "sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==", - "dependencies": { - "@types/node": "*", - "form-data": "^4.0.0" - } - }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "7.9.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.9.0.tgz", @@ -391,17 +374,6 @@ "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", "dev": true }, - "node_modules/abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "dependencies": { - "event-target-shim": "^5.0.0" - }, - "engines": { - "node": ">=6.5" - } - }, "node_modules/acorn": { "version": "8.11.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", @@ -423,17 +395,6 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/agentkeepalive": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz", - "integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==", - "dependencies": { - "humanize-ms": "^1.2.1" - }, - "engines": { - "node": ">= 8.0.0" - } - }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -495,11 +456,6 @@ "node": ">=8" } }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -572,17 +528,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -632,14 +577,6 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -842,14 +779,6 @@ "node": ">=0.10.0" } }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "engines": { - "node": ">=6" - } - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -966,44 +895,6 @@ "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", "dev": true }, - "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/form-data-encoder": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", - "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==" - }, - "node_modules/formdata-node": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", - "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", - "dependencies": { - "node-domexception": "1.0.0", - "web-streams-polyfill": "4.0.0-beta.3" - }, - "engines": { - "node": ">= 12.20" - } - }, - "node_modules/formdata-node/node_modules/web-streams-polyfill": { - "version": "4.0.0-beta.3", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", - "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", - "engines": { - "node": ">= 14" - } - }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -1089,14 +980,6 @@ "node": ">=8" } }, - "node_modules/humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", - "dependencies": { - "ms": "^2.0.0" - } - }, "node_modules/ignore": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", @@ -1312,25 +1195,6 @@ "node": ">=8.6" } }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -1343,54 +1207,12 @@ "node": "*" } }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, - "node_modules/node-domexception": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" - } - ], - "engines": { - "node": ">=10.5.0" - } - }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -1401,25 +1223,16 @@ } }, "node_modules/openai": { - "version": "4.80.0", - "resolved": "https://registry.npmjs.org/openai/-/openai-4.80.0.tgz", - "integrity": "sha512-5TqdNQgjOMxo3CkCvtjzuSwuznO/o3q5aak0MTy6IjRvPtvVA1wAFGJU3eZT1JHzhs2wFb/xtDG0o6Y/2KGCfw==", + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-6.6.0.tgz", + "integrity": "sha512-1yWk4cBsHF5Bq9TreHYOHY7pbqdlT74COnm8vPx7WKn36StS+Hyk8DdAitnLaw67a5Cudkz5EmlFQjSrNnrA2w==", "license": "Apache-2.0", - "dependencies": { - "@types/node": "^18.11.18", - "@types/node-fetch": "^2.6.4", - "abort-controller": "^3.0.0", - "agentkeepalive": "^4.2.1", - "form-data-encoder": "1.7.2", - "formdata-node": "^4.3.2", - "node-fetch": "^2.6.7" - }, "bin": { "openai": "bin/cli" }, "peerDependencies": { "ws": "^8.18.0", - "zod": "^3.23.8" + "zod": "^3.25 || ^4.0" }, "peerDependenciesMeta": { "ws": { @@ -1741,11 +1554,6 @@ "node": ">=8.0" } }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, "node_modules/ts-api-utils": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", @@ -1866,11 +1674,6 @@ } } }, - "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" - }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -1892,20 +1695,6 @@ "integrity": "sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==", "dev": true }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index f3f9b75..0e01261 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,6 @@ "typescript-eslint": "^7.9.0" }, "dependencies": { - "openai": "^4.80.0" + "openai": "^6.6.0" } } diff --git a/src/chat.ts b/src/chat.ts index ead1dc6..b55b34d 100644 --- a/src/chat.ts +++ b/src/chat.ts @@ -11,7 +11,7 @@ type Conversation = { * Assistant class that allows to have conversations while keeping the history */ export class ChatAssistant { - public constructor(private readonly openai: OpenAI, public readonly temperature: number, public readonly chatModel: string = "gpt-4-turbo") { + public constructor(private readonly openai: OpenAI, public readonly temperature: number, public readonly chatModel: string = "gpt-5-mini") { } /** diff --git a/src/image.ts b/src/image.ts index 0fa8c7a..fb0a7b8 100644 --- a/src/image.ts +++ b/src/image.ts @@ -28,7 +28,7 @@ export class ImageGenerator { this.logger.log("Got image!", data); - if (data.length < numberOfImages) { + if (!data || data.length < numberOfImages) { throw new Error("Insufficient amount of images generated"); } diff --git a/src/story.ts b/src/story.ts index ddb7b75..bf70e0e 100644 --- a/src/story.ts +++ b/src/story.ts @@ -5,7 +5,7 @@ import { ChatAssistant } from "./chat"; type StoryParams = { prompt: string, story: string, temperature: number }; -const DEFAULT_MODEL = "gpt-4o"; +const DEFAULT_MODEL = "gpt-5-mini"; /** Prompt used to generate the story */ export const systemInfo = `You are Story Bot, a language model that helps users create stories, scripts and more. diff --git a/src/verify.ts b/src/verify.ts index 19ae939..a145865 100644 --- a/src/verify.ts +++ b/src/verify.ts @@ -14,9 +14,9 @@ type StoryResult = { validStory: true } | { validStory: false, reasonForRejectio * @returns a `{validStory:boolean,reasonForRejection?:string}` object. * If validStory is false, the reasonForRejection will contain the information */ -export async function verifyPrompt(prompt: string, openai: OpenAI, chatModel: string = "gpt-4-turbo"): Promise { +export async function verifyPrompt(prompt: string, openai: OpenAI, chatModel: string = "gpt-5-mini"): Promise { const moderator = await openai.moderations.create({ - model: "omni-moderation-latest", + model: "omni-moderation", input: `A user submitted the following input to generate a story or blogpost: ${prompt}` }); @@ -65,11 +65,16 @@ export async function verifyPrompt(prompt: string, openai: OpenAI, chatModel: st }); const responseMessage = response.choices[0]?.message; - if (!responseMessage?.tool_calls || !responseMessage.tool_calls[0]?.function.arguments) { + if (!responseMessage?.tool_calls || !responseMessage.tool_calls[0]) { throw new Error("Missing tool calls"); } - const { isStory, kindOfPrompt } = JSON.parse(responseMessage.tool_calls[0].function.arguments) as { isStory: boolean, kindOfPrompt: string }; + const toolCall = responseMessage.tool_calls[0]; + if (toolCall.type !== 'function' || !toolCall.function.arguments) { + throw new Error("Invalid tool call type"); + } + + const { isStory, kindOfPrompt } = JSON.parse(toolCall.function.arguments) as { isStory: boolean, kindOfPrompt: string }; return isStory ? { validStory: true } : { validStory: false, reasonForRejection: kindOfPrompt }; } From 0423b1e27e64960adf31d1acd8db29b30434edb2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Oct 2025 23:42:00 +0000 Subject: [PATCH 3/4] Fix temperature handling for gpt-5-mini and correct moderation model Co-authored-by: Bullrich <8524599+Bullrich@users.noreply.github.com> --- src/chat.ts | 13 ++++++++++--- src/verify.ts | 2 +- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/chat.ts b/src/chat.ts index b55b34d..6b993a2 100644 --- a/src/chat.ts +++ b/src/chat.ts @@ -28,11 +28,18 @@ export class ChatAssistant { * console.log("Argentina size is:", followUp.answer.content); */ public async chat(...messages: OpenAI.Chat.Completions.ChatCompletionMessageParam[]): Promise { - const response = await this.openai.chat.completions.create({ + // gpt-5-mini only supports temperature = 1 (default), so we omit it for that model + const completionParams: OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming = { model: this.chatModel, - temperature: this.temperature, messages: messages, - }); + }; + + // Only include temperature if the model is not gpt-5-mini + if (this.chatModel !== "gpt-5-mini") { + completionParams.temperature = this.temperature; + } + + const response = await this.openai.chat.completions.create(completionParams); if (response.choices.length < 1) { throw new Error("No results found on prompt request"); diff --git a/src/verify.ts b/src/verify.ts index a145865..6e2cc38 100644 --- a/src/verify.ts +++ b/src/verify.ts @@ -16,7 +16,7 @@ type StoryResult = { validStory: true } | { validStory: false, reasonForRejectio */ export async function verifyPrompt(prompt: string, openai: OpenAI, chatModel: string = "gpt-5-mini"): Promise { const moderator = await openai.moderations.create({ - model: "omni-moderation", + model: "omni-moderation-2024-09-26", input: `A user submitted the following input to generate a story or blogpost: ${prompt}` }); From f4ed18e880943e6c7439b204ae580dad52072a48 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Oct 2025 23:50:00 +0000 Subject: [PATCH 4/4] Use omni-moderation-latest and add comprehensive API documentation Co-authored-by: Bullrich <8524599+Bullrich@users.noreply.github.com> --- README.md | 183 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/chat.ts | 6 ++ src/image.ts | 22 +++++- src/story.ts | 26 ++++++- src/verify.ts | 4 +- 5 files changed, 237 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 567eb92..21fac2b 100644 --- a/README.md +++ b/README.md @@ -13,8 +13,11 @@ Typescript library used to generate the stories for [StoryBot](https://storybot. ## Usage +### Quick Start + ```typescript import { createStory } from "story-gpt"; +import { OpenAI } from "openai"; const story = await createStory("A story about a happy horse", new OpenAI({apiKey: ">my api key<"})); @@ -22,6 +25,186 @@ console.log("The story is named %s and it's tells the following story:", story.t console.log("See the cover picture for the story here:", story.image); ``` +## API Reference + +### `createStory(prompt, openai)` + +Main utility function that creates a complete story with title and image. + +**Parameters:** +- `prompt` (string): The prompt to generate the story from +- `openai` (OpenAI): The authenticated OpenAI client + +**Returns:** `Promise` containing: +- `prompt`: The original prompt +- `title`: Generated story title +- `content`: The story content +- `temperature`: Temperature value used for generation +- `image`: URL to the generated image (Note: this link expires, so download it) + +**Example:** +```typescript +import { createStory } from "story-gpt"; +import { OpenAI } from "openai"; + +const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY }); +const story = await createStory("A tale of a brave knight", openai); +console.log(story.title); // e.g., "The Knight's Quest" +``` + +### `Story` Class + +Class for generating and managing stories. + +#### `Story.generateStory(prompt, openai, chatModel?, logger?)` + +Static method to generate a story from a prompt. + +**Parameters:** +- `prompt` (string): The prompt to generate the story from +- `openai` (OpenAI): The authenticated OpenAI client +- `chatModel` (string, optional): The model to use. Defaults to `gpt-5-mini`. Note: `gpt-5-mini` only supports temperature=1 +- `logger` (ILogger, optional): Logger instance for debugging. Defaults to console + +**Returns:** `Promise` + +**Example:** +```typescript +import { Story } from "story-gpt"; +import { OpenAI } from "openai"; + +const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY }); +const story = await Story.generateStory("A mystery in the old mansion", openai); +console.log(story.content); // The generated story text +``` + +#### `story.generateTitle()` + +Generates a title for the story. + +**Returns:** `Promise` - The generated title + +#### `story.generateImage(size?, model?)` + +Generates an image for the story using DALL-E. + +**Parameters:** +- `size` (ImageSize, optional): Image size. Defaults to `"1024x1024"`. Options: `'256x256' | '512x512' | '1024x1024' | '1792x1024' | '1024x1792'` +- `model` (Model, optional): DALL-E model. Defaults to `"dall-e-3"`. Options: `'dall-e-2' | 'dall-e-3'` + +**Returns:** `Promise` - URL to the generated image + +### `verifyPrompt(prompt, openai, chatModel?)` + +Utility function that verifies if a prompt contains potentially harmful content and if it qualifies as a story. + +**Parameters:** +- `prompt` (string): The prompt to analyze +- `openai` (OpenAI): The authenticated OpenAI client +- `chatModel` (string, optional): The model to use. Defaults to `gpt-5-mini` + +**Returns:** `Promise` where StoryResult is: +- `{ validStory: true }` if the prompt is valid +- `{ validStory: false, reasonForRejection: string }` if invalid + +**Example:** +```typescript +import { verifyPrompt } from "story-gpt"; +import { OpenAI } from "openai"; + +const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY }); +const result = await verifyPrompt("Write a story about a dragon", openai); +if (result.validStory) { + console.log("Prompt is valid!"); +} else { + console.log("Rejected:", result.reasonForRejection); +} +``` + +### `ImageGenerator` Class + +Class for generating images using OpenAI's DALL-E. + +#### Constructor: `new ImageGenerator(openai, logger)` + +**Parameters:** +- `openai` (OpenAI): The authenticated OpenAI client +- `logger` (ILogger): Logger instance for debugging + +#### `imageGenerator.generateImage(prompt, size?, model?)` + +Generates a single image from a text prompt. + +**Parameters:** +- `prompt` (string): The text prompt describing the image +- `size` (ImageSize, optional): Image size. Defaults to `"512x512"` +- `model` (Model, optional): DALL-E model. Defaults to `"dall-e-3"` + +**Returns:** `Promise` - URL to the generated image + +**Example:** +```typescript +import { ImageGenerator } from "story-gpt"; +import { OpenAI } from "openai"; + +const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY }); +const imageGen = new ImageGenerator(openai, console); +const imageUrl = await imageGen.generateImage("A sunset over mountains", "1024x1024", "dall-e-3"); +``` + +#### `imageGenerator.generateImages(prompt, numberOfImages, size?, model?)` + +Generates multiple images from a text prompt. + +**Parameters:** +- `prompt` (string): The text prompt describing the images +- `numberOfImages` (number): Number of images to generate (1-5) +- `size` (ImageSize, optional): Image size. Defaults to `"512x512"` +- `model` (Model, optional): DALL-E model. Defaults to `"dall-e-3"` + +**Returns:** `Promise` - Array of URLs to the generated images + +### `ChatAssistant` Class + +Assistant class for having conversations while keeping the history. + +#### Constructor: `new ChatAssistant(openai, temperature, chatModel?)` + +**Parameters:** +- `openai` (OpenAI): The authenticated OpenAI client +- `temperature` (number): Temperature for response generation (0-2). Note: `gpt-5-mini` only supports temperature=1 +- `chatModel` (string, optional): The model to use. Defaults to `gpt-5-mini` + +#### `chatAssistant.chat(...messages)` + +Starts a conversation and returns an answer with the ability to continue the conversation. + +**Parameters:** +- `messages` (ChatCompletionMessageParam[]): Thread messages, usually the system message and a first message + +**Returns:** `Promise` with: +- `answer`: The response from the assistant +- `chat(message)`: Function to continue the conversation + +**Example:** +```typescript +import { ChatAssistant } from "story-gpt"; +import { OpenAI } from "openai"; + +const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY }); +const assistant = new ChatAssistant(openai, 1, "gpt-5-mini"); +const conv = await assistant.chat({role: "user", content: "Where is Argentina?"}); +console.log("Answer is", conv.answer.content); +const followUp = await conv.chat({role: "user", content: "And how big is it?"}); +console.log("Argentina size is:", followUp.answer.content); +``` + +## Important Notes + +- **Temperature with gpt-5-mini**: The `gpt-5-mini` model only supports the default temperature value (1). When using this model, the library automatically omits the temperature parameter to avoid API errors. +- **Image URLs**: Generated image URLs are temporary and will expire. Make sure to download the images if you need to persist them. +- **API Key**: You need a valid OpenAI API key to use this library. Set it as an environment variable or pass it directly to the OpenAI client. + ## Development ### Running E2E Tests diff --git a/src/chat.ts b/src/chat.ts index 6b993a2..1214b8e 100644 --- a/src/chat.ts +++ b/src/chat.ts @@ -11,6 +11,12 @@ type Conversation = { * Assistant class that allows to have conversations while keeping the history */ export class ChatAssistant { + /** + * Creates a new ChatAssistant instance + * @param openai The authenticated OpenAI client + * @param temperature The temperature for response generation (0-2). Note: gpt-5-mini only supports temperature=1 (default) + * @param chatModel Optional, the model to use. Defaults to gpt-5-mini + */ public constructor(private readonly openai: OpenAI, public readonly temperature: number, public readonly chatModel: string = "gpt-5-mini") { } diff --git a/src/image.ts b/src/image.ts index fb0a7b8..58557e8 100644 --- a/src/image.ts +++ b/src/image.ts @@ -5,17 +5,37 @@ export type ImageSize = '256x256' | '512x512' | '1024x1024' | '1792x1024' | '102 export type Model = 'dall-e-2' | 'dall-e-3'; /** - * Class used to generate images + * Class used to generate images using OpenAI's DALL-E */ export class ImageGenerator { + /** + * Creates a new ImageGenerator instance + * @param openai The authenticated OpenAI client + * @param logger Logger instance for debugging + */ public constructor(private readonly openai: OpenAI, private readonly logger: ILogger) { } + /** + * Generates a single image from a text prompt + * @param prompt The text prompt describing the image to generate + * @param size Optional, the size of the image. Defaults to "512x512" + * @param model Optional, the DALL-E model to use. Defaults to "dall-e-3" + * @returns A Promise that resolves to the URL of the generated image + */ public async generateImage(prompt: string, size: ImageSize = "512x512", model: Model = "dall-e-3"): Promise { const image = await this.generateImages(prompt, 1, size, model); return image[0]!; } + /** + * Generates multiple images from a text prompt + * @param prompt The text prompt describing the images to generate + * @param numberOfImages The number of images to generate (1-5) + * @param size Optional, the size of the images. Defaults to "512x512" + * @param model Optional, the DALL-E model to use. Defaults to "dall-e-3" + * @returns A Promise that resolves to an array of URLs for the generated images + */ public async generateImages(prompt: string, numberOfImages: 1 | 2 | 3 | 4 | 5, size: ImageSize = "512x512", model: Model = "dall-e-3"): Promise { const response = await this.openai.images.generate({ model, diff --git a/src/story.ts b/src/story.ts index bf70e0e..74fca3d 100644 --- a/src/story.ts +++ b/src/story.ts @@ -23,6 +23,13 @@ export class Story { public readonly content: string; public readonly temperature: number; + /** + * Creates a new Story instance + * @param openai The authenticated OpenAI client + * @param storyParams Story parameters including prompt, story content, and temperature + * @param chatModel Optional, the model to use. Defaults to gpt-5-mini. Note: gpt-5-mini only supports temperature=1 (default) + * @param logger Optional logger instance for debugging. Defaults to console + */ constructor(private readonly openai: OpenAI, storyParams: StoryParams, chatModel: string = DEFAULT_MODEL, private readonly logger: ILogger = console) { this.prompt = storyParams.prompt; this.content = storyParams.story; @@ -31,7 +38,14 @@ export class Story { this.chat = new ChatAssistant(this.openai, storyParams.temperature, chatModel); } - /** Utility method which allows a Story object to be generated from a prompt with a story */ + /** + * Utility method which allows a Story object to be generated from a prompt with a story + * @param prompt The prompt to generate the story from + * @param openai The authenticated OpenAI client + * @param chatModel Optional, the model to use. Defaults to gpt-5-mini. Note: gpt-5-mini only supports temperature=1 (default) + * @param logger Optional logger instance for debugging. Defaults to console + * @returns A Promise that resolves to a Story instance + */ static async generateStory(prompt: string, openai: OpenAI, chatModel: string = DEFAULT_MODEL, logger: ILogger = console): Promise { const chat = new ChatAssistant(openai, Math.round(Math.random() * 100) / 100, chatModel); logger.log("Generating story for prompt", prompt); @@ -45,6 +59,10 @@ export class Story { return new Story(openai, { prompt, story: story.answer.content, temperature: chat.temperature }, chatModel); } + /** + * Generates a title for the story + * @returns A Promise that resolves to the generated title + */ async generateTitle(): Promise { const titleQuestion = await this.chat.chat(...this.creationPrompt, { role: "user", content: "What would you call the story (or post)? Respond only with the name, no other text is needed." }); let title = titleQuestion.answer.content; @@ -63,6 +81,12 @@ export class Story { return title; } + /** + * Generates an image for the story using DALL-E + * @param size Optional, the size of the image. Defaults to "1024x1024" + * @param model Optional, the DALL-E model to use. Defaults to "dall-e-3" + * @returns A Promise that resolves to the URL of the generated image + */ async generateImage(size: ImageSize = "1024x1024", model: Model = "dall-e-3"): Promise { this.logger.log("Generating image prompts"); const imgPrompt = "Based on the previous story, write a prompt for an image generation service Dall-E. " + diff --git a/src/verify.ts b/src/verify.ts index 6e2cc38..469f6eb 100644 --- a/src/verify.ts +++ b/src/verify.ts @@ -10,13 +10,13 @@ type StoryResult = { validStory: true } | { validStory: false, reasonForRejectio * "I'm sorry, but X is not a prompt for a story" or "User prompt contains the following flagged topics: violence" * @param prompt The prompt to analyze * @param openai The openai authenticated client - * @param chatModel optional, the model to use. Defaults to gpt-4-turbo + * @param chatModel optional, the model to use. Defaults to gpt-5-mini * @returns a `{validStory:boolean,reasonForRejection?:string}` object. * If validStory is false, the reasonForRejection will contain the information */ export async function verifyPrompt(prompt: string, openai: OpenAI, chatModel: string = "gpt-5-mini"): Promise { const moderator = await openai.moderations.create({ - model: "omni-moderation-2024-09-26", + model: "omni-moderation-latest", input: `A user submitted the following input to generate a story or blogpost: ${prompt}` });