ai-zero-token 1.0.0 → 1.0.2
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/README.md +235 -58
- 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 +15 -10
- package/dist/cli/commands/login.js +0 -1
- package/dist/cli/commands/models.js +0 -1
- package/dist/cli/commands/serve.js +42 -4
- package/dist/cli/commands/start.js +10 -0
- package/dist/cli/commands/status.js +1 -1
- package/dist/cli/index.js +4 -1
- package/dist/cli/shared.js +57 -6
- package/dist/cli.js +0 -1
- package/dist/core/context.js +7 -2
- package/dist/core/models/openai-codex-models.js +0 -1
- package/dist/core/providers/http-client.js +97 -9
- 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 +89 -16
- package/dist/core/services/chat-service.js +24 -14
- package/dist/core/services/config-service.js +0 -1
- package/dist/core/services/image-service.js +360 -0
- package/dist/core/services/model-service.js +4 -2
- 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 +2615 -0
- package/dist/server/app.js +566 -39
- package/dist/server/index.js +13 -3
- package/dist/store.js +0 -1
- package/package.json +14 -6
- 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
|
@@ -12,6 +12,7 @@ const TOKEN_URL = "https://auth.openai.com/oauth/token";
|
|
|
12
12
|
const REDIRECT_URI = "http://localhost:1455/auth/callback";
|
|
13
13
|
const SCOPE = "openid profile email offline_access";
|
|
14
14
|
const JWT_CLAIM_PATH = "https://api.openai.com/auth";
|
|
15
|
+
const PROFILE_CLAIM_PATH = "https://api.openai.com/profile";
|
|
15
16
|
const SUCCESS_HTML = `<!doctype html>
|
|
16
17
|
<html lang="zh-CN">
|
|
17
18
|
<head>
|
|
@@ -41,6 +42,18 @@ function decodeJwtPayload(token) {
|
|
|
41
42
|
return null;
|
|
42
43
|
}
|
|
43
44
|
}
|
|
45
|
+
function extractEmailFromPayload(payload) {
|
|
46
|
+
const profileClaim = payload?.[PROFILE_CLAIM_PATH];
|
|
47
|
+
const nestedEmail = profileClaim?.email;
|
|
48
|
+
if (typeof nestedEmail === "string" && nestedEmail.trim()) {
|
|
49
|
+
return nestedEmail.trim();
|
|
50
|
+
}
|
|
51
|
+
const topLevelEmail = payload?.email;
|
|
52
|
+
if (typeof topLevelEmail === "string" && topLevelEmail.trim()) {
|
|
53
|
+
return topLevelEmail.trim();
|
|
54
|
+
}
|
|
55
|
+
return void 0;
|
|
56
|
+
}
|
|
44
57
|
function parseAuthorizationInput(value) {
|
|
45
58
|
const trimmed = value.trim();
|
|
46
59
|
if (!trimmed) {
|
|
@@ -74,11 +87,10 @@ function extractProfile(accessToken, refreshToken, expires) {
|
|
|
74
87
|
if (typeof accountId !== "string" || !accountId.trim()) {
|
|
75
88
|
throw new Error("\u65E0\u6CD5\u4ECE access token \u4E2D\u63D0\u53D6 accountId\u3002");
|
|
76
89
|
}
|
|
77
|
-
const email =
|
|
78
|
-
const profileKey = email ?? accountId;
|
|
90
|
+
const email = extractEmailFromPayload(payload);
|
|
79
91
|
return {
|
|
80
92
|
provider: "openai-codex",
|
|
81
|
-
profileId: `openai-codex:${
|
|
93
|
+
profileId: `openai-codex:${accountId}`,
|
|
82
94
|
mode: "oauth_account",
|
|
83
95
|
access: accessToken,
|
|
84
96
|
refresh: refreshToken,
|
|
@@ -281,4 +293,3 @@ export {
|
|
|
281
293
|
loginOpenAICodex,
|
|
282
294
|
refreshOpenAICodexToken
|
|
283
295
|
};
|
|
284
|
-
//# sourceMappingURL=oauth.js.map
|
|
@@ -2,7 +2,11 @@
|
|
|
2
2
|
import {
|
|
3
3
|
clearStore,
|
|
4
4
|
getActiveProfile,
|
|
5
|
-
|
|
5
|
+
listProfiles,
|
|
6
|
+
removeProfile,
|
|
7
|
+
saveProfile,
|
|
8
|
+
setActiveProfile,
|
|
9
|
+
updateProfile
|
|
6
10
|
} from "../store/profile-store.js";
|
|
7
11
|
import {
|
|
8
12
|
loginOpenAICodex,
|
|
@@ -12,47 +16,116 @@ class AuthService {
|
|
|
12
16
|
constructor(configService) {
|
|
13
17
|
this.configService = configService;
|
|
14
18
|
}
|
|
19
|
+
maskSecret(value) {
|
|
20
|
+
if (value.length <= 12) {
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
return `${value.slice(0, 8)}...${value.slice(-6)}`;
|
|
24
|
+
}
|
|
25
|
+
toManagedProfile(profile) {
|
|
26
|
+
return {
|
|
27
|
+
...profile,
|
|
28
|
+
mode: "oauth_account"
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
toProfileSummary(profile, activeProfileId) {
|
|
32
|
+
return {
|
|
33
|
+
provider: profile.provider,
|
|
34
|
+
profileId: profile.profileId,
|
|
35
|
+
accountId: profile.accountId,
|
|
36
|
+
email: profile.email,
|
|
37
|
+
quota: profile.quota,
|
|
38
|
+
expiresAt: profile.expires,
|
|
39
|
+
accessTokenPreview: this.maskSecret(profile.access),
|
|
40
|
+
refreshTokenPreview: this.maskSecret(profile.refresh),
|
|
41
|
+
isActive: profile.profileId === activeProfileId
|
|
42
|
+
};
|
|
43
|
+
}
|
|
15
44
|
async login(provider) {
|
|
16
45
|
if (provider !== "openai-codex") {
|
|
17
46
|
throw new Error(`\u6682\u4E0D\u652F\u6301 provider: ${provider}`);
|
|
18
47
|
}
|
|
19
48
|
const profile = await loginOpenAICodex();
|
|
20
49
|
await saveProfile(profile);
|
|
21
|
-
return
|
|
22
|
-
...profile,
|
|
23
|
-
mode: "oauth_account"
|
|
24
|
-
};
|
|
50
|
+
return this.toManagedProfile(profile);
|
|
25
51
|
}
|
|
26
52
|
async getActiveProfile(provider = "openai-codex") {
|
|
27
53
|
const profile = await getActiveProfile();
|
|
28
54
|
if (!profile || profile.provider !== provider) {
|
|
29
55
|
return null;
|
|
30
56
|
}
|
|
31
|
-
return
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
57
|
+
return this.toManagedProfile(profile);
|
|
58
|
+
}
|
|
59
|
+
async listProfiles(provider = "openai-codex") {
|
|
60
|
+
const [profiles, activeProfile] = await Promise.all([
|
|
61
|
+
listProfiles(),
|
|
62
|
+
this.getActiveProfile(provider)
|
|
63
|
+
]);
|
|
64
|
+
const activeProfileId = activeProfile?.profileId;
|
|
65
|
+
return profiles.filter((profile) => profile.provider === provider).sort((left, right) => {
|
|
66
|
+
if (left.profileId === activeProfileId) {
|
|
67
|
+
return -1;
|
|
68
|
+
}
|
|
69
|
+
if (right.profileId === activeProfileId) {
|
|
70
|
+
return 1;
|
|
71
|
+
}
|
|
72
|
+
return right.expires - left.expires;
|
|
73
|
+
}).map((profile) => this.toProfileSummary(profile, activeProfileId));
|
|
74
|
+
}
|
|
75
|
+
async activateProfile(profileId, provider = "openai-codex") {
|
|
76
|
+
const profiles = await listProfiles();
|
|
77
|
+
const target = profiles.find((profile) => profile.profileId === profileId && profile.provider === provider);
|
|
78
|
+
if (!target) {
|
|
79
|
+
throw new Error(`\u6CA1\u6709\u627E\u5230\u53EF\u5207\u6362\u7684\u8D26\u53F7: ${profileId}`);
|
|
80
|
+
}
|
|
81
|
+
const activated = await setActiveProfile(profileId);
|
|
82
|
+
if (!activated) {
|
|
83
|
+
throw new Error(`\u5207\u6362\u8D26\u53F7\u5931\u8D25: ${profileId}`);
|
|
84
|
+
}
|
|
85
|
+
return this.toManagedProfile(activated);
|
|
86
|
+
}
|
|
87
|
+
async removeProfile(profileId, provider = "openai-codex") {
|
|
88
|
+
const profiles = await listProfiles();
|
|
89
|
+
const target = profiles.find((profile) => profile.profileId === profileId && profile.provider === provider);
|
|
90
|
+
if (!target) {
|
|
91
|
+
throw new Error(`\u6CA1\u6709\u627E\u5230\u8981\u5220\u9664\u7684\u8D26\u53F7: ${profileId}`);
|
|
92
|
+
}
|
|
93
|
+
await removeProfile(profileId);
|
|
35
94
|
}
|
|
36
95
|
async requireUsableProfile(provider = "openai-codex") {
|
|
37
96
|
const profile = await this.getActiveProfile(provider);
|
|
38
97
|
if (!profile) {
|
|
39
|
-
throw new Error(`\u8FD8\u6CA1\u6709\u767B\u5F55 ${provider}\u3002\u5148\u8FD0\u884C
|
|
98
|
+
throw new Error(`\u8FD8\u6CA1\u6709\u767B\u5F55 ${provider}\u3002\u5148\u8FD0\u884C azt login`);
|
|
40
99
|
}
|
|
41
100
|
if (Date.now() < profile.expires) {
|
|
42
101
|
return profile;
|
|
43
102
|
}
|
|
44
103
|
const refreshed = await refreshOpenAICodexToken(profile);
|
|
45
104
|
await saveProfile(refreshed);
|
|
46
|
-
return
|
|
47
|
-
...refreshed,
|
|
48
|
-
mode: "oauth_account"
|
|
49
|
-
};
|
|
105
|
+
return this.toManagedProfile(refreshed);
|
|
50
106
|
}
|
|
51
107
|
async logoutAll() {
|
|
52
108
|
await clearStore();
|
|
53
109
|
}
|
|
110
|
+
async updateProfileQuota(profileId, quota, provider = "openai-codex") {
|
|
111
|
+
if (!quota) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
await updateProfile(profileId, (profile) => {
|
|
115
|
+
if (profile.provider !== provider) {
|
|
116
|
+
return profile;
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
...profile,
|
|
120
|
+
quota
|
|
121
|
+
};
|
|
122
|
+
});
|
|
123
|
+
}
|
|
54
124
|
async getStatus() {
|
|
55
|
-
const profile = await
|
|
125
|
+
const [profile, profiles] = await Promise.all([
|
|
126
|
+
this.getActiveProfile(),
|
|
127
|
+
this.listProfiles()
|
|
128
|
+
]);
|
|
56
129
|
const defaultModel = await this.configService.getDefaultModel();
|
|
57
130
|
const server = await this.configService.getServerConfig();
|
|
58
131
|
return {
|
|
@@ -62,6 +135,7 @@ class AuthService {
|
|
|
62
135
|
defaultModel,
|
|
63
136
|
loggedIn: Boolean(profile),
|
|
64
137
|
expiresAt: profile?.expires,
|
|
138
|
+
profileCount: profiles.length,
|
|
65
139
|
serverHost: server.host,
|
|
66
140
|
serverPort: server.port
|
|
67
141
|
};
|
|
@@ -70,4 +144,3 @@ class AuthService {
|
|
|
70
144
|
export {
|
|
71
145
|
AuthService
|
|
72
146
|
};
|
|
73
|
-
//# sourceMappingURL=auth-service.js.map
|
|
@@ -6,23 +6,33 @@ class ChatService {
|
|
|
6
6
|
}
|
|
7
7
|
async chat(request) {
|
|
8
8
|
const provider = request.provider ?? "openai-codex";
|
|
9
|
-
const model = await this.deps.modelService.resolveModel(provider, request.model
|
|
10
|
-
|
|
11
|
-
const result = await askOpenAICodex({
|
|
12
|
-
profile,
|
|
13
|
-
prompt: request.input,
|
|
14
|
-
model,
|
|
15
|
-
system: request.system
|
|
9
|
+
const model = await this.deps.modelService.resolveModel(provider, request.model, {
|
|
10
|
+
allowUnknown: request.experimental?.allowUnknownModel
|
|
16
11
|
});
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
12
|
+
const profile = await this.deps.authService.requireUsableProfile(provider);
|
|
13
|
+
try {
|
|
14
|
+
const result = await askOpenAICodex({
|
|
15
|
+
profile,
|
|
16
|
+
prompt: request.input,
|
|
17
|
+
model,
|
|
18
|
+
system: request.system,
|
|
19
|
+
bodyOverride: request.experimental?.codexBody
|
|
20
|
+
});
|
|
21
|
+
await this.deps.authService.updateProfileQuota(profile.profileId, result.quota, provider);
|
|
22
|
+
return {
|
|
23
|
+
provider,
|
|
24
|
+
model,
|
|
25
|
+
text: result.text,
|
|
26
|
+
raw: result.raw,
|
|
27
|
+
artifacts: result.artifacts
|
|
28
|
+
};
|
|
29
|
+
} catch (error) {
|
|
30
|
+
const quota = error.quota;
|
|
31
|
+
await this.deps.authService.updateProfileQuota(profile.profileId, quota, provider);
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
23
34
|
}
|
|
24
35
|
}
|
|
25
36
|
export {
|
|
26
37
|
ChatService
|
|
27
38
|
};
|
|
28
|
-
//# sourceMappingURL=chat-service.js.map
|
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { askOpenAICodex } from "../providers/openai-codex/chat.js";
|
|
3
|
+
const SUPPORTED_IMAGE_MODELS = /* @__PURE__ */ new Set([
|
|
4
|
+
"gpt-image-1",
|
|
5
|
+
"gpt-image-1-mini",
|
|
6
|
+
"gpt-image-1.5",
|
|
7
|
+
"gpt-image-2"
|
|
8
|
+
]);
|
|
9
|
+
const SUPPORTED_IMAGE_SIZES = /* @__PURE__ */ new Set([
|
|
10
|
+
"1024x1024",
|
|
11
|
+
"1024x1536",
|
|
12
|
+
"1536x1024"
|
|
13
|
+
]);
|
|
14
|
+
const SUPPORTED_IMAGE_QUALITIES = /* @__PURE__ */ new Set([
|
|
15
|
+
"low",
|
|
16
|
+
"medium",
|
|
17
|
+
"high"
|
|
18
|
+
]);
|
|
19
|
+
const SUPPORTED_IMAGE_FORMATS = /* @__PURE__ */ new Set([
|
|
20
|
+
"png",
|
|
21
|
+
"webp",
|
|
22
|
+
"jpeg"
|
|
23
|
+
]);
|
|
24
|
+
const SUPPORTED_IMAGE_BACKGROUNDS = /* @__PURE__ */ new Set([
|
|
25
|
+
"transparent",
|
|
26
|
+
"opaque"
|
|
27
|
+
]);
|
|
28
|
+
function truncateForLog(value, max = 160) {
|
|
29
|
+
if (value.length <= max) {
|
|
30
|
+
return value;
|
|
31
|
+
}
|
|
32
|
+
return `${value.slice(0, max)}...`;
|
|
33
|
+
}
|
|
34
|
+
function isRecord(value) {
|
|
35
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
36
|
+
}
|
|
37
|
+
function toImageGenerationOutput(value) {
|
|
38
|
+
if (!isRecord(value) || value.type !== "image_generation_call") {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
43
|
+
function toImageGenerationEventOutput(value) {
|
|
44
|
+
if (!isRecord(value) || typeof value.type !== "string") {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
if (value.type.startsWith("response.output_item.") && isRecord(value.item)) {
|
|
48
|
+
return toImageGenerationOutput(value.item);
|
|
49
|
+
}
|
|
50
|
+
if (value.type === "response.image_generation_call.partial_image") {
|
|
51
|
+
return {
|
|
52
|
+
id: typeof value.item_id === "string" ? value.item_id : void 0,
|
|
53
|
+
type: "image_generation_call",
|
|
54
|
+
partial_image_b64: typeof value.partial_image_b64 === "string" ? value.partial_image_b64 : void 0,
|
|
55
|
+
background: typeof value.background === "string" ? value.background : void 0,
|
|
56
|
+
output_format: typeof value.output_format === "string" ? value.output_format : void 0,
|
|
57
|
+
size: typeof value.size === "string" ? value.size : void 0
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
function normalizeReturnedSize(size, fallback) {
|
|
63
|
+
if (typeof size === "string" && SUPPORTED_IMAGE_SIZES.has(size)) {
|
|
64
|
+
return size;
|
|
65
|
+
}
|
|
66
|
+
if (typeof fallback === "string" && SUPPORTED_IMAGE_SIZES.has(fallback)) {
|
|
67
|
+
return fallback;
|
|
68
|
+
}
|
|
69
|
+
return void 0;
|
|
70
|
+
}
|
|
71
|
+
function normalizeReturnedQuality(quality) {
|
|
72
|
+
if (typeof quality === "string" && SUPPORTED_IMAGE_QUALITIES.has(quality)) {
|
|
73
|
+
return quality;
|
|
74
|
+
}
|
|
75
|
+
return void 0;
|
|
76
|
+
}
|
|
77
|
+
function normalizeReturnedFormat(format) {
|
|
78
|
+
if (typeof format === "string" && SUPPORTED_IMAGE_FORMATS.has(format)) {
|
|
79
|
+
return format;
|
|
80
|
+
}
|
|
81
|
+
return void 0;
|
|
82
|
+
}
|
|
83
|
+
function normalizeReturnedBackground(background) {
|
|
84
|
+
if (typeof background === "string" && SUPPORTED_IMAGE_BACKGROUNDS.has(background)) {
|
|
85
|
+
return background;
|
|
86
|
+
}
|
|
87
|
+
return void 0;
|
|
88
|
+
}
|
|
89
|
+
function collectImageGenerationOutputs(raw) {
|
|
90
|
+
if (!isRecord(raw)) {
|
|
91
|
+
return [];
|
|
92
|
+
}
|
|
93
|
+
const finalItems = /* @__PURE__ */ new Map();
|
|
94
|
+
const partialItems = /* @__PURE__ */ new Map();
|
|
95
|
+
const response = isRecord(raw.response) ? raw.response : null;
|
|
96
|
+
const events = Array.isArray(raw.events) ? raw.events : [];
|
|
97
|
+
if (response && Array.isArray(response.output)) {
|
|
98
|
+
for (const output of response.output) {
|
|
99
|
+
const image = toImageGenerationOutput(output);
|
|
100
|
+
if (!image || !image.id) {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (typeof image.result === "string" && image.result.length > 0) {
|
|
104
|
+
finalItems.set(image.id, image);
|
|
105
|
+
} else if (typeof image.partial_image_b64 === "string" && image.partial_image_b64.length > 0) {
|
|
106
|
+
partialItems.set(image.id, image);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
for (const event of events) {
|
|
111
|
+
const image = toImageGenerationEventOutput(event);
|
|
112
|
+
if (!image || !image.id) {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (typeof image.result === "string" && image.result.length > 0) {
|
|
116
|
+
finalItems.set(image.id, image);
|
|
117
|
+
} else if (typeof image.partial_image_b64 === "string" && image.partial_image_b64.length > 0) {
|
|
118
|
+
partialItems.set(image.id, image);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (finalItems.size > 0) {
|
|
122
|
+
return Array.from(finalItems.values());
|
|
123
|
+
}
|
|
124
|
+
return Array.from(partialItems.values()).map((item) => ({
|
|
125
|
+
...item,
|
|
126
|
+
result: item.partial_image_b64
|
|
127
|
+
}));
|
|
128
|
+
}
|
|
129
|
+
function summarizeImageDebug(raw) {
|
|
130
|
+
if (!isRecord(raw)) {
|
|
131
|
+
return {
|
|
132
|
+
rawType: typeof raw
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
const response = isRecord(raw.response) ? raw.response : null;
|
|
136
|
+
const events = Array.isArray(raw.events) ? raw.events : [];
|
|
137
|
+
const imageEvents = events.filter((event) => isRecord(event) && typeof event.type === "string" && event.type.includes("image_generation")).slice(0, 12).map((event) => {
|
|
138
|
+
const safeEvent = event;
|
|
139
|
+
return {
|
|
140
|
+
type: safeEvent.type,
|
|
141
|
+
item_id: typeof safeEvent.item_id === "string" ? safeEvent.item_id : void 0,
|
|
142
|
+
output_index: typeof safeEvent.output_index === "number" ? safeEvent.output_index : void 0,
|
|
143
|
+
partial_image_b64_length: typeof safeEvent.partial_image_b64 === "string" ? safeEvent.partial_image_b64.length : void 0
|
|
144
|
+
};
|
|
145
|
+
});
|
|
146
|
+
return {
|
|
147
|
+
response_status: typeof response?.status === "string" ? response.status : void 0,
|
|
148
|
+
response_error: isRecord(response?.error) ? {
|
|
149
|
+
code: typeof response.error.code === "string" ? response.error.code : void 0,
|
|
150
|
+
message: typeof response.error.message === "string" ? response.error.message : void 0,
|
|
151
|
+
type: typeof response.error.type === "string" ? response.error.type : void 0
|
|
152
|
+
} : void 0,
|
|
153
|
+
response_output_length: Array.isArray(response?.output) ? response.output.length : 0,
|
|
154
|
+
event_count: events.length,
|
|
155
|
+
event_types: events.filter((event) => isRecord(event) && typeof event.type === "string").slice(0, 20).map((event) => event.type),
|
|
156
|
+
error_events: events.filter((event) => isRecord(event) && (event.type === "error" || event.type === "response.failed")).slice(0, 5).map((event) => {
|
|
157
|
+
const safeEvent = event;
|
|
158
|
+
const eventError = isRecord(safeEvent.error) ? safeEvent.error : null;
|
|
159
|
+
const eventResponse = isRecord(safeEvent.response) ? safeEvent.response : null;
|
|
160
|
+
const responseError = eventResponse && isRecord(eventResponse.error) ? eventResponse.error : null;
|
|
161
|
+
return {
|
|
162
|
+
type: safeEvent.type,
|
|
163
|
+
code: typeof eventError?.code === "string" ? eventError.code : typeof responseError?.code === "string" ? responseError.code : void 0,
|
|
164
|
+
message: typeof eventError?.message === "string" ? eventError.message : typeof responseError?.message === "string" ? responseError.message : void 0
|
|
165
|
+
};
|
|
166
|
+
}),
|
|
167
|
+
image_events: imageEvents
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
function extractImageFailureMessage(raw) {
|
|
171
|
+
if (!isRecord(raw)) {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
const response = isRecord(raw.response) ? raw.response : null;
|
|
175
|
+
if (response) {
|
|
176
|
+
const responseError = isRecord(response.error) ? response.error : null;
|
|
177
|
+
const responseStatus = typeof response.status === "string" ? response.status : void 0;
|
|
178
|
+
const responseMessage = typeof responseError?.message === "string" ? responseError.message : typeof responseError?.code === "string" ? responseError.code : null;
|
|
179
|
+
if (responseStatus === "failed" && responseMessage) {
|
|
180
|
+
return responseMessage;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
const events = Array.isArray(raw.events) ? raw.events : [];
|
|
184
|
+
for (const event of events) {
|
|
185
|
+
if (!isRecord(event)) {
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
if (event.type === "error") {
|
|
189
|
+
const eventError = isRecord(event.error) ? event.error : event;
|
|
190
|
+
const message = typeof eventError.message === "string" ? eventError.message : typeof eventError.code === "string" ? eventError.code : null;
|
|
191
|
+
if (message) {
|
|
192
|
+
return message;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if (event.type === "response.failed" && isRecord(event.response)) {
|
|
196
|
+
const responseError = isRecord(event.response.error) ? event.response.error : null;
|
|
197
|
+
const message = typeof responseError?.message === "string" ? responseError.message : typeof responseError?.code === "string" ? responseError.code : null;
|
|
198
|
+
if (message) {
|
|
199
|
+
return message;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
function extractImageUsage(raw) {
|
|
206
|
+
if (!isRecord(raw) || !isRecord(raw.response)) {
|
|
207
|
+
return void 0;
|
|
208
|
+
}
|
|
209
|
+
const toolUsage = isRecord(raw.response.tool_usage) ? raw.response.tool_usage : null;
|
|
210
|
+
const imageGen = toolUsage && isRecord(toolUsage.image_gen) ? toolUsage.image_gen : null;
|
|
211
|
+
if (!imageGen || typeof imageGen.input_tokens !== "number" || typeof imageGen.output_tokens !== "number" || typeof imageGen.total_tokens !== "number") {
|
|
212
|
+
return void 0;
|
|
213
|
+
}
|
|
214
|
+
return {
|
|
215
|
+
input_tokens: imageGen.input_tokens,
|
|
216
|
+
input_tokens_details: isRecord(imageGen.input_tokens_details) ? {
|
|
217
|
+
image_tokens: Number(imageGen.input_tokens_details.image_tokens ?? 0),
|
|
218
|
+
text_tokens: Number(imageGen.input_tokens_details.text_tokens ?? 0)
|
|
219
|
+
} : void 0,
|
|
220
|
+
output_tokens: imageGen.output_tokens,
|
|
221
|
+
output_tokens_details: isRecord(imageGen.output_tokens_details) ? {
|
|
222
|
+
image_tokens: Number(imageGen.output_tokens_details.image_tokens ?? 0),
|
|
223
|
+
text_tokens: Number(imageGen.output_tokens_details.text_tokens ?? 0)
|
|
224
|
+
} : void 0,
|
|
225
|
+
total_tokens: imageGen.total_tokens
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
class ImageService {
|
|
229
|
+
constructor(deps) {
|
|
230
|
+
this.deps = deps;
|
|
231
|
+
}
|
|
232
|
+
resolveRequestedImageModel(model) {
|
|
233
|
+
if (!model) {
|
|
234
|
+
return "gpt-image-2";
|
|
235
|
+
}
|
|
236
|
+
if (!SUPPORTED_IMAGE_MODELS.has(model)) {
|
|
237
|
+
throw new Error(`\u5F53\u524D\u7F51\u5173\u4EC5\u652F\u6301\u8FD9\u4E9B\u751F\u56FE\u6A21\u578B: ${Array.from(SUPPORTED_IMAGE_MODELS).join(", ")}`);
|
|
238
|
+
}
|
|
239
|
+
return model;
|
|
240
|
+
}
|
|
241
|
+
isFreePlan(profile) {
|
|
242
|
+
return profile.quota?.planType === "free";
|
|
243
|
+
}
|
|
244
|
+
async generate(request) {
|
|
245
|
+
const profile = await this.deps.authService.requireUsableProfile("openai-codex");
|
|
246
|
+
if (this.isFreePlan(profile)) {
|
|
247
|
+
throw new Error("\u5F53\u524D\u8D26\u53F7\u4E3A free \u5957\u9910\uFF0C\u4E0D\u652F\u6301\u56FE\u7247\u751F\u6210\u3002\u8BF7\u5207\u6362\u5230 Plus \u6216\u66F4\u9AD8\u5957\u9910\u8D26\u53F7\u3002");
|
|
248
|
+
}
|
|
249
|
+
const orchestratorModel = await this.deps.configService.getDefaultModel();
|
|
250
|
+
const requestedImageModel = this.resolveRequestedImageModel(request.model);
|
|
251
|
+
const requestSummary = {
|
|
252
|
+
requestedImageModel,
|
|
253
|
+
orchestratorModel,
|
|
254
|
+
promptLength: request.prompt.length,
|
|
255
|
+
promptPreview: truncateForLog(request.prompt),
|
|
256
|
+
size: request.size ?? "default",
|
|
257
|
+
quality: request.quality ?? "default",
|
|
258
|
+
background: request.background ?? "default",
|
|
259
|
+
outputFormat: request.outputFormat ?? "default",
|
|
260
|
+
outputCompression: typeof request.outputCompression === "number" ? request.outputCompression : void 0,
|
|
261
|
+
moderation: request.moderation ?? "default"
|
|
262
|
+
};
|
|
263
|
+
console.info("[gateway:image] upstream request", requestSummary);
|
|
264
|
+
const tool = {
|
|
265
|
+
type: "image_generation",
|
|
266
|
+
model: requestedImageModel
|
|
267
|
+
};
|
|
268
|
+
if (request.size) {
|
|
269
|
+
tool.size = request.size;
|
|
270
|
+
}
|
|
271
|
+
if (request.quality) {
|
|
272
|
+
tool.quality = request.quality;
|
|
273
|
+
}
|
|
274
|
+
if (request.background) {
|
|
275
|
+
tool.background = request.background;
|
|
276
|
+
}
|
|
277
|
+
if (request.outputFormat) {
|
|
278
|
+
tool.output_format = request.outputFormat;
|
|
279
|
+
}
|
|
280
|
+
if (typeof request.outputCompression === "number") {
|
|
281
|
+
tool.output_compression = request.outputCompression;
|
|
282
|
+
}
|
|
283
|
+
if (request.moderation) {
|
|
284
|
+
tool.moderation = request.moderation;
|
|
285
|
+
}
|
|
286
|
+
let result;
|
|
287
|
+
try {
|
|
288
|
+
result = await askOpenAICodex({
|
|
289
|
+
profile,
|
|
290
|
+
model: orchestratorModel,
|
|
291
|
+
bodyOverride: {
|
|
292
|
+
model: orchestratorModel,
|
|
293
|
+
input: [
|
|
294
|
+
{
|
|
295
|
+
role: "user",
|
|
296
|
+
content: [
|
|
297
|
+
{
|
|
298
|
+
type: "input_text",
|
|
299
|
+
text: request.prompt
|
|
300
|
+
}
|
|
301
|
+
]
|
|
302
|
+
}
|
|
303
|
+
],
|
|
304
|
+
tools: [tool],
|
|
305
|
+
tool_choice: {
|
|
306
|
+
type: "image_generation"
|
|
307
|
+
},
|
|
308
|
+
include: ["reasoning.encrypted_content"]
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
await this.deps.authService.updateProfileQuota(profile.profileId, result.quota, "openai-codex");
|
|
312
|
+
} catch (error) {
|
|
313
|
+
const quota = error.quota;
|
|
314
|
+
await this.deps.authService.updateProfileQuota(profile.profileId, quota, "openai-codex");
|
|
315
|
+
throw error;
|
|
316
|
+
}
|
|
317
|
+
const raw = isRecord(result.raw) ? result.raw : {};
|
|
318
|
+
const response = isRecord(raw.response) ? raw.response : null;
|
|
319
|
+
const images = collectImageGenerationOutputs(raw);
|
|
320
|
+
const debugSummary = summarizeImageDebug(raw);
|
|
321
|
+
if (images.length === 0) {
|
|
322
|
+
const upstreamFailure = extractImageFailureMessage(raw);
|
|
323
|
+
console.error("[gateway:image] parse failure", {
|
|
324
|
+
...requestSummary,
|
|
325
|
+
upstreamFailure,
|
|
326
|
+
debug: debugSummary
|
|
327
|
+
});
|
|
328
|
+
if (upstreamFailure) {
|
|
329
|
+
throw new Error(`\u4E0A\u6E38\u56FE\u7247\u751F\u6210\u5931\u8D25: ${upstreamFailure}`);
|
|
330
|
+
}
|
|
331
|
+
throw new Error("\u56FE\u7247\u751F\u6210\u8BF7\u6C42\u5DF2\u5B8C\u6210\uFF0C\u4F46\u6CA1\u6709\u89E3\u6790\u51FA image_generation_call \u7ED3\u679C\u3002");
|
|
332
|
+
}
|
|
333
|
+
const first = images[0];
|
|
334
|
+
const imageResult = {
|
|
335
|
+
created: typeof response?.created_at === "number" ? response.created_at : Math.floor(Date.now() / 1e3),
|
|
336
|
+
data: images.map((image) => ({
|
|
337
|
+
b64_json: image.result ?? "",
|
|
338
|
+
...image.revised_prompt ? { revised_prompt: image.revised_prompt } : {}
|
|
339
|
+
})),
|
|
340
|
+
background: normalizeReturnedBackground(first.background),
|
|
341
|
+
output_format: normalizeReturnedFormat(first.output_format),
|
|
342
|
+
quality: normalizeReturnedQuality(first.quality),
|
|
343
|
+
size: normalizeReturnedSize(first.size, request.size),
|
|
344
|
+
usage: extractImageUsage(raw)
|
|
345
|
+
};
|
|
346
|
+
console.info("[gateway:image] upstream response", {
|
|
347
|
+
...requestSummary,
|
|
348
|
+
imageCount: imageResult.data.length,
|
|
349
|
+
firstImageBase64Length: imageResult.data[0]?.b64_json.length ?? 0,
|
|
350
|
+
outputFormat: imageResult.output_format ?? request.outputFormat ?? "unknown",
|
|
351
|
+
quality: imageResult.quality ?? request.quality ?? "unknown",
|
|
352
|
+
size: imageResult.size ?? request.size ?? "unknown",
|
|
353
|
+
debug: debugSummary
|
|
354
|
+
});
|
|
355
|
+
return imageResult;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
export {
|
|
359
|
+
ImageService
|
|
360
|
+
};
|
|
@@ -23,13 +23,16 @@ class ModelService {
|
|
|
23
23
|
}
|
|
24
24
|
return this.configService.getDefaultModel(provider);
|
|
25
25
|
}
|
|
26
|
-
async resolveModel(provider = "openai-codex", requested) {
|
|
26
|
+
async resolveModel(provider = "openai-codex", requested, options) {
|
|
27
27
|
if (provider !== "openai-codex") {
|
|
28
28
|
throw new Error(`\u6682\u4E0D\u652F\u6301 provider: ${provider}`);
|
|
29
29
|
}
|
|
30
30
|
if (!requested) {
|
|
31
31
|
return this.configService.getDefaultModel(provider);
|
|
32
32
|
}
|
|
33
|
+
if (options?.allowUnknown) {
|
|
34
|
+
return requested;
|
|
35
|
+
}
|
|
33
36
|
if (!isSupportedCodexModel(requested)) {
|
|
34
37
|
throw new Error(`\u5F53\u524D demo \u672A\u5185\u7F6E\u6A21\u578B: ${requested}`);
|
|
35
38
|
}
|
|
@@ -39,4 +42,3 @@ class ModelService {
|
|
|
39
42
|
export {
|
|
40
43
|
ModelService
|
|
41
44
|
};
|
|
42
|
-
//# sourceMappingURL=model-service.js.map
|