From 831cea350309f672032f75b8d990d59fe42f438d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 23 May 2025 19:18:35 +0000 Subject: [PATCH 1/4] Initial plan for issue From 7cffb79d866ec64ddd8815081042ba30335a8e31 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 23 May 2025 19:21:25 +0000 Subject: [PATCH 2/4] Initial plan for fixing node-fetch compatibility Co-authored-by: jddev273 <59052623+jddev273@users.noreply.github.com> --- package-lock.json | 24 ++++++++++++++---------- package.json | 2 +- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7583eda..2ae9ca5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,21 +1,24 @@ { "name": "streamed-chatgpt-api", - "version": "1.0.0", + "version": "1.0.8", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "streamed-chatgpt-api", - "version": "1.0.0", + "version": "1.0.8", "license": "MIT", + "dependencies": { + "node-fetch": "^3.3.2" + }, "devDependencies": { "jest": "^29.5.0" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" }, "optionalDependencies": { - "node-fetch": "^3.1.0" + "node-fetch": "^3.3.2" } }, "node_modules/@ampproject/remapping": { @@ -2834,9 +2837,10 @@ } }, "node_modules/node-fetch": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.1.tgz", - "integrity": "sha512-cRVc/kyto/7E5shrWca1Wsea4y6tL9iYJE5FBCius3JQfb/4P4I295PfhgbJQBLTx6lATE4z+wK0rPM4VS2uow==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "license": "MIT", "optional": true, "dependencies": { "data-uri-to-buffer": "^4.0.0", @@ -5717,9 +5721,9 @@ "optional": true }, "node-fetch": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.1.tgz", - "integrity": "sha512-cRVc/kyto/7E5shrWca1Wsea4y6tL9iYJE5FBCius3JQfb/4P4I295PfhgbJQBLTx6lATE4z+wK0rPM4VS2uow==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", "optional": true, "requires": { "data-uri-to-buffer": "^4.0.0", diff --git a/package.json b/package.json index a5b43c3..ced96f5 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "node": ">=18.0.0" }, "optionalDependencies": { - "node-fetch": "^3.1.0" + "node-fetch": "^3.3.2" }, "devDependencies": { "jest": "^29.5.0" From b7cd69d9f5667edbabef615ee31b9aab6c7da624 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 23 May 2025 19:22:29 +0000 Subject: [PATCH 3/4] Add support for node-fetch streams Co-authored-by: jddev273 <59052623+jddev273@users.noreply.github.com> --- index.js | 102 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 98 insertions(+), 4 deletions(-) diff --git a/index.js b/index.js index 411d734..98aa9d7 100644 --- a/index.js +++ b/index.js @@ -86,6 +86,91 @@ async function fetchStreamedChat(options, onChunkReceived) { }); } + // A function to process the response stream from node-fetch using Node.js stream methods + async function processNodeStream(readableStream, decoder, onChunkReceived) { + try { + // Set up event listeners for the Node.js readable stream + return new Promise((resolve, reject) => { + let buffer = ''; + + // Setup read timeout timer + let readTimeoutId = null; + const resetReadTimeout = () => { + if (readTimeoutId) clearTimeout(readTimeoutId); + readTimeoutId = setTimeout(() => { + reject(new Error('Timeout')); + }, readTimeout); + }; + + // Initial timeout + resetReadTimeout(); + + // Set up total time timeout + const totalTimeoutPromise = totalTimeTimeout(); + totalTimeoutPromise.catch(reject); + + readableStream.on('data', (chunk) => { + try { + // Reset read timeout when data is received + resetReadTimeout(); + + // Decode the chunk and add it to the buffer + const textChunk = decoder.decode(chunk, { stream: true }); + buffer += textChunk; + + // Process complete lines + const lines = buffer.split('\n'); + buffer = lines.pop(); // Keep the last potentially incomplete line in the buffer + + // Process each complete line + for (const line of lines) { + if (line.trim() === '') continue; + + // Remove the "data: " prefix from the line + const message = line.replace(/^data: /, ''); + + // If the message indicates the end of the stream, resolve + if (message === '[DONE]') { + if (readTimeoutId) clearTimeout(readTimeoutId); + resolve(); + return; + } + + // Otherwise, invoke the onChunkReceived callback with the message + onChunkReceived(message); + } + } catch (error) { + if (readTimeoutId) clearTimeout(readTimeoutId); + reject(error); + } + }); + + readableStream.on('end', () => { + // Clear timeout when stream ends + if (readTimeoutId) clearTimeout(readTimeoutId); + + // Process any remaining data in the buffer + if (buffer.trim() !== '') { + const message = buffer.replace(/^data: /, ''); + if (message !== '[DONE]' && message.trim() !== '') { + onChunkReceived(message); + } + } + resolve(); + }); + + readableStream.on('error', (error) => { + // Clear timeout on error + if (readTimeoutId) clearTimeout(readTimeoutId); + console.error('Error reading stream:', error); + reject(error); + }); + }); + } catch (error) { + console.error('Error processing node stream:', error); + } + } + // A function to process the response stream and invoke the onChunkReceived callback // for each valid line in the stream async function processStream(reader, decoder, onChunkReceived) { @@ -175,12 +260,21 @@ async function fetchStreamedChat(options, onChunkReceived) { const response = await fetchChatResponseWithRetry(apiKey, requestOptions, retryCount); - // Initialize the reader and decoder - const reader = response.body.getReader(); + // Initialize the decoder const decoder = new TextDecoder('utf-8'); - // Process the response stream - await processStream(reader, decoder, onChunkReceived); + // Check if response.body has getReader method (native fetch) + if (typeof response.body.getReader === 'function') { + // Initialize the reader for native fetch + const reader = response.body.getReader(); + + // Process the response stream using getReader + await processStream(reader, decoder, onChunkReceived); + } else { + // Handle node-fetch which doesn't have getReader + // Process the stream using Node.js stream methods + await processNodeStream(response.body, decoder, onChunkReceived); + } } module.exports = { fetchStreamedChat, fetchStreamedChatContent }; From 9f7203d840da6dc9e2ee8a67a51b24ccc71d6b66 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 23 May 2025 19:23:23 +0000 Subject: [PATCH 4/4] Update documentation for node-fetch compatibility Co-authored-by: jddev273 <59052623+jddev273@users.noreply.github.com> --- README.md | 8 ++++++++ test-compatibility.js | 23 +++++++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 test-compatibility.js diff --git a/README.md b/README.md index da41dcd..6990559 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ A Node.js module for streaming ChatGPT API responses using the OpenAI API. Strea ChatGPT is an advanced AI language model developed by OpenAI. This module enables you to interact with the ChatGPT API, allowing you to send messages and receive AI-generated responses in real-time. The OpenAI API provides access to various models, including the gpt-3.5-turbo model, which is used by default in this module. +This library works with both native `fetch` (available in Node.js 18+) and `node-fetch` (automatically used as a fallback for older Node.js versions) for maximum compatibility. + ## Usage Example A simple node web app showing usage of the module with streamed chat can be found here: [Streamed ChatGPT API Usage Example](https://github.com/jddev273/simple-chatgpt-chat-streaming-demo) @@ -18,6 +20,12 @@ Install using npm: npm install streamed-chatgpt-api ``` +The package works with Node.js 18+ (which has native `fetch`) out of the box. For Node.js versions below 18, the package will automatically use `node-fetch` as a fallback. If you need to explicitly use `node-fetch` in your project, you can install it separately: + +``` +npm install node-fetch +``` + ## Usage To use the module, first import it: diff --git a/test-compatibility.js b/test-compatibility.js new file mode 100644 index 0000000..add2ca6 --- /dev/null +++ b/test-compatibility.js @@ -0,0 +1,23 @@ +// Create simple test to ensure compatibility with both fetch implementations + +// First test the implementation with node-fetch +console.log('Testing with node-fetch...'); + +// Save original fetch +const originalFetch = globalThis.fetch; + +// Force use of node-fetch by setting globalThis.fetch to undefined +globalThis.fetch = undefined; + +// Now when we require the library, it should use node-fetch +const { fetchStreamedChat: fetchWithNodeFetch } = require('./index'); + +// Restore original fetch +globalThis.fetch = originalFetch; + +// Now test with native fetch +console.log('Testing with native fetch...'); +const { fetchStreamedChat: fetchWithNativeFetch } = require('./index'); + +console.log('Both implementations loaded successfully. Check completed.'); +console.log('Note: Full functional testing requires API keys and real API calls.'); \ No newline at end of file