ai-zero-token 1.0.1 → 1.0.3
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 +14 -0
- package/README.md +235 -69
- package/dist/api.js +0 -1
- package/dist/cli/commands/ask.js +131 -5
- package/dist/cli/commands/clear.js +0 -1
- package/dist/cli/commands/help.js +17 -11
- package/dist/cli/commands/login.js +0 -1
- package/dist/cli/commands/models.js +14 -4
- package/dist/cli/commands/serve.js +41 -4
- package/dist/cli/commands/start.js +10 -0
- package/dist/cli/commands/status.js +1 -1
- package/dist/cli/index.js +5 -2
- package/dist/cli/shared.js +57 -6
- package/dist/cli.js +0 -1
- package/dist/core/context.js +10 -2
- package/dist/core/models/openai-codex-models.js +89 -1
- package/dist/core/providers/http-client.js +137 -14
- package/dist/core/providers/openai-codex/chat.js +217 -24
- package/dist/core/providers/openai-codex/oauth.js +15 -4
- package/dist/core/providers/openai-codex/pkce.js +0 -1
- package/dist/core/services/auth-service.js +125 -16
- package/dist/core/services/chat-service.js +24 -14
- package/dist/core/services/config-service.js +4 -5
- package/dist/core/services/image-service.js +405 -0
- package/dist/core/services/model-service.js +35 -8
- package/dist/core/services/version-service.js +97 -0
- package/dist/core/store/profile-store.js +79 -6
- package/dist/core/store/settings-store.js +1 -2
- package/dist/core/types.js +0 -1
- package/dist/http.js +0 -1
- package/dist/models.js +0 -1
- package/dist/oauth.js +0 -1
- package/dist/pkce.js +0 -1
- package/dist/server/admin-page.js +3165 -0
- package/dist/server/app.js +599 -40
- package/dist/server/index.js +0 -1
- package/dist/store.js +0 -1
- package/docs/API_USAGE.md +120 -0
- package/package.json +14 -3
- package/dist/api.js.map +0 -1
- package/dist/cli/commands/ask.js.map +0 -1
- package/dist/cli/commands/clear.js.map +0 -1
- package/dist/cli/commands/help.js.map +0 -1
- package/dist/cli/commands/login.js.map +0 -1
- package/dist/cli/commands/models.js.map +0 -1
- package/dist/cli/commands/serve.js.map +0 -1
- package/dist/cli/commands/status.js.map +0 -1
- package/dist/cli/index.js.map +0 -1
- package/dist/cli/shared.js.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/core/context.js.map +0 -1
- package/dist/core/models/openai-codex-models.js.map +0 -1
- package/dist/core/providers/http-client.js.map +0 -1
- package/dist/core/providers/openai-codex/chat.js.map +0 -1
- package/dist/core/providers/openai-codex/oauth.js.map +0 -1
- package/dist/core/providers/openai-codex/pkce.js.map +0 -1
- package/dist/core/services/auth-service.js.map +0 -1
- package/dist/core/services/chat-service.js.map +0 -1
- package/dist/core/services/config-service.js.map +0 -1
- package/dist/core/services/model-service.js.map +0 -1
- package/dist/core/store/profile-store.js.map +0 -1
- package/dist/core/store/settings-store.js.map +0 -1
- package/dist/core/types.js.map +0 -1
- package/dist/http.js.map +0 -1
- package/dist/models.js.map +0 -1
- package/dist/oauth.js.map +0 -1
- package/dist/pkce.js.map +0 -1
- package/dist/server/app.js.map +0 -1
- package/dist/server/index.js.map +0 -1
- package/dist/store.js.map +0 -1
|
@@ -1,9 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createGatewayContext } from "../../core/context.js";
|
|
3
|
-
async function runModelsCommand() {
|
|
3
|
+
async function runModelsCommand(args = []) {
|
|
4
4
|
const ctx = createGatewayContext();
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
const refresh = args.includes("--refresh");
|
|
6
|
+
const result = refresh ? await ctx.modelService.refreshModels() : {
|
|
7
|
+
models: await ctx.modelService.listModels(),
|
|
8
|
+
catalog: await ctx.modelService.getCatalog()
|
|
9
|
+
};
|
|
10
|
+
console.log(refresh ? "\u5DF2\u91CD\u65B0\u8BFB\u53D6 Codex \u6A21\u578B\u5217\u8868:" : "\u5F53\u524D demo \u53EF\u7528\u6A21\u578B\u5217\u8868:");
|
|
11
|
+
console.log(`- \u6765\u6E90: ${result.catalog.source === "codex-cache" ? "Codex \u672C\u5730\u7F13\u5B58" : "\u9879\u76EE\u5185\u7F6E\u56DE\u9000\u5217\u8868"}`);
|
|
12
|
+
console.log(`- \u8DEF\u5F84: ${result.catalog.cachePath}`);
|
|
13
|
+
if (result.catalog.fetchedAt) {
|
|
14
|
+
console.log(`- Codex \u66F4\u65B0\u65F6\u95F4: ${result.catalog.fetchedAt}`);
|
|
15
|
+
}
|
|
16
|
+
console.log(`- \u6570\u91CF: ${result.catalog.modelCount}`);
|
|
17
|
+
for (const model of result.models) {
|
|
7
18
|
const suffix = model.isDefault ? " (\u9ED8\u8BA4)" : "";
|
|
8
19
|
console.log(`- ${model.id}${suffix}`);
|
|
9
20
|
}
|
|
@@ -11,4 +22,3 @@ async function runModelsCommand() {
|
|
|
11
22
|
export {
|
|
12
23
|
runModelsCommand
|
|
13
24
|
};
|
|
14
|
-
//# sourceMappingURL=models.js.map
|
|
@@ -2,18 +2,55 @@
|
|
|
2
2
|
import { createGatewayContext } from "../../core/context.js";
|
|
3
3
|
import { startServer } from "../../server/index.js";
|
|
4
4
|
import { parseServeArgs } from "../shared.js";
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
import { spawn } from "node:child_process";
|
|
6
|
+
function createBrowserUrl(host, port) {
|
|
7
|
+
if (host === "0.0.0.0" || host === "::") {
|
|
8
|
+
return `http://127.0.0.1:${port}`;
|
|
9
|
+
}
|
|
10
|
+
return `http://${host}:${port}`;
|
|
11
|
+
}
|
|
12
|
+
function createListenUrl(host, port) {
|
|
13
|
+
return `http://${host}:${port}`;
|
|
14
|
+
}
|
|
15
|
+
function tryOpenBrowser(url) {
|
|
16
|
+
try {
|
|
17
|
+
if (process.platform === "darwin") {
|
|
18
|
+
const child2 = spawn("open", [url], { stdio: "ignore", detached: true });
|
|
19
|
+
child2.unref();
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
if (process.platform === "win32") {
|
|
23
|
+
const child2 = spawn("cmd", ["/c", "start", "", url], { stdio: "ignore", detached: true });
|
|
24
|
+
child2.unref();
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
const child = spawn("xdg-open", [url], { stdio: "ignore", detached: true });
|
|
28
|
+
child.unref();
|
|
29
|
+
return true;
|
|
30
|
+
} catch {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
async function runServeCommand(args, options) {
|
|
35
|
+
const { host, port, openBrowser } = parseServeArgs(args);
|
|
7
36
|
const ctx = createGatewayContext();
|
|
8
37
|
const status = await ctx.authService.getStatus();
|
|
9
38
|
const server = await startServer({ host, port });
|
|
39
|
+
const adminUrl = createBrowserUrl(server.host, server.port);
|
|
40
|
+
const listenUrl = createListenUrl(server.host, server.port);
|
|
41
|
+
const shouldOpenBrowser = openBrowser ?? options?.openBrowserByDefault ?? false;
|
|
10
42
|
console.log("\u672C\u5730\u7F51\u5173\u5DF2\u542F\u52A8\u3002");
|
|
11
|
-
console.log(`url:
|
|
43
|
+
console.log(`url: ${listenUrl}`);
|
|
44
|
+
console.log(`admin: ${adminUrl}`);
|
|
45
|
+
console.log(`apiBase: ${adminUrl}/v1`);
|
|
12
46
|
console.log(`corsOrigin: ${server.corsOrigin}`);
|
|
13
47
|
console.log(`activeProvider: ${status.activeProvider ?? "none"}`);
|
|
14
48
|
console.log(`defaultModel: ${status.defaultModel}`);
|
|
49
|
+
if (shouldOpenBrowser) {
|
|
50
|
+
const opened = tryOpenBrowser(adminUrl);
|
|
51
|
+
console.log(opened ? "\u5DF2\u5C1D\u8BD5\u6253\u5F00\u7BA1\u7406\u9875\u9762\u3002" : "\u672A\u80FD\u81EA\u52A8\u6253\u5F00\u7BA1\u7406\u9875\u9762\uFF0C\u8BF7\u624B\u52A8\u8BBF\u95EE\u4E0A\u9762\u7684 admin \u5730\u5740\u3002");
|
|
52
|
+
}
|
|
15
53
|
}
|
|
16
54
|
export {
|
|
17
55
|
runServeCommand
|
|
18
56
|
};
|
|
19
|
-
//# sourceMappingURL=serve.js.map
|
|
@@ -15,6 +15,7 @@ async function runStatusCommand() {
|
|
|
15
15
|
console.log("\u5F53\u524D\u767B\u5F55\u72B6\u6001:");
|
|
16
16
|
console.log(`provider: ${status.activeProvider}`);
|
|
17
17
|
console.log(`profileId: ${status.activeProfileId}`);
|
|
18
|
+
console.log(`profileCount: ${status.profileCount}`);
|
|
18
19
|
console.log(`defaultModel: ${status.defaultModel}`);
|
|
19
20
|
console.log(`serverHost: ${status.serverHost}`);
|
|
20
21
|
console.log(`serverPort: ${status.serverPort}`);
|
|
@@ -29,4 +30,3 @@ async function runStatusCommand() {
|
|
|
29
30
|
export {
|
|
30
31
|
runStatusCommand
|
|
31
32
|
};
|
|
32
|
-
//# sourceMappingURL=status.js.map
|
package/dist/cli/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import { printHelp } from "./commands/help.js";
|
|
|
5
5
|
import { runLoginCommand } from "./commands/login.js";
|
|
6
6
|
import { runModelsCommand } from "./commands/models.js";
|
|
7
7
|
import { runServeCommand } from "./commands/serve.js";
|
|
8
|
+
import { runStartCommand } from "./commands/start.js";
|
|
8
9
|
import { runStatusCommand } from "./commands/status.js";
|
|
9
10
|
async function runCli(argv = process.argv.slice(2)) {
|
|
10
11
|
const [command, ...rest] = argv;
|
|
@@ -16,7 +17,7 @@ async function runCli(argv = process.argv.slice(2)) {
|
|
|
16
17
|
await runStatusCommand();
|
|
17
18
|
return;
|
|
18
19
|
case "models":
|
|
19
|
-
await runModelsCommand();
|
|
20
|
+
await runModelsCommand(rest);
|
|
20
21
|
return;
|
|
21
22
|
case "ask":
|
|
22
23
|
await runAskCommand(rest);
|
|
@@ -24,6 +25,9 @@ async function runCli(argv = process.argv.slice(2)) {
|
|
|
24
25
|
case "serve":
|
|
25
26
|
await runServeCommand(rest);
|
|
26
27
|
return;
|
|
28
|
+
case "start":
|
|
29
|
+
await runStartCommand(rest);
|
|
30
|
+
return;
|
|
27
31
|
case "clear":
|
|
28
32
|
await runClearCommand();
|
|
29
33
|
return;
|
|
@@ -40,4 +44,3 @@ async function runCli(argv = process.argv.slice(2)) {
|
|
|
40
44
|
export {
|
|
41
45
|
runCli
|
|
42
46
|
};
|
|
43
|
-
//# sourceMappingURL=index.js.map
|
package/dist/cli/shared.js
CHANGED
|
@@ -7,16 +7,55 @@ function formatExpiry(expires) {
|
|
|
7
7
|
function parseAskArgs(args) {
|
|
8
8
|
const rest = [...args];
|
|
9
9
|
let model;
|
|
10
|
+
let payloadFile;
|
|
11
|
+
let dumpRawFile;
|
|
12
|
+
let writeArtifactsDir;
|
|
13
|
+
let printRaw = false;
|
|
14
|
+
let allowUnknownModel = false;
|
|
10
15
|
for (let index = 0; index < rest.length; index += 1) {
|
|
11
|
-
if (rest[index]
|
|
16
|
+
if (rest[index] === "--model") {
|
|
17
|
+
model = rest[index + 1];
|
|
18
|
+
rest.splice(index, 2);
|
|
19
|
+
index -= 1;
|
|
12
20
|
continue;
|
|
13
21
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
22
|
+
if (rest[index] === "--payload-file") {
|
|
23
|
+
payloadFile = rest[index + 1];
|
|
24
|
+
rest.splice(index, 2);
|
|
25
|
+
index -= 1;
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
if (rest[index] === "--dump-raw") {
|
|
29
|
+
dumpRawFile = rest[index + 1];
|
|
30
|
+
rest.splice(index, 2);
|
|
31
|
+
index -= 1;
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (rest[index] === "--write-artifacts-dir") {
|
|
35
|
+
writeArtifactsDir = rest[index + 1];
|
|
36
|
+
rest.splice(index, 2);
|
|
37
|
+
index -= 1;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (rest[index] === "--print-raw") {
|
|
41
|
+
printRaw = true;
|
|
42
|
+
rest.splice(index, 1);
|
|
43
|
+
index -= 1;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (rest[index] === "--allow-unknown-model") {
|
|
47
|
+
allowUnknownModel = true;
|
|
48
|
+
rest.splice(index, 1);
|
|
49
|
+
index -= 1;
|
|
50
|
+
}
|
|
17
51
|
}
|
|
18
52
|
return {
|
|
19
53
|
model,
|
|
54
|
+
payloadFile,
|
|
55
|
+
dumpRawFile,
|
|
56
|
+
writeArtifactsDir,
|
|
57
|
+
printRaw,
|
|
58
|
+
allowUnknownModel,
|
|
20
59
|
prompt: rest.join(" ").trim()
|
|
21
60
|
};
|
|
22
61
|
}
|
|
@@ -24,6 +63,7 @@ function parseServeArgs(args) {
|
|
|
24
63
|
const rest = [...args];
|
|
25
64
|
let host;
|
|
26
65
|
let port;
|
|
66
|
+
let openBrowser;
|
|
27
67
|
for (let index = 0; index < rest.length; index += 1) {
|
|
28
68
|
if (rest[index] === "--host") {
|
|
29
69
|
host = rest[index + 1];
|
|
@@ -38,13 +78,24 @@ function parseServeArgs(args) {
|
|
|
38
78
|
}
|
|
39
79
|
rest.splice(index, 2);
|
|
40
80
|
index -= 1;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (rest[index] === "--open") {
|
|
84
|
+
openBrowser = true;
|
|
85
|
+
rest.splice(index, 1);
|
|
86
|
+
index -= 1;
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (rest[index] === "--no-open") {
|
|
90
|
+
openBrowser = false;
|
|
91
|
+
rest.splice(index, 1);
|
|
92
|
+
index -= 1;
|
|
41
93
|
}
|
|
42
94
|
}
|
|
43
|
-
return { host, port };
|
|
95
|
+
return { host, port, openBrowser };
|
|
44
96
|
}
|
|
45
97
|
export {
|
|
46
98
|
formatExpiry,
|
|
47
99
|
parseAskArgs,
|
|
48
100
|
parseServeArgs
|
|
49
101
|
};
|
|
50
|
-
//# sourceMappingURL=shared.js.map
|
package/dist/cli.js
CHANGED
package/dist/core/context.js
CHANGED
|
@@ -2,23 +2,31 @@
|
|
|
2
2
|
import { ConfigService } from "./services/config-service.js";
|
|
3
3
|
import { AuthService } from "./services/auth-service.js";
|
|
4
4
|
import { ChatService } from "./services/chat-service.js";
|
|
5
|
+
import { ImageService } from "./services/image-service.js";
|
|
5
6
|
import { ModelService } from "./services/model-service.js";
|
|
7
|
+
import { VersionService } from "./services/version-service.js";
|
|
6
8
|
function createGatewayContext() {
|
|
7
9
|
const configService = new ConfigService();
|
|
8
10
|
const authService = new AuthService(configService);
|
|
9
11
|
const modelService = new ModelService(configService);
|
|
12
|
+
const versionService = new VersionService();
|
|
10
13
|
const chatService = new ChatService({
|
|
11
14
|
authService,
|
|
12
15
|
modelService
|
|
13
16
|
});
|
|
17
|
+
const imageService = new ImageService({
|
|
18
|
+
authService,
|
|
19
|
+
configService
|
|
20
|
+
});
|
|
14
21
|
return {
|
|
15
22
|
configService,
|
|
16
23
|
authService,
|
|
17
24
|
modelService,
|
|
18
|
-
|
|
25
|
+
versionService,
|
|
26
|
+
chatService,
|
|
27
|
+
imageService
|
|
19
28
|
};
|
|
20
29
|
}
|
|
21
30
|
export {
|
|
22
31
|
createGatewayContext
|
|
23
32
|
};
|
|
24
|
-
//# sourceMappingURL=context.js.map
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
2
5
|
const DEFAULT_CODEX_MODEL = "gpt-5.4";
|
|
3
6
|
const CODEX_MODEL_INFOS = [
|
|
4
7
|
{ provider: "openai-codex", id: "gpt-5.4", name: "GPT-5.4", input: ["text", "image"], source: "static" },
|
|
@@ -22,6 +25,88 @@ const SUPPORTED_CODEX_MODELS = [
|
|
|
22
25
|
"gpt-5.1-codex-mini",
|
|
23
26
|
"gpt-5.1-codex-max"
|
|
24
27
|
];
|
|
28
|
+
function getCodexModelsCachePath() {
|
|
29
|
+
return process.env.CODEX_MODELS_CACHE_PATH || path.join(os.homedir(), ".codex", "models_cache.json");
|
|
30
|
+
}
|
|
31
|
+
function normalizeInputModalities(input) {
|
|
32
|
+
const rawValues = Array.isArray(input) ? input : [];
|
|
33
|
+
const values = /* @__PURE__ */ new Set();
|
|
34
|
+
for (const item of rawValues) {
|
|
35
|
+
if (item === "text" || item === "image") {
|
|
36
|
+
values.add(item);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (values.size === 0) {
|
|
40
|
+
values.add("text");
|
|
41
|
+
}
|
|
42
|
+
return Array.from(values);
|
|
43
|
+
}
|
|
44
|
+
function normalizeCodexCacheEntry(entry) {
|
|
45
|
+
if (!entry || typeof entry.slug !== "string" || !entry.slug) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
if (typeof entry.visibility === "string" && entry.visibility !== "list") {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
provider: "openai-codex",
|
|
53
|
+
id: entry.slug,
|
|
54
|
+
name: typeof entry.display_name === "string" && entry.display_name ? entry.display_name : entry.slug,
|
|
55
|
+
input: normalizeInputModalities(entry.input_modalities),
|
|
56
|
+
source: "codex-cache"
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function dedupeModels(models) {
|
|
60
|
+
const seen = /* @__PURE__ */ new Set();
|
|
61
|
+
const next = [];
|
|
62
|
+
for (const model of models) {
|
|
63
|
+
if (seen.has(model.id)) {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
seen.add(model.id);
|
|
67
|
+
next.push(model);
|
|
68
|
+
}
|
|
69
|
+
return next;
|
|
70
|
+
}
|
|
71
|
+
async function getCodexModelCatalog() {
|
|
72
|
+
const cachePath = getCodexModelsCachePath();
|
|
73
|
+
try {
|
|
74
|
+
const raw = await fs.readFile(cachePath, "utf8");
|
|
75
|
+
const parsed = JSON.parse(raw);
|
|
76
|
+
const models = dedupeModels((parsed.models ?? []).map(normalizeCodexCacheEntry).filter(Boolean));
|
|
77
|
+
if (models.length > 0) {
|
|
78
|
+
return {
|
|
79
|
+
models,
|
|
80
|
+
catalog: {
|
|
81
|
+
source: "codex-cache",
|
|
82
|
+
cachePath,
|
|
83
|
+
fetchedAt: parsed.fetched_at,
|
|
84
|
+
modelCount: models.length
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
} catch {
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
models: CODEX_MODEL_INFOS,
|
|
92
|
+
catalog: {
|
|
93
|
+
source: "static-fallback",
|
|
94
|
+
cachePath,
|
|
95
|
+
modelCount: CODEX_MODEL_INFOS.length
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
async function hasCodexModel(model) {
|
|
100
|
+
const { models } = await getCodexModelCatalog();
|
|
101
|
+
return models.some((item) => item.id === model);
|
|
102
|
+
}
|
|
103
|
+
async function getPreferredCodexModel() {
|
|
104
|
+
const { models } = await getCodexModelCatalog();
|
|
105
|
+
if (models.some((item) => item.id === DEFAULT_CODEX_MODEL)) {
|
|
106
|
+
return DEFAULT_CODEX_MODEL;
|
|
107
|
+
}
|
|
108
|
+
return models[0]?.id ?? DEFAULT_CODEX_MODEL;
|
|
109
|
+
}
|
|
25
110
|
function isSupportedCodexModel(model) {
|
|
26
111
|
return SUPPORTED_CODEX_MODELS.includes(model);
|
|
27
112
|
}
|
|
@@ -29,6 +114,9 @@ export {
|
|
|
29
114
|
CODEX_MODEL_INFOS,
|
|
30
115
|
DEFAULT_CODEX_MODEL,
|
|
31
116
|
SUPPORTED_CODEX_MODELS,
|
|
117
|
+
getCodexModelCatalog,
|
|
118
|
+
getCodexModelsCachePath,
|
|
119
|
+
getPreferredCodexModel,
|
|
120
|
+
hasCodexModel,
|
|
32
121
|
isSupportedCodexModel
|
|
33
122
|
};
|
|
34
|
-
//# sourceMappingURL=openai-codex-models.js.map
|
|
@@ -1,7 +1,63 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
3
|
const CURL_STATUS_MARKER = "\n__CURL_STATUS__:";
|
|
4
|
-
|
|
4
|
+
const CURL_HEADERS_MARKER = "\n__CURL_HEADERS__:";
|
|
5
|
+
let requestSequence = 0;
|
|
6
|
+
function nextRequestId() {
|
|
7
|
+
requestSequence += 1;
|
|
8
|
+
return `http-${String(requestSequence).padStart(4, "0")}`;
|
|
9
|
+
}
|
|
10
|
+
function roundMs(value) {
|
|
11
|
+
return Math.round(value * 100) / 100;
|
|
12
|
+
}
|
|
13
|
+
function finalizeTiming(startedAt, phases) {
|
|
14
|
+
return {
|
|
15
|
+
phasesMs: Object.fromEntries(
|
|
16
|
+
Object.entries(phases).map(([key, value]) => [key, roundMs(value)])
|
|
17
|
+
),
|
|
18
|
+
totalMs: roundMs(performance.now() - startedAt)
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function logHttpTiming(params) {
|
|
22
|
+
console.info("[http] request timing", {
|
|
23
|
+
requestId: params.requestId,
|
|
24
|
+
method: params.method,
|
|
25
|
+
url: params.url,
|
|
26
|
+
transport: params.transport,
|
|
27
|
+
status: params.status,
|
|
28
|
+
bodyLength: params.bodyLength,
|
|
29
|
+
fallbackFrom: params.fallbackFrom,
|
|
30
|
+
phasesMs: params.timing.phasesMs,
|
|
31
|
+
totalMs: params.timing.totalMs
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
function normalizeHeaders(headers) {
|
|
35
|
+
const normalized = {};
|
|
36
|
+
headers.forEach((value, key) => {
|
|
37
|
+
normalized[key.toLowerCase()] = value;
|
|
38
|
+
});
|
|
39
|
+
return normalized;
|
|
40
|
+
}
|
|
41
|
+
function normalizeCurlHeaders(value) {
|
|
42
|
+
if (!value || typeof value !== "object") {
|
|
43
|
+
return {};
|
|
44
|
+
}
|
|
45
|
+
return Object.fromEntries(
|
|
46
|
+
Object.entries(value).flatMap(([key, rawValue]) => {
|
|
47
|
+
if (typeof rawValue === "string" && rawValue.trim()) {
|
|
48
|
+
return [[key.toLowerCase(), rawValue.trim()]];
|
|
49
|
+
}
|
|
50
|
+
if (Array.isArray(rawValue)) {
|
|
51
|
+
const joined = rawValue.filter((item) => typeof item === "string" && item.trim()).join(", ");
|
|
52
|
+
return joined ? [[key.toLowerCase(), joined]] : [];
|
|
53
|
+
}
|
|
54
|
+
return [];
|
|
55
|
+
})
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
async function runCurlRequest(init, params) {
|
|
59
|
+
const requestId = params?.requestId ?? nextRequestId();
|
|
60
|
+
const startedAt = performance.now();
|
|
5
61
|
const args = [
|
|
6
62
|
"--silent",
|
|
7
63
|
"--show-error",
|
|
@@ -10,7 +66,7 @@ async function runCurlRequest(init) {
|
|
|
10
66
|
init.method,
|
|
11
67
|
init.url,
|
|
12
68
|
"--write-out",
|
|
13
|
-
`${CURL_STATUS_MARKER}%{http_code}`
|
|
69
|
+
`${CURL_STATUS_MARKER}%{http_code}${CURL_HEADERS_MARKER}%{header_json}`
|
|
14
70
|
];
|
|
15
71
|
for (const [key, value] of Object.entries(init.headers ?? {})) {
|
|
16
72
|
args.push("--header", `${key}: ${value}`);
|
|
@@ -22,6 +78,9 @@ async function runCurlRequest(init) {
|
|
|
22
78
|
env: process.env,
|
|
23
79
|
stdio: ["ignore", "pipe", "pipe"]
|
|
24
80
|
});
|
|
81
|
+
const phases = {
|
|
82
|
+
spawnCurlMs: performance.now() - startedAt
|
|
83
|
+
};
|
|
25
84
|
let stdout = "";
|
|
26
85
|
let stderr = "";
|
|
27
86
|
child.stdout.setEncoding("utf8");
|
|
@@ -36,49 +95,113 @@ async function runCurlRequest(init) {
|
|
|
36
95
|
child.on("error", reject);
|
|
37
96
|
child.on("close", (code) => resolve(code ?? 1));
|
|
38
97
|
});
|
|
98
|
+
phases.waitForCurlMs = performance.now() - startedAt - phases.spawnCurlMs;
|
|
39
99
|
if (exitCode !== 0) {
|
|
40
100
|
throw new Error(stderr.trim() || `curl \u8BF7\u6C42\u5931\u8D25\uFF0C\u9000\u51FA\u7801 ${exitCode}`);
|
|
41
101
|
}
|
|
42
|
-
const
|
|
43
|
-
|
|
102
|
+
const parseStartedAt = performance.now();
|
|
103
|
+
const statusMarkerIndex = stdout.lastIndexOf(CURL_STATUS_MARKER);
|
|
104
|
+
const headersMarkerIndex = stdout.lastIndexOf(CURL_HEADERS_MARKER);
|
|
105
|
+
if (statusMarkerIndex === -1) {
|
|
44
106
|
throw new Error("curl \u54CD\u5E94\u7F3A\u5C11\u72B6\u6001\u7801\u6807\u8BB0\u3002");
|
|
45
107
|
}
|
|
46
|
-
|
|
47
|
-
|
|
108
|
+
if (headersMarkerIndex === -1 || headersMarkerIndex < statusMarkerIndex) {
|
|
109
|
+
throw new Error("curl \u54CD\u5E94\u7F3A\u5C11\u54CD\u5E94\u5934\u6807\u8BB0\u3002");
|
|
110
|
+
}
|
|
111
|
+
const body = stdout.slice(0, statusMarkerIndex);
|
|
112
|
+
const statusText = stdout.slice(statusMarkerIndex + CURL_STATUS_MARKER.length, headersMarkerIndex).trim();
|
|
48
113
|
const status = Number.parseInt(statusText, 10);
|
|
49
114
|
if (!Number.isFinite(status)) {
|
|
50
115
|
throw new Error(`\u65E0\u6CD5\u89E3\u6790 curl \u72B6\u6001\u7801: ${statusText}`);
|
|
51
116
|
}
|
|
117
|
+
const headersText = stdout.slice(headersMarkerIndex + CURL_HEADERS_MARKER.length).trim();
|
|
118
|
+
let headers = {};
|
|
119
|
+
if (headersText) {
|
|
120
|
+
try {
|
|
121
|
+
headers = normalizeCurlHeaders(JSON.parse(headersText));
|
|
122
|
+
} catch (error) {
|
|
123
|
+
console.warn("[http] failed to parse curl response headers", {
|
|
124
|
+
requestId,
|
|
125
|
+
url: init.url,
|
|
126
|
+
error: error instanceof Error ? error.message : String(error)
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
phases.parseResponseMs = performance.now() - parseStartedAt;
|
|
131
|
+
const timing = finalizeTiming(startedAt, phases);
|
|
132
|
+
logHttpTiming({
|
|
133
|
+
requestId,
|
|
134
|
+
method: init.method,
|
|
135
|
+
url: init.url,
|
|
136
|
+
status,
|
|
137
|
+
transport: "curl",
|
|
138
|
+
timing,
|
|
139
|
+
bodyLength: body.length,
|
|
140
|
+
fallbackFrom: params?.fallbackFrom
|
|
141
|
+
});
|
|
52
142
|
return {
|
|
53
143
|
body,
|
|
54
144
|
status,
|
|
55
|
-
transport: "curl"
|
|
145
|
+
transport: "curl",
|
|
146
|
+
timing,
|
|
147
|
+
requestId,
|
|
148
|
+
headers
|
|
56
149
|
};
|
|
57
150
|
}
|
|
58
151
|
async function requestText(init) {
|
|
152
|
+
const requestId = nextRequestId();
|
|
59
153
|
const useCurlOnly = process.env.OAUTH_DEMO_USE_CURL === "1";
|
|
60
|
-
const timeoutMs = init.timeoutMs
|
|
154
|
+
const timeoutMs = init.timeoutMs;
|
|
155
|
+
const signal = typeof timeoutMs === "number" && Number.isFinite(timeoutMs) && timeoutMs > 0 ? AbortSignal.timeout(timeoutMs) : void 0;
|
|
61
156
|
if (!useCurlOnly) {
|
|
157
|
+
const startedAt = performance.now();
|
|
158
|
+
const phases = {};
|
|
62
159
|
try {
|
|
160
|
+
const fetchStartedAt = performance.now();
|
|
63
161
|
const response = await fetch(init.url, {
|
|
64
162
|
method: init.method,
|
|
65
163
|
headers: init.headers,
|
|
66
164
|
body: init.body,
|
|
67
|
-
signal
|
|
165
|
+
signal
|
|
166
|
+
});
|
|
167
|
+
phases.waitForHeadersMs = performance.now() - fetchStartedAt;
|
|
168
|
+
const readBodyStartedAt = performance.now();
|
|
169
|
+
const body = await response.text();
|
|
170
|
+
phases.readBodyMs = performance.now() - readBodyStartedAt;
|
|
171
|
+
const timing = finalizeTiming(startedAt, phases);
|
|
172
|
+
logHttpTiming({
|
|
173
|
+
requestId,
|
|
174
|
+
method: init.method,
|
|
175
|
+
url: init.url,
|
|
176
|
+
status: response.status,
|
|
177
|
+
transport: "fetch",
|
|
178
|
+
timing,
|
|
179
|
+
bodyLength: body.length
|
|
68
180
|
});
|
|
69
181
|
return {
|
|
70
|
-
body
|
|
182
|
+
body,
|
|
71
183
|
status: response.status,
|
|
72
|
-
transport: "fetch"
|
|
184
|
+
transport: "fetch",
|
|
185
|
+
timing,
|
|
186
|
+
requestId,
|
|
187
|
+
headers: normalizeHeaders(response.headers)
|
|
73
188
|
};
|
|
74
189
|
} catch (error) {
|
|
75
190
|
const message = error instanceof Error ? error.message : String(error);
|
|
76
|
-
console.
|
|
191
|
+
console.warn("[http] fetch attempt failed", {
|
|
192
|
+
requestId,
|
|
193
|
+
method: init.method,
|
|
194
|
+
url: init.url,
|
|
195
|
+
elapsedMs: roundMs(performance.now() - startedAt),
|
|
196
|
+
error: message
|
|
197
|
+
});
|
|
77
198
|
}
|
|
78
199
|
}
|
|
79
|
-
return runCurlRequest(init
|
|
200
|
+
return runCurlRequest(init, {
|
|
201
|
+
requestId,
|
|
202
|
+
fallbackFrom: useCurlOnly ? void 0 : "fetch"
|
|
203
|
+
});
|
|
80
204
|
}
|
|
81
205
|
export {
|
|
82
206
|
requestText
|
|
83
207
|
};
|
|
84
|
-
//# sourceMappingURL=http-client.js.map
|