@bubblebrain-ai/bubble 0.0.18 → 0.0.19

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.
@@ -13,4 +13,5 @@ export declare function createChatGptFetch(options?: ChatGptFetchOptions): ChatG
13
13
  export declare function createChatGptDispatcher(env?: NodeJS.ProcessEnv, input?: RequestInfo | URL): Dispatcher | undefined;
14
14
  export declare function withChatGptNetworkOptions(input: RequestInfo | URL, init: RequestInit | undefined, env?: NodeJS.ProcessEnv, dispatcher?: Dispatcher | undefined): RequestInitWithDispatcher;
15
15
  export declare function normalizeChatGptNetworkError(error: unknown, env?: NodeJS.ProcessEnv): Error;
16
+ export declare function parseMacSystemProxyForUrl(output: string, url: URL): string | undefined;
16
17
  export {};
@@ -1,3 +1,4 @@
1
+ import { execFileSync } from "node:child_process";
1
2
  import { readFileSync } from "node:fs";
2
3
  import { delimiter } from "node:path";
3
4
  import { rootCertificates } from "node:tls";
@@ -19,9 +20,8 @@ export function getChatGptFetch(env = process.env) {
19
20
  export function createChatGptFetch(options = {}) {
20
21
  const env = options.env ?? process.env;
21
22
  const fetchImpl = options.fetch ?? ((input, init) => globalThis.fetch(input, init));
22
- const dispatcher = createChatGptDispatcher(env);
23
23
  return async (input, init) => {
24
- const requestInit = withChatGptNetworkOptions(input, init, env, dispatcher);
24
+ const requestInit = withChatGptNetworkOptions(input, init, env);
25
25
  try {
26
26
  return await fetchImpl(input, requestInit);
27
27
  }
@@ -34,9 +34,9 @@ export function createChatGptDispatcher(env = process.env, input) {
34
34
  if (isBunRuntime())
35
35
  return undefined;
36
36
  const ca = loadExtraCaCertificates(env);
37
- if (!hasProxyEnv(env) && ca.length === 0)
38
- return undefined;
39
37
  const proxy = input ? nodeProxyForUrl(input, env) : defaultNodeProxy(env);
38
+ if (!proxy && ca.length === 0)
39
+ return undefined;
40
40
  const caOptions = ca.length > 0 ? { ca: [...rootCertificates, ...ca] } : undefined;
41
41
  if (proxy) {
42
42
  return new ProxyAgent({
@@ -73,41 +73,144 @@ export function normalizeChatGptNetworkError(error, env = process.env) {
73
73
  : "This looks like a proxy or network transport failure.",
74
74
  hasProxyEnv(env)
75
75
  ? "Bubble is using proxy environment variables for ChatGPT requests. Make sure NO_PROXY includes localhost,127.0.0.1."
76
- : "If your network requires a proxy, set HTTPS_PROXY or HTTP_PROXY, and set NO_PROXY=localhost,127.0.0.1.",
76
+ : hasMacSystemProxy(env)
77
+ ? "Bubble detected the macOS system proxy for ChatGPT requests. If it is stale, restart your proxy client or set BUBBLE_DISABLE_SYSTEM_PROXY=1 and configure HTTPS_PROXY explicitly."
78
+ : "If your network requires a proxy, set HTTPS_PROXY or HTTP_PROXY, and set NO_PROXY=localhost,127.0.0.1.",
77
79
  "Do not disable TLS verification with NODE_TLS_REJECT_UNAUTHORIZED=0.",
78
80
  `Original error: ${firstMeaningfulErrorMessage(error) || "unknown network error"}`,
79
81
  ].join(" ");
80
82
  return new Error(message, { cause: error });
81
83
  }
82
84
  function hasProxyEnv(env) {
83
- return Boolean(env.HTTPS_PROXY || env.https_proxy || env.HTTP_PROXY || env.http_proxy || env.ALL_PROXY || env.all_proxy);
85
+ return Boolean(env.BUBBLE_CHATGPT_PROXY ||
86
+ env.bubble_chatgpt_proxy ||
87
+ env.HTTPS_PROXY ||
88
+ env.https_proxy ||
89
+ env.HTTP_PROXY ||
90
+ env.http_proxy ||
91
+ env.ALL_PROXY ||
92
+ env.all_proxy);
84
93
  }
85
94
  function isBunRuntime() {
86
95
  return typeof globalThis.Bun !== "undefined";
87
96
  }
88
97
  function bunProxyForUrl(input, env) {
98
+ return proxyForUrl(input, env);
99
+ }
100
+ function nodeProxyForUrl(input, env) {
101
+ return proxyForUrl(input, env);
102
+ }
103
+ function proxyForUrl(input, env) {
89
104
  const url = urlFromInput(input);
90
105
  if (!url || shouldBypassProxy(url, env))
91
106
  return undefined;
107
+ const explicit = explicitProxyForUrl(url, env);
108
+ return explicit ?? macSystemProxyForUrl(url, env);
109
+ }
110
+ function defaultNodeProxy(env) {
111
+ return (env.BUBBLE_CHATGPT_PROXY ??
112
+ env.bubble_chatgpt_proxy ??
113
+ env.HTTPS_PROXY ??
114
+ env.https_proxy ??
115
+ env.HTTP_PROXY ??
116
+ env.http_proxy ??
117
+ env.ALL_PROXY ??
118
+ env.all_proxy ??
119
+ macSystemProxyForUrl(new URL("https://chatgpt.com/"), env));
120
+ }
121
+ function explicitProxyForUrl(url, env) {
122
+ const chatGptProxy = env.BUBBLE_CHATGPT_PROXY ?? env.bubble_chatgpt_proxy;
123
+ if (chatGptProxy)
124
+ return chatGptProxy;
92
125
  const allProxy = env.ALL_PROXY ?? env.all_proxy;
93
126
  if (url.protocol === "https:")
94
127
  return env.HTTPS_PROXY ?? env.https_proxy ?? allProxy;
95
128
  if (url.protocol === "http:")
96
129
  return env.HTTP_PROXY ?? env.http_proxy ?? allProxy;
130
+ return allProxy;
131
+ }
132
+ function hasMacSystemProxy(env) {
133
+ return Boolean(macSystemProxyForUrl(new URL("https://chatgpt.com/"), env));
134
+ }
135
+ function macSystemProxyForUrl(url, env) {
136
+ if (process.platform !== "darwin" || truthyEnv(env.BUBBLE_DISABLE_SYSTEM_PROXY))
137
+ return undefined;
138
+ const output = readMacSystemProxy();
139
+ return output ? parseMacSystemProxyForUrl(output, url) : undefined;
140
+ }
141
+ export function parseMacSystemProxyForUrl(output, url) {
142
+ if (macProxyExceptionMatches(output, url.hostname, url.port))
143
+ return undefined;
144
+ const values = parseScutilProxyValues(output);
145
+ if (url.protocol === "https:") {
146
+ return formatMacProxy(values.HTTPSEnable, values.HTTPSProxy, values.HTTPSPort);
147
+ }
148
+ if (url.protocol === "http:") {
149
+ return formatMacProxy(values.HTTPEnable, values.HTTPProxy, values.HTTPPort);
150
+ }
97
151
  return undefined;
98
152
  }
99
- function nodeProxyForUrl(input, env) {
100
- const url = urlFromInput(input);
101
- if (!url || shouldBypassProxy(url, env))
153
+ function parseScutilProxyValues(output) {
154
+ const values = {};
155
+ for (const line of output.split(/\r?\n/)) {
156
+ const match = line.match(/^\s*([A-Za-z]+(?:Enable|Proxy|Port))\s*:\s*(.+?)\s*$/);
157
+ if (match)
158
+ values[match[1]] = match[2];
159
+ }
160
+ return values;
161
+ }
162
+ function formatMacProxy(enabled, host, port) {
163
+ if (enabled !== "1" || !host || !port)
102
164
  return undefined;
103
- if (url.protocol === "https:")
104
- return env.HTTPS_PROXY ?? env.https_proxy ?? env.ALL_PROXY ?? env.all_proxy;
105
- if (url.protocol === "http:")
106
- return env.HTTP_PROXY ?? env.http_proxy ?? env.ALL_PROXY ?? env.all_proxy;
107
- return defaultNodeProxy(env);
165
+ if (/^[a-z][a-z0-9+.-]*:\/\//i.test(host))
166
+ return host;
167
+ const normalizedHost = host.includes(":") && !host.startsWith("[") ? `[${host}]` : host;
168
+ return `http://${normalizedHost}:${port}`;
108
169
  }
109
- function defaultNodeProxy(env) {
110
- return env.HTTPS_PROXY ?? env.https_proxy ?? env.HTTP_PROXY ?? env.http_proxy ?? env.ALL_PROXY ?? env.all_proxy;
170
+ function macProxyExceptionMatches(output, hostname, port) {
171
+ const exceptions = parseMacProxyExceptions(output);
172
+ return exceptions.some((entry) => noProxyEntryMatches(entry.toLowerCase(), hostname.toLowerCase(), port));
173
+ }
174
+ function parseMacProxyExceptions(output) {
175
+ const exceptions = [];
176
+ let inExceptions = false;
177
+ for (const line of output.split(/\r?\n/)) {
178
+ if (line.includes("ExceptionsList")) {
179
+ inExceptions = true;
180
+ continue;
181
+ }
182
+ if (!inExceptions)
183
+ continue;
184
+ if (/^\s*}\s*$/.test(line))
185
+ break;
186
+ const match = line.match(/^\s*\d+\s*:\s*(.+?)\s*$/);
187
+ if (match)
188
+ exceptions.push(match[1].trim());
189
+ }
190
+ return exceptions;
191
+ }
192
+ let cachedMacSystemProxy;
193
+ function readMacSystemProxy() {
194
+ const now = Date.now();
195
+ if (cachedMacSystemProxy && now - cachedMacSystemProxy.checkedAtMs < 5_000) {
196
+ return cachedMacSystemProxy.output;
197
+ }
198
+ let output;
199
+ try {
200
+ output = execFileSync("scutil", ["--proxy"], {
201
+ encoding: "utf-8",
202
+ stdio: ["ignore", "pipe", "ignore"],
203
+ timeout: 1_000,
204
+ });
205
+ }
206
+ catch {
207
+ output = undefined;
208
+ }
209
+ cachedMacSystemProxy = { checkedAtMs: now, output };
210
+ return output;
211
+ }
212
+ function truthyEnv(value) {
213
+ return /^(1|true|yes|on)$/i.test(value?.trim() ?? "");
111
214
  }
112
215
  function bunExtraCaFiles(env) {
113
216
  const bun = globalThis.Bun;
@@ -177,6 +280,9 @@ function networkEnvSignature(env) {
177
280
  env.https_proxy,
178
281
  env.ALL_PROXY,
179
282
  env.all_proxy,
283
+ env.BUBBLE_CHATGPT_PROXY,
284
+ env.bubble_chatgpt_proxy,
285
+ env.BUBBLE_DISABLE_SYSTEM_PROXY,
180
286
  env.NO_PROXY,
181
287
  env.no_proxy,
182
288
  env.NODE_EXTRA_CA_CERTS,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bubblebrain-ai/bubble",
3
- "version": "0.0.18",
3
+ "version": "0.0.19",
4
4
  "description": "A terminal coding agent",
5
5
  "type": "module",
6
6
  "engines": {