ai-zero-token 1.0.4 → 1.0.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.
- package/CHANGELOG.md +7 -0
- package/README.md +2 -0
- package/dist/core/providers/http-client.js +23 -2
- package/dist/core/services/config-service.js +30 -0
- package/dist/core/services/image-service.js +4 -9
- package/dist/core/store/settings-store.js +10 -0
- package/dist/server/admin-page.js +75 -0
- package/dist/server/app.js +13 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.5 - 2026-04-27
|
|
4
|
+
|
|
5
|
+
- Added management-page proxy configuration for upstream requests, persisted in local settings.
|
|
6
|
+
- Routed upstream requests through configured curl proxy settings when enabled.
|
|
7
|
+
- Removed the local fixed-size allowlist for image generation `size`, allowing upstream validation to decide supported values.
|
|
8
|
+
- Documented the proxy configuration workflow without including a specific proxy address.
|
|
9
|
+
|
|
3
10
|
## 1.0.4 - 2026-04-24
|
|
4
11
|
|
|
5
12
|
- Moved persistent account and settings state to the user home directory at `~/.ai-zero-token/.state`.
|
package/README.md
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
|
+
import { loadSettings } from "../store/settings-store.js";
|
|
3
4
|
const CURL_STATUS_MARKER = "\n__CURL_STATUS__:";
|
|
4
5
|
const CURL_HEADERS_MARKER = "\n__CURL_HEADERS__:";
|
|
5
6
|
let requestSequence = 0;
|
|
@@ -68,6 +69,12 @@ async function runCurlRequest(init, params) {
|
|
|
68
69
|
"--write-out",
|
|
69
70
|
`${CURL_STATUS_MARKER}%{http_code}${CURL_HEADERS_MARKER}%{header_json}`
|
|
70
71
|
];
|
|
72
|
+
if (params?.proxy?.enabled && params.proxy.url.trim()) {
|
|
73
|
+
args.push("--proxy", params.proxy.url.trim());
|
|
74
|
+
if (params.proxy.noProxy.trim()) {
|
|
75
|
+
args.push("--noproxy", params.proxy.noProxy.trim());
|
|
76
|
+
}
|
|
77
|
+
}
|
|
71
78
|
for (const [key, value] of Object.entries(init.headers ?? {})) {
|
|
72
79
|
args.push("--header", `${key}: ${value}`);
|
|
73
80
|
}
|
|
@@ -148,12 +155,25 @@ async function runCurlRequest(init, params) {
|
|
|
148
155
|
headers
|
|
149
156
|
};
|
|
150
157
|
}
|
|
158
|
+
async function loadNetworkProxySettings() {
|
|
159
|
+
try {
|
|
160
|
+
const settings = await loadSettings();
|
|
161
|
+
return settings.networkProxy;
|
|
162
|
+
} catch (error) {
|
|
163
|
+
console.warn("[http] failed to load network proxy settings", {
|
|
164
|
+
error: error instanceof Error ? error.message : String(error)
|
|
165
|
+
});
|
|
166
|
+
return void 0;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
151
169
|
async function requestText(init) {
|
|
152
170
|
const requestId = nextRequestId();
|
|
171
|
+
const proxy = await loadNetworkProxySettings();
|
|
153
172
|
const useCurlOnly = process.env.OAUTH_DEMO_USE_CURL === "1";
|
|
173
|
+
const useConfiguredProxy = !!proxy?.enabled && !!proxy.url.trim();
|
|
154
174
|
const timeoutMs = init.timeoutMs;
|
|
155
175
|
const signal = typeof timeoutMs === "number" && Number.isFinite(timeoutMs) && timeoutMs > 0 ? AbortSignal.timeout(timeoutMs) : void 0;
|
|
156
|
-
if (!useCurlOnly) {
|
|
176
|
+
if (!useCurlOnly && !useConfiguredProxy) {
|
|
157
177
|
const startedAt = performance.now();
|
|
158
178
|
const phases = {};
|
|
159
179
|
try {
|
|
@@ -199,7 +219,8 @@ async function requestText(init) {
|
|
|
199
219
|
}
|
|
200
220
|
return runCurlRequest(init, {
|
|
201
221
|
requestId,
|
|
202
|
-
fallbackFrom: useCurlOnly ? void 0 : "fetch"
|
|
222
|
+
fallbackFrom: useCurlOnly || useConfiguredProxy ? void 0 : "fetch",
|
|
223
|
+
proxy
|
|
203
224
|
});
|
|
204
225
|
}
|
|
205
226
|
export {
|
|
@@ -41,6 +41,36 @@ class ConfigService {
|
|
|
41
41
|
await saveSettings(next);
|
|
42
42
|
return next;
|
|
43
43
|
}
|
|
44
|
+
async setNetworkProxy(params) {
|
|
45
|
+
const url = params.url?.trim() ?? "";
|
|
46
|
+
const noProxy = params.noProxy?.trim() || "localhost,127.0.0.1,::1";
|
|
47
|
+
if (params.enabled) {
|
|
48
|
+
if (!url) {
|
|
49
|
+
throw new Error("\u542F\u7528\u4EE3\u7406\u65F6\u5FC5\u987B\u586B\u5199\u4EE3\u7406\u5730\u5740\u3002");
|
|
50
|
+
}
|
|
51
|
+
let parsed;
|
|
52
|
+
try {
|
|
53
|
+
parsed = new URL(url);
|
|
54
|
+
} catch {
|
|
55
|
+
throw new Error("\u4EE3\u7406\u5730\u5740\u683C\u5F0F\u9519\u8BEF\uFF0C\u8BF7\u586B\u5199\u5B8C\u6574\u7684\u4EE3\u7406 URL\u3002");
|
|
56
|
+
}
|
|
57
|
+
const supportedProtocols = /* @__PURE__ */ new Set(["http:", "https:", "socks4:", "socks4a:", "socks5:", "socks5h:"]);
|
|
58
|
+
if (!supportedProtocols.has(parsed.protocol)) {
|
|
59
|
+
throw new Error("\u4EE3\u7406\u5730\u5740\u4EC5\u652F\u6301 http\u3001https\u3001socks4\u3001socks4a\u3001socks5 \u6216 socks5h\u3002");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const settings = await this.getSettings();
|
|
63
|
+
const next = {
|
|
64
|
+
...settings,
|
|
65
|
+
networkProxy: {
|
|
66
|
+
enabled: params.enabled,
|
|
67
|
+
url,
|
|
68
|
+
noProxy
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
await saveSettings(next);
|
|
72
|
+
return next;
|
|
73
|
+
}
|
|
44
74
|
async getServerConfig() {
|
|
45
75
|
const settings = await this.getSettings();
|
|
46
76
|
return settings.server;
|
|
@@ -6,11 +6,6 @@ const SUPPORTED_IMAGE_MODELS = /* @__PURE__ */ new Set([
|
|
|
6
6
|
"gpt-image-1.5",
|
|
7
7
|
"gpt-image-2"
|
|
8
8
|
]);
|
|
9
|
-
const SUPPORTED_IMAGE_SIZES = /* @__PURE__ */ new Set([
|
|
10
|
-
"1024x1024",
|
|
11
|
-
"1024x1536",
|
|
12
|
-
"1536x1024"
|
|
13
|
-
]);
|
|
14
9
|
const SUPPORTED_IMAGE_QUALITIES = /* @__PURE__ */ new Set([
|
|
15
10
|
"low",
|
|
16
11
|
"medium",
|
|
@@ -62,11 +57,11 @@ function toImageGenerationEventOutput(value) {
|
|
|
62
57
|
return null;
|
|
63
58
|
}
|
|
64
59
|
function normalizeReturnedSize(size, fallback) {
|
|
65
|
-
if (typeof size === "string" &&
|
|
66
|
-
return size;
|
|
60
|
+
if (typeof size === "string" && size.trim()) {
|
|
61
|
+
return size.trim();
|
|
67
62
|
}
|
|
68
|
-
if (typeof fallback === "string" &&
|
|
69
|
-
return fallback;
|
|
63
|
+
if (typeof fallback === "string" && fallback.trim()) {
|
|
64
|
+
return fallback.trim();
|
|
70
65
|
}
|
|
71
66
|
return void 0;
|
|
72
67
|
}
|
|
@@ -10,6 +10,11 @@ function createDefaultSettings() {
|
|
|
10
10
|
version: 1,
|
|
11
11
|
defaultProvider: "openai-codex",
|
|
12
12
|
defaultModel: "gpt-5.4",
|
|
13
|
+
networkProxy: {
|
|
14
|
+
enabled: false,
|
|
15
|
+
url: "",
|
|
16
|
+
noProxy: "localhost,127.0.0.1,::1"
|
|
17
|
+
},
|
|
13
18
|
server: {
|
|
14
19
|
host: "0.0.0.0",
|
|
15
20
|
port: 8787
|
|
@@ -26,6 +31,11 @@ async function loadSettings() {
|
|
|
26
31
|
version: 1,
|
|
27
32
|
defaultProvider: parsed.defaultProvider ?? defaults.defaultProvider,
|
|
28
33
|
defaultModel: parsed.defaultModel ?? defaults.defaultModel,
|
|
34
|
+
networkProxy: {
|
|
35
|
+
enabled: parsed.networkProxy?.enabled ?? defaults.networkProxy.enabled,
|
|
36
|
+
url: parsed.networkProxy?.url ?? defaults.networkProxy.url,
|
|
37
|
+
noProxy: parsed.networkProxy?.noProxy ?? defaults.networkProxy.noProxy
|
|
38
|
+
},
|
|
29
39
|
server: {
|
|
30
40
|
host: parsed.server?.host ?? defaults.server.host,
|
|
31
41
|
port: parsed.server?.port ?? defaults.server.port
|
|
@@ -990,6 +990,21 @@ function renderAdminPage() {
|
|
|
990
990
|
color: var(--text-soft);
|
|
991
991
|
}
|
|
992
992
|
|
|
993
|
+
.checkbox-row {
|
|
994
|
+
display: flex;
|
|
995
|
+
align-items: center;
|
|
996
|
+
gap: 10px;
|
|
997
|
+
color: var(--text-soft);
|
|
998
|
+
font-size: 13px;
|
|
999
|
+
font-weight: 600;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
.checkbox-row input {
|
|
1003
|
+
width: 16px;
|
|
1004
|
+
height: 16px;
|
|
1005
|
+
accent-color: var(--brand);
|
|
1006
|
+
}
|
|
1007
|
+
|
|
993
1008
|
.pre {
|
|
994
1009
|
margin: 0;
|
|
995
1010
|
padding: 14px;
|
|
@@ -1465,6 +1480,21 @@ function renderAdminPage() {
|
|
|
1465
1480
|
</div>
|
|
1466
1481
|
</div>
|
|
1467
1482
|
|
|
1483
|
+
<div class="field">
|
|
1484
|
+
<label class="checkbox-row" for="proxyEnabled">
|
|
1485
|
+
<input id="proxyEnabled" type="checkbox" />
|
|
1486
|
+
\u542F\u7528\u4E0A\u6E38\u4EE3\u7406
|
|
1487
|
+
</label>
|
|
1488
|
+
<label for="proxyUrl">\u4EE3\u7406\u5730\u5740</label>
|
|
1489
|
+
<input class="input" id="proxyUrl" type="text" placeholder="\u586B\u5199\u4F60\u7684\u4EE3\u7406\u5730\u5740" />
|
|
1490
|
+
<label for="proxyNoProxy">\u76F4\u8FDE\u5730\u5740</label>
|
|
1491
|
+
<input class="input" id="proxyNoProxy" type="text" placeholder="localhost,127.0.0.1,::1" />
|
|
1492
|
+
<p class="hint">\u542F\u7528\u540E\uFF0COAuth \u6362\u53D6 token\u3001\u6A21\u578B\u5237\u65B0\u548C\u63A5\u53E3\u8F6C\u53D1\u4F1A\u901A\u8FC7\u6B64\u4EE3\u7406\u8BBF\u95EE\u6D77\u5916\u4E0A\u6E38\u3002</p>
|
|
1493
|
+
<div class="actions">
|
|
1494
|
+
<button class="btn-primary" id="saveProxyBtn" type="button">\u4FDD\u5B58\u4EE3\u7406\u914D\u7F6E</button>
|
|
1495
|
+
</div>
|
|
1496
|
+
</div>
|
|
1497
|
+
|
|
1468
1498
|
<div class="field">
|
|
1469
1499
|
<label for="requestBody">\u8BF7\u6C42\u4F53 JSON</label>
|
|
1470
1500
|
<textarea class="textarea" id="requestBody" spellcheck="false"></textarea>
|
|
@@ -1638,6 +1668,9 @@ function renderAdminPage() {
|
|
|
1638
1668
|
const profileSearch = document.getElementById("profileSearch");
|
|
1639
1669
|
const profileStatusFilter = document.getElementById("profileStatusFilter");
|
|
1640
1670
|
const profileSort = document.getElementById("profileSort");
|
|
1671
|
+
const proxyEnabled = document.getElementById("proxyEnabled");
|
|
1672
|
+
const proxyUrl = document.getElementById("proxyUrl");
|
|
1673
|
+
const proxyNoProxy = document.getElementById("proxyNoProxy");
|
|
1641
1674
|
|
|
1642
1675
|
function setBusy(button, busy) {
|
|
1643
1676
|
if (button) {
|
|
@@ -2567,6 +2600,7 @@ function renderAdminPage() {
|
|
|
2567
2600
|
["Base URL", config.baseUrl],
|
|
2568
2601
|
["Provider", config.status.activeProvider || "openai-codex"],
|
|
2569
2602
|
["\u9ED8\u8BA4\u6A21\u578B", config.settings.defaultModel],
|
|
2603
|
+
["\u4E0A\u6E38\u4EE3\u7406", config.settings.networkProxy && config.settings.networkProxy.enabled ? "\u5DF2\u542F\u7528" : "\u672A\u542F\u7528"],
|
|
2570
2604
|
["\u5F53\u524D\u7248\u672C", getVersionValue(config)],
|
|
2571
2605
|
["\u5F53\u524D\u5957\u9910", config.profile ? getPlanType(config.profile) : "\u672A\u767B\u5F55"],
|
|
2572
2606
|
["\u751F\u56FE\u80FD\u529B", getImageCapability(config.profile).detail],
|
|
@@ -2585,6 +2619,7 @@ function renderAdminPage() {
|
|
|
2585
2619
|
["API Base URL", config.baseUrl],
|
|
2586
2620
|
["\u5F53\u524D\u8D26\u53F7", getProfileDisplayLabel(config.profile)],
|
|
2587
2621
|
["\u9ED8\u8BA4\u6A21\u578B", config.settings.defaultModel],
|
|
2622
|
+
["\u4E0A\u6E38\u4EE3\u7406", config.settings.networkProxy && config.settings.networkProxy.enabled ? config.settings.networkProxy.url : "\u672A\u542F\u7528"],
|
|
2588
2623
|
["\u7248\u672C\u72B6\u6001", getVersionDetail(config)],
|
|
2589
2624
|
["\u5F53\u524D\u5957\u9910", config.profile ? getPlanType(config.profile) : "\u672A\u767B\u5F55"],
|
|
2590
2625
|
["\u751F\u56FE\u80FD\u529B", getImageCapability(config.profile).detail],
|
|
@@ -2640,6 +2675,17 @@ function renderAdminPage() {
|
|
|
2640
2675
|
hint.textContent = parts.join("\uFF0C") + "\u3002";
|
|
2641
2676
|
}
|
|
2642
2677
|
|
|
2678
|
+
function renderProxySettings(config) {
|
|
2679
|
+
const proxy = config.settings.networkProxy || {
|
|
2680
|
+
enabled: false,
|
|
2681
|
+
url: "",
|
|
2682
|
+
noProxy: "localhost,127.0.0.1,::1",
|
|
2683
|
+
};
|
|
2684
|
+
proxyEnabled.checked = !!proxy.enabled;
|
|
2685
|
+
proxyUrl.value = proxy.url || "";
|
|
2686
|
+
proxyNoProxy.value = proxy.noProxy || "localhost,127.0.0.1,::1";
|
|
2687
|
+
}
|
|
2688
|
+
|
|
2643
2689
|
function syncHero(config) {
|
|
2644
2690
|
const profileText = config.profile
|
|
2645
2691
|
? "\u5F53\u524D\u8D26\u53F7\u4E3A " + getProfileDisplayLabel(config.profile) + "\uFF0C\u5957\u9910 " + getPlanType(config.profile) + "\uFF0C\u53EF\u5728\u53F3\u4FA7\u5B8C\u6210\u6A21\u578B\u5207\u6362\u548C\u63A5\u53E3\u8C03\u8BD5\u3002"
|
|
@@ -2676,6 +2722,7 @@ function renderAdminPage() {
|
|
|
2676
2722
|
renderProfiles(config);
|
|
2677
2723
|
renderModelOptions(config);
|
|
2678
2724
|
renderModelCatalogStatus(config);
|
|
2725
|
+
renderProxySettings(config);
|
|
2679
2726
|
renderUpdatePanel(config);
|
|
2680
2727
|
renderEndpoints(config);
|
|
2681
2728
|
renderServiceInfo(config);
|
|
@@ -2880,6 +2927,33 @@ function renderAdminPage() {
|
|
|
2880
2927
|
}
|
|
2881
2928
|
}
|
|
2882
2929
|
|
|
2930
|
+
async function saveProxy() {
|
|
2931
|
+
const button = document.getElementById("saveProxyBtn");
|
|
2932
|
+
setBusy(button, true);
|
|
2933
|
+
authStatus.textContent = "\u6B63\u5728\u4FDD\u5B58\u4EE3\u7406\u914D\u7F6E...";
|
|
2934
|
+
try {
|
|
2935
|
+
const config = await fetchJson("/_gateway/admin/settings", {
|
|
2936
|
+
method: "PUT",
|
|
2937
|
+
headers: {
|
|
2938
|
+
"Content-Type": "application/json",
|
|
2939
|
+
},
|
|
2940
|
+
body: formatJson({
|
|
2941
|
+
networkProxy: {
|
|
2942
|
+
enabled: proxyEnabled.checked,
|
|
2943
|
+
url: proxyUrl.value,
|
|
2944
|
+
noProxy: proxyNoProxy.value,
|
|
2945
|
+
},
|
|
2946
|
+
}),
|
|
2947
|
+
});
|
|
2948
|
+
renderConfig(config);
|
|
2949
|
+
authStatus.textContent = proxyEnabled.checked ? "\u4EE3\u7406\u914D\u7F6E\u5DF2\u542F\u7528\u3002" : "\u4EE3\u7406\u914D\u7F6E\u5DF2\u5173\u95ED\u3002";
|
|
2950
|
+
} catch (error) {
|
|
2951
|
+
authStatus.textContent = error.message;
|
|
2952
|
+
} finally {
|
|
2953
|
+
setBusy(button, false);
|
|
2954
|
+
}
|
|
2955
|
+
}
|
|
2956
|
+
|
|
2883
2957
|
async function refreshModels() {
|
|
2884
2958
|
const button = document.getElementById("refreshModelsBtn");
|
|
2885
2959
|
setBusy(button, true);
|
|
@@ -3034,6 +3108,7 @@ function renderAdminPage() {
|
|
|
3034
3108
|
document.getElementById("closeImagePreviewBtn").addEventListener("click", closeImagePreviewModal);
|
|
3035
3109
|
document.getElementById("refreshModelsBtn").addEventListener("click", refreshModels);
|
|
3036
3110
|
document.getElementById("saveModelBtn").addEventListener("click", saveModel);
|
|
3111
|
+
document.getElementById("saveProxyBtn").addEventListener("click", saveProxy);
|
|
3037
3112
|
runTestBtn.addEventListener("click", runTest);
|
|
3038
3113
|
document.querySelectorAll("[data-result-tab]").forEach(function (button) {
|
|
3039
3114
|
button.addEventListener("click", function () {
|
package/dist/server/app.js
CHANGED
|
@@ -67,7 +67,12 @@ const chatCompletionsBodySchema = z.object({
|
|
|
67
67
|
user: z.string().optional()
|
|
68
68
|
}).passthrough();
|
|
69
69
|
const settingsUpdateSchema = z.object({
|
|
70
|
-
defaultModel: z.string().min(1)
|
|
70
|
+
defaultModel: z.string().min(1).optional(),
|
|
71
|
+
networkProxy: z.object({
|
|
72
|
+
enabled: z.boolean(),
|
|
73
|
+
url: z.string().optional(),
|
|
74
|
+
noProxy: z.string().optional()
|
|
75
|
+
}).optional()
|
|
71
76
|
});
|
|
72
77
|
const profileActionSchema = z.object({
|
|
73
78
|
profileId: z.string().min(1)
|
|
@@ -77,7 +82,7 @@ const imageGenerationsBodySchema = z.object({
|
|
|
77
82
|
model: z.string().optional(),
|
|
78
83
|
n: z.number().int().positive().optional(),
|
|
79
84
|
quality: z.enum(["low", "medium", "high", "auto"]).optional(),
|
|
80
|
-
size: z.
|
|
85
|
+
size: z.string().min(1).optional(),
|
|
81
86
|
background: z.enum(["transparent", "opaque", "auto"]).optional(),
|
|
82
87
|
output_format: z.enum(["png", "webp", "jpeg"]).optional(),
|
|
83
88
|
output_compression: z.number().int().min(0).max(100).optional(),
|
|
@@ -473,7 +478,12 @@ function createApp(params) {
|
|
|
473
478
|
}
|
|
474
479
|
};
|
|
475
480
|
}
|
|
476
|
-
|
|
481
|
+
if (parsed.data.defaultModel) {
|
|
482
|
+
await ctx.configService.setDefaultModel(parsed.data.defaultModel);
|
|
483
|
+
}
|
|
484
|
+
if (parsed.data.networkProxy) {
|
|
485
|
+
await ctx.configService.setNetworkProxy(parsed.data.networkProxy);
|
|
486
|
+
}
|
|
477
487
|
return buildAdminConfig(request);
|
|
478
488
|
});
|
|
479
489
|
app.get("/v1/models", async () => ({
|
package/package.json
CHANGED