@miruzero/zgpt 0.1.3 → 0.1.5

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.
@@ -1,4 +1,4 @@
1
- 'use strict';
1
+ 'use strict';
2
2
 
3
3
  const https = require('node:https');
4
4
  const fs = require('node:fs');
@@ -6,6 +6,8 @@ const path = require('node:path');
6
6
  const paths = require('../../config/paths');
7
7
 
8
8
  const RETRY_AFTER_CAP_MS = 30000;
9
+ const DEFAULT_HTTP_IDLE_TIMEOUT_MS = 30_000;
10
+ const DEFAULT_HTTP_TOTAL_TIMEOUT_MS = 45_000;
9
11
  const DEFAULT_RATE_LIMIT_COOLDOWN_MS = 30000;
10
12
  const MAX_RATE_LIMIT_COOLDOWN_MS = 15 * 60 * 1000;
11
13
 
@@ -141,6 +143,14 @@ exports.parseRetryAfterMs = (headers = {}) => {
141
143
  };
142
144
  const safeParse = (text) => { try { return JSON.parse(text); } catch { return null; } };
143
145
  const rawRequest = (target, token, sessionHeaders, options, parseBody) => new Promise((resolve, reject) => {
146
+ let settled = false;
147
+ let deadlineTimer = null;
148
+ const finish = (next, value) => {
149
+ if (settled) return;
150
+ settled = true;
151
+ if (deadlineTimer) clearTimeout(deadlineTimer);
152
+ next(value);
153
+ };
144
154
  const payload = options.body === undefined ? null : Buffer.from(JSON.stringify(options.body));
145
155
  let headers = exports.buildHeaders(token, sessionHeaders, target, options.route);
146
156
  if (options.headers) headers = cleanHeaders({ ...headers, ...options.headers });
@@ -151,28 +161,47 @@ const rawRequest = (target, token, sessionHeaders, options, parseBody) => new Pr
151
161
  const aborted = () => {
152
162
  const error = new Error(`ChatGPT upstream stream aborted: ${target}`);
153
163
  error.code = 'UPSTREAM_ABORTED';
154
- reject(error);
164
+ finish(reject, error);
155
165
  };
156
166
  res.on('data', (chunk) => { options.onChunk?.(chunk); chunks.push(chunk); });
157
167
  res.on('end', () => {
158
168
  ended = true;
159
169
  const text = Buffer.concat(chunks).toString('utf8');
160
170
  const response = { status: res.statusCode, ...(parseBody ? { json: safeParse(text) } : { text }), contentType: res.headers['content-type'] || null, headers: { ...res.headers } };
161
- exports.persistResponseHeaders(sessionHeaders, response.headers, options);
162
- resolve(response);
171
+ try {
172
+ exports.persistResponseHeaders(sessionHeaders, response.headers, options);
173
+ finish(resolve, response);
174
+ } catch (error) { finish(reject, error); }
163
175
  });
164
176
  res.on('aborted', aborted);
165
- res.on('error', reject);
177
+ res.on('error', error => finish(reject, error));
166
178
  res.on('close', () => { if (!ended) aborted(); });
167
179
  });
168
- req.on('error', reject);
169
- if (Number.isFinite(options.idleTimeoutMs) && options.idleTimeoutMs > 0 && typeof req.setTimeout === 'function') {
170
- req.setTimeout(options.idleTimeoutMs, () => {
171
- const error = new Error(`ChatGPT upstream stream idle timeout: ${target}`);
180
+ req.on('error', error => finish(reject, error));
181
+ const idleTimeoutMs = options.idleTimeoutMs === 0 ? 0
182
+ : Number.isFinite(options.idleTimeoutMs) && options.idleTimeoutMs > 0
183
+ ? options.idleTimeoutMs : DEFAULT_HTTP_IDLE_TIMEOUT_MS;
184
+ if (idleTimeoutMs && typeof req.setTimeout === 'function') {
185
+ req.setTimeout(idleTimeoutMs, () => {
186
+ const error = new Error(`ChatGPT upstream idle timeout: ${target}`);
172
187
  error.code = 'UPSTREAM_IDLE_TIMEOUT';
173
- req.destroy(error);
188
+ if (typeof req.destroy === 'function') req.destroy(error);
189
+ finish(reject, error);
174
190
  });
175
191
  }
192
+ // Active SSE streams may run longer than a fixed total deadline; they still
193
+ // have an idle timeout. Ordinary HTTP gets both an idle and a total budget.
194
+ const streaming = typeof options.onChunk === 'function' ||
195
+ /text\/event-stream/i.test(String(options.headers?.accept || ''));
196
+ const totalTimeoutMs = options.totalTimeoutMs === 0 ? 0
197
+ : Number.isFinite(options.totalTimeoutMs) && options.totalTimeoutMs > 0
198
+ ? options.totalTimeoutMs : streaming ? 0 : DEFAULT_HTTP_TOTAL_TIMEOUT_MS;
199
+ if (totalTimeoutMs) deadlineTimer = setTimeout(() => {
200
+ const error = new Error(`ChatGPT upstream request deadline exceeded: ${target}`);
201
+ error.code = 'UPSTREAM_TIMEOUT';
202
+ if (typeof req.destroy === 'function') req.destroy(error);
203
+ finish(reject, error);
204
+ }, totalTimeoutMs);
176
205
  if (payload) req.write(payload);
177
206
  req.end();
178
207
  });
@@ -187,5 +216,4 @@ const guardedRequest = async (target, token, sessionHeaders, options, parseBody)
187
216
  exports.requestJson = (target, token, sessionHeaders = {}, options = {}) =>
188
217
  guardedRequest(target, token, sessionHeaders, options, true);
189
218
  exports.requestText = (target, token, sessionHeaders = {}, options = {}) =>
190
- guardedRequest(target, token, sessionHeaders, options, false);
191
-
219
+ guardedRequest(target, token, sessionHeaders, options, false);